@contrail/cli 3.5.0 → 3.5.1-alpha.63493a5
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 +3 -3
- package/dist/api/types.d.ts +0 -5
- package/dist/api/types.js +1 -36
- package/dist/commands/types/getAll.js +0 -18
- package/dist/commands/types/loadAll.js +0 -2
- package/dist/commands/types/mergeTypeProperties.js +0 -5
- package/dist/commands/types/uploadOptionSetHierarchies.js +0 -117
- package/dist/commands/types/uploadOptionSets.js +1 -1
- package/dist/commands/types/uploadRuleSets.js +2 -85
- package/dist/commands/types/uploadTypePolicies.js +2 -86
- package/dist/commands/types/uploadTypePropertyPolicies.d.ts +0 -1
- package/dist/commands/types/uploadTypePropertyPolicies.js +3 -139
- package/dist/commands/types/uploadUsageInfo.d.ts +3 -0
- package/dist/commands/types/uploadUsageInfo.js +4 -1
- package/dist/helpers/types/type-uploader.js +1 -1
- package/dist/templates/typescript/tsup.config.ts +0 -0
- package/node_modules/@contrail/loader/lib/color-processor.js +9 -23
- package/node_modules/@contrail/loader/lib/color-processor.spec.js +112 -62
- 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 +2 -0
- package/node_modules/@contrail/loader/lib/common/chunk-by-url-length.spec.js +4 -0
- package/node_modules/@contrail/loader/lib/common/color-validation.d.ts +26 -0
- package/node_modules/@contrail/loader/lib/common/color-validation.js +163 -0
- package/node_modules/@contrail/loader/lib/common/color-validation.spec.d.ts +1 -0
- package/node_modules/@contrail/loader/lib/common/color-validation.spec.js +102 -0
- package/node_modules/@contrail/loader/lib/common/size-range-helpers.js +0 -1
- package/node_modules/@contrail/loader/lib/constants.d.ts +0 -3
- package/node_modules/@contrail/loader/lib/constants.js +1 -4
- package/node_modules/@contrail/loader/lib/entities-v2/colors-v2/color-processor-v2.js +9 -23
- package/node_modules/@contrail/loader/lib/entities-v2/colors-v2/color-processor-v2.spec.js +112 -62
- package/node_modules/@contrail/loader/lib/entities-v2/entities-helper.js +0 -1
- package/node_modules/@contrail/loader/lib/entities-v2/linesheet-v2/assortment-processor.js +0 -1
- package/node_modules/@contrail/loader/lib/entities-v2/linesheet-v2/item-processor.js +8 -22
- package/node_modules/@contrail/loader/lib/entities-v2/linesheet-v2/item-processor.spec.js +43 -1
- package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.d.ts +5 -0
- package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.js +562 -0
- package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.spec.d.ts +1 -0
- package/node_modules/@contrail/loader/lib/pipeline/identifier-resolver.spec.js +231 -0
- package/node_modules/@contrail/loader/lib/pipeline/item-execute-helpers.js +77 -3
- package/node_modules/@contrail/loader/lib/pipeline/item-execute-helpers.spec.js +82 -0
- package/node_modules/@contrail/loader/lib/pipeline/preview.js +0 -5
- 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 +18 -0
- package/node_modules/@contrail/loader/lib/pipeline/transform.spec.js +3 -2
- package/node_modules/@contrail/loader/lib/pipeline/types.d.ts +5 -2
- package/node_modules/@contrail/loader/lib/pipeline/types.js +2 -0
- package/node_modules/@contrail/loader/lib/pipeline/validate.js +184 -437
- package/node_modules/@contrail/loader/lib/pipeline/validate.spec.js +157 -53
- package/node_modules/@contrail/loader/package.json +3 -2
- package/node_modules/@contrail/sdk/.github/workflows/publish-to-npm.yml +120 -0
- package/node_modules/@contrail/sdk/lib/auth/login.js +42 -3
- package/node_modules/@contrail/sdk/lib/core/request.d.ts +3 -0
- package/node_modules/@contrail/sdk/lib/core/request.js +100 -4
- package/node_modules/@contrail/sdk/lib/entities/entities.client.js +1 -1
- package/node_modules/@contrail/sdk/lib/entities/entity.resource.registry.js +1 -0
- package/node_modules/@contrail/sdk/lib/files/files.d.ts +13 -3
- package/node_modules/@contrail/sdk/lib/files/files.js +66 -25
- package/node_modules/@contrail/sdk/package.json +4 -1
- package/node_modules/validate-color/LICENSE +21 -0
- package/node_modules/validate-color/README.md +265 -0
- package/node_modules/validate-color/lib/index.d.ts +10 -0
- package/node_modules/validate-color/lib/index.js +1 -0
- package/node_modules/validate-color/package.json +91 -0
- package/node_modules/validate-color/test/redos.js +29 -0
- package/oclif.manifest.json +5 -2
- package/package.json +3 -3
|
@@ -28,18 +28,21 @@ describe('Item Processor', () => {
|
|
|
28
28
|
family_federated_id: 'family_1',
|
|
29
29
|
input_col_1: 'value_1',
|
|
30
30
|
input_col_2: 'value_2',
|
|
31
|
+
typePath: 'item',
|
|
31
32
|
},
|
|
32
33
|
{
|
|
33
34
|
name: 'item2',
|
|
34
35
|
family_federated_id: 'family_2',
|
|
35
36
|
input_col_1: 'value_3',
|
|
36
37
|
input_col_2: 'value_4',
|
|
38
|
+
typePath: 'item',
|
|
37
39
|
},
|
|
38
40
|
{
|
|
39
41
|
name: 'item3',
|
|
40
42
|
family_federated_id: 'family_3',
|
|
41
43
|
input_col_1: 'value_5',
|
|
42
44
|
input_col_2: 'value_6',
|
|
45
|
+
typePath: 'item',
|
|
43
46
|
},
|
|
44
47
|
];
|
|
45
48
|
const config = {
|
|
@@ -167,7 +170,7 @@ describe('Item Processor', () => {
|
|
|
167
170
|
},
|
|
168
171
|
]);
|
|
169
172
|
});
|
|
170
|
-
it('should
|
|
173
|
+
it('should throw error if typepath is not present in csv header', () => {
|
|
171
174
|
const data = [
|
|
172
175
|
{
|
|
173
176
|
name: 'item1',
|
|
@@ -193,6 +196,42 @@ describe('Item Processor', () => {
|
|
|
193
196
|
conditionalColumns: [],
|
|
194
197
|
};
|
|
195
198
|
const result = item_processor_1.ItemProcessor.validateLoadFile(data, config);
|
|
199
|
+
expect(result.generalErrors).toEqual([
|
|
200
|
+
{
|
|
201
|
+
message: constants_1.ITEM_PROCESSOR_ERRORS.TYPEPATH_INVALID,
|
|
202
|
+
issueType: entity_types_1.IssueType.CONFIG,
|
|
203
|
+
},
|
|
204
|
+
]);
|
|
205
|
+
});
|
|
206
|
+
it('should not throw error if every row has a valid typePath', () => {
|
|
207
|
+
const data = [
|
|
208
|
+
{
|
|
209
|
+
name: 'item1',
|
|
210
|
+
family_federated_id: 'family_1',
|
|
211
|
+
input_col_1: 'value_1',
|
|
212
|
+
input_col_2: 'value_2',
|
|
213
|
+
typePath: 'item',
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: 'item2',
|
|
217
|
+
family_federated_id: 'family_2',
|
|
218
|
+
input_col_1: 'value_3',
|
|
219
|
+
input_col_2: 'value_4',
|
|
220
|
+
typePath: 'item:material:fabric:yarn',
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: 'item3',
|
|
224
|
+
family_federated_id: 'family_3',
|
|
225
|
+
input_col_1: 'value_5',
|
|
226
|
+
input_col_2: 'value_6',
|
|
227
|
+
typePath: 'item:material:fabric:leather',
|
|
228
|
+
},
|
|
229
|
+
];
|
|
230
|
+
const config = {
|
|
231
|
+
...loaderProcess,
|
|
232
|
+
conditionalColumns: [],
|
|
233
|
+
};
|
|
234
|
+
const result = item_processor_1.ItemProcessor.validateLoadFile(data, config);
|
|
196
235
|
expect(result.generalErrors).toEqual([]);
|
|
197
236
|
});
|
|
198
237
|
});
|
|
@@ -600,14 +639,17 @@ describe('Item Processor', () => {
|
|
|
600
639
|
{
|
|
601
640
|
itemFamilyFederatedId: '1',
|
|
602
641
|
name: 'foo',
|
|
642
|
+
typePath: 'item',
|
|
603
643
|
},
|
|
604
644
|
{
|
|
605
645
|
itemFamilyFederatedId: '1',
|
|
606
646
|
name: 'bar',
|
|
647
|
+
typePath: 'item',
|
|
607
648
|
},
|
|
608
649
|
{
|
|
609
650
|
itemFamilyFederatedId: '3',
|
|
610
651
|
name: 'test',
|
|
652
|
+
typePath: 'item',
|
|
611
653
|
},
|
|
612
654
|
];
|
|
613
655
|
const config = {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { EntitiesClient, LoaderConfig, PreValidationIssue } from './types';
|
|
2
|
+
export declare function isProjectItemAndAssortmentLoad(config: LoaderConfig): boolean;
|
|
3
|
+
export declare function resolveIdentifiers(rows: Record<string, any>[], config: LoaderConfig, client?: EntitiesClient): Promise<{
|
|
4
|
+
issues: PreValidationIssue[];
|
|
5
|
+
}>;
|
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveIdentifiers = exports.isProjectItemAndAssortmentLoad = void 0;
|
|
4
|
+
const entities_helper_1 = require("../entities-v2/entities-helper");
|
|
5
|
+
const types_1 = require("./types");
|
|
6
|
+
const CONFIG_CATEGORY = types_1.PreValidationCategory.CONFIG;
|
|
7
|
+
const MISSING_REQUIRED_FIELD_CATEGORY = types_1.PreValidationCategory.MISSING_REQUIRED_FIELD;
|
|
8
|
+
const REFERENCE_NOT_FOUND_CATEGORY = types_1.PreValidationCategory.REFERENCE_DATA_NOT_FOUND;
|
|
9
|
+
function isEmpty(value) {
|
|
10
|
+
return value === null || value === undefined || value === '';
|
|
11
|
+
}
|
|
12
|
+
function forEachRow(rows, callback) {
|
|
13
|
+
rows.forEach((row, index) => callback(row, index + 2));
|
|
14
|
+
}
|
|
15
|
+
function isProjectItemAndAssortmentLoad(config) {
|
|
16
|
+
const types = new Set(config.loadType);
|
|
17
|
+
return types.has(types_1.LoadType.PROJECT_ITEM) && types.has(types_1.LoadType.ASSORTMENT);
|
|
18
|
+
}
|
|
19
|
+
exports.isProjectItemAndAssortmentLoad = isProjectItemAndAssortmentLoad;
|
|
20
|
+
async function resolveIdentifiers(rows, config, client) {
|
|
21
|
+
const issues = [];
|
|
22
|
+
if (!rows?.length)
|
|
23
|
+
return { issues };
|
|
24
|
+
const hasProjectSource = rows.some((row) => row.workspaceIdentifier !== undefined || row.projectId !== undefined);
|
|
25
|
+
const useComboMode = isProjectItemAndAssortmentLoad(config) && !hasProjectSource;
|
|
26
|
+
const projectItemColumnIssues = useComboMode ? [] : validateProjectItemColumns(rows, config);
|
|
27
|
+
issues.push(...projectItemColumnIssues);
|
|
28
|
+
if (!useComboMode && client && projectItemColumnIssues.length === 0) {
|
|
29
|
+
issues.push(...(await validateAndResolveProjectIdentifiers(rows, config, client)));
|
|
30
|
+
}
|
|
31
|
+
const assortmentColumnIssues = validateAssortmentColumns(rows, config);
|
|
32
|
+
issues.push(...assortmentColumnIssues);
|
|
33
|
+
if (client && assortmentColumnIssues.length === 0) {
|
|
34
|
+
issues.push(...(await validateAndResolveAssortmentIdentifiers(rows, config, client)));
|
|
35
|
+
}
|
|
36
|
+
if (useComboMode && client && assortmentColumnIssues.length === 0) {
|
|
37
|
+
issues.push(...(await stampProjectIdsFromAssortmentWorkspaces(rows, client)));
|
|
38
|
+
}
|
|
39
|
+
return { issues };
|
|
40
|
+
}
|
|
41
|
+
exports.resolveIdentifiers = resolveIdentifiers;
|
|
42
|
+
async function validateAndResolveProjectIdentifiers(rows, config, client) {
|
|
43
|
+
const isProjectItemLoad = config.loadType.some((lt) => lt === types_1.LoadType.PROJECT_ITEM);
|
|
44
|
+
if (!isProjectItemLoad)
|
|
45
|
+
return [];
|
|
46
|
+
const hasWorkspaceIdentifier = rows.some((row) => row.workspaceIdentifier);
|
|
47
|
+
if (hasWorkspaceIdentifier) {
|
|
48
|
+
return validateWorkspaceIdentifiers(rows, client);
|
|
49
|
+
}
|
|
50
|
+
return validateProjectIds(rows, client);
|
|
51
|
+
}
|
|
52
|
+
async function validateWorkspaceIdentifiers(rows, client) {
|
|
53
|
+
const issues = [];
|
|
54
|
+
const cache = {};
|
|
55
|
+
const uniqueIdentifiers = new Set();
|
|
56
|
+
for (const row of rows) {
|
|
57
|
+
if (row.workspaceIdentifier)
|
|
58
|
+
uniqueIdentifiers.add(row.workspaceIdentifier);
|
|
59
|
+
}
|
|
60
|
+
for (const wsIdentifier of uniqueIdentifiers) {
|
|
61
|
+
try {
|
|
62
|
+
const project = await resolveProjectIdFromWorkspace(client, wsIdentifier);
|
|
63
|
+
cache[wsIdentifier] = project;
|
|
64
|
+
if (!project) {
|
|
65
|
+
const affectedRows = [];
|
|
66
|
+
rows.forEach((row, index) => {
|
|
67
|
+
if (row.workspaceIdentifier === wsIdentifier) {
|
|
68
|
+
affectedRows.push({ row: index + 2, value: wsIdentifier });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
issues.push({
|
|
72
|
+
severity: types_1.PreValidationSeverity.ERROR,
|
|
73
|
+
category: REFERENCE_NOT_FOUND_CATEGORY,
|
|
74
|
+
columnName: 'workspaceIdentifier',
|
|
75
|
+
propertySlug: 'workspaceIdentifier',
|
|
76
|
+
propertyType: 'string',
|
|
77
|
+
message: `Could not resolve workspace identifier "${wsIdentifier}" to a project. Verify the workspace exists and is a PROJECT type workspace.`,
|
|
78
|
+
affectedRows,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
issues.push({
|
|
84
|
+
severity: types_1.PreValidationSeverity.ERROR,
|
|
85
|
+
category: REFERENCE_NOT_FOUND_CATEGORY,
|
|
86
|
+
columnName: 'workspaceIdentifier',
|
|
87
|
+
propertySlug: 'workspaceIdentifier',
|
|
88
|
+
propertyType: 'string',
|
|
89
|
+
message: `Failed to resolve workspace identifier "${wsIdentifier}": ${e.message}`,
|
|
90
|
+
affectedRows: [],
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (issues.length === 0) {
|
|
95
|
+
for (const row of rows) {
|
|
96
|
+
if (row.workspaceIdentifier) {
|
|
97
|
+
const project = cache[row.workspaceIdentifier];
|
|
98
|
+
row.projectId = project.id;
|
|
99
|
+
row.projectName = project.name;
|
|
100
|
+
delete row.workspaceIdentifier;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return issues;
|
|
105
|
+
}
|
|
106
|
+
async function validateProjectIds(rows, client) {
|
|
107
|
+
const issues = [];
|
|
108
|
+
const nameCache = {};
|
|
109
|
+
const uniqueProjectIds = new Set();
|
|
110
|
+
for (const row of rows) {
|
|
111
|
+
if (row.projectId)
|
|
112
|
+
uniqueProjectIds.add(row.projectId);
|
|
113
|
+
}
|
|
114
|
+
if (uniqueProjectIds.size === 0)
|
|
115
|
+
return [];
|
|
116
|
+
for (const projectId of uniqueProjectIds) {
|
|
117
|
+
try {
|
|
118
|
+
const project = await (0, entities_helper_1.retryOnTimeout)(client, entities_helper_1.EntitiesLambdaCallType.GET, {
|
|
119
|
+
entityName: 'project',
|
|
120
|
+
id: projectId,
|
|
121
|
+
skipHydrateRelationships: true,
|
|
122
|
+
});
|
|
123
|
+
if (!project) {
|
|
124
|
+
const affectedRows = [];
|
|
125
|
+
rows.forEach((row, index) => {
|
|
126
|
+
if (row.projectId === projectId) {
|
|
127
|
+
affectedRows.push({ row: index + 2, value: projectId });
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
issues.push({
|
|
131
|
+
severity: types_1.PreValidationSeverity.ERROR,
|
|
132
|
+
category: REFERENCE_NOT_FOUND_CATEGORY,
|
|
133
|
+
columnName: 'projectId',
|
|
134
|
+
propertySlug: 'projectId',
|
|
135
|
+
propertyType: 'string',
|
|
136
|
+
message: `Project "${projectId}" not found. Verify the project ID is correct and the project exists.`,
|
|
137
|
+
affectedRows,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
nameCache[projectId] = project.name || project.identifier || projectId;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (e) {
|
|
145
|
+
const affectedRows = [];
|
|
146
|
+
rows.forEach((row, index) => {
|
|
147
|
+
if (row.projectId === projectId) {
|
|
148
|
+
affectedRows.push({ row: index + 2, value: projectId });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
issues.push({
|
|
152
|
+
severity: types_1.PreValidationSeverity.ERROR,
|
|
153
|
+
category: REFERENCE_NOT_FOUND_CATEGORY,
|
|
154
|
+
columnName: 'projectId',
|
|
155
|
+
propertySlug: 'projectId',
|
|
156
|
+
propertyType: 'string',
|
|
157
|
+
message: `Failed to validate project "${projectId}": ${e.message}`,
|
|
158
|
+
affectedRows,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (issues.length === 0) {
|
|
163
|
+
for (const row of rows) {
|
|
164
|
+
if (row.projectId && nameCache[row.projectId]) {
|
|
165
|
+
row.projectName = nameCache[row.projectId];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return issues;
|
|
170
|
+
}
|
|
171
|
+
async function resolveProjectIdFromWorkspace(client, workspaceIdentifier) {
|
|
172
|
+
const workspaces = await (0, entities_helper_1.retryOnTimeout)(client, entities_helper_1.EntitiesLambdaCallType.GET, {
|
|
173
|
+
entityName: 'workspace',
|
|
174
|
+
criteria: {
|
|
175
|
+
identifier: workspaceIdentifier,
|
|
176
|
+
workspaceType: 'PROJECT',
|
|
177
|
+
},
|
|
178
|
+
skipHydrateRelationships: true,
|
|
179
|
+
});
|
|
180
|
+
if (!workspaces?.length)
|
|
181
|
+
return null;
|
|
182
|
+
const workspaceId = workspaces[0]?.id;
|
|
183
|
+
if (!workspaceId)
|
|
184
|
+
return null;
|
|
185
|
+
const workspace = await (0, entities_helper_1.retryOnTimeout)(client, entities_helper_1.EntitiesLambdaCallType.GET, {
|
|
186
|
+
entityName: 'workspace',
|
|
187
|
+
id: workspaceId,
|
|
188
|
+
relations: ['project'],
|
|
189
|
+
skipHydrateRelationships: true,
|
|
190
|
+
});
|
|
191
|
+
const project = workspace?.project;
|
|
192
|
+
if (!project?.id)
|
|
193
|
+
return null;
|
|
194
|
+
return { id: project.id, name: project.name || project.id };
|
|
195
|
+
}
|
|
196
|
+
function validateProjectItemColumns(rows, config) {
|
|
197
|
+
const isProjectItemLoad = config.loadType.some((lt) => lt === types_1.LoadType.PROJECT_ITEM);
|
|
198
|
+
if (!isProjectItemLoad)
|
|
199
|
+
return [];
|
|
200
|
+
const issues = [];
|
|
201
|
+
const hasWorkspaceIdentifier = rows.some((row) => row.workspaceIdentifier !== undefined);
|
|
202
|
+
const hasProjectId = rows.some((row) => row.projectId !== undefined);
|
|
203
|
+
if (hasWorkspaceIdentifier && hasProjectId) {
|
|
204
|
+
issues.push({
|
|
205
|
+
severity: types_1.PreValidationSeverity.ERROR,
|
|
206
|
+
category: CONFIG_CATEGORY,
|
|
207
|
+
columnName: 'workspaceIdentifier / projectId',
|
|
208
|
+
propertySlug: '',
|
|
209
|
+
propertyType: '',
|
|
210
|
+
message: 'Rows contain both "workspaceIdentifier" and "projectId" columns. Use one or the other to identify the target project.',
|
|
211
|
+
affectedRows: [],
|
|
212
|
+
});
|
|
213
|
+
return issues;
|
|
214
|
+
}
|
|
215
|
+
if (!hasWorkspaceIdentifier && !hasProjectId) {
|
|
216
|
+
issues.push({
|
|
217
|
+
severity: types_1.PreValidationSeverity.ERROR,
|
|
218
|
+
category: CONFIG_CATEGORY,
|
|
219
|
+
columnName: 'workspaceIdentifier / projectId',
|
|
220
|
+
propertySlug: '',
|
|
221
|
+
propertyType: '',
|
|
222
|
+
message: 'PROJECT_ITEM loads require a "workspaceIdentifier" or "projectId" column (or a config-level workspaceIdentifier) to identify the target project.',
|
|
223
|
+
affectedRows: [],
|
|
224
|
+
});
|
|
225
|
+
return issues;
|
|
226
|
+
}
|
|
227
|
+
const columnName = hasWorkspaceIdentifier ? 'workspaceIdentifier' : 'projectId';
|
|
228
|
+
const emptyRows = [];
|
|
229
|
+
forEachRow(rows, (row, rowNumber) => {
|
|
230
|
+
if (isEmpty(row[columnName])) {
|
|
231
|
+
emptyRows.push({ row: rowNumber, value: '' });
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
if (emptyRows.length > 0) {
|
|
235
|
+
issues.push({
|
|
236
|
+
severity: types_1.PreValidationSeverity.ERROR,
|
|
237
|
+
category: MISSING_REQUIRED_FIELD_CATEGORY,
|
|
238
|
+
columnName,
|
|
239
|
+
propertySlug: columnName,
|
|
240
|
+
propertyType: 'string',
|
|
241
|
+
message: `"${columnName}" cannot be empty for project-item rows. ${emptyRows.length} row(s) have an empty value.`,
|
|
242
|
+
affectedRows: emptyRows,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
return issues;
|
|
246
|
+
}
|
|
247
|
+
function validateAssortmentColumns(rows, config) {
|
|
248
|
+
const isAssortmentLoad = config.loadType.some((lt) => lt === types_1.LoadType.ASSORTMENT);
|
|
249
|
+
if (!isAssortmentLoad)
|
|
250
|
+
return [];
|
|
251
|
+
const issues = [];
|
|
252
|
+
const hasAssortmentId = rows.some((row) => row.assortmentId !== undefined);
|
|
253
|
+
const hasAssortmentIdentifier = rows.some((row) => row.assortmentIdentifier !== undefined);
|
|
254
|
+
const assortmentIdFromConditions = (config.conditionalColumns || []).some((col) => (col.toProperty === 'assortmentId' || col.toProperty === 'assortmentIdentifier') && col.conditions?.length);
|
|
255
|
+
if (!hasAssortmentId && !hasAssortmentIdentifier) {
|
|
256
|
+
issues.push({
|
|
257
|
+
severity: types_1.PreValidationSeverity.ERROR,
|
|
258
|
+
category: CONFIG_CATEGORY,
|
|
259
|
+
columnName: 'assortmentId / assortmentIdentifier',
|
|
260
|
+
propertySlug: '',
|
|
261
|
+
propertyType: '',
|
|
262
|
+
message: 'ASSORTMENT loads require an "assortmentId" or "assortmentIdentifier" column (or a config-level assortmentId/assortmentIdentifier) to identify the target assortment.',
|
|
263
|
+
affectedRows: [],
|
|
264
|
+
});
|
|
265
|
+
return issues;
|
|
266
|
+
}
|
|
267
|
+
const bothRows = [];
|
|
268
|
+
forEachRow(rows, (row, rowNumber) => {
|
|
269
|
+
const hasId = !isEmpty(row.assortmentId);
|
|
270
|
+
const hasIdent = !isEmpty(row.assortmentIdentifier);
|
|
271
|
+
if (hasId && hasIdent) {
|
|
272
|
+
bothRows.push({ row: rowNumber, value: `${row.assortmentId} / ${row.assortmentIdentifier}` });
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
if (bothRows.length > 0) {
|
|
276
|
+
issues.push({
|
|
277
|
+
severity: types_1.PreValidationSeverity.ERROR,
|
|
278
|
+
category: CONFIG_CATEGORY,
|
|
279
|
+
columnName: 'assortmentId / assortmentIdentifier',
|
|
280
|
+
propertySlug: '',
|
|
281
|
+
propertyType: '',
|
|
282
|
+
message: `${bothRows.length} row(s) have both "assortmentId" and "assortmentIdentifier". Each row must use one or the other.`,
|
|
283
|
+
affectedRows: bothRows,
|
|
284
|
+
});
|
|
285
|
+
return issues;
|
|
286
|
+
}
|
|
287
|
+
const emptyIdRows = [];
|
|
288
|
+
const emptyIdentRows = [];
|
|
289
|
+
forEachRow(rows, (row, rowNumber) => {
|
|
290
|
+
if (hasAssortmentId && row.assortmentId !== undefined) {
|
|
291
|
+
if (isEmpty(row.assortmentId)) {
|
|
292
|
+
emptyIdRows.push({ row: rowNumber, value: '' });
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
else if (hasAssortmentId && row.assortmentId === undefined && !assortmentIdFromConditions) {
|
|
296
|
+
emptyIdRows.push({ row: rowNumber, value: '' });
|
|
297
|
+
}
|
|
298
|
+
if (hasAssortmentIdentifier && row.assortmentIdentifier !== undefined) {
|
|
299
|
+
if (isEmpty(row.assortmentIdentifier)) {
|
|
300
|
+
emptyIdentRows.push({ row: rowNumber, value: '' });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
if (emptyIdRows.length > 0) {
|
|
305
|
+
issues.push({
|
|
306
|
+
severity: types_1.PreValidationSeverity.ERROR,
|
|
307
|
+
category: MISSING_REQUIRED_FIELD_CATEGORY,
|
|
308
|
+
columnName: 'assortmentId',
|
|
309
|
+
propertySlug: 'assortmentId',
|
|
310
|
+
propertyType: 'string',
|
|
311
|
+
message: `"assortmentId" cannot be empty for assortment rows. ${emptyIdRows.length} row(s) have an empty value.`,
|
|
312
|
+
affectedRows: emptyIdRows,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
if (emptyIdentRows.length > 0) {
|
|
316
|
+
issues.push({
|
|
317
|
+
severity: types_1.PreValidationSeverity.ERROR,
|
|
318
|
+
category: MISSING_REQUIRED_FIELD_CATEGORY,
|
|
319
|
+
columnName: 'assortmentIdentifier',
|
|
320
|
+
propertySlug: 'assortmentIdentifier',
|
|
321
|
+
propertyType: 'string',
|
|
322
|
+
message: `"assortmentIdentifier" cannot be empty for assortment rows. ${emptyIdentRows.length} row(s) have an empty value.`,
|
|
323
|
+
affectedRows: emptyIdentRows,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
return issues;
|
|
327
|
+
}
|
|
328
|
+
async function validateAndResolveAssortmentIdentifiers(rows, config, client) {
|
|
329
|
+
const isAssortmentLoad = config.loadType.some((lt) => lt === types_1.LoadType.ASSORTMENT);
|
|
330
|
+
if (!isAssortmentLoad)
|
|
331
|
+
return [];
|
|
332
|
+
const hasAssortmentIdentifier = rows.some((row) => row.assortmentIdentifier);
|
|
333
|
+
if (hasAssortmentIdentifier) {
|
|
334
|
+
return resolveAssortmentIdentifiers(rows, client);
|
|
335
|
+
}
|
|
336
|
+
return validateAssortmentIds(rows, client);
|
|
337
|
+
}
|
|
338
|
+
async function resolveAssortmentIdentifiers(rows, client) {
|
|
339
|
+
const issues = [];
|
|
340
|
+
const cache = {};
|
|
341
|
+
const uniqueIdentifiers = new Set();
|
|
342
|
+
for (const row of rows) {
|
|
343
|
+
if (row.assortmentIdentifier)
|
|
344
|
+
uniqueIdentifiers.add(row.assortmentIdentifier);
|
|
345
|
+
}
|
|
346
|
+
for (const identifier of uniqueIdentifiers) {
|
|
347
|
+
try {
|
|
348
|
+
const assortments = await (0, entities_helper_1.retryOnTimeout)(client, entities_helper_1.EntitiesLambdaCallType.GET, {
|
|
349
|
+
entityName: 'assortment',
|
|
350
|
+
criteria: { identifier },
|
|
351
|
+
skipHydrateRelationships: true,
|
|
352
|
+
});
|
|
353
|
+
if (!assortments?.length) {
|
|
354
|
+
cache[identifier] = null;
|
|
355
|
+
const affectedRows = [];
|
|
356
|
+
rows.forEach((row, index) => {
|
|
357
|
+
if (row.assortmentIdentifier === identifier) {
|
|
358
|
+
affectedRows.push({ row: index + 2, value: identifier });
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
issues.push({
|
|
362
|
+
severity: types_1.PreValidationSeverity.ERROR,
|
|
363
|
+
category: REFERENCE_NOT_FOUND_CATEGORY,
|
|
364
|
+
columnName: 'assortmentIdentifier',
|
|
365
|
+
propertySlug: 'assortmentIdentifier',
|
|
366
|
+
propertyType: 'string',
|
|
367
|
+
message: `Assortment with identifier "${identifier}" not found. Verify the identifier is correct and the assortment exists.`,
|
|
368
|
+
affectedRows,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
const assortment = assortments[0];
|
|
373
|
+
cache[identifier] = {
|
|
374
|
+
id: assortment.id,
|
|
375
|
+
name: assortment.name || assortment.id,
|
|
376
|
+
workspaceId: assortment.workspaceId,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
catch (e) {
|
|
381
|
+
issues.push({
|
|
382
|
+
severity: types_1.PreValidationSeverity.ERROR,
|
|
383
|
+
category: REFERENCE_NOT_FOUND_CATEGORY,
|
|
384
|
+
columnName: 'assortmentIdentifier',
|
|
385
|
+
propertySlug: 'assortmentIdentifier',
|
|
386
|
+
propertyType: 'string',
|
|
387
|
+
message: `Failed to resolve assortment identifier "${identifier}": ${e.message}`,
|
|
388
|
+
affectedRows: [],
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (issues.length === 0) {
|
|
393
|
+
for (const row of rows) {
|
|
394
|
+
if (row.assortmentIdentifier) {
|
|
395
|
+
const assortment = cache[row.assortmentIdentifier];
|
|
396
|
+
row.assortmentId = assortment.id;
|
|
397
|
+
row.assortmentName = assortment.name;
|
|
398
|
+
if (assortment.workspaceId) {
|
|
399
|
+
row.assortmentWorkspaceId = assortment.workspaceId;
|
|
400
|
+
}
|
|
401
|
+
delete row.assortmentIdentifier;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return issues;
|
|
406
|
+
}
|
|
407
|
+
async function validateAssortmentIds(rows, client) {
|
|
408
|
+
const issues = [];
|
|
409
|
+
const nameCache = {};
|
|
410
|
+
const workspaceCache = {};
|
|
411
|
+
const uniqueIds = new Set();
|
|
412
|
+
for (const row of rows) {
|
|
413
|
+
if (row.assortmentId)
|
|
414
|
+
uniqueIds.add(row.assortmentId);
|
|
415
|
+
}
|
|
416
|
+
if (uniqueIds.size === 0)
|
|
417
|
+
return [];
|
|
418
|
+
for (const assortmentId of uniqueIds) {
|
|
419
|
+
try {
|
|
420
|
+
const assortment = await (0, entities_helper_1.retryOnTimeout)(client, entities_helper_1.EntitiesLambdaCallType.GET, {
|
|
421
|
+
entityName: 'assortment',
|
|
422
|
+
id: assortmentId,
|
|
423
|
+
skipHydrateRelationships: true,
|
|
424
|
+
});
|
|
425
|
+
if (assortment) {
|
|
426
|
+
nameCache[assortmentId] = assortment.name || assortmentId;
|
|
427
|
+
workspaceCache[assortmentId] = assortment.workspaceId;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
catch (e) {
|
|
431
|
+
const affectedRows = [];
|
|
432
|
+
rows.forEach((row, index) => {
|
|
433
|
+
if (row.assortmentId === assortmentId) {
|
|
434
|
+
affectedRows.push({ row: index + 2, value: assortmentId });
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
const parsedDetails = typeof e?.details === 'string' ? JSON.parse(e.details).statusCode : e?.details?.statusCode;
|
|
438
|
+
const message = parsedDetails === 404
|
|
439
|
+
? `Assortment "${assortmentId}" not found. Verify the assortment ID is correct and the assortment exists.`
|
|
440
|
+
: `Failed to validate assortment "${assortmentId}": ${e.message}`;
|
|
441
|
+
issues.push({
|
|
442
|
+
severity: types_1.PreValidationSeverity.ERROR,
|
|
443
|
+
category: REFERENCE_NOT_FOUND_CATEGORY,
|
|
444
|
+
columnName: 'assortmentId',
|
|
445
|
+
propertySlug: 'assortmentId',
|
|
446
|
+
propertyType: 'string',
|
|
447
|
+
message,
|
|
448
|
+
affectedRows,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (issues.length === 0) {
|
|
453
|
+
for (const row of rows) {
|
|
454
|
+
if (row.assortmentId) {
|
|
455
|
+
if (nameCache[row.assortmentId]) {
|
|
456
|
+
row.assortmentName = nameCache[row.assortmentId];
|
|
457
|
+
}
|
|
458
|
+
if (workspaceCache[row.assortmentId]) {
|
|
459
|
+
row.assortmentWorkspaceId = workspaceCache[row.assortmentId];
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return issues;
|
|
465
|
+
}
|
|
466
|
+
async function stampProjectIdsFromAssortmentWorkspaces(rows, client) {
|
|
467
|
+
const issues = [];
|
|
468
|
+
const assortmentToWorkspace = new Map();
|
|
469
|
+
const assortmentsMissingWorkspace = new Set();
|
|
470
|
+
for (const row of rows) {
|
|
471
|
+
const assortmentId = row.assortmentId;
|
|
472
|
+
if (!assortmentId)
|
|
473
|
+
continue;
|
|
474
|
+
const workspaceId = row.assortmentWorkspaceId;
|
|
475
|
+
if (!workspaceId) {
|
|
476
|
+
assortmentsMissingWorkspace.add(assortmentId);
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
assortmentToWorkspace.set(assortmentId, workspaceId);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
for (const assortmentId of assortmentsMissingWorkspace) {
|
|
483
|
+
const affectedRows = [];
|
|
484
|
+
rows.forEach((row, index) => {
|
|
485
|
+
if (row.assortmentId === assortmentId && !row.assortmentWorkspaceId) {
|
|
486
|
+
affectedRows.push({ row: index + 2, value: assortmentId });
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
issues.push({
|
|
490
|
+
severity: types_1.PreValidationSeverity.ERROR,
|
|
491
|
+
category: REFERENCE_NOT_FOUND_CATEGORY,
|
|
492
|
+
columnName: 'assortmentId',
|
|
493
|
+
propertySlug: 'assortmentId',
|
|
494
|
+
propertyType: 'string',
|
|
495
|
+
message: `Assortment "${assortmentId}" has no workspaceId. Cannot derive a project for project-item rows from this assortment.`,
|
|
496
|
+
affectedRows,
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
if (issues.length > 0) {
|
|
500
|
+
return issues;
|
|
501
|
+
}
|
|
502
|
+
const projectByWorkspaceId = {};
|
|
503
|
+
const uniqueWorkspaceIds = new Set(assortmentToWorkspace.values());
|
|
504
|
+
for (const workspaceId of uniqueWorkspaceIds) {
|
|
505
|
+
try {
|
|
506
|
+
const workspace = await (0, entities_helper_1.retryOnTimeout)(client, entities_helper_1.EntitiesLambdaCallType.GET, {
|
|
507
|
+
entityName: 'workspace',
|
|
508
|
+
id: workspaceId,
|
|
509
|
+
relations: ['project'],
|
|
510
|
+
skipHydrateRelationships: true,
|
|
511
|
+
});
|
|
512
|
+
const project = workspace?.project;
|
|
513
|
+
if (!project?.id) {
|
|
514
|
+
const affectedAssortmentIds = [...assortmentToWorkspace.entries()]
|
|
515
|
+
.filter(([, wsId]) => wsId === workspaceId)
|
|
516
|
+
.map(([aId]) => aId);
|
|
517
|
+
const affectedRows = [];
|
|
518
|
+
rows.forEach((row, index) => {
|
|
519
|
+
if (row.assortmentId && affectedAssortmentIds.includes(row.assortmentId)) {
|
|
520
|
+
affectedRows.push({ row: index + 2, value: row.assortmentId });
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
issues.push({
|
|
524
|
+
severity: types_1.PreValidationSeverity.ERROR,
|
|
525
|
+
category: REFERENCE_NOT_FOUND_CATEGORY,
|
|
526
|
+
columnName: 'assortmentId',
|
|
527
|
+
propertySlug: 'assortmentId',
|
|
528
|
+
propertyType: 'string',
|
|
529
|
+
message: `Assortment workspace "${workspaceId}" has no associated project. Cannot derive a project for project-item rows. Verify the assortment lives in a PROJECT-type workspace.`,
|
|
530
|
+
affectedRows,
|
|
531
|
+
});
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
projectByWorkspaceId[workspaceId] = { id: project.id, name: project.name || project.id };
|
|
535
|
+
}
|
|
536
|
+
catch (e) {
|
|
537
|
+
issues.push({
|
|
538
|
+
severity: types_1.PreValidationSeverity.ERROR,
|
|
539
|
+
category: REFERENCE_NOT_FOUND_CATEGORY,
|
|
540
|
+
columnName: 'assortmentId',
|
|
541
|
+
propertySlug: 'assortmentId',
|
|
542
|
+
propertyType: 'string',
|
|
543
|
+
message: `Failed to resolve workspace "${workspaceId}" for assortment-derived project: ${e.message}`,
|
|
544
|
+
affectedRows: [],
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (issues.length > 0) {
|
|
549
|
+
return issues;
|
|
550
|
+
}
|
|
551
|
+
for (const row of rows) {
|
|
552
|
+
const workspaceId = row.assortmentWorkspaceId;
|
|
553
|
+
if (!workspaceId)
|
|
554
|
+
continue;
|
|
555
|
+
const project = projectByWorkspaceId[workspaceId];
|
|
556
|
+
if (!project)
|
|
557
|
+
continue;
|
|
558
|
+
row.projectId = project.id;
|
|
559
|
+
row.projectName = project.name;
|
|
560
|
+
}
|
|
561
|
+
return issues;
|
|
562
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|