Skip to content

fix(personalize,import): fix CT linking failure from empty-audience experiences and missing variant entry data file #280

Open
cs-raj wants to merge 7 commits into
developmentfrom
fix/DX-9469
Open

fix(personalize,import): fix CT linking failure from empty-audience experiences and missing variant entry data file #280
cs-raj wants to merge 7 commits into
developmentfrom
fix/DX-9469

Conversation

@cs-raj

@cs-raj cs-raj commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Two related bugs in the Personalize import pipeline:

  1. experiences.ts — pendingVariantAndVariantGrpForExperience was populated with ALL created experience UIDs regardless of whether valid variants were submitted to the API. Experiences with variants:[] never get variant groups from the Personalize backend, so the validation loop timed out and set importData=false, skipping attachCTsInExperience for every experience including valid ones. Fixed by changing importExperienceVersions to return boolean and only adding UIDs to the pending set when valid variants were actually submitted. Also adds a null guard in attachCTsInExperience before accessing variantGroup to prevent a silent crash.

  2. entries.ts — a bare return inside the no-environments-file branch of the entries import function exited the entire function, skipping createEntryDataForVariantEntry(). This meant data-for-variant-entry.json was never written whenever a stack had no environments, causing the variant-entries module to warn about the missing file and skip all variant entry imports. Fixed by removing the return.

Problems

Bug 1 — CT linking broken when any experience has empty audiences

When importing a stack that contains experiences with referredAudiences: [] (AB
tests or segmented experiences with no audiences, or with only Lytics audiences),
the CLI fails to link any variant group to any content type — even for other
experiences in the same import that do have valid audiences.

Steps to reproduce:

  1. Export a stack with at least one experience that has empty referredAudiences
  2. Import into a new stack
  3. All content-type ↔ variant-group links are missing across the entire import

Bug 2 — Variant entry data file never written when no environments exist

When the imported stack has no environments, the entries import function hits an
early return before createEntryDataForVariantEntry() runs. The file
mapper/entries/data-for-variant-entry.json is never created, so the
variant-entries module warns "Variant entry data file not found" and skips all
variant entry imports silently.


Root Causes

Bug 1: pendingVariantAndVariantGrpForExperience was built from the full
experiencesUidMapper — all created experience UIDs regardless of whether valid
variants were submitted. Experiences with variants: [] never trigger variant-group
creation in the Personalize backend. The validation polling loop exhausted all 6
retries (60 s) waiting for groups that would never appear, then set
personalizeConfig.importData = false, gating attachCTsInExperience for the
entire import.

Bug 2: Inside if (!skipEntriesPublish), when this.envs was empty, a bare
return exited the whole import function. createEntryDataForVariantEntry() at
the end of the function was never reached.


Fixes

contentstack-variants/src/import/experiences.ts

  1. importExperienceVersions return type Promise<void>Promise<boolean>

    • false: no versions file, variants: [], or all variants stripped by lookUpAudiences
    • true: at least one valid version was submitted to the Personalize API
  2. Caller uses the return valueSet<string> experienceUidsWithVariants
    collects UIDs only when importExperienceVersions returns true.
    pendingVariantAndVariantGrpForExperience = Array.from(experienceUidsWithVariants)

  3. Null guard in attachCTsInExperiencegetVariantGroup can return empty
    variant_groups: [], making variantGroup undefined. Added guard before
    accessing it to log a warn and skip instead of crashing silently inside
    Promise.allSettled.

contentstack-import/src/import/modules/entries.ts

Removed the bare return from the no-environments-found branch. The publish loop
degrades gracefully with empty envs (serializePublishEntries nulls out all entries
when no environment matches), and createEntryDataForVariantEntry() now always runs.


Edge Cases Confirmed Safe

  • All experiences have empty audiences → pending list empty → validation resolves
    immediately → attachCTsInExperience called → null guard handles missing variant groups
  • Mixed (some empty, some valid) → only valid UIDs polled → validation succeeds → CT linking works
  • No environments file → publish loop runs but makes zero API calls → variant entry data file written correctly

Files Changed

File Change
contentstack-variants/src/import/experiences.ts Bug 1 core fix + null guard
contentstack-import/src/import/modules/entries.ts Bug 2 fix — remove early return
contentstack-variants/test/unit/import/experiences.test.ts New — 15 unit tests
contentstack-variants/test/unit/mock/contents/personalize/experiences/experiences.json New mock
contentstack-variants/test/unit/mock/contents/personalize/experiences/versions/*.json New mocks
contentstack-variants/test/unit/mock/contents/mapper/personalize/audiences/uid-mapping.json New mock

cs-raj and others added 2 commits July 10, 2026 08:44
…xperiences and missing variant entry data file [DX-9469]

Two related bugs in the Personalize import pipeline:

1. experiences.ts — pendingVariantAndVariantGrpForExperience was populated with ALL
   created experience UIDs regardless of whether valid variants were submitted to the
   API. Experiences with variants:[] never get variant groups from the Personalize
   backend, so the validation loop timed out and set importData=false, skipping
   attachCTsInExperience for every experience including valid ones. Fixed by changing
   importExperienceVersions to return boolean and only adding UIDs to the pending set
   when valid variants were actually submitted. Also adds a null guard in
   attachCTsInExperience before accessing variantGroup to prevent a silent crash.

2. entries.ts — a bare return inside the no-environments-file branch of the entries
   import function exited the entire function, skipping createEntryDataForVariantEntry().
   This meant data-for-variant-entry.json was never written whenever a stack had no
   environments, causing the variant-entries module to warn about the missing file and
   skip all variant entry imports. Fixed by removing the return.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…DX-9469

Revert changes to audiences.test.ts, variant-entries.test.ts, and
package.json (test script narrowing) — these were workarounds for the
ESM/uuid runner issue and are not part of the DX-9469 fix scope.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cs-raj cs-raj requested a review from a team as a code owner July 10, 2026 03:22
@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 0 0 25 ✅ Passed
🟡 Medium Severity 0 1 500 ✅ Passed
🔵 Low Severity 0 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 0 30 / 120 days ✅ Passed
🟡 Medium 0 1 90 / 365 days ⚠️ Warning
🔵 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: 0
  • Medium without fixes: 1
  • Low without fixes: 0

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

Consider reviewing these vulnerabilities when fixes become available.

@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 0 0 25 ✅ Passed
🟡 Medium Severity 0 1 500 ✅ Passed
🔵 Low Severity 0 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 0 30 / 120 days ✅ Passed
🟡 Medium 0 1 90 / 365 days ⚠️ Warning
🔵 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: 0
  • Medium without fixes: 1
  • Low without fixes: 0

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

Consider reviewing these vulnerabilities when fixes become available.

cs-raj and others added 2 commits July 10, 2026 08:59
…h empty envs [DX-9469]

After removing the bare return, publishEntries is called even when no
environments exist — serializePublishEntries handles empty envs as a
no-op. Updated test assertion to reflect this and added assertion that
createEntryDataForVariantEntry always runs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@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 0 0 25 ✅ Passed
🟡 Medium Severity 0 1 500 ✅ Passed
🔵 Low Severity 0 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 0 30 / 120 days ✅ Passed
🟡 Medium 0 1 90 / 365 days ⚠️ Warning
🔵 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: 0
  • Medium without fixes: 1
  • Low without fixes: 0

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

Consider reviewing these vulnerabilities when fixes become available.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Fixes two import-pipeline bugs that could (a) prevent Content Type ↔ Variant Group linking across an entire Personalize import when some experiences have no valid variants, and (b) skip generating the variant-entry data file when the stack has no environments—causing variant entry imports to be skipped.

Changes:

  • Personalize experiences import: only poll/validate variant-group creation for experiences where at least one valid version/variant was actually submitted; add a guard when variant groups are missing during CT attachment.
  • Entries import: remove the early return on missing environments so createEntryDataForVariantEntry() always runs.
  • Add focused unit tests and mock fixtures covering empty-audience / lytics-only / mixed / missing-versions-file scenarios.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/contentstack-variants/src/import/experiences.ts Tracks only experiences with valid variants for validation polling; adds null-guard before CT attachment to missing variant groups.
packages/contentstack-variants/src/import/variant-entries.ts Downgrades missing data-for-variant-entry.json message from warn to debug and clarifies “no variant entries to import”.
packages/contentstack-import/src/import/modules/entries.ts Removes early return on missing environments so variant entry data file creation always occurs.
packages/contentstack-import/test/unit/import/modules/entries.test.ts Updates expectations to ensure publish path is exercised and variant-entry data file creation always runs.
packages/contentstack-variants/test/unit/import/experiences.test.ts Adds unit/integration tests for importExperienceVersions and pending validation list behavior.
packages/contentstack-variants/test/unit/mock/contents/personalize/experiences/experiences.json Adds experience fixtures used by tests.
packages/contentstack-variants/test/unit/mock/contents/personalize/experiences/experiences-content-types.json Adds content-type mapping fixtures for CT attachment tests.
packages/contentstack-variants/test/unit/mock/contents/personalize/experiences/versions/*.json Adds version fixtures for valid/mixed/lytics-only/empty/draft scenarios.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

log.debug(`Mapped version with status: ${versionReqObj.status}`, this.config.context);
} else if (versionReqObj?.status && !(versionReqObj.variants?.length ?? 0)) {
log.warn(`Skipping version ${versionReqObj.status}: no valid variants (all had unmapped Lytics audiences)`, this.config.context);
log.warn(`Skipping version ${versionReqObj.status}: no valid variants after audience mapping — variants may have had no audiences or all audiences were unmapped`, this.config.context);
Comment thread packages/contentstack-import/src/import/modules/entries.ts
- experiences.ts: pass versionReqObj (already audience-mapped) to
  lookUpEvents instead of original version — was relying on mutation
  side effect of lookUpAudiences to make this work

- entries.ts: skip publish loop and success log entirely when envs is
  empty, instead of running the loop as a no-op; createEntryDataForVariantEntry
  still runs unconditionally afterward

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@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 0 0 25 ✅ Passed
🟡 Medium Severity 0 1 500 ✅ Passed
🔵 Low Severity 0 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 0 30 / 120 days ✅ Passed
🟡 Medium 0 1 90 / 365 days ⚠️ Warning
🔵 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: 0
  • Medium without fixes: 1
  • Low without fixes: 0

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

Consider reviewing these vulnerabilities when fixes become available.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Comment thread packages/contentstack-variants/src/import/experiences.ts
…X-9469]

If versionReqObj.status was e.g. PAUSED (not PAUSE), the previous code
silently added a 4th key to the Record-typed map, causing Object.values
to find a non-undefined entry and return true — even though
handleVersionUpdateOrCreate only destructures ACTIVE/DRAFT/PAUSE and
would have processed nothing. The experience UID then incorrectly entered
the polling pending list, reintroducing the timeout bug.

Fix: narrow versionMap to a typed object with only the three known keys
and validate status against a HANDLED_STATUSES set before writing.
Unrecognized statuses are logged and skipped.

Also aligns the variant-skip warning message with the generic wording
adopted in fix/DX-9469-v2.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@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 0 0 25 ✅ Passed
🟡 Medium Severity 0 1 500 ✅ Passed
🔵 Low Severity 0 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 0 30 / 120 days ✅ Passed
🟡 Medium 0 1 90 / 365 days ⚠️ Warning
🔵 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: 0
  • Medium without fixes: 1
  • Low without fixes: 0

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

Consider reviewing these vulnerabilities when fixes become available.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Comment thread packages/contentstack-variants/src/import/experiences.ts

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Comment on lines 86 to 89
log.debug(`Checking for variant entry data file: ${filePath}`, this.config.context);
if (!existsSync(filePath)) {
log.warn(`Variant entry data file not found at path: ${filePath}`, this.config.context);
log.debug(`No variant entries to import (data-for-variant-entry.json not found at: ${filePath})`, this.config.context);
return;
Comment on lines 276 to 299
// Publishing entries
if (!this.importConfig.skipEntriesPublish) {
log.info('Starting entry publishing process...', this.importConfig.context);
this.envs = fileHelper.readFileSync(this.envPath) || {};
if (Object.keys(this.envs).length === 0) {
log.warn(
`No environments file found at ${this.envPath}. Entries will not be published.`,
this.importConfig.context,
);
return;
} else {
log.debug(`Loaded ${Object.keys(this.envs).length} environments.`, this.importConfig.context);
}

for (let entryRequestOption of entryRequestOptions) {
await this.publishEntries(entryRequestOption).catch((error) => {
handleAndLogError(
error,
{ ...this.importConfig.context, cTUid: entryRequestOption.cTUid, locale: entryRequestOption.locale },
`Error in publishing entries of ${entryRequestOption.cTUid} in locale ${entryRequestOption.locale}`,
);
});
for (let entryRequestOption of entryRequestOptions) {
await this.publishEntries(entryRequestOption).catch((error) => {
handleAndLogError(
error,
{ ...this.importConfig.context, cTUid: entryRequestOption.cTUid, locale: entryRequestOption.locale },
`Error in publishing entries of ${entryRequestOption.cTUid} in locale ${entryRequestOption.locale}`,
);
});
}
log.success('All the entries have been published successfully', this.importConfig.context);
}
log.success('All the entries have been published successfully', this.importConfig.context);
} else {
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