@contrail/cli 3.4.10 → 3.4.11-alpha.0f34b47
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/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/templates/typescript/tsup.config.ts +0 -0
- package/node_modules/@contrail/loader/lib/common/chunk-by-url-length.d.ts +6 -0
- package/node_modules/@contrail/loader/lib/common/chunk-by-url-length.js +31 -0
- package/node_modules/@contrail/loader/lib/common/chunk-by-url-length.spec.d.ts +1 -0
- package/node_modules/@contrail/loader/lib/common/chunk-by-url-length.spec.js +70 -0
- package/node_modules/@contrail/loader/lib/common/data-transformer-functions.d.ts +9 -1
- package/node_modules/@contrail/loader/lib/common/data-transformer-functions.js +53 -8
- package/node_modules/@contrail/loader/lib/common/data-transformer.spec.js +128 -0
- package/node_modules/@contrail/loader/lib/common/entities-helper.js +2 -1
- package/node_modules/@contrail/loader/lib/constants.d.ts +2 -0
- package/node_modules/@contrail/loader/lib/constants.js +3 -1
- package/node_modules/@contrail/loader/lib/entities-v2/entities-helper.js +2 -1
- package/node_modules/@contrail/loader/lib/entities-v2/entity-processor-v2.js +2 -2
- package/node_modules/@contrail/loader/lib/entities-v2/entity-processor-v2.spec.js +8 -10
- package/node_modules/@contrail/loader/lib/entities-v2/file-preprocessor-v2.js +2 -1
- package/node_modules/@contrail/loader/lib/entities-v2/linesheet-v2/item-helper.js +2 -1
- package/node_modules/@contrail/loader/lib/entity-processor.js +2 -2
- package/node_modules/@contrail/loader/lib/entity-processor.spec.js +8 -10
- package/node_modules/@contrail/loader/lib/pipeline/item-execute-helpers.js +2 -1
- package/node_modules/@contrail/loader/lib/pipeline/project-item-execute-helpers.js +3 -2
- package/node_modules/@contrail/loader/lib/pipeline/transform.js +32 -6
- package/node_modules/@contrail/loader/lib/pipeline/transform.spec.js +64 -1
- package/node_modules/@contrail/loader/package.json +1 -1
- package/oclif.manifest.json +5 -2
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.uploadTypePropertyPolicies = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const sdk_1 = require("@contrail/sdk");
|
|
6
6
|
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
@@ -8,12 +8,10 @@ const path = tslib_1.__importStar(require("path"));
|
|
|
8
8
|
const login_1 = require("../../auth/login");
|
|
9
9
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
10
10
|
const core_1 = require("@oclif/core");
|
|
11
|
-
const type_extractor_1 = require("../../helpers/types/type-extractor");
|
|
12
11
|
const users_1 = require("../../api/users");
|
|
13
12
|
const basecommand_1 = require("../../basecommand");
|
|
14
13
|
const logger_1 = tslib_1.__importDefault(require("../../logger"));
|
|
15
14
|
const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
|
|
16
|
-
const _ = tslib_1.__importStar(require("lodash"));
|
|
17
15
|
class UploadTypePropertyPolicies extends basecommand_1.BaseCommand {
|
|
18
16
|
static description = 'Upload type property policies';
|
|
19
17
|
static examples = [
|
|
@@ -38,14 +36,12 @@ async function uploadTypePropertyPolicies(dir) {
|
|
|
38
36
|
try {
|
|
39
37
|
await (0, login_1.authUser)();
|
|
40
38
|
logger_1.default.info(`${chalk_1.default.yellow.bold('TYPE PROPERTY POLICY')} Uploading`);
|
|
41
|
-
const
|
|
39
|
+
const reducedTypePropertyPolicies = await loadTPPolicies(dir);
|
|
42
40
|
await uploadTPPolicyObjects(reducedTypePropertyPolicies);
|
|
43
|
-
await uploadTypePropertyPolicyLinks(reducedTypePropertyPolicyLinks);
|
|
44
41
|
logger_1.default.info(`${chalk_1.default.yellow.bold('TYPE PROPERTY POLICY')} Uploaded`);
|
|
45
42
|
}
|
|
46
43
|
catch (e) {
|
|
47
44
|
logger_1.default.info(`${chalk_1.default.red.bold('FAIL')} Type Property Policy Upload Failed . ${e}`);
|
|
48
|
-
//TODO make this only print if a debug flag is set
|
|
49
45
|
console.trace(e);
|
|
50
46
|
return;
|
|
51
47
|
}
|
|
@@ -54,15 +50,13 @@ async function uploadTypePropertyPolicies(dir) {
|
|
|
54
50
|
exports.uploadTypePropertyPolicies = uploadTypePropertyPolicies;
|
|
55
51
|
function loadTPPolicies(fromDir) {
|
|
56
52
|
const tpps = [];
|
|
57
|
-
let tppLinks = [];
|
|
58
53
|
const dir = path.join(fromDir, 'type-property-policies');
|
|
59
54
|
try {
|
|
60
55
|
const files = fs_1.default.readdirSync(dir);
|
|
61
56
|
for (const file of files) {
|
|
62
57
|
tpps.push(js_yaml_1.default.load(fs_1.default.readFileSync(path.join(dir, file), 'utf8')));
|
|
63
58
|
}
|
|
64
|
-
|
|
65
|
-
return [tpps, tppLinks];
|
|
59
|
+
return tpps;
|
|
66
60
|
}
|
|
67
61
|
catch (e) {
|
|
68
62
|
throw new Error(`ERROR PARSING YAML: ${e}`);
|
|
@@ -155,133 +149,3 @@ async function uploadTPPolicyObjects(reducedTypePropertyPolicies) {
|
|
|
155
149
|
}
|
|
156
150
|
}
|
|
157
151
|
}
|
|
158
|
-
async function uploadTypePropertyPolicyLinks(reducedTypePropertyPolicyLinks) {
|
|
159
|
-
const typesWithProperties = await (0, type_extractor_1.getTypes)();
|
|
160
|
-
const typeProperties = [];
|
|
161
|
-
typesWithProperties.forEach((type) => {
|
|
162
|
-
if (type.typeProperties) {
|
|
163
|
-
for (const tp of type.typeProperties) {
|
|
164
|
-
tp.typeId = type.id;
|
|
165
|
-
tp.typeSlug = type.slug;
|
|
166
|
-
typeProperties.push(tp);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
const currentTPPs = await new sdk_1.Entities().get({
|
|
171
|
-
entityName: 'type-property-policy',
|
|
172
|
-
});
|
|
173
|
-
const currentTPPLinks = await new sdk_1.Entities().get({
|
|
174
|
-
entityName: 'type-property-policy-link',
|
|
175
|
-
});
|
|
176
|
-
logger_1.default.info(`found ${currentTPPLinks.length} current type property policy links`);
|
|
177
|
-
logger_1.default.info(`found ${reducedTypePropertyPolicyLinks.length} type property policy links to load`);
|
|
178
|
-
const currentLinksHashes = {};
|
|
179
|
-
for (const currentLink of currentTPPLinks) {
|
|
180
|
-
const foundTPs = findAllOccurrences(typeProperties, (tp) => tp.id === currentLink.typePropertyId);
|
|
181
|
-
for (const foundTP of foundTPs) {
|
|
182
|
-
const foundTPP = currentTPPs.find((tpp) => tpp.id === currentLink.typePropertyPolicyId);
|
|
183
|
-
if (!foundTP || !foundTPP) {
|
|
184
|
-
logger_1.default.info(`skipping unhashable, invalid policy link: ${JSON.stringify(currentLink, null, 2)}`);
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
const hash = `${foundTP.typeSlug}|${foundTP.slug}|${foundTPP.identifier}`;
|
|
188
|
-
currentLinksHashes[hash] = currentLink;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
logger_1.default.debug(`current links hashes: ${JSON.stringify(currentLinksHashes, null, 2)}`);
|
|
192
|
-
const reducedLinksByHashes = {};
|
|
193
|
-
for (const loadedLink of reducedTypePropertyPolicyLinks) {
|
|
194
|
-
const hash = `${loadedLink.typeSlug}|${loadedLink.typePropertySlug}|${loadedLink.typePropertyPolicyIdentifier}`;
|
|
195
|
-
reducedLinksByHashes[hash] = loadedLink;
|
|
196
|
-
}
|
|
197
|
-
const toCreate = Object.keys(reducedLinksByHashes)
|
|
198
|
-
.filter((x) => !currentLinksHashes[x])
|
|
199
|
-
.map((x) => {
|
|
200
|
-
return {
|
|
201
|
-
[x]: reducedLinksByHashes[x],
|
|
202
|
-
};
|
|
203
|
-
})
|
|
204
|
-
.reduce((a, b) => Object.assign(a, b), {});
|
|
205
|
-
const toDelete = _.uniqBy(Object.keys(currentLinksHashes)
|
|
206
|
-
.filter((x) => !reducedLinksByHashes[x])
|
|
207
|
-
.map((x) => {
|
|
208
|
-
return {
|
|
209
|
-
[x]: currentLinksHashes[x],
|
|
210
|
-
};
|
|
211
|
-
}), 'id').reduce((a, b) => Object.assign(a, b), {});
|
|
212
|
-
logger_1.default.debug(`reduced links hashes: ${JSON.stringify(reducedLinksByHashes, null, 2)}`);
|
|
213
|
-
logger_1.default.info(`found ${Object.keys(toCreate).length} type property policy links to create`);
|
|
214
|
-
logger_1.default.info(`found ${Object.keys(toDelete).length} type property policy links to delete`);
|
|
215
|
-
logger_1.default.debug(`to create: ${JSON.stringify(toCreate, null, 2)}`);
|
|
216
|
-
logger_1.default.debug(`to delete: ${JSON.stringify(toDelete, null, 2)}`);
|
|
217
|
-
logger_1.default.debug(`current links hashes to delete: ${JSON.stringify(Object.keys(currentLinksHashes).filter((x) => !reducedLinksByHashes[x]), null, 2)}`);
|
|
218
|
-
// fs.writeFileSync('toCreate.json', JSON.stringify(toCreate, null, 2));
|
|
219
|
-
// fs.writeFileSync('toDelete.json', JSON.stringify(toDelete, null, 2));
|
|
220
|
-
// fs.writeFileSync('currentLinksHashes.json', JSON.stringify(currentLinksHashes, null, 2));
|
|
221
|
-
// fs.writeFileSync('reducedLinksByHashes.json', JSON.stringify(reducedLinksByHashes, null, 2));
|
|
222
|
-
// fs.writeFileSync(
|
|
223
|
-
// 'current_link_hashes_to_delete.json',
|
|
224
|
-
// JSON.stringify(
|
|
225
|
-
// Object.keys(currentLinksHashes).filter((x) => !reducedLinksByHashes[x]),
|
|
226
|
-
// null,
|
|
227
|
-
// 2,
|
|
228
|
-
// ),
|
|
229
|
-
// );
|
|
230
|
-
// return;
|
|
231
|
-
for (const [hash, link] of Object.entries(toCreate)) {
|
|
232
|
-
try {
|
|
233
|
-
if (!transformReducedLink(link, typeProperties, currentTPPs)) {
|
|
234
|
-
continue;
|
|
235
|
-
}
|
|
236
|
-
logger_1.default.info(`Creating: ${hash}: ${JSON.stringify(link, null, 2)}`);
|
|
237
|
-
await new sdk_1.Entities().create({
|
|
238
|
-
entityName: 'type-property-policy-link',
|
|
239
|
-
object: {
|
|
240
|
-
typePropertyPolicyId: link.typePropertyPolicyId,
|
|
241
|
-
typePropertyId: link.typePropertyId,
|
|
242
|
-
},
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
catch (err) {
|
|
246
|
-
logger_1.default.error(`Error Uploading type property policy links: ${err}`);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
for (const [hash, link] of Object.entries(toDelete)) {
|
|
250
|
-
try {
|
|
251
|
-
logger_1.default.info(`Deleting: ${hash}`);
|
|
252
|
-
await new sdk_1.Entities().delete({
|
|
253
|
-
entityName: 'type-property-policy-link',
|
|
254
|
-
id: link['id'],
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
catch (err) {
|
|
258
|
-
logger_1.default.error(`Error Deleting type property policy link: ${err}`);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
function transformReducedLink(TPPLink, typeProperties, typePropertyPolicies) {
|
|
262
|
-
const foundTP = typeProperties.find((tp) => tp.slug === TPPLink.typePropertySlug && tp.typeSlug === TPPLink.typeSlug);
|
|
263
|
-
if (!foundTP) {
|
|
264
|
-
logger_1.default.error(`Could not find type property with slug ${TPPLink.typePropertySlug} `);
|
|
265
|
-
return false;
|
|
266
|
-
}
|
|
267
|
-
TPPLink.typePropertyId = foundTP.id;
|
|
268
|
-
const foundTPP = typePropertyPolicies.find((tpp) => tpp.identifier === TPPLink.typePropertyPolicyIdentifier);
|
|
269
|
-
if (!foundTPP) {
|
|
270
|
-
logger_1.default.error(`Could not find type property policy with identifier ${TPPLink.typePropertyPolicyIdentifier} `);
|
|
271
|
-
return false;
|
|
272
|
-
}
|
|
273
|
-
TPPLink.typePropertyPolicyId = foundTPP.id;
|
|
274
|
-
return true;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
function findAllOccurrences(array, evaluator) {
|
|
278
|
-
const found = [];
|
|
279
|
-
for (let i = 0; i < array.length; i++) {
|
|
280
|
-
const element = array[i];
|
|
281
|
-
if (evaluator(element)) {
|
|
282
|
-
found.push(element);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
return found;
|
|
286
|
-
}
|
|
287
|
-
exports.findAllOccurrences = findAllOccurrences;
|
|
@@ -2,6 +2,9 @@ import { BaseCommand } from '../../basecommand';
|
|
|
2
2
|
import { PersistReducedUsageInfo } from '../../interfaces';
|
|
3
3
|
export default class UploadUsageInfo extends BaseCommand {
|
|
4
4
|
static description: string;
|
|
5
|
+
static deprecationOptions: {
|
|
6
|
+
message: string;
|
|
7
|
+
};
|
|
5
8
|
static examples: string[];
|
|
6
9
|
static flags: {
|
|
7
10
|
dir: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
@@ -13,7 +13,10 @@ const basecommand_1 = require("../../basecommand");
|
|
|
13
13
|
const logger_1 = tslib_1.__importDefault(require("../../logger"));
|
|
14
14
|
const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
|
|
15
15
|
class UploadUsageInfo extends basecommand_1.BaseCommand {
|
|
16
|
-
static description = 'Upload extracted usage info';
|
|
16
|
+
static description = '[DEPRECATED] Upload extracted usage info. Use uploadTypeOverrides instead.';
|
|
17
|
+
static deprecationOptions = {
|
|
18
|
+
message: 'uploadUsageInfo is deprecated. Usage info is now managed as type overrides. Use `contrail types uploadTypeOverrides` instead.',
|
|
19
|
+
};
|
|
17
20
|
static examples = [
|
|
18
21
|
`$ contrail types uploadUsageInfo
|
|
19
22
|
`,
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.chunkByUrlLength = void 0;
|
|
4
|
+
const constants_1 = require("../constants");
|
|
5
|
+
function wireEncodedLength(id) {
|
|
6
|
+
return encodeURIComponent(id.replace(/,/g, '_COMMA_')).length;
|
|
7
|
+
}
|
|
8
|
+
function chunkByUrlLength(ids, options = {}) {
|
|
9
|
+
const maxBytes = options.maxBytes ?? constants_1.FEDERATED_ID_FETCH_MAX_URL_BYTES;
|
|
10
|
+
const maxCount = options.maxCount ?? constants_1.FEDERATED_ID_FETCH_MAX_COUNT;
|
|
11
|
+
const result = [];
|
|
12
|
+
let current = [];
|
|
13
|
+
let currentBytes = 0;
|
|
14
|
+
for (const id of ids) {
|
|
15
|
+
const encodedLength = wireEncodedLength(id);
|
|
16
|
+
const additionalBytes = current.length === 0 ? encodedLength : encodedLength + 1;
|
|
17
|
+
const wouldExceedBytes = currentBytes + additionalBytes > maxBytes;
|
|
18
|
+
const wouldExceedCount = current.length >= maxCount;
|
|
19
|
+
if (current.length > 0 && (wouldExceedBytes || wouldExceedCount)) {
|
|
20
|
+
result.push(current);
|
|
21
|
+
current = [];
|
|
22
|
+
currentBytes = 0;
|
|
23
|
+
}
|
|
24
|
+
current.push(id);
|
|
25
|
+
currentBytes += current.length === 1 ? encodedLength : encodedLength + 1;
|
|
26
|
+
}
|
|
27
|
+
if (current.length > 0)
|
|
28
|
+
result.push(current);
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
exports.chunkByUrlLength = chunkByUrlLength;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const chunk_by_url_length_1 = require("./chunk-by-url-length");
|
|
4
|
+
describe('chunkByUrlLength', () => {
|
|
5
|
+
it('returns an empty array for empty input', () => {
|
|
6
|
+
expect((0, chunk_by_url_length_1.chunkByUrlLength)([])).toEqual([]);
|
|
7
|
+
});
|
|
8
|
+
it('packs short ids into a single chunk up to maxCount', () => {
|
|
9
|
+
const ids = Array.from({ length: 50 }, (_, i) => `id-${i}`);
|
|
10
|
+
const chunks = (0, chunk_by_url_length_1.chunkByUrlLength)(ids, { maxCount: 200, maxBytes: 10000 });
|
|
11
|
+
expect(chunks).toHaveLength(1);
|
|
12
|
+
expect(chunks[0]).toHaveLength(50);
|
|
13
|
+
});
|
|
14
|
+
it('splits on the maxCount cap when bytes are not the constraint', () => {
|
|
15
|
+
const ids = Array.from({ length: 450 }, (_, i) => `id${i}`);
|
|
16
|
+
const chunks = (0, chunk_by_url_length_1.chunkByUrlLength)(ids, { maxCount: 200, maxBytes: 10000 });
|
|
17
|
+
expect(chunks.map((c) => c.length)).toEqual([200, 200, 50]);
|
|
18
|
+
});
|
|
19
|
+
it('splits on the maxBytes cap when ids are long', () => {
|
|
20
|
+
const longId = 'A'.repeat(500);
|
|
21
|
+
const ids = Array.from({ length: 20 }, () => longId);
|
|
22
|
+
const chunks = (0, chunk_by_url_length_1.chunkByUrlLength)(ids, { maxCount: 200, maxBytes: 2000 });
|
|
23
|
+
for (const chunk of chunks) {
|
|
24
|
+
const encoded = chunk.map((id) => encodeURIComponent(id)).join(',');
|
|
25
|
+
expect(encoded.length).toBeLessThanOrEqual(2000);
|
|
26
|
+
}
|
|
27
|
+
expect(chunks.flat()).toHaveLength(20);
|
|
28
|
+
});
|
|
29
|
+
it('accounts for URL-encoding expansion (spaces, percent signs, commas)', () => {
|
|
30
|
+
const id = 'FL63497 Carbon Brushed Cotton Part dyed 60% Cotton_COMMA_ 40% Polyester';
|
|
31
|
+
const ids = Array.from({ length: 200 }, () => id);
|
|
32
|
+
const chunks = (0, chunk_by_url_length_1.chunkByUrlLength)(ids, { maxCount: 200, maxBytes: 6000 });
|
|
33
|
+
expect(chunks.length).toBeGreaterThan(1);
|
|
34
|
+
for (const chunk of chunks) {
|
|
35
|
+
const encoded = chunk.map((x) => encodeURIComponent(x)).join(',');
|
|
36
|
+
expect(encoded.length).toBeLessThanOrEqual(6000);
|
|
37
|
+
}
|
|
38
|
+
expect(chunks.flat()).toHaveLength(200);
|
|
39
|
+
});
|
|
40
|
+
it('isolates a single id that exceeds maxBytes into its own chunk', () => {
|
|
41
|
+
const oversized = 'X'.repeat(10000);
|
|
42
|
+
const ids = ['short-1', 'short-2', oversized, 'short-3'];
|
|
43
|
+
const chunks = (0, chunk_by_url_length_1.chunkByUrlLength)(ids, { maxCount: 200, maxBytes: 100 });
|
|
44
|
+
const oversizedChunk = chunks.find((c) => c.length === 1 && c[0] === oversized);
|
|
45
|
+
expect(oversizedChunk).toBeDefined();
|
|
46
|
+
expect(chunks.flat()).toEqual(ids);
|
|
47
|
+
});
|
|
48
|
+
it('preserves order across chunks', () => {
|
|
49
|
+
const ids = Array.from({ length: 123 }, (_, i) => `id-${i}`);
|
|
50
|
+
const chunks = (0, chunk_by_url_length_1.chunkByUrlLength)(ids, { maxCount: 25, maxBytes: 10000 });
|
|
51
|
+
expect(chunks.flat()).toEqual(ids);
|
|
52
|
+
});
|
|
53
|
+
it('accounts for the SDK\'s `,` → `_COMMA_` substitution', () => {
|
|
54
|
+
const idWithCommas = 'FOO Cotton, 60% Polyester, 40% Elastane';
|
|
55
|
+
const ids = Array.from({ length: 100 }, () => idWithCommas);
|
|
56
|
+
const chunks = (0, chunk_by_url_length_1.chunkByUrlLength)(ids, { maxCount: 200, maxBytes: 1000 });
|
|
57
|
+
for (const chunk of chunks) {
|
|
58
|
+
const wireLength = chunk.map((id) => encodeURIComponent(id.replace(/,/g, '_COMMA_'))).join(',').length;
|
|
59
|
+
expect(wireLength).toBeLessThanOrEqual(1000);
|
|
60
|
+
}
|
|
61
|
+
expect(chunks.flat()).toHaveLength(100);
|
|
62
|
+
});
|
|
63
|
+
it('defaults to the module-level caps when no options are passed', () => {
|
|
64
|
+
const ids = Array.from({ length: 500 }, (_, i) => `id-${i}`);
|
|
65
|
+
const chunks = (0, chunk_by_url_length_1.chunkByUrlLength)(ids);
|
|
66
|
+
expect(chunks[0]).toHaveLength(200);
|
|
67
|
+
expect(chunks[1]).toHaveLength(200);
|
|
68
|
+
expect(chunks[2]).toHaveLength(100);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -9,6 +9,9 @@ export declare type TypePropertyMaps = {
|
|
|
9
9
|
typePropertiesBySlug: Record<string, TypeProperty>;
|
|
10
10
|
typePropertiesByLabel: Record<string, TypeProperty>;
|
|
11
11
|
optionSetUsingTypePropertiesBySlug: Record<string, TypePropertyWithLookups>;
|
|
12
|
+
typePathStateBySlug?: Record<string, Map<string, {
|
|
13
|
+
isDisabled: boolean;
|
|
14
|
+
}>>;
|
|
12
15
|
};
|
|
13
16
|
export declare const applyFederatedMappings: PreprocessingFunction;
|
|
14
17
|
export declare const removeUnwantedProperties: PreprocessingFunction;
|
|
@@ -20,9 +23,14 @@ export declare const applyConditionalColumns: PreprocessingFunction;
|
|
|
20
23
|
export declare function getPropertiesBySlugAndLabel(reducedTypes: Partial<Type>[]): {
|
|
21
24
|
typePropertiesBySlug: Record<string, TypeProperty>;
|
|
22
25
|
typePropertiesByLabel: Record<string, TypeProperty>;
|
|
26
|
+
typePathStateBySlug: Record<string, Map<string, {
|
|
27
|
+
isDisabled: boolean;
|
|
28
|
+
}>>;
|
|
23
29
|
};
|
|
24
30
|
export declare function getOptionSetsUsingTypePropertiesBySlug(reducedTypes: Partial<Type>[]): Record<string, TypePropertyWithLookups>;
|
|
25
|
-
export declare function convertFromLabelToSlug(csvJSON: any, typePropertiesBySlug: any, typePropertiesByLabel: any, convertedFields: any
|
|
31
|
+
export declare function convertFromLabelToSlug(csvJSON: any, typePropertiesBySlug: any, typePropertiesByLabel: any, convertedFields: any, typePathStateBySlug?: Record<string, Map<string, {
|
|
32
|
+
isDisabled: boolean;
|
|
33
|
+
}>>): ProcessingIssues[];
|
|
26
34
|
export declare function convertFromDisplayToValue(csvJSON: any[], optionSetUsingTypePropertiesBySlug: any, typePropertySlugs: string[], typePropertyLabels: string[]): ProcessingIssues[];
|
|
27
35
|
export declare function identifyKeysInProperties(csvJSON: any[], typePropertySlugs: string[], typePropertyLabels: string[]): {
|
|
28
36
|
keysInPropertySlugs: Map<string, boolean>;
|
|
@@ -64,7 +64,7 @@ exports.trimValues = trimValues;
|
|
|
64
64
|
const transformDataBasedOnTypes = function (csvJSON, config, typePropertyMaps, addWarnings = true) {
|
|
65
65
|
const rows = createCopy(csvJSON);
|
|
66
66
|
const { typePropertiesBySlug, typePropertiesByLabel, optionSetUsingTypePropertiesBySlug } = typePropertyMaps;
|
|
67
|
-
const labelWarnings = convertFromLabelToSlug(rows, typePropertiesBySlug, typePropertiesByLabel, getConvertedFields(config.federatedMappings));
|
|
67
|
+
const labelWarnings = convertFromLabelToSlug(rows, typePropertiesBySlug, typePropertiesByLabel, getConvertedFields(config.federatedMappings), typePropertyMaps.typePathStateBySlug);
|
|
68
68
|
const displayValueWarnings = convertFromDisplayToValue(rows, optionSetUsingTypePropertiesBySlug, Object.keys(typePropertiesBySlug), Object.keys(typePropertiesByLabel));
|
|
69
69
|
if (!addWarnings) {
|
|
70
70
|
return { rows, errors: [], warnings: [], generalErrors: [] };
|
|
@@ -100,15 +100,25 @@ exports.applyConditionalColumns = applyConditionalColumns;
|
|
|
100
100
|
function getPropertiesBySlugAndLabel(reducedTypes) {
|
|
101
101
|
const typePropertiesBySlug = {};
|
|
102
102
|
const typePropertiesByLabel = {};
|
|
103
|
+
const typePathStateBySlug = {};
|
|
103
104
|
for (const type of reducedTypes) {
|
|
104
|
-
if (type.typeProperties)
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
if (!type.typeProperties)
|
|
106
|
+
continue;
|
|
107
|
+
const typePath = type.typePath;
|
|
108
|
+
for (const typeProperty of type.typeProperties) {
|
|
109
|
+
const slug = typeProperty.slug.trim();
|
|
110
|
+
typePropertiesBySlug[slug] = typeProperty;
|
|
111
|
+
if (typeProperty.label) {
|
|
107
112
|
typePropertiesByLabel[typeProperty.label.trim()] = typeProperty;
|
|
108
113
|
}
|
|
114
|
+
if (typePath) {
|
|
115
|
+
if (!typePathStateBySlug[slug])
|
|
116
|
+
typePathStateBySlug[slug] = new Map();
|
|
117
|
+
typePathStateBySlug[slug].set(typePath, { isDisabled: !!typeProperty.isArchived });
|
|
118
|
+
}
|
|
109
119
|
}
|
|
110
120
|
}
|
|
111
|
-
return { typePropertiesBySlug, typePropertiesByLabel };
|
|
121
|
+
return { typePropertiesBySlug, typePropertiesByLabel, typePathStateBySlug };
|
|
112
122
|
}
|
|
113
123
|
exports.getPropertiesBySlugAndLabel = getPropertiesBySlugAndLabel;
|
|
114
124
|
function getOptionSetsUsingTypePropertiesBySlug(reducedTypes) {
|
|
@@ -146,16 +156,38 @@ function getConvertedFields(federatedMappings) {
|
|
|
146
156
|
}
|
|
147
157
|
return convertedFields;
|
|
148
158
|
}
|
|
149
|
-
function convertFromLabelToSlug(csvJSON, typePropertiesBySlug, typePropertiesByLabel, convertedFields) {
|
|
159
|
+
function convertFromLabelToSlug(csvJSON, typePropertiesBySlug, typePropertiesByLabel, convertedFields, typePathStateBySlug) {
|
|
150
160
|
const keysToBeIgnored = ['federatedId', 'typePath', 'itemFamilyFederatedId', 'itemOptionFederatedId', 'optionGroup'];
|
|
151
161
|
const keysWithoutProperties = new Set();
|
|
152
162
|
const archivedKeys = new Set();
|
|
163
|
+
const subtypeAwareArchivedSkips = new Map();
|
|
164
|
+
const useSubtypeAware = !!typePathStateBySlug;
|
|
153
165
|
const warnings = [];
|
|
154
166
|
for (const row of csvJSON) {
|
|
155
167
|
for (const key of Object.keys(row)) {
|
|
156
168
|
const slugProperty = typePropertiesBySlug[key];
|
|
157
169
|
const labelProperty = typePropertiesByLabel[key];
|
|
158
|
-
if (
|
|
170
|
+
if (useSubtypeAware) {
|
|
171
|
+
const resolvedSlug = slugProperty?.slug || labelProperty?.slug;
|
|
172
|
+
const rowTypePath = row.typePath;
|
|
173
|
+
if (resolvedSlug && rowTypePath && typePathStateBySlug[resolvedSlug]?.get(rowTypePath)?.isDisabled) {
|
|
174
|
+
const aggregateKey = `${resolvedSlug}|${rowTypePath}`;
|
|
175
|
+
const existing = subtypeAwareArchivedSkips.get(aggregateKey);
|
|
176
|
+
if (existing) {
|
|
177
|
+
existing.count++;
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
subtypeAwareArchivedSkips.set(aggregateKey, {
|
|
181
|
+
slug: resolvedSlug,
|
|
182
|
+
typePath: rowTypePath,
|
|
183
|
+
columnName: key,
|
|
184
|
+
count: 1,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else if (slugProperty?.isArchived || labelProperty?.isArchived) {
|
|
159
191
|
archivedKeys.add(key);
|
|
160
192
|
continue;
|
|
161
193
|
}
|
|
@@ -181,7 +213,20 @@ function convertFromLabelToSlug(csvJSON, typePropertiesBySlug, typePropertiesByL
|
|
|
181
213
|
}
|
|
182
214
|
}
|
|
183
215
|
}
|
|
184
|
-
if (
|
|
216
|
+
if (useSubtypeAware) {
|
|
217
|
+
if (subtypeAwareArchivedSkips.size > 0) {
|
|
218
|
+
warnings.push({
|
|
219
|
+
rowNumber: 1,
|
|
220
|
+
columnIssues: Array.from(subtypeAwareArchivedSkips.values()).map(({ slug, typePath, columnName, count }) => ({
|
|
221
|
+
columnName,
|
|
222
|
+
message: `Property "${slug}" is disabled for typePath "${typePath}". ${count} value(s) will not be processed.`,
|
|
223
|
+
issueType: entity_types_1.IssueType.HEADER_MISMATCH,
|
|
224
|
+
data: '',
|
|
225
|
+
})),
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
else if (archivedKeys.size > 0) {
|
|
185
230
|
warnings.push({
|
|
186
231
|
rowNumber: 1,
|
|
187
232
|
columnIssues: Array.from(archivedKeys).map((key) => ({
|
|
@@ -409,6 +409,110 @@ describe('Data Transformer Functions', () => {
|
|
|
409
409
|
expect(csvJSON[0]).toHaveProperty('size');
|
|
410
410
|
});
|
|
411
411
|
});
|
|
412
|
+
describe('subtype-aware archived handling', () => {
|
|
413
|
+
it('emits per-(slug, typePath) warnings for slug-headed columns archived on a subset of subtypes', () => {
|
|
414
|
+
const csvJSON = [
|
|
415
|
+
{ typePath: 'item:product', season: 'SS25' },
|
|
416
|
+
{ typePath: 'item:material', season: 'SS25' },
|
|
417
|
+
];
|
|
418
|
+
const typePropertiesBySlug = {
|
|
419
|
+
season: { slug: 'season', label: 'Season' },
|
|
420
|
+
};
|
|
421
|
+
const typePropertiesByLabel = {
|
|
422
|
+
Season: { slug: 'season', label: 'Season' },
|
|
423
|
+
};
|
|
424
|
+
const typePathStateBySlug = {
|
|
425
|
+
season: new Map([
|
|
426
|
+
['item:product', { isDisabled: false }],
|
|
427
|
+
['item:material', { isDisabled: true }],
|
|
428
|
+
]),
|
|
429
|
+
};
|
|
430
|
+
const warnings = (0, data_transformer_functions_1.convertFromLabelToSlug)(csvJSON, typePropertiesBySlug, typePropertiesByLabel, {}, typePathStateBySlug);
|
|
431
|
+
expect(csvJSON[0].season).toBe('SS25');
|
|
432
|
+
expect(warnings).toEqual([
|
|
433
|
+
{
|
|
434
|
+
rowNumber: 1,
|
|
435
|
+
columnIssues: [
|
|
436
|
+
{
|
|
437
|
+
columnName: 'season',
|
|
438
|
+
message: 'Property "season" is disabled for typePath "item:material". 1 value(s) will not be processed.',
|
|
439
|
+
issueType: entity_types_1.IssueType.HEADER_MISMATCH,
|
|
440
|
+
data: '',
|
|
441
|
+
},
|
|
442
|
+
],
|
|
443
|
+
},
|
|
444
|
+
]);
|
|
445
|
+
});
|
|
446
|
+
it('renames label-headed columns to slug only on rows whose typePath does not archive the property', () => {
|
|
447
|
+
const csvJSON = [
|
|
448
|
+
{ typePath: 'item:product', Season: 'SS25' },
|
|
449
|
+
{ typePath: 'item:material', Season: 'SS25' },
|
|
450
|
+
];
|
|
451
|
+
const typePropertiesBySlug = {
|
|
452
|
+
season: { slug: 'season', label: 'Season' },
|
|
453
|
+
};
|
|
454
|
+
const typePropertiesByLabel = {
|
|
455
|
+
Season: { slug: 'season', label: 'Season' },
|
|
456
|
+
};
|
|
457
|
+
const typePathStateBySlug = {
|
|
458
|
+
season: new Map([
|
|
459
|
+
['item:product', { isDisabled: false }],
|
|
460
|
+
['item:material', { isDisabled: true }],
|
|
461
|
+
]),
|
|
462
|
+
};
|
|
463
|
+
(0, data_transformer_functions_1.convertFromLabelToSlug)(csvJSON, typePropertiesBySlug, typePropertiesByLabel, {}, typePathStateBySlug);
|
|
464
|
+
expect(csvJSON[0].season).toBe('SS25');
|
|
465
|
+
expect(csvJSON[1]).not.toHaveProperty('season');
|
|
466
|
+
});
|
|
467
|
+
it('emits a single aggregate warning grouping skips by (slug, typePath)', () => {
|
|
468
|
+
const csvJSON = [
|
|
469
|
+
{ typePath: 'item:material', season: 'SS25' },
|
|
470
|
+
{ typePath: 'item:material', season: 'FW25' },
|
|
471
|
+
{ typePath: 'item:cement', season: 'SS25' },
|
|
472
|
+
];
|
|
473
|
+
const typePropertiesBySlug = {
|
|
474
|
+
season: { slug: 'season', label: 'Season' },
|
|
475
|
+
};
|
|
476
|
+
const typePathStateBySlug = {
|
|
477
|
+
season: new Map([
|
|
478
|
+
['item:material', { isDisabled: true }],
|
|
479
|
+
['item:cement', { isDisabled: true }],
|
|
480
|
+
]),
|
|
481
|
+
};
|
|
482
|
+
const warnings = (0, data_transformer_functions_1.convertFromLabelToSlug)(csvJSON, typePropertiesBySlug, {}, {}, typePathStateBySlug);
|
|
483
|
+
expect(warnings).toHaveLength(1);
|
|
484
|
+
expect(warnings[0].columnIssues).toHaveLength(2);
|
|
485
|
+
expect(warnings[0].columnIssues).toEqual(expect.arrayContaining([
|
|
486
|
+
{
|
|
487
|
+
columnName: 'season',
|
|
488
|
+
message: 'Property "season" is disabled for typePath "item:material". 2 value(s) will not be processed.',
|
|
489
|
+
issueType: entity_types_1.IssueType.HEADER_MISMATCH,
|
|
490
|
+
data: '',
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
columnName: 'season',
|
|
494
|
+
message: 'Property "season" is disabled for typePath "item:cement". 1 value(s) will not be processed.',
|
|
495
|
+
issueType: entity_types_1.IssueType.HEADER_MISMATCH,
|
|
496
|
+
data: '',
|
|
497
|
+
},
|
|
498
|
+
]));
|
|
499
|
+
});
|
|
500
|
+
it('does not emit any archived warning when no row targets an archived subtype', () => {
|
|
501
|
+
const csvJSON = [{ typePath: 'item:product', season: 'SS25' }];
|
|
502
|
+
const typePropertiesBySlug = {
|
|
503
|
+
season: { slug: 'season', label: 'Season' },
|
|
504
|
+
};
|
|
505
|
+
const typePathStateBySlug = {
|
|
506
|
+
season: new Map([
|
|
507
|
+
['item:product', { isDisabled: false }],
|
|
508
|
+
['item:material', { isDisabled: true }],
|
|
509
|
+
]),
|
|
510
|
+
};
|
|
511
|
+
const warnings = (0, data_transformer_functions_1.convertFromLabelToSlug)(csvJSON, typePropertiesBySlug, {}, {}, typePathStateBySlug);
|
|
512
|
+
expect(warnings).toEqual([]);
|
|
513
|
+
expect(csvJSON[0].season).toBe('SS25');
|
|
514
|
+
});
|
|
515
|
+
});
|
|
412
516
|
});
|
|
413
517
|
describe('convertFromDisplayToValue', () => {
|
|
414
518
|
it('should convert display values to optionSet values', () => {
|
|
@@ -633,6 +737,30 @@ describe('Data Transformer Functions', () => {
|
|
|
633
737
|
]);
|
|
634
738
|
});
|
|
635
739
|
});
|
|
740
|
+
describe('getPropertiesBySlugAndLabel archive tracking', () => {
|
|
741
|
+
it('records per-typePath archive state per slug', () => {
|
|
742
|
+
const reducedTypes = [
|
|
743
|
+
{
|
|
744
|
+
typePath: 'item:product',
|
|
745
|
+
typeProperties: [
|
|
746
|
+
{ slug: 'season', label: 'Season', isArchived: false },
|
|
747
|
+
{ slug: 'sku', label: 'SKU', isArchived: false },
|
|
748
|
+
],
|
|
749
|
+
},
|
|
750
|
+
{
|
|
751
|
+
typePath: 'item:material',
|
|
752
|
+
typeProperties: [
|
|
753
|
+
{ slug: 'season', label: 'Season', isArchived: true },
|
|
754
|
+
],
|
|
755
|
+
},
|
|
756
|
+
];
|
|
757
|
+
const result = (0, data_transformer_functions_1.getPropertiesBySlugAndLabel)(reducedTypes);
|
|
758
|
+
expect(result.typePathStateBySlug.season.get('item:product')).toEqual({ isDisabled: false });
|
|
759
|
+
expect(result.typePathStateBySlug.season.get('item:material')).toEqual({ isDisabled: true });
|
|
760
|
+
expect(result.typePathStateBySlug.sku.get('item:product')).toEqual({ isDisabled: false });
|
|
761
|
+
expect(result.typePathStateBySlug.sku.has('item:material')).toBe(false);
|
|
762
|
+
});
|
|
763
|
+
});
|
|
636
764
|
describe('getReducedTypes', () => {
|
|
637
765
|
it('should get reduced types', async () => {
|
|
638
766
|
mockClient.get.mockResolvedValueOnce([
|
|
@@ -5,6 +5,7 @@ const util_1 = require("@contrail/util");
|
|
|
5
5
|
const types_1 = require("@contrail/types");
|
|
6
6
|
const entity_types_1 = require("@contrail/entity-types");
|
|
7
7
|
const lodash_1 = require("lodash");
|
|
8
|
+
const chunk_by_url_length_1 = require("./chunk-by-url-length");
|
|
8
9
|
const pLimit = require('p-limit');
|
|
9
10
|
var EntitiesLambdaCallType;
|
|
10
11
|
(function (EntitiesLambdaCallType) {
|
|
@@ -181,7 +182,7 @@ async function assignObjectReferences(client, typePaths, typesByPath, incomingEn
|
|
|
181
182
|
const allORByFedId = {};
|
|
182
183
|
for (const rootType of Object.keys(allORFedIdsByType)) {
|
|
183
184
|
const fedIds = [...new Set(allORFedIdsByType[rootType])];
|
|
184
|
-
const chunks = (0,
|
|
185
|
+
const chunks = (0, chunk_by_url_length_1.chunkByUrlLength)(fedIds);
|
|
185
186
|
const federatedPromises = [];
|
|
186
187
|
for (const chunk of chunks) {
|
|
187
188
|
if (!chunk?.length) {
|
|
@@ -21,6 +21,8 @@ export declare const ITEM_PROCESSOR_ERRORS: {
|
|
|
21
21
|
readonly FAMILY_ID_EMPTY: "itemFamilyFederatedId is null or undefined or empty in the CSV file";
|
|
22
22
|
readonly CONFLICTING_IDS: (ids: any) => string;
|
|
23
23
|
};
|
|
24
|
+
export declare const FEDERATED_ID_FETCH_MAX_URL_BYTES = 4000;
|
|
25
|
+
export declare const FEDERATED_ID_FETCH_MAX_COUNT = 200;
|
|
24
26
|
export declare const ITEM_PROCESSOR_WARNINGS: {
|
|
25
27
|
readonly OPTIONNAME_UNMAPPED: "\"optionName\" is not found in the federatedMapping config, conditionalColumns config, or as a header in the CSV file.";
|
|
26
28
|
readonly OPTIONNAME_NOT_FOUND: (optionName: string) => string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ITEM_PROCESSOR_WARNINGS = exports.ITEM_PROCESSOR_ERRORS = exports.ENTITY_PROCESSOR_ERRORS = exports.CUSTOM_ENTITY_PROCESSOR_ERRORS = exports.COLOR_PROCESSOR_WARNINGS = 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
|
};
|
|
@@ -24,6 +24,8 @@ exports.ITEM_PROCESSOR_ERRORS = {
|
|
|
24
24
|
FAMILY_ID_EMPTY: 'itemFamilyFederatedId is null or undefined or empty in the CSV file',
|
|
25
25
|
CONFLICTING_IDS: (ids) => `Found conflicting family federated Ids: ${ids}`,
|
|
26
26
|
};
|
|
27
|
+
exports.FEDERATED_ID_FETCH_MAX_URL_BYTES = 4000;
|
|
28
|
+
exports.FEDERATED_ID_FETCH_MAX_COUNT = 200;
|
|
27
29
|
exports.ITEM_PROCESSOR_WARNINGS = {
|
|
28
30
|
OPTIONNAME_UNMAPPED: '"optionName" is not found in the federatedMapping config, conditionalColumns config, or as a header in the CSV file.',
|
|
29
31
|
OPTIONNAME_NOT_FOUND: (optionName) => `"${optionName}" column not found in the CSV file`,
|