Skip to content

Commit 995d628

Browse files
fix: related objects status for deployment failure case
1 parent 4c16b1e commit 995d628

File tree

7 files changed

+473
-75
lines changed

7 files changed

+473
-75
lines changed

messages/migrate.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@
284284
"deployComponentsManually": "Metadata Components deployment failed. Please deploy using generated package.xml manually using Salesforce CLI or Workbench. Check the logs for detailed error information.",
285285
"omniscriptDeploymentFailedContinuing": "Omniscript package deployment failed, continuing with report generation. Manual deployment required.",
286286
"deploymentFailedContinuing": "Deployment failed, continuing with report generation. Manual deployment required.",
287+
"manualDeploymentNeeded": "Manual deployment needed",
287288
"ensurePackageInstalled": "Please ensure omniscript customization package is properly installed: %s",
288289
"packageDeploymentFailedWithError": "Omniscript package deployment failed after %s attempts. Error: %s. Please check deployment logs and org settings.",
289290
"maxRetryAttemptsExceeded": "Maximum retry attempts (%s) exceeded for omniscript package deployment",
@@ -329,4 +330,4 @@
329330
"processingNotRequired": "Migration is not required for orgs on standard data model with the Omnistudio Metadata setting turned on.",
330331
"skippingTruncation": "Skipping truncation as the org is on standard data model.",
331332
"loglevelFlagDeprecated": "loglevel is deprecated. Use --verbose instead."
332-
}
333+
}

src/commands/omnistudio/migration/migrate.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,11 @@ export default class Migrate extends SfCommand<MigrateResult> {
278278
messages
279279
);
280280

281+
let deploymentFailed = false;
281282
try {
282283
await postMigrate.deploy(actionItems);
283284
} catch (error) {
285+
deploymentFailed = true;
284286
Logger.error(messages.getMessage('errorDeployingComponents'), error);
285287
Logger.logVerbose(error);
286288
// Even if deployment fails completely, continue with report generation
@@ -295,7 +297,8 @@ export default class Migrate extends SfCommand<MigrateResult> {
295297
messages,
296298
actionItems,
297299
objectsToProcess,
298-
migrateOnly
300+
migrateOnly,
301+
deploymentFailed
299302
);
300303
Logger.log(
301304
messages.getMessage('migrationSuccessfulMessage', [

src/migration/postMigrate.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,9 @@ export class PostMigrate extends BaseMigrationTool {
234234

235235
// Log the specific error details for troubleshooting
236236
Logger.logVerbose(`Deployment error details: ${errorMessage}`);
237+
238+
// Re-throw so callers (migrate.ts) can track deploymentFailed state
239+
throw error;
237240
}
238241
}
239242
}

src/utils/resultsbuilder/index.ts

Lines changed: 103 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ export class ResultsBuilder {
5959
private static successStatus = ['Ready for migration', 'Complete', 'Successfully migrated'];
6060
private static errorStatus = ['Failed', 'Needs manual intervention'];
6161

62+
/** Set at report generation start; used to show "Manual deployment needed" when deployment failed */
63+
private static deploymentFailed = false;
64+
6265
public static async generateReport(
6366
results: MigratedObject[],
6467
relatedObjectMigrationResult: RelatedObjectAssesmentInfo,
@@ -67,8 +70,11 @@ export class ResultsBuilder {
6770
messages: Messages<string>,
6871
actionItems: string[],
6972
objectsToProcess: string[],
70-
migrateOnly: string
73+
migrateOnly: string,
74+
deploymentFailed = false
7175
): Promise<void> {
76+
ResultsBuilder.deploymentFailed = deploymentFailed;
77+
7278
fs.mkdirSync(resultsDir, { recursive: true });
7379
Logger.info(messages.getMessage('generatingComponentReports'));
7480
for (const result of results) {
@@ -382,7 +388,9 @@ export class ResultsBuilder {
382388
total: result.length,
383389
filterGroups: [
384390
...this.getStatusFilterGroup(
385-
result.flatMap((item) => item.experienceSiteAssessmentPageInfos.map((page) => page.status))
391+
result.flatMap((item) =>
392+
item.experienceSiteAssessmentPageInfos.map((page) => this.resolveDisplayStatus(page.status, messages))
393+
)
386394
),
387395
],
388396
headerGroups: [
@@ -421,7 +429,7 @@ export class ResultsBuilder {
421429
],
422430
},
423431
],
424-
rows: this.getRowsForExperienceSites(result),
432+
rows: this.getRowsForExperienceSites(result, messages),
425433
props: JSON.stringify({
426434
recordName: 'Pages',
427435
rowBased: true,
@@ -453,7 +461,9 @@ export class ResultsBuilder {
453461
},
454462
assessmentDate: new Date().toLocaleString(),
455463
total: result.length,
456-
filterGroups: [...this.getStatusFilterGroup(result.map((item) => item.status))],
464+
filterGroups: [
465+
...this.getStatusFilterGroup(result.map((item) => this.resolveDisplayStatus(item.status, messages))),
466+
],
457467
headerGroups: [
458468
{
459469
header: [
@@ -499,14 +509,14 @@ export class ResultsBuilder {
499509
createRowDataParam('path', item.name, false, 1, 1, true, item.path),
500510
createRowDataParam(
501511
'status',
502-
item.status,
512+
this.resolveDisplayStatus(item.status, messages),
503513
false,
504514
1,
505515
1,
506516
false,
507517
undefined,
508518
undefined,
509-
item.status === 'Successfully migrated' ? 'text-success' : 'text-error'
519+
this.resolveStatusCssClass(item.status)
510520
),
511521
createRowDataParam(
512522
'diff',
@@ -658,7 +668,11 @@ export class ResultsBuilder {
658668
},
659669
assessmentDate: new Date().toLocaleString(),
660670
total: result.length,
661-
filterGroups: [...this.getStatusFilterGroup(result.flatMap((item) => this.getStatusFromErrors(item.errors)))],
671+
filterGroups: [
672+
...this.getStatusFilterGroup(
673+
result.flatMap((item) => this.resolveDisplayStatus(this.getStatusFromErrors(item.errors), messages))
674+
),
675+
],
662676
headerGroups: [
663677
{
664678
header: [
@@ -695,21 +709,27 @@ export class ResultsBuilder {
695709
],
696710
},
697711
],
698-
rows: this.getLwcRowsForReport(result),
712+
rows: this.getLwcRowsForReport(result, messages),
699713
};
700714

701715
const reportTemplate = fs.readFileSync(reportTemplateFilePath, 'utf8');
702716
const html = TemplateParser.generate(reportTemplate, data, messages);
703717
fs.writeFileSync(path.join(resultsDir, lwcFileName), html);
704718
}
705719

706-
private static getLwcRowsForReport(lwcAssessmentInfos: LWCAssessmentInfo[]): ReportRowParam[] {
720+
private static getLwcRowsForReport(
721+
lwcAssessmentInfos: LWCAssessmentInfo[],
722+
messages: Messages<string>
723+
): ReportRowParam[] {
707724
const rows: ReportRowParam[] = [];
708725

709726
for (const lwcAssessmentInfo of lwcAssessmentInfos) {
710727
let showCommon = true;
711728
const rid = `${this.rowClass}${this.rowId++}`;
712729
const commonRowSpan = Math.max(1, lwcAssessmentInfo.changeInfos.length);
730+
const actualStatus = this.getStatusFromErrors(lwcAssessmentInfo.errors);
731+
const displayStatus = this.resolveDisplayStatus(actualStatus, messages);
732+
const statusCssClass = this.resolveStatusCssClass(actualStatus);
713733
for (const fileChangeInfo of lwcAssessmentInfo.changeInfos) {
714734
rows.push({
715735
rowId: rid,
@@ -719,14 +739,14 @@ export class ResultsBuilder {
719739
createRowDataParam('name', lwcAssessmentInfo.name, true, commonRowSpan, 1, false),
720740
createRowDataParam(
721741
'status',
722-
this.getStatusFromErrors(lwcAssessmentInfo.errors),
742+
displayStatus,
723743
false,
724744
commonRowSpan,
725745
1,
726746
false,
727747
undefined,
728748
undefined,
729-
this.getStatusCssClass(lwcAssessmentInfo.errors)
749+
statusCssClass
730750
),
731751
]
732752
: []),
@@ -1021,57 +1041,66 @@ export class ResultsBuilder {
10211041
];
10221042
}
10231043

1024-
private static getDifferentStatusDataForFlexipage(data: FlexiPageAssessmentInfo[]): SummaryItemDetailParam[] {
1044+
private static buildStatusSummary(counts: {
1045+
completed: number;
1046+
manualDeploymentNeeded: number;
1047+
skipped: number;
1048+
failed: number;
1049+
}): SummaryItemDetailParam[] {
1050+
const result: SummaryItemDetailParam[] = [
1051+
{ name: 'Successfully migrated', count: counts.completed, cssClass: 'text-success' },
1052+
];
1053+
if (counts.manualDeploymentNeeded > 0) {
1054+
result.push({
1055+
name: 'Manual deployment needed',
1056+
count: counts.manualDeploymentNeeded,
1057+
cssClass: 'text-error',
1058+
});
1059+
}
1060+
result.push({ name: 'Skipped', count: counts.skipped, cssClass: 'text-error' });
1061+
result.push({ name: 'Failed', count: counts.failed, cssClass: 'text-error' });
1062+
return result;
1063+
}
1064+
1065+
private static countStatusesFromItems(statuses: string[]): {
1066+
completed: number;
1067+
manualDeploymentNeeded: number;
1068+
skipped: number;
1069+
failed: number;
1070+
} {
10251071
let completed = 0;
1072+
let manualDeploymentNeeded = 0;
10261073
let skipped = 0;
10271074
let failed = 0;
1028-
data.forEach((item) => {
1029-
if (item.status === 'Successfully migrated') completed++;
1030-
else if (item.status === 'Skipped') skipped++;
1031-
else failed++;
1032-
});
1075+
for (const status of statuses) {
1076+
if (this.isManualDeploymentNeeded(status)) {
1077+
manualDeploymentNeeded++;
1078+
} else if (status === 'Successfully migrated') {
1079+
completed++;
1080+
} else if (status === 'Skipped') {
1081+
skipped++;
1082+
} else {
1083+
failed++;
1084+
}
1085+
}
1086+
return { completed, manualDeploymentNeeded, skipped, failed };
1087+
}
10331088

1034-
return [
1035-
{ name: 'Successfully migrated', count: completed, cssClass: 'text-success' },
1036-
{ name: 'Skipped', count: skipped, cssClass: 'text-error' },
1037-
{ name: 'Failed', count: failed, cssClass: 'text-error' },
1038-
];
1089+
private static getDifferentStatusDataForFlexipage(data: FlexiPageAssessmentInfo[]): SummaryItemDetailParam[] {
1090+
return this.buildStatusSummary(this.countStatusesFromItems(data.map((item) => item.status)));
10391091
}
10401092

10411093
private static getDifferentStatusDataForLwc(data: LWCAssessmentInfo[]): SummaryItemDetailParam[] {
1042-
let completed = 0;
1043-
let failed = 0;
1044-
data.forEach((item) => {
1045-
if (this.getStatusFromErrors(item.errors) === 'Successfully migrated') completed++;
1046-
else failed++;
1047-
});
1048-
1049-
return [
1050-
{ name: 'Successfully migrated', count: completed, cssClass: 'text-success' },
1051-
{ name: 'Skipped', count: 0, cssClass: 'text-error' },
1052-
{ name: 'Failed', count: failed, cssClass: 'text-error' },
1053-
];
1094+
return this.buildStatusSummary(
1095+
this.countStatusesFromItems(data.map((item) => this.getStatusFromErrors(item.errors)))
1096+
);
10541097
}
10551098

10561099
private static getDifferentStatusDataForExperienceSites(
10571100
data: ExperienceSiteAssessmentInfo[]
10581101
): SummaryItemDetailParam[] {
1059-
let completed = 0;
1060-
let skipped = 0;
1061-
let failed = 0;
1062-
data
1063-
.flatMap((item) => item.experienceSiteAssessmentPageInfos)
1064-
.forEach((item) => {
1065-
if (item.status === 'Successfully migrated') completed++;
1066-
else if (item.status === 'Skipped') skipped++;
1067-
else failed++;
1068-
});
1069-
1070-
return [
1071-
{ name: 'Successfully migrated', count: completed, cssClass: 'text-success' },
1072-
{ name: 'Skipped', count: skipped, cssClass: 'text-error' },
1073-
{ name: 'Failed', count: failed, cssClass: 'text-error' },
1074-
];
1102+
const statuses = data.flatMap((item) => item.experienceSiteAssessmentPageInfos.map((page) => page.status));
1103+
return this.buildStatusSummary(this.countStatusesFromItems(statuses));
10751104
}
10761105

10771106
private static getStatusFilterGroup(statuses: string[]): FilterGroupParam[] {
@@ -1085,13 +1114,26 @@ export class ResultsBuilder {
10851114
return 'Successfully migrated';
10861115
}
10871116

1088-
private static getStatusCssClass(errors: string[], neutralSuccess = false): string {
1089-
if (errors && errors.length > 0) return 'text-error';
1090-
if (neutralSuccess) return '';
1091-
return 'text-success';
1117+
/** True when deployment failed but the component was successfully processed locally. */
1118+
private static isManualDeploymentNeeded(status: string): boolean {
1119+
return this.deploymentFailed && status === 'Successfully migrated';
1120+
}
1121+
1122+
/** Returns display status — "Manual deployment needed" when deployment failed, original status otherwise. */
1123+
private static resolveDisplayStatus(status: string, messages: Messages<string>): string {
1124+
return this.isManualDeploymentNeeded(status) ? messages.getMessage('manualDeploymentNeeded') : status;
1125+
}
1126+
1127+
/** Returns CSS class — error for manual deployment needed, success/error otherwise. */
1128+
private static resolveStatusCssClass(status: string): string {
1129+
if (this.isManualDeploymentNeeded(status)) return 'text-error';
1130+
return status === 'Successfully migrated' ? 'text-success' : 'text-error';
10921131
}
10931132

1094-
private static getRowsForExperienceSites(result: ExperienceSiteAssessmentInfo[]): ReportRowParam[] {
1133+
private static getRowsForExperienceSites(
1134+
result: ExperienceSiteAssessmentInfo[],
1135+
messages: Messages<string>
1136+
): ReportRowParam[] {
10951137
const rows: ReportRowParam[] = [];
10961138

10971139
result.forEach((item) => {
@@ -1100,7 +1142,7 @@ export class ResultsBuilder {
11001142
item.experienceSiteAssessmentPageInfos.forEach((page) => {
11011143
rows.push({
11021144
rowId: rId,
1103-
data: this.getRowDataForExperienceSites(page, item, showBundleName),
1145+
data: this.getRowDataForExperienceSites(page, item, showBundleName, messages),
11041146
});
11051147
showBundleName = false;
11061148
});
@@ -1112,8 +1154,11 @@ export class ResultsBuilder {
11121154
private static getRowDataForExperienceSites(
11131155
page: ExperienceSiteAssessmentPageInfo,
11141156
item: ExperienceSiteAssessmentInfo,
1115-
showBundleName: boolean
1157+
showBundleName: boolean,
1158+
messages: Messages<string>
11161159
): ReportDataParam[] {
1160+
const displayStatus = this.resolveDisplayStatus(page.status, messages);
1161+
const statusCssClass = this.resolveStatusCssClass(page.status);
11171162
return [
11181163
createRowDataParam(
11191164
'name',
@@ -1128,17 +1173,7 @@ export class ResultsBuilder {
11281173
),
11291174
createRowDataParam('pageName', page.name, false, 1, 1, false, undefined, undefined),
11301175
createRowDataParam('path', page.name + this.experienceSiteFileSuffix, false, 1, 1, true, page.path),
1131-
createRowDataParam(
1132-
'status',
1133-
page.status,
1134-
false,
1135-
1,
1136-
1,
1137-
false,
1138-
undefined,
1139-
undefined,
1140-
page.status === 'Successfully migrated' ? 'text-success' : 'text-error'
1141-
),
1176+
createRowDataParam('status', displayStatus, false, 1, 1, false, undefined, undefined, statusCssClass),
11421177
createRowDataParam(
11431178
'diff',
11441179
page.name + 'diff',

0 commit comments

Comments
 (0)