@cyberismo/data-handler 0.0.14 → 0.0.16
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/card-metadata-updater.js +8 -4
- package/dist/card-metadata-updater.js.map +1 -1
- package/dist/command-handler.d.ts +4 -0
- package/dist/command-handler.js +29 -19
- package/dist/command-handler.js.map +1 -1
- package/dist/command-manager.d.ts +25 -2
- package/dist/command-manager.js +30 -5
- package/dist/command-manager.js.map +1 -1
- package/dist/commands/create.d.ts +1 -1
- package/dist/commands/create.js +45 -93
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/edit.d.ts +1 -15
- package/dist/commands/edit.js +15 -89
- package/dist/commands/edit.js.map +1 -1
- package/dist/commands/export.d.ts +11 -2
- package/dist/commands/export.js +58 -58
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/import.d.ts +9 -1
- package/dist/commands/import.js +17 -11
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/move.d.ts +1 -2
- package/dist/commands/move.js +107 -146
- package/dist/commands/move.js.map +1 -1
- package/dist/commands/remove.d.ts +8 -1
- package/dist/commands/remove.js +17 -48
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/rename.d.ts +4 -9
- package/dist/commands/rename.js +34 -108
- package/dist/commands/rename.js.map +1 -1
- package/dist/commands/show.d.ts +22 -34
- package/dist/commands/show.js +103 -151
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/transition.d.ts +9 -2
- package/dist/commands/transition.js +49 -44
- package/dist/commands/transition.js.map +1 -1
- package/dist/commands/update.d.ts +18 -12
- package/dist/commands/update.js +34 -18
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/validate.d.ts +18 -10
- package/dist/commands/validate.js +101 -47
- package/dist/commands/validate.js.map +1 -1
- package/dist/containers/card-container.d.ts +87 -24
- package/dist/containers/card-container.js +183 -279
- package/dist/containers/card-container.js.map +1 -1
- package/dist/containers/project/calculation-engine.d.ts +13 -4
- package/dist/containers/project/calculation-engine.js +79 -77
- package/dist/containers/project/calculation-engine.js.map +1 -1
- package/dist/containers/project/card-cache.d.ts +146 -0
- package/dist/containers/project/card-cache.js +411 -0
- package/dist/containers/project/card-cache.js.map +1 -0
- package/dist/containers/project/project-paths.d.ts +5 -4
- package/dist/containers/project/project-paths.js +16 -12
- package/dist/containers/project/project-paths.js.map +1 -1
- package/dist/containers/project/resource-cache.d.ts +169 -0
- package/dist/containers/project/resource-cache.js +507 -0
- package/dist/containers/project/resource-cache.js.map +1 -0
- package/dist/containers/project/resource-handler.d.ts +129 -0
- package/dist/containers/project/resource-handler.js +206 -0
- package/dist/containers/project/resource-handler.js.map +1 -0
- package/dist/containers/project.d.ts +114 -195
- package/dist/containers/project.js +425 -535
- package/dist/containers/project.js.map +1 -1
- package/dist/containers/template.d.ts +22 -32
- package/dist/containers/template.js +113 -115
- package/dist/containers/template.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/interfaces/folder-content-interfaces.d.ts +7 -4
- package/dist/interfaces/folder-content-interfaces.js +3 -3
- package/dist/interfaces/folder-content-interfaces.js.map +1 -1
- package/dist/interfaces/macros.d.ts +1 -0
- package/dist/interfaces/macros.js +1 -1
- package/dist/interfaces/macros.js.map +1 -1
- package/dist/interfaces/project-interfaces.d.ts +7 -5
- package/dist/interfaces/project-interfaces.js.map +1 -1
- package/dist/interfaces/resource-interfaces.d.ts +25 -22
- package/dist/interfaces/resource-interfaces.js +3 -0
- package/dist/interfaces/resource-interfaces.js.map +1 -1
- package/dist/macros/common.d.ts +10 -10
- package/dist/macros/createCards/index.d.ts +0 -13
- package/dist/macros/createCards/index.js.map +1 -1
- package/dist/macros/createCards/types.d.ts +44 -0
- package/dist/macros/createCards/types.js +15 -0
- package/dist/macros/createCards/types.js.map +1 -0
- package/dist/macros/graph/index.d.ts +2 -6
- package/dist/macros/graph/index.js +14 -28
- package/dist/macros/graph/index.js.map +1 -1
- package/dist/macros/graph/types.d.ts +23 -0
- package/dist/macros/graph/types.js +15 -0
- package/dist/macros/graph/types.js.map +1 -0
- package/dist/macros/image/index.d.ts +8 -16
- package/dist/macros/image/index.js +36 -33
- package/dist/macros/image/index.js.map +1 -1
- package/dist/macros/image/types.d.ts +38 -0
- package/dist/macros/image/types.js +15 -0
- package/dist/macros/image/types.js.map +1 -0
- package/dist/macros/include/index.d.ts +1 -6
- package/dist/macros/include/index.js +4 -7
- package/dist/macros/include/index.js.map +1 -1
- package/dist/macros/include/types.d.ts +31 -0
- package/dist/macros/include/types.js +15 -0
- package/dist/macros/include/types.js.map +1 -0
- package/dist/macros/index.d.ts +1 -1
- package/dist/macros/index.js +2 -2
- package/dist/macros/index.js.map +1 -1
- package/dist/macros/percentage/index.d.ts +0 -6
- package/dist/macros/percentage/index.js.map +1 -1
- package/dist/macros/percentage/types.d.ts +31 -0
- package/dist/macros/percentage/types.js +15 -0
- package/dist/macros/percentage/types.js.map +1 -0
- package/dist/macros/report/index.d.ts +0 -3
- package/dist/macros/report/index.js +3 -6
- package/dist/macros/report/index.js.map +1 -1
- package/dist/macros/report/types.d.ts +19 -0
- package/dist/macros/report/types.js +15 -0
- package/dist/macros/report/types.js.map +1 -0
- package/dist/macros/scoreCard/index.d.ts +0 -6
- package/dist/macros/scoreCard/index.js.map +1 -1
- package/dist/macros/scoreCard/types.d.ts +31 -0
- package/dist/macros/scoreCard/types.js +15 -0
- package/dist/macros/scoreCard/types.js.map +1 -0
- package/dist/macros/types.d.ts +25 -0
- package/dist/macros/types.js +2 -0
- package/dist/macros/types.js.map +1 -0
- package/dist/macros/vega/index.d.ts +0 -4
- package/dist/macros/vega/index.js.map +1 -1
- package/dist/macros/vega/types.d.ts +20 -0
- package/dist/macros/vega/types.js +2 -0
- package/dist/macros/vega/types.js.map +1 -0
- package/dist/macros/vegalite/index.d.ts +0 -4
- package/dist/macros/vegalite/index.js.map +1 -1
- package/dist/macros/vegalite/types.d.ts +20 -0
- package/dist/macros/vegalite/types.js +15 -0
- package/dist/macros/vegalite/types.js.map +1 -0
- package/dist/macros/xref/index.d.ts +0 -3
- package/dist/macros/xref/index.js +5 -14
- package/dist/macros/xref/index.js.map +1 -1
- package/dist/macros/xref/types.d.ts +19 -0
- package/dist/macros/xref/types.js +15 -0
- package/dist/macros/xref/types.js.map +1 -0
- package/dist/module-manager.d.ts +16 -3
- package/dist/module-manager.js +55 -23
- package/dist/module-manager.js.map +1 -1
- package/dist/project-settings.d.ts +16 -3
- package/dist/project-settings.js +79 -14
- package/dist/project-settings.js.map +1 -1
- package/dist/resources/calculation-resource.d.ts +6 -33
- package/dist/resources/calculation-resource.js +11 -60
- package/dist/resources/calculation-resource.js.map +1 -1
- package/dist/resources/card-type-resource.d.ts +10 -22
- package/dist/resources/card-type-resource.js +46 -66
- package/dist/resources/card-type-resource.js.map +1 -1
- package/dist/resources/create-defaults.d.ts +3 -2
- package/dist/resources/create-defaults.js +3 -2
- package/dist/resources/create-defaults.js.map +1 -1
- package/dist/resources/field-type-resource.d.ts +8 -22
- package/dist/resources/field-type-resource.js +35 -60
- package/dist/resources/field-type-resource.js.map +1 -1
- package/dist/resources/file-resource.d.ts +14 -35
- package/dist/resources/file-resource.js +22 -301
- package/dist/resources/file-resource.js.map +1 -1
- package/dist/resources/folder-resource.d.ts +44 -66
- package/dist/resources/folder-resource.js +102 -149
- package/dist/resources/folder-resource.js.map +1 -1
- package/dist/resources/graph-model-resource.d.ts +9 -34
- package/dist/resources/graph-model-resource.js +18 -64
- package/dist/resources/graph-model-resource.js.map +1 -1
- package/dist/resources/graph-view-resource.d.ts +9 -29
- package/dist/resources/graph-view-resource.js +13 -48
- package/dist/resources/graph-view-resource.js.map +1 -1
- package/dist/resources/link-type-resource.d.ts +9 -23
- package/dist/resources/link-type-resource.js +11 -33
- package/dist/resources/link-type-resource.js.map +1 -1
- package/dist/resources/report-resource.d.ts +10 -23
- package/dist/resources/report-resource.js +20 -67
- package/dist/resources/report-resource.js.map +1 -1
- package/dist/resources/resource-object.d.ts +143 -23
- package/dist/resources/resource-object.js +369 -48
- package/dist/resources/resource-object.js.map +1 -1
- package/dist/resources/template-resource.d.ts +10 -17
- package/dist/resources/template-resource.js +19 -27
- package/dist/resources/template-resource.js.map +1 -1
- package/dist/resources/workflow-resource.d.ts +9 -25
- package/dist/resources/workflow-resource.js +25 -55
- package/dist/resources/workflow-resource.js.map +1 -1
- package/dist/utils/card-utils.d.ts +69 -19
- package/dist/utils/card-utils.js +179 -30
- package/dist/utils/card-utils.js.map +1 -1
- package/dist/utils/clingo-fact-builder.d.ts +25 -14
- package/dist/utils/clingo-fact-builder.js +27 -5
- package/dist/utils/clingo-fact-builder.js.map +1 -1
- package/dist/utils/clingo-facts.js +14 -7
- package/dist/utils/clingo-facts.js.map +1 -1
- package/dist/utils/clingo-parser.js +1 -1
- package/dist/utils/clingo-parser.js.map +1 -1
- package/dist/utils/constants.d.ts +2 -0
- package/dist/utils/constants.js +4 -0
- package/dist/utils/constants.js.map +1 -1
- package/dist/utils/csv.js +1 -1
- package/dist/utils/csv.js.map +1 -1
- package/dist/utils/resource-utils.d.ts +1 -0
- package/dist/utils/resource-utils.js +2 -1
- package/dist/utils/resource-utils.js.map +1 -1
- package/package.json +11 -11
- package/src/card-metadata-updater.ts +9 -7
- package/src/command-handler.ts +35 -23
- package/src/command-manager.ts +32 -19
- package/src/commands/create.ts +59 -160
- package/src/commands/edit.ts +16 -132
- package/src/commands/export.ts +71 -81
- package/src/commands/import.ts +26 -18
- package/src/commands/move.ts +143 -179
- package/src/commands/remove.ts +20 -59
- package/src/commands/rename.ts +45 -156
- package/src/commands/show.ts +153 -211
- package/src/commands/transition.ts +53 -58
- package/src/commands/update.ts +44 -23
- package/src/commands/validate.ts +108 -82
- package/src/containers/card-container.ts +200 -360
- package/src/containers/project/calculation-engine.ts +81 -105
- package/src/containers/project/card-cache.ts +497 -0
- package/src/containers/project/project-paths.ts +21 -13
- package/src/containers/project/resource-cache.ts +648 -0
- package/src/containers/project/resource-handler.ts +265 -0
- package/src/containers/project.ts +551 -693
- package/src/containers/template.ts +129 -142
- package/src/index.ts +1 -0
- package/src/interfaces/folder-content-interfaces.ts +14 -7
- package/src/interfaces/macros.ts +2 -0
- package/src/interfaces/project-interfaces.ts +14 -7
- package/src/interfaces/resource-interfaces.ts +30 -27
- package/src/macros/createCards/index.ts +1 -12
- package/src/macros/createCards/types.ts +46 -0
- package/src/macros/graph/index.ts +27 -52
- package/src/macros/graph/types.ts +24 -0
- package/src/macros/image/index.ts +50 -61
- package/src/macros/image/types.ts +39 -0
- package/src/macros/include/index.ts +6 -15
- package/src/macros/include/types.ts +32 -0
- package/src/macros/index.ts +2 -2
- package/src/macros/percentage/index.ts +1 -7
- package/src/macros/percentage/types.ts +32 -0
- package/src/macros/report/index.ts +4 -13
- package/src/macros/report/types.ts +20 -0
- package/src/macros/scoreCard/index.ts +1 -7
- package/src/macros/scoreCard/types.ts +32 -0
- package/src/macros/types.ts +48 -0
- package/src/macros/vega/index.ts +1 -4
- package/src/macros/vega/types.ts +21 -0
- package/src/macros/vegalite/index.ts +1 -4
- package/src/macros/vegalite/types.ts +22 -0
- package/src/macros/xref/index.ts +6 -20
- package/src/macros/xref/types.ts +20 -0
- package/src/module-manager.ts +79 -22
- package/src/project-settings.ts +84 -15
- package/src/resources/calculation-resource.ts +21 -91
- package/src/resources/card-type-resource.ts +74 -109
- package/src/resources/create-defaults.ts +3 -2
- package/src/resources/field-type-resource.ts +61 -104
- package/src/resources/file-resource.ts +33 -441
- package/src/resources/folder-resource.ts +130 -207
- package/src/resources/graph-model-resource.ts +36 -95
- package/src/resources/graph-view-resource.ts +28 -70
- package/src/resources/link-type-resource.ts +23 -53
- package/src/resources/report-resource.ts +34 -96
- package/src/resources/resource-object.ts +511 -66
- package/src/resources/template-resource.ts +32 -44
- package/src/resources/workflow-resource.ts +42 -85
- package/src/utils/card-utils.ts +217 -31
- package/src/utils/clingo-fact-builder.ts +28 -16
- package/src/utils/clingo-facts.ts +16 -7
- package/src/utils/clingo-parser.ts +1 -1
- package/src/utils/constants.ts +6 -0
- package/src/utils/csv.ts +1 -1
- package/src/utils/resource-utils.ts +2 -1
- package/dist/containers/project/resource-collector.d.ts +0 -87
- package/dist/containers/project/resource-collector.js +0 -337
- package/dist/containers/project/resource-collector.js.map +0 -1
- package/src/containers/project/resource-collector.ts +0 -396
|
@@ -13,10 +13,17 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
// node
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import { basename, join, resolve } from 'node:path';
|
|
17
|
+
import {
|
|
18
|
+
constants as fsConstants,
|
|
19
|
+
copyFile,
|
|
20
|
+
mkdir,
|
|
21
|
+
unlink,
|
|
22
|
+
writeFile,
|
|
23
|
+
} from 'node:fs/promises';
|
|
18
24
|
|
|
19
|
-
|
|
25
|
+
// base class
|
|
26
|
+
import { CardContainer } from './card-container.js';
|
|
20
27
|
|
|
21
28
|
import { CalculationEngine } from './project/calculation-engine.js';
|
|
22
29
|
import {
|
|
@@ -31,222 +38,373 @@ import {
|
|
|
31
38
|
type ModuleSetting,
|
|
32
39
|
type ProjectFetchCardDetails,
|
|
33
40
|
type ProjectMetadata,
|
|
34
|
-
type ProjectSettings,
|
|
35
|
-
type Resource,
|
|
36
|
-
type ResourceFolderType,
|
|
37
41
|
} from '../interfaces/project-interfaces.js';
|
|
38
|
-
import {
|
|
42
|
+
import { pathExists } from '../utils/file-utils.js';
|
|
39
43
|
import { generateRandomString } from '../utils/random.js';
|
|
40
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
cardPathParts,
|
|
46
|
+
isModulePath,
|
|
47
|
+
isTemplateCard,
|
|
48
|
+
} from '../utils/card-utils.js';
|
|
41
49
|
import { ProjectConfiguration } from '../project-settings.js';
|
|
42
50
|
import { ProjectPaths } from './project/project-paths.js';
|
|
43
51
|
import { readJsonFile } from '../utils/json.js';
|
|
44
|
-
import {
|
|
45
|
-
|
|
46
|
-
type ResourceName,
|
|
47
|
-
resourceNameToString,
|
|
48
|
-
} from '../utils/resource-utils.js';
|
|
49
|
-
import {
|
|
50
|
-
ResourcesFrom,
|
|
51
|
-
ResourceCollector,
|
|
52
|
-
} from './project/resource-collector.js';
|
|
53
|
-
import type { Template } from './template.js';
|
|
52
|
+
import { ResourcesFrom } from './project/resource-cache.js';
|
|
53
|
+
import { ResourceHandler } from './project/resource-handler.js';
|
|
54
54
|
import { Validate } from '../commands/validate.js';
|
|
55
|
+
import { ContentWatcher } from './project/project-content-watcher.js';
|
|
56
|
+
import { getChildLogger } from '../utils/log-utils.js';
|
|
55
57
|
|
|
56
|
-
import {
|
|
57
|
-
import { CardTypeResource } from '../resources/card-type-resource.js';
|
|
58
|
-
import { FieldTypeResource } from '../resources/field-type-resource.js';
|
|
59
|
-
import { GraphModelResource } from '../resources/graph-model-resource.js';
|
|
60
|
-
import { GraphViewResource } from '../resources/graph-view-resource.js';
|
|
61
|
-
import { LinkTypeResource } from '../resources/link-type-resource.js';
|
|
62
|
-
import { ReportResource } from '../resources/report-resource.js';
|
|
63
|
-
import { TemplateResource } from '../resources/template-resource.js';
|
|
64
|
-
import { WorkflowResource } from '../resources/workflow-resource.js';
|
|
58
|
+
import type { Template } from './template.js';
|
|
65
59
|
|
|
66
|
-
import {
|
|
67
|
-
import { pathToResourceName } from '../utils/resource-utils.js';
|
|
60
|
+
import { ROOT } from '../utils/constants.js';
|
|
68
61
|
|
|
69
62
|
// Re-export this, so that classes that use Project do not need to have separate import.
|
|
70
63
|
export { ResourcesFrom };
|
|
71
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Options for Project initialization.
|
|
67
|
+
* autoSave - If project configuration changes are saved automatically. Default true.
|
|
68
|
+
* watchResourceChanges - If project refresh automatically to filesystem changes. Default false.
|
|
69
|
+
*/
|
|
70
|
+
export interface ProjectOptions {
|
|
71
|
+
autoSave?: boolean;
|
|
72
|
+
watchResourceChanges?: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
72
75
|
/**
|
|
73
76
|
* Represents project folder.
|
|
74
77
|
*/
|
|
75
78
|
export class Project extends CardContainer {
|
|
76
79
|
public calculationEngine: CalculationEngine;
|
|
77
|
-
private
|
|
80
|
+
private logger = getChildLogger({ module: 'Project' });
|
|
78
81
|
private projectPaths: ProjectPaths;
|
|
82
|
+
private resourceHandler: ResourceHandler;
|
|
83
|
+
private resourceWatcher: ContentWatcher | undefined;
|
|
79
84
|
private settings: ProjectConfiguration;
|
|
80
85
|
private validator: Validate;
|
|
81
|
-
private resourceWatcher: ContentWatcher | undefined;
|
|
82
|
-
|
|
83
|
-
// Created resources are held in a cache.
|
|
84
|
-
// In the cache, key is resource name, and data is resource metadata (as JSON).
|
|
85
|
-
private createdResources = new Map<string, JSON>();
|
|
86
86
|
|
|
87
87
|
constructor(
|
|
88
88
|
path: string,
|
|
89
|
-
private
|
|
89
|
+
private options: ProjectOptions = {
|
|
90
|
+
autoSave: true,
|
|
91
|
+
watchResourceChanges: false,
|
|
92
|
+
},
|
|
90
93
|
) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
this.calculationEngine = new CalculationEngine(this);
|
|
94
|
-
|
|
95
|
-
this.settings = new ProjectConfiguration(
|
|
94
|
+
const settings = new ProjectConfiguration(
|
|
96
95
|
join(path, '.cards', 'local', Project.projectConfigFileName),
|
|
96
|
+
options.autoSave ?? true,
|
|
97
97
|
);
|
|
98
|
+
super(path, settings.cardKeyPrefix, '');
|
|
99
|
+
this.settings = settings;
|
|
100
|
+
|
|
101
|
+
this.logger.info({ path }, 'Initializing project');
|
|
102
|
+
|
|
103
|
+
this.calculationEngine = new CalculationEngine(this);
|
|
98
104
|
this.projectPaths = new ProjectPaths(path);
|
|
99
|
-
this.
|
|
105
|
+
this.resourceHandler = new ResourceHandler(this);
|
|
100
106
|
|
|
101
107
|
this.containerName = this.settings.name;
|
|
102
108
|
// todo: implement project validation
|
|
103
109
|
this.validator = Validate.getInstance();
|
|
104
|
-
|
|
110
|
+
|
|
111
|
+
this.logger.info(
|
|
112
|
+
{ name: this.containerName },
|
|
113
|
+
'Project initialization complete',
|
|
114
|
+
);
|
|
105
115
|
|
|
106
116
|
const ignoreRenameFileChanges = true;
|
|
107
117
|
|
|
108
118
|
// Watch changes in .cards if there are multiple instances of Project being
|
|
109
119
|
// run concurrently.
|
|
110
|
-
if (this.watchResourceChanges) {
|
|
120
|
+
if (this.options.watchResourceChanges) {
|
|
111
121
|
this.resourceWatcher = new ContentWatcher(
|
|
112
122
|
ignoreRenameFileChanges,
|
|
113
123
|
this.paths.resourcesFolder,
|
|
114
124
|
(fileName: string) => {
|
|
115
125
|
void (async () => {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
join(this.paths.resourcesFolder, fileName),
|
|
121
|
-
);
|
|
122
|
-
if (!resource) {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
} catch {
|
|
126
|
-
// it wasn't a resource that changed, so ignore the change
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
const resourceName = `${resource.prefix}/${resource.type}/${resource.identifier}`;
|
|
130
|
-
await this.replaceCacheValue(resourceName);
|
|
131
|
-
this.resources.collectLocalResources();
|
|
126
|
+
this.resources.handleFileSystemChange(
|
|
127
|
+
join(this.paths.resourcesFolder, fileName),
|
|
128
|
+
);
|
|
129
|
+
this.resources.changed();
|
|
132
130
|
})();
|
|
133
131
|
},
|
|
134
132
|
);
|
|
135
133
|
}
|
|
136
134
|
}
|
|
137
135
|
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (this.createdResources.has(resourceName)) {
|
|
143
|
-
// First, remove the old version from cache
|
|
144
|
-
this.createdResources.delete(resourceName);
|
|
136
|
+
// Changes a card's parent in the cache and updates all relationships.
|
|
137
|
+
private changeParent(updatedCard: Card, previousParent?: string) {
|
|
138
|
+
if (previousParent && previousParent !== ROOT) {
|
|
139
|
+
this.removeCachedChildren(previousParent, updatedCard.key);
|
|
145
140
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
141
|
+
if (updatedCard.parent && updatedCard.parent !== ROOT) {
|
|
142
|
+
this.updateCachedChildren(updatedCard.parent, updatedCard);
|
|
143
|
+
}
|
|
144
|
+
this.cardCache.updateCard(updatedCard.key, updatedCard);
|
|
149
145
|
}
|
|
150
146
|
|
|
151
147
|
// Finds specific module.
|
|
152
|
-
private async findModule(
|
|
153
|
-
|
|
154
|
-
|
|
148
|
+
private async findModule(
|
|
149
|
+
moduleName: string,
|
|
150
|
+
): Promise<{ name: string; path: string } | undefined> {
|
|
151
|
+
const moduleExists = this.resources.moduleNames().includes(moduleName);
|
|
152
|
+
if (!moduleExists) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// For modules, we need to construct the local path where the module is stored
|
|
157
|
+
const moduleConfig = this.configuration.modules?.find(
|
|
158
|
+
(module) => module.name === moduleName,
|
|
155
159
|
);
|
|
160
|
+
if (!moduleConfig) {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
name: moduleName,
|
|
166
|
+
path: join(this.paths.modulesFolder, moduleConfig.name),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Handles attachment changes after filesystem operations.
|
|
171
|
+
private async handleAttachmentChange(
|
|
172
|
+
cardKey: string,
|
|
173
|
+
operation: 'added' | 'removed' | 'refresh',
|
|
174
|
+
fileName: string,
|
|
175
|
+
): Promise<void> {
|
|
176
|
+
if (operation === 'added') {
|
|
177
|
+
this.cardCache.addAttachment(cardKey, fileName);
|
|
178
|
+
} else if (operation === 'removed') {
|
|
179
|
+
this.cardCache.deleteAttachment(cardKey, fileName);
|
|
180
|
+
} else if (operation === 'refresh') {
|
|
181
|
+
const newAttachments = this.cardCache.getCardAttachments(cardKey);
|
|
182
|
+
if (newAttachments) {
|
|
183
|
+
this.cardCache.updateCardAttachments(cardKey, newAttachments);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Determines the parent card key from a card's filesystem path.
|
|
189
|
+
// todo: could be moved to card-utils
|
|
190
|
+
private parentFromPath(cardPath: string): string {
|
|
191
|
+
return cardPathParts(this.projectPrefix, cardPath).parents.at(-1) || 'root';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Remove children from a card in the card cache
|
|
195
|
+
private removeCachedChildren(parentKey: string, childKey: string) {
|
|
196
|
+
const parentCard = this.cardCache.getCard(parentKey);
|
|
197
|
+
if (parentCard && parentCard.children) {
|
|
198
|
+
parentCard.children = parentCard.children.filter(
|
|
199
|
+
(child) => child !== childKey,
|
|
200
|
+
);
|
|
201
|
+
this.cardCache.updateCard(parentCard.key, parentCard);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Updates children in the card cache
|
|
206
|
+
private updateCachedChildren(parentKey: string, newChild: Card) {
|
|
207
|
+
const parentCard = this.cardCache.getCard(parentKey);
|
|
208
|
+
if (parentCard) {
|
|
209
|
+
// Add or update the child in the parent's children array
|
|
210
|
+
const existingChildIndex = parentCard.children?.findIndex(
|
|
211
|
+
(child) => child === newChild.key,
|
|
212
|
+
);
|
|
213
|
+
if (existingChildIndex === -1) {
|
|
214
|
+
parentCard.children.push(newChild.key);
|
|
215
|
+
}
|
|
216
|
+
this.cardCache.updateCard(parentCard.key, parentCard);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Validates that card's data is valid.
|
|
221
|
+
private async validateCard(card: Card): Promise<string> {
|
|
222
|
+
const invalidCustomData = await this.validator.validateCustomFields(
|
|
223
|
+
this,
|
|
224
|
+
card,
|
|
225
|
+
this.projectPrefixes(),
|
|
226
|
+
);
|
|
227
|
+
const invalidWorkFlow = await this.validator.validateWorkflowState(
|
|
228
|
+
this,
|
|
229
|
+
card,
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
const invalidLabels = this.validator.validateCardLabels(card);
|
|
233
|
+
if (
|
|
234
|
+
invalidCustomData.length === 0 &&
|
|
235
|
+
invalidWorkFlow.length === 0 &&
|
|
236
|
+
invalidLabels.length === 0
|
|
237
|
+
) {
|
|
238
|
+
return '';
|
|
239
|
+
}
|
|
240
|
+
const errors: string[] = [];
|
|
241
|
+
if (invalidCustomData.length > 0) {
|
|
242
|
+
errors.push(invalidCustomData);
|
|
243
|
+
}
|
|
244
|
+
if (invalidWorkFlow.length > 0) {
|
|
245
|
+
errors.push(invalidWorkFlow);
|
|
246
|
+
}
|
|
247
|
+
if (invalidLabels.length > 0) {
|
|
248
|
+
errors.push(invalidLabels);
|
|
249
|
+
}
|
|
250
|
+
return errors.join('\n');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Populate template cards into the card cache.
|
|
255
|
+
*/
|
|
256
|
+
protected async populateTemplateCards(): Promise<void> {
|
|
257
|
+
try {
|
|
258
|
+
const templateResources = this.resources.templates();
|
|
259
|
+
const prefixes = this.projectPrefixes();
|
|
260
|
+
const loadPromises = templateResources.map(async (template) => {
|
|
261
|
+
try {
|
|
262
|
+
this.validator.validResourceName(
|
|
263
|
+
'templates',
|
|
264
|
+
template.data?.name || '',
|
|
265
|
+
prefixes,
|
|
266
|
+
);
|
|
267
|
+
} catch (error) {
|
|
268
|
+
this.logger.warn(
|
|
269
|
+
{ templateName: template, error },
|
|
270
|
+
`Template name '${template}' does not follow required format, skipping`,
|
|
271
|
+
);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const templateObject = template.templateObject();
|
|
276
|
+
const isCreated = templateObject && templateObject.isCreated();
|
|
277
|
+
if (!templateObject || !isCreated) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
await this.cardCache.populateFromPath(
|
|
282
|
+
templateObject.templateCardsFolder(),
|
|
283
|
+
false,
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
await Promise.all(loadPromises);
|
|
288
|
+
|
|
289
|
+
// Once all templates have been fetched, build child-parent relationships.
|
|
290
|
+
this.cardCache.populateChildrenRelationships();
|
|
291
|
+
} catch (error) {
|
|
292
|
+
this.logger.error(
|
|
293
|
+
{ error },
|
|
294
|
+
'Failed to populate template cards into the card cache',
|
|
295
|
+
);
|
|
296
|
+
}
|
|
156
297
|
}
|
|
157
298
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
return this.resources.resources(type, from);
|
|
299
|
+
/**
|
|
300
|
+
* Populate both the project cards, and all template cards into card cache.
|
|
301
|
+
*/
|
|
302
|
+
protected async populateCardsCache(): Promise<void> {
|
|
303
|
+
await this.cardCache.populateFromPath(this.paths.cardRootFolder);
|
|
304
|
+
await this.populateTemplateCards();
|
|
165
305
|
}
|
|
166
306
|
|
|
167
307
|
/**
|
|
168
|
-
*
|
|
169
|
-
* @
|
|
170
|
-
* @param data JSON data for the resource.
|
|
308
|
+
* Returns all template cards from the project. This includes all module templates' cards.
|
|
309
|
+
* @returns all the template cards from the project
|
|
171
310
|
*/
|
|
172
|
-
public
|
|
173
|
-
this.
|
|
174
|
-
this.createdResources.set(resource.name, data);
|
|
311
|
+
public allTemplateCards(): Card[] {
|
|
312
|
+
return this.cardCache.getAllTemplateCards();
|
|
175
313
|
}
|
|
176
314
|
|
|
177
315
|
/**
|
|
178
316
|
* Returns an array of all the attachments in the project card's (excluding ones in templates).
|
|
179
317
|
* @returns all attachments in the project.
|
|
180
318
|
*/
|
|
181
|
-
public
|
|
319
|
+
public attachments(): CardAttachment[] {
|
|
182
320
|
return super.attachments(this.paths.cardRootFolder);
|
|
183
321
|
}
|
|
184
322
|
|
|
185
323
|
/**
|
|
186
|
-
* Returns
|
|
187
|
-
*
|
|
188
|
-
* @
|
|
324
|
+
* Returns attachments from cards at a specific path using the card cache.
|
|
325
|
+
* This method allows templates to access attachments from the shared cache.
|
|
326
|
+
* @param path The path to get attachments from
|
|
327
|
+
* @returns Array of attachments from cards at the specified path
|
|
189
328
|
*/
|
|
190
|
-
public
|
|
191
|
-
|
|
192
|
-
): Promise<Resource[]> {
|
|
193
|
-
return this.resources.resources('calculations', from);
|
|
329
|
+
public attachmentsByPath(path: string): CardAttachment[] {
|
|
330
|
+
return super.attachments(path);
|
|
194
331
|
}
|
|
195
332
|
|
|
196
333
|
/**
|
|
197
|
-
* Returns path to card's attachment folder.
|
|
334
|
+
* Returns path to a card's attachment folder.
|
|
198
335
|
* @param cardKey card key
|
|
199
|
-
* @returns path to card's attachment folder.
|
|
200
|
-
* @throws if card path cannot be found
|
|
336
|
+
* @returns path to a card's attachment folder.
|
|
201
337
|
*/
|
|
202
|
-
public
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const cardPath = await this.cardFolder(cardKey);
|
|
206
|
-
return join(cardPath, 'a');
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const pathToProjectCard = this.pathToCard(cardKey);
|
|
210
|
-
if (!pathToProjectCard) {
|
|
211
|
-
throw new Error(`Card '${cardKey}' not found`);
|
|
212
|
-
}
|
|
213
|
-
return join(this.paths.cardRootFolder, pathToProjectCard, 'a');
|
|
338
|
+
public cardAttachmentFolder(cardKey: string): string {
|
|
339
|
+
const pathToCard = this.findCard(cardKey).path;
|
|
340
|
+
return join(pathToCard, 'a');
|
|
214
341
|
}
|
|
215
342
|
|
|
216
343
|
/**
|
|
217
|
-
*
|
|
218
|
-
* @param cardKey card
|
|
219
|
-
* @param
|
|
220
|
-
* @
|
|
344
|
+
* Creates an attachment for a card.
|
|
345
|
+
* @param cardKey The card to add attachment to
|
|
346
|
+
* @param attachmentName The name for the attachment file
|
|
347
|
+
* @param attachmentData The attachment data (file path or buffer)
|
|
348
|
+
* @throws If trying to add attachment to module card, or if attachment is not found
|
|
221
349
|
*/
|
|
222
|
-
public async
|
|
350
|
+
public async createCardAttachment(
|
|
223
351
|
cardKey: string,
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
352
|
+
attachmentName: string,
|
|
353
|
+
attachmentData: string | Buffer,
|
|
354
|
+
): Promise<void> {
|
|
355
|
+
const attachmentFolder = this.cardAttachmentFolder(cardKey);
|
|
356
|
+
|
|
357
|
+
// Check if this is a module template
|
|
358
|
+
if (isModulePath(attachmentFolder)) {
|
|
359
|
+
throw new Error(`Cannot modify imported module`);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Create the attachment folder if it doesn't exist
|
|
363
|
+
await mkdir(attachmentFolder, { recursive: true });
|
|
364
|
+
|
|
365
|
+
const attachmentPath = join(attachmentFolder, basename(attachmentName));
|
|
366
|
+
|
|
367
|
+
if (Buffer.isBuffer(attachmentData)) {
|
|
368
|
+
await writeFile(attachmentPath, attachmentData, { flag: 'wx' });
|
|
369
|
+
} else {
|
|
370
|
+
try {
|
|
371
|
+
await copyFile(
|
|
372
|
+
attachmentData,
|
|
373
|
+
attachmentPath,
|
|
374
|
+
fsConstants.COPYFILE_EXCL,
|
|
375
|
+
);
|
|
376
|
+
} catch {
|
|
377
|
+
throw new Error(`Attachment file not found: ${attachmentData}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Update cache
|
|
382
|
+
await this.handleAttachmentChange(
|
|
383
|
+
cardKey,
|
|
384
|
+
'added',
|
|
385
|
+
basename(attachmentName),
|
|
386
|
+
);
|
|
227
387
|
}
|
|
228
388
|
|
|
229
389
|
/**
|
|
230
|
-
* Returns path to card's folder.
|
|
390
|
+
* Returns path to a card's folder.
|
|
231
391
|
* @param cardKey card key
|
|
232
|
-
* @returns path to card's folder.
|
|
392
|
+
* @returns path to a card's folder.
|
|
233
393
|
*/
|
|
234
394
|
public async cardFolder(cardKey: string): Promise<string> {
|
|
235
|
-
const found =
|
|
395
|
+
const found = super.findCard(cardKey);
|
|
236
396
|
if (found) {
|
|
237
397
|
return found.path;
|
|
238
398
|
}
|
|
239
399
|
|
|
240
|
-
const templates =
|
|
241
|
-
const templatePromises = templates.map(
|
|
242
|
-
const templateObject =
|
|
243
|
-
this,
|
|
244
|
-
resourceName(template.name),
|
|
245
|
-
).templateObject();
|
|
400
|
+
const templates = this.resources.templates();
|
|
401
|
+
const templatePromises = templates.map((template) => {
|
|
402
|
+
const templateObject = template.templateObject();
|
|
246
403
|
const templateCard = templateObject
|
|
247
|
-
?
|
|
404
|
+
? templateObject.findCard(cardKey)
|
|
248
405
|
: undefined;
|
|
249
|
-
|
|
406
|
+
const path = templateCard ? templateCard.path : '';
|
|
407
|
+
return path;
|
|
250
408
|
});
|
|
251
409
|
|
|
252
410
|
const templatePaths = await Promise.all(templatePromises);
|
|
@@ -254,104 +412,58 @@ export class Project extends CardContainer {
|
|
|
254
412
|
}
|
|
255
413
|
|
|
256
414
|
/**
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
* @
|
|
260
|
-
* @returns card path logical parts
|
|
261
|
-
* @throws when called with wrong path, or wrong card owner
|
|
262
|
-
* todo: if prefix would be parameter; this could be static, or util method
|
|
415
|
+
* Fetches full Card data for given card keys
|
|
416
|
+
* @param cardIds array of card keys to fetch
|
|
417
|
+
* @returns Card data to the given card keys
|
|
263
418
|
*/
|
|
264
|
-
public
|
|
265
|
-
const
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
let startIndex = -1;
|
|
271
|
-
let templatesNameIndex = -1;
|
|
272
|
-
|
|
273
|
-
const cardRootIndex = pathParts.indexOf('cardRoot');
|
|
274
|
-
const projectInternalsIndex = pathParts.indexOf('.cards');
|
|
275
|
-
|
|
276
|
-
if (projectInternalsIndex === -1 && cardRootIndex >= 0) {
|
|
277
|
-
startIndex = projectInternalsIndex;
|
|
278
|
-
} else if (projectInternalsIndex >= 0 && cardRootIndex === -1) {
|
|
279
|
-
const templatesIndex = pathParts.indexOf('templates');
|
|
280
|
-
startIndex = templatesIndex;
|
|
281
|
-
if (templatesIndex === -1) {
|
|
282
|
-
throw new Error(
|
|
283
|
-
`Invalid card path. Template card must have 'templates' in path`,
|
|
284
|
-
);
|
|
285
|
-
}
|
|
286
|
-
const modulesIndex = pathParts.indexOf('modules');
|
|
287
|
-
if (modulesIndex !== -1) {
|
|
288
|
-
prefix = pathParts.at(modulesIndex + 1) || '';
|
|
289
|
-
}
|
|
290
|
-
templatesNameIndex = templatesIndex + 1;
|
|
291
|
-
template = `${prefix}/templates/${pathParts.at(templatesNameIndex)}`;
|
|
292
|
-
} else {
|
|
293
|
-
throw new Error(`Card must be either project card, or template card`);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Look for parents in the path.
|
|
297
|
-
let previousWasParent = false;
|
|
298
|
-
for (let index = startIndex; index <= pathParts.length; index++) {
|
|
299
|
-
if (previousWasParent) {
|
|
300
|
-
previousWasParent = false;
|
|
301
|
-
parents.push(pathParts.at(index - 2));
|
|
302
|
-
}
|
|
303
|
-
const cardsSubFolder = pathParts.at(index) === 'c';
|
|
304
|
-
const ignoreOrNotTemplatesParent =
|
|
305
|
-
index - 1 !== templatesNameIndex || templatesNameIndex === -1;
|
|
306
|
-
if (cardsSubFolder && ignoreOrNotTemplatesParent) {
|
|
307
|
-
previousWasParent = true;
|
|
419
|
+
public cardKeysToCards(cardIds: string[]): Card[] {
|
|
420
|
+
const cards: Card[] = [];
|
|
421
|
+
for (const cardId of cardIds) {
|
|
422
|
+
const card = this.cardCache.getCard(cardId);
|
|
423
|
+
if (card) {
|
|
424
|
+
cards.push(card);
|
|
308
425
|
}
|
|
309
426
|
}
|
|
310
|
-
|
|
311
|
-
return {
|
|
312
|
-
cardKey: cardKey,
|
|
313
|
-
parents: parents,
|
|
314
|
-
prefix: prefix,
|
|
315
|
-
template: template,
|
|
316
|
-
};
|
|
427
|
+
return cards;
|
|
317
428
|
}
|
|
318
429
|
|
|
319
430
|
/**
|
|
320
|
-
* Returns an array of all the cards in the project.
|
|
321
|
-
* @
|
|
322
|
-
* @param
|
|
431
|
+
* Returns an array of all the cards in the project.
|
|
432
|
+
* @note These are project cards only, by default (unless path dictates otherwise).
|
|
433
|
+
* @param path Path from which to fetch the cards. Generally it is best to fetch from Project root, e.g. Project.cardRootFolder
|
|
434
|
+
* @param details Which details to include in the cards; by default all details are included.
|
|
323
435
|
* @returns all cards from the given path in the project.
|
|
324
436
|
*/
|
|
325
|
-
public
|
|
437
|
+
public cards(
|
|
326
438
|
path: string = this.paths.cardRootFolder,
|
|
327
|
-
details
|
|
328
|
-
):
|
|
439
|
+
details?: FetchCardDetails,
|
|
440
|
+
): Card[] {
|
|
329
441
|
return super.cards(path, details);
|
|
330
442
|
}
|
|
331
443
|
|
|
332
444
|
/**
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
* @
|
|
336
|
-
*/
|
|
337
|
-
public async cardTypes(
|
|
338
|
-
from: ResourcesFrom = ResourcesFrom.all,
|
|
339
|
-
): Promise<Resource[]> {
|
|
340
|
-
return this.resources.resources('cardTypes', from);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* Updates all local resources.
|
|
445
|
+
* Accessor for cards cache.
|
|
446
|
+
* Used by template container (it needs to access project's cache, not their own instance).
|
|
447
|
+
* @note Should not be used directly (other than Template).
|
|
345
448
|
*/
|
|
346
|
-
public
|
|
347
|
-
this.
|
|
449
|
+
public get cardsCache() {
|
|
450
|
+
return this.cardCache;
|
|
348
451
|
}
|
|
349
452
|
|
|
350
453
|
/**
|
|
351
|
-
*
|
|
454
|
+
* Returns children of a given card; as Card array
|
|
455
|
+
* @param card Parent card to fetch children from
|
|
456
|
+
* @returns children of a given card; as Card array
|
|
352
457
|
*/
|
|
353
|
-
public
|
|
354
|
-
|
|
458
|
+
public childrenCards(card: Card): Card[] {
|
|
459
|
+
const cards: Card[] = [];
|
|
460
|
+
for (const child of card.children) {
|
|
461
|
+
const card = this.cardCache.getCard(child);
|
|
462
|
+
if (card) {
|
|
463
|
+
cards.push(card);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return cards;
|
|
355
467
|
}
|
|
356
468
|
|
|
357
469
|
/**
|
|
@@ -371,8 +483,9 @@ export class Project extends CardContainer {
|
|
|
371
483
|
if (!card || !card.path || !isTemplateCard(card)) {
|
|
372
484
|
return undefined;
|
|
373
485
|
}
|
|
374
|
-
const { template } = this.
|
|
375
|
-
|
|
486
|
+
const { template } = cardPathParts(this.projectPrefix, card.path);
|
|
487
|
+
const templateResource = this.resources.byType(template, 'templates');
|
|
488
|
+
return templateResource.templateObject();
|
|
376
489
|
}
|
|
377
490
|
|
|
378
491
|
/**
|
|
@@ -386,14 +499,13 @@ export class Project extends CardContainer {
|
|
|
386
499
|
}
|
|
387
500
|
|
|
388
501
|
/**
|
|
389
|
-
* Returns
|
|
390
|
-
* @param
|
|
391
|
-
* @
|
|
502
|
+
* Returns specific card.
|
|
503
|
+
* @param cardToFind Card key to find
|
|
504
|
+
* @param details Defines which card details are included in the return values.
|
|
505
|
+
* @returns specific card details, or undefined if card is not part of the project.
|
|
392
506
|
*/
|
|
393
|
-
public
|
|
394
|
-
|
|
395
|
-
): Promise<Resource[]> {
|
|
396
|
-
return this.resources.resources('fieldTypes', from);
|
|
507
|
+
public findCard(cardToFind: string, details?: ProjectFetchCardDetails): Card {
|
|
508
|
+
return super.findCard(cardToFind, details);
|
|
397
509
|
}
|
|
398
510
|
|
|
399
511
|
/**
|
|
@@ -415,81 +527,12 @@ export class Project extends CardContainer {
|
|
|
415
527
|
return Project.findProjectRoot(parentPath);
|
|
416
528
|
}
|
|
417
529
|
|
|
418
|
-
/**
|
|
419
|
-
* Returns specific card.
|
|
420
|
-
* @param cardToFind Card key to find
|
|
421
|
-
* @param details Defines which card details are included in the return values.
|
|
422
|
-
* @returns specific card details, or undefined if card is not part of the project.
|
|
423
|
-
*/
|
|
424
|
-
public async findSpecificCard(
|
|
425
|
-
cardToFind: string,
|
|
426
|
-
details: ProjectFetchCardDetails = {},
|
|
427
|
-
): Promise<Card | undefined> {
|
|
428
|
-
let card;
|
|
429
|
-
|
|
430
|
-
if (
|
|
431
|
-
details.location === CardLocation.projectOnly ||
|
|
432
|
-
details.location === CardLocation.all ||
|
|
433
|
-
!details.location
|
|
434
|
-
) {
|
|
435
|
-
card = await super.findCard(
|
|
436
|
-
this.paths.cardRootFolder,
|
|
437
|
-
cardToFind,
|
|
438
|
-
details,
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
if (
|
|
443
|
-
!card &&
|
|
444
|
-
(details.location === CardLocation.templatesOnly ||
|
|
445
|
-
details.location === CardLocation.all ||
|
|
446
|
-
!details.location)
|
|
447
|
-
) {
|
|
448
|
-
const templates = await this.templates();
|
|
449
|
-
for (const template of templates) {
|
|
450
|
-
const templateObject = new TemplateResource(
|
|
451
|
-
this,
|
|
452
|
-
resourceName(template.name),
|
|
453
|
-
).templateObject();
|
|
454
|
-
if (!templateObject) continue;
|
|
455
|
-
|
|
456
|
-
// optimize: execute each find in template parallel
|
|
457
|
-
card = await templateObject.findSpecificCard(cardToFind, details);
|
|
458
|
-
if (card) {
|
|
459
|
-
break;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
return card;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
/**
|
|
467
|
-
* Returns an array of all the graph models in the project.
|
|
468
|
-
* @param from Defines where resources are collected from.
|
|
469
|
-
* @returns array of all the graph models in the project.
|
|
470
|
-
*/
|
|
471
|
-
public async graphModels(
|
|
472
|
-
from: ResourcesFrom = ResourcesFrom.all,
|
|
473
|
-
): Promise<Resource[]> {
|
|
474
|
-
return this.resources.resources('graphModels', from);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
/**
|
|
478
|
-
* Returns an array of all the graph views in the project.
|
|
479
|
-
* @param from Defines where resources are collected from.
|
|
480
|
-
* @returns array of all the graph views in the project.
|
|
481
|
-
*/
|
|
482
|
-
public async graphViews(
|
|
483
|
-
from: ResourcesFrom = ResourcesFrom.all,
|
|
484
|
-
): Promise<Resource[]> {
|
|
485
|
-
return this.resources.resources('graphViews', from);
|
|
486
|
-
}
|
|
487
|
-
|
|
488
530
|
/**
|
|
489
531
|
* When card changes.
|
|
490
532
|
* @param changedCard Card that was changed.
|
|
491
533
|
*/
|
|
492
534
|
public async handleCardChanged(changedCard: Card) {
|
|
535
|
+
// Notify the calculation engine about the change
|
|
493
536
|
return this.calculationEngine.handleCardChanged(changedCard);
|
|
494
537
|
}
|
|
495
538
|
|
|
@@ -497,39 +540,86 @@ export class Project extends CardContainer {
|
|
|
497
540
|
* When cards are removed.
|
|
498
541
|
* @param deletedCard Card that is to be removed.
|
|
499
542
|
*/
|
|
500
|
-
public async
|
|
543
|
+
public async handleCardDeleted(deletedCard: Card) {
|
|
544
|
+
// Delete children from the cache first
|
|
545
|
+
if (deletedCard.children && deletedCard.children.length > 0) {
|
|
546
|
+
for (const child of deletedCard.children) {
|
|
547
|
+
try {
|
|
548
|
+
const childCard = this.findCard(child);
|
|
549
|
+
await this.handleCardDeleted(childCard);
|
|
550
|
+
} catch {
|
|
551
|
+
this.logger.warn(
|
|
552
|
+
`Accessing child '${child}' of '${deletedCard.key}' when deleting cards caused an exception`,
|
|
553
|
+
);
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
await super.removeCard(deletedCard.key);
|
|
501
559
|
return this.calculationEngine.handleDeleteCard(deletedCard);
|
|
502
560
|
}
|
|
503
561
|
|
|
504
562
|
/**
|
|
505
|
-
* When
|
|
506
|
-
* @param
|
|
563
|
+
* When card is moved.
|
|
564
|
+
* @param movedCard Card that moved
|
|
565
|
+
* @param newParentCard New parent for the 'movedCard'
|
|
566
|
+
* @param oldParentCard Previous parent of the 'movedCard'
|
|
507
567
|
*/
|
|
508
|
-
public async
|
|
509
|
-
|
|
568
|
+
public async handleCardMoved(
|
|
569
|
+
movedCard: Card,
|
|
570
|
+
newParentCard?: Card,
|
|
571
|
+
oldParentCard?: Card,
|
|
572
|
+
) {
|
|
573
|
+
if (newParentCard) {
|
|
574
|
+
this.cardCache.updateCard(newParentCard.key, newParentCard);
|
|
575
|
+
}
|
|
576
|
+
if (oldParentCard) {
|
|
577
|
+
this.cardCache.updateCard(oldParentCard.key, oldParentCard);
|
|
578
|
+
}
|
|
579
|
+
this.cardCache.updateCard(movedCard.key, movedCard);
|
|
580
|
+
|
|
581
|
+
// todo: it would be enough to just update parent, previous parent and changed card
|
|
582
|
+
this.cardCache.populateChildrenRelationships();
|
|
583
|
+
await this.handleCardChanged(movedCard);
|
|
584
|
+
await this.calculationEngine.handleCardMoved();
|
|
510
585
|
}
|
|
511
586
|
|
|
512
587
|
/**
|
|
513
|
-
*
|
|
514
|
-
* @param
|
|
515
|
-
* @returns true if a given card is found from project, false otherwise.
|
|
588
|
+
* When new cards are added.
|
|
589
|
+
* @param cards Added cards.
|
|
516
590
|
*/
|
|
517
|
-
public
|
|
518
|
-
|
|
591
|
+
public async handleNewCards(cards: Card[]) {
|
|
592
|
+
// Add new cards to the card cache
|
|
593
|
+
cards.forEach((card) => {
|
|
594
|
+
const cardWithParent = {
|
|
595
|
+
...card,
|
|
596
|
+
parent: card.parent || this.parentFromPath(card.path),
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
this.cardCache.updateCard(cardWithParent.key, cardWithParent);
|
|
600
|
+
|
|
601
|
+
// Update the parent's children list in the cache
|
|
602
|
+
if (cardWithParent.parent && cardWithParent.parent !== ROOT) {
|
|
603
|
+
this.updateCachedChildren(cardWithParent.parent, cardWithParent);
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
return this.calculationEngine.handleNewCards(cards);
|
|
519
607
|
}
|
|
520
608
|
|
|
521
609
|
/**
|
|
522
610
|
* Adds a module from project.
|
|
523
|
-
* @param module
|
|
611
|
+
* @param module Module to add
|
|
524
612
|
*/
|
|
525
613
|
public async importModule(module: ModuleSetting) {
|
|
526
614
|
// Add module as a dependency.
|
|
527
615
|
await this.configuration.addModule(module);
|
|
528
|
-
|
|
616
|
+
this.resources.changedModules();
|
|
617
|
+
await this.populateTemplateCards();
|
|
618
|
+
this.logger.info(`Imported module '${module.name}'`);
|
|
529
619
|
}
|
|
530
620
|
|
|
531
621
|
/**
|
|
532
|
-
* Checks if given path is a project.
|
|
622
|
+
* Checks if a given path is a project.
|
|
533
623
|
* @param path Path to a project
|
|
534
624
|
* @returns true, if in the given path there is a project; false otherwise
|
|
535
625
|
*/
|
|
@@ -537,33 +627,11 @@ export class Project extends CardContainer {
|
|
|
537
627
|
return pathExists(join(path, 'cardRoot'));
|
|
538
628
|
}
|
|
539
629
|
|
|
540
|
-
/**
|
|
541
|
-
* Returns whether card is a template card or not
|
|
542
|
-
* @param cardKey card to check.
|
|
543
|
-
* @todo: This is only used from 'remove'. Could it use the static checker?
|
|
544
|
-
* @returns true, if card is template card; false otherwise
|
|
545
|
-
*/
|
|
546
|
-
public async isTemplateCard(cardKey: string): Promise<boolean> {
|
|
547
|
-
const templateCards = await this.allTemplateCards();
|
|
548
|
-
return templateCards.find((card) => card.key === cardKey) != null;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
/**
|
|
552
|
-
* Returns an array of all the link types in the project.
|
|
553
|
-
* @param from Defines where resources are collected from.
|
|
554
|
-
* @returns array of all link types in the project.
|
|
555
|
-
*/
|
|
556
|
-
public async linkTypes(
|
|
557
|
-
from: ResourcesFrom = ResourcesFrom.all,
|
|
558
|
-
): Promise<Resource[]> {
|
|
559
|
-
return this.resources.resources('linkTypes', from);
|
|
560
|
-
}
|
|
561
|
-
|
|
562
630
|
/**
|
|
563
631
|
* Returns an array of cards in the project, in the templates or both.
|
|
564
632
|
* Cards don't have content and nor metadata.
|
|
565
633
|
* @param cardsFrom Where to return cards from (project, templates, or both)
|
|
566
|
-
* @returns all cards in the project.
|
|
634
|
+
* @returns all cards in the project per container.
|
|
567
635
|
*/
|
|
568
636
|
public async listCards(
|
|
569
637
|
cardsFrom: CardLocation = CardLocation.all,
|
|
@@ -573,9 +641,9 @@ export class Project extends CardContainer {
|
|
|
573
641
|
cardsFrom === CardLocation.all ||
|
|
574
642
|
cardsFrom === CardLocation.projectOnly
|
|
575
643
|
) {
|
|
576
|
-
const projectCards =
|
|
577
|
-
(
|
|
578
|
-
|
|
644
|
+
const projectCards = super
|
|
645
|
+
.cards(this.paths.cardRootFolder)
|
|
646
|
+
.map((item) => item.key);
|
|
579
647
|
cardListContainer.push({
|
|
580
648
|
name: this.projectName,
|
|
581
649
|
type: 'project',
|
|
@@ -587,18 +655,15 @@ export class Project extends CardContainer {
|
|
|
587
655
|
cardsFrom === CardLocation.all ||
|
|
588
656
|
cardsFrom === CardLocation.templatesOnly
|
|
589
657
|
) {
|
|
590
|
-
const templates =
|
|
658
|
+
const templates = this.resources.templates();
|
|
591
659
|
for (const template of templates) {
|
|
592
|
-
const templateObject =
|
|
593
|
-
this,
|
|
594
|
-
resourceName(template.name),
|
|
595
|
-
).templateObject();
|
|
660
|
+
const templateObject = template.templateObject();
|
|
596
661
|
if (templateObject) {
|
|
597
662
|
// todo: optimization - do all this in parallel
|
|
598
|
-
const templateCards =
|
|
599
|
-
if (templateCards) {
|
|
663
|
+
const templateCards = templateObject.listCards();
|
|
664
|
+
if (templateCards.length) {
|
|
600
665
|
cardListContainer.push({
|
|
601
|
-
name: template.name,
|
|
666
|
+
name: template.data?.name || '',
|
|
602
667
|
type: 'template',
|
|
603
668
|
cards: templateCards.map((item) => item.key),
|
|
604
669
|
});
|
|
@@ -618,142 +683,59 @@ export class Project extends CardContainer {
|
|
|
618
683
|
public async listCardIds(
|
|
619
684
|
cardsFrom: CardLocation = CardLocation.all,
|
|
620
685
|
): Promise<Set<string>> {
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
promises.push(
|
|
627
|
-
super
|
|
628
|
-
.cards(this.paths.cardRootFolder)
|
|
629
|
-
.then((cards) => new Set(cards.map((card) => card.key))),
|
|
630
|
-
);
|
|
631
|
-
}
|
|
632
|
-
if (
|
|
633
|
-
cardsFrom === CardLocation.all ||
|
|
634
|
-
cardsFrom === CardLocation.templatesOnly
|
|
635
|
-
) {
|
|
636
|
-
promises.push(
|
|
637
|
-
(async () => {
|
|
638
|
-
const templates = await this.templates();
|
|
639
|
-
const templateResources = templates.map(
|
|
640
|
-
(template) =>
|
|
641
|
-
new TemplateResource(this, resourceName(template.name)),
|
|
642
|
-
);
|
|
643
|
-
const templateObjectsResults =
|
|
644
|
-
await Promise.allSettled(templateResources);
|
|
645
|
-
const templateObjects = templateObjectsResults
|
|
646
|
-
.filter(
|
|
647
|
-
(result): result is PromiseFulfilledResult<TemplateResource> =>
|
|
648
|
-
result.status === 'fulfilled' && result.value !== null,
|
|
649
|
-
)
|
|
650
|
-
.map((result) => result.value);
|
|
651
|
-
|
|
652
|
-
const listCardsResults = await Promise.allSettled(
|
|
653
|
-
templateObjects.map((obj) => obj.templateObject().listCards()),
|
|
654
|
-
);
|
|
655
|
-
const templateCardIds = new Set<string>();
|
|
656
|
-
listCardsResults
|
|
657
|
-
.filter(
|
|
658
|
-
(result): result is PromiseFulfilledResult<Card[]> =>
|
|
659
|
-
result.status === 'fulfilled',
|
|
660
|
-
)
|
|
661
|
-
.forEach((result) => {
|
|
662
|
-
result.value.forEach((card) => templateCardIds.add(card.key));
|
|
663
|
-
});
|
|
664
|
-
return templateCardIds;
|
|
665
|
-
})(),
|
|
666
|
-
);
|
|
686
|
+
const cardContainers = await this.listCards(cardsFrom);
|
|
687
|
+
const allCardIDs = new Set<string>();
|
|
688
|
+
for (const container of cardContainers) {
|
|
689
|
+
const cards = container.cards;
|
|
690
|
+
cards.forEach((card) => allCardIDs.add(card));
|
|
667
691
|
}
|
|
668
|
-
|
|
669
|
-
return new Set(allCardIdSets.flatMap((set) => [...set]));
|
|
692
|
+
return allCardIDs;
|
|
670
693
|
}
|
|
671
694
|
|
|
672
695
|
/**
|
|
673
696
|
* Returns details of a certain module.
|
|
674
697
|
* @param moduleName Name of the module.
|
|
675
|
-
* @returns module details, or undefined if
|
|
698
|
+
* @returns module details, or undefined if module cannot be found.
|
|
676
699
|
*/
|
|
677
700
|
public async module(moduleName: string): Promise<ModuleContent | undefined> {
|
|
678
701
|
const module = await this.findModule(moduleName);
|
|
679
702
|
if (module && module.path) {
|
|
680
|
-
const modulePath =
|
|
681
|
-
const moduleConfig =
|
|
703
|
+
const modulePath = module.path;
|
|
704
|
+
const moduleConfig = await readJsonFile(
|
|
682
705
|
join(modulePath, Project.projectConfigFileName),
|
|
683
|
-
)
|
|
706
|
+
);
|
|
684
707
|
return {
|
|
685
708
|
name: moduleConfig.name,
|
|
686
709
|
modules: moduleConfig.modules,
|
|
687
710
|
hubs: moduleConfig.hubs,
|
|
688
711
|
path: modulePath,
|
|
689
712
|
cardKeyPrefix: moduleConfig.cardKeyPrefix,
|
|
690
|
-
calculations:
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
moduleName,
|
|
712
|
-
)),
|
|
713
|
-
],
|
|
714
|
-
graphViews: [
|
|
715
|
-
...(await this.resources.collectResourcesFromModules(
|
|
716
|
-
'graphViews',
|
|
717
|
-
moduleName,
|
|
718
|
-
)),
|
|
719
|
-
],
|
|
720
|
-
linkTypes: [
|
|
721
|
-
...(await this.resources.collectResourcesFromModules(
|
|
722
|
-
'linkTypes',
|
|
723
|
-
moduleName,
|
|
724
|
-
)),
|
|
725
|
-
],
|
|
726
|
-
reports: [
|
|
727
|
-
...(await this.resources.collectResourcesFromModules(
|
|
728
|
-
'reports',
|
|
729
|
-
moduleName,
|
|
730
|
-
)),
|
|
731
|
-
],
|
|
732
|
-
templates: [
|
|
733
|
-
...(await this.resources.collectResourcesFromModules(
|
|
734
|
-
'templates',
|
|
735
|
-
moduleName,
|
|
736
|
-
)),
|
|
737
|
-
],
|
|
738
|
-
workflows: [
|
|
739
|
-
...(await this.resources.collectResourcesFromModules(
|
|
740
|
-
'workflows',
|
|
741
|
-
moduleName,
|
|
742
|
-
)),
|
|
743
|
-
],
|
|
713
|
+
calculations: this.resources.moduleResourceNames(
|
|
714
|
+
'calculations',
|
|
715
|
+
moduleName,
|
|
716
|
+
),
|
|
717
|
+
cardTypes: this.resources.moduleResourceNames('cardTypes', moduleName),
|
|
718
|
+
fieldTypes: this.resources.moduleResourceNames(
|
|
719
|
+
'fieldTypes',
|
|
720
|
+
moduleName,
|
|
721
|
+
),
|
|
722
|
+
graphModels: this.resources.moduleResourceNames(
|
|
723
|
+
'graphModels',
|
|
724
|
+
moduleName,
|
|
725
|
+
),
|
|
726
|
+
graphViews: this.resources.moduleResourceNames(
|
|
727
|
+
'graphViews',
|
|
728
|
+
moduleName,
|
|
729
|
+
),
|
|
730
|
+
linkTypes: this.resources.moduleResourceNames('linkTypes', moduleName),
|
|
731
|
+
reports: this.resources.moduleResourceNames('reports', moduleName),
|
|
732
|
+
templates: this.resources.moduleResourceNames('templates', moduleName),
|
|
733
|
+
workflows: this.resources.moduleResourceNames('workflows', moduleName),
|
|
744
734
|
};
|
|
745
735
|
}
|
|
746
736
|
return undefined;
|
|
747
737
|
}
|
|
748
738
|
|
|
749
|
-
/**
|
|
750
|
-
* Returns list of modules in the project.
|
|
751
|
-
* @returns list of modules in the project.
|
|
752
|
-
*/
|
|
753
|
-
public async modules(): Promise<Resource[]> {
|
|
754
|
-
return this.resources.resources('modules');
|
|
755
|
-
}
|
|
756
|
-
|
|
757
739
|
/**
|
|
758
740
|
* Returns a new unique card key with project prefix (e.g. test_x649it4x).
|
|
759
741
|
* Random part of string will be always 8 characters in base-36 (0-9a-z)
|
|
@@ -822,15 +804,16 @@ export class Project extends CardContainer {
|
|
|
822
804
|
}
|
|
823
805
|
|
|
824
806
|
/**
|
|
825
|
-
*
|
|
826
|
-
* @param cardKey card to check path for.
|
|
827
|
-
* @returns path to a given card.
|
|
807
|
+
* Populates the card cache, if it has not been populated.
|
|
828
808
|
*/
|
|
829
|
-
public
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
809
|
+
public async populateCaches() {
|
|
810
|
+
if (!this.cardCache.isPopulated) {
|
|
811
|
+
// Only collect modules that are registered in the project configuration
|
|
812
|
+
if (this.configuration.modules && this.configuration.modules.length > 0) {
|
|
813
|
+
this.resources.changedModules();
|
|
814
|
+
}
|
|
815
|
+
await this.populateCardsCache();
|
|
816
|
+
}
|
|
834
817
|
}
|
|
835
818
|
|
|
836
819
|
/**
|
|
@@ -848,158 +831,76 @@ export class Project extends CardContainer {
|
|
|
848
831
|
}
|
|
849
832
|
|
|
850
833
|
/**
|
|
851
|
-
*
|
|
834
|
+
* Returns all prefixes used in the project (project's own plus all from imported modules).
|
|
852
835
|
* @returns all prefixes used in the project.
|
|
853
|
-
* @todo - move the module prefix fetch to resource-collector.
|
|
854
|
-
* Make it use cached value that is only changed when module is removed/imported.
|
|
855
836
|
*/
|
|
856
|
-
public
|
|
837
|
+
public projectPrefixes(): string[] {
|
|
857
838
|
const prefixes: string[] = [this.projectPrefix];
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
files = await readdir(this.paths.modulesFolder, {
|
|
861
|
-
withFileTypes: true,
|
|
862
|
-
recursive: true,
|
|
863
|
-
});
|
|
864
|
-
const configurationFiles = files
|
|
865
|
-
.filter((dirent) => dirent.isFile())
|
|
866
|
-
.filter((dirent) => dirent.name === Project.projectConfigFileName);
|
|
867
|
-
|
|
868
|
-
const configurationPromises = configurationFiles.map(async (file) => {
|
|
869
|
-
const configuration = (await readJsonFile(
|
|
870
|
-
join(file.parentPath, file.name),
|
|
871
|
-
)) as ProjectSettings;
|
|
872
|
-
return configuration.cardKeyPrefix;
|
|
873
|
-
});
|
|
874
|
-
|
|
875
|
-
const configurationPrefixes = await Promise.all(configurationPromises);
|
|
876
|
-
prefixes.push(...configurationPrefixes);
|
|
877
|
-
} catch {
|
|
878
|
-
// do nothing if readdir throws // TODO: Log it
|
|
879
|
-
}
|
|
839
|
+
const moduleNames = this.configuration.modules.map((item) => item.name);
|
|
840
|
+
prefixes.push(...moduleNames);
|
|
880
841
|
|
|
881
842
|
return prefixes;
|
|
882
843
|
}
|
|
883
844
|
|
|
884
845
|
/**
|
|
885
|
-
*
|
|
886
|
-
* @param
|
|
887
|
-
* @
|
|
846
|
+
* Removes an attachment from a card.
|
|
847
|
+
* @param cardKey The card to remove attachment from
|
|
848
|
+
* @param fileName The name of the attachment file to remove
|
|
849
|
+
* @throws if trying to remove module card attachment, or the attachment was not found.
|
|
888
850
|
*/
|
|
889
|
-
public async
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
851
|
+
public async removeCardAttachment(
|
|
852
|
+
cardKey: string,
|
|
853
|
+
fileName: string,
|
|
854
|
+
): Promise<void> {
|
|
855
|
+
const attachmentFolder = this.cardAttachmentFolder(cardKey);
|
|
894
856
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
* @returns handlebar files from reports.
|
|
899
|
-
*/
|
|
900
|
-
public async reportHandlerBarFiles(from: ResourcesFrom = ResourcesFrom.all) {
|
|
901
|
-
const reports = await this.reports(from);
|
|
902
|
-
const handleBarFiles: string[] = [];
|
|
903
|
-
for (const reportResourceName of reports) {
|
|
904
|
-
const name = resourceName(reportResourceName.name);
|
|
905
|
-
const report = new ReportResource(this, name);
|
|
906
|
-
handleBarFiles.push(...(await report.handleBarFiles()));
|
|
857
|
+
// Modules cannot be modified.
|
|
858
|
+
if (isModulePath(attachmentFolder)) {
|
|
859
|
+
throw new Error(`Cannot modify imported module`);
|
|
907
860
|
}
|
|
908
|
-
return handleBarFiles;
|
|
909
|
-
}
|
|
910
861
|
|
|
911
|
-
|
|
912
|
-
* Removes a resource from Project.
|
|
913
|
-
* @param resource Resource to remove.
|
|
914
|
-
*/
|
|
915
|
-
public removeResource(resource: Resource) {
|
|
916
|
-
this.resources.remove(resource);
|
|
917
|
-
this.createdResources.delete(resource.name);
|
|
918
|
-
}
|
|
862
|
+
const attachmentPath = join(attachmentFolder, fileName);
|
|
919
863
|
|
|
920
|
-
/**
|
|
921
|
-
* Returns metadata from a given resource
|
|
922
|
-
* @param name Name of a resource
|
|
923
|
-
* @returns Metadata from the resource.
|
|
924
|
-
*/
|
|
925
|
-
public async resource<Type>(name: string): Promise<Type | undefined> {
|
|
926
|
-
const resName = resourceName(name);
|
|
927
|
-
if (this.createdResources.has(resourceNameToString(resName))) {
|
|
928
|
-
const value = this.createdResources.get(
|
|
929
|
-
resourceNameToString(resName),
|
|
930
|
-
) as unknown as Type;
|
|
931
|
-
return value;
|
|
932
|
-
}
|
|
933
|
-
let resource = undefined;
|
|
934
864
|
try {
|
|
935
|
-
|
|
936
|
-
} catch {
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
const data = resource?.data as Type;
|
|
940
|
-
if (!data) {
|
|
941
|
-
return undefined;
|
|
865
|
+
await unlink(attachmentPath);
|
|
866
|
+
} catch (error) {
|
|
867
|
+
this.logger.error({ error }, 'Removing card attachment');
|
|
868
|
+
throw new Error(`Attachment not found: ${fileName}`);
|
|
942
869
|
}
|
|
943
|
-
|
|
870
|
+
await this.handleAttachmentChange(cardKey, 'removed', fileName);
|
|
944
871
|
}
|
|
945
872
|
|
|
946
873
|
/**
|
|
947
|
-
*
|
|
874
|
+
* Removes a module from the project cache and configuration.
|
|
875
|
+
* @note that ModuleManager removes the actual files.
|
|
876
|
+
* @param moduleName Module name to remove.
|
|
948
877
|
*/
|
|
949
|
-
public
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
/**
|
|
954
|
-
* Checks if a given resource exists in the project already.
|
|
955
|
-
* @param resourceType Type of resource as a string.
|
|
956
|
-
* @param name Valid name of resource.
|
|
957
|
-
* @returns boolean, true if resource exists; false otherwise.
|
|
958
|
-
*/
|
|
959
|
-
public async resourceExists(
|
|
960
|
-
resourceType: ResourceFolderType,
|
|
961
|
-
name: string,
|
|
962
|
-
): Promise<boolean> {
|
|
963
|
-
const resources = await this.resourcesOfType(
|
|
964
|
-
resourceType,
|
|
965
|
-
ResourcesFrom.all,
|
|
878
|
+
public async removeModule(moduleName: string) {
|
|
879
|
+
const toBeRemovedTemplates = this.resources.moduleResourceNames(
|
|
880
|
+
'templates',
|
|
881
|
+
moduleName,
|
|
966
882
|
);
|
|
967
|
-
|
|
968
|
-
|
|
883
|
+
|
|
884
|
+
// First, remove template cards from the cache that are part of removed templates.
|
|
885
|
+
for (const templateName of toBeRemovedTemplates) {
|
|
886
|
+
this.cardCache.deleteCardsFromTemplate(templateName);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// Then, remove all module resources from cache
|
|
890
|
+
this.resources.removeModule(moduleName);
|
|
891
|
+
|
|
892
|
+
// Finally, remove module from project configuration
|
|
893
|
+
await this.configuration.removeModule(moduleName);
|
|
894
|
+
|
|
895
|
+
this.logger.info(`Removed module '${moduleName}'`);
|
|
969
896
|
}
|
|
970
897
|
|
|
971
898
|
/**
|
|
972
|
-
*
|
|
973
|
-
* @
|
|
974
|
-
* To manipulate the resource (create files, delete files etc), use the resource object's API.
|
|
975
|
-
* @param project Project from which resources are created from.
|
|
976
|
-
* @param name Resource name
|
|
977
|
-
* @throws if called with unsupported resource type.
|
|
978
|
-
* @returns Created resource.
|
|
899
|
+
* Accessor for resource handler.
|
|
900
|
+
* @returns Resource handler instance.
|
|
979
901
|
*/
|
|
980
|
-
public
|
|
981
|
-
|
|
982
|
-
return new CalculationResource(project, name);
|
|
983
|
-
} else if (name.type === 'cardTypes') {
|
|
984
|
-
return new CardTypeResource(project, name);
|
|
985
|
-
} else if (name.type === 'fieldTypes') {
|
|
986
|
-
return new FieldTypeResource(project, name);
|
|
987
|
-
} else if (name.type === 'graphModels') {
|
|
988
|
-
return new GraphModelResource(project, name);
|
|
989
|
-
} else if (name.type === 'graphViews') {
|
|
990
|
-
return new GraphViewResource(project, name);
|
|
991
|
-
} else if (name.type === 'linkTypes') {
|
|
992
|
-
return new LinkTypeResource(project, name);
|
|
993
|
-
} else if (name.type === 'reports') {
|
|
994
|
-
return new ReportResource(project, name);
|
|
995
|
-
} else if (name.type === 'templates') {
|
|
996
|
-
return new TemplateResource(project, name);
|
|
997
|
-
} else if (name.type === 'workflows') {
|
|
998
|
-
return new WorkflowResource(project, name);
|
|
999
|
-
}
|
|
1000
|
-
throw new Error(
|
|
1001
|
-
`Unsupported resource type '${resourceNameToString(name)}'`,
|
|
1002
|
-
);
|
|
902
|
+
public get resources(): ResourceHandler {
|
|
903
|
+
return this.resourceHandler;
|
|
1003
904
|
}
|
|
1004
905
|
|
|
1005
906
|
/**
|
|
@@ -1012,7 +913,7 @@ export class Project extends CardContainer {
|
|
|
1012
913
|
path: this.basePath,
|
|
1013
914
|
prefix: this.projectPrefix,
|
|
1014
915
|
hubs: this.configuration.hubs,
|
|
1015
|
-
modules:
|
|
916
|
+
modules: this.resources.moduleNames(),
|
|
1016
917
|
numberOfCards: (await this.listCards(CardLocation.projectOnly))[0].cards
|
|
1017
918
|
.length,
|
|
1018
919
|
};
|
|
@@ -1022,73 +923,41 @@ export class Project extends CardContainer {
|
|
|
1022
923
|
* Show cards of a project.
|
|
1023
924
|
* @returns an array of all project cards in the project.
|
|
1024
925
|
*/
|
|
1025
|
-
public
|
|
926
|
+
public showProjectCards(): Card[] {
|
|
1026
927
|
return this.showCards(this.paths.cardRootFolder);
|
|
1027
928
|
}
|
|
1028
929
|
|
|
1029
|
-
/**
|
|
1030
|
-
* Returns all template cards from the project. This includes all module templates' cards.
|
|
1031
|
-
* @param cardDetails which details to fetch. Optional.
|
|
1032
|
-
* @returns all the template cards from the project
|
|
1033
|
-
*/
|
|
1034
|
-
public async allTemplateCards(
|
|
1035
|
-
cardDetails?: FetchCardDetails,
|
|
1036
|
-
): Promise<Card[]> {
|
|
1037
|
-
const templates = await this.templates();
|
|
1038
|
-
const cards: Card[] = [];
|
|
1039
|
-
for (const template of templates) {
|
|
1040
|
-
const templateCards = await this.templateCards(
|
|
1041
|
-
template.name,
|
|
1042
|
-
cardDetails,
|
|
1043
|
-
);
|
|
1044
|
-
if (templateCards) cards.push(...templateCards);
|
|
1045
|
-
}
|
|
1046
|
-
return cards;
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
930
|
/**
|
|
1050
931
|
* Returns cards from single template.
|
|
1051
|
-
* @param templateName Name of the template
|
|
1052
|
-
* @param cardDetails Card information
|
|
932
|
+
* @param templateName Name of the template (supports both full names like 'decision/templates/decision' and short names like 'decision')
|
|
1053
933
|
* @returns List of cards from template.
|
|
1054
934
|
*/
|
|
1055
|
-
public
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
)
|
|
1063
|
-
return await templateObject?.cards('', cardDetails);
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
/**
|
|
1067
|
-
* Array of templates in the project.
|
|
1068
|
-
* @param from Defines where resources are collected from.
|
|
1069
|
-
* @returns array of all templates in the project.
|
|
1070
|
-
*/
|
|
1071
|
-
public async templates(
|
|
1072
|
-
from: ResourcesFrom = ResourcesFrom.all,
|
|
1073
|
-
): Promise<Resource[]> {
|
|
1074
|
-
return this.resources.resources('templates', from);
|
|
935
|
+
public templateCards(templateName: string): Card[] {
|
|
936
|
+
const templateCards = this.cardCache.getAllTemplateCards();
|
|
937
|
+
return templateCards.filter((cachedCard) => {
|
|
938
|
+
if (cachedCard.location === 'project') {
|
|
939
|
+
return false;
|
|
940
|
+
}
|
|
941
|
+
return cachedCard.location === templateName;
|
|
942
|
+
});
|
|
1075
943
|
}
|
|
1076
944
|
|
|
1077
945
|
/**
|
|
1078
|
-
* Update card content.
|
|
1079
|
-
* @param cardKey card
|
|
946
|
+
* Update a card's content.
|
|
947
|
+
* @param cardKey card key to update.
|
|
1080
948
|
* @param content changed content
|
|
1081
949
|
*/
|
|
1082
950
|
public async updateCardContent(cardKey: string, content: string) {
|
|
1083
|
-
const card =
|
|
1084
|
-
metadata: true,
|
|
1085
|
-
content: true,
|
|
1086
|
-
});
|
|
1087
|
-
if (!card) {
|
|
1088
|
-
throw new Error(`Card '${cardKey}' does not exist in the project`);
|
|
1089
|
-
}
|
|
951
|
+
const card = this.findCard(cardKey);
|
|
1090
952
|
card.content = content;
|
|
953
|
+
|
|
954
|
+
// Update lastUpdated timestamp in metadata
|
|
955
|
+
if (card.metadata) {
|
|
956
|
+
card.metadata.lastUpdated = new Date().toISOString();
|
|
957
|
+
}
|
|
958
|
+
|
|
1091
959
|
await this.saveCard(card);
|
|
960
|
+
await this.handleCardChanged(card);
|
|
1092
961
|
}
|
|
1093
962
|
|
|
1094
963
|
/**
|
|
@@ -1102,21 +971,15 @@ export class Project extends CardContainer {
|
|
|
1102
971
|
changedKey: string,
|
|
1103
972
|
newValue: MetadataContent,
|
|
1104
973
|
) {
|
|
1105
|
-
const
|
|
1106
|
-
const card = await this.findCard(
|
|
1107
|
-
templateCard ? this.paths.templatesFolder : this.paths.cardRootFolder,
|
|
1108
|
-
cardKey,
|
|
1109
|
-
{
|
|
1110
|
-
metadata: true,
|
|
1111
|
-
},
|
|
1112
|
-
);
|
|
1113
|
-
if (!card) {
|
|
1114
|
-
throw new Error(`Card '${cardKey}' does not exist in the project`);
|
|
1115
|
-
}
|
|
1116
|
-
|
|
974
|
+
const card = this.findCard(cardKey);
|
|
1117
975
|
if (!card.metadata || card.metadata[changedKey] === newValue) {
|
|
1118
976
|
return;
|
|
1119
977
|
}
|
|
978
|
+
|
|
979
|
+
const isRankChange = changedKey === 'rank';
|
|
980
|
+
const previousPath = isRankChange ? card.path : undefined;
|
|
981
|
+
const previousParent = isRankChange ? card.parent : undefined;
|
|
982
|
+
|
|
1120
983
|
const cardAsRecord: Record<string, MetadataContent> = card.metadata;
|
|
1121
984
|
cardAsRecord[changedKey] = newValue;
|
|
1122
985
|
|
|
@@ -1127,63 +990,58 @@ export class Project extends CardContainer {
|
|
|
1127
990
|
throw new Error(invalidCard);
|
|
1128
991
|
}
|
|
1129
992
|
|
|
1130
|
-
await this.saveCardMetadata(card);
|
|
1131
|
-
|
|
993
|
+
const updated = await this.saveCardMetadata(card);
|
|
994
|
+
if (!updated) return;
|
|
1132
995
|
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
return this.saveCardMetadata(card);
|
|
996
|
+
// For rank changes, check if path changed (indicating a move)
|
|
997
|
+
if (isRankChange) {
|
|
998
|
+
const updatedCard = this.findCard(cardKey);
|
|
999
|
+
if (updatedCard.path !== previousPath) {
|
|
1000
|
+
this.changeParent(updatedCard, previousParent);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1141
1003
|
}
|
|
1142
1004
|
|
|
1143
1005
|
/**
|
|
1144
|
-
*
|
|
1145
|
-
*
|
|
1146
|
-
* @
|
|
1006
|
+
* Updates the entire card in the card cache and handles any path/parent changes.
|
|
1007
|
+
* Also persists changes to content and metadata files.
|
|
1008
|
+
* @param card The card with updated information (path, parent, metadata, etc.)
|
|
1147
1009
|
*/
|
|
1148
|
-
public async
|
|
1149
|
-
const
|
|
1150
|
-
|
|
1151
|
-
card,
|
|
1152
|
-
);
|
|
1153
|
-
const invalidWorkFlow = await this.validator.validateWorkflowState(
|
|
1154
|
-
this,
|
|
1155
|
-
card,
|
|
1156
|
-
);
|
|
1010
|
+
public async updateCard(card: Card) {
|
|
1011
|
+
const cachedCard = this.cardCache.getCard(card.key);
|
|
1012
|
+
const pathChange = cachedCard && cachedCard.path !== card.path;
|
|
1157
1013
|
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
invalidCustomData.length === 0 &&
|
|
1161
|
-
invalidWorkFlow.length === 0 &&
|
|
1162
|
-
invalidLabels.length === 0
|
|
1163
|
-
) {
|
|
1164
|
-
return '';
|
|
1014
|
+
if (pathChange) {
|
|
1015
|
+
this.changeParent(card, cachedCard.parent);
|
|
1165
1016
|
}
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1017
|
+
|
|
1018
|
+
const metadataChanged =
|
|
1019
|
+
cachedCard &&
|
|
1020
|
+
JSON.stringify(cachedCard.metadata) !== JSON.stringify(card.metadata);
|
|
1021
|
+
if (metadataChanged) {
|
|
1022
|
+
await this.saveCardMetadata(card);
|
|
1169
1023
|
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1024
|
+
|
|
1025
|
+
const contentChanged = cachedCard && cachedCard.content !== card.content;
|
|
1026
|
+
if (contentChanged) {
|
|
1027
|
+
await this.saveCardContent(card);
|
|
1172
1028
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1029
|
+
|
|
1030
|
+
this.cardCache.updateCard(card.key, card);
|
|
1031
|
+
if (metadataChanged || contentChanged || pathChange) {
|
|
1032
|
+
await this.handleCardChanged(card);
|
|
1175
1033
|
}
|
|
1176
|
-
return errors.join('\n');
|
|
1177
1034
|
}
|
|
1178
1035
|
|
|
1179
1036
|
/**
|
|
1180
|
-
*
|
|
1181
|
-
* @param
|
|
1182
|
-
* @
|
|
1037
|
+
* Updates a card's metadata.
|
|
1038
|
+
* @param card affected card
|
|
1039
|
+
* @param changedMetadata changed content for the card
|
|
1183
1040
|
*/
|
|
1184
|
-
public async
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1041
|
+
public async updateCardMetadata(card: Card, changedMetadata: CardMetadata) {
|
|
1042
|
+
card.metadata = changedMetadata;
|
|
1043
|
+
if (await this.saveCardMetadata(card)) {
|
|
1044
|
+
await this.handleCardChanged(card);
|
|
1045
|
+
}
|
|
1188
1046
|
}
|
|
1189
1047
|
}
|