Skip to content

Commit 166ce9a

Browse files
Merge pull request #442 from sf-aastha-paruthi/u/aparuthi/StandardModelFixesBlitz
Blitz fixes - P1 issues
2 parents 607fb4b + 5357e67 commit 166ce9a

File tree

8 files changed

+780
-5
lines changed

8 files changed

+780
-5
lines changed

src/migration/dataraptor.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { Logger } from '../utils/logger';
2525
import { createProgressBar } from './base';
2626
import { Constants } from '../utils/constants/stringContants';
2727
import { isStandardDataModel } from '../utils/dataModelService';
28+
import { prioritizeCleanNamesFirst } from '../utils/recordPrioritization';
2829

2930
export class DataRaptorMigrationTool extends BaseMigrationTool implements MigrationTool {
3031
static readonly DRBUNDLE_NAME = 'DRBundle__c';
@@ -431,7 +432,7 @@ export class DataRaptorMigrationTool extends BaseMigrationTool implements Migrat
431432
// Get All DRBundle__c records
432433
private async getAllDataRaptors(): Promise<AnyJson[]> {
433434
//DebugTimer.getInstance().lap('Query DRBundle');
434-
return await QueryTools.queryAll(
435+
const dataRaptors = await QueryTools.queryAll(
435436
this.connection,
436437
this.getQueryNamespace(),
437438
this.getBundleObjectName(),
@@ -442,6 +443,13 @@ export class DataRaptorMigrationTool extends BaseMigrationTool implements Migrat
442443
}
443444
throw err;
444445
});
446+
447+
// Apply prioritization only for standard data model
448+
if (this.IS_STANDARD_DATA_MODEL) {
449+
return this.prioritizeDataRaptorsWithoutSpecialCharacters(dataRaptors);
450+
}
451+
452+
return dataRaptors;
445453
}
446454

447455
// Get All Items
@@ -609,4 +617,15 @@ export class DataRaptorMigrationTool extends BaseMigrationTool implements Migrat
609617
? DataRaptorMigrationTool.OMNIDATATRANSFORMITEM_NAME
610618
: DataRaptorMigrationTool.DRMAPITEM_NAME;
611619
}
620+
621+
/**
622+
* Prioritizes DataRaptors by name characteristics:
623+
* - Clean names (alphanumeric only) are processed first
624+
* - Names with special characters are processed after
625+
* This avoids naming conflicts during migration when special characters are cleaned
626+
*/
627+
private prioritizeDataRaptorsWithoutSpecialCharacters(dataRaptors: AnyJson[]): AnyJson[] {
628+
const nameField = this.getBundleFieldKey('Name');
629+
return prioritizeCleanNamesFirst(dataRaptors, nameField);
630+
}
612631
}

src/migration/flexcard.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { Constants } from '../utils/constants/stringContants';
2222
import { StorageUtil } from '../utils/storageUtil';
2323
import { getUpdatedAssessmentStatus } from '../utils/stringUtils';
2424
import { isStandardDataModel } from '../utils/dataModelService';
25+
import { prioritizeCleanNamesFirst } from '../utils/recordPrioritization';
2526

2627
export class CardMigrationTool extends BaseMigrationTool implements MigrationTool {
2728
static readonly VLOCITYCARD_NAME = 'VlocityCard__c';
@@ -159,6 +160,7 @@ export class CardMigrationTool extends BaseMigrationTool implements MigrationToo
159160
try {
160161
Logger.log(this.messages.getMessage('startingFlexCardAssessment'));
161162
const flexCards = await this.getAllCards();
163+
162164
Logger.log(this.messages.getMessage('foundFlexCardsToAssess', [flexCards.length]));
163165

164166
const flexCardsAssessmentInfos = await this.processCardComponents(flexCards);
@@ -734,12 +736,14 @@ export class CardMigrationTool extends BaseMigrationTool implements MigrationToo
734736
filters.set(this.namespacePrefix + 'CardType__c', 'flex');
735737
}
736738

739+
let flexCards: AnyJson[];
740+
737741
if (this.allVersions) {
738742
const sortFields = [
739743
{ field: 'Name', direction: SortDirection.ASC },
740744
{ field: this.getFieldKey('Version__c'), direction: SortDirection.ASC },
741745
];
742-
return await QueryTools.queryWithFilterAndSort(
746+
flexCards = await QueryTools.queryWithFilterAndSort(
743747
this.connection,
744748
this.getQueryNamespace(),
745749
this.getCardObjectName(),
@@ -756,7 +760,7 @@ export class CardMigrationTool extends BaseMigrationTool implements MigrationToo
756760
});
757761
} else {
758762
filters.set(this.getFieldKey('Active__c'), true);
759-
return await QueryTools.queryWithFilter(
763+
flexCards = await QueryTools.queryWithFilter(
760764
this.connection,
761765
this.getQueryNamespace(),
762766
this.getCardObjectName(),
@@ -769,6 +773,13 @@ export class CardMigrationTool extends BaseMigrationTool implements MigrationToo
769773
throw err;
770774
});
771775
}
776+
777+
// Apply prioritization only for standard data model
778+
if (this.IS_STANDARD_DATA_MODEL) {
779+
return this.prioritizeFlexCardsWithoutSpecialCharacters(flexCards);
780+
}
781+
782+
return flexCards;
772783
}
773784

774785
// Upload All the VlocityCard__c records to OmniUiCard for custom model and update references for standard
@@ -1767,4 +1778,15 @@ export class CardMigrationTool extends BaseMigrationTool implements MigrationToo
17671778
// Save the updated definition back to the mappedObject
17681779
mappedObject[CardMappings.Definition__c] = JSON.stringify(definition);
17691780
}
1781+
1782+
/**
1783+
* Prioritizes FlexCards by name characteristics:
1784+
* - Clean names (alphanumeric only) are processed first
1785+
* - Names with special characters are processed after
1786+
* This avoids naming conflicts during migration when special characters are cleaned
1787+
*/
1788+
private prioritizeFlexCardsWithoutSpecialCharacters(flexCards: AnyJson[]): AnyJson[] {
1789+
const nameField = this.getFieldKey('Name');
1790+
return prioritizeCleanNamesFirst(flexCards, nameField);
1791+
}
17701792
}

src/migration/omniscript.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { Logger } from '../utils/logger';
3939
import { createProgressBar } from './base';
4040
import { StorageUtil } from '../utils/storageUtil';
4141
import { isStandardDataModel } from '../utils/dataModelService';
42+
import { prioritizeCleanNamesFirst } from '../utils/recordPrioritization';
4243

4344
export class OmniScriptMigrationTool extends BaseMigrationTool implements MigrationTool {
4445
private readonly exportType: OmniScriptExportType;
@@ -1408,13 +1409,15 @@ export class OmniScriptMigrationTool extends BaseMigrationTool implements Migrat
14081409
filters.set(this.getFieldKey('IsProcedure__c'), false);
14091410
}
14101411

1412+
let omniscripts: AnyJson[];
1413+
14111414
if (this.allVersions) {
14121415
const sortFields = [
14131416
{ field: this.getFieldKey('Type__c'), direction: SortDirection.ASC },
14141417
{ field: this.getFieldKey('SubType__c'), direction: SortDirection.ASC },
14151418
{ field: this.getFieldKey('Version__c'), direction: SortDirection.ASC },
14161419
];
1417-
return await QueryTools.queryWithFilterAndSort(
1420+
omniscripts = await QueryTools.queryWithFilterAndSort(
14181421
this.connection,
14191422
this.getQueryNamespace(),
14201423
this.getOmniscriptObjectName(),
@@ -1431,7 +1434,7 @@ export class OmniScriptMigrationTool extends BaseMigrationTool implements Migrat
14311434
});
14321435
} else {
14331436
filters.set(this.getFieldKey('IsActive__c'), true);
1434-
return await QueryTools.queryWithFilter(
1437+
omniscripts = await QueryTools.queryWithFilter(
14351438
this.connection,
14361439
this.getQueryNamespace(),
14371440
this.getOmniscriptObjectName(),
@@ -1444,6 +1447,13 @@ export class OmniScriptMigrationTool extends BaseMigrationTool implements Migrat
14441447
throw err;
14451448
});
14461449
}
1450+
1451+
// Apply prioritization only for standard data model
1452+
if (this.IS_STANDARD_DATA_MODEL) {
1453+
return this.prioritizeOmniscriptsWithoutSpecialCharacters(omniscripts);
1454+
}
1455+
1456+
return omniscripts;
14471457
}
14481458

14491459
// Get All Elements w.r.t OmniScript__c i.e Elements tagged to passed in IP/OS
@@ -2135,6 +2145,19 @@ export class OmniScriptMigrationTool extends BaseMigrationTool implements Migrat
21352145
: Object.keys(OmniScriptDefinitionMappings);
21362146
}
21372147

2148+
/**
2149+
* Prioritizes OmniScripts by name characteristics:
2150+
* - Clean names (alphanumeric only) are processed first
2151+
* - Names with special characters are processed after
2152+
* This avoids naming conflicts during migration when special characters are cleaned
2153+
*/
2154+
private prioritizeOmniscriptsWithoutSpecialCharacters(omniscripts: AnyJson[]): AnyJson[] {
2155+
// Check both Type__c and SubType__c fields
2156+
const typeField = this.getFieldKey('Type__c');
2157+
const subTypeField = this.getFieldKey('SubType__c');
2158+
return prioritizeCleanNamesFirst(omniscripts, [typeField, subTypeField]);
2159+
}
2160+
21382161
/**
21392162
* Collects reserved keys found in PropertySet tagsToValidate
21402163
* @param propertySet - The PropertySet JSON object to validate

src/utils/apex/parser/apexparser.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ import { CharStreams, ParserRuleContext, Token, TokenStreamRewriter } from 'antl
2121
import { ParseTreeWalker } from 'antlr4ts/tree/ParseTreeWalker';
2222
import { Logger } from '../../logger';
2323

24+
// Constants for reserved keywords and special interface names
25+
const SYSTEM_NAMESPACE = 'System';
26+
const CALLABLE_INTERFACE = 'Callable';
27+
2428
export class ApexASTParser {
2529
private apexFileContent: string;
2630
private implementsInterface: Map<InterfaceImplements, Token[]> = new Map();
@@ -260,6 +264,16 @@ export class InterfaceMatcher {
260264
) {
261265
tokens.push(typeNameContexts[0].id().Identifier().symbol);
262266
tokens.push(typeNameContexts[1].id().Identifier().symbol);
267+
} else if (
268+
// Special case for System.Callable - "System" is a reserved keyword so Identifier() returns null
269+
checkFor.namespace === SYSTEM_NAMESPACE &&
270+
checkFor.name === CALLABLE_INTERFACE &&
271+
typeNameContexts.length === 2 &&
272+
!typeNameContexts[0]?.id()?.Identifier() && // System is a reserved keyword, no Identifier
273+
typeNameContexts[1]?.id()?.Identifier()?.symbol?.text === CALLABLE_INTERFACE
274+
) {
275+
tokens.push(typeNameContexts[0].start);
276+
tokens.push(typeNameContexts[1].id().Identifier().symbol);
263277
}
264278
return tokens;
265279
}

src/utils/recordPrioritization.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* Utility functions for record prioritization based on name characteristics
3+
*/
4+
5+
import { AnyJson } from '@salesforce/ts-types';
6+
7+
/**
8+
* Checks if a string is a valid alphanumeric name for migration:
9+
* - Must start with a letter (a-z, A-Z)
10+
* - Can contain letters and numbers (a-z, A-Z, 0-9) after the first character
11+
* - Cannot start with a number
12+
* - Cannot contain special characters
13+
*
14+
* @param str - The string to check
15+
* @returns true if the string is valid (starts with letter, alphanumeric only), false otherwise
16+
*
17+
* @example
18+
* hasOnlyAlphanumericCharacters('MyName123') // true
19+
* hasOnlyAlphanumericCharacters('Test1') // true
20+
* hasOnlyAlphanumericCharacters('123Test') // false - starts with number
21+
* hasOnlyAlphanumericCharacters('My-Name') // false - contains special character
22+
*/
23+
export function hasOnlyAlphanumericCharacters(str: string): boolean {
24+
// Must start with a letter, followed by any number of alphanumeric characters
25+
return /^[a-zA-Z][a-zA-Z0-9]*$/.test(str);
26+
}
27+
28+
/**
29+
* Prioritizes records by partitioning them into two groups:
30+
* - Group 1: Records with valid migration names (start with letter, alphanumeric only)
31+
* - Group 2: Records with names that need cleaning (start with number or contain special characters)
32+
*
33+
* Returns Group 1 followed by Group 2, effectively prioritizing cleaner names
34+
* for processing first. This helps avoid naming conflicts during migration when
35+
* special characters are cleaned/removed or numbers are prefixed.
36+
* This prevents the error in standard data model as when we try to update a record with special chars first
37+
* which already has clean name record in database we get FIELD_INTEGRITY_EXCEPTION as we cannot create a duplicate record with the same identifier
38+
*
39+
* @param records - Array of records to prioritize
40+
* @param fieldNames - Single field name or array of field names to check for each record
41+
* @returns Array with records reordered (valid migration names first, then others)
42+
*
43+
* @example
44+
* // Single field
45+
* prioritizeCleanNamesFirst(flexCards, 'Name')
46+
*
47+
* // Multiple fields (all must be valid)
48+
* prioritizeCleanNamesFirst(omniScripts, ['Type__c', 'SubType__c'])
49+
*/
50+
export function prioritizeCleanNamesFirst<T extends AnyJson>(records: T[], fieldNames: string | string[]): T[] {
51+
const cleanNameRecords: T[] = []; // Only alphanumeric in names
52+
const specialCharRecords: T[] = []; // Has special characters in names
53+
54+
const fieldsToCheck = Array.isArray(fieldNames) ? fieldNames : [fieldNames];
55+
56+
for (const record of records) {
57+
// Extract values for all specified fields
58+
const namesToCheck = fieldsToCheck.map((field) => (record[field] as string) || '');
59+
60+
// Check if all names are valid (start with letter, alphanumeric only)
61+
const allAlphanumeric = namesToCheck.every((name) => name && hasOnlyAlphanumericCharacters(name));
62+
63+
if (allAlphanumeric) {
64+
cleanNameRecords.push(record);
65+
} else {
66+
specialCharRecords.push(record);
67+
}
68+
}
69+
70+
// Return clean names first, then names with special characters
71+
return [...cleanNameRecords, ...specialCharRecords];
72+
}

test/migration/flexcard-standard-datamodel.test.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,4 +613,95 @@ describe('FlexCard Standard Data Model (Metadata API Disabled) - Assessment and
613613
expect(result.OmniUiCardKey).to.equal('TestCard/TestAuthor/1.0');
614614
});
615615
});
616+
617+
describe('Standard Data Model - FlexCard Prioritization with Migration Status', () => {
618+
it('should set correct migration status for clean names vs special character names', async () => {
619+
// Test FlexCards with clean names (should have Success status)
620+
const cleanNameCards = [
621+
{
622+
Id: 'fc001',
623+
Name: 'Flexcard001', // Clean name
624+
DataSourceConfig: JSON.stringify({ type: 'None' }),
625+
PropertySetConfig: JSON.stringify({ layout: 'Card' }),
626+
IsActive: true,
627+
OmniUiCardType: 'Parent',
628+
VersionNumber: 1,
629+
},
630+
{
631+
Id: 'fc002',
632+
Name: 'Flexcard002', // Clean name
633+
DataSourceConfig: JSON.stringify({ type: 'None' }),
634+
PropertySetConfig: JSON.stringify({ layout: 'Card' }),
635+
IsActive: true,
636+
OmniUiCardType: 'Parent',
637+
VersionNumber: 1,
638+
},
639+
{
640+
Id: 'fc003',
641+
Name: 'Flexcard003', // Clean name
642+
DataSourceConfig: JSON.stringify({ type: 'None' }),
643+
PropertySetConfig: JSON.stringify({ layout: 'Card' }),
644+
IsActive: true,
645+
OmniUiCardType: 'Parent',
646+
VersionNumber: 1,
647+
},
648+
];
649+
650+
// Test FlexCards with special characters (should have Warnings status)
651+
const specialCharCards = [
652+
{
653+
Id: 'fc004',
654+
Name: 'Flexcard_001', // Special character (underscore)
655+
DataSourceConfig: JSON.stringify({ type: 'None' }),
656+
PropertySetConfig: JSON.stringify({ layout: 'Card' }),
657+
IsActive: true,
658+
OmniUiCardType: 'Parent',
659+
VersionNumber: 1,
660+
},
661+
{
662+
Id: 'fc005',
663+
Name: 'Flexcard_002', // Special character (underscore)
664+
DataSourceConfig: JSON.stringify({ type: 'None' }),
665+
PropertySetConfig: JSON.stringify({ layout: 'Card' }),
666+
IsActive: true,
667+
OmniUiCardType: 'Parent',
668+
VersionNumber: 1,
669+
},
670+
{
671+
Id: 'fc006',
672+
Name: 'Flexcard_003', // Special character (underscore)
673+
DataSourceConfig: JSON.stringify({ type: 'None' }),
674+
PropertySetConfig: JSON.stringify({ layout: 'Card' }),
675+
IsActive: true,
676+
OmniUiCardType: 'Parent',
677+
VersionNumber: 1,
678+
},
679+
];
680+
681+
const duplicateSet = new Set<string>();
682+
683+
// Process clean name cards
684+
for (const card of cleanNameCards) {
685+
const result = await (cardTool as any).processFlexCard(card, duplicateSet);
686+
687+
// Verify clean names have "Ready for migration" status with no warnings
688+
expect(result.name).to.equal(card.Name);
689+
expect(result.oldName).to.equal(card.Name);
690+
expect(result.migrationStatus).to.equal('Ready for migration');
691+
expect(result.warnings).to.have.length(0);
692+
}
693+
694+
// Process special character cards
695+
for (const card of specialCharCards) {
696+
const result = await (cardTool as any).processFlexCard(card, duplicateSet);
697+
698+
// Verify special character names have appropriate migration status
699+
expect(result.oldName).to.equal(card.Name);
700+
expect(result.name).to.not.equal(card.Name); // Name should be cleaned
701+
// FlexCards with special characters may have different statuses based on complexity
702+
expect(result.migrationStatus).to.be.oneOf(['Warnings', 'Needs manual intervention']);
703+
expect(result.warnings).to.have.length.greaterThan(0);
704+
}
705+
});
706+
});
616707
});

0 commit comments

Comments
 (0)