Skip to content

Commit e64f9e7

Browse files
author
naman-contentstack
committed
feat: add pagination and test cases for AM package
1 parent e8b9a0a commit e64f9e7

21 files changed

Lines changed: 1637 additions & 32 deletions

File tree

.talismanrc

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
fileignoreconfig:
2-
- filename: pnpm-lock.yaml
3-
checksum: 2b0f2461ea1bb240a9210b9cf99dc403a756199712b7270f9792a590480451bd
4-
- filename: packages/contentstack-import/test/unit/import/modules/base-class.test.ts
5-
checksum: fe372852d5f2f3f57ef62c603406c30ccecdb444c17133ac0b21dda399b962c0
6-
- filename: packages/contentstack-bulk-operations/test/unit/commands/bulk-am-assets.test.ts
7-
checksum: f8d21db7db0ca2eebe7cc40af0a59f02e74e1689efb6d50a1072dc5ca3e03e9b
8-
- filename: packages/contentstack-export/src/export/modules/taxonomies.ts
9-
checksum: b6d077118280bc88385405f504f921468a9fd490ac37a4a21f741be729fd1ca3
10-
- filename: packages/contentstack-export/test/unit/export/modules/taxonomies.test.ts
11-
checksum: cab2ad4d897d23f04f988c1f018a9583ab7f0ee1815994d7bc9fce23dea70073
2+
- filename: packages/contentstack-asset-management/test/unit/import/asset-types.test.ts
3+
checksum: fa4c08a47a52b8bd27353b9ba712e6128674315610d3372b53abe6af361ba0b7
4+
- filename: packages/contentstack-asset-management/test/unit/import/fields.test.ts
5+
checksum: c2830111af2bf72c5a661fe2251a3520a865c42c3980289195a08d713703e4b1
6+
- filename: packages/contentstack-asset-management/test/unit/import/spaces.test.ts
7+
checksum: f95e4b7ce04d4beb70c7f70c762f03541588c211a8c15ba68d426c98215a0769
8+
- filename: packages/contentstack-asset-management/test/unit/import/assets.test.ts
9+
checksum: 37613b7f2b5812282eedf87e0b9f87efc8eebb5e3cf75afc5f0eaa3605be4b5d
10+
- filename: packages/contentstack-asset-management/test/unit/utils/cs-assets-api-adapter.test.ts
11+
checksum: 3d9d252ffa66a28380a015857fe7358fe7e2ba2efe90adaf715f9ff2a408450f
12+
- filename: packages/contentstack-asset-management/test/unit/import/base.test.ts
13+
checksum: c6ed81639052b5905f481e90d6c17e19b099b30916d4cf6bf7eabfe33fd15530
1214
version: '1.0'

packages/contentstack-asset-management/src/constants/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ export const FALLBACK_AM_CHUNK_FILE_SIZE_MB = 1;
44
export const FALLBACK_AM_API_CONCURRENCY = 5;
55
/** @deprecated Use FALLBACK_AM_API_CONCURRENCY */
66
export const DEFAULT_AM_API_CONCURRENCY = FALLBACK_AM_API_CONCURRENCY;
7+
export const FALLBACK_AM_API_PAGE_SIZE = 100;
8+
export const FALLBACK_AM_API_FETCH_CONCURRENCY = 5;
79

810
/** Fallback strip lists when import options omit `fieldsImportInvalidKeys` / `assetTypesImportInvalidKeys`. */
911
export const FALLBACK_FIELDS_IMPORT_INVALID_KEYS = [

packages/contentstack-asset-management/src/export/assets.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ export default class ExportAssets extends CSAssetsExportAdapter {
2828
log.debug(`Fetching folders and assets for space ${workspace.space_uid}`, this.exportContext.context);
2929

3030
const [folders, assetsData] = await Promise.all([
31-
this.getWorkspaceFolders(workspace.space_uid, workspace.uid),
32-
this.getWorkspaceAssets(workspace.space_uid, workspace.uid),
31+
this.getWorkspaceFolders(workspace.space_uid, workspace.uid, this.apiPageSize, this.apiFetchConcurrency),
32+
this.getWorkspaceAssets(workspace.space_uid, workspace.uid, this.apiPageSize, this.apiFetchConcurrency),
3333
]);
3434

3535
const assetItems = getAssetItems(assetsData);

packages/contentstack-asset-management/src/export/base.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { FsUtility, log, CLIProgressManager, configHandler } from '@contentstack
55
import type { CSAssetsAPIConfig } from '../types/cs-assets-api';
66
import type { ExportContext } from '../types/export-types';
77
import { CSAssetsAdapter } from '../utils/cs-assets-api-adapter';
8-
import { CS_ASSETS_MAIN_PROCESS_NAME, FALLBACK_AM_API_CONCURRENCY, FALLBACK_AM_CHUNK_FILE_SIZE_MB } from '../constants/index';
8+
import { CS_ASSETS_MAIN_PROCESS_NAME, FALLBACK_AM_API_CONCURRENCY, FALLBACK_AM_API_FETCH_CONCURRENCY, FALLBACK_AM_API_PAGE_SIZE, FALLBACK_AM_CHUNK_FILE_SIZE_MB } from '../constants/index';
99

1010
export type { ExportContext };
1111

@@ -82,6 +82,14 @@ export class CSAssetsExportAdapter extends CSAssetsAdapter {
8282
return this.exportContext.downloadAssetsConcurrency ?? this.apiConcurrency;
8383
}
8484

85+
protected get apiPageSize(): number {
86+
return this.exportContext.pageSize ?? FALLBACK_AM_API_PAGE_SIZE;
87+
}
88+
89+
protected get apiFetchConcurrency(): number {
90+
return this.exportContext.fetchConcurrency ?? FALLBACK_AM_API_FETCH_CONCURRENCY;
91+
}
92+
8593
protected getAssetTypesDir(): string {
8694
return pResolve(this.exportContext.spacesRootPath, 'asset_types');
8795
}

packages/contentstack-asset-management/src/export/spaces.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ export class ExportSpaces {
7979
chunkFileSizeMb,
8080
apiConcurrency: this.options.apiConcurrency,
8181
downloadAssetsConcurrency: this.options.downloadAssetsConcurrency,
82+
pageSize: this.options.pageSize,
83+
fetchConcurrency: this.options.fetchConcurrency,
8284
};
8385

8486
const sharedFieldsDir = pResolve(spacesRootPath, 'fields');

packages/contentstack-asset-management/src/import-setup/import-setup-asset-mappers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { join, resolve } from 'node:path';
44

55
import { formatError, log } from '@contentstack/cli-utilities';
66

7-
import { IMPORT_ASSETS_MAPPER_FILES, PROCESS_NAMES, PROCESS_STATUS } from '../constants/index';
7+
import { FALLBACK_AM_API_FETCH_CONCURRENCY, FALLBACK_AM_API_PAGE_SIZE, IMPORT_ASSETS_MAPPER_FILES, PROCESS_NAMES, PROCESS_STATUS } from '../constants/index';
88
import type { CSAssetsAPIConfig, ImportContext } from '../types/cs-assets-api';
99
import type { AssetMapperImportSetupResult, RunAssetMapperImportSetupParams } from '../types/import-setup-asset-mapper';
1010
import ImportAssets from '../import/assets';
@@ -25,7 +25,7 @@ export default class ImportSetupAssetMappers extends AssetManagementImportSetupA
2525
private async fetchExistingSpaceUidsInOrg(apiConfig: CSAssetsAPIConfig): Promise<Set<string>> {
2626
const adapter = new CSAssetsAdapter(apiConfig);
2727
await adapter.init();
28-
const { spaces } = await adapter.listSpaces();
28+
const { spaces } = await adapter.listSpaces(FALLBACK_AM_API_PAGE_SIZE, FALLBACK_AM_API_FETCH_CONCURRENCY);
2929
const uids = new Set<string>();
3030
for (const s of spaces) {
3131
if (s.uid) {

packages/contentstack-asset-management/src/import/spaces.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type {
1010
ImportSpacesOptions,
1111
SpaceMapping,
1212
} from '../types/cs-assets-api';
13-
import { CS_ASSETS_MAIN_PROCESS_NAME, PROCESS_NAMES, getSpaceProcessName } from '../constants/index';
13+
import { CS_ASSETS_MAIN_PROCESS_NAME, FALLBACK_AM_API_PAGE_SIZE, FALLBACK_AM_API_FETCH_CONCURRENCY, PROCESS_NAMES, getSpaceProcessName } from '../constants/index';
1414
import { CSAssetsAdapter } from '../utils/cs-assets-api-adapter';
1515
import ImportAssetTypes from './asset-types';
1616
import ImportFields from './fields';
@@ -112,7 +112,7 @@ export class ImportSpaces {
112112
try {
113113
const adapterForList = new CSAssetsAdapter(apiConfig);
114114
await adapterForList.init();
115-
const { spaces } = await adapterForList.listSpaces();
115+
const { spaces } = await adapterForList.listSpaces(FALLBACK_AM_API_PAGE_SIZE, FALLBACK_AM_API_FETCH_CONCURRENCY);
116116
for (const s of spaces) {
117117
if (s.uid) existingSpaceUids.add(s.uid);
118118
}

packages/contentstack-asset-management/src/types/cs-assets-api.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,11 @@ export type SearchAssetsResponse = {
167167

168168
export interface ICSAssetsAdapter {
169169
init(): Promise<void>;
170-
listSpaces(): Promise<SpacesListResponse>;
170+
listSpaces(pageSize?: number, fetchConcurrency?: number): Promise<SpacesListResponse>;
171171
getSpace(spaceUid: string): Promise<SpaceResponse>;
172172
getWorkspaceFields(spaceUid: string): Promise<FieldsResponse>;
173-
getWorkspaceAssets(spaceUid: string, workspaceUid?: string): Promise<unknown>;
174-
getWorkspaceFolders(spaceUid: string, workspaceUid?: string): Promise<unknown>;
173+
getWorkspaceAssets(spaceUid: string, workspaceUid?: string, pageSize?: number, fetchConcurrency?: number): Promise<unknown>;
174+
getWorkspaceFolders(spaceUid: string, workspaceUid?: string, pageSize?: number, fetchConcurrency?: number): Promise<unknown>;
175175
getWorkspaceAssetTypes(spaceUid: string): Promise<AssetTypesResponse>;
176176
searchAssets(params: SearchAssetsParams): Promise<SearchAssetsResponse>;
177177
bulkDeleteAssets(
@@ -233,6 +233,8 @@ export type AssetManagementExportOptions = {
233233
* Max parallel asset file downloads per workspace.
234234
*/
235235
downloadAssetsConcurrency?: number;
236+
pageSize?: number;
237+
fetchConcurrency?: number;
236238
};
237239

238240
// ---------------------------------------------------------------------------

packages/contentstack-asset-management/src/types/export-types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ export type ExportContext = {
55
chunkFileSizeMb?: number;
66
apiConcurrency?: number;
77
downloadAssetsConcurrency?: number;
8+
pageSize?: number;
9+
fetchConcurrency?: number;
810
};
911

1012
/**

packages/contentstack-asset-management/src/utils/cs-assets-api-adapter.ts

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import { readFileSync } from 'node:fs';
22
import { basename } from 'node:path';
33
import { HttpClient, log, authenticationHandler, handleAndLogError } from '@contentstack/cli-utilities';
44

5+
import { chunkArray } from './concurrent-batch';
6+
import { FALLBACK_AM_API_FETCH_CONCURRENCY, FALLBACK_AM_API_PAGE_SIZE } from '../constants/index';
7+
58
import type {
69
CSAssetsAPIConfig,
710
AssetTypesResponse,
@@ -189,11 +192,17 @@ export class CSAssetsAdapter implements ICSAssetsAdapter {
189192
}
190193
}
191194

192-
async listSpaces(): Promise<SpacesListResponse> {
195+
async listSpaces(pageSize = FALLBACK_AM_API_PAGE_SIZE, fetchConcurrency = FALLBACK_AM_API_FETCH_CONCURRENCY): Promise<SpacesListResponse> {
193196
log.debug('Fetching all spaces in org', this.config.context);
194-
const result = await this.getSpaceLevel<SpacesListResponse>('', '/api/spaces', {});
195-
log.debug(`Fetched ${result?.count ?? result?.spaces?.length ?? '?'} space(s)`, this.config.context);
196-
return result;
197+
const items = await this.fetchAllPages(
198+
'',
199+
'/api/spaces',
200+
'spaces',
201+
pageSize,
202+
fetchConcurrency,
203+
);
204+
log.debug(`Fetched ${items.length} space(s)`, this.config.context);
205+
return { spaces: items as Space[], count: items.length };
197206
}
198207

199208
async getSpace(spaceUid: string): Promise<SpaceResponse> {
@@ -230,22 +239,73 @@ export class CSAssetsAdapter implements ICSAssetsAdapter {
230239
return result;
231240
}
232241

233-
async getWorkspaceAssets(spaceUid: string, workspaceUid?: string): Promise<unknown> {
234-
return this.getWorkspaceCollection(
242+
243+
/**
244+
* Fetch all pages of a paginated collection by issuing the first request to determine
245+
* the total count, then issuing remaining page requests with controlled concurrency.
246+
*/
247+
private async fetchAllPages(
248+
spaceUid: string,
249+
path: string,
250+
itemsKey: string,
251+
pageSize: number,
252+
concurrency: number,
253+
baseParams: Record<string, unknown> = {},
254+
): Promise<unknown[]> {
255+
const first = await this.getSpaceLevel<Record<string, unknown>>(spaceUid, path, {
256+
...baseParams, limit: String(pageSize), skip: '0',
257+
});
258+
259+
const total: number = Number(first?.count ?? 0);
260+
const firstItems: unknown[] = Array.isArray(first?.[itemsKey]) ? (first[itemsKey] as unknown[]) : [];
261+
if (firstItems.length >= total) return firstItems;
262+
263+
const skips = Array.from(
264+
{ length: Math.ceil(total / pageSize) - 1 },
265+
(_, i) => (i + 1) * pageSize,
266+
);
267+
268+
const skipBatches = chunkArray(skips, concurrency);
269+
const rest: unknown[] = [];
270+
271+
for (const batch of skipBatches) {
272+
const pages = await Promise.all(
273+
batch.map((skip) =>
274+
this.getSpaceLevel<Record<string, unknown>>(spaceUid, path, {
275+
...baseParams, limit: String(pageSize), skip: String(skip),
276+
}).then((r) => (Array.isArray(r?.[itemsKey]) ? (r[itemsKey] as unknown[]) : [])),
277+
),
278+
);
279+
rest.push(...pages.flat());
280+
}
281+
282+
return [...firstItems, ...rest];
283+
}
284+
285+
async getWorkspaceAssets(spaceUid: string, workspaceUid?: string, pageSize = FALLBACK_AM_API_PAGE_SIZE, fetchConcurrency = FALLBACK_AM_API_FETCH_CONCURRENCY): Promise<unknown> {
286+
const baseParams: Record<string, unknown> = workspaceUid ? { workspace: workspaceUid } : {};
287+
const items = await this.fetchAllPages(
235288
spaceUid,
236289
`/api/spaces/${encodeURIComponent(spaceUid)}/assets`,
237290
'assets',
238-
workspaceUid ? { workspace: workspaceUid } : {},
291+
pageSize,
292+
fetchConcurrency,
293+
baseParams,
239294
);
295+
return { assets: items, count: items.length };
240296
}
241297

242-
async getWorkspaceFolders(spaceUid: string, workspaceUid?: string): Promise<unknown> {
243-
return this.getWorkspaceCollection(
298+
async getWorkspaceFolders(spaceUid: string, workspaceUid?: string, pageSize = FALLBACK_AM_API_PAGE_SIZE, fetchConcurrency = FALLBACK_AM_API_FETCH_CONCURRENCY): Promise<unknown> {
299+
const baseParams: Record<string, unknown> = workspaceUid ? { workspace: workspaceUid } : {};
300+
const items = await this.fetchAllPages(
244301
spaceUid,
245302
`/api/spaces/${encodeURIComponent(spaceUid)}/folders`,
246303
'folders',
247-
workspaceUid ? { workspace: workspaceUid } : {},
304+
pageSize,
305+
fetchConcurrency,
306+
baseParams,
248307
);
308+
return { folders: items, count: items.length };
249309
}
250310

251311
async getWorkspaceAssetTypes(spaceUid: string): Promise<AssetTypesResponse> {

0 commit comments

Comments
 (0)