Skip to content

Commit f2a7598

Browse files
authored
Merge pull request #331 from sf-aastha-paruthi/u/aparuthi/ExperienceSitesMigration
@W-18777114 [Migration] Migrate OS and FC components associated with vLocity LWC wrapper to Omniscript standard wrapper in experience sites
2 parents 788e3b6 + 3d486f9 commit f2a7598

File tree

16 files changed

+1913
-33
lines changed

16 files changed

+1913
-33
lines changed

src/commands/omnistudio/migration/migrate.ts

Lines changed: 82 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
*/
1010
import * as os from 'os';
1111
import { flags } from '@salesforce/command';
12-
import { Messages } from '@salesforce/core';
13-
import { ExecuteAnonymousResult } from 'jsforce';
12+
import { Connection, Messages } from '@salesforce/core';
1413
import OmniStudioBaseCommand from '../../basecommand';
1514
import { DataRaptorMigrationTool } from '../../../migration/dataraptor';
1615
import { DebugTimer, MigratedObject, MigratedRecordInfo } from '../../../utils';
@@ -24,8 +23,8 @@ import { generatePackageXml } from '../../../utils/generatePackageXml';
2423
import { OmnistudioOrgDetails, OrgUtils } from '../../../utils/orgUtils';
2524
import { Constants } from '../../../utils/constants/stringContants';
2625
import { OrgPreferences } from '../../../utils/orgPreferences';
27-
import { AnonymousApexRunner } from '../../../utils/apex/executor/AnonymousApexRunner';
2826
import { ProjectPathUtil } from '../../../utils/projectPathUtil';
27+
import { PostMigrate } from '../../../migration/postMigrate';
2928

3029
// Initialize Messages with the current plugin directory
3130
Messages.importMessagesDirectory(__dirname);
@@ -85,7 +84,6 @@ export default class Migrate extends OmniStudioBaseCommand {
8584
const migrateOnly = (this.flags.only || '') as string;
8685
const allVersions = this.flags.allversions || (false as boolean);
8786
const relatedObjects = (this.flags.relatedobjects || '') as string;
88-
8987
// this.org is guaranteed because requiresUsername=true, as opposed to supportsUsername
9088
const conn = this.org.getConnection();
9189
if (apiVersion) {
@@ -124,9 +122,10 @@ export default class Migrate extends OmniStudioBaseCommand {
124122
let projectPath: string;
125123
let objectsToProcess: string[] = [];
126124
let targetApexNamespace: string;
125+
const isExperienceBundleMetadataAPIProgramaticallyEnabled: { value: boolean } = { value: false };
127126
if (relatedObjects) {
128127
// To-Do: Add LWC to valid options when GA is released
129-
const validOptions = [Constants.Apex];
128+
const validOptions = [Constants.Apex, Constants.ExpSites];
130129
objectsToProcess = relatedObjects.split(',').map((obj) => obj.trim());
131130
// Validate input
132131
for (const obj of objectsToProcess) {
@@ -141,7 +140,15 @@ export default class Migrate extends OmniStudioBaseCommand {
141140
// Use ProjectPathUtil for APEX project folder selection (matches assess.ts logic)
142141
projectPath = await ProjectPathUtil.getProjectPath(messages, true);
143142
targetApexNamespace = await this.getTargetApexNamespace(objectsToProcess, targetApexNamespace);
144-
}
143+
await this.handleExperienceSitePrerequisites(
144+
objectsToProcess,
145+
conn,
146+
isExperienceBundleMetadataAPIProgramaticallyEnabled
147+
);
148+
Logger.logVerbose(
149+
'The objects to process after handleExpSitePrerequisite are ' + JSON.stringify(objectsToProcess)
150+
);
151+
} // TODO - What if general consent is no
145152
}
146153

147154
Logger.log(messages.getMessage('migrationInitialization', [String(namespace)]));
@@ -181,8 +188,20 @@ export default class Migrate extends OmniStudioBaseCommand {
181188
relatedObjectMigrationResult.lwcAssessmentInfos
182189
);
183190

191+
// POST MIGRATION
184192
let actionItems = [];
185-
actionItems = await this.setDesignersToUseStandardDataModel(namespace);
193+
const postMigrate: PostMigrate = new PostMigrate(
194+
this.org,
195+
namespace,
196+
conn,
197+
this.logger,
198+
messages,
199+
this.ux,
200+
objectsToProcess
201+
);
202+
203+
actionItems = await postMigrate.setDesignersToUseStandardDataModel(namespace);
204+
await postMigrate.restoreExperienceAPIMetadataSettings(isExperienceBundleMetadataAPIProgramaticallyEnabled);
186205

187206
await ResultsBuilder.generateReport(
188207
objectMigrationResults,
@@ -200,27 +219,47 @@ export default class Migrate extends OmniStudioBaseCommand {
200219
return { objectMigrationResults };
201220
}
202221

203-
private async setDesignersToUseStandardDataModel(namespace: string): Promise<string[]> {
204-
const userActionMessage: string[] = [];
205-
try {
206-
Logger.logVerbose('Setting designers to use the standard data model');
207-
const apexCode = `
208-
${namespace}.OmniStudioPostInstallClass.useStandardDataModel();
209-
`;
210-
211-
const result: ExecuteAnonymousResult = await AnonymousApexRunner.run(this.org, apexCode);
212-
if (result?.success === false) {
213-
const message = result?.exceptionStackTrace;
214-
Logger.error(`Error occurred while setting designers to use the standard data model ${message}`);
215-
userActionMessage.push(messages.getMessage('manuallySwitchDesignerToStandardDataModel'));
216-
} else if (result?.success === true) {
217-
Logger.logVerbose('Successfully executed setDesignersToUseStandardDataModel');
222+
private async handleExperienceSitePrerequisites(
223+
objectsToProcess: string[],
224+
conn: Connection,
225+
isExperienceBundleMetadataAPIProgramaticallyEnabled: { value: boolean }
226+
): Promise<void> {
227+
if (objectsToProcess.includes(Constants.ExpSites)) {
228+
const expMetadataApiConsent = await this.getExpSiteMetadataEnableConsent();
229+
Logger.logVerbose(`The consent for exp site is ${expMetadataApiConsent}`);
230+
231+
if (expMetadataApiConsent === false) {
232+
Logger.warn('Consent for experience sites is not provided. Experience sites will not be processed');
233+
this.removeKeyFromRelatedObjectsToProcess(Constants.ExpSites, objectsToProcess);
234+
Logger.logVerbose(`Objects to process after removing expsite are ${JSON.stringify(objectsToProcess)}`);
235+
return;
236+
}
237+
238+
const isMetadataAPIPreEnabled = await OrgPreferences.isExperienceBundleMetadataAPIEnabled(conn);
239+
if (isMetadataAPIPreEnabled === true) {
240+
Logger.logVerbose('ExperienceBundle metadata api is already enabled');
241+
return;
218242
}
219-
} catch (ex) {
220-
Logger.error(`Exception occurred while setting designers to use the standard data model ${JSON.stringify(ex)}`);
221-
userActionMessage.push(messages.getMessage('manuallySwitchDesignerToStandardDataModel'));
243+
244+
Logger.logVerbose('ExperienceBundle metadata api needs to be programatically enabled');
245+
isExperienceBundleMetadataAPIProgramaticallyEnabled.value = await OrgPreferences.setExperienceBundleMetadataAPI(
246+
conn,
247+
true
248+
);
249+
if (isExperienceBundleMetadataAPIProgramaticallyEnabled.value === false) {
250+
this.removeKeyFromRelatedObjectsToProcess(Constants.ExpSites, objectsToProcess);
251+
Logger.warn('Since the api could not able enabled the experience sites would not be processed');
252+
}
253+
254+
Logger.logVerbose(`Objects to process are ${JSON.stringify(objectsToProcess)}`);
255+
}
256+
}
257+
258+
private removeKeyFromRelatedObjectsToProcess(keyToRemove: string, relatedObjects: string[]): void {
259+
const index = relatedObjects.indexOf(Constants.ExpSites);
260+
if (index > -1) {
261+
relatedObjects.splice(index, 1);
222262
}
223-
return userActionMessage;
224263
}
225264

226265
private async truncateObjects(migrationObjects: MigrationTool[], debugTimer: DebugTimer): Promise<MigratedObject[]> {
@@ -354,6 +393,23 @@ export default class Migrate extends OmniStudioBaseCommand {
354393
return consent;
355394
}
356395

396+
private async getExpSiteMetadataEnableConsent(): Promise<boolean> {
397+
let consent: boolean | null = null;
398+
399+
while (consent === null) {
400+
try {
401+
consent = await Logger.confirm(
402+
'By proceeding further, you hereby consent to enable digital experience metadata api(y/n). If y sites will be processed, if n expsites will not be processed'
403+
);
404+
} catch (error) {
405+
Logger.log(messages.getMessage('invalidYesNoResponse'));
406+
consent = null;
407+
}
408+
}
409+
410+
return consent;
411+
}
412+
357413
private mergeRecordAndUploadResults(
358414
migrationResults: MigrationResult,
359415
migrationTool: MigrationTool

src/migration/flexcard.ts

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,21 @@ import CardMappings from '../mappings/VlocityCard';
44
import { DebugTimer, QueryTools, SortDirection } from '../utils';
55
import { NetUtils } from '../utils/net';
66
import { BaseMigrationTool } from './base';
7-
import { MigrationResult, MigrationTool, ObjectMapping, UploadRecordResult } from './interfaces';
7+
import {
8+
MigrationResult,
9+
MigrationTool,
10+
ObjectMapping,
11+
UploadRecordResult,
12+
MigrationStorage,
13+
FlexcardStorage,
14+
} from './interfaces';
815
import { Connection, Messages } from '@salesforce/core';
916
import { UX } from '@salesforce/command';
1017
import { FlexCardAssessmentInfo } from '../../src/utils';
1118
import { Logger } from '../utils/logger';
1219
import { createProgressBar } from './base';
1320
import { Constants } from '../utils/constants/stringContants';
21+
import { StorageUtil } from '../utils/storageUtil';
1422

1523
export class CardMigrationTool extends BaseMigrationTool implements MigrationTool {
1624
static readonly VLOCITYCARD_NAME = 'VlocityCard__c';
@@ -75,6 +83,7 @@ export class CardMigrationTool extends BaseMigrationTool implements MigrationToo
7583
async migrate(): Promise<MigrationResult[]> {
7684
// Get All the Active VlocityCard__c records
7785
const cards = await this.getAllActiveCards();
86+
7887
Logger.log(this.messages.getMessage('foundFlexCardsToMigrate', [cards.length]));
7988

8089
const progressBar = createProgressBar('Migrating', 'Flexcard');
@@ -445,6 +454,9 @@ export class CardMigrationTool extends BaseMigrationTool implements MigrationToo
445454
await this.uploadCard(cards, card, cardsUploadInfo, originalRecords, uniqueNames);
446455
progressBar.update(++progressCounter);
447456
}
457+
458+
this.prepareStorageForFlexcards(cardsUploadInfo, originalRecords);
459+
448460
progressBar.stop();
449461

450462
return cardsUploadInfo;
@@ -526,8 +538,9 @@ export class CardMigrationTool extends BaseMigrationTool implements MigrationToo
526538
this.messages.getMessage('cardAuthorNameChangeMessage', [transformedCardAuthorName])
527539
);
528540
}
541+
542+
uploadResult.newName = transformedCardName;
529543
if (transformedCardName !== card['Name']) {
530-
uploadResult.newName = transformedCardName;
531544
uploadResult.warnings.unshift(this.messages.getMessage('cardNameChangeMessage', [transformedCardName]));
532545
}
533546

@@ -571,6 +584,52 @@ export class CardMigrationTool extends BaseMigrationTool implements MigrationToo
571584
}
572585
}
573586

587+
private prepareStorageForFlexcards(
588+
cardsUploadInfo: Map<string, UploadRecordResult>,
589+
originalRecords: Map<string, any>
590+
) {
591+
Logger.logVerbose('Started preparing storage for flexcards');
592+
let storage: MigrationStorage = StorageUtil.getOmnistudioMigrationStorage();
593+
594+
for (let key of Array.from(originalRecords.keys())) {
595+
try {
596+
let oldrecord = originalRecords.get(key);
597+
let newrecord = cardsUploadInfo.get(key);
598+
599+
Logger.logVerbose('Oldrecord - ' + JSON.stringify(oldrecord));
600+
Logger.logVerbose('Newrecord - ' + JSON.stringify(newrecord));
601+
602+
let value: FlexcardStorage = {
603+
name: newrecord['newName'],
604+
isDuplicate: false,
605+
};
606+
607+
if (newrecord.hasErrors) {
608+
value.error = newrecord.errors;
609+
value.migrationSuccess = false;
610+
} else {
611+
value.migrationSuccess = true;
612+
}
613+
614+
let finalKey = `${oldrecord['Name']}`;
615+
if (storage.fcStorage.has(finalKey)) {
616+
// Key already exists - handle accordingly
617+
Logger.logVerbose(`Key ${finalKey} already exists in flexcard storage`);
618+
value.isDuplicate = true;
619+
storage.fcStorage.set(finalKey, value);
620+
} else {
621+
// Key doesn't exist - safe to set
622+
storage.fcStorage.set(finalKey, value);
623+
}
624+
} catch (error) {
625+
Logger.logVerbose('Error occurred while processing key for flexcard storage');
626+
Logger.error(error);
627+
}
628+
629+
StorageUtil.printMigrationStorage();
630+
}
631+
}
632+
574633
private getChildCards(card: AnyJson): string[] {
575634
let childs = [];
576635
const definition = JSON.parse(card[this.namespacePrefix + 'Definition__c']);

src/migration/interfaces.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ export interface UploadRecordResult {
4343
warnings: string[];
4444
hasErrors: boolean;
4545
success?: boolean;
46+
type?: string;
47+
subtype?: string;
48+
language?: string;
4649
}
4750

4851
export interface MigrationResult {
@@ -91,3 +94,74 @@ export interface RelatedObjectMigrationResult {
9194
apexClasses: string[];
9295
lwcComponents: string[];
9396
}
97+
98+
export interface ExpSitePageJson {
99+
// Index signature to allow any additional properties
100+
[key: string]: any;
101+
appPageId: string;
102+
componentName: string;
103+
dataProviders: unknown[]; // Replace 'any' with a specific type if known
104+
id: string;
105+
label: string;
106+
regions: ExpSiteRegion[];
107+
themeLayoutType: string;
108+
type: string;
109+
viewType: string;
110+
}
111+
112+
export interface ExpSiteRegion {
113+
// Index signature to allow any additional properties
114+
[key: string]: any;
115+
id: string;
116+
regionName: string;
117+
type: string;
118+
components?: ExpSiteComponent[]; // Optional, as some regions don't have components
119+
}
120+
121+
export interface ExpSiteComponent {
122+
// Index signature to allow any additional properties
123+
[key: string]: any;
124+
componentAttributes: ExpSiteComponentAttributes;
125+
componentName: string;
126+
id: string;
127+
renderPriority?: string;
128+
renditionMap: unknown; // Replace with better type if known
129+
type: string;
130+
subtype?: string;
131+
language?: string;
132+
}
133+
134+
export interface ExpSiteComponentAttributes {
135+
// Index signature to allow any additional string properties
136+
[key: string]: any;
137+
// This is a union of possible structures. You can separate them if needed.
138+
layout?: string;
139+
params?: string;
140+
standAlone?: boolean;
141+
target?: string;
142+
customHeadTags?: string;
143+
description?: string;
144+
title?: string;
145+
richTextValue?: string;
146+
}
147+
148+
export interface MigrationStorage {
149+
osStorage: Map<string, OmniScriptStorage>;
150+
fcStorage: Map<string, FlexcardStorage>;
151+
}
152+
153+
export interface Storage {
154+
migrationSuccess?: boolean;
155+
error?: string[];
156+
isDuplicate: boolean;
157+
}
158+
159+
export interface OmniScriptStorage extends Storage {
160+
type: string;
161+
subtype: string;
162+
language: string;
163+
}
164+
165+
export interface FlexcardStorage extends Storage {
166+
name: string;
167+
}

0 commit comments

Comments
 (0)