Skip to content

Commit 746281e

Browse files
@W-18307604 feat: identify data model and package version used in the org
2 parents c2645d0 + edf2901 commit 746281e

File tree

4 files changed

+341
-1
lines changed

4 files changed

+341
-1
lines changed

src/commands/omnistudio/migration/assess.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { DebugTimer, DataRaptorAssessmentInfo, FlexCardAssessmentInfo } from '..
1313

1414
import { Logger } from '../../../utils/logger';
1515
import OmnistudioRelatedObjectMigrationFacade from '../../../migration/related/OmnistudioRelatedObjectMigrationFacade';
16+
import { OmnistudioOrgDetails, OrgUtils } from '../../../utils/orgUtils';
1617

1718
Messages.importMessagesDirectory(__dirname);
1819
const messages = Messages.loadMessages('@salesforce/plugin-omnistudio-migration-tool', 'assess');
@@ -47,6 +48,18 @@ export default class Assess extends OmniStudioBaseCommand {
4748
const apiVersion = (this.flags.apiversion || '55.0') as string;
4849
const allVersions = (this.flags.allversions || false) as boolean;
4950
const conn = this.org.getConnection();
51+
const orgs: OmnistudioOrgDetails = await OrgUtils.getOrgDetails(conn, namespace);
52+
53+
if (orgs.packageDetails.length === 0) {
54+
this.ux.log('No package installed on given org.');
55+
return;
56+
}
57+
58+
if (orgs.omniStudioOrgPermissionEnabled) {
59+
this.ux.log('The org is already on standard data model.');
60+
return;
61+
}
62+
5063
Logger.initialiseLogger(this.ux, this.logger);
5164
const projectDirectory = OmnistudioRelatedObjectMigrationFacade.intializeProject();
5265
conn.setApiVersion(apiVersion);

src/commands/omnistudio/migration/migrate.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { OmniScriptExportType, OmniScriptMigrationTool } from '../../../migratio
2222
import { Logger } from '../../../utils/logger';
2323
import OmnistudioRelatedObjectMigrationFacade from '../../../migration/related/OmnistudioRelatedObjectMigrationFacade';
2424
import { generatePackageXml } from '../../../utils/generatePackageXml';
25+
import { OmnistudioOrgDetails, OrgUtils } from '../../../utils/orgUtils';
2526

2627
// Initialize Messages with the current plugin directory
2728
Messages.importMessagesDirectory(__dirname);
@@ -71,6 +72,18 @@ export default class Migrate extends OmniStudioBaseCommand {
7172
const conn = this.org.getConnection();
7273
conn.setApiVersion(apiVersion);
7374

75+
const orgs: OmnistudioOrgDetails = await OrgUtils.getOrgDetails(conn, namespace);
76+
77+
if (orgs.packageDetails.length === 0) {
78+
this.ux.log('No package installed on given org.');
79+
return;
80+
}
81+
82+
if (orgs.omniStudioOrgPermissionEnabled) {
83+
this.ux.log('The org is already on standard data model.');
84+
return;
85+
}
86+
7487
// Let's time every step
7588
DebugTimer.getInstance().start();
7689
let projectPath: string;

src/utils/orgUtils/index.ts

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
/* eslint-disable */
2+
3+
import { Connection } from '@salesforce/core';
4+
import { QueryTools } from '../query';
5+
6+
interface InstalledPackage {
7+
MajorVersion: number;
8+
MinorVersion: number;
9+
NamespacePrefix: string;
10+
Name: string;
11+
}
12+
13+
export interface OmnistudioOrgDetails {
14+
packageDetails: PackageDetail[];
15+
omniStudioOrgPermissionEnabled: boolean;
16+
}
17+
18+
export interface PackageDetail {
19+
version: string;
20+
namespace: string;
21+
}
22+
23+
export class OrgUtils {
24+
/**
25+
* Skip the 'omnistudio' namespace because it belongs to the foundation package,
26+
* which already works with the standard model and does not need migration.
27+
* */
28+
private static readonly namespaces = new Set<string>([
29+
'as_dev_01',
30+
'as_dev_02',
31+
'as_dev_03',
32+
'as_dev_04',
33+
'as_dev_05',
34+
'as_dev_06',
35+
'as_dev_07',
36+
'as_dev_08',
37+
'as_dev_09',
38+
'as_dev_10',
39+
'as_dev_11',
40+
'as_dev_12',
41+
'as_dev_13',
42+
'as_dev_14',
43+
'as_dev_15',
44+
'as_dev_16',
45+
'as_dev_17',
46+
'as_dev_18',
47+
'as_dev_19',
48+
'as_dev_20',
49+
'cci_01',
50+
'cci_02',
51+
'cci_03',
52+
'cci_04',
53+
'cci_05',
54+
'cci_06',
55+
'cci_07',
56+
'clm_dev_01',
57+
'clm_dev_02',
58+
'clm_dev_03',
59+
'clm_dev_04',
60+
'clm_dev_05',
61+
'clm_dev_06',
62+
'clm_dev_07',
63+
'clm_dev_08',
64+
'clm_dev_09',
65+
'clm_dev_10',
66+
'clm_dev_11',
67+
'clm_dev_12',
68+
'clm_dev_13',
69+
'clm_dev_14',
70+
'clm_dev_15',
71+
'clm_dev_16',
72+
'clm_dev_17',
73+
'clm_dev_18',
74+
'clm_dev_19',
75+
'clm_dev_20',
76+
'clm_dev_21',
77+
'clm_dev_22',
78+
'clm_dev_23',
79+
'clm_dev_24',
80+
'clm_dev_25',
81+
'clm_dev_26',
82+
'clm_dev_27',
83+
'clm_dev_28',
84+
'clm_dev_29',
85+
'clm_dev_30',
86+
'common',
87+
'devops001gs0',
88+
'devops002gs0',
89+
'devops003gs0',
90+
'devops004gs0',
91+
'devops005gs0',
92+
'devops006r1',
93+
'devops007r1',
94+
'devops008r1',
95+
'devops009r1',
96+
'devops010r1',
97+
'devopsimpkg01',
98+
'devopsimpkg11',
99+
'devopsimpkg12',
100+
'devopsimpkg13',
101+
'devopsimpkg14',
102+
'devopsimpkg15',
103+
'devopsimpkg16',
104+
'devopsimpkg17',
105+
'devopsimpkg18',
106+
'devopsimpkg19',
107+
'devopsimpkg20',
108+
'devopsimpkg21',
109+
'devopsimpkg22',
110+
'devopsimpkg23',
111+
'devopsimpkg24',
112+
'devopsimpkg25',
113+
'devopsimpkg26',
114+
'foundationpkgtest',
115+
'industries001',
116+
'industriesgs0',
117+
'ins_exp_pc01',
118+
'ins_exp_pc02',
119+
'ins_exp_pc03',
120+
'ins_exp_vb01',
121+
'ins_exp_vb02',
122+
'ins_exp_vb03',
123+
'ins_fsc04_gs0',
124+
'instest12',
125+
'kc_na46',
126+
'pc_dev_na46',
127+
'pc_qe_na46',
128+
'perf_dc230',
129+
'scalpel',
130+
'sfi_media_1',
131+
'sfi_media_2',
132+
'sfi_media_3',
133+
'sfi_media_4',
134+
'sfi_media_5',
135+
'sfi_media_6',
136+
'sfi_media_7',
137+
'sfi_media_8',
138+
'sfi_media_9',
139+
'sfi_media_10',
140+
'slncloud_na81_0',
141+
'slncloud_na81_1',
142+
'slncloud_r1_01',
143+
'slncloud_r1_02',
144+
'slncloud_stg_01',
145+
'slncloud_test',
146+
'solutioncloud',
147+
'stmfahins01_oie',
148+
'stmpahins01',
149+
'vb_dev_na46',
150+
'vb_qe_na46',
151+
'vlocity_bmk',
152+
'vlocity_clmperf',
153+
'vlocity_cmt',
154+
'vlocity_cpq1',
155+
'vlocity_cpq2',
156+
'vlocity_cpq3',
157+
'vlocity_cpq4',
158+
'vlocity_cpq5',
159+
'vlocity_cpq6',
160+
'vlocity_cpq7',
161+
'vlocity_cpq8',
162+
'vlocity_cpq9',
163+
'vlocity_cpq10',
164+
'vlocity_cpq11',
165+
'vlocity_cpq12',
166+
'vlocity_cpq13',
167+
'vlocity_cpq14',
168+
'vlocity_cpq15',
169+
'vlocity_cpq16',
170+
'vlocity_cpq17',
171+
'vlocity_cpq18',
172+
'vlocity_cpq19',
173+
'vlocity_cpq20',
174+
'vlocity_cpq21',
175+
'vlocity_cpq22',
176+
'vlocity_cpq23',
177+
'vlocity_cpq24',
178+
'vlocity_cpq25',
179+
'vlocity_cpq26',
180+
'vlocity_cpq27',
181+
'vlocity_cpq28',
182+
'vlocity_cpq29',
183+
'vlocity_cpq30',
184+
'vlocity_cpq31',
185+
'vlocity_cpq32',
186+
'vlocity_cpq33',
187+
'vlocity_cpq34',
188+
'vlocity_cpq35',
189+
'vlocity_cpq36',
190+
'vlocity_cpq37',
191+
'vlocity_cpq38',
192+
'vlocity_cpq39',
193+
'vlocity_cpq40',
194+
'vlocity_dc',
195+
'vlocity_digital',
196+
'vlocity_erg',
197+
'vlocity_fsc_gs0',
198+
'vlocity_ins',
199+
'vlocity_ins_fsc',
200+
'vlocity_lwc1',
201+
'vlocity_lwc2',
202+
'vlocity_lwc3',
203+
'vlocity_lwc4',
204+
'vlocity_lwc5',
205+
'vlocity_lwc6',
206+
'vlocity_lwc7',
207+
'vlocity_lwc8',
208+
'vlocity_lwc9',
209+
'vlocity_lwc10',
210+
'vlocity_lwc11',
211+
'vlocity_lwc12',
212+
'vlocity_lwc13',
213+
'vlocity_lwc14',
214+
'vlocity_lwc15',
215+
'vlocity_lwc16',
216+
'vlocity_lwc17',
217+
'vlocity_lwc18',
218+
'vlocity_lwc19',
219+
'vlocity_lwc20',
220+
'vlocity_lwc21',
221+
'vlocity_lwc22',
222+
'vlocity_lwc23',
223+
'vlocity_lwc24',
224+
'vlocity_lwc25',
225+
'vlocity_lwc26',
226+
'vlocity_lwc27',
227+
'vlocity_lwc28',
228+
'vlocity_lwc29',
229+
'vlocity_lwc30',
230+
'vlocity_lwc31',
231+
'vlocity_lwc32',
232+
'vlocity_lwc33',
233+
'vlocity_lwctest',
234+
'vlocity_perf',
235+
'vlocity_ps',
236+
'vlocity_upc',
237+
'vlocityins1',
238+
'vlocityins2',
239+
'vlocityins2_fsc',
240+
'vlocityins3',
241+
'vlocityins4',
242+
'vlocityins5',
243+
'vlocityins6',
244+
'vlocityins7',
245+
'vlocityins8',
246+
'vlocityins9',
247+
'vlocityins10',
248+
'vlocityins11',
249+
'vlocityins12',
250+
'vlocityins13',
251+
'vlocityins14',
252+
'vlocityins16',
253+
'vlocityins17',
254+
'vlocityins19',
255+
]);
256+
257+
// Define the fields to retrieve from the Publisher object
258+
private static readonly fields = ['MajorVersion', 'MinorVersion', 'NamespacePrefix', 'Name'];
259+
260+
// Define the object name for querying installed packages
261+
private static readonly objectName = 'Publisher';
262+
263+
/**
264+
* Fetches package details (version and namespace) for specific installed packages.
265+
*
266+
* @param connection - Salesforce connection object
267+
* @returns Promise resolving to an array of PackageDetail objects
268+
*/
269+
public static async getOrgDetails(connection: Connection, namespace: string): Promise<OmnistudioOrgDetails> {
270+
//Execute apex rest resource to get omnistudio org permission
271+
const omniStudioOrgPermissionEnabled: boolean = await this.isOmniStudioOrgPermissionEnabled(connection, namespace);
272+
273+
// Query all installed packages and cast the result to InstalledPackage[]
274+
const allInstalledPackages = (await QueryTools.queryAll(
275+
connection,
276+
'',
277+
this.objectName,
278+
this.fields
279+
)) as unknown as InstalledPackage[];
280+
281+
const packageDetails: PackageDetail[] = allInstalledPackages
282+
// Filter packages to only include those with a namespace in the predefined list
283+
.filter((pkg) => this.namespaces.has(pkg.NamespacePrefix))
284+
// Map the filtered packages to the required format: { version, namespace }
285+
.map((pkg) => ({
286+
version: `${pkg.MajorVersion}.${pkg.MinorVersion}`,
287+
namespace: pkg.NamespacePrefix,
288+
}));
289+
290+
return {
291+
packageDetails: packageDetails,
292+
omniStudioOrgPermissionEnabled: omniStudioOrgPermissionEnabled,
293+
};
294+
}
295+
296+
/** *
297+
* @param connection Salesforce connection object
298+
* @param namespace namespace of the org which is required to hit the apex rest resource.
299+
* @returns
300+
*/
301+
public static async isOmniStudioOrgPermissionEnabled(connection: Connection, namespace: string): Promise<boolean> {
302+
try {
303+
return await connection.apex.get('/' + namespace + '/v1/orgPermission');
304+
} catch (e) {
305+
// Returning false as a fallback when the endpoint is not found.
306+
// As part of the 256 MVP, we don't want to block the migration just because the endpoint is missing.
307+
return !(e.errorCode === 'NOT_FOUND');
308+
}
309+
310+
return true;
311+
}
312+
}

src/utils/query/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ export class QueryTools {
66
public static buildCustomObjectQuery(namespace: string, name: string, fields: string[], filters?: Map<string, any>) {
77
const queryFields = this.buildCustomObjectFields(namespace, ['Id', ...fields]);
88

9-
let query = 'SELECT ' + queryFields.join(', ') + ' FROM ' + namespace + '__' + name;
9+
let query = namespace
10+
? 'SELECT ' + queryFields.join(', ') + ' FROM ' + namespace + '__' + name
11+
: 'SELECT ' + queryFields.join(', ') + ' FROM ' + name;
1012

1113
const andFilters = [];
1214
if (filters && filters.size > 0) {

0 commit comments

Comments
 (0)