@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
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ $ npm install -g @contrail/cli
|
|
|
17
17
|
$ contrail COMMAND
|
|
18
18
|
running command...
|
|
19
19
|
$ contrail (--version)
|
|
20
|
-
@contrail/cli/3.5.1-alpha.
|
|
20
|
+
@contrail/cli/3.5.1-alpha.9da7ac5 linux-x64 node-v24.14.1
|
|
21
21
|
$ contrail --help [COMMAND]
|
|
22
22
|
USAGE
|
|
23
23
|
$ contrail COMMAND
|
|
@@ -5,7 +5,14 @@ const entity_types_1 = require("@contrail/entity-types");
|
|
|
5
5
|
const base_processor_1 = require("./base-processor");
|
|
6
6
|
const entity_processor_1 = require("./entity-processor");
|
|
7
7
|
const constants_1 = require("./constants");
|
|
8
|
-
|
|
8
|
+
function removeHexHash(data) {
|
|
9
|
+
for (const row of data ?? []) {
|
|
10
|
+
if (row.hex?.startsWith('#')) {
|
|
11
|
+
row.hex = row.hex.slice(1);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return data;
|
|
15
|
+
}
|
|
9
16
|
class ColorProcessor extends base_processor_1.BaseProcessor {
|
|
10
17
|
static typesToTransformBy = ['color'];
|
|
11
18
|
static requiredProperties = ['typePath', 'typeId', 'federatedId', 'name', 'hex', 'red', 'green', 'blue'];
|
|
@@ -13,7 +20,7 @@ class ColorProcessor extends base_processor_1.BaseProcessor {
|
|
|
13
20
|
if (!data) {
|
|
14
21
|
return [];
|
|
15
22
|
}
|
|
16
|
-
return (
|
|
23
|
+
return removeHexHash(data);
|
|
17
24
|
}
|
|
18
25
|
static validateTypePath(data, config, csvHeaders, generalErrors) {
|
|
19
26
|
const typePathSource = {
|
|
@@ -49,13 +56,20 @@ class ColorProcessor extends base_processor_1.BaseProcessor {
|
|
|
49
56
|
static validateLoadFile(data, config) {
|
|
50
57
|
const { errors, warnings, generalErrors, generalWarnings, csvHeaders } = entity_processor_1.EntityProcessor.validateLoadFile(data, config, entity_types_1.LoadType.COLOR);
|
|
51
58
|
this.validateTypePath(data, config, csvHeaders, generalErrors);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
const redColumn = config?.federatedMappings?.red || 'red';
|
|
60
|
+
const blueColumn = config?.federatedMappings?.blue || 'blue';
|
|
61
|
+
const greenColumn = config?.federatedMappings?.green || 'green';
|
|
62
|
+
const hexColumn = config?.federatedMappings?.hex || 'hex';
|
|
63
|
+
const hasRedColumn = csvHeaders.includes(redColumn);
|
|
64
|
+
const hasBlueColumn = csvHeaders.includes(blueColumn);
|
|
65
|
+
const hasGreenColumn = csvHeaders.includes(greenColumn);
|
|
66
|
+
const hasHexColumn = csvHeaders.includes(hexColumn);
|
|
67
|
+
if (hasHexColumn && hasRedColumn && hasGreenColumn && hasBlueColumn) {
|
|
68
|
+
generalWarnings.push({
|
|
69
|
+
message: constants_1.COLOR_PROCESSOR_WARNINGS.HEX_IGNORED_WARNING,
|
|
70
|
+
issueType: entity_types_1.IssueType.CONFIG,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
59
73
|
return { errors, warnings, generalErrors, generalWarnings };
|
|
60
74
|
}
|
|
61
75
|
}
|
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const color_processor_1 = require("./color-processor");
|
|
4
4
|
const entity_types_1 = require("@contrail/entity-types");
|
|
5
5
|
const constants_1 = require("./constants");
|
|
6
|
-
const color_validation_1 = require("./common/color-validation");
|
|
7
6
|
describe('ColorProcessor', () => {
|
|
8
7
|
const loaderProcess = {
|
|
9
8
|
id: 'mock-loader-process-id',
|
|
@@ -220,38 +219,72 @@ describe('ColorProcessor', () => {
|
|
|
220
219
|
});
|
|
221
220
|
});
|
|
222
221
|
describe('color column validation', () => {
|
|
223
|
-
it('
|
|
222
|
+
it('should warn when both hex and RGB columns are present', () => {
|
|
224
223
|
const data = [
|
|
225
|
-
{ name: 'test-color', red: '255', green: '0', blue: '0', hex: '#ff0000' },
|
|
226
|
-
];
|
|
227
|
-
const result = color_processor_1.ColorProcessor.validateLoadFile(data, loaderProcess);
|
|
228
|
-
expect(result.warnings).toEqual([
|
|
229
224
|
{
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
225
|
+
name: 'test-color',
|
|
226
|
+
red: '255',
|
|
227
|
+
green: '0',
|
|
228
|
+
blue: '0',
|
|
229
|
+
hex: '#ff0000',
|
|
230
|
+
},
|
|
231
|
+
];
|
|
232
|
+
const config = {
|
|
233
|
+
...loaderProcess,
|
|
234
|
+
federatedMappings: {
|
|
235
|
+
...loaderProcess.federatedMappings,
|
|
236
|
+
red: 'red',
|
|
237
|
+
green: 'green',
|
|
238
|
+
blue: 'blue',
|
|
239
|
+
hex: 'hex',
|
|
239
240
|
},
|
|
240
|
-
|
|
241
|
+
};
|
|
242
|
+
const result = color_processor_1.ColorProcessor.validateLoadFile(data, config);
|
|
243
|
+
expect(result.generalWarnings).toContainEqual({
|
|
244
|
+
message: constants_1.COLOR_PROCESSOR_WARNINGS.HEX_IGNORED_WARNING,
|
|
245
|
+
issueType: entity_types_1.IssueType.CONFIG,
|
|
246
|
+
});
|
|
241
247
|
});
|
|
242
|
-
it('
|
|
243
|
-
const data = [
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
248
|
+
it('should not warn when only RGB columns are present', () => {
|
|
249
|
+
const data = [
|
|
250
|
+
{
|
|
251
|
+
name: 'test-color',
|
|
252
|
+
red: '255',
|
|
253
|
+
green: '0',
|
|
254
|
+
blue: '0',
|
|
255
|
+
},
|
|
256
|
+
];
|
|
257
|
+
const config = {
|
|
258
|
+
...loaderProcess,
|
|
259
|
+
federatedMappings: {
|
|
260
|
+
...loaderProcess.federatedMappings,
|
|
261
|
+
red: 'red',
|
|
262
|
+
green: 'green',
|
|
263
|
+
blue: 'blue',
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
const result = color_processor_1.ColorProcessor.validateLoadFile(data, config);
|
|
267
|
+
expect(result.generalWarnings).not.toContainEqual({
|
|
268
|
+
message: constants_1.COLOR_PROCESSOR_WARNINGS.HEX_IGNORED_WARNING,
|
|
269
|
+
issueType: entity_types_1.IssueType.CONFIG,
|
|
270
|
+
});
|
|
247
271
|
});
|
|
248
|
-
it('
|
|
272
|
+
it('should not warn when only hex column is present', () => {
|
|
249
273
|
const data = [{ name: 'test-color', hex: '#ff0000' }];
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
274
|
+
const config = {
|
|
275
|
+
...loaderProcess,
|
|
276
|
+
federatedMappings: {
|
|
277
|
+
...loaderProcess.federatedMappings,
|
|
278
|
+
hex: 'hex',
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
const result = color_processor_1.ColorProcessor.validateLoadFile(data, config);
|
|
282
|
+
expect(result.generalWarnings).not.toContainEqual({
|
|
283
|
+
message: constants_1.COLOR_PROCESSOR_WARNINGS.HEX_IGNORED_WARNING,
|
|
284
|
+
issueType: entity_types_1.IssueType.CONFIG,
|
|
285
|
+
});
|
|
253
286
|
});
|
|
254
|
-
it('
|
|
287
|
+
it('should handle custom column names from federatedMappings', () => {
|
|
255
288
|
const data = [{ name: 'test-color', r: '255', g: '0', b: '0', hexcode: '#ff0000' }];
|
|
256
289
|
const config = {
|
|
257
290
|
...loaderProcess,
|
|
@@ -264,94 +297,11 @@ describe('ColorProcessor', () => {
|
|
|
264
297
|
},
|
|
265
298
|
};
|
|
266
299
|
const result = color_processor_1.ColorProcessor.validateLoadFile(data, config);
|
|
267
|
-
expect(result.
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
columnIssues: [
|
|
271
|
-
{
|
|
272
|
-
columnName: 'hex',
|
|
273
|
-
message: color_validation_1.COLOR_VALIDATION_MESSAGES.HEX_PRIORITIZED_OVER_RGB,
|
|
274
|
-
issueType: entity_types_1.IssueType.INVALID_DATA,
|
|
275
|
-
data: JSON.stringify(data[0]),
|
|
276
|
-
},
|
|
277
|
-
],
|
|
278
|
-
},
|
|
279
|
-
]);
|
|
280
|
-
});
|
|
281
|
-
it('errors when hex is invalid', () => {
|
|
282
|
-
const data = [{ name: 'bad', hex: 'notahex' }];
|
|
283
|
-
const result = color_processor_1.ColorProcessor.validateLoadFile(data, loaderProcess);
|
|
284
|
-
expect(result.errors).toHaveLength(1);
|
|
285
|
-
expect(result.errors[0]).toMatchObject({
|
|
286
|
-
rowNumber: 2,
|
|
287
|
-
columnIssues: [
|
|
288
|
-
{
|
|
289
|
-
columnName: 'hex',
|
|
290
|
-
message: color_validation_1.COLOR_VALIDATION_MESSAGES.HEX_INVALID('notahex'),
|
|
291
|
-
issueType: entity_types_1.IssueType.INVALID_DATA,
|
|
292
|
-
},
|
|
293
|
-
],
|
|
294
|
-
});
|
|
295
|
-
});
|
|
296
|
-
it('errors when RGB is partially populated and hex is missing', () => {
|
|
297
|
-
const data = [{ name: 'partial', red: '255', green: '128' }];
|
|
298
|
-
const result = color_processor_1.ColorProcessor.validateLoadFile(data, loaderProcess);
|
|
299
|
-
expect(result.errors).toHaveLength(1);
|
|
300
|
-
expect(result.errors[0].rowNumber).toBe(2);
|
|
301
|
-
expect(result.errors[0].columnIssues).toEqual([
|
|
302
|
-
{
|
|
303
|
-
columnName: 'blue',
|
|
304
|
-
message: color_validation_1.COLOR_VALIDATION_MESSAGES.RGB_INCOMPLETE(['blue']),
|
|
305
|
-
issueType: entity_types_1.IssueType.INVALID_DATA,
|
|
306
|
-
data: JSON.stringify(data[0]),
|
|
307
|
-
},
|
|
308
|
-
]);
|
|
309
|
-
});
|
|
310
|
-
it('errors when an RGB component is out of range or non-numeric', () => {
|
|
311
|
-
const data = [
|
|
312
|
-
{ name: 'bad-red', red: '300', green: '0', blue: '0' },
|
|
313
|
-
{ name: 'nan-green', red: '10', green: 'foo', blue: '0' },
|
|
314
|
-
];
|
|
315
|
-
const result = color_processor_1.ColorProcessor.validateLoadFile(data, loaderProcess);
|
|
316
|
-
expect(result.errors).toHaveLength(2);
|
|
317
|
-
expect(result.errors[0]).toMatchObject({
|
|
318
|
-
rowNumber: 2,
|
|
319
|
-
columnIssues: [
|
|
320
|
-
{ columnName: 'red', message: color_validation_1.COLOR_VALIDATION_MESSAGES.RGB_OUT_OF_RANGE('red', '300') },
|
|
321
|
-
],
|
|
322
|
-
});
|
|
323
|
-
expect(result.errors[1]).toMatchObject({
|
|
324
|
-
rowNumber: 3,
|
|
325
|
-
columnIssues: [
|
|
326
|
-
{ columnName: 'green', message: color_validation_1.COLOR_VALIDATION_MESSAGES.RGB_OUT_OF_RANGE('green', 'foo') },
|
|
327
|
-
],
|
|
300
|
+
expect(result.generalWarnings).toContainEqual({
|
|
301
|
+
message: constants_1.COLOR_PROCESSOR_WARNINGS.HEX_IGNORED_WARNING,
|
|
302
|
+
issueType: entity_types_1.IssueType.CONFIG,
|
|
328
303
|
});
|
|
329
304
|
});
|
|
330
|
-
it('does not error when neither hex nor RGB are populated (defaults to black)', () => {
|
|
331
|
-
const data = [{ name: 'no-color' }];
|
|
332
|
-
const result = color_processor_1.ColorProcessor.validateLoadFile(data, loaderProcess);
|
|
333
|
-
expect(result.errors).toEqual([]);
|
|
334
|
-
expect(result.warnings).toEqual([]);
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
});
|
|
338
|
-
describe('transformData (hex priority)', () => {
|
|
339
|
-
it('strips red/green/blue when hex is valid', () => {
|
|
340
|
-
const data = [
|
|
341
|
-
{ hex: '#ff0000', red: '255', green: '0', blue: '0', name: 'red' },
|
|
342
|
-
];
|
|
343
|
-
const result = color_processor_1.ColorProcessor.transformData(data);
|
|
344
|
-
expect(result).toEqual([{ hex: 'ff0000', name: 'red' }]);
|
|
345
|
-
});
|
|
346
|
-
it('keeps red/green/blue and removes empty hex when hex is missing', () => {
|
|
347
|
-
const data = [{ red: '255', green: '0', blue: '0', name: 'red' }];
|
|
348
|
-
const result = color_processor_1.ColorProcessor.transformData(data);
|
|
349
|
-
expect(result).toEqual([{ red: '255', green: '0', blue: '0', name: 'red' }]);
|
|
350
|
-
});
|
|
351
|
-
it('defaults to #000000 (without hash) when neither hex nor full RGB is provided', () => {
|
|
352
|
-
const data = [{ name: 'fallback' }];
|
|
353
|
-
const result = color_processor_1.ColorProcessor.transformData(data);
|
|
354
|
-
expect(result).toEqual([{ name: 'fallback', hex: color_validation_1.DEFAULT_HEX }]);
|
|
355
305
|
});
|
|
356
306
|
});
|
|
357
307
|
});
|
|
@@ -2,5 +2,5 @@ interface ChunkByUrlLengthOptions {
|
|
|
2
2
|
maxBytes?: number;
|
|
3
3
|
maxCount?: number;
|
|
4
4
|
}
|
|
5
|
-
export declare function chunkByUrlLength(ids:
|
|
5
|
+
export declare function chunkByUrlLength(ids: string[], options?: ChunkByUrlLengthOptions): string[][];
|
|
6
6
|
export {};
|
|
@@ -6,8 +6,6 @@ function wireEncodedLength(id) {
|
|
|
6
6
|
return encodeURIComponent(id.replace(/,/g, '_COMMA_')).length;
|
|
7
7
|
}
|
|
8
8
|
function chunkByUrlLength(ids, options = {}) {
|
|
9
|
-
if (!ids?.length)
|
|
10
|
-
return [];
|
|
11
9
|
const maxBytes = options.maxBytes ?? constants_1.FEDERATED_ID_FETCH_MAX_URL_BYTES;
|
|
12
10
|
const maxCount = options.maxCount ?? constants_1.FEDERATED_ID_FETCH_MAX_COUNT;
|
|
13
11
|
const result = [];
|
|
@@ -5,10 +5,6 @@ describe('chunkByUrlLength', () => {
|
|
|
5
5
|
it('returns an empty array for empty input', () => {
|
|
6
6
|
expect((0, chunk_by_url_length_1.chunkByUrlLength)([])).toEqual([]);
|
|
7
7
|
});
|
|
8
|
-
it('returns an empty array for null/undefined input (matches lodash.chunk behavior)', () => {
|
|
9
|
-
expect((0, chunk_by_url_length_1.chunkByUrlLength)(undefined)).toEqual([]);
|
|
10
|
-
expect((0, chunk_by_url_length_1.chunkByUrlLength)(null)).toEqual([]);
|
|
11
|
-
});
|
|
12
8
|
it('packs short ids into a single chunk up to maxCount', () => {
|
|
13
9
|
const ids = Array.from({ length: 50 }, (_, i) => `id-${i}`);
|
|
14
10
|
const chunks = (0, chunk_by_url_length_1.chunkByUrlLength)(ids, { maxCount: 200, maxBytes: 10000 });
|
|
@@ -53,6 +53,7 @@ class SizeRangeSanitizer {
|
|
|
53
53
|
}
|
|
54
54
|
const typePropertySlugs = type?.typeProperties?.map((x) => x.slug);
|
|
55
55
|
if ((0, lodash_1.intersection)(typePropertySlugs, [exports.SIZE_RANGE_SLUG, exports.SIZE_RANGE_TEMPLATE_SLUG]).length !== 2) {
|
|
56
|
+
console.debug(`No size ranges for type ${JSON.stringify(type, null, 2)}, skipping.`);
|
|
56
57
|
continue;
|
|
57
58
|
}
|
|
58
59
|
validTypePaths.push(typePath);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export declare const COLOR_PROCESSOR_ERRORS: {
|
|
2
2
|
readonly TYPEPATH_MISSING: "\"typePath\" with value \"color\" is required property but is not found in the federatedMapping config, conditionalColumns config, or as a header in the CSV file. Please add a mapping \"typePath\" mapping with value \"color\" with one of these methods, then try the load again.";
|
|
3
3
|
};
|
|
4
|
+
export declare const COLOR_PROCESSOR_WARNINGS: {
|
|
5
|
+
readonly HEX_IGNORED_WARNING: "\"Hex\" is ignored when red, green, and blue columns are present";
|
|
6
|
+
};
|
|
4
7
|
export declare const CUSTOM_ENTITY_PROCESSOR_ERRORS: {
|
|
5
8
|
readonly TYPEPATH_MISSING: "\"typePath\" with value \"custom-entity:<subtypepath>\" is required property but is not found in the federatedMapping config, conditionalColumns config, or as a header in the CSV file. Please add a mapping \"typePath\" mapping with value \"custom-entity:<subtypepath>\" with one of these methods, then try the load again.";
|
|
6
9
|
};
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ITEM_PROCESSOR_WARNINGS = exports.FEDERATED_ID_FETCH_MAX_COUNT = exports.FEDERATED_ID_FETCH_MAX_URL_BYTES = exports.ITEM_PROCESSOR_ERRORS = exports.ENTITY_PROCESSOR_ERRORS = exports.CUSTOM_ENTITY_PROCESSOR_ERRORS = exports.COLOR_PROCESSOR_ERRORS = void 0;
|
|
3
|
+
exports.ITEM_PROCESSOR_WARNINGS = exports.FEDERATED_ID_FETCH_MAX_COUNT = exports.FEDERATED_ID_FETCH_MAX_URL_BYTES = exports.ITEM_PROCESSOR_ERRORS = exports.ENTITY_PROCESSOR_ERRORS = exports.CUSTOM_ENTITY_PROCESSOR_ERRORS = exports.COLOR_PROCESSOR_WARNINGS = exports.COLOR_PROCESSOR_ERRORS = void 0;
|
|
4
4
|
exports.COLOR_PROCESSOR_ERRORS = {
|
|
5
5
|
TYPEPATH_MISSING: '"typePath" with value "color" is required property but is not found in the federatedMapping config, conditionalColumns config, or as a header in the CSV file. Please add a mapping "typePath" mapping with value "color" with one of these methods, then try the load again.',
|
|
6
6
|
};
|
|
7
|
+
exports.COLOR_PROCESSOR_WARNINGS = {
|
|
8
|
+
HEX_IGNORED_WARNING: '"Hex" is ignored when red, green, and blue columns are present',
|
|
9
|
+
};
|
|
7
10
|
exports.CUSTOM_ENTITY_PROCESSOR_ERRORS = {
|
|
8
11
|
TYPEPATH_MISSING: '"typePath" with value "custom-entity:<subtypepath>" is required property but is not found in the federatedMapping config, conditionalColumns config, or as a header in the CSV file. Please add a mapping "typePath" mapping with value "custom-entity:<subtypepath>" with one of these methods, then try the load again.',
|
|
9
12
|
};
|
|
@@ -6,7 +6,14 @@ const util_1 = require("../../common/util");
|
|
|
6
6
|
const entity_processor_v2_1 = require("../entity-processor-v2");
|
|
7
7
|
const constants_1 = require("../../constants");
|
|
8
8
|
const base_processor_v2_1 = require("../base-processor-v2");
|
|
9
|
-
|
|
9
|
+
function removeHexHash(data) {
|
|
10
|
+
for (const row of data ?? []) {
|
|
11
|
+
if (row.hex?.startsWith('#')) {
|
|
12
|
+
row.hex = row.hex.slice(1);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return data;
|
|
16
|
+
}
|
|
10
17
|
class ColorProcessorV2 extends base_processor_v2_1.BaseProcessorV2 {
|
|
11
18
|
static requiredProperties = ['typePath', 'typeId', 'federatedId', 'name', 'hex', 'red', 'green', 'blue'];
|
|
12
19
|
static canAddProperty(allPropertiesSlugs, propertyName) {
|
|
@@ -16,7 +23,7 @@ class ColorProcessorV2 extends base_processor_v2_1.BaseProcessorV2 {
|
|
|
16
23
|
if (!data) {
|
|
17
24
|
return [];
|
|
18
25
|
}
|
|
19
|
-
return (
|
|
26
|
+
return removeHexHash(data);
|
|
20
27
|
}
|
|
21
28
|
static validateLoadFile(data, config) {
|
|
22
29
|
const { errors, warnings, generalErrors, generalWarnings, csvHeaders, returnImmediately } = entity_processor_v2_1.EntityProcessorV2.validateLoadFile(data, config);
|
|
@@ -34,13 +41,20 @@ class ColorProcessorV2 extends base_processor_v2_1.BaseProcessorV2 {
|
|
|
34
41
|
if (duplicateFederatedIdErrors.length) {
|
|
35
42
|
errors.push(...duplicateFederatedIdErrors);
|
|
36
43
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
const redColumn = config?.federatedMappings?.red || 'red';
|
|
45
|
+
const blueColumn = config?.federatedMappings?.blue || 'blue';
|
|
46
|
+
const greenColumn = config?.federatedMappings?.green || 'green';
|
|
47
|
+
const hexColumn = config?.federatedMappings?.hex || 'hex';
|
|
48
|
+
const hasRedColumn = csvHeaders.includes(redColumn);
|
|
49
|
+
const hasBlueColumn = csvHeaders.includes(blueColumn);
|
|
50
|
+
const hasGreenColumn = csvHeaders.includes(greenColumn);
|
|
51
|
+
const hasHexColumn = csvHeaders.includes(hexColumn);
|
|
52
|
+
if (hasHexColumn && hasRedColumn && hasGreenColumn && hasBlueColumn) {
|
|
53
|
+
generalWarnings.push({
|
|
54
|
+
message: constants_1.COLOR_PROCESSOR_WARNINGS.HEX_IGNORED_WARNING,
|
|
55
|
+
issueType: entity_types_1.IssueType.CONFIG,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
44
58
|
return { errors, warnings, generalErrors, generalWarnings };
|
|
45
59
|
}
|
|
46
60
|
static validateTypePath(data, config, csvHeaders, generalErrors) {
|
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const constants_1 = require("../../constants");
|
|
4
4
|
const color_processor_v2_1 = require("./color-processor-v2");
|
|
5
5
|
const entity_types_1 = require("@contrail/entity-types");
|
|
6
|
-
const color_validation_1 = require("../../common/color-validation");
|
|
7
6
|
describe('ColorProcessorV2', () => {
|
|
8
7
|
const loaderProcess = {
|
|
9
8
|
id: 'mock-loader-process-id',
|
|
@@ -220,38 +219,72 @@ describe('ColorProcessorV2', () => {
|
|
|
220
219
|
});
|
|
221
220
|
});
|
|
222
221
|
describe('color column validation', () => {
|
|
223
|
-
it('
|
|
222
|
+
it('should warn when both hex and RGB columns are present', () => {
|
|
224
223
|
const data = [
|
|
225
|
-
{ name: 'test-color', red: '255', green: '0', blue: '0', hex: '#ff0000' },
|
|
226
|
-
];
|
|
227
|
-
const result = color_processor_v2_1.ColorProcessorV2.validateLoadFile(data, loaderProcess);
|
|
228
|
-
expect(result.warnings).toEqual([
|
|
229
224
|
{
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
225
|
+
name: 'test-color',
|
|
226
|
+
red: '255',
|
|
227
|
+
green: '0',
|
|
228
|
+
blue: '0',
|
|
229
|
+
hex: '#ff0000',
|
|
230
|
+
},
|
|
231
|
+
];
|
|
232
|
+
const config = {
|
|
233
|
+
...loaderProcess,
|
|
234
|
+
federatedMappings: {
|
|
235
|
+
...loaderProcess.federatedMappings,
|
|
236
|
+
red: 'red',
|
|
237
|
+
green: 'green',
|
|
238
|
+
blue: 'blue',
|
|
239
|
+
hex: 'hex',
|
|
239
240
|
},
|
|
240
|
-
|
|
241
|
+
};
|
|
242
|
+
const result = color_processor_v2_1.ColorProcessorV2.validateLoadFile(data, config);
|
|
243
|
+
expect(result.generalWarnings).toContainEqual({
|
|
244
|
+
message: constants_1.COLOR_PROCESSOR_WARNINGS.HEX_IGNORED_WARNING,
|
|
245
|
+
issueType: entity_types_1.IssueType.CONFIG,
|
|
246
|
+
});
|
|
241
247
|
});
|
|
242
|
-
it('
|
|
243
|
-
const data = [
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
248
|
+
it('should not warn when only RGB columns are present', () => {
|
|
249
|
+
const data = [
|
|
250
|
+
{
|
|
251
|
+
name: 'test-color',
|
|
252
|
+
red: '255',
|
|
253
|
+
green: '0',
|
|
254
|
+
blue: '0',
|
|
255
|
+
},
|
|
256
|
+
];
|
|
257
|
+
const config = {
|
|
258
|
+
...loaderProcess,
|
|
259
|
+
federatedMappings: {
|
|
260
|
+
...loaderProcess.federatedMappings,
|
|
261
|
+
red: 'red',
|
|
262
|
+
green: 'green',
|
|
263
|
+
blue: 'blue',
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
const result = color_processor_v2_1.ColorProcessorV2.validateLoadFile(data, config);
|
|
267
|
+
expect(result.generalWarnings).not.toContainEqual({
|
|
268
|
+
message: constants_1.COLOR_PROCESSOR_WARNINGS.HEX_IGNORED_WARNING,
|
|
269
|
+
issueType: entity_types_1.IssueType.CONFIG,
|
|
270
|
+
});
|
|
247
271
|
});
|
|
248
|
-
it('
|
|
272
|
+
it('should not warn when only hex column is present', () => {
|
|
249
273
|
const data = [{ name: 'test-color', hex: '#ff0000' }];
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
274
|
+
const config = {
|
|
275
|
+
...loaderProcess,
|
|
276
|
+
federatedMappings: {
|
|
277
|
+
...loaderProcess.federatedMappings,
|
|
278
|
+
hex: 'hex',
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
const result = color_processor_v2_1.ColorProcessorV2.validateLoadFile(data, config);
|
|
282
|
+
expect(result.generalWarnings).not.toContainEqual({
|
|
283
|
+
message: constants_1.COLOR_PROCESSOR_WARNINGS.HEX_IGNORED_WARNING,
|
|
284
|
+
issueType: entity_types_1.IssueType.CONFIG,
|
|
285
|
+
});
|
|
253
286
|
});
|
|
254
|
-
it('
|
|
287
|
+
it('should handle custom column names from federatedMappings', () => {
|
|
255
288
|
const data = [{ name: 'test-color', r: '255', g: '0', b: '0', hexcode: '#ff0000' }];
|
|
256
289
|
const config = {
|
|
257
290
|
...loaderProcess,
|
|
@@ -264,94 +297,11 @@ describe('ColorProcessorV2', () => {
|
|
|
264
297
|
},
|
|
265
298
|
};
|
|
266
299
|
const result = color_processor_v2_1.ColorProcessorV2.validateLoadFile(data, config);
|
|
267
|
-
expect(result.
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
columnIssues: [
|
|
271
|
-
{
|
|
272
|
-
columnName: 'hex',
|
|
273
|
-
message: color_validation_1.COLOR_VALIDATION_MESSAGES.HEX_PRIORITIZED_OVER_RGB,
|
|
274
|
-
issueType: entity_types_1.IssueType.INVALID_DATA,
|
|
275
|
-
data: JSON.stringify(data[0]),
|
|
276
|
-
},
|
|
277
|
-
],
|
|
278
|
-
},
|
|
279
|
-
]);
|
|
280
|
-
});
|
|
281
|
-
it('errors when hex is invalid', () => {
|
|
282
|
-
const data = [{ name: 'bad', hex: 'notahex' }];
|
|
283
|
-
const result = color_processor_v2_1.ColorProcessorV2.validateLoadFile(data, loaderProcess);
|
|
284
|
-
expect(result.errors).toHaveLength(1);
|
|
285
|
-
expect(result.errors[0]).toMatchObject({
|
|
286
|
-
rowNumber: 2,
|
|
287
|
-
columnIssues: [
|
|
288
|
-
{
|
|
289
|
-
columnName: 'hex',
|
|
290
|
-
message: color_validation_1.COLOR_VALIDATION_MESSAGES.HEX_INVALID('notahex'),
|
|
291
|
-
issueType: entity_types_1.IssueType.INVALID_DATA,
|
|
292
|
-
},
|
|
293
|
-
],
|
|
294
|
-
});
|
|
295
|
-
});
|
|
296
|
-
it('errors when RGB is partially populated and hex is missing', () => {
|
|
297
|
-
const data = [{ name: 'partial', red: '255', green: '128' }];
|
|
298
|
-
const result = color_processor_v2_1.ColorProcessorV2.validateLoadFile(data, loaderProcess);
|
|
299
|
-
expect(result.errors).toHaveLength(1);
|
|
300
|
-
expect(result.errors[0].rowNumber).toBe(2);
|
|
301
|
-
expect(result.errors[0].columnIssues).toEqual([
|
|
302
|
-
{
|
|
303
|
-
columnName: 'blue',
|
|
304
|
-
message: color_validation_1.COLOR_VALIDATION_MESSAGES.RGB_INCOMPLETE(['blue']),
|
|
305
|
-
issueType: entity_types_1.IssueType.INVALID_DATA,
|
|
306
|
-
data: JSON.stringify(data[0]),
|
|
307
|
-
},
|
|
308
|
-
]);
|
|
309
|
-
});
|
|
310
|
-
it('errors when an RGB component is out of range or non-numeric', () => {
|
|
311
|
-
const data = [
|
|
312
|
-
{ name: 'bad-red', red: '300', green: '0', blue: '0' },
|
|
313
|
-
{ name: 'nan-green', red: '10', green: 'foo', blue: '0' },
|
|
314
|
-
];
|
|
315
|
-
const result = color_processor_v2_1.ColorProcessorV2.validateLoadFile(data, loaderProcess);
|
|
316
|
-
expect(result.errors).toHaveLength(2);
|
|
317
|
-
expect(result.errors[0]).toMatchObject({
|
|
318
|
-
rowNumber: 2,
|
|
319
|
-
columnIssues: [
|
|
320
|
-
{ columnName: 'red', message: color_validation_1.COLOR_VALIDATION_MESSAGES.RGB_OUT_OF_RANGE('red', '300') },
|
|
321
|
-
],
|
|
322
|
-
});
|
|
323
|
-
expect(result.errors[1]).toMatchObject({
|
|
324
|
-
rowNumber: 3,
|
|
325
|
-
columnIssues: [
|
|
326
|
-
{ columnName: 'green', message: color_validation_1.COLOR_VALIDATION_MESSAGES.RGB_OUT_OF_RANGE('green', 'foo') },
|
|
327
|
-
],
|
|
300
|
+
expect(result.generalWarnings).toContainEqual({
|
|
301
|
+
message: constants_1.COLOR_PROCESSOR_WARNINGS.HEX_IGNORED_WARNING,
|
|
302
|
+
issueType: entity_types_1.IssueType.CONFIG,
|
|
328
303
|
});
|
|
329
304
|
});
|
|
330
|
-
it('does not error when neither hex nor RGB are populated (defaults to black)', () => {
|
|
331
|
-
const data = [{ name: 'no-color' }];
|
|
332
|
-
const result = color_processor_v2_1.ColorProcessorV2.validateLoadFile(data, loaderProcess);
|
|
333
|
-
expect(result.errors).toEqual([]);
|
|
334
|
-
expect(result.warnings).toEqual([]);
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
describe('transformData (hex priority)', () => {
|
|
338
|
-
it('strips red/green/blue when hex is valid', () => {
|
|
339
|
-
const data = [
|
|
340
|
-
{ hex: '#ff0000', red: '255', green: '0', blue: '0', name: 'red' },
|
|
341
|
-
];
|
|
342
|
-
const result = color_processor_v2_1.ColorProcessorV2.transformData(data);
|
|
343
|
-
expect(result).toEqual([{ hex: 'ff0000', name: 'red' }]);
|
|
344
|
-
});
|
|
345
|
-
it('keeps red/green/blue and removes empty hex when hex is missing', () => {
|
|
346
|
-
const data = [{ red: '255', green: '0', blue: '0', name: 'red' }];
|
|
347
|
-
const result = color_processor_v2_1.ColorProcessorV2.transformData(data);
|
|
348
|
-
expect(result).toEqual([{ red: '255', green: '0', blue: '0', name: 'red' }]);
|
|
349
|
-
});
|
|
350
|
-
it('defaults to #000000 (without hash) when neither hex nor full RGB is provided', () => {
|
|
351
|
-
const data = [{ name: 'fallback' }];
|
|
352
|
-
const result = color_processor_v2_1.ColorProcessorV2.transformData(data);
|
|
353
|
-
expect(result).toEqual([{ name: 'fallback', hex: color_validation_1.DEFAULT_HEX }]);
|
|
354
|
-
});
|
|
355
305
|
});
|
|
356
306
|
it('should return error if conditionalColumns format is incorrect', () => {
|
|
357
307
|
const data = [{ name: 'color1' }];
|
|
@@ -67,6 +67,7 @@ async function retryOnTimeout(client, callType, callParams, maxRetries = 3) {
|
|
|
67
67
|
}
|
|
68
68
|
exports.retryOnTimeout = retryOnTimeout;
|
|
69
69
|
function UpdateNeeded(incoming, existing, fieldsToSkip = [], fieldsToSkipIfOnlyOnExisting = [], type) {
|
|
70
|
+
console.debug('Comparing existing and incoming objects...');
|
|
70
71
|
const diffs = compareIncomingAndExisting(incoming, existing, fieldsToSkip, fieldsToSkipIfOnlyOnExisting, type);
|
|
71
72
|
return diffs?.length > 0;
|
|
72
73
|
}
|
|
@@ -253,6 +253,7 @@ class AssortmentProcessor extends base_processor_v2_1.BaseProcessorV2 {
|
|
|
253
253
|
}
|
|
254
254
|
static async loadObjects(client, data, passedItemsByFederatedId, assortmentId, assortmentIdentifier, typesByTypePath) {
|
|
255
255
|
const { allPropertiesSlugs, typePropertyToValueType } = await (0, item_helper_1.getAssortmentItemProperties)(client, this.requiredProperties, typesByTypePath);
|
|
256
|
+
console.debug('using assortment types:', JSON.stringify({ allPropertiesSlugs, typePropertyToValueType }, null, 2));
|
|
256
257
|
const { incomingAssortmentItems, badAssortmentItems } = this.processIncomingData(data, allPropertiesSlugs, typePropertyToValueType);
|
|
257
258
|
const targetAssortment = await (0, item_helper_1.getAssortment)(client, assortmentId, assortmentIdentifier, data);
|
|
258
259
|
if (!targetAssortment) {
|