Skip to content

Commit 780a2aa

Browse files
committed
feat: flexipage migration with storage
1 parent f94c01d commit 780a2aa

File tree

12 files changed

+236
-52
lines changed

12 files changed

+236
-52
lines changed

messages/assess.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,5 +126,6 @@
126126
"updatedModifiedContent": "updated content in file: %s",
127127
"generatedDiffForFile": "Generated diff for file: %s",
128128
"errorProcessingFlexiPage": "Error processing flexipage file: %s, %s",
129-
"flexipagesWithChanges": "Found %s flexipage files with changes"
129+
"flexipagesWithChanges": "Found %s flexipage files with changes",
130+
"migratingFlexiPages": "Migrating FlexiPages"
130131
}

src/commands/omnistudio/migration/assess.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export default class Assess extends OmniStudioBaseCommand {
4141
}),
4242
relatedobjects: flags.string({
4343
char: 'r',
44-
description: messages.getMessage('apexLwc'),
44+
description: messages.getMessage('relatedObjectGA'),
4545
}),
4646
verbose: flags.builtin({
4747
type: 'builtin',

src/error/errorInterfaces.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
export class KeyNotFoundInStorageError extends Error {
2+
public componentType: string;
3+
public key: string;
4+
5+
public constructor(key: string, componentType: string) {
6+
super(`Key ${key} not found in storage`);
7+
this.key = key;
8+
this.componentType = componentType;
9+
}
10+
}
11+
12+
export class DuplicateKeyError extends Error {
13+
public componentType: string;
14+
public key: string;
15+
16+
public constructor(key: string, componentType: string) {
17+
super(`Key ${key} is duplicate in storage`);
18+
this.key = key;
19+
this.componentType = componentType;
20+
}
21+
}
22+
23+
export class TargetPropertyNotFoundError extends Error {
24+
public componentName: string;
25+
26+
public constructor(componentName: string) {
27+
super(`Target property not found for component ${componentName}`);
28+
this.componentName = componentName;
29+
}
30+
}

src/migration/flexcard.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ export class CardMigrationTool extends BaseMigrationTool implements MigrationToo
185185
}
186186
uniqueNames.add(cleanedName);
187187

188+
this.addToAssessmentStorage(originalName, cleanedName);
189+
188190
// Check for author name changes
189191
const originalAuthor = flexCard[this.namespacePrefix + 'Author__c'];
190192
if (originalAuthor) {
@@ -584,6 +586,18 @@ export class CardMigrationTool extends BaseMigrationTool implements MigrationToo
584586
}
585587
}
586588

589+
private addToAssessmentStorage(originalName: string, cleanedName: string) {
590+
let storage: MigrationStorage = StorageUtil.getOmnistudioAssessmentStorage();
591+
if (storage.fcStorage.has(originalName)) {
592+
storage.fcStorage.get(originalName).isDuplicate = true;
593+
} else {
594+
storage.fcStorage.set(originalName, {
595+
name: cleanedName,
596+
isDuplicate: false,
597+
});
598+
}
599+
}
600+
587601
private prepareStorageForFlexcards(
588602
cardsUploadInfo: Map<string, UploadRecordResult>,
589603
originalRecords: Map<string, any>

src/migration/omniscript.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,13 @@ export class OmniScriptMigrationTool extends BaseMigrationTool implements Migrat
428428

429429
const warnings: string[] = [];
430430

431+
if (omniProcessType === 'OmniScript') {
432+
this.addToAssessmentStorage(
433+
existingTypeVal,
434+
existingSubTypeVal,
435+
omniscript[this.namespacePrefix + 'Language__c']
436+
);
437+
}
431438
const recordName =
432439
`${existingTypeVal.cleanName()}_` +
433440
`${existingSubTypeVal.cleanName()}` +
@@ -774,6 +781,21 @@ export class OmniScriptMigrationTool extends BaseMigrationTool implements Migrat
774781
};
775782
}
776783

784+
private addToAssessmentStorage(type: StringVal, subtype: StringVal, language: string) {
785+
let storage: MigrationStorage = StorageUtil.getOmnistudioAssessmentStorage();
786+
const key = `${type.val}${subtype.val}${language}`;
787+
if (storage.osStorage.has(key)) {
788+
storage.osStorage.get(key).isDuplicate = true;
789+
} else {
790+
storage.osStorage.set(key, {
791+
type: type.cleanName(),
792+
subtype: subtype.cleanName(),
793+
language: language || 'English',
794+
isDuplicate: false,
795+
});
796+
}
797+
}
798+
777799
private updateStorageForOmniscript(
778800
osUploadInfo: Map<string, UploadRecordResult>,
779801
originalOsRecords: Map<string, any>

src/migration/related/FlexipageMigration.ts

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { XMLUtil } from '../../utils/XMLUtil';
1818
import { FileDiffUtil } from '../../utils/lwcparser/fileutils/FileDiffUtil';
1919
import { transformFlexipageBundle } from '../../utils/flexipage/flexiPageTransformer';
2020
import { Flexipage } from '../interfaces';
21+
import { DuplicateKeyError, KeyNotFoundInStorageError, TargetPropertyNotFoundError } from '../../error/errorInterfaces';
2122
import { BaseRelatedObjectMigration } from './BaseRealtedObjectMigration';
2223

2324
/**
@@ -72,7 +73,7 @@ export class FlexipageMigration extends BaseRelatedObjectMigration {
7273
* @returns Array of FlexiPage assessment information
7374
*/
7475
public assess(): FlexiPageAssessmentInfo[] {
75-
Logger.info(this.messages.getMessage('assessingFlexiPages'));
76+
Logger.log(this.messages.getMessage('assessingFlexiPages'));
7677
return this.process('assess');
7778
}
7879

@@ -82,7 +83,7 @@ export class FlexipageMigration extends BaseRelatedObjectMigration {
8283
* @returns Array of FlexiPage assessment information after migration
8384
*/
8485
public migrate(): FlexiPageAssessmentInfo[] {
85-
Logger.info(this.messages.getMessage('migratingFlexiPages'));
86+
Logger.log(this.messages.getMessage('migratingFlexiPages'));
8687
return this.process('migrate');
8788
}
8889

@@ -100,13 +101,13 @@ export class FlexipageMigration extends BaseRelatedObjectMigration {
100101
* @returns Array of FlexiPage assessment information
101102
*/
102103
private process(mode: 'assess' | 'migrate'): FlexiPageAssessmentInfo[] {
103-
Logger.info(this.messages.getMessage('retrievingFlexiPages'));
104+
Logger.logVerbose(this.messages.getMessage('retrievingFlexiPages'));
104105
shell.cd(this.projectPath);
105106
sfProject.retrieve(Constants.FlexiPage, this.org.getUsername());
106107
const files = fs
107108
.readdirSync(path.join(this.projectPath, 'force-app', 'main', 'default', 'flexipages'))
108109
.filter((file) => file.endsWith('.xml'));
109-
Logger.info(this.messages.getMessage('successfullyRetrievedFlexiPages', [files.length]));
110+
Logger.logVerbose(this.messages.getMessage('successfullyRetrievedFlexiPages', [files.length]));
110111
const progressBar = createProgressBar('Migrating', 'Flexipage');
111112
progressBar.setTotal(files.length);
112113
const flexPageAssessmentInfos: FlexiPageAssessmentInfo[] = [];
@@ -124,8 +125,16 @@ export class FlexipageMigration extends BaseRelatedObjectMigration {
124125
])
125126
);
126127
} catch (error) {
127-
Logger.error(this.messages.getMessage('errorProcessingFlexiPage', [file, error]));
128-
Logger.error(error);
128+
if (error instanceof KeyNotFoundInStorageError) {
129+
Logger.error(`${error.componentType} ${error.key} can't be migrated`);
130+
} else if (error instanceof TargetPropertyNotFoundError) {
131+
Logger.error(error.message);
132+
} else if (error instanceof DuplicateKeyError) {
133+
Logger.error(`${error.componentType} ${error.key} is duplicate`);
134+
} else {
135+
Logger.error(this.messages.getMessage('errorProcessingFlexiPage', [file, error]));
136+
Logger.error(error);
137+
}
129138
flexPageAssessmentInfos.push({
130139
name: file,
131140
errors: [error instanceof Error ? error.message : JSON.stringify(error)],
@@ -137,10 +146,12 @@ export class FlexipageMigration extends BaseRelatedObjectMigration {
137146
progressBar.increment();
138147
}
139148
progressBar.stop();
140-
Logger.info(this.messages.getMessage('completedProcessingAllFlexiPages', [flexPageAssessmentInfos.length]));
141-
flexPageAssessmentInfos.filter((flexPageAssessmentInfo) => flexPageAssessmentInfo.status !== 'No Changes');
142-
Logger.info(this.messages.getMessage('flexipagesWithChanges', [flexPageAssessmentInfos.length]));
143-
return flexPageAssessmentInfos;
149+
Logger.logVerbose(this.messages.getMessage('completedProcessingAllFlexiPages', [flexPageAssessmentInfos.length]));
150+
const filteredResults = flexPageAssessmentInfos.filter(
151+
(flexPageAssessmentInfo) => flexPageAssessmentInfo.status !== 'No Changes'
152+
);
153+
Logger.log(this.messages.getMessage('flexipagesWithChanges', [filteredResults.length]));
154+
return filteredResults;
144155
}
145156

146157
/**
@@ -165,9 +176,7 @@ export class FlexipageMigration extends BaseRelatedObjectMigration {
165176
Logger.logVerbose(this.messages.getMessage('readFlexiPageContent', [fileContent.length]));
166177

167178
const json = this.xmlUtil.parse(fileContent) as Flexipage;
168-
const jsonPath = path.join(shell.pwd().toString(), 'json', fileName + '.json');
169-
fs.writeFileSync(jsonPath.toString(), JSON.stringify(json) || '');
170-
const transformedFlexiPage = transformFlexipageBundle(json, this.namespace);
179+
const transformedFlexiPage = transformFlexipageBundle(json, this.namespace, mode);
171180
if (transformedFlexiPage === false) {
172181
return {
173182
name: fileName,
@@ -179,9 +188,6 @@ export class FlexipageMigration extends BaseRelatedObjectMigration {
179188
}
180189
const modifiedContent = this.xmlUtil.build(transformedFlexiPage, 'FlexiPage');
181190

182-
const xmlPath = path.join(shell.pwd().toString(), 'xml', fileName);
183-
fs.writeFileSync(xmlPath.toString(), modifiedContent);
184-
185191
if (mode === 'migrate') {
186192
fs.writeFileSync(filePath, modifiedContent);
187193
Logger.logVerbose(this.messages.getMessage('updatedModifiedContent', [filePath]));

src/utils/flexipage/flexiPageTransformer.ts

Lines changed: 97 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,29 @@
55
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
*/
77

8-
import { Flexipage, FlexiComponentInstanceProperty } from '../../migration/interfaces';
8+
import { DuplicateKeyError, KeyNotFoundInStorageError, TargetPropertyNotFoundError } from '../../error/errorInterfaces';
9+
import {
10+
Flexipage,
11+
FlexiComponentInstanceProperty,
12+
OmniScriptStorage,
13+
FlexcardStorage,
14+
} from '../../migration/interfaces';
15+
import { StorageUtil } from '../storageUtil';
916

1017
/** Attributes to remove during transformation */
1118
const attrsToRemove = ['target'];
1219

1320
/** Component name to look for during transformation */
1421
const lookupComponentName = 'vlocityLWCOmniWrapper';
1522
/** Target component name after transformation */
16-
const targetComponentName = 'runtime_omnistudio:omniscript';
23+
const targetComponentNameOS = 'runtime_omnistudio:omniscript';
1724
/** Target identifier after transformation */
18-
const targetIdentifier = 'runtime_omnistudio_omniscript';
25+
const targetIdentifierOS = 'runtime_omnistudio_omniscript';
26+
27+
const targetComponentNameFlexCard = 'runtime_omnistudio:flexcard';
28+
const targetIdentifierFlexCard = 'runtime_omnistudio_flexcard';
29+
30+
const flexCardPrefix = 'cf';
1931

2032
/**
2133
* Transforms a Flexipage bundle by replacing vlocityLWCOmniWrapper components
@@ -32,7 +44,11 @@ const targetIdentifier = 'runtime_omnistudio_omniscript';
3244
* @returns The transformed Flexipage bundle if changes were made, or false if no changes were needed
3345
* @throws Error if the 'target' property is not found for a component
3446
*/
35-
export function transformFlexipageBundle(ogBundle: Flexipage, namespace: string): Flexipage | boolean {
47+
export function transformFlexipageBundle(
48+
ogBundle: Flexipage,
49+
namespace: string,
50+
mode: 'assess' | 'migrate'
51+
): Flexipage | boolean {
3652
const bundle: Flexipage = JSON.parse(JSON.stringify(ogBundle)) as Flexipage;
3753
let changes = false;
3854

@@ -62,9 +78,13 @@ export function transformFlexipageBundle(ogBundle: Flexipage, namespace: string)
6278
(prop) => prop.name === 'target'
6379
)?.value;
6480
if (!typeSubtypeLanguage) {
65-
throw new Error('target property not found for component ' + item.componentInstance.componentName);
81+
throw new TargetPropertyNotFoundError(item.componentInstance.componentName);
6682
}
67-
const newProps = createNewProps(typeSubtypeLanguage);
83+
const newProps = createNewProps(typeSubtypeLanguage.split(':')[1], namespace, mode);
84+
const targetComponentName = newProps.componentName;
85+
const targetIdentifier = newProps.identifier;
86+
delete newProps.componentName;
87+
delete newProps.identifier;
6888
const leftProps = item.componentInstance.componentInstanceProperties?.filter?.(propRemover) ?? [];
6989
const replacedProps = [
7090
...leftProps,
@@ -88,11 +108,77 @@ export function transformFlexipageBundle(ogBundle: Flexipage, namespace: string)
88108
* @returns Object containing the new properties for the transformed component
89109
*/
90110
// eslint-disable-next-line @typescript-eslint/no-unused-vars
91-
function createNewProps(_property: string): Record<string, string> {
111+
function createNewProps(nameKey: string, namespace: string, mode: 'assess' | 'migrate'): Record<string, string> {
112+
if (nameKey.startsWith(flexCardPrefix)) {
113+
return createNewPropsForFlexCard(nameKey.substring(flexCardPrefix.length), namespace, mode);
114+
}
115+
return createNewPropsForOmniScript(nameKey, namespace, mode);
116+
// return {
117+
// language: 'English',
118+
// subType: 'OSForCustomLWC',
119+
// theme: 'lightning',
120+
// type: 'OSForCustomLWC',
121+
// };
122+
}
123+
124+
function createNewPropsForOmniScript(
125+
nameKey: string,
126+
namespace: string,
127+
mode: 'assess' | 'migrate'
128+
): Record<string, string> {
129+
let migratedScriptName: OmniScriptStorage;
130+
if (mode === 'assess') {
131+
migratedScriptName = StorageUtil.getOmnistudioAssessmentStorage().osStorage.get(nameKey);
132+
} else {
133+
migratedScriptName = StorageUtil.getOmnistudioMigrationStorage().osStorage.get(nameKey);
134+
}
135+
136+
if (!migratedScriptName) {
137+
throw new KeyNotFoundInStorageError(nameKey, 'OmniScript');
138+
}
139+
140+
if (migratedScriptName.isDuplicate) {
141+
throw new DuplicateKeyError(nameKey, 'OmniScript');
142+
}
143+
144+
return {
145+
componentName: targetComponentNameOS,
146+
identifier: targetIdentifierOS,
147+
language: migratedScriptName.language || 'English',
148+
subType: migratedScriptName.subtype,
149+
type: migratedScriptName.type,
150+
theme: 'OSForCustomLWC',
151+
direction: 'ltr',
152+
display: 'Display button to open Omniscript',
153+
inlineVariant: 'brand',
154+
};
155+
}
156+
157+
function createNewPropsForFlexCard(
158+
nameKey: string,
159+
namespace: string,
160+
mode: 'assess' | 'migrate'
161+
): Record<string, string> {
162+
let migratedCardName: FlexcardStorage;
163+
if (mode === 'assess') {
164+
migratedCardName = StorageUtil.getOmnistudioAssessmentStorage().fcStorage.get(nameKey);
165+
} else {
166+
migratedCardName = StorageUtil.getOmnistudioMigrationStorage().fcStorage.get(nameKey);
167+
}
168+
169+
if (!migratedCardName) {
170+
throw new KeyNotFoundInStorageError(nameKey, 'Flexcard');
171+
}
172+
173+
if (migratedCardName.isDuplicate) {
174+
throw new DuplicateKeyError(nameKey, 'Flexcard');
175+
}
176+
92177
return {
93-
language: 'English',
94-
subType: 'OSForCustomLWC',
95-
theme: 'lightning',
96-
type: 'OSForCustomLWC',
178+
componentName: targetComponentNameFlexCard,
179+
identifier: targetIdentifierFlexCard,
180+
flexcardName: migratedCardName.name,
181+
objectApiName: '{!objectApiName}',
182+
recordId: '{!recordId}',
97183
};
98184
}

src/utils/resultsbuilder/FlexipageAssessmentReporter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export class FlexipageAssessmentReporter {
6868
{
6969
name: 'Can be Automated',
7070
count: flexipageAssessmentInfos.filter((info) => info.status === 'Can be Automated').length,
71-
cssClass: 'text-warning',
71+
cssClass: 'text-success',
7272
},
7373
{
7474
name: 'Has Errors',

0 commit comments

Comments
 (0)