From a7e35a6fa9403153c557264d8ba96757ed7842fb Mon Sep 17 00:00:00 2001 From: pahomov_aa Date: Thu, 29 Oct 2026 17:12:37 +0300 Subject: [PATCH] Fix knowledge base cursor pagination --- doc/usage/knowledge-base.md | 7 ++-- .../KnowledgeBaseDocumentsRequest.php | 17 ++++++++-- .../KnowledgeBaseDocumentsResponse.php | 4 +-- tests/src/ResourceGroup/KnowledgeBaseTest.php | 33 ++++++++++++++++--- 4 files changed, 50 insertions(+), 11 deletions(-) diff --git a/doc/usage/knowledge-base.md b/doc/usage/knowledge-base.md index be2feade..d2441fd7 100644 --- a/doc/usage/knowledge-base.md +++ b/doc/usage/knowledge-base.md @@ -45,12 +45,15 @@ $edit->document = ['externalId' => null]; $client->knowledgeBase->documentsEdit($id, $edit); ``` -Sections are returned without pagination. Documents use `page`, `limit` and a +Sections are returned without pagination. Documents use `cursor`, `limit` and a `KnowledgeBaseDocumentFilter`: `ids`, `externalIds`, `sectionId`, `directOnly`, `availableForCopilot`, `hasFile`, `search`. A section filter includes subsections unless `directOnly` is true. With `directOnly = true` and no section, only root documents are selected. -`documentsHistory()` accepts `KnowledgeBaseDocumentsHistoryRequest` and +Pass `$response->pagination->nextCursor` as `$request->cursor` for the next batch; +an empty cursor ends the list. + +`documentsHistory()` uses `page` and accepts `KnowledgeBaseDocumentsHistoryRequest` and `KnowledgeBaseDocumentHistoryFilter` (`sinceId`, `documentId`, `startDate`, `endDate`). Store the last history ID to retrieve subsequent changes. Content changes carry hashes, not document text; fetch the document again to read the current content. Sections have no history endpoint. diff --git a/src/Model/Request/KnowledgeBase/KnowledgeBaseDocumentsRequest.php b/src/Model/Request/KnowledgeBase/KnowledgeBaseDocumentsRequest.php index 06adcf15..5392d4d2 100644 --- a/src/Model/Request/KnowledgeBase/KnowledgeBaseDocumentsRequest.php +++ b/src/Model/Request/KnowledgeBase/KnowledgeBaseDocumentsRequest.php @@ -11,7 +11,6 @@ use RetailCrm\Api\Component\FormData\Mapping as Form; use RetailCrm\Api\Interfaces\RequestInterface; -use RetailCrm\Api\Model\Request\Traits\PageLimitTrait; /** * Class KnowledgeBaseDocumentsRequest @@ -21,7 +20,21 @@ */ class KnowledgeBaseDocumentsRequest implements RequestInterface { - use PageLimitTrait; + /** + * @var int + * + * @Form\Type("int") + * @Form\SerializedName("limit") + */ + public $limit; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("cursor") + */ + public $cursor; /** * @var \RetailCrm\Api\Model\Filter\KnowledgeBase\KnowledgeBaseDocumentFilter diff --git a/src/Model/Response/KnowledgeBase/KnowledgeBaseDocumentsResponse.php b/src/Model/Response/KnowledgeBase/KnowledgeBaseDocumentsResponse.php index fd048030..14dd8efe 100644 --- a/src/Model/Response/KnowledgeBase/KnowledgeBaseDocumentsResponse.php +++ b/src/Model/Response/KnowledgeBase/KnowledgeBaseDocumentsResponse.php @@ -10,7 +10,7 @@ namespace RetailCrm\Api\Model\Response\KnowledgeBase; use RetailCrm\Api\Component\Serializer\Annotation as JMS; -use RetailCrm\Api\Model\Response\AbstractPaginatedResponse; +use RetailCrm\Api\Model\Response\AbstractCursorPaginatedResponse; /** * Class KnowledgeBaseDocumentsResponse @@ -18,7 +18,7 @@ * @category KnowledgeBaseDocumentsResponse * @package RetailCrm\Api\Model\Response\KnowledgeBase */ -class KnowledgeBaseDocumentsResponse extends AbstractPaginatedResponse +class KnowledgeBaseDocumentsResponse extends AbstractCursorPaginatedResponse { /** * @var \RetailCrm\Api\Model\Entity\KnowledgeBase\KnowledgeBaseDocument[] diff --git a/tests/src/ResourceGroup/KnowledgeBaseTest.php b/tests/src/ResourceGroup/KnowledgeBaseTest.php index aeac0881..c2f0dbe3 100644 --- a/tests/src/ResourceGroup/KnowledgeBaseTest.php +++ b/tests/src/ResourceGroup/KnowledgeBaseTest.php @@ -46,7 +46,7 @@ public function testDocuments(): void { $request = new KnowledgeBaseDocumentsRequest(); $request->limit = 20; - $request->page = 1; + $request->cursor = 'opaque+/='; $request->filter = new KnowledgeBaseDocumentFilter(); $request->filter->ids = [7, 8]; $request->filter->externalIds = ['doc-7']; @@ -55,13 +55,13 @@ public function testDocuments(): void $request->filter->hasFile = false; $request->filter->availableForCopilot = false; $request->filter->search = 'Shipping'; - $json = '{"success":true,"pagination":{"limit":20,"currentPage":1,"totalCount":1,"totalPageCount":1},' + $json = '{"success":true,"pagination":{"nextCursor":"next+/="},' . '"documents":[{"id":7,"sectionId":1,"title":"Shipping","content":"# Shipping",' . '"externalId":"doc-7","availableForCopilot":false,"contentHash":"abc",' . '"createdAt":"2026-10-26 10:00:00","updatedAt":"2026-10-26 11:00:00"}]}'; $mock = self::createApiMockBuilder('knowledge-base/documents'); $mock->matchMethod('GET')->matchQuery([ - 'limit' => '20', 'page' => '1', + 'limit' => '20', 'cursor' => 'opaque+/=', 'filter' => ['ids' => ['7', '8'], 'externalIds' => ['doc-7'], 'sectionId' => '1', 'directOnly' => '1', 'availableForCopilot' => '0', 'hasFile' => '0', 'search' => 'Shipping'], ])->reply(200)->withBody($json); @@ -69,10 +69,33 @@ public function testDocuments(): void self::assertModelEqualsToResponse($json, $client->knowledgeBase->documents($request)); } + public function testDocumentsCursorPagination(): void + { + $request = new KnowledgeBaseDocumentsRequest(); + $request->limit = 20; + $ids = []; + foreach (['next+/=', ''] as $index => $nextCursor) { + $query = ['limit' => '20']; + if ($request->cursor !== null) { + $query['cursor'] = $request->cursor; + } + $json = json_encode(['success' => true, 'documents' => [['id' => 7 + $index]], + 'pagination' => ['nextCursor' => $nextCursor]], JSON_THROW_ON_ERROR); + $mock = self::createApiMockBuilder('knowledge-base/documents'); + $mock->matchMethod('GET')->matchQuery($query)->reply(200)->withBody($json); + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->knowledgeBase->documents($request); + $ids[] = $response->documents[0]->id; + $request->cursor = $response->pagination->nextCursor; + self::assertSame($nextCursor, $request->cursor); + } + self::assertSame([7, 8], $ids); + self::assertSame('', $request->cursor); + } + public function testGetDocumentById(): void { - $request = new KnowledgeBaseDocumentByRequest(); - $request->by = 'id'; + $request = new KnowledgeBaseDocumentByRequest('id'); $json = '{"success":true,"document":{"id":7,"title":"Shipping","availableForCopilot":false,' . '"file":{"id":9,"name":"manual.txt","type":"text/plain","size":12,"url":"https://example.com/file"}}}'; $mock = self::createApiMockBuilder('knowledge-base/documents/7');