@cyberismo/data-handler 0.0.17 → 0.0.19
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/dist/command-handler.d.ts +2 -0
- package/dist/command-handler.js +26 -2
- package/dist/command-handler.js.map +1 -1
- package/dist/command-manager.d.ts +2 -0
- package/dist/command-manager.js +3 -0
- package/dist/command-manager.js.map +1 -1
- package/dist/commands/create.d.ts +3 -1
- package/dist/commands/create.js +10 -1
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/migrate.d.ts +33 -0
- package/dist/commands/migrate.js +66 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/containers/project/card-cache.js +13 -1
- package/dist/containers/project/card-cache.js.map +1 -1
- package/dist/containers/project/project-paths.d.ts +1 -0
- package/dist/containers/project/project-paths.js +5 -2
- package/dist/containers/project/project-paths.js.map +1 -1
- package/dist/containers/project.d.ts +10 -0
- package/dist/containers/project.js +39 -2
- package/dist/containers/project.js.map +1 -1
- package/dist/containers/template.js +2 -0
- package/dist/containers/template.js.map +1 -1
- package/dist/interfaces/command-options.d.ts +6 -1
- package/dist/interfaces/project-interfaces.d.ts +4 -0
- package/dist/interfaces/project-interfaces.js.map +1 -1
- package/dist/migrations/index.d.ts +14 -0
- package/dist/migrations/index.js +14 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/migrations/migration-executor.d.ts +79 -0
- package/dist/migrations/migration-executor.js +312 -0
- package/dist/migrations/migration-executor.js.map +1 -0
- package/dist/migrations/migration-worker.d.ts +13 -0
- package/dist/migrations/migration-worker.js +156 -0
- package/dist/migrations/migration-worker.js.map +1 -0
- package/dist/migrations/worker-executor.d.ts +24 -0
- package/dist/migrations/worker-executor.js +157 -0
- package/dist/migrations/worker-executor.js.map +1 -0
- package/dist/project-settings.d.ts +2 -0
- package/dist/project-settings.js +7 -0
- package/dist/project-settings.js.map +1 -1
- package/dist/resources/calculation-resource.d.ts +9 -0
- package/dist/resources/calculation-resource.js +13 -2
- package/dist/resources/calculation-resource.js.map +1 -1
- package/dist/resources/card-type-resource.d.ts +12 -3
- package/dist/resources/card-type-resource.js +73 -91
- package/dist/resources/card-type-resource.js.map +1 -1
- package/dist/resources/field-type-resource.d.ts +10 -1
- package/dist/resources/field-type-resource.js +62 -61
- package/dist/resources/field-type-resource.js.map +1 -1
- package/dist/resources/file-resource.d.ts +27 -2
- package/dist/resources/file-resource.js +46 -8
- package/dist/resources/file-resource.js.map +1 -1
- package/dist/resources/graph-model-resource.d.ts +5 -0
- package/dist/resources/graph-model-resource.js +6 -0
- package/dist/resources/graph-model-resource.js.map +1 -1
- package/dist/resources/graph-view-resource.d.ts +5 -0
- package/dist/resources/graph-view-resource.js +6 -0
- package/dist/resources/graph-view-resource.js.map +1 -1
- package/dist/resources/link-type-resource.d.ts +11 -1
- package/dist/resources/link-type-resource.js +54 -30
- package/dist/resources/link-type-resource.js.map +1 -1
- package/dist/resources/report-resource.d.ts +6 -1
- package/dist/resources/report-resource.js +7 -1
- package/dist/resources/report-resource.js.map +1 -1
- package/dist/resources/resource-object.d.ts +22 -7
- package/dist/resources/resource-object.js +44 -15
- package/dist/resources/resource-object.js.map +1 -1
- package/dist/resources/template-resource.d.ts +5 -1
- package/dist/resources/template-resource.js +11 -27
- package/dist/resources/template-resource.js.map +1 -1
- package/dist/resources/workflow-resource.d.ts +7 -3
- package/dist/resources/workflow-resource.js +90 -82
- package/dist/resources/workflow-resource.js.map +1 -1
- package/dist/utils/card-utils.d.ts +1 -1
- package/dist/utils/common-utils.d.ts +8 -0
- package/dist/utils/common-utils.js +14 -0
- package/dist/utils/common-utils.js.map +1 -1
- package/dist/utils/file-utils.d.ts +15 -3
- package/dist/utils/file-utils.js +48 -9
- package/dist/utils/file-utils.js.map +1 -1
- package/dist/utils/json.js +2 -2
- package/dist/utils/json.js.map +1 -1
- package/package.json +5 -3
- package/src/command-handler.ts +38 -1
- package/src/command-manager.ts +3 -0
- package/src/commands/create.ts +11 -0
- package/src/commands/migrate.ts +88 -0
- package/src/containers/project/card-cache.ts +18 -1
- package/src/containers/project/project-paths.ts +6 -2
- package/src/containers/project.ts +66 -1
- package/src/containers/template.ts +5 -0
- package/src/interfaces/command-options.ts +8 -0
- package/src/interfaces/project-interfaces.ts +4 -0
- package/src/migrations/index.ts +20 -0
- package/src/migrations/migration-executor.ts +478 -0
- package/src/migrations/migration-worker.ts +190 -0
- package/src/migrations/worker-executor.ts +185 -0
- package/src/project-settings.ts +7 -0
- package/src/resources/calculation-resource.ts +13 -2
- package/src/resources/card-type-resource.ts +101 -114
- package/src/resources/field-type-resource.ts +78 -71
- package/src/resources/file-resource.ts +68 -9
- package/src/resources/graph-model-resource.ts +6 -0
- package/src/resources/graph-view-resource.ts +6 -0
- package/src/resources/link-type-resource.ts +66 -36
- package/src/resources/report-resource.ts +7 -1
- package/src/resources/resource-object.ts +57 -18
- package/src/resources/template-resource.ts +12 -27
- package/src/resources/workflow-resource.ts +119 -100
- package/src/utils/common-utils.ts +15 -0
- package/src/utils/file-utils.ts +56 -12
- package/src/utils/json.ts +2 -6
|
@@ -50,6 +50,11 @@ export class FieldTypeResource extends FileResource<FieldType> {
|
|
|
50
50
|
private fromType: DataType = 'integer';
|
|
51
51
|
private toType: DataType = 'integer';
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Creates an instance of FieldTypeResource
|
|
55
|
+
* @param project Project to use
|
|
56
|
+
* @param name Resource name
|
|
57
|
+
*/
|
|
53
58
|
constructor(project: Project, name: ResourceName) {
|
|
54
59
|
super(project, name, 'fieldTypes');
|
|
55
60
|
|
|
@@ -206,7 +211,12 @@ export class FieldTypeResource extends FileResource<FieldType> {
|
|
|
206
211
|
const removedValue = (op.target as EnumDefinition).enumValue;
|
|
207
212
|
const cardTypes = this.relevantCardTypes();
|
|
208
213
|
const allCards = await Promise.all(
|
|
209
|
-
cardTypes.map((cardType) =>
|
|
214
|
+
cardTypes.map((cardType) =>
|
|
215
|
+
this.collectCards(
|
|
216
|
+
cardType,
|
|
217
|
+
(card, cardTypeName) => card.metadata?.cardType === cardTypeName,
|
|
218
|
+
),
|
|
219
|
+
),
|
|
210
220
|
);
|
|
211
221
|
const cardsToUpdate = allCards
|
|
212
222
|
.flat()
|
|
@@ -223,15 +233,6 @@ export class FieldTypeResource extends FileResource<FieldType> {
|
|
|
223
233
|
);
|
|
224
234
|
}
|
|
225
235
|
|
|
226
|
-
// When resource name changes.
|
|
227
|
-
private async handleNameChange(existingName: string) {
|
|
228
|
-
await Promise.all([
|
|
229
|
-
super.updateHandleBars(existingName, this.content.name),
|
|
230
|
-
super.updateCalculations(existingName, this.content.name),
|
|
231
|
-
]);
|
|
232
|
-
await this.write();
|
|
233
|
-
}
|
|
234
|
-
|
|
235
236
|
// Checks if value 'from' can be converted 'to' value.
|
|
236
237
|
private isConversionValid(from: DataType, to: DataType) {
|
|
237
238
|
// Set helpers to avoid dragging 'Operation' object everywhere.
|
|
@@ -284,6 +285,20 @@ export class FieldTypeResource extends FileResource<FieldType> {
|
|
|
284
285
|
}
|
|
285
286
|
}
|
|
286
287
|
|
|
288
|
+
/**
|
|
289
|
+
* When resource name changes.
|
|
290
|
+
* @param existingName Current resource name.
|
|
291
|
+
*/
|
|
292
|
+
protected async onNameChange(existingName: string) {
|
|
293
|
+
await Promise.all([
|
|
294
|
+
super.updateHandleBars(existingName, this.content.name),
|
|
295
|
+
super.updateCalculations(existingName, this.content.name),
|
|
296
|
+
super.updateCardContentReferences(existingName, this.content.name),
|
|
297
|
+
this.updateCardTypes(existingName),
|
|
298
|
+
]);
|
|
299
|
+
await this.write();
|
|
300
|
+
}
|
|
301
|
+
|
|
287
302
|
/**
|
|
288
303
|
* Creates a new field type object. Base class writes the object to disk automatically.
|
|
289
304
|
* @param dataType Type for the new field type.
|
|
@@ -310,16 +325,16 @@ export class FieldTypeResource extends FileResource<FieldType> {
|
|
|
310
325
|
*/
|
|
311
326
|
public static fieldDataTypes(): DataType[] {
|
|
312
327
|
return [
|
|
313
|
-
'shortText',
|
|
314
|
-
'longText',
|
|
315
|
-
'number',
|
|
316
|
-
'integer',
|
|
317
328
|
'boolean',
|
|
318
|
-
'enum',
|
|
319
|
-
'list',
|
|
320
329
|
'date',
|
|
321
330
|
'dateTime',
|
|
331
|
+
'enum',
|
|
332
|
+
'integer',
|
|
333
|
+
'list',
|
|
334
|
+
'longText',
|
|
335
|
+
'number',
|
|
322
336
|
'person',
|
|
337
|
+
'shortText',
|
|
323
338
|
];
|
|
324
339
|
}
|
|
325
340
|
|
|
@@ -372,7 +387,7 @@ export class FieldTypeResource extends FileResource<FieldType> {
|
|
|
372
387
|
public async rename(newName: ResourceName) {
|
|
373
388
|
const existingName = this.content.name;
|
|
374
389
|
await super.rename(newName);
|
|
375
|
-
return this.
|
|
390
|
+
return this.onNameChange(existingName);
|
|
376
391
|
}
|
|
377
392
|
|
|
378
393
|
/**
|
|
@@ -389,64 +404,56 @@ export class FieldTypeResource extends FileResource<FieldType> {
|
|
|
389
404
|
op: Operation<Type>,
|
|
390
405
|
) {
|
|
391
406
|
const { key } = updateKey;
|
|
392
|
-
const nameChange = key === 'name';
|
|
393
|
-
const typeChange = key === 'dataType';
|
|
394
|
-
const enumChange = key === 'enumValues';
|
|
395
|
-
const existingName = this.content.name;
|
|
396
|
-
const existingType = this.content.dataType;
|
|
397
|
-
|
|
398
|
-
await super.update(updateKey, op);
|
|
399
407
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
content.name = super.handleScalar(op) as string;
|
|
403
|
-
} else if (key === 'dataType') {
|
|
404
|
-
const toType = op as ChangeOperation<DataType>;
|
|
405
|
-
if (!FieldTypeResource.fieldDataTypes().includes(toType.to)) {
|
|
406
|
-
throw new Error(
|
|
407
|
-
`Cannot change '${key}' to unknown type '${toType.to}'`,
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
if (existingType === toType.to) {
|
|
411
|
-
throw new Error(`'${key}' is already '${toType.to}'`);
|
|
412
|
-
}
|
|
413
|
-
if (!this.isConversionValid(content.dataType, toType.to)) {
|
|
414
|
-
throw new Error(
|
|
415
|
-
`Cannot change data type from '${content.dataType}' to '${toType.to}'`,
|
|
416
|
-
);
|
|
417
|
-
}
|
|
418
|
-
content.dataType = super.handleScalar(op) as DataType;
|
|
419
|
-
} else if (key === 'displayName') {
|
|
420
|
-
content.displayName = super.handleScalar(op) as string;
|
|
421
|
-
} else if (key === 'enumValues') {
|
|
422
|
-
if (op.name === 'add' || op.name === 'change' || op.name === 'remove') {
|
|
423
|
-
const existingValue = this.enumValueExists<EnumDefinition>(
|
|
424
|
-
op as Operation<EnumDefinition>,
|
|
425
|
-
content.enumValues as EnumDefinition[],
|
|
426
|
-
) as Type;
|
|
427
|
-
op.target = existingValue ?? op.target;
|
|
428
|
-
}
|
|
429
|
-
content.enumValues = super.handleArray(
|
|
430
|
-
op,
|
|
431
|
-
key,
|
|
432
|
-
content.enumValues as Type[],
|
|
433
|
-
) as EnumDefinition[];
|
|
434
|
-
} else if (key === 'description') {
|
|
435
|
-
content.description = super.handleScalar(op) as string;
|
|
408
|
+
if (key === 'name' || key === 'description' || key === 'displayName') {
|
|
409
|
+
await super.update(updateKey, op);
|
|
436
410
|
} else {
|
|
437
|
-
|
|
438
|
-
|
|
411
|
+
const content = structuredClone(this.content);
|
|
412
|
+
const typeChange = key === 'dataType';
|
|
413
|
+
const enumChange = key === 'enumValues';
|
|
414
|
+
const existingType = this.content.dataType;
|
|
415
|
+
if (key === 'name') {
|
|
416
|
+
content.name = super.handleScalar(op) as string;
|
|
417
|
+
} else if (key === 'dataType') {
|
|
418
|
+
const toType = op as ChangeOperation<DataType>;
|
|
419
|
+
if (!FieldTypeResource.fieldDataTypes().includes(toType.to)) {
|
|
420
|
+
throw new Error(
|
|
421
|
+
`Cannot change '${key}' to unknown type '${toType.to}'`,
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
if (existingType === toType.to) {
|
|
425
|
+
throw new Error(`'${key}' is already '${toType.to}'`);
|
|
426
|
+
}
|
|
427
|
+
if (!this.isConversionValid(content.dataType, toType.to)) {
|
|
428
|
+
throw new Error(
|
|
429
|
+
`Cannot change data type from '${content.dataType}' to '${toType.to}'`,
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
content.dataType = super.handleScalar(op) as DataType;
|
|
433
|
+
} else if (key === 'enumValues') {
|
|
434
|
+
if (op.name === 'add' || op.name === 'change' || op.name === 'remove') {
|
|
435
|
+
const existingValue = this.enumValueExists<EnumDefinition>(
|
|
436
|
+
op as Operation<EnumDefinition>,
|
|
437
|
+
content.enumValues as EnumDefinition[],
|
|
438
|
+
) as Type;
|
|
439
|
+
op.target = existingValue ?? op.target;
|
|
440
|
+
}
|
|
441
|
+
content.enumValues = super.handleArray(
|
|
442
|
+
op,
|
|
443
|
+
key,
|
|
444
|
+
content.enumValues as Type[],
|
|
445
|
+
) as EnumDefinition[];
|
|
446
|
+
} else {
|
|
447
|
+
throw new Error(`Unknown property '${key}' for FieldType`);
|
|
448
|
+
}
|
|
439
449
|
|
|
440
|
-
|
|
450
|
+
await super.postUpdate(content, updateKey, op);
|
|
441
451
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
await this.dataTypeChanged();
|
|
448
|
-
} else if (enumChange && op.name === 'remove') {
|
|
449
|
-
await this.handleEnumValueReplacements(op);
|
|
452
|
+
if (typeChange) {
|
|
453
|
+
await this.dataTypeChanged();
|
|
454
|
+
} else if (enumChange && op.name === 'remove') {
|
|
455
|
+
await this.handleEnumValueReplacements(op);
|
|
456
|
+
}
|
|
450
457
|
}
|
|
451
458
|
}
|
|
452
459
|
|
|
@@ -22,7 +22,8 @@ import type {
|
|
|
22
22
|
import type { Project } from '../containers/project.js';
|
|
23
23
|
import type { ResourceBaseMetadata } from '../interfaces/resource-interfaces.js';
|
|
24
24
|
import type { ResourceName } from '../utils/resource-utils.js';
|
|
25
|
-
import type { ShowReturnType } from './resource-object.js';
|
|
25
|
+
import type { Operation, ShowReturnType } from './resource-object.js';
|
|
26
|
+
import type { UpdateKey } from '../interfaces/resource-interfaces.js';
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Base class for file based resources (card types, field types, link types, workflows, ...)
|
|
@@ -34,23 +35,35 @@ export abstract class FileResource<
|
|
|
34
35
|
super(project, name, type);
|
|
35
36
|
this.initialize();
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Collects cards that match the given filter function.
|
|
40
|
+
* @param resourceName The resource name to filter by
|
|
41
|
+
* @param filterFn Function that returns true for cards to include
|
|
42
|
+
* @returns Array of cards that match the filter
|
|
43
|
+
*/
|
|
44
|
+
protected async collectCards(
|
|
45
|
+
resourceName: string,
|
|
46
|
+
filterFn: (card: Card, resourceName: string) => boolean,
|
|
47
|
+
): Promise<Card[]> {
|
|
48
|
+
function filteredCards(
|
|
49
|
+
cardSource: Card[],
|
|
50
|
+
resourceName: string,
|
|
51
|
+
filterFn: (card: Card, resourceName: string) => boolean,
|
|
52
|
+
): Card[] {
|
|
53
|
+
return cardSource.filter((card) => filterFn(card, resourceName));
|
|
42
54
|
}
|
|
43
55
|
|
|
44
56
|
// Collect both project cards ...
|
|
45
57
|
const projectCards = filteredCards(
|
|
46
58
|
this.project.cards(this.project.paths.cardRootFolder),
|
|
47
|
-
|
|
59
|
+
resourceName,
|
|
60
|
+
filterFn,
|
|
48
61
|
);
|
|
49
62
|
// ... and cards from each template that would be affected.
|
|
50
63
|
const templates = this.project.resources.templates(ResourcesFrom.localOnly);
|
|
51
64
|
const templateCards = templates.map((template) => {
|
|
52
65
|
const templateObject = template.templateObject();
|
|
53
|
-
return filteredCards(templateObject.cards(),
|
|
66
|
+
return filteredCards(templateObject.cards(), resourceName, filterFn);
|
|
54
67
|
});
|
|
55
68
|
// Return all affected cards
|
|
56
69
|
const cards = [projectCards, ...templateCards].reduce(
|
|
@@ -59,7 +72,19 @@ export abstract class FileResource<
|
|
|
59
72
|
);
|
|
60
73
|
return cards;
|
|
61
74
|
}
|
|
62
|
-
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* For handling name changes.
|
|
78
|
+
* @param previousName The previous name before the change
|
|
79
|
+
*/
|
|
80
|
+
protected abstract onNameChange?(previousName: string): Promise<void>;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Updates resource key to a new prefix
|
|
84
|
+
* @param name Resource name
|
|
85
|
+
* @param prefixes list of prefixes in the project
|
|
86
|
+
* @returns updated resource name
|
|
87
|
+
*/
|
|
63
88
|
protected updatePrefixInResourceName(name: string, prefixes: string[]) {
|
|
64
89
|
const { identifier, prefix, type } = resourceName(name);
|
|
65
90
|
if (this.moduleResource) {
|
|
@@ -70,6 +95,40 @@ export abstract class FileResource<
|
|
|
70
95
|
: name;
|
|
71
96
|
}
|
|
72
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Updates resource.
|
|
100
|
+
* @param updateKey Key to modify
|
|
101
|
+
* @param op Operation to perform on 'key'
|
|
102
|
+
* @throws if key is unknown.
|
|
103
|
+
*/
|
|
104
|
+
public async update<Type, K extends string>(
|
|
105
|
+
updateKey: UpdateKey<K>,
|
|
106
|
+
op: Operation<Type>,
|
|
107
|
+
) {
|
|
108
|
+
const { key } = updateKey;
|
|
109
|
+
|
|
110
|
+
const nameChange = key === 'name';
|
|
111
|
+
const existingName = this.content.name;
|
|
112
|
+
await super.update(updateKey, op);
|
|
113
|
+
const content = structuredClone(this.content);
|
|
114
|
+
|
|
115
|
+
if (key === 'name') {
|
|
116
|
+
content.name = super.handleScalar(op) as string;
|
|
117
|
+
} else if (key === 'displayName') {
|
|
118
|
+
content.displayName = super.handleScalar(op) as string;
|
|
119
|
+
} else if (key === 'description') {
|
|
120
|
+
content.description = super.handleScalar(op) as string;
|
|
121
|
+
} else {
|
|
122
|
+
throw new Error(`Unknown property '${key}' for folder resource`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
await super.postUpdate(content, updateKey, op);
|
|
126
|
+
|
|
127
|
+
if (nameChange) {
|
|
128
|
+
await this.onNameChange?.(existingName);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
73
132
|
/**
|
|
74
133
|
* Returns the resource metadata content.
|
|
75
134
|
* @returns metadata content
|
|
@@ -38,6 +38,11 @@ export class GraphModelResource extends FolderResource<
|
|
|
38
38
|
GraphModelMetadata,
|
|
39
39
|
GraphModelContent
|
|
40
40
|
> {
|
|
41
|
+
/**
|
|
42
|
+
* Creates an instance of GraphModelResource
|
|
43
|
+
* @param project Project to use
|
|
44
|
+
* @param name Resource name
|
|
45
|
+
*/
|
|
41
46
|
constructor(project: Project, name: ResourceName) {
|
|
42
47
|
super(project, name, 'graphModels');
|
|
43
48
|
|
|
@@ -55,6 +60,7 @@ export class GraphModelResource extends FolderResource<
|
|
|
55
60
|
join(this.internalFolder, CONTENT_FILES.model),
|
|
56
61
|
]),
|
|
57
62
|
super.updateCalculations(existingName, this.content.name),
|
|
63
|
+
super.updateCardContentReferences(existingName, this.content.name),
|
|
58
64
|
]);
|
|
59
65
|
// Finally, write updated content.
|
|
60
66
|
await this.write();
|
|
@@ -39,6 +39,11 @@ export class GraphViewResource extends FolderResource<
|
|
|
39
39
|
GraphViewMetadata,
|
|
40
40
|
GraphViewContent
|
|
41
41
|
> {
|
|
42
|
+
/**
|
|
43
|
+
* Creates instance of GraphViewResource
|
|
44
|
+
* @param project Project to use
|
|
45
|
+
* @param name Resource name
|
|
46
|
+
*/
|
|
42
47
|
constructor(project: Project, name: ResourceName) {
|
|
43
48
|
super(project, name, 'graphViews');
|
|
44
49
|
|
|
@@ -56,6 +61,7 @@ export class GraphViewResource extends FolderResource<
|
|
|
56
61
|
await this.handleBarFile(),
|
|
57
62
|
]),
|
|
58
63
|
super.updateCalculations(existingName, this.content.name),
|
|
64
|
+
super.updateCardContentReferences(existingName, this.content.name),
|
|
59
65
|
]);
|
|
60
66
|
await this.write();
|
|
61
67
|
}
|
|
@@ -25,6 +25,11 @@ import type { ResourceName } from '../utils/resource-utils.js';
|
|
|
25
25
|
* Link Type resource class.
|
|
26
26
|
*/
|
|
27
27
|
export class LinkTypeResource extends FileResource<LinkType> {
|
|
28
|
+
/**
|
|
29
|
+
* Creates instance of LinkTypeResource
|
|
30
|
+
* @param project Project to use
|
|
31
|
+
* @param name Resource name
|
|
32
|
+
*/
|
|
28
33
|
constructor(project: Project, name: ResourceName) {
|
|
29
34
|
super(project, name, 'linkTypes');
|
|
30
35
|
|
|
@@ -32,8 +37,38 @@ export class LinkTypeResource extends FileResource<LinkType> {
|
|
|
32
37
|
this.contentSchema = super.contentSchemaContent(this.contentSchemaId);
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
//
|
|
36
|
-
private async
|
|
40
|
+
// Update card metadata links when link type is renamed
|
|
41
|
+
private async updateCardLinks(from: string, to: string) {
|
|
42
|
+
const cards = await this.collectCards(
|
|
43
|
+
from,
|
|
44
|
+
(card, linkTypeName) =>
|
|
45
|
+
card.metadata?.links?.some((link) => link.linkType === linkTypeName) ??
|
|
46
|
+
false,
|
|
47
|
+
);
|
|
48
|
+
if (cards.length === 0) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
await Promise.all(
|
|
53
|
+
cards.map(async (card) => {
|
|
54
|
+
if (card.metadata?.links) {
|
|
55
|
+
card.metadata.links = card.metadata.links.map((link) => {
|
|
56
|
+
if (link.linkType === from) {
|
|
57
|
+
return { ...link, linkType: to };
|
|
58
|
+
}
|
|
59
|
+
return link;
|
|
60
|
+
});
|
|
61
|
+
await this.project.updateCardMetadata(card, card.metadata);
|
|
62
|
+
}
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* When resource name changes.
|
|
69
|
+
* @param existingName Current resource name.
|
|
70
|
+
*/
|
|
71
|
+
protected async onNameChange(existingName: string) {
|
|
37
72
|
const current = this.content;
|
|
38
73
|
const prefixes = this.project.projectPrefixes();
|
|
39
74
|
if (current.sourceCardTypes) {
|
|
@@ -49,6 +84,8 @@ export class LinkTypeResource extends FileResource<LinkType> {
|
|
|
49
84
|
await Promise.all([
|
|
50
85
|
super.updateHandleBars(existingName, this.content.name),
|
|
51
86
|
super.updateCalculations(existingName, this.content.name),
|
|
87
|
+
super.updateCardContentReferences(existingName, this.content.name),
|
|
88
|
+
this.updateCardLinks(existingName, this.content.name),
|
|
52
89
|
]);
|
|
53
90
|
// Finally, write updated content.
|
|
54
91
|
await this.write();
|
|
@@ -76,7 +113,7 @@ export class LinkTypeResource extends FileResource<LinkType> {
|
|
|
76
113
|
public async rename(newName: ResourceName) {
|
|
77
114
|
const existingName = this.content.name;
|
|
78
115
|
await super.rename(newName);
|
|
79
|
-
return this.
|
|
116
|
+
return this.onNameChange(existingName);
|
|
80
117
|
}
|
|
81
118
|
|
|
82
119
|
/**
|
|
@@ -89,41 +126,34 @@ export class LinkTypeResource extends FileResource<LinkType> {
|
|
|
89
126
|
op: Operation<Type>,
|
|
90
127
|
) {
|
|
91
128
|
const { key } = updateKey;
|
|
92
|
-
const nameChange = key === 'name';
|
|
93
|
-
const existingName = this.content.name;
|
|
94
129
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const content = structuredClone(this.content);
|
|
98
|
-
if (key === 'name') {
|
|
99
|
-
content.name = super.handleScalar(op) as string;
|
|
100
|
-
} else if (key === 'destinationCardTypes') {
|
|
101
|
-
content.destinationCardTypes = super.handleArray(
|
|
102
|
-
op,
|
|
103
|
-
key,
|
|
104
|
-
content.destinationCardTypes as Type[],
|
|
105
|
-
) as string[];
|
|
106
|
-
} else if (key === 'enableLinkDescription') {
|
|
107
|
-
content.enableLinkDescription = super.handleScalar(op) as boolean;
|
|
108
|
-
} else if (key === 'inboundDisplayName') {
|
|
109
|
-
content.inboundDisplayName = super.handleScalar(op) as string;
|
|
110
|
-
} else if (key === 'outboundDisplayName') {
|
|
111
|
-
content.outboundDisplayName = super.handleScalar(op) as string;
|
|
112
|
-
} else if (key === 'sourceCardTypes') {
|
|
113
|
-
content.sourceCardTypes = super.handleArray(
|
|
114
|
-
op,
|
|
115
|
-
key,
|
|
116
|
-
content.sourceCardTypes as Type[],
|
|
117
|
-
) as string[];
|
|
130
|
+
if (key === 'name' || key === 'displayName' || key === 'description') {
|
|
131
|
+
await super.update(updateKey, op);
|
|
118
132
|
} else {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
133
|
+
const content = structuredClone(this.content);
|
|
134
|
+
if (key === 'destinationCardTypes') {
|
|
135
|
+
content.destinationCardTypes = super.handleArray(
|
|
136
|
+
op,
|
|
137
|
+
key,
|
|
138
|
+
content.destinationCardTypes as Type[],
|
|
139
|
+
) as string[];
|
|
140
|
+
} else if (key === 'enableLinkDescription') {
|
|
141
|
+
content.enableLinkDescription = super.handleScalar(op) as boolean;
|
|
142
|
+
} else if (key === 'inboundDisplayName') {
|
|
143
|
+
content.inboundDisplayName = super.handleScalar(op) as string;
|
|
144
|
+
} else if (key === 'outboundDisplayName') {
|
|
145
|
+
content.outboundDisplayName = super.handleScalar(op) as string;
|
|
146
|
+
} else if (key === 'sourceCardTypes') {
|
|
147
|
+
content.sourceCardTypes = super.handleArray(
|
|
148
|
+
op,
|
|
149
|
+
key,
|
|
150
|
+
content.sourceCardTypes as Type[],
|
|
151
|
+
) as string[];
|
|
152
|
+
} else {
|
|
153
|
+
throw new Error(`Unknown property '${key}' for LinkType`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
await super.postUpdate(content, updateKey, op);
|
|
127
157
|
}
|
|
128
158
|
}
|
|
129
159
|
|
|
@@ -42,6 +42,11 @@ export class ReportResource extends FolderResource<
|
|
|
42
42
|
ReportMetadata,
|
|
43
43
|
ReportContent
|
|
44
44
|
> {
|
|
45
|
+
/**
|
|
46
|
+
* Creates instance of ReportResource
|
|
47
|
+
* @param project Project to use
|
|
48
|
+
* @param name Resource name
|
|
49
|
+
*/
|
|
45
50
|
constructor(project: Project, name: ResourceName) {
|
|
46
51
|
super(project, name, 'reports');
|
|
47
52
|
|
|
@@ -68,6 +73,7 @@ export class ReportResource extends FolderResource<
|
|
|
68
73
|
await this.handleBarFiles(),
|
|
69
74
|
),
|
|
70
75
|
super.updateCalculations(existingName, this.content.name),
|
|
76
|
+
super.updateCardContentReferences(existingName, this.content.name),
|
|
71
77
|
]);
|
|
72
78
|
// Finally, write updated content.
|
|
73
79
|
await this.write();
|
|
@@ -152,9 +158,9 @@ export class ReportResource extends FolderResource<
|
|
|
152
158
|
|
|
153
159
|
/**
|
|
154
160
|
* Validates report.
|
|
155
|
-
* @throws when there are validation errors.
|
|
156
161
|
* @param content Content to be validated.
|
|
157
162
|
* @note If content is not provided, base class validation will use resource's current content.
|
|
163
|
+
* @throws when there are validation errors.
|
|
158
164
|
*/
|
|
159
165
|
public async validate(content?: object) {
|
|
160
166
|
const resourceContent = this.contentData();
|
|
@@ -416,7 +416,13 @@ export abstract class ResourceObject<
|
|
|
416
416
|
}
|
|
417
417
|
}
|
|
418
418
|
|
|
419
|
-
|
|
419
|
+
/**
|
|
420
|
+
* Log to migration log resource change
|
|
421
|
+
* @param operationType Operation type
|
|
422
|
+
* @param op Details of operation
|
|
423
|
+
* @param key Which property has been changed
|
|
424
|
+
* @throws when operation type is unknown
|
|
425
|
+
*/
|
|
420
426
|
protected async logResourceOperation<Type>(
|
|
421
427
|
operationType: 'create' | 'delete' | 'update' | 'rename',
|
|
422
428
|
op?: Operation<Type>,
|
|
@@ -596,27 +602,11 @@ export abstract class ResourceObject<
|
|
|
596
602
|
}
|
|
597
603
|
}
|
|
598
604
|
|
|
599
|
-
/**
|
|
600
|
-
* Validates resource identifier to prevent filesystem operations with invalid names
|
|
601
|
-
* todo: To Validate?
|
|
602
|
-
*/
|
|
603
|
-
protected validateResourceIdentifier() {
|
|
604
|
-
if (!this.moduleResource && this.resourceName.identifier) {
|
|
605
|
-
const identifier = this.resourceName.identifier;
|
|
606
|
-
if (!/^[a-zA-Z0-9._-]+$/.test(identifier)) {
|
|
607
|
-
throw new Error(
|
|
608
|
-
`Resource identifier must follow naming rules. Identifier '${identifier}' is invalid`,
|
|
609
|
-
);
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
|
|
614
605
|
/**
|
|
615
606
|
* Update calculation files.
|
|
616
607
|
* @param from Resource name to update
|
|
617
608
|
* @param to New name for resource
|
|
618
|
-
* @throws if 'from' or 'to' is empty string
|
|
619
|
-
* if there was error accessing calculation files.
|
|
609
|
+
* @throws if 'from' or 'to' is empty string
|
|
620
610
|
*/
|
|
621
611
|
protected async updateCalculations(from: string, to: string) {
|
|
622
612
|
if (!from.trim() || !to.trim()) {
|
|
@@ -674,6 +664,39 @@ export abstract class ResourceObject<
|
|
|
674
664
|
);
|
|
675
665
|
}
|
|
676
666
|
|
|
667
|
+
/**
|
|
668
|
+
* Update references in card content.
|
|
669
|
+
* Searches through all card content in the cache and replaces references to the old resource name.
|
|
670
|
+
* @param from Resource name to update
|
|
671
|
+
* @param to New name for resource
|
|
672
|
+
* @throws if 'from' or 'to' is empty string
|
|
673
|
+
*/
|
|
674
|
+
protected async updateCardContentReferences(from: string, to: string) {
|
|
675
|
+
if (!from.trim() || !to.trim()) {
|
|
676
|
+
throw new Error(
|
|
677
|
+
'updateCardContentReferences: "from" and "to" parameters must not be empty',
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
const allCards = this.cards();
|
|
682
|
+
const cardsToUpdate = allCards.filter(
|
|
683
|
+
(card) => card.content && card.content.includes(from),
|
|
684
|
+
);
|
|
685
|
+
|
|
686
|
+
if (cardsToUpdate.length === 0) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
await Promise.all(
|
|
691
|
+
cardsToUpdate.map(async (card) => {
|
|
692
|
+
if (card.content) {
|
|
693
|
+
const updatedContent = card.content.replaceAll(from, to);
|
|
694
|
+
await this.project.updateCardContent(card.key, updatedContent);
|
|
695
|
+
}
|
|
696
|
+
}),
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
|
|
677
700
|
/**
|
|
678
701
|
* Check if there are references to the resource in the card content.
|
|
679
702
|
* @note that this needs to be async, since inherited classes need to async operations
|
|
@@ -695,6 +718,22 @@ export abstract class ResourceObject<
|
|
|
695
718
|
.map((card) => card.key);
|
|
696
719
|
}
|
|
697
720
|
|
|
721
|
+
/**
|
|
722
|
+
* Validates resource identifier to prevent filesystem operations with invalid names
|
|
723
|
+
* todo: move to Validate?
|
|
724
|
+
* @throws if identifier is incorrect
|
|
725
|
+
*/
|
|
726
|
+
protected validateResourceIdentifier() {
|
|
727
|
+
if (!this.moduleResource && this.resourceName.identifier) {
|
|
728
|
+
const identifier = this.resourceName.identifier;
|
|
729
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(identifier)) {
|
|
730
|
+
throw new Error(
|
|
731
|
+
`Resource identifier must follow naming rules. Identifier '${identifier}' is invalid`,
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
698
737
|
/**
|
|
699
738
|
* Checks if resource name is valid.
|
|
700
739
|
* @param newName New name for resource.
|