Skip to content

Commit 967ce7b

Browse files
committed
fix(CROSS-9856): Use another endpoint to request child pages to avoid limit
1 parent 38c97a7 commit 967ce7b

File tree

11 files changed

+195
-39
lines changed

11 files changed

+195
-39
lines changed

components/confluence-sync/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
#### Deprecated
1212
#### Removed
1313

14+
## [2.0.2] - 2025-07-10
15+
16+
### Fixed
17+
18+
* fix: Fix issue when a page has more than 25 children. There was an error when trying to update a children page that had 25 brothers. The API was not returning it as a child of the parent page, so the library was trying to create it as a new page instead of updating it. Now, we use another API call to get all children pages.
19+
1420
## [2.0.1] - 2025-04-15
1521

1622
### Changed

components/confluence-sync/mocks.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
/** @type {import('@mocks-server/core').Configuration} */
99

1010
module.exports = {
11+
// log: "debug",
1112
mock: {
1213
collections: {
1314
// Selected collection

components/confluence-sync/mocks/collections.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const collection: CollectionDefinition[] = [
1313
from: "base",
1414
routes: [
1515
"confluence-get-page:empty-root",
16+
"confluence-get-page-children:empty-root",
1617
"confluence-create-page:empty-root",
1718
// "confluence-update-page:success",
1819
// "confluence-delete-page:success",
@@ -23,6 +24,7 @@ const collection: CollectionDefinition[] = [
2324
from: "base",
2425
routes: [
2526
"confluence-get-page:default-root",
27+
"confluence-get-page-children:default-root",
2628
"confluence-create-page:default-root",
2729
"confluence-update-page:default-root",
2830
"confluence-delete-page:default-root",
@@ -35,6 +37,7 @@ const collection: CollectionDefinition[] = [
3537
from: "base",
3638
routes: [
3739
"confluence-get-page:hierarchical-empty-root",
40+
"confluence-get-page-children:hierarchical-empty-root",
3841
"confluence-create-page:hierarchical-empty-root",
3942
// "confluence-update-page:hierarchical-empty-root",
4043
// "confluence-delete-page:hierarchical-empty-root",
@@ -45,6 +48,7 @@ const collection: CollectionDefinition[] = [
4548
from: "base",
4649
routes: [
4750
"confluence-get-page:hierarchical-default-root",
51+
"confluence-get-page-children:hierarchical-default-root",
4852
"confluence-create-page:hierarchical-default-root",
4953
"confluence-update-page:hierarchical-default-root",
5054
"confluence-delete-page:hierarchical-default-root",
@@ -55,6 +59,7 @@ const collection: CollectionDefinition[] = [
5559
from: "base",
5660
routes: [
5761
"confluence-get-page:flat-mode",
62+
"confluence-get-page-children:flat-mode",
5863
"confluence-create-page:flat-mode",
5964
"confluence-update-page:flat-mode",
6065
"confluence-delete-page:flat-mode",
@@ -66,6 +71,7 @@ const collection: CollectionDefinition[] = [
6671
from: "base",
6772
routes: [
6873
"confluence-get-page:renamed-page",
74+
"confluence-get-page-children:renamed-page",
6975
"confluence-create-page:renamed-page",
7076
"confluence-delete-page:renamed-page",
7177
"confluence-get-attachments:renamed-page",

components/confluence-sync/mocks/routes/Confluence.ts

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ function getPageMiddleware(pages) {
5353
content: "",
5454
version: { number: 1 },
5555
ancestors: page.ancestors,
56-
children: page.children,
5756
};
5857
core.logger.info(`Sending page ${JSON.stringify(pageData)}`);
5958
res.status(200).json(pageData);
@@ -66,6 +65,34 @@ function getPageMiddleware(pages) {
6665
};
6766
}
6867

68+
function getPageChildrenMiddleware(pages) {
69+
return (
70+
req: ServerRequest,
71+
res: ServerResponse,
72+
_next: NextFunction,
73+
core: ScopedCoreInterface,
74+
) => {
75+
core.logger.info(
76+
`Requested page with id ${req.params.pageId} to Confluence`,
77+
);
78+
79+
addRequest("confluence-get-page-children", req);
80+
const page = pages.find(
81+
(pageCandidate) => pageCandidate.id === req.params.pageId,
82+
);
83+
if (page) {
84+
const pageData = page.children;
85+
core.logger.info(`Sending page children ${JSON.stringify(pageData)}`);
86+
res.status(200).json(pageData);
87+
} else {
88+
core.logger.error(
89+
`Page with id ${req.params.pageId} not found in Confluence`,
90+
);
91+
res.status(404).send();
92+
}
93+
};
94+
}
95+
6996
function createPageMiddleware(pages) {
7097
return (
7198
req: ServerRequest,
@@ -272,6 +299,57 @@ const confluenceRoutes: RouteDefinition[] = [
272299
},
273300
],
274301
},
302+
{
303+
id: "confluence-get-page-children",
304+
url: "/rest/api/content/:pageId/child",
305+
method: "GET",
306+
variants: [
307+
{
308+
id: "empty-root",
309+
type: "middleware",
310+
options: {
311+
middleware: getPageChildrenMiddleware(PAGES_EMPTY_ROOT),
312+
},
313+
},
314+
{
315+
id: "default-root",
316+
type: "middleware",
317+
options: {
318+
middleware: getPageChildrenMiddleware(PAGES_DEFAULT_ROOT_GET),
319+
},
320+
},
321+
{
322+
id: "hierarchical-empty-root",
323+
type: "middleware",
324+
options: {
325+
middleware: getPageChildrenMiddleware(PAGES_EMPTY_ROOT_HIERARCHICAL),
326+
},
327+
},
328+
{
329+
id: "hierarchical-default-root",
330+
type: "middleware",
331+
options: {
332+
middleware: getPageChildrenMiddleware(
333+
PAGES_DEFAULT_ROOT_GET_HIERARCHICAL,
334+
),
335+
},
336+
},
337+
{
338+
id: "flat-mode",
339+
type: "middleware",
340+
options: {
341+
middleware: getPageChildrenMiddleware(PAGES_FLAT_MODE),
342+
},
343+
},
344+
{
345+
id: "renamed-page",
346+
type: "middleware",
347+
options: {
348+
middleware: getPageChildrenMiddleware(RENAMED_PAGE),
349+
},
350+
},
351+
],
352+
},
275353
{
276354
id: "confluence-create-page",
277355
url: "/rest/api/content",

components/confluence-sync/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@telefonica/confluence-sync",
33
"description": "Creates/updates/deletes Confluence pages based on a list of objects containing the page contents. Supports nested pages and attachments upload",
4-
"version": "2.0.1",
4+
"version": "2.0.2-beta.1",
55
"license": "Apache-2.0",
66
"author": "Telefónica Innovación Digital",
77
"repository": {

components/confluence-sync/src/confluence/CustomConfluenceClient.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-FileCopyrightText: 2024 Telefónica Innovación Digital
1+
// SPDX-FileCopyrightText: 2025 Telefónica Innovación Digital
22
// SPDX-License-Identifier: Apache-2.0
33

44
import type { LoggerInterface } from "@mocks-server/logger";
@@ -50,22 +50,38 @@ export const CustomConfluenceClient: ConfluenceClientConstructor = class CustomC
5050
public async getPage(id: string): Promise<ConfluencePage> {
5151
try {
5252
this._logger.silly(`Getting page with id ${id}`);
53-
const response: Models.Content =
54-
await this._client.content.getContentById({
53+
54+
const childrenRequest: Promise<Models.ContentChildren> =
55+
this._client.contentChildrenAndDescendants.getContentChildren({
56+
id,
57+
expand: ["children.page"],
58+
});
59+
60+
const pageRequest: Promise<Models.Content> =
61+
this._client.content.getContentById({
5562
id,
56-
expand: ["ancestors", "version.number", "children.page"],
63+
expand: ["ancestors", "version.number"],
5764
});
65+
66+
const [response, childrenResponse] = await Promise.all([
67+
pageRequest,
68+
childrenRequest,
69+
]);
70+
5871
this._logger.silly(
5972
`Get page response: ${JSON.stringify(response, null, 2)}`,
6073
);
74+
this._logger.silly(
75+
`Get children response: ${JSON.stringify(childrenResponse, null, 2)}`,
76+
);
6177
return {
6278
title: response.title,
6379
id: response.id,
6480
version: response.version?.number as number,
6581
ancestors: response.ancestors?.map((ancestor) =>
6682
this._convertToConfluencePageBasicInfo(ancestor),
6783
),
68-
children: response.children?.page?.results?.map((child) =>
84+
children: childrenResponse.page?.results?.map((child) =>
6985
this._convertToConfluencePageBasicInfo(child),
7086
),
7187
};

components/confluence-sync/test/component/specs/Sync.spec.ts

Lines changed: 62 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/*eslint-disable jest/max-expects */
12
// SPDX-FileCopyrightText: 2024 Telefónica Innovación Digital
23
// SPDX-License-Identifier: Apache-2.0
34

@@ -504,56 +505,91 @@ describe("confluence-sync-pages library", () => {
504505
describe("when a page has been renamed", () => {
505506
let requests: SpyRequest[];
506507
let getPageRequests: SpyRequest[];
508+
let getPageChildrenRequests: SpyRequest[];
507509

508510
beforeAll(async () => {
509511
await changeMockCollection("renamed-page");
510512
await confluenceSyncPages.sync(renamedPage);
511513

512514
requests = await getRequests();
513515
getPageRequests = await getRequestsByRouteId("confluence-get-page");
516+
getPageChildrenRequests = await getRequestsByRouteId(
517+
"confluence-get-page-children",
518+
);
514519
createRequests = await getRequestsByRouteId("confluence-create-page");
515520
deleteRequests = await getRequestsByRouteId("confluence-delete-page");
516521
});
517522

518523
it("should execute delete requests before create requests", async () => {
519-
// First request is the one to get the root page
520-
expect(requests[0].routeId).toBe("confluence-get-page");
521-
expect(getPageRequests[0].params?.pageId).toBe("foo-root-id");
522-
// Second request is the one to get the parent page, child of the root page
524+
// Request to get the root page children
525+
expect(requests[0].routeId).toBe("confluence-get-page-children");
526+
expect(getPageChildrenRequests[0].params?.pageId).toBe("foo-root-id");
527+
// Request to get the root page
523528
expect(requests[1].routeId).toBe("confluence-get-page");
529+
expect(getPageRequests[0].params?.pageId).toBe("foo-root-id");
530+
531+
// Request to get the root page children
532+
expect(requests[2].routeId).toBe("confluence-get-page-children");
533+
expect(getPageChildrenRequests[1].params?.pageId).toBe(
534+
"foo-parent-id",
535+
);
536+
// Request to get the parent page, child of the root page
537+
expect(requests[3].routeId).toBe("confluence-get-page");
524538
expect(getPageRequests[1].params?.pageId).toBe("foo-parent-id");
525-
// Third request has to be the one to delete the parent page
526-
expect(requests[2].routeId).toBe("confluence-delete-page");
539+
540+
// Request to delete the parent page
541+
expect(requests[4].routeId).toBe("confluence-delete-page");
527542
expect(deleteRequests[0].params?.pageId).toBe("foo-parent-id");
528-
// Fourth request has to be the one to create the renamed page because is child of the root page
529-
expect(requests[3].routeId).toBe("confluence-create-page");
543+
// Request to create the renamed page because is child of the root page
544+
expect(requests[5].routeId).toBe("confluence-create-page");
530545
expect(createRequests[0].body?.title).toBe("foo-renamed-title");
531-
// Fifth request has to be the one to get the child1 page which is child of the parent page
532-
expect(requests[4].routeId).toBe("confluence-get-page");
546+
547+
// Request to get the child1 page which is child of the parent page
548+
expect(requests[6].routeId).toBe("confluence-get-page-children");
549+
expect(getPageChildrenRequests[2].params?.pageId).toBe(
550+
"foo-child1-id",
551+
);
552+
// Request to get the child1 page which is child of the parent page
553+
expect(requests[7].routeId).toBe("confluence-get-page");
533554
expect(getPageRequests[2].params?.pageId).toBe("foo-child1-id");
534-
// Sixth request has to be the one to delete the child1 page
535-
expect(requests[5].routeId).toBe("confluence-delete-page");
555+
556+
// Request to delete the child1 page
557+
expect(requests[8].routeId).toBe("confluence-delete-page");
536558
expect(deleteRequests[1].params?.pageId).toBe("foo-child1-id");
537-
// Seventh request has to be the one to create the child1 page because is child of the renamed page
538-
expect(requests[6].routeId).toBe("confluence-create-page");
559+
// Request to create the child1 page because is child of the renamed page
560+
expect(requests[9].routeId).toBe("confluence-create-page");
539561
expect(createRequests[1].body?.title).toBe("foo-child1-title");
540-
// Eighth request has to be the one to get the grandChild1 page which is child of the child1 page child of parent page
541-
expect(requests[7].routeId).toBe("confluence-get-page");
562+
563+
// Request to get the grandChild1 page which is child of the child1 page child of parent page
564+
expect(requests[10].routeId).toBe("confluence-get-page-children");
565+
expect(getPageChildrenRequests[3].params?.pageId).toBe(
566+
"foo-grandChild1-id",
567+
);
568+
// Request to get the grandChild1 page which is child of the child1 page child of parent page
569+
expect(requests[11].routeId).toBe("confluence-get-page");
542570
expect(getPageRequests[3].params?.pageId).toBe("foo-grandChild1-id");
543-
// Ninth request has to be the one to delete the grandChild1 page
544-
expect(requests[8].routeId).toBe("confluence-delete-page");
571+
572+
// Request to delete the grandChild1 page
573+
expect(requests[12].routeId).toBe("confluence-delete-page");
545574
expect(deleteRequests[2].params?.pageId).toBe("foo-grandChild1-id");
546-
// Tenth request has to be the one to get the grandChild2 page because is child of the child1 page child of parent page
547-
expect(requests[9].routeId).toBe("confluence-get-page");
575+
576+
// Request to get the grandChild2 page children because is child of the child1 page child of parent page
577+
expect(requests[13].routeId).toBe("confluence-get-page-children");
578+
expect(getPageChildrenRequests[4].params?.pageId).toBe(
579+
"foo-grandChild2-id",
580+
);
581+
// Request to get the grandChild2 page because is child of the child1 page child of parent page
582+
expect(requests[14].routeId).toBe("confluence-get-page");
548583
expect(getPageRequests[4].params?.pageId).toBe("foo-grandChild2-id");
549-
// Eleventh request has to be the one to delete the grandChild2 page
550-
expect(requests[10].routeId).toBe("confluence-delete-page");
584+
585+
// Request to delete the grandChild2 page
586+
expect(requests[15].routeId).toBe("confluence-delete-page");
551587
expect(deleteRequests[3].params?.pageId).toBe("foo-grandChild2-id");
552-
// Twelfth request has to be the one to create the grandChild1 page because is child of the child1 page child of the renamed page
553-
expect(requests[11].routeId).toBe("confluence-create-page");
588+
// Request to create the grandChild1 page because is child of the child1 page child of the renamed page
589+
expect(requests[16].routeId).toBe("confluence-create-page");
554590
expect(createRequests[2].body?.title).toBe("foo-grandChild1-title");
555-
// Thirteenth request has to be the one to create the grandChild2 page because is child of the child1 page child of the renamed page
556-
expect(requests[12].routeId).toBe("confluence-create-page");
591+
// Request to create the grandChild2 page because is child of the child1 page child of the renamed page
592+
expect(requests[17].routeId).toBe("confluence-create-page");
557593
expect(createRequests[3].body?.title).toBe("foo-grandChild2-title");
558594
});
559595

components/confluence-sync/test/unit/specs/confluence/CustomConfluenceClient.test.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-FileCopyrightText: 2024 Telefónica Innovación Digital
1+
// SPDX-FileCopyrightText: 2025 Telefónica Innovación Digital
22
// SPDX-License-Identifier: Apache-2.0
33

44
import type { LoggerInterface } from "@mocks-server/logger";
@@ -59,7 +59,7 @@ describe("customConfluenceClient class", () => {
5959

6060
expect(confluenceClient.content.getContentById).toHaveBeenCalledWith({
6161
id: "foo-id",
62-
expand: ["ancestors", "version.number", "children.page"],
62+
expand: ["ancestors", "version.number"],
6363
});
6464
});
6565

@@ -71,15 +71,18 @@ describe("customConfluenceClient class", () => {
7171
ancestors: [
7272
{ id: "foo-id-ancestor", title: "foo-ancestor", type: "page" },
7373
],
74-
children: {
74+
}));
75+
confluenceClient.contentChildrenAndDescendants.getContentChildren.mockImplementation(
76+
() => ({
7577
page: {
7678
results: [
7779
{ id: "foo-child-1-id", title: "foo-child-1" },
7880
{ id: "foo-child-2-id", title: "foo-child-2" },
7981
],
8082
},
81-
},
82-
}));
83+
}),
84+
);
85+
8386
const response = await customConfluenceClient.getPage("foo-id");
8487

8588
expect(response).toEqual({

components/confluence-sync/test/unit/support/mocks/ConfluenceClient.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ jest.mock("confluence.js");
66
import * as confluenceLibrary from "confluence.js";
77

88
export const confluenceClient = {
9+
contentChildrenAndDescendants: {
10+
getContentChildren: jest.fn().mockResolvedValue({}),
11+
},
912
content: {
1013
getContentById: jest.fn().mockResolvedValue({}),
1114
createContent: jest.fn().mockResolvedValue({}),

0 commit comments

Comments
 (0)