Skip to content

Commit b31423d

Browse files
feat: Assess mode functional issues with Flexcard (#299)
1 parent 17159cb commit b31423d

File tree

16 files changed

+391
-111
lines changed

16 files changed

+391
-111
lines changed

messages/assess.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,5 +84,12 @@
8484
"fileUpdatedToAllowRemoteCalls": "File updated to allow remote calls",
8585
"fileUpdatedToAllowCalls": "File updated to allow calls",
8686
"fileImplementsVlocityOpenInterface": "file %s implements VlocityOpenInterface no changes will be applied",
87-
"methodCallBundleNameUpdated": "Method call bundle name will be updated in %s for class %s method %s"
88-
}
87+
"methodCallBundleNameUpdated": "Method call bundle name will be updated in %s for class %s method %s",
88+
"cardNameChangeMessage": "Card name will be changed from {0} to {1} to follow API naming standards",
89+
"authordNameChangeMessage": "Author name will be changed from {0} to {1} to follow API naming standards",
90+
"omniScriptNameChangeMessage": "OmniScript reference part {0} will be changed to {1} during migration.",
91+
"dataRaptorNameChangeMessage": "DataRaptor reference {0} will be changed to {1} during migration.",
92+
"integrationProcedureNameChangeMessage": "Integration Procedure reference {0} will be changed to {1} during migration.",
93+
"integrationProcedureManualUpdateMessage": "Integration Procedure reference {0} may need manual updates after migration."
94+
95+
}

messages/migrate.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,12 @@
7272
"metadataRetrieved": "Metadata %s retrieved from %s",
7373
"deployingMetadata": "Deploying metadata %s to %s",
7474
"metadataDeployed": "Metadata %s deployed to %s",
75-
"sfProjectCommandError": "Error executing command: %s"
76-
}
75+
"sfProjectCommandError": "Error executing command: %s",
76+
"dataRaptorNameChangeMessage": "DataRaptor reference {0} will be changed to {1} during migration.",
77+
"integrationProcedureNameChangeMessage": "Integration Procedure reference {0} will be changed to {1} during migration.",
78+
"integrationProcedureManualUpdateMessage": "Integration Procedure reference {0} may need manual updates after migration.",
79+
"alreadyStandardModel": "The org is already on standard data model.",
80+
"cardAuthorNameChangeMessage": "Card author name has been modified to fit naming rules: {0}",
81+
"cardNameChangeMessage": "Card name has been modified to fit naming rules: {0}",
82+
"duplicateCardNameMessage": "Potential duplicate: Another card has the same name {0} after name cleaning. This may cause conflicts during migration",
83+
}

src/migration/base.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ export type ComponentType = 'Data Mapper' | 'Flexcard' | 'Omniscript and Integra
1818
*/
1919
export const createProgressBar = (action: string, componentType: ComponentType): cliProgress.SingleBar => {
2020
return new cliProgress.SingleBar({
21-
format: `${action} ${componentType} |${componentType === 'Omniscript and Integration Procedure' ? '' : '\t\t\t\t'} {bar} | {percentage}% || {value}/{total} Tasks`,
21+
format: `${action} ${componentType} |${
22+
componentType === 'Omniscript and Integration Procedure' ? '' : '\t\t\t\t'
23+
} {bar} | {percentage}% || {value}/{total} Tasks`,
2224
barCompleteChar: '\u2588',
2325
barIncompleteChar: '\u2591',
2426
hideCursor: true,

src/migration/flexcard.ts

Lines changed: 228 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { UX } from '@salesforce/command';
1010
import { FlexCardAssessmentInfo } from '../../src/utils';
1111
import { Logger } from '../utils/logger';
1212
import { createProgressBar } from './base';
13+
import { Constants } from '../utils/constants/stringContants';
1314

1415
export class CardMigrationTool extends BaseMigrationTool implements MigrationTool {
1516
static readonly VLOCITYCARD_NAME = 'VlocityCard__c';
@@ -113,21 +114,53 @@ export class CardMigrationTool extends BaseMigrationTool implements MigrationToo
113114
const flexCardAssessmentInfos: FlexCardAssessmentInfo[] = [];
114115
let progressCounter = 0;
115116
const progressBar = createProgressBar('Assessing', 'Flexcard');
116-
progressBar.start(flexCards.length, progressCounter);
117+
progressBar.start(flexCards.length, progressCounter); const uniqueNames = new Set<string>();
118+
117119
const limitedFlexCards = flexCards.slice(0, 200);
118120

119121
// Now process each OmniScript and its elements
120122
for (const flexCard of limitedFlexCards) {
121-
Logger.info(this.messages.getMessage('processingFlexCard', [flexCard['Name']]));
123+
const flexCardName = flexCard['Name'];
124+
Logger.info(this.messages.getMessage('processingFlexCard', [flexCardName]));
122125
const flexCardAssessmentInfo: FlexCardAssessmentInfo = {
123-
name: flexCard['Name'],
126+
name: flexCardName,
124127
id: flexCard['Id'],
125128
dependenciesIP: [],
126129
dependenciesDR: [],
127130
dependenciesOS: [],
131+
dependenciesLWC: [],
128132
infos: [],
129133
warnings: [],
130134
};
135+
136+
// Check for name changes due to API naming requirements
137+
const originalName:string = flexCardName;
138+
const cleanedName:string = this.cleanName(originalName);
139+
if (cleanedName !== originalName) {
140+
flexCardAssessmentInfo.warnings.push(
141+
this.messages.getMessage('cardNameChangeMessage', [originalName, cleanedName])
142+
);
143+
}
144+
145+
// Check for duplicate names
146+
if (uniqueNames.has(cleanedName)) {
147+
flexCardAssessmentInfo.warnings.push(
148+
this.messages.getMessage('duplicateCardNameMessage', [cleanedName])
149+
);
150+
}
151+
uniqueNames.add(cleanedName);
152+
153+
// Check for author name changes
154+
const originalAuthor = flexCard[this.namespacePrefix + 'Author__c'];
155+
if (originalAuthor) {
156+
const cleanedAuthor = this.cleanName(originalAuthor);
157+
if (cleanedAuthor !== originalAuthor) {
158+
flexCardAssessmentInfo.warnings.push(
159+
this.messages.getMessage('authordNameChangeMessage', [originalAuthor, cleanedAuthor])
160+
);
161+
}
162+
}
163+
131164
this.updateDependencies(flexCard, flexCardAssessmentInfo);
132165
flexCardAssessmentInfos.push(flexCardAssessmentInfo);
133166
progressBar.update(++progressCounter);
@@ -136,16 +169,197 @@ export class CardMigrationTool extends BaseMigrationTool implements MigrationToo
136169
}
137170

138171
private updateDependencies(flexCard, flexCardAssessmentInfo): void {
139-
let dataSource = JSON.parse(flexCard[this.namespacePrefix + 'Datasource__c']);
140-
if (dataSource?.datasource) {
172+
let dataSource = JSON.parse(flexCard[this.namespacePrefix + 'Datasource__c'] || '{}');
173+
// Handle both camelCase and lowercase variants
174+
if (dataSource?.dataSource) {
141175
dataSource = dataSource.dataSource;
176+
} else if (dataSource?.datasource) {
177+
dataSource = dataSource.datasource;
178+
}
179+
180+
// Check if it's a DataRaptor source
181+
if (dataSource.type === Constants.DataRaptorComponentName) {
182+
const originalBundle = dataSource.value?.bundle;
183+
if (originalBundle) {
184+
const cleanedBundle:string = this.cleanName(originalBundle);
185+
flexCardAssessmentInfo.dependenciesDR.push(cleanedBundle);
186+
187+
// Add warning if DataRaptor name will change
188+
if (originalBundle !== cleanedBundle) {
189+
flexCardAssessmentInfo.warnings.push(
190+
this.messages.getMessage('dataRaptorNameChangeMessage', [originalBundle, cleanedBundle])
191+
);
192+
}
193+
}
194+
} else if (dataSource.type === Constants.IntegrationProcedurePluralName) {
195+
const originalIpMethod = dataSource.value?.ipMethod;
196+
if (originalIpMethod) {
197+
const parts = originalIpMethod.split('_');
198+
const cleanedParts = parts.map((p) => this.cleanName(p, true));
199+
const cleanedIpMethod = cleanedParts.join('_');
200+
201+
flexCardAssessmentInfo.dependenciesIP.push(cleanedIpMethod);
202+
203+
// Add warning if IP name will change
204+
if (originalIpMethod !== cleanedIpMethod) {
205+
flexCardAssessmentInfo.warnings.push(
206+
this.messages.getMessage('integrationProcedureNameChangeMessage', [originalIpMethod, cleanedIpMethod])
207+
);
208+
}
209+
210+
// Add warning for IP references with more than 2 parts (which potentially need manual updates)
211+
if (parts.length > 2) {
212+
flexCardAssessmentInfo.warnings.push(
213+
this.messages.getMessage('integrationProcedureManualUpdateMessage', [originalIpMethod])
214+
);
215+
}
216+
}
217+
}
218+
219+
// Check for OmniScript dependencies in the card's definition
220+
try {
221+
const definition = JSON.parse(flexCard[this.namespacePrefix + 'Definition__c'] || '{}');
222+
if (definition && definition.states) {
223+
for (const state of definition.states) {
224+
if (state.omniscripts && Array.isArray(state.omniscripts)) {
225+
for (const os of state.omniscripts) {
226+
if (os.type && os.subtype) {
227+
const osRef = `${os.type}_${os.subtype}_${os.language || 'English'}`;
228+
flexCardAssessmentInfo.dependenciesOS.push(osRef);
229+
}
230+
}
231+
}
232+
233+
// Also check for omniscripts referenced in component actions
234+
if (state.components) {
235+
for (const componentKey in state.components) {
236+
if (state.components.hasOwnProperty(componentKey)) {
237+
const component = state.components[componentKey];
238+
this.checkComponentForDependencies(component, flexCardAssessmentInfo);
239+
}
240+
}
241+
}
242+
}
243+
}
244+
} catch (err) {
245+
// Log the error but continue processing
246+
Logger.error(`Error parsing definition for card ${flexCard.Name}: ${err.message}`);
142247
}
143-
if (dataSource['type'] === 'DataRaptor') {
144-
flexCardAssessmentInfo.dependenciesDR.push(dataSource['value']['bundle']);
145-
} else if (dataSource.type === 'IntegrationProcedures') {
146-
flexCardAssessmentInfo.dependenciesIP.push(dataSource['value']['ipMethod']);
248+
}
249+
250+
private checkComponentForDependencies(component: any, flexCardAssessmentInfo: FlexCardAssessmentInfo): void {
251+
// Check if this component is an action element
252+
if (component.element === 'action' && component.property && component.property.actionList) {
253+
// Process each action in the actionList
254+
for (const action of component.property.actionList) {
255+
if (action.stateAction) {
256+
// Case 1: Direct OmniScript reference
257+
if (action.stateAction.type === Constants.OmniScriptComponentName && action.stateAction.omniType) {
258+
const omniType = action.stateAction.omniType;
259+
if (omniType.Name && typeof omniType.Name === 'string') {
260+
const originalName = omniType.Name;
261+
const parts = originalName.split('/');
262+
263+
if (parts.length >= 2) {
264+
// Check for name changes in each part
265+
const cleanedParts = parts.map((p) => this.cleanName(p));
266+
const cleanedName = cleanedParts.join('_');
267+
flexCardAssessmentInfo.dependenciesOS.push(cleanedName);
268+
269+
// Add warning if any part of the name will change
270+
for (let i = 0; i < parts.length; i++) {
271+
if (parts[i] !== cleanedParts[i]) {
272+
flexCardAssessmentInfo.warnings.push(
273+
this.messages.getMessage('omniScriptNameChangeMessage', [parts[i], cleanedParts[i]])
274+
);
275+
}
276+
}
277+
}
278+
}
279+
}
280+
281+
// Case 2: Flyout OmniScript reference
282+
else if (
283+
action.stateAction.type === 'Flyout' &&
284+
action.stateAction.flyoutType === Constants.OmniScriptPluralName &&
285+
action.stateAction.osName
286+
) {
287+
const osName = action.stateAction.osName;
288+
if (typeof osName === 'string') {
289+
// osName is typically in format "Omniscript/Testing/English"
290+
const originalName = osName;
291+
const parts = originalName.split('/');
292+
293+
if (parts.length >= 2) {
294+
// Check for name changes in each part
295+
const cleanedParts = parts.map((p) => this.cleanName(p));
296+
const cleanedName = cleanedParts.join('_');
297+
flexCardAssessmentInfo.dependenciesOS.push(cleanedName);
298+
299+
// Add warning if any part of the name will change
300+
for (let i = 0; i < parts.length; i++) {
301+
if (parts[i] !== cleanedParts[i]) {
302+
flexCardAssessmentInfo.warnings.push(
303+
this.messages.getMessage('omniScriptNameChangeMessage', [parts[i], cleanedParts[i]])
304+
);
305+
}
306+
}
307+
}
308+
}
309+
}
310+
}
311+
}
312+
}
313+
314+
// Check for Custom LWC component
315+
if (component.element === 'customLwc' && component.property) {
316+
// Check customlwcname property
317+
/*if (component.property.customlwcname) {
318+
flexCardAssessmentInfo.dependenciesLWC.push(component.property.customlwcname);
319+
} */
320+
321+
// Also check customLwcData if available (has more details)
322+
if (component.property.customLwcData) {
323+
const lwcData = component.property.customLwcData;
324+
325+
// Use DeveloperName as a more reliable identifier
326+
if (lwcData.DeveloperName) {
327+
const lwcName = lwcData.NamespacePrefix
328+
? `${lwcData.NamespacePrefix}.${lwcData.DeveloperName}`
329+
: lwcData.DeveloperName;
330+
331+
// Avoid duplicates
332+
if (!flexCardAssessmentInfo.dependenciesLWC.includes(lwcName)) {
333+
flexCardAssessmentInfo.dependenciesLWC.push(lwcName);
334+
}
335+
}
336+
}
337+
}
338+
339+
// Check standard component actions if they exist
340+
if (component.actions && Array.isArray(component.actions)) {
341+
for (const action of component.actions) {
342+
if (action.stateAction && action.stateAction.omniType) {
343+
const omniType = action.stateAction.omniType;
344+
if (omniType.Name && typeof omniType.Name === 'string') {
345+
const parts = omniType.Name.split('/');
346+
if (parts.length >= 2) {
347+
const osRef = parts.join('_');
348+
flexCardAssessmentInfo.dependenciesOS.push(osRef);
349+
}
350+
}
351+
}
352+
}
353+
}
354+
355+
// Check child components recursively
356+
if (component.children && Array.isArray(component.children)) {
357+
for (const child of component.children) {
358+
this.checkComponentForDependencies(child, flexCardAssessmentInfo);
359+
}
147360
}
148361
}
362+
149363
// Query all cards that are active
150364
private async getAllActiveCards(): Promise<AnyJson[]> {
151365
//DebugTimer.getInstance().lap('Query Vlocity Cards');
@@ -267,21 +481,21 @@ export class CardMigrationTool extends BaseMigrationTool implements MigrationToo
267481
uploadResult.warnings = uploadResult.warnings || [];
268482
if (transformedCardAuthorName !== card[this.namespacePrefix + 'Author__c']) {
269483
uploadResult.warnings.unshift(
270-
'WARNING: Card author name has been modified to fit naming rules: ' + transformedCardAuthorName
484+
this.messages.getMessage('cardAuthorNameChangeMessage', [transformedCardAuthorName])
271485
);
272486
}
273487
if (transformedCardName !== card['Name']) {
274488
uploadResult.newName = transformedCardName;
275489
uploadResult.warnings.unshift(
276-
'WARNING: Card name has been modified to fit naming rules: ' + transformedCardName
490+
this.messages.getMessage('cardNameChangeMessage', [transformedCardName])
277491
);
278492
}
279493

280494
if (uploadResult.id && invalidIpNames.size > 0) {
281495
const val = Array.from(invalidIpNames.entries())
282496
.map((e) => e[0])
283497
.join(', ');
284-
uploadResult.errors.push('Integration Procedure Actions will need manual updates, please verify: ' + val);
498+
uploadResult.errors.push(this.messages.getMessage('integrationProcedureManualUpdateMessage', [val]));
285499
}
286500

287501
cardsUploadInfo.set(recordId, uploadResult);
@@ -396,9 +610,9 @@ export class CardMigrationTool extends BaseMigrationTool implements MigrationToo
396610
const datasource = JSON.parse(mappedObject[CardMappings.Datasource__c] || '{}');
397611
if (datasource.dataSource) {
398612
const type = datasource.dataSource.type;
399-
if (type === 'DataRaptor') {
613+
if (type === Constants.DataRaptorComponentName) {
400614
datasource.dataSource.value.bundle = this.cleanName(datasource.dataSource.value.bundle);
401-
} else if (type === 'IntegrationProcedures') {
615+
} else if (type === Constants.IntegrationProcedurePluralName) {
402616
const ipMethod: string = datasource.dataSource.value.ipMethod || '';
403617

404618
const parts = ipMethod.split('_');

0 commit comments

Comments
 (0)