Skip to content

Commit d98e17f

Browse files
authored
Merge pull request #62 from Telefonica/release
Request child pages using pagination (#61)
2 parents 38c97a7 + 096efec commit d98e17f

File tree

14 files changed

+459
-80
lines changed

14 files changed

+459
-80
lines changed

.github/ISSUE_TEMPLATE/BUG.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ body:
2323
- child-process-manager
2424
- confluence-sync
2525
- markdown-confluence-sync
26-
default: 0
26+
default: 2
2727
validations:
2828
required: true
2929
- type: textarea
@@ -41,8 +41,9 @@ body:
4141
label: Version
4242
description: What version are you using?
4343
options:
44-
- 1.x (Default)
45-
default: 0
44+
- 1.x
45+
- 2.x
46+
default: 1
4647
validations:
4748
required: true
4849
- type: textarea

.github/workflows/publish.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,3 @@ jobs:
2121
echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
2222
echo "@telefonica:registry=https://registry.npmjs.org/" >> ~/.npmrc
2323
pnpm -r publish --no-git-checks
24-

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-11
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 call directly to the Confluence API paginated and recursively to get all children pages, so we can update them correctly.
19+
1420
## [2.0.1] - 2025-04-15
1521

1622
### Changed

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",
55
"license": "Apache-2.0",
66
"author": "Telefónica Innovación Digital",
77
"repository": {

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

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
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";
55
import type { Models } from "confluence.js";
66
import { ConfluenceClient } from "confluence.js";
7+
import axios from "axios";
78

89
import type {
910
Attachments,
@@ -23,6 +24,8 @@ import { DeletePageError } from "./errors/DeletePageError";
2324
import { PageNotFoundError } from "./errors/PageNotFoundError";
2425
import { UpdatePageError } from "./errors/UpdatePageError";
2526

27+
const GET_CHILDREN_LIMIT = 100;
28+
2629
export const CustomConfluenceClient: ConfluenceClientConstructor = class CustomConfluenceClient
2730
implements ConfluenceClientInterface
2831
{
@@ -47,25 +50,85 @@ export const CustomConfluenceClient: ConfluenceClientConstructor = class CustomC
4750
return this._logger;
4851
}
4952

53+
private async _getChildPages(
54+
parentId: ConfluenceId,
55+
start: number = 0,
56+
otherChildren: Models.Content[] = [],
57+
): Promise<Models.Content[]> {
58+
try {
59+
this._logger.silly(`Getting child pages of parent with id ${parentId}`);
60+
const response = await axios.get<Models.ContentChildren>(
61+
`${this._config.url}/rest/api/content/${parentId}/child`,
62+
{
63+
params: {
64+
start,
65+
limit: GET_CHILDREN_LIMIT,
66+
expand: "page",
67+
},
68+
headers: {
69+
accept: "application/json",
70+
Authorization: `Bearer ${this._config.personalAccessToken}`,
71+
},
72+
},
73+
);
74+
this._logger.silly(
75+
`Get child pages response of page ${parentId}, starting at ${start}: ${JSON.stringify(response.data, null, 2)}`,
76+
);
77+
78+
const childrenResults = response.data.page?.results || [];
79+
const size = response.data.page?.size || 0;
80+
81+
const allChildren: Models.Content[] = [
82+
...otherChildren,
83+
...childrenResults,
84+
];
85+
86+
if (start + childrenResults.length < size) {
87+
const newStart = start + GET_CHILDREN_LIMIT;
88+
this._logger.silly(
89+
`There are more child pages of page with id ${parentId}, fetching next page starting from ${newStart}`,
90+
);
91+
return this._getChildPages(parentId, newStart, allChildren);
92+
}
93+
94+
return allChildren;
95+
} catch (error) {
96+
throw new PageNotFoundError(parentId, { cause: error });
97+
}
98+
}
99+
50100
public async getPage(id: string): Promise<ConfluencePage> {
51101
try {
52102
this._logger.silly(`Getting page with id ${id}`);
53-
const response: Models.Content =
54-
await this._client.content.getContentById({
103+
104+
const childrenRequest: Promise<Models.Content[]> =
105+
this._getChildPages(id);
106+
107+
const pageRequest: Promise<Models.Content> =
108+
this._client.content.getContentById({
55109
id,
56-
expand: ["ancestors", "version.number", "children.page"],
110+
expand: ["ancestors", "version.number"],
57111
});
112+
113+
const [response, childrenResponse] = await Promise.all([
114+
pageRequest,
115+
childrenRequest,
116+
]);
117+
58118
this._logger.silly(
59119
`Get page response: ${JSON.stringify(response, null, 2)}`,
60120
);
121+
this._logger.silly(
122+
`Get children response: ${JSON.stringify(childrenResponse, null, 2)}`,
123+
);
61124
return {
62125
title: response.title,
63126
id: response.id,
64127
version: response.version?.number as number,
65128
ancestors: response.ancestors?.map((ancestor) =>
66129
this._convertToConfluencePageBasicInfo(ancestor),
67130
),
68-
children: response.children?.page?.results?.map((child) =>
131+
children: childrenResponse.map((child) =>
69132
this._convertToConfluencePageBasicInfo(child),
70133
),
71134
};

0 commit comments

Comments
 (0)