@contrail/cli 3.5.1-alpha.68d57e0 → 3.5.1-alpha.9da7ac5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/node_modules/@contrail/loader/lib/color-processor.js +23 -9
- package/node_modules/@contrail/loader/lib/color-processor.spec.js +62 -112
- package/node_modules/@contrail/loader/lib/common/chunk-by-url-length.d.ts +1 -1
- package/node_modules/@contrail/loader/lib/common/chunk-by-url-length.js +0 -2
- package/node_modules/@contrail/loader/lib/common/chunk-by-url-length.spec.js +0 -4
- package/node_modules/@contrail/loader/lib/common/size-range-helpers.js +1 -0
- package/node_modules/@contrail/loader/lib/constants.d.ts +3 -0
- package/node_modules/@contrail/loader/lib/constants.js +4 -1
- package/node_modules/@contrail/loader/lib/entities-v2/colors-v2/color-processor-v2.js +23 -9
- package/node_modules/@contrail/loader/lib/entities-v2/colors-v2/color-processor-v2.spec.js +62 -112
- package/node_modules/@contrail/loader/lib/entities-v2/entities-helper.js +1 -0
- package/node_modules/@contrail/loader/lib/entities-v2/linesheet-v2/assortment-processor.js +1 -0
- package/node_modules/@contrail/loader/lib/entities-v2/linesheet-v2/item-processor.js +22 -8
- package/node_modules/@contrail/loader/lib/entities-v2/linesheet-v2/item-processor.spec.js +1 -43
- package/node_modules/@contrail/loader/lib/pipeline/item-execute-helpers.js +3 -77
- package/node_modules/@contrail/loader/lib/pipeline/item-execute-helpers.spec.js +0 -82
- package/node_modules/@contrail/loader/lib/pipeline/preview.js +5 -0
- package/node_modules/@contrail/loader/lib/pipeline/preview.spec.js +2 -2
- package/node_modules/@contrail/loader/lib/pipeline/project-item-execute-helpers.js +1 -1
- package/node_modules/@contrail/loader/lib/pipeline/transform.js +0 -18
- package/node_modules/@contrail/loader/lib/pipeline/transform.spec.js +2 -3
- package/node_modules/@contrail/loader/lib/pipeline/types.d.ts +2 -5
- package/node_modules/@contrail/loader/lib/pipeline/types.js +0 -2
- package/node_modules/@contrail/loader/lib/pipeline/validate.js +437 -184
- package/node_modules/@contrail/loader/lib/pipeline/validate.spec.js +53 -157
- package/node_modules/@contrail/loader/package.json +2 -3
- package/oclif.manifest.json +1 -1
- package/package.json +2 -2
- package/node_modules/@contrail/loader/lib/common/color-validation.d.ts +0 -26
- package/node_modules/@contrail/loader/lib/common/color-validation.js +0 -163
- package/node_modules/@contrail/loader/lib/common/color-validation.spec.d.ts +0 -1
- package/node_modules/@contrail/loader/lib/common/color-validation.spec.js +0 -102
- package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.d.ts +0 -5
- package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.js +0 -562
- package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.spec.d.ts +0 -1
- package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.spec.js +0 -231
- package/node_modules/validate-color/LICENSE +0 -21
- package/node_modules/validate-color/README.md +0 -265
- package/node_modules/validate-color/lib/index.d.ts +0 -10
- package/node_modules/validate-color/lib/index.js +0 -1
- package/node_modules/validate-color/package.json +0 -91
- package/node_modules/validate-color/test/redos.js +0 -29
|
@@ -2,33 +2,15 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.validate = void 0;
|
|
4
4
|
const types_1 = require("@contrail/types");
|
|
5
|
-
const lodash_1 = require("lodash");
|
|
6
|
-
const data_transformer_functions_1 = require("../common/data-transformer-functions");
|
|
7
5
|
const entity_processor_v2_1 = require("../entities-v2/entity-processor-v2");
|
|
8
6
|
const processor_registry_1 = require("../processor-registry");
|
|
9
7
|
const entities_helper_1 = require("../entities-v2/entities-helper");
|
|
10
|
-
const entities_helper_2 = require("../common/entities-helper");
|
|
11
|
-
const chunk_by_url_length_1 = require("../common/chunk-by-url-length");
|
|
12
|
-
const pLimit = require('p-limit');
|
|
13
8
|
const types_2 = require("./types");
|
|
14
9
|
const CONFIG_CATEGORY = types_2.PreValidationCategory.CONFIG;
|
|
15
10
|
const MISSING_REQUIRED_FIELD_CATEGORY = types_2.PreValidationCategory.MISSING_REQUIRED_FIELD;
|
|
16
11
|
const REFERENCE_NOT_FOUND_CATEGORY = types_2.PreValidationCategory.REFERENCE_DATA_NOT_FOUND;
|
|
17
12
|
const FAMILY_INCONSISTENT_CATEGORY = types_2.PreValidationCategory.FAMILY_PROPERTY_VALUES_INCONSISTENT;
|
|
18
13
|
const SELF_REFERENCE_CATEGORY = types_2.PreValidationCategory.SELF_REFERENCE;
|
|
19
|
-
function issueTypeToCategory(issueType) {
|
|
20
|
-
switch (issueType) {
|
|
21
|
-
case types_2.IssueType.INVALID_DATA:
|
|
22
|
-
return types_2.PreValidationCategory.INVALID_DATA;
|
|
23
|
-
case types_2.IssueType.MISSING_DATA:
|
|
24
|
-
return types_2.PreValidationCategory.MISSING_REQUIRED_FIELD;
|
|
25
|
-
case types_2.IssueType.DUPLICATE_FEDERATED_ID:
|
|
26
|
-
return types_2.PreValidationCategory.DUPLICATE_FEDERATED_ID;
|
|
27
|
-
case types_2.IssueType.CONFIG:
|
|
28
|
-
default:
|
|
29
|
-
return types_2.PreValidationCategory.CONFIG;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
14
|
function isEmpty(value) {
|
|
33
15
|
return value === null || value === undefined || value === '';
|
|
34
16
|
}
|
|
@@ -44,15 +26,24 @@ async function validate(rows, config, columnAnalysis, client) {
|
|
|
44
26
|
if (!rows?.length) {
|
|
45
27
|
return buildResult(issues);
|
|
46
28
|
}
|
|
47
|
-
if (columnAnalysis.identifierIssues?.length) {
|
|
48
|
-
issues.push(...columnAnalysis.identifierIssues);
|
|
49
|
-
}
|
|
50
29
|
const processorIssues = runProcessorValidation(rows, config);
|
|
51
30
|
issues.push(...processorIssues);
|
|
52
31
|
const itemNameIssues = validateItemNames(rows, config);
|
|
53
32
|
issues.push(...itemNameIssues);
|
|
54
33
|
const identifierConflictIssues = validateStaticIdentifierConflicts(config);
|
|
55
34
|
issues.push(...identifierConflictIssues);
|
|
35
|
+
const projectItemColumnIssues = validateProjectItemColumns(rows, config);
|
|
36
|
+
issues.push(...projectItemColumnIssues);
|
|
37
|
+
if (client && projectItemColumnIssues.length === 0) {
|
|
38
|
+
const resolutionIssues = await validateAndResolveProjectIdentifiers(rows, config, client);
|
|
39
|
+
issues.push(...resolutionIssues);
|
|
40
|
+
}
|
|
41
|
+
const assortmentColumnIssues = validateAssortmentColumns(rows, config);
|
|
42
|
+
issues.push(...assortmentColumnIssues);
|
|
43
|
+
if (client && assortmentColumnIssues.length === 0) {
|
|
44
|
+
const assortmentIssues = await validateAndResolveAssortmentIdentifiers(rows, config, client);
|
|
45
|
+
issues.push(...assortmentIssues);
|
|
46
|
+
}
|
|
56
47
|
const partialUpdateIssues = validatePartialAssortmentConfig(rows, config);
|
|
57
48
|
issues.push(...partialUpdateIssues);
|
|
58
49
|
const typePathExistenceIssues = validateTypePathExistence(columnAnalysis.typesByPath);
|
|
@@ -63,12 +54,6 @@ async function validate(rows, config, columnAnalysis, client) {
|
|
|
63
54
|
issues.push(...suffixIssues);
|
|
64
55
|
const selfRefIssues = validateSelfReferences(rows, config, columnAnalysis.typesByPath);
|
|
65
56
|
issues.push(...selfRefIssues);
|
|
66
|
-
if (client) {
|
|
67
|
-
const objectRefIssues = await validateObjectReferences(rows, columnAnalysis.typesByPath, client);
|
|
68
|
-
issues.push(...objectRefIssues);
|
|
69
|
-
}
|
|
70
|
-
const transformIssues = promoteTransformValidationErrors(columnAnalysis);
|
|
71
|
-
issues.push(...transformIssues);
|
|
72
57
|
const optionSetIssues = validateOptionSets(rows, columnAnalysis.typePropertyMaps);
|
|
73
58
|
issues.push(...optionSetIssues);
|
|
74
59
|
const booleanIssues = validateBooleanFormats(rows, columnAnalysis.typePropertyMaps);
|
|
@@ -158,7 +143,7 @@ function runProcessorValidation(rows, config) {
|
|
|
158
143
|
for (const colIssue of error.columnIssues ?? []) {
|
|
159
144
|
issues.push({
|
|
160
145
|
severity: types_2.PreValidationSeverity.ERROR,
|
|
161
|
-
category:
|
|
146
|
+
category: CONFIG_CATEGORY,
|
|
162
147
|
columnName: colIssue.columnName,
|
|
163
148
|
propertySlug: colIssue.columnName,
|
|
164
149
|
propertyType: '',
|
|
@@ -171,7 +156,7 @@ function runProcessorValidation(rows, config) {
|
|
|
171
156
|
for (const colIssue of warning.columnIssues ?? []) {
|
|
172
157
|
issues.push({
|
|
173
158
|
severity: types_2.PreValidationSeverity.WARNING,
|
|
174
|
-
category:
|
|
159
|
+
category: CONFIG_CATEGORY,
|
|
175
160
|
columnName: colIssue.columnName,
|
|
176
161
|
propertySlug: colIssue.columnName,
|
|
177
162
|
propertyType: '',
|
|
@@ -182,89 +167,48 @@ function runProcessorValidation(rows, config) {
|
|
|
182
167
|
}
|
|
183
168
|
return issues;
|
|
184
169
|
}
|
|
185
|
-
function promoteTransformValidationErrors(columnAnalysis) {
|
|
186
|
-
const issues = [];
|
|
187
|
-
const buckets = [
|
|
188
|
-
...(columnAnalysis.errors ?? []),
|
|
189
|
-
...(columnAnalysis.warnings ?? []),
|
|
190
|
-
];
|
|
191
|
-
const optionSetMap = columnAnalysis.typePropertyMaps?.optionSetUsingTypePropertiesBySlug ?? {};
|
|
192
|
-
const isOptionSetColumn = (col) => Boolean(optionSetMap[col] || optionSetMap[(0, data_transformer_functions_1.removeOptionOrFamilySuffix)(col)]);
|
|
193
|
-
const grouped = new Map();
|
|
194
|
-
for (const issue of buckets) {
|
|
195
|
-
for (const ci of issue?.columnIssues ?? []) {
|
|
196
|
-
if (ci?.issueType !== types_2.IssueType.VALIDATION_ERROR)
|
|
197
|
-
continue;
|
|
198
|
-
if (isOptionSetColumn(ci.columnName))
|
|
199
|
-
continue;
|
|
200
|
-
const key = `${ci.columnName}::${ci.message}`;
|
|
201
|
-
if (!grouped.has(key)) {
|
|
202
|
-
grouped.set(key, { columnName: ci.columnName, message: ci.message, affectedRows: [] });
|
|
203
|
-
}
|
|
204
|
-
grouped.get(key).affectedRows.push({
|
|
205
|
-
row: issue.rowNumber ?? -1,
|
|
206
|
-
value: ci.data ?? '',
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
for (const { columnName, message, affectedRows } of grouped.values()) {
|
|
211
|
-
issues.push({
|
|
212
|
-
severity: types_2.PreValidationSeverity.ERROR,
|
|
213
|
-
category: types_2.PreValidationCategory.OPTION_SET,
|
|
214
|
-
columnName,
|
|
215
|
-
propertySlug: (0, data_transformer_functions_1.removeOptionOrFamilySuffix)(columnName),
|
|
216
|
-
propertyType: 'multi_select',
|
|
217
|
-
message,
|
|
218
|
-
affectedRows,
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
return issues;
|
|
222
|
-
}
|
|
223
170
|
function validateOptionSets(rows, typePropertyMaps) {
|
|
224
171
|
const { optionSetUsingTypePropertiesBySlug } = typePropertyMaps;
|
|
225
172
|
const issues = [];
|
|
226
173
|
const skipColumns = new Set();
|
|
227
|
-
const propertyForColumn = (key) => optionSetUsingTypePropertiesBySlug[key] ??
|
|
228
|
-
optionSetUsingTypePropertiesBySlug[(0, data_transformer_functions_1.removeOptionOrFamilySuffix)(key)];
|
|
229
174
|
const affectedRowsByColumn = {};
|
|
230
|
-
const propertyByColumn = {};
|
|
231
175
|
forEachRow(rows, (row, rowNumber) => {
|
|
232
176
|
for (const [key, value] of Object.entries(row)) {
|
|
233
177
|
if (isEmpty(value))
|
|
234
178
|
continue;
|
|
235
|
-
|
|
236
|
-
if (!property)
|
|
179
|
+
if (!optionSetUsingTypePropertiesBySlug[key])
|
|
237
180
|
continue;
|
|
238
181
|
if (!affectedRowsByColumn[key])
|
|
239
182
|
affectedRowsByColumn[key] = [];
|
|
240
183
|
affectedRowsByColumn[key].push({ row: rowNumber, value: String(value) });
|
|
241
|
-
propertyByColumn[key] = property;
|
|
242
184
|
}
|
|
243
185
|
});
|
|
244
|
-
for (const [
|
|
186
|
+
for (const [slug, property] of Object.entries(optionSetUsingTypePropertiesBySlug)) {
|
|
187
|
+
if (!affectedRowsByColumn[slug])
|
|
188
|
+
continue;
|
|
245
189
|
if (!property.typePropertyOptionSetId) {
|
|
246
190
|
issues.push({
|
|
247
191
|
severity: types_2.PreValidationSeverity.ERROR,
|
|
248
192
|
category: types_2.PreValidationCategory.OPTION_SET,
|
|
249
|
-
columnName,
|
|
193
|
+
columnName: slug,
|
|
250
194
|
propertySlug: property.slug,
|
|
251
195
|
propertyType: property.propertyType,
|
|
252
|
-
message: `Property "${
|
|
253
|
-
affectedRows: affectedRowsByColumn[
|
|
196
|
+
message: `Property "${slug}" is type ${PROPERTY_TYPE_DISPLAY[property.propertyType] ?? property.propertyType} but has no option set attached. Attach an option set to this property or change the property type.`,
|
|
197
|
+
affectedRows: affectedRowsByColumn[slug],
|
|
254
198
|
});
|
|
255
|
-
skipColumns.add(
|
|
199
|
+
skipColumns.add(slug);
|
|
256
200
|
}
|
|
257
201
|
else if (!property.options || property.options.length === 0) {
|
|
258
202
|
issues.push({
|
|
259
203
|
severity: types_2.PreValidationSeverity.ERROR,
|
|
260
204
|
category: types_2.PreValidationCategory.OPTION_SET,
|
|
261
|
-
columnName,
|
|
205
|
+
columnName: slug,
|
|
262
206
|
propertySlug: property.slug,
|
|
263
207
|
propertyType: property.propertyType,
|
|
264
|
-
message: `Option set for property "${
|
|
265
|
-
affectedRows: affectedRowsByColumn[
|
|
208
|
+
message: `Option set for property "${slug}" is empty. Values in this column cannot be validated. Populate the option set or remove data from this column.`,
|
|
209
|
+
affectedRows: affectedRowsByColumn[slug],
|
|
266
210
|
});
|
|
267
|
-
skipColumns.add(
|
|
211
|
+
skipColumns.add(slug);
|
|
268
212
|
}
|
|
269
213
|
}
|
|
270
214
|
const issuesByColumn = {};
|
|
@@ -272,7 +216,7 @@ function validateOptionSets(rows, typePropertyMaps) {
|
|
|
272
216
|
for (const [key, value] of Object.entries(row)) {
|
|
273
217
|
if (isEmpty(value))
|
|
274
218
|
continue;
|
|
275
|
-
const property =
|
|
219
|
+
const property = optionSetUsingTypePropertiesBySlug[key];
|
|
276
220
|
if (!property)
|
|
277
221
|
continue;
|
|
278
222
|
if (skipColumns.has(key))
|
|
@@ -654,6 +598,211 @@ function validateAllOverrideSuffixes(rows, config, columnAnalysis) {
|
|
|
654
598
|
}
|
|
655
599
|
return issues;
|
|
656
600
|
}
|
|
601
|
+
async function validateAndResolveProjectIdentifiers(rows, config, client) {
|
|
602
|
+
const isProjectItemLoad = config.loadType.some((lt) => lt === types_2.LoadType.PROJECT_ITEM);
|
|
603
|
+
if (!isProjectItemLoad)
|
|
604
|
+
return [];
|
|
605
|
+
const hasWorkspaceIdentifier = rows.some((row) => row.workspaceIdentifier);
|
|
606
|
+
if (hasWorkspaceIdentifier) {
|
|
607
|
+
return validateWorkspaceIdentifiers(rows, client);
|
|
608
|
+
}
|
|
609
|
+
return validateProjectIds(rows, client);
|
|
610
|
+
}
|
|
611
|
+
async function validateWorkspaceIdentifiers(rows, client) {
|
|
612
|
+
const issues = [];
|
|
613
|
+
const cache = {};
|
|
614
|
+
const uniqueIdentifiers = new Set();
|
|
615
|
+
for (const row of rows) {
|
|
616
|
+
if (row.workspaceIdentifier)
|
|
617
|
+
uniqueIdentifiers.add(row.workspaceIdentifier);
|
|
618
|
+
}
|
|
619
|
+
for (const wsIdentifier of uniqueIdentifiers) {
|
|
620
|
+
try {
|
|
621
|
+
const project = await resolveProjectIdFromWorkspace(client, wsIdentifier);
|
|
622
|
+
cache[wsIdentifier] = project;
|
|
623
|
+
if (!project) {
|
|
624
|
+
const affectedRows = [];
|
|
625
|
+
rows.forEach((row, index) => {
|
|
626
|
+
if (row.workspaceIdentifier === wsIdentifier) {
|
|
627
|
+
affectedRows.push({ row: index + 2, value: wsIdentifier });
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
issues.push({
|
|
631
|
+
severity: types_2.PreValidationSeverity.ERROR,
|
|
632
|
+
category: REFERENCE_NOT_FOUND_CATEGORY,
|
|
633
|
+
columnName: 'workspaceIdentifier',
|
|
634
|
+
propertySlug: 'workspaceIdentifier',
|
|
635
|
+
propertyType: 'string',
|
|
636
|
+
message: `Could not resolve workspace identifier "${wsIdentifier}" to a project. Verify the workspace exists and is a PROJECT type workspace.`,
|
|
637
|
+
affectedRows,
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
catch (e) {
|
|
642
|
+
issues.push({
|
|
643
|
+
severity: types_2.PreValidationSeverity.ERROR,
|
|
644
|
+
category: REFERENCE_NOT_FOUND_CATEGORY,
|
|
645
|
+
columnName: 'workspaceIdentifier',
|
|
646
|
+
propertySlug: 'workspaceIdentifier',
|
|
647
|
+
propertyType: 'string',
|
|
648
|
+
message: `Failed to resolve workspace identifier "${wsIdentifier}": ${e.message}`,
|
|
649
|
+
affectedRows: [],
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (issues.length === 0) {
|
|
654
|
+
for (const row of rows) {
|
|
655
|
+
if (row.workspaceIdentifier) {
|
|
656
|
+
const project = cache[row.workspaceIdentifier];
|
|
657
|
+
row.projectId = project.id;
|
|
658
|
+
row.projectName = project.name;
|
|
659
|
+
delete row.workspaceIdentifier;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return issues;
|
|
664
|
+
}
|
|
665
|
+
async function validateProjectIds(rows, client) {
|
|
666
|
+
const issues = [];
|
|
667
|
+
const nameCache = {};
|
|
668
|
+
const uniqueProjectIds = new Set();
|
|
669
|
+
for (const row of rows) {
|
|
670
|
+
if (row.projectId)
|
|
671
|
+
uniqueProjectIds.add(row.projectId);
|
|
672
|
+
}
|
|
673
|
+
if (uniqueProjectIds.size === 0)
|
|
674
|
+
return [];
|
|
675
|
+
for (const projectId of uniqueProjectIds) {
|
|
676
|
+
try {
|
|
677
|
+
const project = await (0, entities_helper_1.retryOnTimeout)(client, entities_helper_1.EntitiesLambdaCallType.GET, {
|
|
678
|
+
entityName: 'project',
|
|
679
|
+
id: projectId,
|
|
680
|
+
skipHydrateRelationships: true,
|
|
681
|
+
});
|
|
682
|
+
if (!project) {
|
|
683
|
+
const affectedRows = [];
|
|
684
|
+
rows.forEach((row, index) => {
|
|
685
|
+
if (row.projectId === projectId) {
|
|
686
|
+
affectedRows.push({ row: index + 2, value: projectId });
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
issues.push({
|
|
690
|
+
severity: types_2.PreValidationSeverity.ERROR,
|
|
691
|
+
category: REFERENCE_NOT_FOUND_CATEGORY,
|
|
692
|
+
columnName: 'projectId',
|
|
693
|
+
propertySlug: 'projectId',
|
|
694
|
+
propertyType: 'string',
|
|
695
|
+
message: `Project "${projectId}" not found. Verify the project ID is correct and the project exists.`,
|
|
696
|
+
affectedRows,
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
nameCache[projectId] = project.name || project.identifier || projectId;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
catch (e) {
|
|
704
|
+
const affectedRows = [];
|
|
705
|
+
rows.forEach((row, index) => {
|
|
706
|
+
if (row.projectId === projectId) {
|
|
707
|
+
affectedRows.push({ row: index + 2, value: projectId });
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
issues.push({
|
|
711
|
+
severity: types_2.PreValidationSeverity.ERROR,
|
|
712
|
+
category: REFERENCE_NOT_FOUND_CATEGORY,
|
|
713
|
+
columnName: 'projectId',
|
|
714
|
+
propertySlug: 'projectId',
|
|
715
|
+
propertyType: 'string',
|
|
716
|
+
message: `Failed to validate project "${projectId}": ${e.message}`,
|
|
717
|
+
affectedRows,
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
if (issues.length === 0) {
|
|
722
|
+
for (const row of rows) {
|
|
723
|
+
if (row.projectId && nameCache[row.projectId]) {
|
|
724
|
+
row.projectName = nameCache[row.projectId];
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return issues;
|
|
729
|
+
}
|
|
730
|
+
async function resolveProjectIdFromWorkspace(client, workspaceIdentifier) {
|
|
731
|
+
const workspaces = await (0, entities_helper_1.retryOnTimeout)(client, entities_helper_1.EntitiesLambdaCallType.GET, {
|
|
732
|
+
entityName: 'workspace',
|
|
733
|
+
criteria: {
|
|
734
|
+
identifier: workspaceIdentifier,
|
|
735
|
+
workspaceType: 'PROJECT',
|
|
736
|
+
},
|
|
737
|
+
skipHydrateRelationships: true,
|
|
738
|
+
});
|
|
739
|
+
if (!workspaces?.length)
|
|
740
|
+
return null;
|
|
741
|
+
const workspaceId = workspaces[0]?.id;
|
|
742
|
+
if (!workspaceId)
|
|
743
|
+
return null;
|
|
744
|
+
const workspace = await (0, entities_helper_1.retryOnTimeout)(client, entities_helper_1.EntitiesLambdaCallType.GET, {
|
|
745
|
+
entityName: 'workspace',
|
|
746
|
+
id: workspaceId,
|
|
747
|
+
relations: ['project'],
|
|
748
|
+
skipHydrateRelationships: true,
|
|
749
|
+
});
|
|
750
|
+
const project = workspace?.project;
|
|
751
|
+
if (!project?.id)
|
|
752
|
+
return null;
|
|
753
|
+
return { id: project.id, name: project.name || project.id };
|
|
754
|
+
}
|
|
755
|
+
function validateProjectItemColumns(rows, config) {
|
|
756
|
+
const isProjectItemLoad = config.loadType.some((lt) => lt === types_2.LoadType.PROJECT_ITEM);
|
|
757
|
+
if (!isProjectItemLoad)
|
|
758
|
+
return [];
|
|
759
|
+
const issues = [];
|
|
760
|
+
const hasWorkspaceIdentifier = rows.some((row) => row.workspaceIdentifier !== undefined);
|
|
761
|
+
const hasProjectId = rows.some((row) => row.projectId !== undefined);
|
|
762
|
+
if (hasWorkspaceIdentifier && hasProjectId) {
|
|
763
|
+
issues.push({
|
|
764
|
+
severity: types_2.PreValidationSeverity.ERROR,
|
|
765
|
+
category: CONFIG_CATEGORY,
|
|
766
|
+
columnName: 'workspaceIdentifier / projectId',
|
|
767
|
+
propertySlug: '',
|
|
768
|
+
propertyType: '',
|
|
769
|
+
message: 'Rows contain both "workspaceIdentifier" and "projectId" columns. Use one or the other to identify the target project.',
|
|
770
|
+
affectedRows: [],
|
|
771
|
+
});
|
|
772
|
+
return issues;
|
|
773
|
+
}
|
|
774
|
+
if (!hasWorkspaceIdentifier && !hasProjectId) {
|
|
775
|
+
issues.push({
|
|
776
|
+
severity: types_2.PreValidationSeverity.ERROR,
|
|
777
|
+
category: CONFIG_CATEGORY,
|
|
778
|
+
columnName: 'workspaceIdentifier / projectId',
|
|
779
|
+
propertySlug: '',
|
|
780
|
+
propertyType: '',
|
|
781
|
+
message: 'PROJECT_ITEM loads require a "workspaceIdentifier" or "projectId" column (or a config-level workspaceIdentifier) to identify the target project.',
|
|
782
|
+
affectedRows: [],
|
|
783
|
+
});
|
|
784
|
+
return issues;
|
|
785
|
+
}
|
|
786
|
+
const columnName = hasWorkspaceIdentifier ? 'workspaceIdentifier' : 'projectId';
|
|
787
|
+
const emptyRows = [];
|
|
788
|
+
forEachRow(rows, (row, rowNumber) => {
|
|
789
|
+
if (isEmpty(row[columnName])) {
|
|
790
|
+
emptyRows.push({ row: rowNumber, value: '' });
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
if (emptyRows.length > 0) {
|
|
794
|
+
issues.push({
|
|
795
|
+
severity: types_2.PreValidationSeverity.ERROR,
|
|
796
|
+
category: MISSING_REQUIRED_FIELD_CATEGORY,
|
|
797
|
+
columnName,
|
|
798
|
+
propertySlug: columnName,
|
|
799
|
+
propertyType: 'string',
|
|
800
|
+
message: `"${columnName}" cannot be empty for project-item rows. ${emptyRows.length} row(s) have an empty value.`,
|
|
801
|
+
affectedRows: emptyRows,
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
return issues;
|
|
805
|
+
}
|
|
657
806
|
function validateStaticIdentifierConflicts(config) {
|
|
658
807
|
const issues = [];
|
|
659
808
|
const conditionalTargets = new Set((config.conditionalColumns || []).map((col) => col.toProperty));
|
|
@@ -678,6 +827,211 @@ function validateStaticIdentifierConflicts(config) {
|
|
|
678
827
|
}
|
|
679
828
|
return issues;
|
|
680
829
|
}
|
|
830
|
+
function validateAssortmentColumns(rows, config) {
|
|
831
|
+
const isAssortmentLoad = config.loadType.some((lt) => lt === types_2.LoadType.ASSORTMENT);
|
|
832
|
+
if (!isAssortmentLoad)
|
|
833
|
+
return [];
|
|
834
|
+
const issues = [];
|
|
835
|
+
const hasAssortmentId = rows.some((row) => row.assortmentId !== undefined);
|
|
836
|
+
const hasAssortmentIdentifier = rows.some((row) => row.assortmentIdentifier !== undefined);
|
|
837
|
+
const assortmentIdFromConditions = (config.conditionalColumns || []).some((col) => (col.toProperty === 'assortmentId' || col.toProperty === 'assortmentIdentifier') && col.conditions?.length);
|
|
838
|
+
if (!hasAssortmentId && !hasAssortmentIdentifier) {
|
|
839
|
+
issues.push({
|
|
840
|
+
severity: types_2.PreValidationSeverity.ERROR,
|
|
841
|
+
category: CONFIG_CATEGORY,
|
|
842
|
+
columnName: 'assortmentId / assortmentIdentifier',
|
|
843
|
+
propertySlug: '',
|
|
844
|
+
propertyType: '',
|
|
845
|
+
message: 'ASSORTMENT loads require an "assortmentId" or "assortmentIdentifier" column (or a config-level assortmentId/assortmentIdentifier) to identify the target assortment.',
|
|
846
|
+
affectedRows: [],
|
|
847
|
+
});
|
|
848
|
+
return issues;
|
|
849
|
+
}
|
|
850
|
+
const bothRows = [];
|
|
851
|
+
forEachRow(rows, (row, rowNumber) => {
|
|
852
|
+
const hasId = !isEmpty(row.assortmentId);
|
|
853
|
+
const hasIdent = !isEmpty(row.assortmentIdentifier);
|
|
854
|
+
if (hasId && hasIdent) {
|
|
855
|
+
bothRows.push({ row: rowNumber, value: `${row.assortmentId} / ${row.assortmentIdentifier}` });
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
if (bothRows.length > 0) {
|
|
859
|
+
issues.push({
|
|
860
|
+
severity: types_2.PreValidationSeverity.ERROR,
|
|
861
|
+
category: CONFIG_CATEGORY,
|
|
862
|
+
columnName: 'assortmentId / assortmentIdentifier',
|
|
863
|
+
propertySlug: '',
|
|
864
|
+
propertyType: '',
|
|
865
|
+
message: `${bothRows.length} row(s) have both "assortmentId" and "assortmentIdentifier". Each row must use one or the other.`,
|
|
866
|
+
affectedRows: bothRows,
|
|
867
|
+
});
|
|
868
|
+
return issues;
|
|
869
|
+
}
|
|
870
|
+
const emptyIdRows = [];
|
|
871
|
+
const emptyIdentRows = [];
|
|
872
|
+
forEachRow(rows, (row, rowNumber) => {
|
|
873
|
+
if (hasAssortmentId && row.assortmentId !== undefined) {
|
|
874
|
+
if (isEmpty(row.assortmentId)) {
|
|
875
|
+
emptyIdRows.push({ row: rowNumber, value: '' });
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
else if (hasAssortmentId && row.assortmentId === undefined && !assortmentIdFromConditions) {
|
|
879
|
+
emptyIdRows.push({ row: rowNumber, value: '' });
|
|
880
|
+
}
|
|
881
|
+
if (hasAssortmentIdentifier && row.assortmentIdentifier !== undefined) {
|
|
882
|
+
if (isEmpty(row.assortmentIdentifier)) {
|
|
883
|
+
emptyIdentRows.push({ row: rowNumber, value: '' });
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
if (emptyIdRows.length > 0) {
|
|
888
|
+
issues.push({
|
|
889
|
+
severity: types_2.PreValidationSeverity.ERROR,
|
|
890
|
+
category: MISSING_REQUIRED_FIELD_CATEGORY,
|
|
891
|
+
columnName: 'assortmentId',
|
|
892
|
+
propertySlug: 'assortmentId',
|
|
893
|
+
propertyType: 'string',
|
|
894
|
+
message: `"assortmentId" cannot be empty for assortment rows. ${emptyIdRows.length} row(s) have an empty value.`,
|
|
895
|
+
affectedRows: emptyIdRows,
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
if (emptyIdentRows.length > 0) {
|
|
899
|
+
issues.push({
|
|
900
|
+
severity: types_2.PreValidationSeverity.ERROR,
|
|
901
|
+
category: MISSING_REQUIRED_FIELD_CATEGORY,
|
|
902
|
+
columnName: 'assortmentIdentifier',
|
|
903
|
+
propertySlug: 'assortmentIdentifier',
|
|
904
|
+
propertyType: 'string',
|
|
905
|
+
message: `"assortmentIdentifier" cannot be empty for assortment rows. ${emptyIdentRows.length} row(s) have an empty value.`,
|
|
906
|
+
affectedRows: emptyIdentRows,
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
return issues;
|
|
910
|
+
}
|
|
911
|
+
async function validateAndResolveAssortmentIdentifiers(rows, config, client) {
|
|
912
|
+
const isAssortmentLoad = config.loadType.some((lt) => lt === types_2.LoadType.ASSORTMENT);
|
|
913
|
+
if (!isAssortmentLoad)
|
|
914
|
+
return [];
|
|
915
|
+
const hasAssortmentIdentifier = rows.some((row) => row.assortmentIdentifier);
|
|
916
|
+
if (hasAssortmentIdentifier) {
|
|
917
|
+
return resolveAssortmentIdentifiers(rows, client);
|
|
918
|
+
}
|
|
919
|
+
return validateAssortmentIds(rows, client);
|
|
920
|
+
}
|
|
921
|
+
async function resolveAssortmentIdentifiers(rows, client) {
|
|
922
|
+
const issues = [];
|
|
923
|
+
const cache = {};
|
|
924
|
+
const uniqueIdentifiers = new Set();
|
|
925
|
+
for (const row of rows) {
|
|
926
|
+
if (row.assortmentIdentifier)
|
|
927
|
+
uniqueIdentifiers.add(row.assortmentIdentifier);
|
|
928
|
+
}
|
|
929
|
+
for (const identifier of uniqueIdentifiers) {
|
|
930
|
+
try {
|
|
931
|
+
const assortments = await (0, entities_helper_1.retryOnTimeout)(client, entities_helper_1.EntitiesLambdaCallType.GET, {
|
|
932
|
+
entityName: 'assortment',
|
|
933
|
+
criteria: { identifier },
|
|
934
|
+
skipHydrateRelationships: true,
|
|
935
|
+
});
|
|
936
|
+
if (!assortments?.length) {
|
|
937
|
+
cache[identifier] = null;
|
|
938
|
+
const affectedRows = [];
|
|
939
|
+
rows.forEach((row, index) => {
|
|
940
|
+
if (row.assortmentIdentifier === identifier) {
|
|
941
|
+
affectedRows.push({ row: index + 2, value: identifier });
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
issues.push({
|
|
945
|
+
severity: types_2.PreValidationSeverity.ERROR,
|
|
946
|
+
category: REFERENCE_NOT_FOUND_CATEGORY,
|
|
947
|
+
columnName: 'assortmentIdentifier',
|
|
948
|
+
propertySlug: 'assortmentIdentifier',
|
|
949
|
+
propertyType: 'string',
|
|
950
|
+
message: `Assortment with identifier "${identifier}" not found. Verify the identifier is correct and the assortment exists.`,
|
|
951
|
+
affectedRows,
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
else {
|
|
955
|
+
const assortment = assortments[0];
|
|
956
|
+
cache[identifier] = { id: assortment.id, name: assortment.name || assortment.id };
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
catch (e) {
|
|
960
|
+
issues.push({
|
|
961
|
+
severity: types_2.PreValidationSeverity.ERROR,
|
|
962
|
+
category: REFERENCE_NOT_FOUND_CATEGORY,
|
|
963
|
+
columnName: 'assortmentIdentifier',
|
|
964
|
+
propertySlug: 'assortmentIdentifier',
|
|
965
|
+
propertyType: 'string',
|
|
966
|
+
message: `Failed to resolve assortment identifier "${identifier}": ${e.message}`,
|
|
967
|
+
affectedRows: [],
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
if (issues.length === 0) {
|
|
972
|
+
for (const row of rows) {
|
|
973
|
+
if (row.assortmentIdentifier) {
|
|
974
|
+
const assortment = cache[row.assortmentIdentifier];
|
|
975
|
+
row.assortmentId = assortment.id;
|
|
976
|
+
row.assortmentName = assortment.name;
|
|
977
|
+
delete row.assortmentIdentifier;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
return issues;
|
|
982
|
+
}
|
|
983
|
+
async function validateAssortmentIds(rows, client) {
|
|
984
|
+
const issues = [];
|
|
985
|
+
const nameCache = {};
|
|
986
|
+
const uniqueIds = new Set();
|
|
987
|
+
for (const row of rows) {
|
|
988
|
+
if (row.assortmentId)
|
|
989
|
+
uniqueIds.add(row.assortmentId);
|
|
990
|
+
}
|
|
991
|
+
if (uniqueIds.size === 0)
|
|
992
|
+
return [];
|
|
993
|
+
for (const assortmentId of uniqueIds) {
|
|
994
|
+
try {
|
|
995
|
+
const assortment = await (0, entities_helper_1.retryOnTimeout)(client, entities_helper_1.EntitiesLambdaCallType.GET, {
|
|
996
|
+
entityName: 'assortment',
|
|
997
|
+
id: assortmentId,
|
|
998
|
+
skipHydrateRelationships: true,
|
|
999
|
+
});
|
|
1000
|
+
if (assortment) {
|
|
1001
|
+
nameCache[assortmentId] = assortment.name || assortmentId;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
catch (e) {
|
|
1005
|
+
const affectedRows = [];
|
|
1006
|
+
rows.forEach((row, index) => {
|
|
1007
|
+
if (row.assortmentId === assortmentId) {
|
|
1008
|
+
affectedRows.push({ row: index + 2, value: assortmentId });
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
const parsedDetails = typeof e?.details === 'string' ? JSON.parse(e.details).statusCode : e?.details?.statusCode;
|
|
1012
|
+
const message = parsedDetails === 404
|
|
1013
|
+
? `Assortment "${assortmentId}" not found. Verify the assortment ID is correct and the assortment exists.`
|
|
1014
|
+
: `Failed to validate assortment "${assortmentId}": ${e.message}`;
|
|
1015
|
+
issues.push({
|
|
1016
|
+
severity: types_2.PreValidationSeverity.ERROR,
|
|
1017
|
+
category: REFERENCE_NOT_FOUND_CATEGORY,
|
|
1018
|
+
columnName: 'assortmentId',
|
|
1019
|
+
propertySlug: 'assortmentId',
|
|
1020
|
+
propertyType: 'string',
|
|
1021
|
+
message,
|
|
1022
|
+
affectedRows,
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
if (issues.length === 0) {
|
|
1027
|
+
for (const row of rows) {
|
|
1028
|
+
if (row.assortmentId && nameCache[row.assortmentId]) {
|
|
1029
|
+
row.assortmentName = nameCache[row.assortmentId];
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
return issues;
|
|
1034
|
+
}
|
|
681
1035
|
function validatePartialAssortmentConfig(rows, config) {
|
|
682
1036
|
const isAssortmentLoad = config.loadType.some((lt) => lt === types_2.LoadType.ASSORTMENT);
|
|
683
1037
|
if (!isAssortmentLoad || !config.partialAssortmentUpdate)
|
|
@@ -776,107 +1130,6 @@ function buildObjectRefColumnChecks(typesByPath, isItemBased) {
|
|
|
776
1130
|
}
|
|
777
1131
|
return checks;
|
|
778
1132
|
}
|
|
779
|
-
async function validateObjectReferences(rows, typesByPath, client) {
|
|
780
|
-
const objectRefColumns = [];
|
|
781
|
-
for (const typePath of Object.keys(typesByPath ?? {})) {
|
|
782
|
-
const typeObj = typesByPath[typePath];
|
|
783
|
-
for (const prop of typeObj?.objectReferenceProperties ?? []) {
|
|
784
|
-
objectRefColumns.push({
|
|
785
|
-
slug: prop.slug,
|
|
786
|
-
rootType: prop.referencedTypeRootSlug,
|
|
787
|
-
referencedTypePath: prop.referencedTypePath,
|
|
788
|
-
});
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
if (objectRefColumns.length === 0)
|
|
792
|
-
return [];
|
|
793
|
-
const fedIdsByRootType = {};
|
|
794
|
-
const prefixFedId = (fedId, referencedTypePath) => {
|
|
795
|
-
if (referencedTypePath?.startsWith('custom-entity') && !fedId.startsWith('custom-entity:')) {
|
|
796
|
-
return `${referencedTypePath}:${fedId}`;
|
|
797
|
-
}
|
|
798
|
-
return fedId;
|
|
799
|
-
};
|
|
800
|
-
for (const row of rows) {
|
|
801
|
-
for (const ref of objectRefColumns) {
|
|
802
|
-
if (!ref.rootType)
|
|
803
|
-
continue;
|
|
804
|
-
const variants = (0, entities_helper_2.getPropertyVariants)(row, ref.slug, 'values');
|
|
805
|
-
for (const rawValue of variants) {
|
|
806
|
-
if (isEmpty(rawValue))
|
|
807
|
-
continue;
|
|
808
|
-
const fedId = prefixFedId(String(rawValue), ref.referencedTypePath);
|
|
809
|
-
if (!fedIdsByRootType[ref.rootType])
|
|
810
|
-
fedIdsByRootType[ref.rootType] = [];
|
|
811
|
-
fedIdsByRootType[ref.rootType].push(fedId);
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
const limit = pLimit(5);
|
|
816
|
-
const resolvedFedIds = new Set();
|
|
817
|
-
for (const rootType of Object.keys(fedIdsByRootType)) {
|
|
818
|
-
const uniqueFedIds = [...new Set(fedIdsByRootType[rootType])];
|
|
819
|
-
if (uniqueFedIds.length === 0)
|
|
820
|
-
continue;
|
|
821
|
-
const chunks = (0, chunk_by_url_length_1.chunkByUrlLength)(uniqueFedIds);
|
|
822
|
-
const promises = [];
|
|
823
|
-
for (const chunk of chunks) {
|
|
824
|
-
if (!chunk?.length)
|
|
825
|
-
continue;
|
|
826
|
-
promises.push(limit(async () => {
|
|
827
|
-
const batch = await (0, entities_helper_1.retryOnTimeout)(client, entities_helper_1.EntitiesLambdaCallType.GET, {
|
|
828
|
-
entityName: rootType,
|
|
829
|
-
federatedIds: (0, lodash_1.uniq)(chunk),
|
|
830
|
-
skipHydrateRelationships: true,
|
|
831
|
-
});
|
|
832
|
-
if (Array.isArray(batch)) {
|
|
833
|
-
for (const entity of batch) {
|
|
834
|
-
if (entity?.federatedId)
|
|
835
|
-
resolvedFedIds.add(entity.federatedId);
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
}));
|
|
839
|
-
}
|
|
840
|
-
await Promise.all(promises);
|
|
841
|
-
}
|
|
842
|
-
const missingByColumnAndValue = {};
|
|
843
|
-
forEachRow(rows, (row, rowNumber) => {
|
|
844
|
-
for (const ref of objectRefColumns) {
|
|
845
|
-
if (!ref.rootType)
|
|
846
|
-
continue;
|
|
847
|
-
const variants = (0, entities_helper_2.getPropertyVariants)(row, ref.slug, 'keyValue');
|
|
848
|
-
for (const variant of variants) {
|
|
849
|
-
if (isEmpty(variant.value))
|
|
850
|
-
continue;
|
|
851
|
-
const rawValue = String(variant.value);
|
|
852
|
-
const lookupKey = prefixFedId(rawValue, ref.referencedTypePath);
|
|
853
|
-
if (resolvedFedIds.has(lookupKey))
|
|
854
|
-
continue;
|
|
855
|
-
if (!missingByColumnAndValue[variant.key])
|
|
856
|
-
missingByColumnAndValue[variant.key] = new Map();
|
|
857
|
-
const byValue = missingByColumnAndValue[variant.key];
|
|
858
|
-
if (!byValue.has(rawValue))
|
|
859
|
-
byValue.set(rawValue, []);
|
|
860
|
-
byValue.get(rawValue).push({ row: rowNumber, value: rawValue });
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
});
|
|
864
|
-
const issues = [];
|
|
865
|
-
for (const [columnName, byValue] of Object.entries(missingByColumnAndValue)) {
|
|
866
|
-
for (const [missingValue, affectedRows] of byValue) {
|
|
867
|
-
issues.push({
|
|
868
|
-
severity: types_2.PreValidationSeverity.WARNING,
|
|
869
|
-
category: REFERENCE_NOT_FOUND_CATEGORY,
|
|
870
|
-
columnName,
|
|
871
|
-
propertySlug: columnName,
|
|
872
|
-
propertyType: 'object_reference',
|
|
873
|
-
message: `Value "${missingValue}" does not match any existing entity for object reference property "${columnName}".`,
|
|
874
|
-
affectedRows,
|
|
875
|
-
});
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
return issues;
|
|
879
|
-
}
|
|
880
1133
|
const NUMERIC_PROPERTY_TYPES = new Set(['number', 'currency', 'percent', 'sequence']);
|
|
881
1134
|
function validateNumberPrecision(rows, typePropertyMaps) {
|
|
882
1135
|
const { typePropertiesBySlug } = typePropertyMaps;
|