Skip to content

Bugfix/aem cmg 1110 - #1146

Open
shradha-nahar wants to merge 3 commits into
devfrom
bugfix/aem-cmg-1110
Open

Bugfix/aem cmg 1110#1146
shradha-nahar wants to merge 3 commits into
devfrom
bugfix/aem-cmg-1110

Conversation

@shradha-nahar

Copy link
Copy Markdown
Contributor

🔗 Jira Ticket

CMG-1110
CMG-1112
CMG-1113

📋 PR Type

  • 🐛 Bug Fix

📝 Description

CMG-1110: Fixed the Save footer on the "Map Entry Assets" screen being visually clipped out of view. useMeasuredTableHeight now also reserves space for .asset-mapper-toolbar (the status-filter row unique to the asset mapper), which it previously didn't account for — causing the table body to render taller than available space and push the Save button below the overflow: hidden wrapper's visible bounds.

CMG-1112: createEntry (AEM connector) now skips a file when its derived modelId was already used earlier in the same migration run, instead of minting it a fresh random uid.

CMG-1113: .error-container's align-items: flex-start (shown when an invalid path is validated) is now align-items: stretch, matching the neutral .validation-container state.

What changed?

Why?

CMG-1110: The Save button (and its handleSaveAssets → updateAssetMapper flow, wired through to isUpdate and consumed at actual-migration time to decide asset replace-vs-reuse) was already implemented and functional — it just wasn't visible due to a layout height-measurement bug specific to the asset mapper's extra toolbar row.

CMG-1112: AEM can export a page template's structure/model definition into two separate files sharing the same source id (e.g. notitle.model.json and page-content-full-width.template.json). createEntry previously minted a new random uid for the second occurrence rather than recognizing it as a duplicate — creating an untracked entry that could never be matched by the delta "already migrated" check, so a new untracked duplicate was created on every iteration (2 entries after iteration 1, +1 every iteration after). This now matches the collision policy extractEntries (upload-api/migration-aem) already uses for entry_mapper tracking.

CMG-1113: flex-start let the file-path row shrink-to-fit around short/invalid path text instead of filling the container's fixed width, unlike the neutral validation state.

🧩 Affected Areas

  • api — Node.js backend (CMG-1112)
  • ui — React frontend (CMG-1110, CMG-1113)

🧪 How to Test

  1. CMG-1110 - Start an AEM migration project, reach Step 4 "Map Entry", switch to the Assets tab (with at least one matched/updatable asset). Confirm the Save button is visible in the footer and clickable.

  2. CMG-1112 - Run an AEM migration against source data containing a page template whose structure/model definition is exported into two files sharing the same id (e.g. sample templates data: notitle.model.json + page-content-full-width.template.json).Run iteration 1, then iteration 2 (delta) against the same or copied source data.

  • Expected: exactly one Contentstack entry for the colliding "structure" content after iteration 1, no additional duplicate after iteration 2+. Before the fix: 2 entries after iteration 1, +1 every iteration after.
  1. CMG-1113 - On the "Uploaded File" step, enter an invalid local path and click Validate.
  • Expected: the input field keeps its width; it should not visibly narrow.

📸 Screenshots / Recordings

Before After

✅ Author Checklist

Complete this before moving the PR out of Draft.

  • Branch follows naming convention: feature/, bugfix/, or hotfix/ + 5–30 lowercase chars
  • Jira ticket linked above
  • Self-reviewed the diff — no debug logs, commented-out code, or TODOs left in
  • .env / example.env updated if new environment variables were added
  • No sensitive credentials or secrets committed
  • Existing tests pass locally (npm test)
  • New tests written (or not applicable — explain why)
  • README.md / docs updated if behaviour changed
  • Talisman pre-push scan passes (no secrets flagged)

👀 Reviewer Notes

CMG-1112's fix only activates on a same-run modelId repeat — every normal, non-colliding AEM page is unaffected.
CMG-1112 does not address whether AEM template-structure/schema nodes should be migrated as entries at all (they aren't real content) — flagged as a possible larger follow-up, out of scope here.
CMG-1110 and CMG-1113 are both CSS/layout-only fixes with no logic changes.


@shradha-nahar
shradha-nahar requested a review from a team as a code owner August 21, 2026 11:59
@snyk-io

snyk-io Bot commented Aug 21, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions

Copy link
Copy Markdown

🔒 Security Scan Results

ℹ️ Note: Only vulnerabilities with available fixes (upgrades or patches) are counted toward thresholds.

Check Type Count (with fixes) Without fixes Threshold Result
🔴 Critical Severity 0 0 10 ✅ Passed
🟠 High Severity 15 378 25 ✅ Passed
🟡 Medium Severity 19 11 500 ✅ Passed
🔵 Low Severity 2 0 1000 ✅ Passed

⏱️ SLA Breach Summary

⚠️ Warning: The following vulnerabilities have exceeded their SLA thresholds (days since publication).

Severity Breaches (with fixes) Breaches (no fixes) SLA Threshold (with/no fixes) Status
🔴 Critical 0 0 15 / 30 days ✅ Passed
🟠 High 0 371 30 / 120 days ⚠️ Warning
🟡 Medium 0 0 90 / 365 days ✅ Passed
🔵 Low 0 0 180 / 365 days ✅ Passed

ℹ️ Vulnerabilities Without Available Fixes (Informational Only)

The following vulnerabilities were detected but do not have fixes available (no upgrade or patch). These are excluded from failure thresholds:

  • Critical without fixes: 0
  • High without fixes: 378
  • Medium without fixes: 11
  • Low without fixes: 0

⚠️ BUILD PASSED WITH WARNINGS - SLA breaches detected for issues without available fixes

Consider reviewing these vulnerabilities when fixes become available.

@umesh-more-cstk umesh-more-cstk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — head 7d36eb60

Scope: all 4 files (1 api, 3 ui). Not a draft, not docs/lockfile-only. The diff matches the declared Affected Areas — api for CMG-1112, ui for CMG-1110/1113 — no unrelated churn.

2 blockers · 0 questions · 3 nits. Both blockers are in the CMG-1112 change; the two CSS/layout fixes verified clean.

Blockers (CMG-1112, api/src/services/aem.service.ts)

  • L1398 — collision winner is filesystem-order dependent. entriesDir is walked with fs-readdir-recursive (unsorted readdirSync order), so when notitle.model.json and page-content-full-width.template.json collide, whichever the FS yields first wins. If that's the .template.json, the real page content is skipped and the migration keeps a schema node instead — and it reproduces differently per machine. Previously both were written, so nothing was lost; the skip makes the arbitrary choice consequential.
  • L1401 — the skip key is too coarse, and locale-blind. On the derived path (no parseData.id, the normal case for XF and content-page per the comment at 1380-1384) the key degrades to title_templateType or bare templateType, so N untitled content-page files collapse to 1 entry with N−1 silently dropped. Separately, per-locale exports of one page collide on the same key, and the non-first locale is now skipped entirely rather than written to its own locale bucket — that case wants the same uid and both entries, not a skip. Suggest keying on ${modelId}::${mappedLocale} (needs the locale computation hoisted above the check).

Nits

  • L1399 — the only skip in this loop that logs nothing; every other one goes through customLogger. Given the above, this is the drop most likely to need diagnosing.
  • L1397 — the comment cites extractEntries / entry_mapper (and uid-mapper at L1375), none of which exist in this repo; upload-api/migration-aem has no entries lib. Worth clarifying if these mean CLI-side mappers.
  • legacyCms.scss L88 — stretch is right, but .error-container still lacks .validation-container's padding: 10px 15px and instead uses a child margin-left: 10px, so the row is flush to the right border and the two states still differ in width.

Verified correct — no findings

  • CMG-1110. .asset-mapper-toolbar is a direct child of the tableWrapperRef element (assetMapper.tsx:439-440), so the scoped wrapper.querySelector resolves it; it's a sibling of the venus-rendered .TablePanel, so nothing is double-reserved against panelSelector; and its SCSS (index.scss:988) is flex: 0 0 auto with padding + border and no margin, so offsetHeight captures the full flow height it occupies. Making toolbarSelector optional leaves entryMapper's call site untouched, and the dev-only warning matches the existing pattern for the other selectors.
  • CMG-1113. align-items: stretch restores the flex default and matches .validation-container, which sets no align-items at all. Behaviour-free otherwise.

One note on the checklist rather than the code: "New tests written (or not applicable)" is ticked with no explanation, and the CMG-1112 change is a behavioural one. There's no existing suite for api/src/services/aem.service.ts (only upload-api/tests/** covers AEM), so a first test here is a bigger lift than usual — but the same-run-collision path is exactly the kind of thing worth locking down once the key/tie-break questions above are settled.


Generated by Claude Code

// untracked random uid (another duplicate) on every subsequent delta iteration,
// since it can never match anything recorded in entry_mapper. This mirrors
// extractEntries's collision policy in upload-api's migration-aem.
if (modelId && usedEntryUids.has(modelId)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocker: the winner of a collision is whatever the filesystem happens to yield first, so this can keep the template/structure node and drop the real page.

entriesDir is walked with fs-readdir-recursive (import at line 7), i.e. fs.readdirSync order — not sorted, not semantically meaningful. Before this change both colliding files were written (one under modelId, one under a random uid), so nothing was lost; now exactly one survives and which one is arbitrary.

Failure scenario, using this PR's own example: notitle.model.json (the actual page content) and page-content-full-width.template.json (the template's structure definition) share the same id. If the .template.json is yielded first it claims modelId, and the real page — the one carrying [':items'].root content consumed at line 1412 — is continued away. The migration then produces one entry whose body is a schema node and silently loses the page. Because it depends on directory order, the same source data can migrate correctly on one machine and wrongly on another, which makes it painful to reproduce. The Reviewer Notes already acknowledge that structure nodes probably shouldn't become entries at all, which is exactly the file this can pick.

Suggested fix: make the tie-break explicit rather than incidental — prefer the file that is actual content (e.g. the one with parseData?.[':items']?.root, or skip structure/template exports outright) and only fall back to first-wins when both look like content, with the file list sorted so that fallback is at least reproducible.


Generated by Claude Code

if (modelId && usedEntryUids.has(modelId)) {
continue;
}
const uid = modelId || uuidv4?.()?.replace?.(/-/g, '');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocker: modelId isn't unique enough to be a skip key on the derived path, and it ignores locale — both turn "duplicate" into silent content loss.

Two ways distinct files collide on modelId without being duplicate exports of the same page:

  1. Derived ids. When parseData.id is absent, the key is uidCorrector(\${title}_${templateType}`), or uidCorrector(templateType)alone when there's no title (lines 1385-1389). Per the comment right above it, that's the *normal* path for experience fragments andcontent-pages, not an edge case. So two untitled content-pagefiles both key tocontent_pageand only the first is migrated: N such files → 1 entry, N−1 dropped.getTitle(line 1307) also falls back totemplateType`, so these files aren't distinguishable by title downstream either. Previously all N were migrated (N−1 with random uids) — bad for delta tracking, but not lost.

  2. Locale. A page exported per-locale is one file per locale (getCurrentLocale, line 258), bucketed by locale at line 1435 and written to <ct>/<locale>/<locale>.json. Two locale variants of one page that share an id, or share an untranslated title + templateType, collide here — and the non-first locale is now skipped entirely, so that locale's entry never gets written at all. This case is the opposite of a duplicate: it should keep the entry and reuse the same uid, which is exactly how Contentstack localizes.

Suggested fix: key the set on ${modelId}::${mappedLocale} so locale variants survive and legitimately share one uid — that means hoisting the locale/mappedLocale computation (lines 1410-1411) above this check. Then treat a same-locale repeat as a duplicate only when the file really is a redundant export (see the tie-break note on line 1398) rather than whenever the coarse derived key happens to repeat.


Generated by Claude Code

// since it can never match anything recorded in entry_mapper. This mirrors
// extractEntries's collision policy in upload-api's migration-aem.
if (modelId && usedEntryUids.has(modelId)) {
continue;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is the only skip in the loop that leaves no trace. The else at lines 1437-1448 logs Skipped entry from "<file>": <reason> through customLogger, and the DAM skip at line 1369 is structural/expected. This branch drops a file's content entirely, so an operator investigating a missing entry has nothing to go on — and given the two blockers above, that's the case most likely to need investigating.

await customLogger(
  projectId,
  destinationStackId,
  'warn',
  getLogMessage(srcFunc, `Skipped duplicate entry from "${fileName}": uid "${modelId}" already used in this run.`, {})
);
continue;

Generated by Claude Code

// uid here would create a second, permanent duplicate entry that mints yet another
// untracked random uid (another duplicate) on every subsequent delta iteration,
// since it can never match anything recorded in entry_mapper. This mirrors
// extractEntries's collision policy in upload-api's migration-aem.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the symbols this comment leans on don't exist in this repo, so the reasoning can't be checked. extractEntries and entry_mapper return no hits anywhere in the tree, and upload-api/migration-aem has no entries lib at all (only contentType, locales, validate) — same for uid-mapper referenced by the older comment at line 1375. If these mean the Contentstack CLI's import mapper files rather than code in this repo, worth saying so explicitly; as written, "this mirrors extractEntries's collision policy in upload-api's migration-aem" points a future reader at something they can't find, and the PR description repeats it.


Generated by Claude Code

align-items: flex-start;
// Stretch (not flex-start) so the path row keeps the container's full width — flex-start
// let it shrink-to-fit for short/invalid paths, visibly narrowing the input (CMG-1113).
align-items: stretch;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this correctly removes the shrink-to-fit (stretch is the flex default), but it doesn't quite reach the parity with .validation-container that the change is aiming for.

.validation-container (line 65) has padding: 10px 15px. .error-container has no padding and instead relies on .error-container > * { margin-left: 10px } (lines 96-97). So now that children stretch, the path row sits 10px in from the left and flush against the right border — still a different width from the neutral state, and asymmetric within the error state itself. Adding padding: 10px 15px here (and dropping the child margin-left) would line the two states up exactly.

Also pre-existing while you're in this block: margin-left: 20px !important is declared twice, lines 89 and 94.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants