@cyberismo/data-handler 0.0.8 → 0.0.10

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.
Files changed (258) hide show
  1. package/dist/command-handler.d.ts +11 -2
  2. package/dist/command-handler.js +61 -18
  3. package/dist/command-handler.js.map +1 -1
  4. package/dist/command-manager.js +8 -8
  5. package/dist/command-manager.js.map +1 -1
  6. package/dist/commands/calculate.d.ts +7 -44
  7. package/dist/commands/calculate.js +8 -389
  8. package/dist/commands/calculate.js.map +1 -1
  9. package/dist/commands/create.d.ts +6 -3
  10. package/dist/commands/create.js +27 -5
  11. package/dist/commands/create.js.map +1 -1
  12. package/dist/commands/edit.d.ts +15 -3
  13. package/dist/commands/edit.js +52 -9
  14. package/dist/commands/edit.js.map +1 -1
  15. package/dist/commands/export.d.ts +11 -9
  16. package/dist/commands/export.js +80 -28
  17. package/dist/commands/export.js.map +1 -1
  18. package/dist/commands/import.d.ts +7 -0
  19. package/dist/commands/import.js +13 -0
  20. package/dist/commands/import.js.map +1 -1
  21. package/dist/commands/move.d.ts +11 -12
  22. package/dist/commands/move.js +12 -13
  23. package/dist/commands/move.js.map +1 -1
  24. package/dist/commands/remove.d.ts +1 -3
  25. package/dist/commands/remove.js +4 -6
  26. package/dist/commands/remove.js.map +1 -1
  27. package/dist/commands/rename.d.ts +1 -3
  28. package/dist/commands/rename.js +3 -6
  29. package/dist/commands/rename.js.map +1 -1
  30. package/dist/commands/show.d.ts +37 -5
  31. package/dist/commands/show.js +89 -8
  32. package/dist/commands/show.js.map +1 -1
  33. package/dist/commands/transition.d.ts +1 -3
  34. package/dist/commands/transition.js +3 -5
  35. package/dist/commands/transition.js.map +1 -1
  36. package/dist/commands/update.d.ts +5 -1
  37. package/dist/commands/update.js +7 -1
  38. package/dist/commands/update.js.map +1 -1
  39. package/dist/commands/validate.d.ts +7 -0
  40. package/dist/commands/validate.js +31 -4
  41. package/dist/commands/validate.js.map +1 -1
  42. package/dist/containers/card-container.d.ts +6 -0
  43. package/dist/containers/card-container.js +61 -0
  44. package/dist/containers/card-container.js.map +1 -1
  45. package/dist/containers/project/calculation-engine.d.ts +90 -0
  46. package/dist/containers/project/calculation-engine.js +402 -0
  47. package/dist/containers/project/calculation-engine.js.map +1 -0
  48. package/dist/containers/project/resource-collector.js +9 -13
  49. package/dist/containers/project/resource-collector.js.map +1 -1
  50. package/dist/containers/project.d.ts +18 -1
  51. package/dist/containers/project.js +28 -55
  52. package/dist/containers/project.js.map +1 -1
  53. package/dist/containers/template.d.ts +5 -0
  54. package/dist/containers/template.js +9 -0
  55. package/dist/containers/template.js.map +1 -1
  56. package/dist/index.d.ts +5 -2
  57. package/dist/index.js +5 -1
  58. package/dist/index.js.map +1 -1
  59. package/dist/interfaces/macros.d.ts +4 -2
  60. package/dist/interfaces/project-interfaces.d.ts +21 -0
  61. package/dist/interfaces/project-interfaces.js +4 -0
  62. package/dist/interfaces/project-interfaces.js.map +1 -1
  63. package/dist/interfaces/resource-interfaces.d.ts +4 -2
  64. package/dist/interfaces/resource-interfaces.js.map +1 -1
  65. package/dist/macros/base-macro.d.ts +2 -1
  66. package/dist/macros/base-macro.js +14 -2
  67. package/dist/macros/base-macro.js.map +1 -1
  68. package/dist/macros/common.d.ts +16 -9
  69. package/dist/macros/common.js +22 -9
  70. package/dist/macros/common.js.map +1 -1
  71. package/dist/macros/createCards/index.d.ts +2 -2
  72. package/dist/macros/createCards/index.js +4 -0
  73. package/dist/macros/createCards/index.js.map +1 -1
  74. package/dist/macros/graph/index.d.ts +1 -3
  75. package/dist/macros/graph/index.js +1 -6
  76. package/dist/macros/graph/index.js.map +1 -1
  77. package/dist/macros/image/index.d.ts +39 -0
  78. package/dist/macros/image/index.js +82 -0
  79. package/dist/macros/image/index.js.map +1 -0
  80. package/dist/macros/image/metadata.d.ts +18 -0
  81. package/dist/macros/image/metadata.js +22 -0
  82. package/dist/macros/image/metadata.js.map +1 -0
  83. package/dist/macros/include/index.d.ts +31 -0
  84. package/dist/macros/include/index.js +94 -0
  85. package/dist/macros/include/index.js.map +1 -0
  86. package/dist/macros/include/metadata.d.ts +15 -0
  87. package/dist/macros/include/metadata.js +19 -0
  88. package/dist/macros/include/metadata.js.map +1 -0
  89. package/dist/macros/index.d.ts +33 -31
  90. package/dist/macros/index.js +142 -71
  91. package/dist/macros/index.js.map +1 -1
  92. package/dist/macros/percentage/index.d.ts +28 -0
  93. package/dist/macros/percentage/index.js +33 -0
  94. package/dist/macros/percentage/index.js.map +1 -0
  95. package/dist/macros/percentage/metadata.d.ts +15 -0
  96. package/dist/macros/percentage/metadata.js +19 -0
  97. package/dist/macros/percentage/metadata.js.map +1 -0
  98. package/dist/macros/report/index.d.ts +2 -6
  99. package/dist/macros/report/index.js +3 -7
  100. package/dist/macros/report/index.js.map +1 -1
  101. package/dist/macros/scoreCard/index.d.ts +14 -15
  102. package/dist/macros/scoreCard/index.js +14 -18
  103. package/dist/macros/scoreCard/index.js.map +1 -1
  104. package/dist/macros/vega/index.d.ts +28 -0
  105. package/dist/macros/vega/index.js +27 -0
  106. package/dist/macros/vega/index.js.map +1 -0
  107. package/dist/macros/vega/metadata.d.ts +15 -0
  108. package/dist/macros/vega/metadata.js +7 -0
  109. package/dist/macros/vega/metadata.js.map +1 -0
  110. package/dist/macros/vegalite/index.d.ts +26 -0
  111. package/dist/macros/vegalite/index.js +24 -0
  112. package/dist/macros/vegalite/index.js.map +1 -0
  113. package/dist/macros/vegalite/metadata.d.ts +15 -0
  114. package/dist/macros/vegalite/metadata.js +7 -0
  115. package/dist/macros/vegalite/metadata.js.map +1 -0
  116. package/dist/macros/xref/index.d.ts +26 -0
  117. package/dist/macros/xref/index.js +53 -0
  118. package/dist/macros/xref/index.js.map +1 -0
  119. package/dist/macros/xref/metadata.d.ts +15 -0
  120. package/dist/macros/xref/metadata.js +19 -0
  121. package/dist/macros/xref/metadata.js.map +1 -0
  122. package/dist/module-manager.d.ts +1 -0
  123. package/dist/module-manager.js +14 -4
  124. package/dist/module-manager.js.map +1 -1
  125. package/dist/permissions/action-guard.d.ts +2 -2
  126. package/dist/permissions/action-guard.js +1 -1
  127. package/dist/permissions/action-guard.js.map +1 -1
  128. package/dist/resources/card-type-resource.d.ts +2 -0
  129. package/dist/resources/card-type-resource.js +63 -0
  130. package/dist/resources/card-type-resource.js.map +1 -1
  131. package/dist/resources/file-resource.d.ts +2 -0
  132. package/dist/resources/file-resource.js +12 -4
  133. package/dist/resources/file-resource.js.map +1 -1
  134. package/dist/resources/folder-resource.d.ts +19 -0
  135. package/dist/resources/folder-resource.js +51 -2
  136. package/dist/resources/folder-resource.js.map +1 -1
  137. package/dist/resources/graph-model-resource.js +1 -1
  138. package/dist/resources/graph-model-resource.js.map +1 -1
  139. package/dist/resources/graph-view-resource.js +1 -1
  140. package/dist/resources/graph-view-resource.js.map +1 -1
  141. package/dist/resources/report-resource.js +1 -1
  142. package/dist/resources/report-resource.js.map +1 -1
  143. package/dist/resources/resource-object.d.ts +8 -0
  144. package/dist/resources/resource-object.js +10 -1
  145. package/dist/resources/resource-object.js.map +1 -1
  146. package/dist/resources/template-resource.js +1 -1
  147. package/dist/resources/template-resource.js.map +1 -1
  148. package/dist/resources/workflow-resource.d.ts +2 -0
  149. package/dist/resources/workflow-resource.js +53 -9
  150. package/dist/resources/workflow-resource.js.map +1 -1
  151. package/dist/svg/index.d.ts +15 -0
  152. package/dist/svg/index.js +16 -0
  153. package/dist/svg/index.js.map +1 -0
  154. package/dist/svg/lib.d.ts +9 -0
  155. package/dist/svg/lib.js +26 -0
  156. package/dist/svg/lib.js.map +1 -0
  157. package/dist/svg/percentage.d.ts +25 -0
  158. package/dist/svg/percentage.js +90 -0
  159. package/dist/svg/percentage.js.map +1 -0
  160. package/dist/svg/scoreCard.d.ts +19 -0
  161. package/dist/svg/scoreCard.js +55 -0
  162. package/dist/svg/scoreCard.js.map +1 -0
  163. package/dist/types/queries.d.ts +8 -7
  164. package/dist/types/queries.js.map +1 -1
  165. package/dist/utils/card-utils.d.ts +6 -0
  166. package/dist/utils/card-utils.js +12 -0
  167. package/dist/utils/card-utils.js.map +1 -1
  168. package/dist/utils/clingo-facts.d.ts +2 -1
  169. package/dist/utils/clingo-facts.js +19 -2
  170. package/dist/utils/clingo-facts.js.map +1 -1
  171. package/dist/utils/clingo-parser.d.ts +1 -0
  172. package/dist/utils/clingo-parser.js +22 -100
  173. package/dist/utils/clingo-parser.js.map +1 -1
  174. package/dist/utils/constants.d.ts +7 -0
  175. package/dist/utils/constants.js +14 -0
  176. package/dist/utils/constants.js.map +1 -1
  177. package/dist/utils/csv.js.map +1 -1
  178. package/dist/utils/report.d.ts +17 -3
  179. package/dist/utils/report.js +38 -2
  180. package/dist/utils/report.js.map +1 -1
  181. package/dist/utils/resource-utils.d.ts +2 -1
  182. package/dist/utils/resource-utils.js +12 -3
  183. package/dist/utils/resource-utils.js.map +1 -1
  184. package/dist/utils/user-preferences.d.ts +1 -11
  185. package/dist/utils/user-preferences.js +30 -13
  186. package/dist/utils/user-preferences.js.map +1 -1
  187. package/dist/utils/validate.d.ts +2 -3
  188. package/dist/utils/validate.js +2 -2
  189. package/dist/utils/validate.js.map +1 -1
  190. package/package.json +8 -6
  191. package/src/command-handler.ts +96 -17
  192. package/src/command-manager.ts +8 -8
  193. package/src/commands/calculate.ts +11 -525
  194. package/src/commands/create.ts +35 -6
  195. package/src/commands/edit.ts +94 -11
  196. package/src/commands/export.ts +104 -31
  197. package/src/commands/import.ts +16 -0
  198. package/src/commands/move.ts +12 -15
  199. package/src/commands/remove.ts +4 -8
  200. package/src/commands/rename.ts +3 -12
  201. package/src/commands/show.ts +126 -9
  202. package/src/commands/transition.ts +3 -7
  203. package/src/commands/update.ts +6 -0
  204. package/src/commands/validate.ts +41 -13
  205. package/src/containers/card-container.ts +74 -0
  206. package/src/containers/project/calculation-engine.ts +535 -0
  207. package/src/containers/project/resource-collector.ts +13 -15
  208. package/src/containers/project.ts +30 -66
  209. package/src/containers/template.ts +16 -0
  210. package/src/index.ts +13 -2
  211. package/src/interfaces/macros.ts +4 -1
  212. package/src/interfaces/project-interfaces.ts +27 -0
  213. package/src/interfaces/resource-interfaces.ts +5 -2
  214. package/src/macros/base-macro.ts +19 -4
  215. package/src/macros/common.ts +22 -9
  216. package/src/macros/createCards/index.ts +6 -2
  217. package/src/macros/graph/index.ts +6 -10
  218. package/src/macros/image/index.ts +128 -0
  219. package/src/macros/image/metadata.ts +25 -0
  220. package/src/macros/include/index.ts +143 -0
  221. package/src/macros/include/metadata.ts +22 -0
  222. package/src/macros/index.ts +150 -98
  223. package/src/macros/percentage/index.ts +50 -0
  224. package/src/macros/percentage/metadata.ts +22 -0
  225. package/src/macros/report/index.ts +4 -12
  226. package/src/macros/scoreCard/index.ts +21 -25
  227. package/src/macros/vega/index.ts +55 -0
  228. package/src/macros/vega/metadata.ts +21 -0
  229. package/src/macros/vegalite/index.ts +46 -0
  230. package/src/macros/vegalite/metadata.ts +21 -0
  231. package/src/macros/xref/index.ts +73 -0
  232. package/src/macros/xref/metadata.ts +22 -0
  233. package/src/module-manager.ts +15 -5
  234. package/src/permissions/action-guard.ts +3 -3
  235. package/src/resources/card-type-resource.ts +100 -0
  236. package/src/resources/file-resource.ts +16 -4
  237. package/src/resources/folder-resource.ts +59 -2
  238. package/src/resources/graph-model-resource.ts +1 -1
  239. package/src/resources/graph-view-resource.ts +1 -1
  240. package/src/resources/report-resource.ts +1 -1
  241. package/src/resources/resource-object.ts +19 -1
  242. package/src/resources/template-resource.ts +1 -1
  243. package/src/resources/workflow-resource.ts +68 -13
  244. package/src/svg/index.ts +15 -0
  245. package/src/svg/lib.ts +31 -0
  246. package/src/svg/percentage.ts +97 -0
  247. package/src/svg/scoreCard.ts +88 -0
  248. package/src/types/queries.ts +8 -7
  249. package/src/types/string-pixel-width.d.ts +23 -0
  250. package/src/utils/card-utils.ts +13 -0
  251. package/src/utils/clingo-facts.ts +65 -3
  252. package/src/utils/clingo-parser.ts +31 -144
  253. package/src/utils/constants.ts +16 -0
  254. package/src/utils/csv.ts +1 -1
  255. package/src/utils/report.ts +45 -4
  256. package/src/utils/resource-utils.ts +12 -2
  257. package/src/utils/user-preferences.ts +32 -14
  258. package/src/utils/validate.ts +3 -3
@@ -21,9 +21,11 @@ import type {
21
21
  Card,
22
22
  ResourceFolderType,
23
23
  } from '../interfaces/project-interfaces.js';
24
+ import type { Logger } from 'pino';
24
25
  import { type Project, ResourcesFrom } from '../containers/project.js';
25
26
  import type { ResourceContent } from '../interfaces/resource-interfaces.js';
26
27
  import type { ResourceName } from '../utils/resource-utils.js';
28
+ import { getChildLogger } from '../utils/log-utils.js';
27
29
 
28
30
  // Possible operations to perform when doing "update"
29
31
  export type UpdateOperations = 'add' | 'change' | 'rank' | 'remove';
@@ -44,6 +46,7 @@ export type AddOperation<T> = BaseOperation<T> & {
44
46
  export type ChangeOperation<T> = BaseOperation<T> & {
45
47
  name: 'change';
46
48
  to: T;
49
+ mappingTable?: { stateMapping: Record<string, string> }; // Optional state mapping for workflow changes
47
50
  };
48
51
 
49
52
  // Move item in an array to new position.
@@ -81,6 +84,9 @@ export abstract class AbstractResource {
81
84
  protected abstract usage(cards?: Card[]): Promise<string[]>; // list of card keys or resource names where this resource is used in
82
85
  protected abstract validate(content?: object): Promise<void>; // validate the content
83
86
  protected abstract write(): Promise<void>; // write content to disk
87
+ // Abstract getters
88
+ protected abstract get getType(): string;
89
+ protected abstract getLogger(loggerName: string): Logger;
84
90
  }
85
91
 
86
92
  /**
@@ -110,6 +116,14 @@ export class ResourceObject extends AbstractResource {
110
116
  protected async show(): Promise<ResourceContent> {
111
117
  return {} as ResourceContent;
112
118
  }
119
+ protected get getType(): string {
120
+ return this.type;
121
+ }
122
+ protected getLogger(loggerName: string): Logger {
123
+ return getChildLogger({
124
+ module: loggerName,
125
+ });
126
+ }
113
127
  protected async update<Type>(_key: string, _op: Operation<Type>) {}
114
128
  protected async usage(_cards?: Card[]): Promise<string[]> {
115
129
  return [];
@@ -197,7 +211,11 @@ export class ResourceObject extends AbstractResource {
197
211
  );
198
212
  }
199
213
 
200
- const filename = join(calculation.path, basename(calculation.name));
214
+ const filename = join(
215
+ calculation.path,
216
+ basename(calculation.name) + '.lp',
217
+ );
218
+
201
219
  try {
202
220
  const content = await readFile(filename, 'utf-8');
203
221
  const updatedContent = content.replaceAll(from, to);
@@ -146,7 +146,7 @@ export class TemplateResource extends FolderResource {
146
146
 
147
147
  await super.update(key, op);
148
148
 
149
- const content = { ...(this.content as TemplateMetadata) };
149
+ const content = structuredClone(this.content) as TemplateMetadata;
150
150
 
151
151
  if (key === 'name') {
152
152
  content.name = super.handleScalar(op) as string;
@@ -82,12 +82,8 @@ export class WorkflowResource extends FileResource {
82
82
 
83
83
  // Handle change of workflow state.
84
84
  private async handleStateChange(op: ChangeOperation<WorkflowState>) {
85
- const content = { ...(this.content as Workflow) };
86
- const stateName = (
87
- (op.target as WorkflowState).name
88
- ? (op.target as WorkflowState).name
89
- : op.target
90
- ) as string;
85
+ const content = structuredClone(this.content) as Workflow;
86
+ const stateName = this.targetName(op) as string;
91
87
  // Check that state can be changed to
92
88
  content.transitions = content.transitions.filter(
93
89
  (t) => t.toState !== stateName,
@@ -111,12 +107,8 @@ export class WorkflowResource extends FileResource {
111
107
  // Handle removal of workflow state.
112
108
  // State can be removed with or without replacement.
113
109
  private async handleStateRemoval(op: RemoveOperation<WorkflowState>) {
114
- const content = { ...(this.content as Workflow) };
115
- const stateName = (
116
- (op.target as WorkflowState).name
117
- ? (op.target as WorkflowState).name
118
- : op.target
119
- ) as string;
110
+ const content = structuredClone(this.content) as Workflow;
111
+ const stateName = this.targetName(op) as string;
120
112
 
121
113
  // If there is no replacement value, remove all transitions "to" and "from" this state.
122
114
  if (!op.replacementValue) {
@@ -154,6 +146,42 @@ export class WorkflowResource extends FileResource {
154
146
  }
155
147
  }
156
148
 
149
+ // Returns target name irregardless of the type
150
+ private targetName(op: Operation<WorkflowState | WorkflowTransition>) {
151
+ const name = op.target.name ? op.target.name : op.target;
152
+ return name;
153
+ }
154
+
155
+ // Potentially updates the changed transition with current properties.
156
+ private async transitionObject(op: ChangeOperation<WorkflowTransition>) {
157
+ const content = structuredClone(this.content) as Workflow;
158
+ const targetTransitionName = this.targetName(op);
159
+ const currentTransition = content.transitions.filter(
160
+ (item) => item.name === targetTransitionName,
161
+ )[0];
162
+
163
+ if (currentTransition) {
164
+ op.to.fromState =
165
+ op.to.fromState.length === 0
166
+ ? currentTransition.fromState
167
+ : op.to.fromState;
168
+ op.to.toState = op.to.toState ?? currentTransition.toState;
169
+ }
170
+
171
+ if (
172
+ op.to.name === undefined ||
173
+ op.to.toState === undefined ||
174
+ op.to.fromState == undefined ||
175
+ op.to.fromState.length === 0
176
+ ) {
177
+ throw new Error(
178
+ `Cannot change transition '${targetTransitionName}' for workflow '${this.content.name}'.
179
+ Updated transition must have 'name', 'toState' and 'fromState' properties.`,
180
+ );
181
+ }
182
+ return op.to;
183
+ }
184
+
157
185
  // Check if operation is a string operation.
158
186
  private isStringOperation(op: Operation<unknown>): op is Operation<string> {
159
187
  return typeof op.target === 'string';
@@ -250,7 +278,7 @@ export class WorkflowResource extends FileResource {
250
278
 
251
279
  await super.update(key, op);
252
280
 
253
- const content = { ...(this.content as Workflow) };
281
+ const content = structuredClone(this.content) as Workflow;
254
282
 
255
283
  if (key === 'name') {
256
284
  content.name = super.handleScalar(op) as string;
@@ -270,6 +298,33 @@ export class WorkflowResource extends FileResource {
270
298
  throw new Error(`Unknown property '${key}' for Workflow`);
271
299
  }
272
300
 
301
+ // If workflow transition is removed, then above call to 'handleArray' is all that is needed.
302
+
303
+ if (key === 'transitions' && op.name === 'change') {
304
+ // If workflow transition is changed, update to full object and change the content.
305
+ let changeOp: ChangeOperation<WorkflowTransition>;
306
+ if (this.isStringOperation(op)) {
307
+ const targetTransition = (this.content as Workflow).transitions.find(
308
+ (transition) => transition.name === op.target,
309
+ )!;
310
+ changeOp = {
311
+ name: 'change',
312
+ target: targetTransition as WorkflowTransition,
313
+ to: {
314
+ name: op.to,
315
+ toState: targetTransition.toState,
316
+ fromState: targetTransition.fromState,
317
+ },
318
+ };
319
+ } else {
320
+ changeOp = op as ChangeOperation<WorkflowTransition>;
321
+ }
322
+ const newTransition = await this.transitionObject(changeOp);
323
+ content.transitions = content.transitions.map((item) =>
324
+ item.name == newTransition.name ? newTransition : item,
325
+ );
326
+ }
327
+
273
328
  if (key === 'states' && op.name === 'remove') {
274
329
  // If workflow state is removed, remove all transitions "to" and "from" this state.
275
330
  let removeOp: RemoveOperation<WorkflowState>;
@@ -0,0 +1,15 @@
1
+ /**
2
+ Cyberismo
3
+ Copyright © Cyberismo Ltd and contributors 2025
4
+
5
+ This program is free software: you can redistribute it and/or modify it under
6
+ the terms of the GNU Affero General Public License version 3 as published by
7
+ the Free Software Foundation. This program is distributed in the hope that it
8
+ will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9
+ of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
+ See the GNU Affero General Public License for more details.
11
+ You should have received a copy of the GNU Affero General Public
12
+ License along with this program. If not, see <https://www.gnu.org/licenses/>.
13
+ */
14
+ export * from './scoreCard.js';
15
+ export * from './percentage.js';
package/src/svg/lib.ts ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ Cyberismo
3
+ Copyright © Cyberismo Ltd and contributors 2025
4
+
5
+ This program is free software: you can redistribute it and/or modify it under
6
+ the terms of the GNU Affero General Public License version 3 as published by
7
+ the Free Software Foundation. This program is distributed in the hope that it
8
+ will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9
+ of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
+ See the GNU Affero General Public License for more details.
11
+ You should have received a copy of the GNU Affero General Public
12
+ License along with this program. If not, see <https://www.gnu.org/licenses/>.
13
+ */
14
+ import pixelWidth from 'string-pixel-width';
15
+
16
+ /**
17
+ * Measures the width of a text string
18
+ * @param text - The text to measure
19
+ * @param font - The font to use
20
+ * @param size - The size of the font
21
+ * @param bold - Whether the text is bold
22
+ * @returns The width of the text
23
+ */
24
+ export function measureTextWidth(
25
+ text: string,
26
+ font: string,
27
+ size: number,
28
+ bold = false,
29
+ ): number {
30
+ return pixelWidth(text, { font, size, bold });
31
+ }
@@ -0,0 +1,97 @@
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
+ interface PercentageOptions {
15
+ title: string;
16
+ value: number;
17
+ legend: string;
18
+ colour?: 'blue' | 'green' | 'yellow' | 'red' | 'orange' | 'purple';
19
+ }
20
+
21
+ const SIZE = 160;
22
+ const STROKE = 18;
23
+ const R = (SIZE - STROKE) / 2;
24
+ const CIRC = 2 * Math.PI * R;
25
+ const TITLE_HEIGHT = 70; // Space for the title above the donut
26
+ const EXTRA_WIDTH = 80; // Extra width for the title area
27
+ const SVG_WIDTH = SIZE + EXTRA_WIDTH;
28
+ // SVG_HEIGHT will be calculated dynamically below
29
+ const TITLE_FONT_SIZE = 22;
30
+ const VALUE_FONT_SIZE = 32;
31
+ const LEGEND_FONT_SIZE = 18;
32
+ const DONUT_COLOR_RED = '#b22217';
33
+ const TITLE_Y = 36;
34
+ const LINE_SPACING = 1.2;
35
+
36
+ /**
37
+ * Splits a string into lines of up to maxLen characters, breaking at spaces.
38
+ */
39
+ function wrapText(text: string, maxLen: number): string[] {
40
+ const words = text.split(' ');
41
+ const lines: string[] = [];
42
+ let current = '';
43
+ for (const word of words) {
44
+ if ((current + (current ? ' ' : '') + word).length > maxLen) {
45
+ if (current) lines.push(current);
46
+ current = word;
47
+ } else {
48
+ current += (current ? ' ' : '') + word;
49
+ }
50
+ }
51
+ if (current) lines.push(current);
52
+ return lines;
53
+ }
54
+
55
+ /**
56
+ * Creates a percentage widget as an SVG
57
+ * @param options - The options for the percentage
58
+ * @returns The widget as an SVG
59
+ */
60
+ export function percentage(options: PercentageOptions): string {
61
+ const { title, value, legend, colour = 'blue' } = options;
62
+ const offset = CIRC * (1 - value / 100);
63
+ const DONUT_COLOR = colour === 'red' ? DONUT_COLOR_RED : colour;
64
+ const titleLines = wrapText(title, 24);
65
+ const donutYOffset =
66
+ TITLE_Y +
67
+ (titleLines.length - 1) * TITLE_FONT_SIZE * LINE_SPACING +
68
+ TITLE_FONT_SIZE +
69
+ 24;
70
+ const donutCenterY = donutYOffset + SIZE / 2 - TITLE_HEIGHT / 2;
71
+ const dynamicSVGHeight = donutYOffset + SIZE / 2 + R + 20;
72
+ return `
73
+ <svg width="${SVG_WIDTH}" height="${dynamicSVGHeight}" viewBox="0 0 ${SVG_WIDTH} ${dynamicSVGHeight}" xmlns="http://www.w3.org/2000/svg">
74
+ <title>${title}</title>
75
+
76
+ <!-- Visible Title (wrapped) -->
77
+ <text x="${SVG_WIDTH / 2}" y="${TITLE_Y}" text-anchor="middle" font-size="${TITLE_FONT_SIZE}" font-weight="bold">
78
+ ${titleLines.map((line, i) => `<tspan x='${SVG_WIDTH / 2}' dy='${i === 0 ? 0 : LINE_SPACING}em'>${line}</tspan>`).join('')}
79
+ </text>
80
+
81
+ <!-- Background track -->
82
+ <circle cx="${SVG_WIDTH / 2}" cy="${donutCenterY}" r="${R}"
83
+ fill="none" stroke="#eee" stroke-width="${STROKE}" />
84
+
85
+ <!-- Progress arc -->
86
+ <circle cx="${SVG_WIDTH / 2}" cy="${donutCenterY}" r="${R}"
87
+ fill="none" stroke="${DONUT_COLOR}" stroke-width="${STROKE}"
88
+ stroke-dasharray="${CIRC}" stroke-dashoffset="${offset}"
89
+ stroke-linecap="butt"
90
+ transform="rotate(-90 ${SVG_WIDTH / 2} ${donutCenterY})" />
91
+
92
+ <!-- Numbers -->
93
+ <text x="${SVG_WIDTH / 2}" y="${donutCenterY - 8}" text-anchor="middle" font-size="${VALUE_FONT_SIZE}" font-weight="bold">${value}%</text>
94
+ <text x="${SVG_WIDTH / 2}" y="${donutCenterY + 20}" text-anchor="middle" font-size="${LEGEND_FONT_SIZE}">${legend}</text>
95
+ </svg>
96
+ `;
97
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ Cyberismo
3
+ Copyright © Cyberismo Ltd and contributors 2025
4
+
5
+ This program is free software: you can redistribute it and/or modify it under
6
+ the terms of the GNU Affero General Public License version 3 as published by
7
+ the Free Software Foundation. This program is distributed in the hope that it
8
+ will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9
+ of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
+ See the GNU Affero General Public License for more details.
11
+ You should have received a copy of the GNU Affero General Public
12
+ License along with this program. If not, see <https://www.gnu.org/licenses/>.
13
+ */
14
+ import { measureTextWidth } from './lib.js';
15
+
16
+ // Padding on y-axis
17
+ const PADDING = 24;
18
+ // font used to estimate text width
19
+ const FONT_FAMILY = 'Helvetica';
20
+ const TITLE_SIZE = 16;
21
+ const VALUE_SIZE = 48;
22
+ const UNIT_SIZE = 24;
23
+ const CAPTION_SIZE = 14;
24
+ const UNIT_OFFSET = 3;
25
+ const LINE_GAP_TITLE = 16;
26
+ const LINE_GAP_CAPTION = 16;
27
+
28
+ /**
29
+ * Options for the score card
30
+ * @param title - The title of the score card
31
+ * @param value - The value of the score card
32
+ * @param unit - The unit of the score card
33
+ * @param legend - The legend of the score card
34
+ */
35
+ export interface ScoreCardOptions {
36
+ value: number;
37
+ title?: string;
38
+ legend?: string;
39
+ unit?: string;
40
+ }
41
+
42
+ /**
43
+ * Creates an SVG score card
44
+ * @param options - The options for the score card
45
+ * @returns The SVG score card
46
+ */
47
+ export function scoreCard(options: ScoreCardOptions): string {
48
+ const { title = '', value, unit = '', legend = '' } = options;
49
+
50
+ const titleWidth = measureTextWidth(title, FONT_FAMILY, TITLE_SIZE, false);
51
+ const valueWidth = measureTextWidth(
52
+ String(value),
53
+ FONT_FAMILY,
54
+ VALUE_SIZE,
55
+ true,
56
+ );
57
+ const unitWidth = measureTextWidth(unit, FONT_FAMILY, UNIT_SIZE, false);
58
+ const captionWidth = measureTextWidth(
59
+ legend,
60
+ FONT_FAMILY,
61
+ CAPTION_SIZE,
62
+ false,
63
+ );
64
+
65
+ const valueLineWidth = valueWidth + unitWidth + UNIT_OFFSET;
66
+ const maxTextWidth = Math.max(titleWidth, valueLineWidth, captionWidth);
67
+
68
+ const width = Math.ceil(maxTextWidth + PADDING * 2);
69
+
70
+ const titleHeight = title.length > 0 ? TITLE_SIZE + LINE_GAP_TITLE : 0;
71
+ const valueHeight = VALUE_SIZE;
72
+ const captionHeight = legend.length > 0 ? CAPTION_SIZE + LINE_GAP_CAPTION : 0;
73
+
74
+ const textBlockHeight = titleHeight + valueHeight + captionHeight;
75
+
76
+ const height = Math.ceil(textBlockHeight + PADDING * 2);
77
+
78
+ const svgContent = `<svg class="card" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg">
79
+ <rect rx="8" ry="8" fill="#fff" stroke="#dfe4ea" stroke-width="3" width="${width}" height="${height}"/>
80
+ <g text-anchor="middle">
81
+ <text class="title" x="${width / 2}" y="${TITLE_SIZE / 2 + PADDING}" font-size="${TITLE_SIZE}" font-weight="400" fill="#001829" dominant-baseline="middle">${title}</text>
82
+ <text class="value" x="${width / 2}" y="${titleHeight + VALUE_SIZE / 2 + PADDING}" font-size="${VALUE_SIZE}" font-weight="700" fill="#333" dominant-baseline="middle">${value}<tspan class="unit" font-size="${UNIT_SIZE}" font-weight="400" dx="${UNIT_OFFSET}">${unit}</tspan></text>
83
+ <text class="caption" x="${width / 2}" y="${titleHeight + valueHeight + LINE_GAP_CAPTION + CAPTION_SIZE / 2 + PADDING}" font-size="${CAPTION_SIZE}" font-weight="400" fill="#999" dominant-baseline="middle">${legend}</text>
84
+ </g>
85
+ </svg>`;
86
+
87
+ return svgContent;
88
+ }
@@ -61,11 +61,6 @@ export interface DeniedOperationCollection {
61
61
 
62
62
  export interface BaseResult extends Record<string, unknown> {
63
63
  key: string;
64
- labels: string[];
65
- links: CalculationLink[];
66
- notifications: Notification[];
67
- policyChecks: PolicyCheckCollection;
68
- deniedOperations: DeniedOperationCollection;
69
64
  }
70
65
 
71
66
  export interface ParseResult<T extends BaseResult> {
@@ -97,9 +92,15 @@ interface CardQueryResult extends BaseResult {
97
92
  rank: string;
98
93
  title: string;
99
94
  cardType: string;
95
+ cardTypeDisplayName: string;
100
96
  workflowState: string;
101
97
  lastUpdated: string;
102
- fields?: CardQueryField[];
98
+ fields: CardQueryField[];
99
+ labels: string[];
100
+ links: CalculationLink[];
101
+ notifications: Notification[];
102
+ policyChecks: PolicyCheckCollection;
103
+ deniedOperations: DeniedOperationCollection;
103
104
  }
104
105
  interface FieldsToUpdateQueryResult extends BaseResult {
105
106
  updateFields: UpdateField[];
@@ -135,7 +136,7 @@ interface CardQueryField extends BaseResult {
135
136
  visibility: 'always' | 'optional';
136
137
  index: number;
137
138
  fieldDisplayName: string;
138
- description: string;
139
+ fieldDescription: string;
139
140
  dataType: DataType;
140
141
  isCalculated: boolean;
141
142
  value: string | number | boolean | null | EnumValue | ListValueItem[];
@@ -0,0 +1,23 @@
1
+ /**
2
+ Cyberismo
3
+ Copyright © Cyberismo Ltd and contributors 2025
4
+
5
+ This program is free software: you can redistribute it and/or modify it under
6
+ the terms of the GNU Affero General Public License version 3 as published by
7
+ the Free Software Foundation. This program is distributed in the hope that it
8
+ will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9
+ of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
+ See the GNU Affero General Public License for more details.
11
+ You should have received a copy of the GNU Affero General Public
12
+ License along with this program. If not, see <https://www.gnu.org/licenses/>.
13
+ */
14
+ declare module 'string-pixel-width' {
15
+ interface PixelWidthOptions {
16
+ font?: string;
17
+ size?: number;
18
+ bold?: boolean;
19
+ italic?: boolean;
20
+ }
21
+ function pixelWidth(text: string, options?: PixelWidthOptions): number;
22
+ export default pixelWidth;
23
+ }
@@ -81,3 +81,16 @@ export const sortCards = (a: string, b: string) => {
81
81
  if (aParts[1] < bParts[1]) return -1;
82
82
  return 0;
83
83
  };
84
+
85
+ /**
86
+ * Returns module name from card key
87
+ * @param cardKey card key
88
+ * @returns module name
89
+ */
90
+ export const moduleNameFromCardKey = (cardKey: string) => {
91
+ const parts = cardKey.split('_');
92
+ if (parts.length !== 2) {
93
+ throw new Error(`Invalid card key: ${cardKey}`);
94
+ }
95
+ return parts[0];
96
+ };
@@ -16,7 +16,11 @@ import {
16
16
  type AllowedClingoType,
17
17
  ClingoFactBuilder,
18
18
  } from './clingo-fact-builder.js';
19
- import type { Card, ModuleContent } from '../interfaces/project-interfaces.js';
19
+ import type {
20
+ Card,
21
+ Context,
22
+ ModuleContent,
23
+ } from '../interfaces/project-interfaces.js';
20
24
  import type {
21
25
  CardType,
22
26
  FieldType,
@@ -101,6 +105,23 @@ export const createWorkflowFacts = (workflow: Workflow) => {
101
105
  Facts.Workflow.WORKFLOW,
102
106
  workflow.name,
103
107
  );
108
+
109
+ if (workflow.displayName)
110
+ builder.addFact(
111
+ Facts.Common.FIELD,
112
+ workflow.name,
113
+ 'displayName',
114
+ workflow.displayName,
115
+ );
116
+
117
+ if (workflow.description)
118
+ builder.addFact(
119
+ Facts.Common.FIELD,
120
+ workflow.name,
121
+ 'description',
122
+ workflow.description,
123
+ );
124
+
104
125
  // add states
105
126
  for (const state of workflow.states) {
106
127
  if (state.category) {
@@ -340,6 +361,22 @@ export const createCardTypeFacts = (cardType: CardType) => {
340
361
 
341
362
  builder.addFact(Facts.CardType.CARD_TYPE, cardType.name);
342
363
 
364
+ if (cardType.displayName)
365
+ builder.addFact(
366
+ Facts.Common.FIELD,
367
+ cardType.name,
368
+ 'displayName',
369
+ cardType.displayName,
370
+ );
371
+
372
+ if (cardType.description)
373
+ builder.addFact(
374
+ Facts.Common.FIELD,
375
+ cardType.name,
376
+ 'description',
377
+ cardType.description,
378
+ );
379
+
343
380
  builder.addFact(
344
381
  Facts.Common.FIELD,
345
382
  cardType.name,
@@ -415,14 +452,39 @@ export const createCardTypeFacts = (cardType: CardType) => {
415
452
  return builder.buildAll();
416
453
  };
417
454
 
455
+ export const createContextFacts = (context: Context) => {
456
+ const builder = new ClingoProgramBuilder();
457
+ builder.addFact(context);
458
+ return builder.buildAll();
459
+ };
460
+
418
461
  /**
419
462
  * Creates Clingo facts for a link type.
420
463
  * @param linkType Link type metadata
421
464
  * @returns clingo facts as a string
422
465
  */
423
466
  export const createLinkTypeFacts = (linkType: LinkType) => {
424
- const builder = new ClingoProgramBuilder()
425
- .addFact(Facts.LinkType.LINK_TYPE, linkType.name)
467
+ const builder = new ClingoProgramBuilder().addFact(
468
+ Facts.LinkType.LINK_TYPE,
469
+ linkType.name,
470
+ );
471
+
472
+ if (linkType.displayName)
473
+ builder.addFact(
474
+ Facts.Common.FIELD,
475
+ linkType.name,
476
+ 'displayName',
477
+ linkType.displayName,
478
+ );
479
+
480
+ if (linkType.description)
481
+ builder.addFact(
482
+ Facts.Common.FIELD,
483
+ linkType.name,
484
+ 'description',
485
+ linkType.description,
486
+ );
487
+ builder
426
488
  .addFact(
427
489
  Facts.Common.FIELD,
428
490
  linkType.name,