Setting the file. One moment. Completion Report · Replatform · wix/skills · Skills Docslib/completion-report.js
JavaScript·200 lines·6 KB
STATUS_RANK
=
new
Map
(
COMPLETION_STATUS
.
map
((
status
,
index
)
=>
[status, index]));
14
15function count(value, field) {
16 if (value === undefined || value === null) {
17 return 0;
18 }
19 if (!Number.isInteger(value) || value < 0) {
20 throw new Error(`${field} must be a non-negative integer`);
21 }
22 return value;
23}
24
25function requireString(value, field) {
26 if (!value || typeof value !== 'string') {
27 throw new Error(`${field} must be a non-empty string`);
28 }
29 return value;
30}
31
32function asArray(value, field) {
33 if (value === undefined || value === null) {
34 return [];
35 }
36 if (!Array.isArray(value)) {
37 throw new Error(`${field} must be an array`);
38 }
39 return value;
40}
41
42function hasCount(rows, field) {
43 return rows.some((row) => row[field] > 0);
44}
45
46function worseStatus(left, right) {
47 const leftRank = STATUS_RANK.has(left) ? STATUS_RANK.get(left) : STATUS_RANK.get('complete');
48 const rightRank = STATUS_RANK.has(right) ? STATUS_RANK.get(right) : STATUS_RANK.get('complete');
49 return leftRank <= rightRank ? left : right;
50}
51
52function chooseCompletionStatus(input = {}) {
53 const statuses = asArray(input.statuses, 'statuses');
54 let selected = input.defaultStatus || 'complete';
55 if (!STATUS_RANK.has(selected)) {
56 throw new Error(`unknown completion status: ${selected}`);
57 }
58
59 for (const status of statuses) {
60 if (!STATUS_RANK.has(status)) {
61 throw new Error(`unknown completion status: ${status}`);
62 }
63 selected = worseStatus(selected, status);
64 }
65
66 if (input.aborted) {
67 selected = worseStatus(selected, 'aborted');
68 }
69 if (input.hasMismatches) {
70 selected = worseStatus(selected, 'incomplete_with_mismatches');
71 }
72 if (input.hasFailures) {
73 selected = worseStatus(selected, 'incomplete_with_failures');
74 }
75 if (input.hasDeferred) {
76 selected = worseStatus(selected, 'complete_with_deferred_records');
77 }
78 if (input.hasRecoveredRecords) {
79 selected = worseStatus(selected, 'complete_with_recovered_records');
80 }
81 if (input.hasWarnings) {
82 selected = worseStatus(selected, 'complete_with_warnings');
83 }
84
85 return selected;
86}
87
88function normalizeCompletenessRow(row, index = 0) {
89 if (!row || typeof row !== 'object' || Array.isArray(row)) {
90 throw new Error(`entityCompleteness[${index}] must be an object`);
91 }
92
93 const entity = requireString(row.entity, `entityCompleteness[${index}].entity`);
94 const subtype = row.subtype === undefined || row.subtype === null ? 'all' : requireString(row.subtype, `entityCompleteness[${index}].subtype`);
95 const extracted = count(row.extracted, `${entity}/${subtype}.extracted`);
96 const inScope = count(row.inScope, `${entity}/${subtype}.inScope`);
97 const attempted = count(row.attempted, `${entity}/${subtype}.attempted`);
98 const imported = count(row.imported, `${entity}/${subtype}.imported`);
99 const alreadyPresentByCrosswalk = count(
100 row.alreadyPresentByCrosswalk === undefined ? row.alreadyPresent : row.alreadyPresentByCrosswalk,
101 `${entity}/${subtype}.alreadyPresentByCrosswalk`
102 );
103 const deferred = count(row.deferred, `${entity}/${subtype}.deferred`);
104 const failed = count(row.failed, `${entity}/${subtype}.failed`);
105 const skippedOutOfScope = count(row.skippedOutOfScope, `${entity}/${subtype}.skippedOutOfScope`);
106 const unexpectedSkipped = count(
107 row.unexpectedSkipped === undefined ? row.skippedInScope : row.unexpectedSkipped,
108 `${entity}/${subtype}.unexpectedSkipped`
109 );
110 const reconciled = imported + alreadyPresentByCrosswalk + deferred + failed;
111 const expectedReconciled = inScope;
112 const mismatch = reconciled !== expectedReconciled || unexpectedSkipped > 0;
113
114 return {
115 entity,
116 subtype,
117 extracted,
118 inScope,
119 attempted,
120 imported,
121 alreadyPresentByCrosswalk,
122 deferred,
123 failed,
124 skippedOutOfScope,
125 unexpectedSkipped,
126 reconciled,
127 expectedReconciled,
128 mismatch,
129 reasons: Array.isArray(row.reasons) ? row.reasons : [],
130 };
131}
132
133function headlineForRow(row) {
134 const parts = [];
135 if (row.mismatch) {
136 parts.push(`mismatch ${row.reconciled}/${row.expectedReconciled}`);
137 }
138 if (row.deferred > 0) {
139 parts.push(`${row.deferred} deferred`);
140 }
141 if (row.failed > 0) {
142 parts.push(`${row.failed} failed`);
143 }
144 if (row.unexpectedSkipped > 0) {
145 parts.push(`${row.unexpectedSkipped} unexpected skipped`);
146 }
147 if (!parts.length) {
148 return null;
149 }
150 return {
151 entity: row.entity,
152 subtype: row.subtype,
153 message: `${row.entity}/${row.subtype}: ${parts.join(', ')}`,
154 };
155}
156
157function createCompletionReport(input = {}) {
158 const entityCompleteness = asArray(input.entityCompleteness, 'entityCompleteness')
159 .map((row, index) => normalizeCompletenessRow(row, index));
160 const mismatches = entityCompleteness.filter((row) => row.mismatch);
161 const headline = entityCompleteness.map(headlineForRow).filter(Boolean);
162 const warnings = asArray(input.warnings, 'warnings');
163 const recoveryActions = asArray(input.recoveryActions, 'recoveryActions');
164 const status = chooseCompletionStatus({
165 statuses: input.statuses,
166 aborted: input.aborted,
167 hasMismatches: mismatches.length > 0,
168 hasFailures: hasCount(entityCompleteness, 'failed'),
169 hasDeferred: hasCount(entityCompleteness, 'deferred'),
170 hasRecoveredRecords: recoveryActions.length > 0 || Boolean(input.hasRecoveredRecords),
171 hasWarnings: warnings.length > 0 || Boolean(input.hasWarnings),
172 });
173
174 return {
175 schemaVersion: 1,
176 runId: input.runId || null,
177 status,
178 headline,
179 entityCompleteness,
180 mismatches: mismatches.map((row) => ({
181 entity: row.entity,
182 subtype: row.subtype,
183 expectedReconciled: row.expectedReconciled,
184 reconciled: row.reconciled,
185 unexpectedSkipped: row.unexpectedSkipped,
186 })),
187 warnings,
188 recoveryActions,
189 artifacts: input.artifacts || {},
190 destinations: input.destinations || {},
191 urlPreservation: input.urlPreservation || null,
192 };
193}
194
195module.exports = {
196 COMPLETION_STATUS,
197 chooseCompletionStatus,
198 createCompletionReport,
199 normalizeCompletenessRow,
200};