From 1c63c1384d6d4162050cbc9d08527eaa8411b837 Mon Sep 17 00:00:00 2001 From: Pritesh Jain Date: Sun, 1 Nov 2026 22:00:44 +0530 Subject: [PATCH] fix(sap-smartedit): keep the SAP label when the entry title falls back to the id When a type's label column is blank or shared on some rows, the api titles those entries by source id. That overwrote the only copy of the label (two nav nodes titled Accessories lost it). Such types now also map the column to source_. Co-Authored-By: Claude Opus 5.5 --- .../libs/contentTypes.ts | 28 ++++++++++++- .../nav-node-label.impex | 12 ++++++ .../contentTypes.sourceLabel.test.ts | 42 +++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 upload-api/tests/fixtures/sap-smartedit-source-label/nav-node-label.impex create mode 100644 upload-api/tests/unit/migration-sap-smartedit/contentTypes.sourceLabel.test.ts diff --git a/upload-api/migration-sap-smartedit/libs/contentTypes.ts b/upload-api/migration-sap-smartedit/libs/contentTypes.ts index dc9fa0e2..a785dc59 100644 --- a/upload-api/migration-sap-smartedit/libs/contentTypes.ts +++ b/upload-api/migration-sap-smartedit/libs/contentTypes.ts @@ -3,7 +3,7 @@ import path from 'path'; import config from '../config/index.json'; import { CT, DataConfig, Field } from '../interface/interface'; import mapField, { baseField } from './schemaMapper'; -import { ensureDir, writeJson, parseImpexPath, ImpexColumnDef } from '../utils/helper'; +import { ensureDir, writeJson, parseImpexPath, ImpexColumnDef, ImpexTypeBlock } from '../utils/helper'; const { contentTypes: contentTypesConfig } = config.modules; const contentTypeFolderPath = path.resolve(config.data, contentTypesConfig.dirName); @@ -252,6 +252,31 @@ function ensureMandatoryFields(fieldMapping: Field[]): void { urlField.advanced = { ...urlField.advanced, mandatory: true }; } +/** + * The api writes the source id as the entry title when a row's label is blank or + * shared by several items (two CMSNavigationNodes both titled "Accessories"), so + * titles stay unique. That overwrote the only copy of the label: the SAP text was + * lost, and a storefront could not tell "untitled in SAP" from a real title. When + * any row would fall back like that, keep the label column as its own field too. + */ +function keepSourceLabel(fieldMapping: Field[], block: ImpexTypeBlock): void { + const titleField = fieldMapping.find((f) => f.contentstackFieldUid === 'title'); + const col = titleField?.otherCmsField; + if (!col || !block.columns.has(col)) return; // synthesized title: no label column to keep + const owners = new Map>(); + let blank = false; + for (const row of block.rows) { + const label = row[col]; + if (!label) { blank = true; continue; } + if (!owners.has(label)) owners.set(label, new Set()); + owners.get(label)!.add(row.uid ?? row.code ?? JSON.stringify(row)); + } + if (!blank && [...owners.values()].every((ids) => ids.size === 1)) return; + const field = baseField(`source_${col}`, 'string', 'single_line_text'); + field.otherCmsField = col; + fieldMapping.push(field); +} + /** * Parse the ImpEx export into Contentstack content-type schemas. * @@ -327,6 +352,7 @@ async function extractContentTypes( } ensureMandatoryFields(fieldMapping); + keepSourceLabel(fieldMapping, block); const contentType = { otherCmsTitle: type, diff --git a/upload-api/tests/fixtures/sap-smartedit-source-label/nav-node-label.impex b/upload-api/tests/fixtures/sap-smartedit-source-label/nav-node-label.impex new file mode 100644 index 00000000..691b058b --- /dev/null +++ b/upload-api/tests/fixtures/sap-smartedit-source-label/nav-node-label.impex @@ -0,0 +1,12 @@ +INSERT_UPDATE CMSNavigationNode; uid[unique=true]; title[lang=en]; parent(uid) + ; AccessoriesNavNode; Accessories; Root + ; AccessoriesCatNavNode; Accessories; Root + ; LeafNavNode; ; Root + ; BrandsNavNode; Brands; Root + +INSERT_UPDATE CMSParagraphComponent; uid[unique=true]; name; content + ; para1; First Paragraph;

a

+ ; para2; Second Paragraph;

b

+ +INSERT_UPDATE CMSNavigationEntry; uid[unique=true]; navigationNode(uid); item(uid) + ; entry1; LeafNavNode; para1 diff --git a/upload-api/tests/unit/migration-sap-smartedit/contentTypes.sourceLabel.test.ts b/upload-api/tests/unit/migration-sap-smartedit/contentTypes.sourceLabel.test.ts new file mode 100644 index 00000000..25105b63 --- /dev/null +++ b/upload-api/tests/unit/migration-sap-smartedit/contentTypes.sourceLabel.test.ts @@ -0,0 +1,42 @@ +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import path from 'path'; +import fs from 'fs'; +import { extractContentTypes } from '../../../migration-sap-smartedit/index'; + +/** + * The api titles an entry by its source id when the label is blank or shared, which + * overwrote the only copy of the SAP label (two nav nodes titled "Accessories" lost + * it). Types where that fallback can happen keep the label in its own field. + */ +const FIXTURE = path.join(__dirname, '../../fixtures/sap-smartedit-source-label/nav-node-label.impex'); + +let cts: any[] = []; +const fields = (type: string) => cts.find((c) => c.otherCmsTitle === type)?.fieldMapping ?? []; + +beforeAll(async () => { + cts = (await extractContentTypes('cs', FIXTURE, {} as any)) as any[]; +}); + +afterAll(() => { + fs.rmSync(path.join(process.cwd(), 'cmsMigrationData'), { recursive: true, force: true }); +}); + +describe('keepSourceLabel — the label survives a title that falls back to the id', () => { + it('adds source_title, read from the title column, when labels are shared or blank', () => { + const f = fields('CMSNavigationNode').find((x: any) => x.contentstackFieldUid === 'source_title'); + expect(f?.otherCmsField).toBe('title'); + expect(f?.contentstackFieldType).toBe('single_line_text'); + }); + + it('keeps title itself mapped to the same column', () => { + expect(fields('CMSNavigationNode').find((x: any) => x.contentstackFieldUid === 'title')?.otherCmsField).toBe('title'); + }); + + it('adds nothing when the type has no label column (title is synthesized)', () => { + expect(fields('CMSNavigationEntry').some((x: any) => x.contentstackFieldUid.startsWith('source_'))).toBe(false); + }); + + it('adds nothing when every label is present and unique', () => { + expect(fields('CMSParagraphComponent').some((x: any) => x.contentstackFieldUid.startsWith('source_'))).toBe(false); + }); +});