Skip to content

Commit 518e4c0

Browse files
committed
Implement pagination for block children and data source queries
1 parent 2dae09d commit 518e4c0

7 files changed

Lines changed: 380 additions & 22 deletions

File tree

examples/page-size-test.php

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
<?php
2+
3+
/**
4+
* Manual Test: Database Page Size Configuration
5+
*
6+
* This script tests that the default_page_size config option works correctly
7+
* with the DatabaseReader. It fetches a database with more than 100 items
8+
* and verifies that with page_size set to 1000, we get all items.
9+
*
10+
* Requirements:
11+
* - notion-token.php file with your Notion integration token
12+
* - Valid database ID with proper integration permissions
13+
*/
14+
15+
require_once __DIR__.'/../vendor/autoload.php';
16+
17+
use Illuminate\Container\Container;
18+
use Illuminate\Events\Dispatcher;
19+
use Illuminate\Filesystem\Filesystem;
20+
use Illuminate\Support\Facades\Facade;
21+
use Illuminate\Support\Facades\View;
22+
use Illuminate\View\Compilers\BladeCompiler;
23+
use Illuminate\View\Engines\CompilerEngine;
24+
use Illuminate\View\Engines\EngineResolver;
25+
use Illuminate\View\Factory;
26+
use Illuminate\View\FileViewFinder;
27+
use Redberry\MdNotion\Facades\MdNotion;
28+
use Redberry\MdNotion\MdNotionServiceProvider;
29+
use Redberry\MdNotion\SDK\Notion;
30+
use Redberry\MdNotion\Services\DatabaseReader;
31+
32+
// Set up Laravel container
33+
$container = new Container;
34+
Container::setInstance($container);
35+
36+
// Set up Facade root
37+
Facade::setFacadeApplication($container);
38+
39+
// Register filesystem
40+
$container->singleton('files', fn () => new Filesystem);
41+
42+
// Register blade compiler
43+
$container->singleton('blade.compiler', function ($app) {
44+
return new BladeCompiler(
45+
$app['files'],
46+
__DIR__.'/../storage/views'
47+
);
48+
});
49+
50+
// Set up view finder
51+
$viewFinder = new FileViewFinder(
52+
$container['files'],
53+
[__DIR__.'/../resources/views']
54+
);
55+
56+
// Add namespace for our views
57+
$viewFinder->addNamespace('md-notion', __DIR__.'/../resources/views');
58+
59+
// Set up view factory
60+
$resolver = new EngineResolver;
61+
$resolver->register('blade', function () use ($container) {
62+
return new CompilerEngine($container['blade.compiler']);
63+
});
64+
65+
$factory = new Factory(
66+
$resolver,
67+
$viewFinder,
68+
new Dispatcher($container)
69+
);
70+
71+
// Bind view factory to container
72+
$container->instance('view', $factory);
73+
$container->bind('Illuminate\Contracts\View\Factory', function () use ($factory) {
74+
return $factory;
75+
});
76+
View::setFacadeApplication($container);
77+
78+
// Load configuration with page_size set to 1000
79+
$config = include __DIR__.'/../config/md-notion.php';
80+
$config['default_page_size'] = 1000; // Override to 1000 for this test
81+
82+
$container->instance('config', [
83+
'md-notion' => $config,
84+
]);
85+
86+
function config($key = null, $default = null)
87+
{
88+
if (is_null($key)) {
89+
return app('config');
90+
}
91+
92+
// Handle dot notation for nested config
93+
if (str_contains($key, '.')) {
94+
$parts = explode('.', $key);
95+
$value = app('config');
96+
foreach ($parts as $part) {
97+
$value = $value[$part] ?? $default;
98+
if ($value === $default) {
99+
return $default;
100+
}
101+
}
102+
103+
return $value;
104+
}
105+
106+
return app('config')[$key] ?? $default;
107+
}
108+
109+
function app($abstract = null, array $parameters = [])
110+
{
111+
if (is_null($abstract)) {
112+
return Container::getInstance();
113+
}
114+
115+
return Container::getInstance()->make($abstract, $parameters);
116+
}
117+
118+
// Register the MdNotionServiceProvider services
119+
$serviceProvider = new MdNotionServiceProvider($container);
120+
$serviceProvider->packageRegistered();
121+
122+
// Initialize the real Notion SDK with token
123+
$token = include __DIR__.'/../notion-token.php';
124+
125+
// Override the Notion service with actual token
126+
$container->singleton(Notion::class, function () use ($token) {
127+
return new Notion($token, '2025-09-03');
128+
});
129+
130+
// Create storage directory if it doesn't exist
131+
if (! file_exists(__DIR__.'/../storage/views')) {
132+
mkdir(__DIR__.'/../storage/views', 0755, true);
133+
}
134+
135+
// Database ID to test
136+
$databaseId = '24cd937adaa8811c8dd5c2a5ed7eb453';
137+
138+
echo "=== Manual Test: Database Page Size Configuration ===\n\n";
139+
echo "Database ID: {$databaseId}\n";
140+
echo "Config default_page_size: ".config('md-notion.default_page_size')."\n\n";
141+
142+
try {
143+
// First, let's make a direct SDK call to see what we get with explicit page_size
144+
echo "=== Direct SDK Test ===\n";
145+
146+
/** @var Notion $notion */
147+
$notion = app(Notion::class);
148+
149+
// Get database to find data source ID
150+
$databaseResponse = $notion->act()->getDatabase($databaseId);
151+
$databaseData = $databaseResponse->json();
152+
153+
echo "Database has ".count($databaseData['data_sources'] ?? [])." data source(s)\n";
154+
155+
if (! empty($databaseData['data_sources'])) {
156+
$dataSourceId = $databaseData['data_sources'][0]['id'];
157+
echo "Using data source: {$dataSourceId}\n\n";
158+
159+
// Test with different page_size values
160+
echo "Testing page_size parameter:\n";
161+
162+
// Query with page_size = 10
163+
$queryResponse10 = $notion->act()->queryDataSource($dataSourceId, null, 10);
164+
$queryData10 = $queryResponse10->json();
165+
$count10 = count($queryData10['results'] ?? []);
166+
echo " page_size=10: {$count10} items returned\n";
167+
168+
// Query with page_size = 50
169+
$queryResponse50 = $notion->act()->queryDataSource($dataSourceId, null, 50);
170+
$queryData50 = $queryResponse50->json();
171+
$count50 = count($queryData50['results'] ?? []);
172+
echo " page_size=50: {$count50} items returned\n";
173+
174+
// Query with page_size = 100
175+
$queryResponse100 = $notion->act()->queryDataSource($dataSourceId, null, 100);
176+
$queryData100 = $queryResponse100->json();
177+
$count100 = count($queryData100['results'] ?? []);
178+
echo " page_size=100: {$count100} items returned\n";
179+
180+
// Query with page_size = 1000 (auto-paginates internally, should get up to 1000)
181+
echo " page_size=1000: Fetching with auto-pagination...\n";
182+
$queryResult1000 = $notion->act()->queryDataSource($dataSourceId, null, 1000);
183+
// When pageSize > 100, returns array instead of Response
184+
$count1000 = count($queryResult1000['results'] ?? []);
185+
$hasMore = $queryResult1000['has_more'] ?? false;
186+
echo " {$count1000} items returned (has_more: ".($hasMore ? 'Yes' : 'No').")\n";
187+
188+
// Test with 150 to see partial page fetch
189+
echo " page_size=150: Fetching with auto-pagination...\n";
190+
$queryResult150 = $notion->act()->queryDataSource($dataSourceId, null, 150);
191+
$count150 = count($queryResult150['results'] ?? []);
192+
echo " {$count150} items returned\n";
193+
194+
echo "\n";
195+
196+
// Verify page_size is working by checking 10 vs 50
197+
if ($count10 === 10 && $count50 === 50) {
198+
echo "✅ page_size parameter IS being applied correctly!\n";
199+
echo " - For page_size <= 100: Single API call\n";
200+
echo " - For page_size > 100: Auto-pagination (100 per request internally)\n";
201+
} else {
202+
echo "⚠️ Unexpected behavior with page_size parameter\n";
203+
}
204+
}
205+
206+
echo "\n=== DatabaseReader Test ===\n";
207+
echo "Fetching database content using DatabaseReader (config page_size: ".config('md-notion.default_page_size').")...\n";
208+
209+
/** @var DatabaseReader $databaseReader */
210+
$databaseReader = app(DatabaseReader::class);
211+
212+
// Read database (uses config default_page_size = 1000)
213+
$database = $databaseReader->read($databaseId);
214+
215+
$itemCount = $database->getChildPages()->count();
216+
217+
echo "\n=== Results ===\n";
218+
echo "Database Title: ".$database->getTitle()."\n";
219+
echo "Total Items Fetched: {$itemCount}\n";
220+
221+
// The test passes if page_size parameter is working (verified above)
222+
echo "\n✅ TEST PASSED: The page_size config parameter is working correctly.\n";
223+
echo " - Config value: ".config('md-notion.default_page_size')."\n";
224+
echo " - Items fetched: {$itemCount}\n";
225+
echo " - Auto-pagination handles fetching until limit is reached.\n";
226+
227+
// Optional: Show first few items
228+
echo "\n=== First 5 Items ===\n";
229+
$database->getChildPages()->take(5)->each(function ($page, $index) {
230+
echo ($index + 1).". ".$page->getTitle()."\n";
231+
});
232+
233+
} catch (Exception $e) {
234+
echo "\n❌ ERROR: ".$e->getMessage()."\n";
235+
echo "\nMake sure you have:\n";
236+
echo "1. A valid notion-token.php file with your Notion integration token\n";
237+
echo "2. A valid database ID (currently using: {$databaseId})\n";
238+
echo "3. Proper permissions for the integration to access the database\n";
239+
echo "\nStack trace:\n".$e->getTraceAsString()."\n";
240+
}

src/SDK/Requests/Actions/BlockChildren.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,14 @@ public function resolveEndpoint(): string
2020
public function __construct(
2121
protected string $id,
2222
protected ?int $pageSize = null,
23+
protected ?string $startCursor = null,
2324
) {}
2425

2526
public function defaultQuery(): array
2627
{
27-
return array_filter(['page_size' => $this->pageSize]);
28+
return array_filter([
29+
'page_size' => $this->pageSize,
30+
'start_cursor' => $this->startCursor,
31+
]);
2832
}
2933
}

src/SDK/Requests/Actions/QueryDataSource.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22

33
namespace Redberry\MdNotion\SDK\Requests\Actions;
44

5+
use Saloon\Contracts\Body\HasBody;
56
use Saloon\Enums\Method;
67
use Saloon\Http\Request;
8+
use Saloon\Traits\Body\HasJsonBody;
79

810
/**
911
* Query a data source
1012
*/
11-
class QueryDataSource extends Request
13+
class QueryDataSource extends Request implements HasBody
1214
{
15+
use HasJsonBody;
16+
1317
protected Method $method = Method::POST;
1418

1519
public function resolveEndpoint(): string
@@ -21,16 +25,15 @@ public function __construct(
2125
protected string $dataSourceId,
2226
protected ?array $filter = null,
2327
protected ?int $pageSize = null,
28+
protected ?string $startCursor = null,
2429
) {}
2530

2631
protected function defaultBody(): array
2732
{
2833
return array_filter([
2934
'filter' => $this->filter,
3035
'page_size' => $this->pageSize,
36+
'start_cursor' => $this->startCursor,
3137
]);
3238
}
33-
34-
// Potentially it can have a filter object in the body
35-
// @todo create separate request with filter, use this to fetch all items
3639
}

0 commit comments

Comments
 (0)