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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -372,3 +372,4 @@ app.json

# Test coverage (global)
coverage/
export-stack/
28 changes: 16 additions & 12 deletions api/src/utils/entry-update.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,20 +122,21 @@ export const removeEntriesFromDatabase = async (projectId: string, loggerPath?:

for (const localeDir of localeDirs) {
const localeCode = localeDir.name;
// Skip delta cleanup only when this is a restart AND the locale was never migrated
// before β€” newly-added locales have no prior state to diff against, so the regular
// full-import pipeline should pick them up untouched. On iteration 1 we always
// process (the function may be a no-op then, but tests/legacy code can still call it).
if (
// True when this locale is brand-new in this delta run (wasn't migrated before).
// We still need to process its entry files: source entries that already exist in
// Contentstack (migrated in a prior iteration with a different locale mapping) must
// be LOCALIZED on their existing CS UID rather than imported as duplicate new entries.
// Entries with no prior CS UID are left in the import data to be created fresh.
const isNewLocaleInDelta =
(projectData?.iteration ?? 1) > 1 &&
isFullMigrationForLocale(projectData ?? {}, localeCode)
) {
isFullMigrationForLocale(projectData ?? {}, localeCode);

if (isNewLocaleInDelta) {
writeLogEntry(
`Skipping delta cleanup for new locale "${localeCode}" β€” full import.`,
`New locale "${localeCode}" in delta run β€” routing existing entries through localize path instead of re-creating.`,
"removeEntriesFromDatabase",
loggerPath,
);
continue;
}
// entry_mapper rows are tagged with the SOURCE locale code (e.g. "en-IN");
// directories on disk use the DESTINATION code (e.g. "en-in"). Translate.
Expand Down Expand Up @@ -195,16 +196,19 @@ export const removeEntriesFromDatabase = async (projectId: string, loggerPath?:
const row =
(sourceLocale && rowByUidAndLang.get(`${key}::${sourceLocale}`)) ||
rowByUid.get(key);
if (row?.isUpdate) {
// For a new locale in a delta run, ALL entries with an existing CS UID
// must be localized (not re-created). For existing locales the user must
// explicitly mark entries with isUpdate to include them in the update pass.
if (row?.isUpdate || isNewLocaleInDelta) {
const entryData = { ...data[key], __locale: localeCode, __csUid: csEntryUid };
delete entryData?.uid;

if (!entriesToUpdate[contentTypeName]) {
entriesToUpdate[contentTypeName] = {};
}
entriesToUpdate[contentTypeName][`${csEntryUid}::${localeCode}`] = entryData;
writeLogEntry(`Collected update entry "${csEntryUid}" (locale "${localeCode}") for content type "${contentTypeName}"`, "removeEntriesFromDatabase", loggerPath);
writeLogEntry(`Entry "${key}" has been prepared for update in Contentstack as "${csEntryUid}" (locale "${localeCode}")`, "removeEntriesFromDatabase", loggerPath);
writeLogEntry(`Collected ${isNewLocaleInDelta ? 'localize' : 'update'} entry "${csEntryUid}" (locale "${localeCode}") for content type "${contentTypeName}"`, "removeEntriesFromDatabase", loggerPath);
writeLogEntry(`Entry "${key}" has been prepared for ${isNewLocaleInDelta ? 'localization' : 'update'} in Contentstack as "${csEntryUid}" (locale "${localeCode}")`, "removeEntriesFromDatabase", loggerPath);
}

// Existing entry β†’ remove from import data so it is NOT re-created.
Expand Down
10 changes: 8 additions & 2 deletions ui/src/components/ContentMapper/entryMapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ const EntryMapper = ({ handleStepChange }: entryMapperProps) => {
plural: `${totalCounts === 0 ? 'Count' : ''}`
}}
/>
{totalCounts > 0 && (
{(totalCounts > 0 || (tableData?.length ?? 0) > 0) && (
<div className="mapper-footer">
<div>
{/* Total Entries: <strong>{totalCounts}</strong> */}
Expand All @@ -670,7 +670,13 @@ const EntryMapper = ({ handleStepChange }: entryMapperProps) => {
className="saveButton"
onClick={handleSaveContentType}
version="v2"
disabled={newMigrationData?.project_current_step > 4}
// Lock the Save button only while a migration is actively in flight.
// Using migrationStarted alone would permanently lock revisits on delta
// iterations since migrationStarted stays true after completion.
disabled={
!!newMigrationData?.migration_execution?.migrationStarted &&
!newMigrationData?.migration_execution?.migrationCompleted
}
isLoading={isLoadingSaveButton}
>
Save
Expand Down
8 changes: 5 additions & 3 deletions ui/src/components/ContentMapper/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -560,10 +560,12 @@ div .table-row {
}

// With pagination on, the venus pagination bar renders at the bottom of the table.
// Let the Total/Save footer flow below it (not sticky-overlapping) so the two bars
// don't collide and the Save button isn't clipped at the viewport edge.
// Pin the Total/Save footer to the viewport bottom so it stays reachable when the
// table + pagination combined exceed the available viewport height. Without this the
// Save button gets clipped off-screen on small windows (Map Entry step 4).
.mapper-footer {
position: static;
position: sticky;
bottom: 0;
}
}

Expand Down
Loading
Loading