Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 5 additions & 37 deletions api/src/services/contentMapper.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2496,20 +2496,6 @@ const getAssetMapping = async (req: Request) => {
await uidMapperCurrent.read();
const uidMapperPrev: any = iteration > 1 ? await getNearestPriorUidMapper(projectId, iteration) : null;

// Whether we actually have any uid data to resolve against yet. getUidMapperDb creates
// the file with an empty `assets: {}` default, so a fresh iteration directory (visited
// right after a restart, before this iteration's CLI import has run and written
// writeUidMapping's output) legitimately has none β€” distinct from "this project simply
// has no previously-migrated assets". Also true if any row already carries a
// pre-resolved uid from creation time (putTestData resolves it then, see ~line 280).
const hasAnyUidData =
Object.keys((uidMapperCurrent?.data as any)?.assets ?? {}).length > 0 ||
Object.keys((uidMapperPrev?.data as any)?.assets ?? {}).length > 0 ||
(assetMapping ?? []).some((item: any) => {
const uid = item?.contentstackAssetUid;
return uid != null && String(uid).trim() !== '';
});

let uidEnriched = (assetMapping ?? []).map((item: any) => {
if (!item) return item;
const existing = item?.contentstackAssetUid;
Expand Down Expand Up @@ -2558,29 +2544,11 @@ const getAssetMapping = async (req: Request) => {
return { ...item, status: 'ok' };
});

// Delta migration intent: on iteration 2+ the Assets tab lists ONLY assets that
// were already migrated in a prior iteration β€” i.e. those with a Contentstack
// uid. The user selects which of those to update with the current file's newer
// version. Brand-new assets in this iteration have no prior uid; they upload
// automatically during the run and don't need a Map Entry row (nothing to
// select or update yet). Iteration 1 is untouched β€” everything is new then.
//
// Exception: always surface 'failed'/'missing' rows even without a uid. A brand-new
// asset that fails to download NEVER gets a Contentstack uid (it never successfully
// migrates), so the has-uid check alone would hide it from view forever β€” the user
// would have no way to discover or retry it.
// Only apply the delta filter once we actually have uid data to filter with β€”
// otherwise a race right after restart (this iteration's uid-mapper.json not written
// yet) would filter out EVERY row and render an empty tab indistinguishable from "no
// previously-migrated assets", which could be mistaken for correct behavior since
// CMG-1097 already gives that empty state a legitimate-looking layout.
const displayMapping = iteration > 1 && hasAnyUidData
? enrichedMapping.filter((item: any) => {
const uid = item?.contentstackAssetUid;
const hasUid = uid != null && String(uid).trim() !== '';
return hasUid || item?.status === 'failed' || item?.status === 'missing';
})
: enrichedMapping;
// Show every asset regardless of iteration β€” brand-new-this-iteration assets
// alongside previously-migrated/updatable ones β€” matching getEntryMapping's
// behavior for entries. Consistent across all CMS connectors since this read
// path is shared.
const displayMapping = enrichedMapping;

// Aggregate counts across the FULL (unpaginated, unsearched) visible set β€” the banner
// needs "3 assets won't migrate" regardless of which page or search term is active.
Expand Down
9 changes: 7 additions & 2 deletions api/src/services/wordpress.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1918,8 +1918,13 @@ async function saveAsset(assets: any, retryCount: number, affix: string, destina
return assets["wp:post_id"];
} catch (err: any) {
const assetName = assets["title"] || nameWithoutExt;
failedJSON[assets["wp:post_id"]] = {
failedUid: assets["wp:post_id"],
// Must be keyed by customId (assets_<wp:post_id>), not the bare wp:post_id β€” that's
// the otherCmsAssetUid extractAssets.ts assigns this row, and the only key
// getAssetMapping looks up in cs_failed.json to resolve a row's status. Keying by the
// bare id here (as this used to) meant a failed attachment was queried as
// `assets_<id>`, never matched, and silently showed as "ok" instead of "failed".
failedJSON[customId] = {
failedUid: customId,
name: assetName,
url,
reason_for_error: err?.message || "error",
Expand Down
20 changes: 20 additions & 0 deletions api/tests/unit/services/contentMapper.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1113,5 +1113,25 @@ describe('contentMapper.service', () => {
expect(result.count).toBe(1);
expect(result.assetMapping[0].filename).toBe('windmill.jpg');
});

it('shows brand-new assets alongside previously-migrated ones on iteration 2+', async () => {
(ProjectModelLowdb.chain.get as ReturnType<typeof vi.fn>).mockReturnValue(
createChain({ find: { ...project, iteration: 2 } })
);
mockUidMapperDb.data = { entry: {}, assets: { 'src-1': 'cs-1' } };
(mockAssetMapperDb.chain.get as ReturnType<typeof vi.fn>).mockReturnValue(
createChain({
filter: [
{ projectId: 'proj-1', otherCmsAssetUid: 'src-1', contentstackAssetUid: 'cs-1', filename: 'a.jpg', title: 'A' },
{ projectId: 'proj-1', otherCmsAssetUid: 'src-2', filename: 'b.jpg', title: 'B' },
],
})
);

const result = await contentMapperService.getAssetMapping(baseReq());

expect(result.count).toBe(2);
expect(result.assetMapping.map((item: any) => item.otherCmsAssetUid).sort()).toEqual(['src-1', 'src-2']);
});
});
});
5 changes: 4 additions & 1 deletion upload-api/migration-wordpress/libs/extractAssets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ const extractAssets = async (filePath: string): Promise<AssetMappingRow[]> => {

rows.push({
id,
otherCmsAssetUid: id,
// Must match the `assets_<wp:post_id>` key wordpress.service.ts uses as the
// asset's customId/uid when staging it for CLI import β€” that's the key the CLI
// writes back into uid-mapping.json, and the read path looks this up verbatim.
otherCmsAssetUid: `assets_${id}`,
Comment thread
chetan-contentstack marked this conversation as resolved.
filename,
title,
file_size: '',
Expand Down
Loading