@cyberismo/data-handler 0.0.13 → 0.0.15
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 +1 -3
- package/dist/card-metadata-updater.js.map +1 -1
- package/dist/command-handler.js +13 -17
- package/dist/command-handler.js.map +1 -1
- package/dist/command-manager.d.ts +1 -1
- package/dist/command-manager.js +4 -3
- package/dist/command-manager.js.map +1 -1
- package/dist/commands/create.d.ts +3 -3
- package/dist/commands/create.js +20 -81
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/edit.d.ts +12 -25
- package/dist/commands/edit.js +25 -74
- package/dist/commands/edit.js.map +1 -1
- package/dist/commands/export.js +4 -17
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/fetch.js +2 -1
- package/dist/commands/fetch.js.map +1 -1
- package/dist/commands/import.js +3 -5
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/move.d.ts +1 -2
- package/dist/commands/move.js +108 -146
- package/dist/commands/move.js.map +1 -1
- package/dist/commands/remove.js +15 -49
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/rename.d.ts +1 -0
- package/dist/commands/rename.js +13 -7
- package/dist/commands/rename.js.map +1 -1
- package/dist/commands/show.d.ts +7 -25
- package/dist/commands/show.js +39 -113
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/transition.js +27 -30
- package/dist/commands/transition.js.map +1 -1
- package/dist/commands/update.d.ts +5 -3
- package/dist/commands/update.js +19 -5
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/validate.d.ts +3 -3
- package/dist/commands/validate.js +20 -27
- 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 +6 -0
- package/dist/containers/project/calculation-engine.js +36 -29
- 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/resource-collector.d.ts +24 -1
- package/dist/containers/project/resource-collector.js +8 -1
- package/dist/containers/project/resource-collector.js.map +1 -1
- package/dist/containers/project.d.ts +119 -84
- package/dist/containers/project.js +423 -253
- package/dist/containers/project.js.map +1 -1
- package/dist/containers/template.d.ts +15 -31
- package/dist/containers/template.js +97 -104
- 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 +12 -5
- package/dist/interfaces/folder-content-interfaces.js +5 -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 +16 -10
- package/dist/interfaces/project-interfaces.js +10 -8
- package/dist/interfaces/project-interfaces.js.map +1 -1
- package/dist/interfaces/resource-interfaces.d.ts +21 -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 +2 -2
- 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/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.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.js +4 -4
- package/dist/module-manager.js.map +1 -1
- package/dist/project-settings.js.map +1 -1
- package/dist/resources/calculation-resource.d.ts +43 -0
- package/dist/resources/calculation-resource.js +75 -0
- package/dist/resources/calculation-resource.js.map +1 -0
- package/dist/resources/card-type-resource.d.ts +4 -21
- package/dist/resources/card-type-resource.js +13 -44
- package/dist/resources/card-type-resource.js.map +1 -1
- package/dist/resources/create-defaults.d.ts +13 -6
- package/dist/resources/create-defaults.js +19 -5
- package/dist/resources/create-defaults.js.map +1 -1
- package/dist/resources/field-type-resource.d.ts +4 -21
- package/dist/resources/field-type-resource.js +14 -38
- package/dist/resources/field-type-resource.js.map +1 -1
- package/dist/resources/file-resource.d.ts +12 -29
- package/dist/resources/file-resource.js +19 -287
- package/dist/resources/file-resource.js.map +1 -1
- package/dist/resources/folder-resource.d.ts +32 -51
- package/dist/resources/folder-resource.js +68 -96
- package/dist/resources/folder-resource.js.map +1 -1
- package/dist/resources/graph-model-resource.d.ts +5 -33
- package/dist/resources/graph-model-resource.js +8 -61
- package/dist/resources/graph-model-resource.js.map +1 -1
- package/dist/resources/graph-view-resource.d.ts +5 -28
- package/dist/resources/graph-view-resource.js +6 -45
- package/dist/resources/graph-view-resource.js.map +1 -1
- package/dist/resources/link-type-resource.d.ts +4 -21
- package/dist/resources/link-type-resource.js +6 -31
- package/dist/resources/link-type-resource.js.map +1 -1
- package/dist/resources/report-resource.d.ts +5 -17
- package/dist/resources/report-resource.js +6 -44
- package/dist/resources/report-resource.js.map +1 -1
- package/dist/resources/resource-object.d.ts +58 -23
- package/dist/resources/resource-object.js +307 -26
- package/dist/resources/resource-object.js.map +1 -1
- package/dist/resources/template-resource.d.ts +4 -15
- package/dist/resources/template-resource.js +10 -25
- package/dist/resources/template-resource.js.map +1 -1
- package/dist/resources/workflow-resource.d.ts +4 -23
- package/dist/resources/workflow-resource.js +12 -38
- 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-facts.js +11 -3
- 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 +5 -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/error-utils.d.ts +34 -0
- package/dist/utils/error-utils.js +56 -0
- package/dist/utils/error-utils.js.map +1 -0
- package/dist/utils/log-utils.d.ts +0 -27
- package/dist/utils/log-utils.js +0 -58
- package/dist/utils/log-utils.js.map +1 -1
- package/dist/utils/user-preferences.js +6 -3
- package/dist/utils/user-preferences.js.map +1 -1
- package/package.json +5 -5
- package/src/card-metadata-updater.ts +3 -5
- package/src/command-handler.ts +14 -19
- package/src/command-manager.ts +4 -3
- package/src/commands/create.ts +28 -112
- package/src/commands/edit.ts +27 -118
- package/src/commands/export.ts +8 -29
- package/src/commands/fetch.ts +2 -1
- package/src/commands/import.ts +4 -6
- package/src/commands/move.ts +144 -179
- package/src/commands/remove.ts +12 -54
- package/src/commands/rename.ts +22 -7
- package/src/commands/show.ts +51 -156
- package/src/commands/transition.ts +30 -33
- package/src/commands/update.ts +27 -9
- package/src/commands/validate.ts +22 -37
- package/src/containers/card-container.ts +200 -360
- package/src/containers/project/calculation-engine.ts +43 -33
- package/src/containers/project/card-cache.ts +497 -0
- package/src/containers/project/resource-collector.ts +9 -1
- package/src/containers/project.ts +533 -328
- package/src/containers/template.ts +109 -127
- package/src/index.ts +1 -0
- package/src/interfaces/folder-content-interfaces.ts +23 -5
- package/src/interfaces/macros.ts +2 -0
- package/src/interfaces/project-interfaces.ts +19 -10
- package/src/interfaces/resource-interfaces.ts +22 -24
- package/src/macros/createCards/index.ts +1 -12
- package/src/macros/createCards/types.ts +46 -0
- package/src/macros/graph/index.ts +3 -7
- 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/percentage/index.ts +1 -7
- package/src/macros/percentage/types.ts +32 -0
- package/src/macros/report/index.ts +1 -4
- 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 +5 -5
- package/src/project-settings.ts +1 -1
- package/src/resources/calculation-resource.ts +101 -0
- package/src/resources/card-type-resource.ts +24 -59
- package/src/resources/create-defaults.ts +21 -5
- package/src/resources/field-type-resource.ts +22 -51
- package/src/resources/file-resource.ts +27 -403
- package/src/resources/folder-resource.ts +99 -125
- package/src/resources/graph-model-resource.ts +17 -74
- package/src/resources/graph-view-resource.ts +14 -54
- package/src/resources/link-type-resource.ts +13 -40
- package/src/resources/report-resource.ts +17 -57
- package/src/resources/resource-object.ts +454 -39
- package/src/resources/template-resource.ts +16 -29
- package/src/resources/workflow-resource.ts +26 -50
- package/src/utils/card-utils.ts +217 -31
- package/src/utils/clingo-facts.ts +13 -3
- package/src/utils/clingo-parser.ts +1 -1
- package/src/utils/constants.ts +7 -0
- package/src/utils/csv.ts +1 -1
- package/src/utils/error-utils.ts +62 -0
- package/src/utils/log-utils.ts +0 -68
- package/src/utils/user-preferences.ts +7 -3
|
@@ -12,20 +12,21 @@
|
|
|
12
12
|
License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
13
13
|
*/
|
|
14
14
|
// node
|
|
15
|
-
import {
|
|
16
|
-
import { readdir } from 'node:fs/promises';
|
|
15
|
+
import { basename, join, resolve } from 'node:path';
|
|
16
|
+
import { constants as fsConstants, copyFile, mkdir, readdir, unlink, writeFile, } from 'node:fs/promises';
|
|
17
17
|
import { CardContainer } from './card-container.js'; // base class
|
|
18
18
|
import { CalculationEngine } from './project/calculation-engine.js';
|
|
19
19
|
import { CardLocation, } from '../interfaces/project-interfaces.js';
|
|
20
|
-
import {
|
|
20
|
+
import { pathExists } from '../utils/file-utils.js';
|
|
21
21
|
import { generateRandomString } from '../utils/random.js';
|
|
22
|
-
import { isTemplateCard } from '../utils/card-utils.js';
|
|
22
|
+
import { cardPathParts, isModulePath, isTemplateCard, } from '../utils/card-utils.js';
|
|
23
23
|
import { ProjectConfiguration } from '../project-settings.js';
|
|
24
24
|
import { ProjectPaths } from './project/project-paths.js';
|
|
25
25
|
import { readJsonFile } from '../utils/json.js';
|
|
26
|
-
import { resourceName, resourceNameToString, } from '../utils/resource-utils.js';
|
|
26
|
+
import { pathToResourceName, resourceName, resourceNameToString, } from '../utils/resource-utils.js';
|
|
27
27
|
import { ResourcesFrom, ResourceCollector, } from './project/resource-collector.js';
|
|
28
28
|
import { Validate } from '../commands/validate.js';
|
|
29
|
+
import { CalculationResource } from '../resources/calculation-resource.js';
|
|
29
30
|
import { CardTypeResource } from '../resources/card-type-resource.js';
|
|
30
31
|
import { FieldTypeResource } from '../resources/field-type-resource.js';
|
|
31
32
|
import { GraphModelResource } from '../resources/graph-model-resource.js';
|
|
@@ -35,7 +36,8 @@ import { ReportResource } from '../resources/report-resource.js';
|
|
|
35
36
|
import { TemplateResource } from '../resources/template-resource.js';
|
|
36
37
|
import { WorkflowResource } from '../resources/workflow-resource.js';
|
|
37
38
|
import { ContentWatcher } from './project/project-content-watcher.js';
|
|
38
|
-
import {
|
|
39
|
+
import { getChildLogger } from '../utils/log-utils.js';
|
|
40
|
+
import { ROOT } from '../utils/constants.js';
|
|
39
41
|
// Re-export this, so that classes that use Project do not need to have separate import.
|
|
40
42
|
export { ResourcesFrom };
|
|
41
43
|
/**
|
|
@@ -44,25 +46,30 @@ export { ResourcesFrom };
|
|
|
44
46
|
export class Project extends CardContainer {
|
|
45
47
|
watchResourceChanges;
|
|
46
48
|
calculationEngine;
|
|
47
|
-
resources;
|
|
48
|
-
projectPaths;
|
|
49
|
-
settings;
|
|
50
|
-
validator;
|
|
51
|
-
resourceWatcher;
|
|
52
49
|
// Created resources are held in a cache.
|
|
53
50
|
// In the cache, key is resource name, and data is resource metadata (as JSON).
|
|
54
51
|
createdResources = new Map();
|
|
52
|
+
logger = getChildLogger({ module: 'Project' });
|
|
53
|
+
projectPaths;
|
|
54
|
+
resources;
|
|
55
|
+
resourceWatcher;
|
|
56
|
+
settings;
|
|
57
|
+
validator;
|
|
55
58
|
constructor(path, watchResourceChanges) {
|
|
56
|
-
|
|
59
|
+
const settings = new ProjectConfiguration(join(path, '.cards', 'local', Project.projectConfigFileName));
|
|
60
|
+
super(path, settings.cardKeyPrefix, '');
|
|
57
61
|
this.watchResourceChanges = watchResourceChanges;
|
|
62
|
+
this.settings = settings;
|
|
63
|
+
this.logger.info({ path }, 'Initializing project');
|
|
58
64
|
this.calculationEngine = new CalculationEngine(this);
|
|
59
|
-
this.settings = new ProjectConfiguration(join(path, '.cards', 'local', Project.projectConfigFileName));
|
|
60
65
|
this.projectPaths = new ProjectPaths(path);
|
|
61
66
|
this.resources = new ResourceCollector(this);
|
|
62
67
|
this.containerName = this.settings.name;
|
|
63
68
|
// todo: implement project validation
|
|
64
69
|
this.validator = Validate.getInstance();
|
|
70
|
+
this.logger.info({ resourcesFolder: this.paths.resourcesFolder }, 'Collecting local resources');
|
|
65
71
|
this.resources.collectLocalResources();
|
|
72
|
+
this.logger.info({ name: this.containerName }, 'Project initialization complete');
|
|
66
73
|
const ignoreRenameFileChanges = true;
|
|
67
74
|
// Watch changes in .cards if there are multiple instances of Project being
|
|
68
75
|
// run concurrently.
|
|
@@ -87,7 +94,48 @@ export class Project extends CardContainer {
|
|
|
87
94
|
});
|
|
88
95
|
}
|
|
89
96
|
}
|
|
90
|
-
//
|
|
97
|
+
// Changes a card's parent in the cache and updates all relationships.
|
|
98
|
+
changeParent(updatedCard, previousParent) {
|
|
99
|
+
if (previousParent && previousParent !== ROOT) {
|
|
100
|
+
this.removeCachedChildren(previousParent, updatedCard.key);
|
|
101
|
+
}
|
|
102
|
+
if (updatedCard.parent && updatedCard.parent !== ROOT) {
|
|
103
|
+
this.updateCachedChildren(updatedCard.parent, updatedCard);
|
|
104
|
+
}
|
|
105
|
+
this.cardCache.updateCard(updatedCard.key, updatedCard);
|
|
106
|
+
}
|
|
107
|
+
// Finds specific module.
|
|
108
|
+
async findModule(moduleName) {
|
|
109
|
+
return (await this.resources.resources('modules')).find((item) => item.name === moduleName && item.path);
|
|
110
|
+
}
|
|
111
|
+
// Handles attachment changes after filesystem operations.
|
|
112
|
+
async handleAttachmentChange(cardKey, operation, fileName) {
|
|
113
|
+
if (operation === 'added') {
|
|
114
|
+
this.cardCache.addAttachment(cardKey, fileName);
|
|
115
|
+
}
|
|
116
|
+
else if (operation === 'removed') {
|
|
117
|
+
this.cardCache.deleteAttachment(cardKey, fileName);
|
|
118
|
+
}
|
|
119
|
+
else if (operation === 'refresh') {
|
|
120
|
+
const newAttachments = this.cardCache.getCardAttachments(cardKey);
|
|
121
|
+
if (newAttachments) {
|
|
122
|
+
this.cardCache.updateCardAttachments(cardKey, newAttachments);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Determines the parent card key from a card's filesystem path.
|
|
127
|
+
parentFromPath(cardPath) {
|
|
128
|
+
return cardPathParts(this.projectPrefix, cardPath).parents.at(-1) || 'root';
|
|
129
|
+
}
|
|
130
|
+
// Remove children from a card in the card cache
|
|
131
|
+
removeCachedChildren(parentKey, childKey) {
|
|
132
|
+
const parentCard = this.cardCache.getCard(parentKey);
|
|
133
|
+
if (parentCard && parentCard.children) {
|
|
134
|
+
parentCard.children = parentCard.children.filter((child) => child !== childKey);
|
|
135
|
+
this.cardCache.updateCard(parentCard.key, parentCard);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Removes current version of a resource from the resource cache.
|
|
91
139
|
// Then re-creates the resource with current data and caches the value again.
|
|
92
140
|
// If the value wasn't in the cache before, it will be added.
|
|
93
141
|
async replaceCacheValue(resourceName) {
|
|
@@ -96,19 +144,65 @@ export class Project extends CardContainer {
|
|
|
96
144
|
this.createdResources.delete(resourceName);
|
|
97
145
|
}
|
|
98
146
|
const resourceData = await this.resource(resourceName);
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
// Finds specific module.
|
|
104
|
-
async findModule(moduleName) {
|
|
105
|
-
return (await this.resources.resources('modules')).find((item) => item.name === moduleName && item.path);
|
|
147
|
+
if (resourceData) {
|
|
148
|
+
this.createdResources.set(resourceName, resourceData);
|
|
149
|
+
}
|
|
106
150
|
}
|
|
107
151
|
// Returns (local or all) resources of a given type.
|
|
108
|
-
// @todo: if this would be public, we could remove cardTypes(), fieldTypes(), ... and similar APIs
|
|
109
152
|
async resourcesOfType(type, from = ResourcesFrom.localOnly) {
|
|
110
153
|
return this.resources.resources(type, from);
|
|
111
154
|
}
|
|
155
|
+
// Updates children in the card cache
|
|
156
|
+
updateCachedChildren(parentKey, newChild) {
|
|
157
|
+
const parentCard = this.cardCache.getCard(parentKey);
|
|
158
|
+
if (parentCard) {
|
|
159
|
+
// Add or update the child in the parent's children array
|
|
160
|
+
const existingChildIndex = parentCard.children?.findIndex((child) => child === newChild.key);
|
|
161
|
+
if (existingChildIndex === -1) {
|
|
162
|
+
parentCard.children.push(newChild.key);
|
|
163
|
+
}
|
|
164
|
+
this.cardCache.updateCard(parentCard.key, parentCard);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Populate template cards into the card cache.
|
|
169
|
+
*/
|
|
170
|
+
async populateTemplateCards() {
|
|
171
|
+
try {
|
|
172
|
+
// Gets local & module templates
|
|
173
|
+
const templateResources = await this.templates();
|
|
174
|
+
const prefixes = await this.projectPrefixes();
|
|
175
|
+
const loadPromises = templateResources.map(async (template) => {
|
|
176
|
+
try {
|
|
177
|
+
this.validator.validResourceName('templates', template.name, prefixes);
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
this.logger.warn({ templateName: template.name, error }, `Template name '${template.name}' does not follow required format, skipping`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const templateResource = new TemplateResource(this, resourceName(template.name));
|
|
184
|
+
const templateObject = templateResource.templateObject();
|
|
185
|
+
const isCreated = templateObject && templateObject.isCreated();
|
|
186
|
+
if (!templateObject || !isCreated) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
await this.cardCache.populateFromPath(templateObject.templateCardsFolder(), false);
|
|
190
|
+
});
|
|
191
|
+
await Promise.all(loadPromises);
|
|
192
|
+
// Once all templates have been fetched, build child-parent relationships.
|
|
193
|
+
this.cardCache.populateChildrenRelationships();
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
this.logger.error({ error }, 'Failed to populate template cards into the card cache');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Populate both the project cards, and all template cards into card cache.
|
|
201
|
+
*/
|
|
202
|
+
async populateCardsCache() {
|
|
203
|
+
await this.cardCache.populateFromPath(this.paths.cardRootFolder);
|
|
204
|
+
await this.populateTemplateCards();
|
|
205
|
+
}
|
|
112
206
|
/**
|
|
113
207
|
* Add a given 'resource' to the local resource arrays.
|
|
114
208
|
* @param resource Resource to add.
|
|
@@ -118,13 +212,45 @@ export class Project extends CardContainer {
|
|
|
118
212
|
this.resources.add(resource);
|
|
119
213
|
this.createdResources.set(resource.name, data);
|
|
120
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Returns all template cards from the project. This includes all module templates' cards.
|
|
217
|
+
* @returns all the template cards from the project
|
|
218
|
+
*/
|
|
219
|
+
allTemplateCards() {
|
|
220
|
+
return this.cardCache.getAllTemplateCards();
|
|
221
|
+
}
|
|
121
222
|
/**
|
|
122
223
|
* Returns an array of all the attachments in the project card's (excluding ones in templates).
|
|
123
224
|
* @returns all attachments in the project.
|
|
124
225
|
*/
|
|
125
|
-
|
|
226
|
+
attachments() {
|
|
126
227
|
return super.attachments(this.paths.cardRootFolder);
|
|
127
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Returns attachments from cards at a specific path using the card cache.
|
|
231
|
+
* This method allows templates to access attachments from the shared cache.
|
|
232
|
+
* @param path The path to get attachments from
|
|
233
|
+
* @returns Array of attachments from cards at the specified path
|
|
234
|
+
*/
|
|
235
|
+
attachmentsByPath(path) {
|
|
236
|
+
return super.attachments(path);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Returns all the attachments in the template cards.
|
|
240
|
+
* @returns all the attachments in the template cards.
|
|
241
|
+
*/
|
|
242
|
+
async attachmentsFromTemplates() {
|
|
243
|
+
const templateAttachments = [];
|
|
244
|
+
const templates = await this.templates();
|
|
245
|
+
for (const template of templates) {
|
|
246
|
+
const templateResource = new TemplateResource(this, resourceName(template.name));
|
|
247
|
+
const templateObject = templateResource.templateObject();
|
|
248
|
+
if (templateObject) {
|
|
249
|
+
templateAttachments.push(...templateObject.attachments());
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return templateAttachments;
|
|
253
|
+
}
|
|
128
254
|
/**
|
|
129
255
|
* Returns an array of all the calculation files (*.lp) in the project.
|
|
130
256
|
* @param from Defines where resources are collected from.
|
|
@@ -134,117 +260,119 @@ export class Project extends CardContainer {
|
|
|
134
260
|
return this.resources.resources('calculations', from);
|
|
135
261
|
}
|
|
136
262
|
/**
|
|
137
|
-
* Returns path to card's attachment folder.
|
|
263
|
+
* Returns path to a card's attachment folder.
|
|
138
264
|
* @param cardKey card key
|
|
139
|
-
* @returns path to card's attachment folder.
|
|
140
|
-
* @throws if card path cannot be found
|
|
265
|
+
* @returns path to a card's attachment folder.
|
|
141
266
|
*/
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
267
|
+
cardAttachmentFolder(cardKey) {
|
|
268
|
+
const pathToCard = this.findCard(cardKey).path;
|
|
269
|
+
return join(pathToCard, 'a');
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Creates an attachment for a card.
|
|
273
|
+
* @param cardKey The card to add attachment to
|
|
274
|
+
* @param attachmentName The name for the attachment file
|
|
275
|
+
* @param attachmentData The attachment data (file path or buffer)
|
|
276
|
+
* @throws If trying to add attachment to module card, or if attachment is not found
|
|
277
|
+
*/
|
|
278
|
+
async createCardAttachment(cardKey, attachmentName, attachmentData) {
|
|
279
|
+
const attachmentFolder = this.cardAttachmentFolder(cardKey);
|
|
280
|
+
// Check if this is a module template
|
|
281
|
+
if (isModulePath(attachmentFolder)) {
|
|
282
|
+
throw new Error(`Cannot modify imported module`);
|
|
147
283
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
284
|
+
// Create the attachment folder if it doesn't exist
|
|
285
|
+
await mkdir(attachmentFolder, { recursive: true });
|
|
286
|
+
const attachmentPath = join(attachmentFolder, basename(attachmentName));
|
|
287
|
+
if (Buffer.isBuffer(attachmentData)) {
|
|
288
|
+
await writeFile(attachmentPath, attachmentData, { flag: 'wx' });
|
|
151
289
|
}
|
|
152
|
-
|
|
290
|
+
else {
|
|
291
|
+
try {
|
|
292
|
+
await copyFile(attachmentData, attachmentPath, fsConstants.COPYFILE_EXCL);
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
throw new Error(`Attachment file not found: ${attachmentData}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// Update cache
|
|
299
|
+
await this.handleAttachmentChange(cardKey, 'added', basename(attachmentName));
|
|
153
300
|
}
|
|
154
301
|
/**
|
|
155
|
-
*
|
|
156
|
-
* @param cardKey card
|
|
157
|
-
* @param
|
|
158
|
-
* @
|
|
302
|
+
* Removes an attachment from a card.
|
|
303
|
+
* @param cardKey The card to remove attachment from
|
|
304
|
+
* @param fileName The name of the attachment file to remove
|
|
305
|
+
* @throws if trying to remove module card attachment, or the attachment was not found.
|
|
159
306
|
*/
|
|
160
|
-
async
|
|
161
|
-
|
|
307
|
+
async removeCardAttachment(cardKey, fileName) {
|
|
308
|
+
const attachmentFolder = this.cardAttachmentFolder(cardKey);
|
|
309
|
+
// Modules cannot be modified.
|
|
310
|
+
if (isModulePath(attachmentFolder)) {
|
|
311
|
+
throw new Error(`Cannot modify imported module`);
|
|
312
|
+
}
|
|
313
|
+
const attachmentPath = join(attachmentFolder, fileName);
|
|
314
|
+
try {
|
|
315
|
+
await unlink(attachmentPath);
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
this.logger.error({ error }, 'Removing card attachment');
|
|
319
|
+
throw new Error(`Attachment not found: ${fileName}`);
|
|
320
|
+
}
|
|
321
|
+
await this.handleAttachmentChange(cardKey, 'removed', fileName);
|
|
162
322
|
}
|
|
163
323
|
/**
|
|
164
|
-
* Returns path to card's folder.
|
|
324
|
+
* Returns path to a card's folder.
|
|
165
325
|
* @param cardKey card key
|
|
166
|
-
* @returns path to card's folder.
|
|
326
|
+
* @returns path to a card's folder.
|
|
167
327
|
*/
|
|
168
328
|
async cardFolder(cardKey) {
|
|
169
|
-
const found =
|
|
329
|
+
const found = super.findCard(cardKey);
|
|
170
330
|
if (found) {
|
|
171
331
|
return found.path;
|
|
172
332
|
}
|
|
173
333
|
const templates = await this.templates();
|
|
174
|
-
const templatePromises = templates.map(
|
|
334
|
+
const templatePromises = templates.map((template) => {
|
|
175
335
|
const templateObject = new TemplateResource(this, resourceName(template.name)).templateObject();
|
|
176
336
|
const templateCard = templateObject
|
|
177
|
-
?
|
|
337
|
+
? templateObject.findCard(cardKey)
|
|
178
338
|
: undefined;
|
|
179
|
-
|
|
339
|
+
const path = templateCard ? templateCard.path : '';
|
|
340
|
+
return path;
|
|
180
341
|
});
|
|
181
342
|
const templatePaths = await Promise.all(templatePromises);
|
|
182
343
|
return templatePaths.find((path) => path !== '') || '';
|
|
183
344
|
}
|
|
184
345
|
/**
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
* @
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const parents = [];
|
|
196
|
-
let prefix = this.projectPrefix;
|
|
197
|
-
let template = '';
|
|
198
|
-
let startIndex = -1;
|
|
199
|
-
let templatesNameIndex = -1;
|
|
200
|
-
const cardRootIndex = pathParts.indexOf('cardRoot');
|
|
201
|
-
const projectInternalsIndex = pathParts.indexOf('.cards');
|
|
202
|
-
if (projectInternalsIndex === -1 && cardRootIndex >= 0) {
|
|
203
|
-
startIndex = projectInternalsIndex;
|
|
204
|
-
}
|
|
205
|
-
else if (projectInternalsIndex >= 0 && cardRootIndex === -1) {
|
|
206
|
-
const templatesIndex = pathParts.indexOf('templates');
|
|
207
|
-
startIndex = templatesIndex;
|
|
208
|
-
if (templatesIndex === -1) {
|
|
209
|
-
throw new Error(`Invalid card path. Template card must have 'templates' in path`);
|
|
210
|
-
}
|
|
211
|
-
const modulesIndex = pathParts.indexOf('modules');
|
|
212
|
-
if (modulesIndex !== -1) {
|
|
213
|
-
prefix = pathParts.at(modulesIndex + 1) || '';
|
|
214
|
-
}
|
|
215
|
-
templatesNameIndex = templatesIndex + 1;
|
|
216
|
-
template = `${prefix}/templates/${pathParts.at(templatesNameIndex)}`;
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
throw new Error(`Card must be either project card, or template card`);
|
|
220
|
-
}
|
|
221
|
-
// Look for parents in the path.
|
|
222
|
-
let previousWasParent = false;
|
|
223
|
-
for (let index = startIndex; index <= pathParts.length; index++) {
|
|
224
|
-
if (previousWasParent) {
|
|
225
|
-
previousWasParent = false;
|
|
226
|
-
parents.push(pathParts.at(index - 2));
|
|
227
|
-
}
|
|
228
|
-
const cardsSubFolder = pathParts.at(index) === 'c';
|
|
229
|
-
const ignoreOrNotTemplatesParent = index - 1 !== templatesNameIndex || templatesNameIndex === -1;
|
|
230
|
-
if (cardsSubFolder && ignoreOrNotTemplatesParent) {
|
|
231
|
-
previousWasParent = true;
|
|
346
|
+
* Fetches full Card data for given card keys
|
|
347
|
+
* @param cardIds array of card keys to fetch
|
|
348
|
+
* @returns Card data to the given card keys
|
|
349
|
+
*/
|
|
350
|
+
cardKeysToCards(cardIds) {
|
|
351
|
+
const cards = [];
|
|
352
|
+
for (const cardId of cardIds) {
|
|
353
|
+
const card = this.cardCache.getCard(cardId);
|
|
354
|
+
if (card) {
|
|
355
|
+
cards.push(card);
|
|
232
356
|
}
|
|
233
357
|
}
|
|
234
|
-
return
|
|
235
|
-
cardKey: cardKey,
|
|
236
|
-
parents: parents,
|
|
237
|
-
prefix: prefix,
|
|
238
|
-
template: template,
|
|
239
|
-
};
|
|
358
|
+
return cards;
|
|
240
359
|
}
|
|
241
360
|
/**
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
* @
|
|
361
|
+
* Accessor for cards cache.
|
|
362
|
+
* Used by template container (it needs to access project's cache, not their own instance).
|
|
363
|
+
* @note Should not be used directly (other than Template).
|
|
364
|
+
*/
|
|
365
|
+
get cardsCache() {
|
|
366
|
+
return this.cardCache;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Returns an array of all the cards in the project.
|
|
370
|
+
* @note These are project cards only, by default (unless path dictates otherwise).
|
|
371
|
+
* @param path Path from which to fetch the cards. Generally it is best to fetch from Project root, e.g. Project.cardRootFolder
|
|
372
|
+
* @param details Which details to include in the cards; by default all details are included.
|
|
245
373
|
* @returns all cards from the given path in the project.
|
|
246
374
|
*/
|
|
247
|
-
|
|
375
|
+
cards(path = this.paths.cardRootFolder, details) {
|
|
248
376
|
return super.cards(path, details);
|
|
249
377
|
}
|
|
250
378
|
/**
|
|
@@ -255,6 +383,21 @@ export class Project extends CardContainer {
|
|
|
255
383
|
async cardTypes(from = ResourcesFrom.all) {
|
|
256
384
|
return this.resources.resources('cardTypes', from);
|
|
257
385
|
}
|
|
386
|
+
/**
|
|
387
|
+
* Returns children of a given card; as Card array
|
|
388
|
+
* @param card Parent card to fetch children from
|
|
389
|
+
* @returns children of a given card; as Card array
|
|
390
|
+
*/
|
|
391
|
+
childrenCards(card) {
|
|
392
|
+
const cards = [];
|
|
393
|
+
for (const child of card.children) {
|
|
394
|
+
const card = this.cardCache.getCard(child);
|
|
395
|
+
if (card) {
|
|
396
|
+
cards.push(card);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return cards;
|
|
400
|
+
}
|
|
258
401
|
/**
|
|
259
402
|
* Updates all local resources.
|
|
260
403
|
*/
|
|
@@ -264,8 +407,8 @@ export class Project extends CardContainer {
|
|
|
264
407
|
/**
|
|
265
408
|
* Updates all imported module resources.
|
|
266
409
|
*/
|
|
267
|
-
|
|
268
|
-
|
|
410
|
+
collectModuleResources() {
|
|
411
|
+
this.resources.moduleImported();
|
|
269
412
|
}
|
|
270
413
|
/**
|
|
271
414
|
* Returns project configuration.
|
|
@@ -283,7 +426,7 @@ export class Project extends CardContainer {
|
|
|
283
426
|
if (!card || !card.path || !isTemplateCard(card)) {
|
|
284
427
|
return undefined;
|
|
285
428
|
}
|
|
286
|
-
const { template } = this.
|
|
429
|
+
const { template } = cardPathParts(this.projectPrefix, card.path);
|
|
287
430
|
return new TemplateResource(this, resourceName(template)).templateObject();
|
|
288
431
|
}
|
|
289
432
|
/**
|
|
@@ -325,30 +468,8 @@ export class Project extends CardContainer {
|
|
|
325
468
|
* @param details Defines which card details are included in the return values.
|
|
326
469
|
* @returns specific card details, or undefined if card is not part of the project.
|
|
327
470
|
*/
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if (details.location === CardLocation.projectOnly ||
|
|
331
|
-
details.location === CardLocation.all ||
|
|
332
|
-
!details.location) {
|
|
333
|
-
card = await super.findCard(this.paths.cardRootFolder, cardToFind, details);
|
|
334
|
-
}
|
|
335
|
-
if (!card &&
|
|
336
|
-
(details.location === CardLocation.templatesOnly ||
|
|
337
|
-
details.location === CardLocation.all ||
|
|
338
|
-
!details.location)) {
|
|
339
|
-
const templates = await this.templates();
|
|
340
|
-
for (const template of templates) {
|
|
341
|
-
const templateObject = new TemplateResource(this, resourceName(template.name)).templateObject();
|
|
342
|
-
if (!templateObject)
|
|
343
|
-
continue;
|
|
344
|
-
// optimize: execute each find in template parallel
|
|
345
|
-
card = await templateObject.findSpecificCard(cardToFind, details);
|
|
346
|
-
if (card) {
|
|
347
|
-
break;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
return card;
|
|
471
|
+
findCard(cardToFind, details) {
|
|
472
|
+
return super.findCard(cardToFind, details);
|
|
352
473
|
}
|
|
353
474
|
/**
|
|
354
475
|
* Returns an array of all the graph models in the project.
|
|
@@ -371,57 +492,87 @@ export class Project extends CardContainer {
|
|
|
371
492
|
* @param changedCard Card that was changed.
|
|
372
493
|
*/
|
|
373
494
|
async handleCardChanged(changedCard) {
|
|
495
|
+
// Notify the calculation engine about the change
|
|
374
496
|
return this.calculationEngine.handleCardChanged(changedCard);
|
|
375
497
|
}
|
|
376
498
|
/**
|
|
377
499
|
* When cards are removed.
|
|
378
500
|
* @param deletedCard Card that is to be removed.
|
|
379
501
|
*/
|
|
380
|
-
async
|
|
502
|
+
async handleCardDeleted(deletedCard) {
|
|
503
|
+
// Delete children from the cache first
|
|
504
|
+
if (deletedCard.children && deletedCard.children.length > 0) {
|
|
505
|
+
for (const child of deletedCard.children) {
|
|
506
|
+
try {
|
|
507
|
+
const childCard = this.findCard(child);
|
|
508
|
+
await this.handleCardDeleted(childCard);
|
|
509
|
+
}
|
|
510
|
+
catch {
|
|
511
|
+
this.logger.warn(`Accessing child '${child}' of '${deletedCard.key}' when deleting cards caused an exception`);
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
await super.removeCard(deletedCard.key);
|
|
381
517
|
return this.calculationEngine.handleDeleteCard(deletedCard);
|
|
382
518
|
}
|
|
519
|
+
/**
|
|
520
|
+
* When card is moved.
|
|
521
|
+
* @param movedCard Card that moved
|
|
522
|
+
* @param newParentCard New parent for the 'movedCard'
|
|
523
|
+
* @param oldParentCard Previous parent of the 'movedCard'
|
|
524
|
+
*/
|
|
525
|
+
async handleCardMoved(movedCard, newParentCard, oldParentCard) {
|
|
526
|
+
if (newParentCard) {
|
|
527
|
+
this.cardCache.updateCard(newParentCard.key, newParentCard);
|
|
528
|
+
}
|
|
529
|
+
if (oldParentCard) {
|
|
530
|
+
this.cardCache.updateCard(oldParentCard.key, oldParentCard);
|
|
531
|
+
}
|
|
532
|
+
this.cardCache.updateCard(movedCard.key, movedCard);
|
|
533
|
+
// todo: it would be enough to just update parent, previous parent and changed card
|
|
534
|
+
this.cardCache.populateChildrenRelationships();
|
|
535
|
+
await this.handleCardChanged(movedCard);
|
|
536
|
+
await this.calculationEngine.handleCardMoved();
|
|
537
|
+
}
|
|
383
538
|
/**
|
|
384
539
|
* When new cards are added.
|
|
385
540
|
* @param cards Added cards.
|
|
386
541
|
*/
|
|
387
542
|
async handleNewCards(cards) {
|
|
543
|
+
// Add new cards to the card cache
|
|
544
|
+
cards.forEach((card) => {
|
|
545
|
+
const cardWithParent = {
|
|
546
|
+
...card,
|
|
547
|
+
parent: card.parent || this.parentFromPath(card.path),
|
|
548
|
+
};
|
|
549
|
+
this.cardCache.updateCard(cardWithParent.key, cardWithParent);
|
|
550
|
+
// Update the parent's children list in the cache
|
|
551
|
+
if (cardWithParent.parent && cardWithParent.parent !== ROOT) {
|
|
552
|
+
this.updateCachedChildren(cardWithParent.parent, cardWithParent);
|
|
553
|
+
}
|
|
554
|
+
});
|
|
388
555
|
return this.calculationEngine.handleNewCards(cards);
|
|
389
556
|
}
|
|
390
|
-
/**
|
|
391
|
-
* Checks if a given card is part of this project.
|
|
392
|
-
* @param cardKey card to check.
|
|
393
|
-
* @returns true if a given card is found from project, false otherwise.
|
|
394
|
-
*/
|
|
395
|
-
hasCard(cardKey) {
|
|
396
|
-
return super.hasCard(cardKey, this.paths.cardRootFolder);
|
|
397
|
-
}
|
|
398
557
|
/**
|
|
399
558
|
* Adds a module from project.
|
|
400
|
-
* @param module
|
|
559
|
+
* @param module Module to add
|
|
401
560
|
*/
|
|
402
561
|
async importModule(module) {
|
|
403
562
|
// Add module as a dependency.
|
|
404
563
|
await this.configuration.addModule(module);
|
|
405
|
-
|
|
564
|
+
this.collectModuleResources();
|
|
565
|
+
await this.populateTemplateCards();
|
|
566
|
+
this.logger.info(`Imported module '${module.name}'`);
|
|
406
567
|
}
|
|
407
568
|
/**
|
|
408
|
-
* Checks if given path is a project.
|
|
569
|
+
* Checks if a given path is a project.
|
|
409
570
|
* @param path Path to a project
|
|
410
571
|
* @returns true, if in the given path there is a project; false otherwise
|
|
411
572
|
*/
|
|
412
573
|
static isCreated(path) {
|
|
413
574
|
return pathExists(join(path, 'cardRoot'));
|
|
414
575
|
}
|
|
415
|
-
/**
|
|
416
|
-
* Returns whether card is a template card or not
|
|
417
|
-
* @param cardKey card to check.
|
|
418
|
-
* @todo: This is only used from 'remove'. Could it use the static checker?
|
|
419
|
-
* @returns true, if card is template card; false otherwise
|
|
420
|
-
*/
|
|
421
|
-
async isTemplateCard(cardKey) {
|
|
422
|
-
const templateCards = await this.allTemplateCards();
|
|
423
|
-
return templateCards.find((card) => card.key === cardKey) != null;
|
|
424
|
-
}
|
|
425
576
|
/**
|
|
426
577
|
* Returns an array of all the link types in the project.
|
|
427
578
|
* @param from Defines where resources are collected from.
|
|
@@ -434,13 +585,15 @@ export class Project extends CardContainer {
|
|
|
434
585
|
* Returns an array of cards in the project, in the templates or both.
|
|
435
586
|
* Cards don't have content and nor metadata.
|
|
436
587
|
* @param cardsFrom Where to return cards from (project, templates, or both)
|
|
437
|
-
* @returns all cards in the project.
|
|
588
|
+
* @returns all cards in the project per container.
|
|
438
589
|
*/
|
|
439
590
|
async listCards(cardsFrom = CardLocation.all) {
|
|
440
591
|
const cardListContainer = [];
|
|
441
592
|
if (cardsFrom === CardLocation.all ||
|
|
442
593
|
cardsFrom === CardLocation.projectOnly) {
|
|
443
|
-
const projectCards =
|
|
594
|
+
const projectCards = super
|
|
595
|
+
.cards(this.paths.cardRootFolder)
|
|
596
|
+
.map((item) => item.key);
|
|
444
597
|
cardListContainer.push({
|
|
445
598
|
name: this.projectName,
|
|
446
599
|
type: 'project',
|
|
@@ -454,8 +607,8 @@ export class Project extends CardContainer {
|
|
|
454
607
|
const templateObject = new TemplateResource(this, resourceName(template.name)).templateObject();
|
|
455
608
|
if (templateObject) {
|
|
456
609
|
// todo: optimization - do all this in parallel
|
|
457
|
-
const templateCards =
|
|
458
|
-
if (templateCards) {
|
|
610
|
+
const templateCards = templateObject.listCards();
|
|
611
|
+
if (templateCards.length) {
|
|
459
612
|
cardListContainer.push({
|
|
460
613
|
name: template.name,
|
|
461
614
|
type: 'template',
|
|
@@ -474,45 +627,24 @@ export class Project extends CardContainer {
|
|
|
474
627
|
* @note that cardIDs are not sorted.
|
|
475
628
|
*/
|
|
476
629
|
async listCardIds(cardsFrom = CardLocation.all) {
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
.then((cards) => new Set(cards.map((card) => card.key))));
|
|
630
|
+
const cardContainers = await this.listCards(cardsFrom);
|
|
631
|
+
const allCardIDs = new Set();
|
|
632
|
+
for (const container of cardContainers) {
|
|
633
|
+
const cards = container.cards;
|
|
634
|
+
cards.forEach((card) => allCardIDs.add(card));
|
|
483
635
|
}
|
|
484
|
-
|
|
485
|
-
cardsFrom === CardLocation.templatesOnly) {
|
|
486
|
-
promises.push((async () => {
|
|
487
|
-
const templates = await this.templates();
|
|
488
|
-
const templateResources = templates.map((template) => new TemplateResource(this, resourceName(template.name)));
|
|
489
|
-
const templateObjectsResults = await Promise.allSettled(templateResources);
|
|
490
|
-
const templateObjects = templateObjectsResults
|
|
491
|
-
.filter((result) => result.status === 'fulfilled' && result.value !== null)
|
|
492
|
-
.map((result) => result.value);
|
|
493
|
-
const listCardsResults = await Promise.allSettled(templateObjects.map((obj) => obj.templateObject().listCards()));
|
|
494
|
-
const templateCardIds = new Set();
|
|
495
|
-
listCardsResults
|
|
496
|
-
.filter((result) => result.status === 'fulfilled')
|
|
497
|
-
.forEach((result) => {
|
|
498
|
-
result.value.forEach((card) => templateCardIds.add(card.key));
|
|
499
|
-
});
|
|
500
|
-
return templateCardIds;
|
|
501
|
-
})());
|
|
502
|
-
}
|
|
503
|
-
const allCardIdSets = await Promise.all(promises);
|
|
504
|
-
return new Set(allCardIdSets.flatMap((set) => [...set]));
|
|
636
|
+
return allCardIDs;
|
|
505
637
|
}
|
|
506
638
|
/**
|
|
507
639
|
* Returns details of a certain module.
|
|
508
640
|
* @param moduleName Name of the module.
|
|
509
|
-
* @returns module details, or undefined if
|
|
641
|
+
* @returns module details, or undefined if module cannot be found.
|
|
510
642
|
*/
|
|
511
643
|
async module(moduleName) {
|
|
512
644
|
const module = await this.findModule(moduleName);
|
|
513
645
|
if (module && module.path) {
|
|
514
646
|
const modulePath = join(module.path, module.name);
|
|
515
|
-
const moduleConfig =
|
|
647
|
+
const moduleConfig = await readJsonFile(join(modulePath, Project.projectConfigFileName));
|
|
516
648
|
return {
|
|
517
649
|
name: moduleConfig.name,
|
|
518
650
|
modules: moduleConfig.modules,
|
|
@@ -623,15 +755,12 @@ export class Project extends CardContainer {
|
|
|
623
755
|
return this.projectPaths;
|
|
624
756
|
}
|
|
625
757
|
/**
|
|
626
|
-
*
|
|
627
|
-
* @param cardKey card to check path for.
|
|
628
|
-
* @returns path to a given card.
|
|
758
|
+
* Populates the card cache, if it has not been populated.
|
|
629
759
|
*/
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
return foundFile ? dirname(foundFile) : '';
|
|
760
|
+
async populateCaches() {
|
|
761
|
+
if (!this.cardCache.isPopulated) {
|
|
762
|
+
await this.populateCardsCache();
|
|
763
|
+
}
|
|
635
764
|
}
|
|
636
765
|
/**
|
|
637
766
|
* Returns project name.
|
|
@@ -655,6 +784,7 @@ export class Project extends CardContainer {
|
|
|
655
784
|
const prefixes = [this.projectPrefix];
|
|
656
785
|
let files;
|
|
657
786
|
try {
|
|
787
|
+
// TODO: Could be optimized so that prefixes are stored once fetched.
|
|
658
788
|
files = await readdir(this.paths.modulesFolder, {
|
|
659
789
|
withFileTypes: true,
|
|
660
790
|
recursive: true,
|
|
@@ -669,11 +799,39 @@ export class Project extends CardContainer {
|
|
|
669
799
|
const configurationPrefixes = await Promise.all(configurationPromises);
|
|
670
800
|
prefixes.push(...configurationPrefixes);
|
|
671
801
|
}
|
|
672
|
-
catch {
|
|
673
|
-
|
|
802
|
+
catch (error) {
|
|
803
|
+
this.logger.error({ error }, 'Failed to collect prefixes in use');
|
|
674
804
|
}
|
|
675
805
|
return prefixes;
|
|
676
806
|
}
|
|
807
|
+
/**
|
|
808
|
+
* Removes a module from the project
|
|
809
|
+
* @param module Module (name) to remove.
|
|
810
|
+
*/
|
|
811
|
+
async removeModule(moduleName) {
|
|
812
|
+
const toBeRemovedTemplates = this.resources.moduleResources.resourceArray('templates', moduleName);
|
|
813
|
+
// First, remove cards from the cache
|
|
814
|
+
for (const template of toBeRemovedTemplates) {
|
|
815
|
+
this.cardCache.deleteCardsFromTemplate(template.name);
|
|
816
|
+
}
|
|
817
|
+
// Then, remove module from project configuration
|
|
818
|
+
await this.configuration.removeModule(moduleName);
|
|
819
|
+
this.collectModuleResources();
|
|
820
|
+
this.logger.info(`Removed module '${moduleName}'`);
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Removes a resource from Project.
|
|
824
|
+
* @param resource Resource to remove.
|
|
825
|
+
*/
|
|
826
|
+
removeResource(resource) {
|
|
827
|
+
// Template cards must be removed from the cache when resource is removed.
|
|
828
|
+
if (resource.path.includes('templates')) {
|
|
829
|
+
const templateName = resourceNameToString(resourceName(resource.name));
|
|
830
|
+
this.cardCache.deleteCardsFromTemplate(templateName);
|
|
831
|
+
}
|
|
832
|
+
this.resources.remove(resource);
|
|
833
|
+
this.createdResources.delete(resource.name);
|
|
834
|
+
}
|
|
677
835
|
/**
|
|
678
836
|
* Array of reports in the project.
|
|
679
837
|
* @param from Defines where resources are collected from.
|
|
@@ -697,20 +855,12 @@ export class Project extends CardContainer {
|
|
|
697
855
|
}
|
|
698
856
|
return handleBarFiles;
|
|
699
857
|
}
|
|
700
|
-
/**
|
|
701
|
-
* Removes a resource from Project.
|
|
702
|
-
* @param resource Resource to remove.
|
|
703
|
-
*/
|
|
704
|
-
removeResource(resource) {
|
|
705
|
-
this.resources.remove(resource);
|
|
706
|
-
this.createdResources.delete(resource.name);
|
|
707
|
-
}
|
|
708
858
|
/**
|
|
709
859
|
* Returns metadata from a given resource
|
|
710
860
|
* @param name Name of a resource
|
|
711
861
|
* @returns Metadata from the resource.
|
|
712
862
|
*/
|
|
713
|
-
|
|
863
|
+
resource(name) {
|
|
714
864
|
const resName = resourceName(name);
|
|
715
865
|
if (this.createdResources.has(resourceNameToString(resName))) {
|
|
716
866
|
const value = this.createdResources.get(resourceNameToString(resName));
|
|
@@ -756,7 +906,10 @@ export class Project extends CardContainer {
|
|
|
756
906
|
* @returns Created resource.
|
|
757
907
|
*/
|
|
758
908
|
static resourceObject(project, name) {
|
|
759
|
-
if (name.type === '
|
|
909
|
+
if (name.type === 'calculations') {
|
|
910
|
+
return new CalculationResource(project, name);
|
|
911
|
+
}
|
|
912
|
+
else if (name.type === 'cardTypes') {
|
|
760
913
|
return new CardTypeResource(project, name);
|
|
761
914
|
}
|
|
762
915
|
else if (name.type === 'fieldTypes') {
|
|
@@ -801,33 +954,22 @@ export class Project extends CardContainer {
|
|
|
801
954
|
* Show cards of a project.
|
|
802
955
|
* @returns an array of all project cards in the project.
|
|
803
956
|
*/
|
|
804
|
-
|
|
957
|
+
showProjectCards() {
|
|
805
958
|
return this.showCards(this.paths.cardRootFolder);
|
|
806
959
|
}
|
|
807
|
-
/**
|
|
808
|
-
* Returns all template cards from the project. This includes all module templates' cards.
|
|
809
|
-
* @param cardDetails which details to fetch. Optional.
|
|
810
|
-
* @returns all the template cards from the project
|
|
811
|
-
*/
|
|
812
|
-
async allTemplateCards(cardDetails) {
|
|
813
|
-
const templates = await this.templates();
|
|
814
|
-
const cards = [];
|
|
815
|
-
for (const template of templates) {
|
|
816
|
-
const templateCards = await this.templateCards(template.name, cardDetails);
|
|
817
|
-
if (templateCards)
|
|
818
|
-
cards.push(...templateCards);
|
|
819
|
-
}
|
|
820
|
-
return cards;
|
|
821
|
-
}
|
|
822
960
|
/**
|
|
823
961
|
* Returns cards from single template.
|
|
824
|
-
* @param templateName Name of the template
|
|
825
|
-
* @param cardDetails Card information
|
|
962
|
+
* @param templateName Name of the template (supports both full names like 'decision/templates/decision' and short names like 'decision')
|
|
826
963
|
* @returns List of cards from template.
|
|
827
964
|
*/
|
|
828
|
-
|
|
829
|
-
const
|
|
830
|
-
return
|
|
965
|
+
templateCards(templateName) {
|
|
966
|
+
const templateCards = this.cardCache.getAllTemplateCards();
|
|
967
|
+
return templateCards.filter((cachedCard) => {
|
|
968
|
+
if (cachedCard.location === 'project') {
|
|
969
|
+
return false;
|
|
970
|
+
}
|
|
971
|
+
return cachedCard.location === templateName;
|
|
972
|
+
});
|
|
831
973
|
}
|
|
832
974
|
/**
|
|
833
975
|
* Array of templates in the project.
|
|
@@ -838,20 +980,19 @@ export class Project extends CardContainer {
|
|
|
838
980
|
return this.resources.resources('templates', from);
|
|
839
981
|
}
|
|
840
982
|
/**
|
|
841
|
-
* Update card content.
|
|
842
|
-
* @param cardKey card
|
|
983
|
+
* Update a card's content.
|
|
984
|
+
* @param cardKey card key to update.
|
|
843
985
|
* @param content changed content
|
|
844
986
|
*/
|
|
845
987
|
async updateCardContent(cardKey, content) {
|
|
846
|
-
const card =
|
|
847
|
-
metadata: true,
|
|
848
|
-
content: true,
|
|
849
|
-
});
|
|
850
|
-
if (!card) {
|
|
851
|
-
throw new Error(`Card '${cardKey}' does not exist in the project`);
|
|
852
|
-
}
|
|
988
|
+
const card = this.findCard(cardKey);
|
|
853
989
|
card.content = content;
|
|
990
|
+
// Update lastUpdated timestamp in metadata
|
|
991
|
+
if (card.metadata) {
|
|
992
|
+
card.metadata.lastUpdated = new Date().toISOString();
|
|
993
|
+
}
|
|
854
994
|
await this.saveCard(card);
|
|
995
|
+
await this.handleCardChanged(card);
|
|
855
996
|
}
|
|
856
997
|
/**
|
|
857
998
|
* Updates card metadata's single key.
|
|
@@ -860,16 +1001,13 @@ export class Project extends CardContainer {
|
|
|
860
1001
|
* @param newValue changed value for the key
|
|
861
1002
|
*/
|
|
862
1003
|
async updateCardMetadataKey(cardKey, changedKey, newValue) {
|
|
863
|
-
const
|
|
864
|
-
const card = await this.findCard(templateCard ? this.paths.templatesFolder : this.paths.cardRootFolder, cardKey, {
|
|
865
|
-
metadata: true,
|
|
866
|
-
});
|
|
867
|
-
if (!card) {
|
|
868
|
-
throw new Error(`Card '${cardKey}' does not exist in the project`);
|
|
869
|
-
}
|
|
1004
|
+
const card = this.findCard(cardKey);
|
|
870
1005
|
if (!card.metadata || card.metadata[changedKey] === newValue) {
|
|
871
1006
|
return;
|
|
872
1007
|
}
|
|
1008
|
+
const isRankChange = changedKey === 'rank';
|
|
1009
|
+
const previousPath = isRankChange ? card.path : undefined;
|
|
1010
|
+
const previousParent = isRankChange ? card.parent : undefined;
|
|
873
1011
|
const cardAsRecord = card.metadata;
|
|
874
1012
|
cardAsRecord[changedKey] = newValue;
|
|
875
1013
|
const invalidCard = isTemplateCard(card)
|
|
@@ -878,26 +1016,58 @@ export class Project extends CardContainer {
|
|
|
878
1016
|
if (invalidCard.length !== 0) {
|
|
879
1017
|
throw new Error(invalidCard);
|
|
880
1018
|
}
|
|
881
|
-
await this.saveCardMetadata(card);
|
|
1019
|
+
const updated = await this.saveCardMetadata(card);
|
|
1020
|
+
if (!updated)
|
|
1021
|
+
return;
|
|
1022
|
+
// For rank changes, check if path changed (indicating a move)
|
|
1023
|
+
if (isRankChange) {
|
|
1024
|
+
const updatedCard = this.findCard(cardKey);
|
|
1025
|
+
if (updatedCard.path !== previousPath) {
|
|
1026
|
+
this.changeParent(updatedCard, previousParent);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
882
1029
|
}
|
|
883
1030
|
/**
|
|
884
|
-
* Updates card
|
|
1031
|
+
* Updates the entire card in the card cache and handles any path/parent changes.
|
|
1032
|
+
* Also persists changes to content and metadata files.
|
|
1033
|
+
* @param card The card with updated information (path, parent, metadata, etc.)
|
|
1034
|
+
*/
|
|
1035
|
+
async updateCard(card) {
|
|
1036
|
+
const cachedCard = this.cardCache.getCard(card.key);
|
|
1037
|
+
const pathChange = cachedCard && cachedCard.path !== card.path;
|
|
1038
|
+
if (pathChange) {
|
|
1039
|
+
this.changeParent(card, cachedCard.parent);
|
|
1040
|
+
}
|
|
1041
|
+
const metadataChanged = cachedCard &&
|
|
1042
|
+
JSON.stringify(cachedCard.metadata) !== JSON.stringify(card.metadata);
|
|
1043
|
+
if (metadataChanged) {
|
|
1044
|
+
await this.saveCardMetadata(card);
|
|
1045
|
+
}
|
|
1046
|
+
const contentChanged = cachedCard && cachedCard.content !== card.content;
|
|
1047
|
+
if (contentChanged) {
|
|
1048
|
+
await this.saveCardContent(card);
|
|
1049
|
+
}
|
|
1050
|
+
this.cardCache.updateCard(card.key, card);
|
|
1051
|
+
if (metadataChanged || contentChanged || pathChange) {
|
|
1052
|
+
await this.handleCardChanged(card);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Updates a card's metadata.
|
|
885
1057
|
* @param card affected card
|
|
886
1058
|
* @param changedMetadata changed content for the card
|
|
887
1059
|
*/
|
|
888
1060
|
async updateCardMetadata(card, changedMetadata) {
|
|
889
1061
|
card.metadata = changedMetadata;
|
|
890
|
-
|
|
1062
|
+
if (await this.saveCardMetadata(card)) {
|
|
1063
|
+
await this.handleCardChanged(card);
|
|
1064
|
+
}
|
|
891
1065
|
}
|
|
892
|
-
|
|
893
|
-
* Validates that card's data is valid.
|
|
894
|
-
* @param card Card to validate.
|
|
895
|
-
* @returns validation errors, if any
|
|
896
|
-
*/
|
|
1066
|
+
// Validates that card's data is valid.
|
|
897
1067
|
async validateCard(card) {
|
|
898
1068
|
const invalidCustomData = await this.validator.validateCustomFields(this, card);
|
|
899
|
-
const invalidWorkFlow =
|
|
900
|
-
const invalidLabels =
|
|
1069
|
+
const invalidWorkFlow = this.validator.validateWorkflowState(this, card);
|
|
1070
|
+
const invalidLabels = this.validator.validateCardLabels(card);
|
|
901
1071
|
if (invalidCustomData.length === 0 &&
|
|
902
1072
|
invalidWorkFlow.length === 0 &&
|
|
903
1073
|
invalidLabels.length === 0) {
|