@cyberismo/data-handler 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/command-handler.d.ts +11 -2
- package/dist/command-handler.js +61 -18
- package/dist/command-handler.js.map +1 -1
- package/dist/command-manager.js +8 -8
- package/dist/command-manager.js.map +1 -1
- package/dist/commands/calculate.d.ts +7 -44
- package/dist/commands/calculate.js +8 -389
- package/dist/commands/calculate.js.map +1 -1
- package/dist/commands/create.d.ts +7 -4
- package/dist/commands/create.js +42 -15
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/edit.d.ts +9 -3
- package/dist/commands/edit.js +33 -9
- package/dist/commands/edit.js.map +1 -1
- package/dist/commands/export.d.ts +13 -11
- package/dist/commands/export.js +80 -28
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/import.d.ts +7 -0
- package/dist/commands/import.js +21 -2
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/move.d.ts +11 -12
- package/dist/commands/move.js +12 -13
- package/dist/commands/move.js.map +1 -1
- package/dist/commands/remove.d.ts +2 -4
- package/dist/commands/remove.js +8 -16
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/rename.d.ts +1 -3
- package/dist/commands/rename.js +3 -6
- package/dist/commands/rename.js.map +1 -1
- package/dist/commands/show.d.ts +37 -5
- package/dist/commands/show.js +85 -7
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/transition.d.ts +1 -3
- package/dist/commands/transition.js +3 -5
- package/dist/commands/transition.js.map +1 -1
- package/dist/commands/update.d.ts +5 -1
- package/dist/commands/update.js +7 -1
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/validate.d.ts +7 -8
- package/dist/commands/validate.js +30 -35
- package/dist/commands/validate.js.map +1 -1
- package/dist/containers/card-container.d.ts +6 -0
- package/dist/containers/card-container.js +61 -0
- package/dist/containers/card-container.js.map +1 -1
- package/dist/containers/project/calculation-engine.d.ts +90 -0
- package/dist/containers/project/calculation-engine.js +402 -0
- package/dist/containers/project/calculation-engine.js.map +1 -0
- package/dist/containers/project/resource-collector.d.ts +2 -1
- package/dist/containers/project/resource-collector.js +41 -33
- package/dist/containers/project/resource-collector.js.map +1 -1
- package/dist/containers/project.d.ts +18 -6
- package/dist/containers/project.js +37 -72
- package/dist/containers/project.js.map +1 -1
- package/dist/containers/template.d.ts +5 -0
- package/dist/containers/template.js +9 -0
- package/dist/containers/template.js.map +1 -1
- package/dist/exceptions/index.d.ts +20 -0
- package/dist/exceptions/index.js +16 -0
- package/dist/exceptions/index.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces/macros.d.ts +7 -2
- package/dist/interfaces/project-interfaces.d.ts +21 -0
- package/dist/interfaces/project-interfaces.js +4 -0
- package/dist/interfaces/project-interfaces.js.map +1 -1
- package/dist/interfaces/resource-interfaces.d.ts +1 -1
- package/dist/interfaces/resource-interfaces.js.map +1 -1
- package/dist/macros/base-macro.d.ts +2 -0
- package/dist/macros/base-macro.js +66 -19
- package/dist/macros/base-macro.js.map +1 -1
- package/dist/macros/common.d.ts +16 -9
- package/dist/macros/common.js +22 -9
- package/dist/macros/common.js.map +1 -1
- package/dist/macros/createCards/index.d.ts +1 -2
- package/dist/macros/createCards/index.js.map +1 -1
- package/dist/macros/graph/index.d.ts +1 -3
- package/dist/macros/graph/index.js +11 -9
- package/dist/macros/graph/index.js.map +1 -1
- package/dist/macros/image/index.d.ts +39 -0
- package/dist/macros/image/index.js +78 -0
- package/dist/macros/image/index.js.map +1 -0
- package/dist/macros/image/metadata.d.ts +18 -0
- package/dist/macros/image/metadata.js +22 -0
- package/dist/macros/image/metadata.js.map +1 -0
- package/dist/macros/include/index.d.ts +32 -0
- package/dist/macros/include/index.js +97 -0
- package/dist/macros/include/index.js.map +1 -0
- package/dist/macros/include/metadata.d.ts +15 -0
- package/dist/macros/include/metadata.js +19 -0
- package/dist/macros/include/metadata.js.map +1 -0
- package/dist/macros/index.d.ts +39 -31
- package/dist/macros/index.js +167 -73
- package/dist/macros/index.js.map +1 -1
- package/dist/macros/percentage/index.d.ts +29 -0
- package/dist/macros/percentage/index.js +36 -0
- package/dist/macros/percentage/index.js.map +1 -0
- package/dist/macros/percentage/metadata.d.ts +15 -0
- package/dist/macros/percentage/metadata.js +19 -0
- package/dist/macros/percentage/metadata.js.map +1 -0
- package/dist/macros/report/index.d.ts +2 -5
- package/dist/macros/report/index.js +20 -12
- package/dist/macros/report/index.js.map +1 -1
- package/dist/macros/scoreCard/index.d.ts +15 -15
- package/dist/macros/scoreCard/index.js +16 -17
- package/dist/macros/scoreCard/index.js.map +1 -1
- package/dist/macros/vega/index.d.ts +28 -0
- package/dist/macros/vega/index.js +27 -0
- package/dist/macros/vega/index.js.map +1 -0
- package/dist/macros/vega/metadata.d.ts +15 -0
- package/dist/macros/vega/metadata.js +7 -0
- package/dist/macros/vega/metadata.js.map +1 -0
- package/dist/macros/vegalite/index.d.ts +27 -0
- package/dist/macros/vegalite/index.js +27 -0
- package/dist/macros/vegalite/index.js.map +1 -0
- package/dist/macros/vegalite/metadata.d.ts +15 -0
- package/dist/macros/vegalite/metadata.js +7 -0
- package/dist/macros/vegalite/metadata.js.map +1 -0
- package/dist/macros/xref/index.d.ts +26 -0
- package/dist/macros/xref/index.js +53 -0
- package/dist/macros/xref/index.js.map +1 -0
- package/dist/macros/xref/metadata.d.ts +15 -0
- package/dist/macros/xref/metadata.js +19 -0
- package/dist/macros/xref/metadata.js.map +1 -0
- package/dist/module-manager.d.ts +17 -4
- package/dist/module-manager.js +192 -58
- package/dist/module-manager.js.map +1 -1
- package/dist/permissions/action-guard.d.ts +2 -2
- package/dist/permissions/action-guard.js +1 -1
- package/dist/permissions/action-guard.js.map +1 -1
- package/dist/project-settings.js +2 -8
- package/dist/project-settings.js.map +1 -1
- package/dist/resources/card-type-resource.d.ts +2 -0
- package/dist/resources/card-type-resource.js +63 -0
- package/dist/resources/card-type-resource.js.map +1 -1
- package/dist/resources/file-resource.d.ts +2 -0
- package/dist/resources/file-resource.js +12 -4
- package/dist/resources/file-resource.js.map +1 -1
- package/dist/resources/folder-resource.d.ts +19 -0
- package/dist/resources/folder-resource.js +51 -2
- package/dist/resources/folder-resource.js.map +1 -1
- package/dist/resources/graph-model-resource.js +1 -1
- package/dist/resources/graph-model-resource.js.map +1 -1
- package/dist/resources/graph-view-resource.js +1 -1
- package/dist/resources/graph-view-resource.js.map +1 -1
- package/dist/resources/report-resource.js +1 -1
- package/dist/resources/report-resource.js.map +1 -1
- package/dist/resources/resource-object.d.ts +8 -0
- package/dist/resources/resource-object.js +9 -0
- package/dist/resources/resource-object.js.map +1 -1
- package/dist/resources/template-resource.js +1 -1
- package/dist/resources/template-resource.js.map +1 -1
- package/dist/resources/workflow-resource.d.ts +2 -0
- package/dist/resources/workflow-resource.js +53 -9
- package/dist/resources/workflow-resource.js.map +1 -1
- package/dist/svg/index.d.ts +15 -0
- package/dist/svg/index.js +16 -0
- package/dist/svg/index.js.map +1 -0
- package/dist/svg/lib.d.ts +9 -0
- package/dist/svg/lib.js +26 -0
- package/dist/svg/lib.js.map +1 -0
- package/dist/svg/percentage.d.ts +25 -0
- package/dist/svg/percentage.js +90 -0
- package/dist/svg/percentage.js.map +1 -0
- package/dist/svg/scoreCard.d.ts +19 -0
- package/dist/svg/scoreCard.js +55 -0
- package/dist/svg/scoreCard.js.map +1 -0
- package/dist/types/queries.d.ts +8 -7
- package/dist/types/queries.js.map +1 -1
- package/dist/utils/card-utils.d.ts +6 -0
- package/dist/utils/card-utils.js +12 -0
- package/dist/utils/card-utils.js.map +1 -1
- package/dist/utils/clingo-facts.d.ts +2 -1
- package/dist/utils/clingo-facts.js +19 -2
- package/dist/utils/clingo-facts.js.map +1 -1
- package/dist/utils/clingo-parser.d.ts +1 -0
- package/dist/utils/clingo-parser.js +22 -100
- package/dist/utils/clingo-parser.js.map +1 -1
- package/dist/utils/constants.d.ts +7 -0
- package/dist/utils/constants.js +14 -0
- package/dist/utils/constants.js.map +1 -1
- package/dist/utils/csv.js.map +1 -1
- package/dist/utils/report.d.ts +17 -3
- package/dist/utils/report.js +38 -2
- package/dist/utils/report.js.map +1 -1
- package/dist/utils/resource-utils.d.ts +1 -0
- package/dist/utils/resource-utils.js +9 -0
- package/dist/utils/resource-utils.js.map +1 -1
- package/dist/utils/user-preferences.d.ts +1 -11
- package/dist/utils/user-preferences.js +30 -13
- package/dist/utils/user-preferences.js.map +1 -1
- package/dist/utils/validate.d.ts +2 -3
- package/dist/utils/validate.js +2 -2
- package/dist/utils/validate.js.map +1 -1
- package/package.json +8 -6
- package/src/command-handler.ts +96 -17
- package/src/command-manager.ts +8 -8
- package/src/commands/calculate.ts +11 -525
- package/src/commands/create.ts +53 -16
- package/src/commands/edit.ts +53 -11
- package/src/commands/export.ts +108 -34
- package/src/commands/import.ts +31 -2
- package/src/commands/move.ts +12 -15
- package/src/commands/remove.ts +10 -19
- package/src/commands/rename.ts +3 -12
- package/src/commands/show.ts +121 -8
- package/src/commands/transition.ts +3 -7
- package/src/commands/update.ts +6 -0
- package/src/commands/validate.ts +39 -47
- package/src/containers/card-container.ts +74 -0
- package/src/containers/project/calculation-engine.ts +535 -0
- package/src/containers/project/resource-collector.ts +45 -26
- package/src/containers/project.ts +66 -84
- package/src/containers/template.ts +16 -0
- package/src/exceptions/index.ts +36 -0
- package/src/index.ts +13 -2
- package/src/interfaces/macros.ts +7 -1
- package/src/interfaces/project-interfaces.ts +27 -0
- package/src/interfaces/resource-interfaces.ts +1 -0
- package/src/macros/base-macro.ts +89 -25
- package/src/macros/common.ts +22 -9
- package/src/macros/createCards/index.ts +1 -2
- package/src/macros/graph/index.ts +17 -12
- package/src/macros/image/index.ts +121 -0
- package/src/macros/image/metadata.ts +25 -0
- package/src/macros/include/index.ts +147 -0
- package/src/macros/include/metadata.ts +22 -0
- package/src/macros/index.ts +179 -100
- package/src/macros/percentage/index.ts +54 -0
- package/src/macros/percentage/metadata.ts +22 -0
- package/src/macros/report/index.ts +22 -17
- package/src/macros/scoreCard/index.ts +23 -23
- package/src/macros/vega/index.ts +55 -0
- package/src/macros/vega/metadata.ts +21 -0
- package/src/macros/vegalite/index.ts +50 -0
- package/src/macros/vegalite/metadata.ts +21 -0
- package/src/macros/xref/index.ts +73 -0
- package/src/macros/xref/metadata.ts +22 -0
- package/src/module-manager.ts +241 -69
- package/src/permissions/action-guard.ts +3 -3
- package/src/project-settings.ts +2 -11
- package/src/resources/card-type-resource.ts +100 -0
- package/src/resources/file-resource.ts +16 -4
- package/src/resources/folder-resource.ts +59 -2
- package/src/resources/graph-model-resource.ts +1 -1
- package/src/resources/graph-view-resource.ts +1 -1
- package/src/resources/report-resource.ts +1 -1
- package/src/resources/resource-object.ts +14 -0
- package/src/resources/template-resource.ts +1 -1
- package/src/resources/workflow-resource.ts +68 -13
- package/src/svg/index.ts +15 -0
- package/src/svg/lib.ts +31 -0
- package/src/svg/percentage.ts +97 -0
- package/src/svg/scoreCard.ts +88 -0
- package/src/types/queries.ts +8 -7
- package/src/types/string-pixel-width.d.ts +23 -0
- package/src/utils/card-utils.ts +13 -0
- package/src/utils/clingo-facts.ts +65 -3
- package/src/utils/clingo-parser.ts +31 -144
- package/src/utils/constants.ts +16 -0
- package/src/utils/csv.ts +1 -1
- package/src/utils/report.ts +45 -4
- package/src/utils/resource-utils.ts +9 -0
- package/src/utils/user-preferences.ts +32 -14
- package/src/utils/validate.ts +3 -3
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Cyberismo
|
|
3
|
+
Copyright © Cyberismo Ltd and contributors 2025
|
|
4
|
+
This program is free software: you can redistribute it and/or modify it under
|
|
5
|
+
the terms of the GNU Affero General Public License version 3 as published by
|
|
6
|
+
the Free Software Foundation. This program is distributed in the hope that it
|
|
7
|
+
will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
|
8
|
+
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
9
|
+
See the GNU Affero General Public License for more details.
|
|
10
|
+
You should have received a copy of the GNU Affero General Public
|
|
11
|
+
License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// node
|
|
15
|
+
import { basename, join, resolve } from 'node:path';
|
|
16
|
+
import { readFile } from 'node:fs/promises';
|
|
17
|
+
|
|
18
|
+
import { sanitizeSvgBase64 } from '../../utils/sanitize-svg.js';
|
|
19
|
+
import { instance } from '@viz-js/viz';
|
|
20
|
+
|
|
21
|
+
import type {
|
|
22
|
+
BaseResult,
|
|
23
|
+
ParseResult,
|
|
24
|
+
QueryName,
|
|
25
|
+
QueryResult,
|
|
26
|
+
} from '../../types/queries.js';
|
|
27
|
+
import type { Card, Context } from '../../interfaces/project-interfaces.js';
|
|
28
|
+
import ClingoParser from '../../utils/clingo-parser.js';
|
|
29
|
+
import { pathExists } from '../../utils/file-utils.js';
|
|
30
|
+
import { Mutex } from 'async-mutex';
|
|
31
|
+
import Handlebars from 'handlebars';
|
|
32
|
+
import { type Project, ResourcesFrom } from '../../containers/project.js';
|
|
33
|
+
import { getChildLogger } from '../../utils/log-utils.js';
|
|
34
|
+
import {
|
|
35
|
+
createCardFacts,
|
|
36
|
+
createCardTypeFacts,
|
|
37
|
+
createContextFacts,
|
|
38
|
+
createFieldTypeFacts,
|
|
39
|
+
createLinkTypeFacts,
|
|
40
|
+
createModuleFacts,
|
|
41
|
+
createProjectFacts,
|
|
42
|
+
createReportFacts,
|
|
43
|
+
createTemplateFacts,
|
|
44
|
+
createWorkflowFacts,
|
|
45
|
+
} from '../../utils/clingo-facts.js';
|
|
46
|
+
import { CardMetadataUpdater } from '../../card-metadata-updater.js';
|
|
47
|
+
import type {
|
|
48
|
+
CardType,
|
|
49
|
+
FieldType,
|
|
50
|
+
LinkType,
|
|
51
|
+
ReportMetadata,
|
|
52
|
+
TemplateMetadata,
|
|
53
|
+
Workflow,
|
|
54
|
+
} from '../../interfaces/resource-interfaces.js';
|
|
55
|
+
import {
|
|
56
|
+
removeAllPrograms,
|
|
57
|
+
solve,
|
|
58
|
+
setProgram,
|
|
59
|
+
removeProgram,
|
|
60
|
+
} from '@cyberismo/node-clingo';
|
|
61
|
+
import { generateReportContent } from '../../utils/report.js';
|
|
62
|
+
import { lpFiles, graphvizReport } from '@cyberismo/assets';
|
|
63
|
+
import {
|
|
64
|
+
type ResourceName,
|
|
65
|
+
resourceNameToString,
|
|
66
|
+
} from '../../utils/resource-utils.js';
|
|
67
|
+
|
|
68
|
+
// Define the all category that will be used for all programs
|
|
69
|
+
const ALL_CATEGORY = 'all';
|
|
70
|
+
|
|
71
|
+
export class CalculationEngine {
|
|
72
|
+
constructor(private project: Project) {}
|
|
73
|
+
|
|
74
|
+
private static mutex = new Mutex();
|
|
75
|
+
|
|
76
|
+
private get logger() {
|
|
77
|
+
return getChildLogger({
|
|
78
|
+
module: 'calculate',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Storage for in-memory program content
|
|
83
|
+
private modules: string = '';
|
|
84
|
+
|
|
85
|
+
private modulesInitialized = false;
|
|
86
|
+
|
|
87
|
+
// Initialize modules during construction
|
|
88
|
+
private async initializeModules() {
|
|
89
|
+
try {
|
|
90
|
+
// Collect all available calculations at initialization time
|
|
91
|
+
this.modules = await this.generateModules();
|
|
92
|
+
this.modulesInitialized = true;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
this.logger.error(error, 'Failed to initialize modules');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Gets the logic program content for a specific card
|
|
100
|
+
* @param cardKey The key of the card
|
|
101
|
+
* @returns The logic program content for the card
|
|
102
|
+
*/
|
|
103
|
+
public async cardLogicProgram(cardKey: string): Promise<string> {
|
|
104
|
+
const card = await this.project.findSpecificCard(cardKey, {
|
|
105
|
+
metadata: true,
|
|
106
|
+
});
|
|
107
|
+
if (!card) {
|
|
108
|
+
throw new Error(`Card '${cardKey}' does not exist in the project`);
|
|
109
|
+
}
|
|
110
|
+
return createCardFacts(card, this.project);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// // Wrapper to run onCreation query.
|
|
114
|
+
private async creationQuery(cardKeys: string[], context: Context) {
|
|
115
|
+
if (!cardKeys) return undefined;
|
|
116
|
+
return this.runQuery('onCreation', context, {
|
|
117
|
+
cardKeys,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Generate card tree content
|
|
122
|
+
private async setCardTreeContent() {
|
|
123
|
+
const cards = await this.getCards(undefined);
|
|
124
|
+
|
|
125
|
+
for (const card of cards) {
|
|
126
|
+
await this.setCardContent(card);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private async setCardContent(card: Card) {
|
|
131
|
+
const cardContent = await createCardFacts(card, this.project);
|
|
132
|
+
setProgram(card.key, cardContent, [ALL_CATEGORY]);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Generates logic programs related to modules (and project itself).
|
|
136
|
+
private async generateModules() {
|
|
137
|
+
const modules = await this.project.modules();
|
|
138
|
+
let content = '';
|
|
139
|
+
for (const module of await Promise.all(
|
|
140
|
+
modules.map((mod) => this.project.module(mod.name)),
|
|
141
|
+
)) {
|
|
142
|
+
if (!module) continue;
|
|
143
|
+
const moduleContent = createModuleFacts(module);
|
|
144
|
+
content = content.concat(moduleContent);
|
|
145
|
+
}
|
|
146
|
+
const projectContent = createProjectFacts(this.project.projectPrefix);
|
|
147
|
+
content = content.concat(projectContent);
|
|
148
|
+
return content;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Sets individual CardType programs
|
|
152
|
+
private async setCardTypesPrograms() {
|
|
153
|
+
const cardTypes = await this.project.cardTypes();
|
|
154
|
+
|
|
155
|
+
for (const cardType of await Promise.all(
|
|
156
|
+
cardTypes.map((c) => this.project.resource<CardType>(c.name)),
|
|
157
|
+
)) {
|
|
158
|
+
if (!cardType) continue;
|
|
159
|
+
|
|
160
|
+
const cardTypeContent = createCardTypeFacts(cardType);
|
|
161
|
+
setProgram(cardType.name, cardTypeContent, [ALL_CATEGORY]);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Sets individual FieldType programs
|
|
166
|
+
private async setFieldTypesPrograms() {
|
|
167
|
+
const fieldTypes = await this.project.fieldTypes();
|
|
168
|
+
|
|
169
|
+
for (const fieldType of await Promise.all(
|
|
170
|
+
fieldTypes.map((m) => this.project.resource<FieldType>(m.name)),
|
|
171
|
+
)) {
|
|
172
|
+
if (!fieldType) continue;
|
|
173
|
+
|
|
174
|
+
const fieldTypeContent = createFieldTypeFacts(fieldType);
|
|
175
|
+
setProgram(fieldType.name, fieldTypeContent, [ALL_CATEGORY]);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Sets individual LinkType programs
|
|
180
|
+
private async setLinkTypesPrograms() {
|
|
181
|
+
const linkTypes = await this.project.linkTypes();
|
|
182
|
+
|
|
183
|
+
for (const linkType of await Promise.all(
|
|
184
|
+
linkTypes.map((c) => this.project.resource<LinkType>(c.name)),
|
|
185
|
+
)) {
|
|
186
|
+
if (!linkType) continue;
|
|
187
|
+
|
|
188
|
+
const linkTypeContent = createLinkTypeFacts(linkType);
|
|
189
|
+
setProgram(linkType.name, linkTypeContent, [ALL_CATEGORY]);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Sets individual Workflow programs
|
|
194
|
+
private async setWorkflowsPrograms() {
|
|
195
|
+
const workflows = await this.project.workflows();
|
|
196
|
+
|
|
197
|
+
for (const workflow of await Promise.all(
|
|
198
|
+
workflows.map((m) => this.project.resource<Workflow>(m.name)),
|
|
199
|
+
)) {
|
|
200
|
+
if (!workflow) continue;
|
|
201
|
+
|
|
202
|
+
const workflowContent = createWorkflowFacts(workflow);
|
|
203
|
+
setProgram(workflow.name, workflowContent, [ALL_CATEGORY]);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Sets individual Report programs
|
|
208
|
+
private async setReportsPrograms() {
|
|
209
|
+
const reports = await this.project.reports();
|
|
210
|
+
|
|
211
|
+
for (const report of await Promise.all(
|
|
212
|
+
reports.map((r) => this.project.resource<ReportMetadata>(r.name)),
|
|
213
|
+
)) {
|
|
214
|
+
if (!report) continue;
|
|
215
|
+
|
|
216
|
+
const reportContent = createReportFacts(report);
|
|
217
|
+
setProgram(report.name, reportContent, [ALL_CATEGORY]);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Sets individual Template programs
|
|
222
|
+
private async setTemplatesPrograms() {
|
|
223
|
+
const templates = await this.project.templates();
|
|
224
|
+
|
|
225
|
+
for (const template of await Promise.all(
|
|
226
|
+
templates.map((r) => this.project.resource<TemplateMetadata>(r.name)),
|
|
227
|
+
)) {
|
|
228
|
+
if (!template) continue;
|
|
229
|
+
|
|
230
|
+
const templateContent = createTemplateFacts(template);
|
|
231
|
+
const cards = await this.getCards(template.name);
|
|
232
|
+
for (const card of cards) {
|
|
233
|
+
const cardContent = await createCardFacts(card, this.project);
|
|
234
|
+
setProgram(card.key, cardContent, [ALL_CATEGORY]);
|
|
235
|
+
}
|
|
236
|
+
setProgram(template.name, templateContent, [ALL_CATEGORY]);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Sets individual Calculation programs
|
|
241
|
+
private async setCalculationsPrograms() {
|
|
242
|
+
const calculations = await this.project.calculations(ResourcesFrom.all);
|
|
243
|
+
|
|
244
|
+
for (const calculationFile of calculations) {
|
|
245
|
+
if (calculationFile.path) {
|
|
246
|
+
const moduleLogicFile = resolve(
|
|
247
|
+
join(calculationFile.path, basename(calculationFile.name)),
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const filePath = moduleLogicFile.endsWith('.lp')
|
|
251
|
+
? moduleLogicFile
|
|
252
|
+
: moduleLogicFile + '.lp';
|
|
253
|
+
|
|
254
|
+
if (pathExists(filePath)) {
|
|
255
|
+
try {
|
|
256
|
+
const moduleContent = await readFile(filePath, 'utf-8');
|
|
257
|
+
setProgram(calculationFile.name, moduleContent, [ALL_CATEGORY]);
|
|
258
|
+
} catch (error) {
|
|
259
|
+
this.logger.warn(
|
|
260
|
+
error,
|
|
261
|
+
`Failed to read calculation ${calculationFile.name}`,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Gets either all the cards (no parent), or a subtree.
|
|
270
|
+
private async getCards(templateName?: string): Promise<Card[]> {
|
|
271
|
+
return templateName
|
|
272
|
+
? this.project.templateCards(templateName)
|
|
273
|
+
: this.project.cards();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Checks that Clingo successfully returned result.
|
|
277
|
+
private async parseClingoResult(
|
|
278
|
+
data: string[],
|
|
279
|
+
): Promise<ParseResult<BaseResult>> {
|
|
280
|
+
const parser = new ClingoParser();
|
|
281
|
+
return parser.parseInput(data.join('\n'));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private async run(query: string, context: Context): Promise<string[]> {
|
|
285
|
+
try {
|
|
286
|
+
const res = await CalculationEngine.mutex.runExclusive(async () => {
|
|
287
|
+
// Use the main category to include all programs
|
|
288
|
+
const basePrograms = [ALL_CATEGORY];
|
|
289
|
+
|
|
290
|
+
this.logger.trace(
|
|
291
|
+
{
|
|
292
|
+
clingo: true,
|
|
293
|
+
},
|
|
294
|
+
'Solving',
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
const contextFacts = createContextFacts(context);
|
|
298
|
+
setProgram('context', contextFacts, [ALL_CATEGORY]);
|
|
299
|
+
// Then solve with the program - need to pass the program as parameter
|
|
300
|
+
return solve(query, basePrograms);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
if (res && res.answers && res.answers.length > 0) {
|
|
304
|
+
return res.answers;
|
|
305
|
+
}
|
|
306
|
+
throw new Error('Failed to run Clingo solve. No answers returned.');
|
|
307
|
+
} catch (error) {
|
|
308
|
+
this.logger.error(
|
|
309
|
+
{
|
|
310
|
+
error,
|
|
311
|
+
query,
|
|
312
|
+
},
|
|
313
|
+
'Clingo solve failed',
|
|
314
|
+
);
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Generates a logic program.
|
|
321
|
+
*/
|
|
322
|
+
public async generate() {
|
|
323
|
+
await CalculationEngine.mutex.runExclusive(async () => {
|
|
324
|
+
this.logger.trace(
|
|
325
|
+
{
|
|
326
|
+
clingo: true,
|
|
327
|
+
},
|
|
328
|
+
'Generating logic program',
|
|
329
|
+
);
|
|
330
|
+
removeAllPrograms();
|
|
331
|
+
|
|
332
|
+
if (!this.modulesInitialized) {
|
|
333
|
+
await this.initializeModules();
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Set base common programs with main category
|
|
337
|
+
setProgram('base', lpFiles.common.base, [ALL_CATEGORY]);
|
|
338
|
+
setProgram('queryLanguage', lpFiles.common.queryLanguage, [ALL_CATEGORY]);
|
|
339
|
+
setProgram('utils', lpFiles.common.utils, [ALL_CATEGORY]);
|
|
340
|
+
setProgram('modules', this.modules, [ALL_CATEGORY]);
|
|
341
|
+
|
|
342
|
+
// Set individual resource type programs
|
|
343
|
+
await this.setCardTreeContent();
|
|
344
|
+
await this.setCardTypesPrograms();
|
|
345
|
+
await this.setFieldTypesPrograms();
|
|
346
|
+
await this.setLinkTypesPrograms();
|
|
347
|
+
await this.setWorkflowsPrograms();
|
|
348
|
+
await this.setReportsPrograms();
|
|
349
|
+
await this.setTemplatesPrograms();
|
|
350
|
+
await this.setCalculationsPrograms();
|
|
351
|
+
|
|
352
|
+
this.logger.trace(
|
|
353
|
+
{
|
|
354
|
+
clingo: true,
|
|
355
|
+
},
|
|
356
|
+
'Logic program set',
|
|
357
|
+
);
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* When card changes, update the card specific calculations.
|
|
363
|
+
* @param changedCard Card that was changed.
|
|
364
|
+
*/
|
|
365
|
+
public async handleCardChanged(changedCard: Card) {
|
|
366
|
+
await CalculationEngine.mutex.runExclusive(async () => {
|
|
367
|
+
await this.setCardContent(changedCard);
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* When cards are removed, automatically remove card-specific calculations.
|
|
373
|
+
* @param deletedCard Card that is to be removed.
|
|
374
|
+
*/
|
|
375
|
+
public async handleDeleteCard(deletedCard: Card) {
|
|
376
|
+
if (!deletedCard) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
try {
|
|
380
|
+
await CalculationEngine.mutex.runExclusive(async () => {
|
|
381
|
+
if (!removeProgram(deletedCard.key)) {
|
|
382
|
+
this.logger.warn(
|
|
383
|
+
{
|
|
384
|
+
cardKey: deletedCard.key,
|
|
385
|
+
},
|
|
386
|
+
'Tried to remove card program that does not exist',
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
} catch {
|
|
391
|
+
this.logger.warn('Removing program failed');
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* When new cards are added, automatically calculate card-specific values.
|
|
397
|
+
* @param cards Added cards.
|
|
398
|
+
*/
|
|
399
|
+
public async handleNewCards(cards: Card[]) {
|
|
400
|
+
if (!cards) {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
await CalculationEngine.mutex.runExclusive(async () => {
|
|
404
|
+
for (const card of cards) {
|
|
405
|
+
await this.setCardContent(card);
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
const cardKeys = cards.map((item) => item.key);
|
|
409
|
+
const queryResult = await this.creationQuery(cardKeys, 'localApp');
|
|
410
|
+
if (
|
|
411
|
+
!queryResult ||
|
|
412
|
+
queryResult.at(0) === undefined ||
|
|
413
|
+
queryResult.at(0)?.updateFields === undefined
|
|
414
|
+
) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
const fieldsToUpdate = queryResult.at(0)!.updateFields;
|
|
418
|
+
await CardMetadataUpdater.apply(this.project, fieldsToUpdate);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Gets the logic program content for a specific resource
|
|
423
|
+
* @param resourceName The name of the resource
|
|
424
|
+
* @returns The logic program content for the resource
|
|
425
|
+
*/
|
|
426
|
+
public async resourceLogicProgram(
|
|
427
|
+
resourceName: ResourceName,
|
|
428
|
+
): Promise<string> {
|
|
429
|
+
const resource = await this.project.resource(
|
|
430
|
+
resourceNameToString(resourceName),
|
|
431
|
+
);
|
|
432
|
+
if (!resource) {
|
|
433
|
+
throw new Error(
|
|
434
|
+
`Resource '${resourceNameToString(resourceName)}' does not exist in the project`,
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
switch (resourceName.type) {
|
|
439
|
+
case 'cardTypes':
|
|
440
|
+
return createCardTypeFacts(resource as CardType);
|
|
441
|
+
case 'fieldTypes':
|
|
442
|
+
return createFieldTypeFacts(resource as FieldType);
|
|
443
|
+
case 'linkTypes':
|
|
444
|
+
return createLinkTypeFacts(resource as LinkType);
|
|
445
|
+
case 'workflows':
|
|
446
|
+
return createWorkflowFacts(resource as Workflow);
|
|
447
|
+
case 'reports':
|
|
448
|
+
return createReportFacts(resource as ReportMetadata);
|
|
449
|
+
case 'templates':
|
|
450
|
+
return createTemplateFacts(resource as TemplateMetadata);
|
|
451
|
+
default:
|
|
452
|
+
throw new Error(
|
|
453
|
+
`Resource ${resourceNameToString(resourceName)} does not have a logic program`,
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Runs given logic program and creates a graph using clingraph
|
|
460
|
+
* @param data Provide a query or/and a file which can be given to clingraph
|
|
461
|
+
* @param timeout Maximum amount of milliseconds clingraph is allowed to run
|
|
462
|
+
* @returns a base64 encoded image as a string
|
|
463
|
+
*/
|
|
464
|
+
public async runGraph(model: string, view: string, context: Context) {
|
|
465
|
+
this.logger.trace(
|
|
466
|
+
{
|
|
467
|
+
model,
|
|
468
|
+
view,
|
|
469
|
+
},
|
|
470
|
+
'Running graph',
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
const result = await generateReportContent({
|
|
474
|
+
calculate: this,
|
|
475
|
+
contentTemplate: graphvizReport.content,
|
|
476
|
+
queryTemplate: graphvizReport.query,
|
|
477
|
+
options: {
|
|
478
|
+
model: model,
|
|
479
|
+
view: view,
|
|
480
|
+
},
|
|
481
|
+
graph: true,
|
|
482
|
+
context,
|
|
483
|
+
});
|
|
484
|
+
let graph = (await instance()).renderString(result, {
|
|
485
|
+
format: 'svg',
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// asciidoctor-pdf will error on the a elements with xtitle attribute
|
|
489
|
+
// because of the unescaped <font> tags.
|
|
490
|
+
if (context === 'exportedDocument') {
|
|
491
|
+
graph = graph.replace(/xlink:title="[^"]*"/g, '');
|
|
492
|
+
}
|
|
493
|
+
return sanitizeSvgBase64(graph);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Runs a logic program using clingo.
|
|
498
|
+
* @param query Logic program to be run
|
|
499
|
+
* @returns parsed program output
|
|
500
|
+
*/
|
|
501
|
+
public async runLogicProgram(query: string, context: Context = 'localApp') {
|
|
502
|
+
const clingoOutput = await this.run(query, context);
|
|
503
|
+
return this.parseClingoResult(clingoOutput);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Runs a pre-defined query.
|
|
508
|
+
* @param queryName Name of the query file without extension
|
|
509
|
+
* @param options Any object that contains state for handlebars
|
|
510
|
+
* @returns parsed program output
|
|
511
|
+
*/
|
|
512
|
+
public async runQuery<T extends QueryName>(
|
|
513
|
+
queryName: T,
|
|
514
|
+
context: Context = 'localApp',
|
|
515
|
+
options?: unknown,
|
|
516
|
+
): Promise<QueryResult<T>[]> {
|
|
517
|
+
let content = lpFiles.queries[queryName];
|
|
518
|
+
const handlebars = Handlebars.create();
|
|
519
|
+
const compiled = handlebars.compile(content);
|
|
520
|
+
content = compiled(options || {});
|
|
521
|
+
if (!content) {
|
|
522
|
+
throw new Error(`Query file ${queryName} not found`);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
this.logger.trace({ queryName }, 'Running query');
|
|
526
|
+
const clingoOutput = await this.run(content, context);
|
|
527
|
+
|
|
528
|
+
const result = await this.parseClingoResult(clingoOutput);
|
|
529
|
+
|
|
530
|
+
if (result.error) {
|
|
531
|
+
throw new Error(result.error);
|
|
532
|
+
}
|
|
533
|
+
return result.results as QueryResult<T>[];
|
|
534
|
+
}
|
|
535
|
+
}
|
|
@@ -14,9 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
import { type Dirent, readdirSync } from 'node:fs';
|
|
16
16
|
import { readdir } from 'node:fs/promises';
|
|
17
|
-
import { join } from 'node:path';
|
|
17
|
+
import { extname, join } from 'node:path';
|
|
18
18
|
|
|
19
|
-
import { CardContainer } from '../card-container.js';
|
|
20
19
|
import type { Project } from '../project.js';
|
|
21
20
|
import type { ProjectPaths } from './project-paths.js';
|
|
22
21
|
import type {
|
|
@@ -38,6 +37,9 @@ export enum ResourcesFrom {
|
|
|
38
37
|
localOnly = 'local',
|
|
39
38
|
}
|
|
40
39
|
|
|
40
|
+
// This class collects resources that have these types of files.
|
|
41
|
+
const allowedExtensions = ['.lp', '.json'];
|
|
42
|
+
|
|
41
43
|
// Helper class to contain collected resources.
|
|
42
44
|
class ResourceCollection {
|
|
43
45
|
public calculations: Resource[] = [];
|
|
@@ -55,17 +57,31 @@ class ResourceCollection {
|
|
|
55
57
|
* @param type Resource array type to return.
|
|
56
58
|
* @returns resource array of a give type.
|
|
57
59
|
*/
|
|
58
|
-
public resourceArray(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (type === '
|
|
65
|
-
if (type === '
|
|
66
|
-
if (type === '
|
|
67
|
-
if (type === '
|
|
68
|
-
|
|
60
|
+
public resourceArray(
|
|
61
|
+
type: ResourceFolderType,
|
|
62
|
+
moduleName?: string,
|
|
63
|
+
): Resource[] {
|
|
64
|
+
let resources: Resource[] = [];
|
|
65
|
+
|
|
66
|
+
if (type === 'calculations') resources = this.calculations;
|
|
67
|
+
else if (type === 'cardTypes') resources = this.cardTypes;
|
|
68
|
+
else if (type === 'fieldTypes') resources = this.fieldTypes;
|
|
69
|
+
else if (type === 'graphViews') resources = this.graphViews;
|
|
70
|
+
else if (type === 'graphModels') resources = this.graphModels;
|
|
71
|
+
else if (type === 'linkTypes') resources = this.linkTypes;
|
|
72
|
+
else if (type === 'reports') resources = this.reports;
|
|
73
|
+
else if (type === 'templates') resources = this.templates;
|
|
74
|
+
else if (type === 'workflows') resources = this.workflows;
|
|
75
|
+
else throw new Error(`Unknown resource type '${type}'`);
|
|
76
|
+
|
|
77
|
+
if (moduleName) {
|
|
78
|
+
resources = resources.filter((item) => {
|
|
79
|
+
const { prefix } = resourceName(item.name);
|
|
80
|
+
return moduleName === prefix;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return resources;
|
|
69
85
|
}
|
|
70
86
|
}
|
|
71
87
|
|
|
@@ -95,9 +111,7 @@ export class ResourceCollector {
|
|
|
95
111
|
}
|
|
96
112
|
|
|
97
113
|
const isValidFile = (item: Dirent): boolean =>
|
|
98
|
-
item.isFile() &&
|
|
99
|
-
item.name !== CardContainer.schemaContentFile &&
|
|
100
|
-
item.name !== '.gitkeep';
|
|
114
|
+
item.isFile() && allowedExtensions.includes(extname(item.name));
|
|
101
115
|
|
|
102
116
|
const processResource = async (resource: Dirent): Promise<Resource[]> => {
|
|
103
117
|
const resourcePath = join(
|
|
@@ -162,6 +176,7 @@ export class ResourceCollector {
|
|
|
162
176
|
// Adds a resource type from all modules.
|
|
163
177
|
private async addResourcesFromModules(
|
|
164
178
|
type: ResourceFolderType,
|
|
179
|
+
moduleName?: string,
|
|
165
180
|
): Promise<Resource[]> {
|
|
166
181
|
try {
|
|
167
182
|
// 'modules' is a bit special; it is collected separately from actual resources.
|
|
@@ -174,7 +189,7 @@ export class ResourceCollector {
|
|
|
174
189
|
}
|
|
175
190
|
|
|
176
191
|
await this.addModuleResources();
|
|
177
|
-
return this.modules.resourceArray(type);
|
|
192
|
+
return this.modules.resourceArray(type, moduleName);
|
|
178
193
|
} catch {
|
|
179
194
|
return [];
|
|
180
195
|
}
|
|
@@ -216,13 +231,10 @@ export class ResourceCollector {
|
|
|
216
231
|
}
|
|
217
232
|
resources.push(
|
|
218
233
|
...entries
|
|
219
|
-
.filter(
|
|
220
|
-
|
|
221
|
-
entry.isFile() &&
|
|
222
|
-
|
|
223
|
-
entry.name !== CardContainer.schemaContentFile
|
|
224
|
-
);
|
|
225
|
-
})
|
|
234
|
+
.filter(
|
|
235
|
+
(entry) =>
|
|
236
|
+
entry.isFile() && allowedExtensions.includes(extname(entry.name)),
|
|
237
|
+
)
|
|
226
238
|
.map((entry) => {
|
|
227
239
|
if (entry.name.endsWith('.json')) {
|
|
228
240
|
entry.name = stripExtension(entry.name);
|
|
@@ -261,10 +273,14 @@ export class ResourceCollector {
|
|
|
261
273
|
/**
|
|
262
274
|
* Collect specific resource from modules.
|
|
263
275
|
* @param type Type of resource (e.g. 'templates').
|
|
276
|
+
* @param moduleName Name of the module to collect resources from
|
|
264
277
|
* @returns array of collected items.
|
|
265
278
|
*/
|
|
266
|
-
public async collectResourcesFromModules(
|
|
267
|
-
|
|
279
|
+
public async collectResourcesFromModules(
|
|
280
|
+
type: ResourceFolderType,
|
|
281
|
+
moduleName?: string,
|
|
282
|
+
) {
|
|
283
|
+
return (await this.addResourcesFromModules(type, moduleName)).map((item) =>
|
|
268
284
|
stripExtension(item.name),
|
|
269
285
|
);
|
|
270
286
|
}
|
|
@@ -284,6 +300,9 @@ export class ResourceCollector {
|
|
|
284
300
|
|
|
285
301
|
const { type } = resourceName(resource.name);
|
|
286
302
|
switch (type) {
|
|
303
|
+
case 'calculations':
|
|
304
|
+
addItem(this.local.calculations, resource);
|
|
305
|
+
break;
|
|
287
306
|
case 'cardTypes':
|
|
288
307
|
addItem(this.local.cardTypes, resource);
|
|
289
308
|
break;
|