Skip to content

Commit 917fb56

Browse files
committed
W-16276197:Bootstrap the enhancement of the Migration Plugin
1 parent 1545bf6 commit 917fb56

File tree

3 files changed

+297
-0
lines changed

3 files changed

+297
-0
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
2+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
3+
/* eslint-disable @typescript-eslint/no-explicit-any */
4+
/*
5+
* Copyright (c) 2024, your-company, inc.
6+
* All rights reserved.
7+
* Licensed under the BSD 3-Clause license.
8+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
9+
*/
10+
import * as os from 'os';
11+
import { flags } from '@salesforce/command';
12+
import { Messages } from '@salesforce/core';
13+
import '../../../utils/prototypes';
14+
import OmniStudioBaseCommand from '../../basecommand';
15+
import { LWCComponentMigrationTool, CustomLabelMigrationTool, ApexClassMigrationTool } from '../../../migration/interfaces';
16+
import { DebugTimer, MigratedObject, MigratedRecordInfo } from '../../../utils';
17+
import { MigrationResult, MigrationTool } from '../../../migration/interfaces';
18+
import { ResultsBuilder } from '../../../utils/resultsbuilder';
19+
20+
// Initialize Messages with the current plugin directory
21+
Messages.importMessagesDirectory(__dirname);
22+
23+
// Load the specific messages for this file. Messages from @salesforce/command, @salesforce/core,
24+
// or any library that is using the messages framework can also be loaded this way.
25+
const messages = Messages.loadMessages('@salesforce/plugin-omnistudio-related-object-migration-tool', 'migrate');
26+
27+
export default class OmnistudioRelatedObjectMigrationFacade extends OmniStudioBaseCommand {
28+
public static description = messages.getMessage('commandDescription');
29+
30+
public static examples = messages.getMessage('examples').split(os.EOL);
31+
32+
public static args = [{ name: 'file' }];
33+
34+
protected static flagsConfig = {
35+
namespace: flags.string({
36+
char: 'n',
37+
description: messages.getMessage('namespaceFlagDescription'),
38+
}),
39+
only: flags.string({
40+
char: 'o',
41+
description: messages.getMessage('onlyFlagDescription'),
42+
}),
43+
allversions: flags.boolean({
44+
char: 'a',
45+
description: messages.getMessage('allVersionsDescription'),
46+
required: false,
47+
}),
48+
};
49+
50+
public async migrateAll(migrationResult: MigrationResult, namespace: string, relatedObjects: string[]): Promise<any> {
51+
const apiVersion = '55.0'; // Define the API version or make it configurable
52+
const conn = this.org.getConnection();
53+
conn.setApiVersion(apiVersion);
54+
55+
// Start the debug timer
56+
DebugTimer.getInstance().start();
57+
58+
// Register the migration tools based on the relatedObjects parameter
59+
let migrationTools: MigrationTool[] = [];
60+
if (relatedObjects.includes('lwc')) {
61+
migrationTools.push(new LWCComponentMigrationTool(namespace, conn, this.logger, messages, this.ux));
62+
}
63+
if (relatedObjects.includes('labels')) {
64+
migrationTools.push(new CustomLabelMigrationTool(namespace, conn, this.logger, messages, this.ux));
65+
}
66+
if (relatedObjects.includes('apex')) {
67+
migrationTools.push(new ApexClassMigrationTool(namespace, conn, this.logger, messages, this.ux));
68+
}
69+
70+
if (migrationTools.length === 0) {
71+
throw new Error(messages.getMessage('noMigrationToolsSelected'));
72+
}
73+
74+
// Migrate individual objects
75+
const debugTimer = DebugTimer.getInstance();
76+
let objectMigrationResults: MigratedObject[] = [];
77+
78+
// Truncate existing objects if necessary
79+
let allTruncateComplete = true;
80+
for (const tool of migrationTools.reverse()) {
81+
try {
82+
this.ux.log('Cleaning: ' + tool.getName());
83+
debugTimer.lap('Cleaning: ' + tool.getName());
84+
await tool.truncate();
85+
} catch (ex: any) {
86+
allTruncateComplete = false;
87+
objectMigrationResults.push({
88+
name: tool.getName(),
89+
errors: [ex.message],
90+
});
91+
}
92+
}
93+
94+
if (allTruncateComplete) {
95+
for (const tool of migrationTools.reverse()) {
96+
try {
97+
this.ux.log('Migrating: ' + tool.getName());
98+
debugTimer.lap('Migrating: ' + tool.getName());
99+
const results = await tool.migrate();
100+
101+
objectMigrationResults = objectMigrationResults.concat(
102+
results.map((r) => ({
103+
name: r.name,
104+
data: this.mergeRecordAndUploadResults(r, tool),
105+
}))
106+
);
107+
} catch (ex: any) {
108+
this.logger.error(JSON.stringify(ex));
109+
objectMigrationResults.push({
110+
name: tool.getName(),
111+
errors: [ex.message],
112+
});
113+
}
114+
}
115+
}
116+
117+
// Stop the debug timer
118+
const timer = DebugTimer.getInstance().stop();
119+
120+
await ResultsBuilder.generate(objectMigrationResults, conn.instanceUrl);
121+
122+
// Save timer to debug logger
123+
this.logger.debug(timer);
124+
125+
// Return results needed for --json flag
126+
return { objectMigrationResults };
127+
}
128+
129+
private mergeRecordAndUploadResults(
130+
migrationResults: MigrationResult,
131+
migrationTool: MigrationTool
132+
): MigratedRecordInfo[] {
133+
const mergedResults: MigratedRecordInfo[] = [];
134+
135+
for (const record of Array.from(migrationResults.records.values())) {
136+
const obj = {
137+
id: record['Id'],
138+
name: migrationTool.getRecordName(record),
139+
status: 'Skipped',
140+
errors: record['errors'],
141+
migratedId: undefined,
142+
warnings: [],
143+
migratedName: '',
144+
};
145+
146+
if (migrationResults.results.has(record['Id'])) {
147+
const recordResults = migrationResults.results.get(record['Id']);
148+
149+
let errors: any[] = obj.errors || [];
150+
errors = errors.concat(recordResults.errors || []);
151+
152+
obj.status = !recordResults || recordResults.hasErrors ? 'Error' : 'Complete';
153+
obj.errors = errors;
154+
obj.migratedId = recordResults.id;
155+
obj.warnings = recordResults.warnings;
156+
obj.migratedName = recordResults.newName;
157+
}
158+
159+
mergedResults.push(obj);
160+
}
161+
162+
return mergedResults;
163+
}
164+
}

src/migration/interfaces.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,33 @@ export interface NameTransformData {
8282
dupNameSet: Set<string>;
8383
nameWithSepcialCharactorSet: Set<string>;
8484
}
85+
86+
export interface RelatedObjectsMigrate {
87+
/**
88+
* Identifies migration candidates based on the provided migration results and namespace.
89+
* @param migrationResults List of migration results to identify objects from.
90+
* @param namespace The namespace used to identify objects.
91+
* @returns List of identified migration candidates as strings.
92+
*/
93+
identifyObjects(migrationResults: MigrationResult[], namespace: string): Promise<string[]>;
94+
95+
/**
96+
* Private method to perform the migration of related objects based on the provided candidates.
97+
* @param migrationResults List of migration results to use for migration.
98+
* @param namespace The namespace used to perform the migration.
99+
* @param migrationCandidates List of candidates to migrate.
100+
*/
101+
migrateRelatedObjects(migrationResults: MigrationResult[], namespace: string, migrationCandidates: string[]): Promise<void>;
102+
}
103+
104+
export interface LWCComponentMigrationTool extends MigrationTool {
105+
// Specific methods or properties for LWCComponentMigrationTool if any
106+
}
107+
108+
export interface CustomLabelMigrationTool extends MigrationTool {
109+
// Specific methods or properties for CustomLabelMigrationTool if any
110+
}
111+
112+
export interface ApexClassMigrationTool extends MigrationTool {
113+
// Specific methods or properties for ApexClassMigrationTool if any
114+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { expect } from 'chai';
2+
import * as sinon from 'sinon';
3+
import { ux } from '@salesforce/command';
4+
import OmniStudioBaseCommand from '../../basecommand';
5+
import { LWCComponentMigrationTool, CustomLabelMigrationTool, ApexClassMigrationTool } from '../../../migration/interfaces';
6+
import OmnistudioRelatedObjectMigrationFacade from '../../../path/to/OmnistudioRelatedObjectMigrationFacade'; // Adjust import as necessary
7+
import { DebugTimer, MigratedObject, MigratedRecordInfo } from '../../../utils';
8+
import { MigrationResult, MigrationTool } from '../../../migration/interfaces';
9+
10+
describe('OmnistudioRelatedObjectMigrationFacade', function () {
11+
let facade: OmnistudioRelatedObjectMigrationFacade;
12+
let sandbox: sinon.SinonSandbox;
13+
14+
beforeEach(() => {
15+
sandbox = sinon.createSandbox();
16+
17+
// Stub the necessary methods and objects
18+
facade = new OmnistudioRelatedObjectMigrationFacade();
19+
sandbox.stub(facade, 'org').value({
20+
getConnection: () => ({
21+
setApiVersion: sinon.stub(),
22+
instanceUrl: 'http://example.com'
23+
})
24+
});
25+
26+
sandbox.stub(facade, 'logger').value({
27+
error: sinon.stub(),
28+
debug: sinon.stub()
29+
});
30+
31+
sandbox.stub(facade, 'ux').value(ux);
32+
});
33+
34+
afterEach(() => {
35+
sandbox.restore();
36+
});
37+
38+
it('should migrate all specified objects', async function () {
39+
const migrationResult: MigrationResult = {
40+
records: new Map(),
41+
results: new Map()
42+
};
43+
44+
const lwcTool = sandbox.createStubInstance(LWCComponentMigrationTool);
45+
const labelsTool = sandbox.createStubInstance(CustomLabelMigrationTool);
46+
const apexTool = sandbox.createStubInstance(ApexClassMigrationTool);
47+
48+
lwcTool.migrate.resolves([{ name: 'LWC Component', id: '001' }]);
49+
labelsTool.migrate.resolves([{ name: 'Custom Label', id: '002' }]);
50+
apexTool.migrate.resolves([{ name: 'Apex Class', id: '003' }]);
51+
52+
sandbox.stub(facade, 'migrateAll').resolves({
53+
objectMigrationResults: [
54+
{ name: 'LWC Component', data: [] },
55+
{ name: 'Custom Label', data: [] },
56+
{ name: 'Apex Class', data: [] }
57+
]
58+
});
59+
60+
await facade.migrateAll(migrationResult, 'testNamespace', ['lwc', 'labels', 'apex']);
61+
62+
expect(lwcTool.migrate.calledOnce).to.be.true;
63+
expect(labelsTool.migrate.calledOnce).to.be.true;
64+
expect(apexTool.migrate.calledOnce).to.be.true;
65+
66+
// Assert that the migration results are processed correctly
67+
expect(facade.logger.debug.calledOnce).to.be.true;
68+
expect(facade.ux.log.called).to.have.lengthOf(3); // Assuming each tool logs once
69+
});
70+
71+
it('should handle errors during migration', async function () {
72+
const migrationResult: MigrationResult = {
73+
records: new Map(),
74+
results: new Map()
75+
};
76+
77+
const lwcTool = sandbox.createStubInstance(LWCComponentMigrationTool);
78+
const labelsTool = sandbox.createStubInstance(CustomLabelMigrationTool);
79+
const apexTool = sandbox.createStubInstance(ApexClassMigrationTool);
80+
81+
lwcTool.migrate.rejects(new Error('LWC migration error'));
82+
labelsTool.migrate.resolves([{ name: 'Custom Label', id: '002' }]);
83+
apexTool.migrate.resolves([{ name: 'Apex Class', id: '003' }]);
84+
85+
sandbox.stub(facade, 'migrateAll').resolves({
86+
objectMigrationResults: [
87+
{ name: 'LWC Component', data: [], errors: ['LWC migration error'] },
88+
{ name: 'Custom Label', data: [] },
89+
{ name: 'Apex Class', data: [] }
90+
]
91+
});
92+
93+
await facade.migrateAll(migrationResult, 'testNamespace', ['lwc', 'labels', 'apex']);
94+
95+
expect(lwcTool.migrate.calledOnce).to.be.true;
96+
expect(labelsTool.migrate.calledOnce).to.be.true;
97+
expect(apexTool.migrate.calledOnce).to.be.true;
98+
99+
// Check that the error was logged
100+
expect(facade.logger.error.calledOnce).to.be.true;
101+
expect(facade.ux.log.called).to.have.lengthOf(3); // Assuming each tool logs once
102+
});
103+
});

0 commit comments

Comments
 (0)