@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.
Files changed (265) 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 +7 -4
  10. package/dist/commands/create.js +42 -15
  11. package/dist/commands/create.js.map +1 -1
  12. package/dist/commands/edit.d.ts +9 -3
  13. package/dist/commands/edit.js +33 -9
  14. package/dist/commands/edit.js.map +1 -1
  15. package/dist/commands/export.d.ts +13 -11
  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 +21 -2
  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 +2 -4
  25. package/dist/commands/remove.js +8 -16
  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 +85 -7
  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 -8
  40. package/dist/commands/validate.js +30 -35
  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.d.ts +2 -1
  49. package/dist/containers/project/resource-collector.js +41 -33
  50. package/dist/containers/project/resource-collector.js.map +1 -1
  51. package/dist/containers/project.d.ts +18 -6
  52. package/dist/containers/project.js +37 -72
  53. package/dist/containers/project.js.map +1 -1
  54. package/dist/containers/template.d.ts +5 -0
  55. package/dist/containers/template.js +9 -0
  56. package/dist/containers/template.js.map +1 -1
  57. package/dist/exceptions/index.d.ts +20 -0
  58. package/dist/exceptions/index.js +16 -0
  59. package/dist/exceptions/index.js.map +1 -1
  60. package/dist/index.d.ts +5 -2
  61. package/dist/index.js +5 -1
  62. package/dist/index.js.map +1 -1
  63. package/dist/interfaces/macros.d.ts +7 -2
  64. package/dist/interfaces/project-interfaces.d.ts +21 -0
  65. package/dist/interfaces/project-interfaces.js +4 -0
  66. package/dist/interfaces/project-interfaces.js.map +1 -1
  67. package/dist/interfaces/resource-interfaces.d.ts +1 -1
  68. package/dist/interfaces/resource-interfaces.js.map +1 -1
  69. package/dist/macros/base-macro.d.ts +2 -0
  70. package/dist/macros/base-macro.js +66 -19
  71. package/dist/macros/base-macro.js.map +1 -1
  72. package/dist/macros/common.d.ts +16 -9
  73. package/dist/macros/common.js +22 -9
  74. package/dist/macros/common.js.map +1 -1
  75. package/dist/macros/createCards/index.d.ts +1 -2
  76. package/dist/macros/createCards/index.js.map +1 -1
  77. package/dist/macros/graph/index.d.ts +1 -3
  78. package/dist/macros/graph/index.js +11 -9
  79. package/dist/macros/graph/index.js.map +1 -1
  80. package/dist/macros/image/index.d.ts +39 -0
  81. package/dist/macros/image/index.js +78 -0
  82. package/dist/macros/image/index.js.map +1 -0
  83. package/dist/macros/image/metadata.d.ts +18 -0
  84. package/dist/macros/image/metadata.js +22 -0
  85. package/dist/macros/image/metadata.js.map +1 -0
  86. package/dist/macros/include/index.d.ts +32 -0
  87. package/dist/macros/include/index.js +97 -0
  88. package/dist/macros/include/index.js.map +1 -0
  89. package/dist/macros/include/metadata.d.ts +15 -0
  90. package/dist/macros/include/metadata.js +19 -0
  91. package/dist/macros/include/metadata.js.map +1 -0
  92. package/dist/macros/index.d.ts +39 -31
  93. package/dist/macros/index.js +167 -73
  94. package/dist/macros/index.js.map +1 -1
  95. package/dist/macros/percentage/index.d.ts +29 -0
  96. package/dist/macros/percentage/index.js +36 -0
  97. package/dist/macros/percentage/index.js.map +1 -0
  98. package/dist/macros/percentage/metadata.d.ts +15 -0
  99. package/dist/macros/percentage/metadata.js +19 -0
  100. package/dist/macros/percentage/metadata.js.map +1 -0
  101. package/dist/macros/report/index.d.ts +2 -5
  102. package/dist/macros/report/index.js +20 -12
  103. package/dist/macros/report/index.js.map +1 -1
  104. package/dist/macros/scoreCard/index.d.ts +15 -15
  105. package/dist/macros/scoreCard/index.js +16 -17
  106. package/dist/macros/scoreCard/index.js.map +1 -1
  107. package/dist/macros/vega/index.d.ts +28 -0
  108. package/dist/macros/vega/index.js +27 -0
  109. package/dist/macros/vega/index.js.map +1 -0
  110. package/dist/macros/vega/metadata.d.ts +15 -0
  111. package/dist/macros/vega/metadata.js +7 -0
  112. package/dist/macros/vega/metadata.js.map +1 -0
  113. package/dist/macros/vegalite/index.d.ts +27 -0
  114. package/dist/macros/vegalite/index.js +27 -0
  115. package/dist/macros/vegalite/index.js.map +1 -0
  116. package/dist/macros/vegalite/metadata.d.ts +15 -0
  117. package/dist/macros/vegalite/metadata.js +7 -0
  118. package/dist/macros/vegalite/metadata.js.map +1 -0
  119. package/dist/macros/xref/index.d.ts +26 -0
  120. package/dist/macros/xref/index.js +53 -0
  121. package/dist/macros/xref/index.js.map +1 -0
  122. package/dist/macros/xref/metadata.d.ts +15 -0
  123. package/dist/macros/xref/metadata.js +19 -0
  124. package/dist/macros/xref/metadata.js.map +1 -0
  125. package/dist/module-manager.d.ts +17 -4
  126. package/dist/module-manager.js +192 -58
  127. package/dist/module-manager.js.map +1 -1
  128. package/dist/permissions/action-guard.d.ts +2 -2
  129. package/dist/permissions/action-guard.js +1 -1
  130. package/dist/permissions/action-guard.js.map +1 -1
  131. package/dist/project-settings.js +2 -8
  132. package/dist/project-settings.js.map +1 -1
  133. package/dist/resources/card-type-resource.d.ts +2 -0
  134. package/dist/resources/card-type-resource.js +63 -0
  135. package/dist/resources/card-type-resource.js.map +1 -1
  136. package/dist/resources/file-resource.d.ts +2 -0
  137. package/dist/resources/file-resource.js +12 -4
  138. package/dist/resources/file-resource.js.map +1 -1
  139. package/dist/resources/folder-resource.d.ts +19 -0
  140. package/dist/resources/folder-resource.js +51 -2
  141. package/dist/resources/folder-resource.js.map +1 -1
  142. package/dist/resources/graph-model-resource.js +1 -1
  143. package/dist/resources/graph-model-resource.js.map +1 -1
  144. package/dist/resources/graph-view-resource.js +1 -1
  145. package/dist/resources/graph-view-resource.js.map +1 -1
  146. package/dist/resources/report-resource.js +1 -1
  147. package/dist/resources/report-resource.js.map +1 -1
  148. package/dist/resources/resource-object.d.ts +8 -0
  149. package/dist/resources/resource-object.js +9 -0
  150. package/dist/resources/resource-object.js.map +1 -1
  151. package/dist/resources/template-resource.js +1 -1
  152. package/dist/resources/template-resource.js.map +1 -1
  153. package/dist/resources/workflow-resource.d.ts +2 -0
  154. package/dist/resources/workflow-resource.js +53 -9
  155. package/dist/resources/workflow-resource.js.map +1 -1
  156. package/dist/svg/index.d.ts +15 -0
  157. package/dist/svg/index.js +16 -0
  158. package/dist/svg/index.js.map +1 -0
  159. package/dist/svg/lib.d.ts +9 -0
  160. package/dist/svg/lib.js +26 -0
  161. package/dist/svg/lib.js.map +1 -0
  162. package/dist/svg/percentage.d.ts +25 -0
  163. package/dist/svg/percentage.js +90 -0
  164. package/dist/svg/percentage.js.map +1 -0
  165. package/dist/svg/scoreCard.d.ts +19 -0
  166. package/dist/svg/scoreCard.js +55 -0
  167. package/dist/svg/scoreCard.js.map +1 -0
  168. package/dist/types/queries.d.ts +8 -7
  169. package/dist/types/queries.js.map +1 -1
  170. package/dist/utils/card-utils.d.ts +6 -0
  171. package/dist/utils/card-utils.js +12 -0
  172. package/dist/utils/card-utils.js.map +1 -1
  173. package/dist/utils/clingo-facts.d.ts +2 -1
  174. package/dist/utils/clingo-facts.js +19 -2
  175. package/dist/utils/clingo-facts.js.map +1 -1
  176. package/dist/utils/clingo-parser.d.ts +1 -0
  177. package/dist/utils/clingo-parser.js +22 -100
  178. package/dist/utils/clingo-parser.js.map +1 -1
  179. package/dist/utils/constants.d.ts +7 -0
  180. package/dist/utils/constants.js +14 -0
  181. package/dist/utils/constants.js.map +1 -1
  182. package/dist/utils/csv.js.map +1 -1
  183. package/dist/utils/report.d.ts +17 -3
  184. package/dist/utils/report.js +38 -2
  185. package/dist/utils/report.js.map +1 -1
  186. package/dist/utils/resource-utils.d.ts +1 -0
  187. package/dist/utils/resource-utils.js +9 -0
  188. package/dist/utils/resource-utils.js.map +1 -1
  189. package/dist/utils/user-preferences.d.ts +1 -11
  190. package/dist/utils/user-preferences.js +30 -13
  191. package/dist/utils/user-preferences.js.map +1 -1
  192. package/dist/utils/validate.d.ts +2 -3
  193. package/dist/utils/validate.js +2 -2
  194. package/dist/utils/validate.js.map +1 -1
  195. package/package.json +8 -6
  196. package/src/command-handler.ts +96 -17
  197. package/src/command-manager.ts +8 -8
  198. package/src/commands/calculate.ts +11 -525
  199. package/src/commands/create.ts +53 -16
  200. package/src/commands/edit.ts +53 -11
  201. package/src/commands/export.ts +108 -34
  202. package/src/commands/import.ts +31 -2
  203. package/src/commands/move.ts +12 -15
  204. package/src/commands/remove.ts +10 -19
  205. package/src/commands/rename.ts +3 -12
  206. package/src/commands/show.ts +121 -8
  207. package/src/commands/transition.ts +3 -7
  208. package/src/commands/update.ts +6 -0
  209. package/src/commands/validate.ts +39 -47
  210. package/src/containers/card-container.ts +74 -0
  211. package/src/containers/project/calculation-engine.ts +535 -0
  212. package/src/containers/project/resource-collector.ts +45 -26
  213. package/src/containers/project.ts +66 -84
  214. package/src/containers/template.ts +16 -0
  215. package/src/exceptions/index.ts +36 -0
  216. package/src/index.ts +13 -2
  217. package/src/interfaces/macros.ts +7 -1
  218. package/src/interfaces/project-interfaces.ts +27 -0
  219. package/src/interfaces/resource-interfaces.ts +1 -0
  220. package/src/macros/base-macro.ts +89 -25
  221. package/src/macros/common.ts +22 -9
  222. package/src/macros/createCards/index.ts +1 -2
  223. package/src/macros/graph/index.ts +17 -12
  224. package/src/macros/image/index.ts +121 -0
  225. package/src/macros/image/metadata.ts +25 -0
  226. package/src/macros/include/index.ts +147 -0
  227. package/src/macros/include/metadata.ts +22 -0
  228. package/src/macros/index.ts +179 -100
  229. package/src/macros/percentage/index.ts +54 -0
  230. package/src/macros/percentage/metadata.ts +22 -0
  231. package/src/macros/report/index.ts +22 -17
  232. package/src/macros/scoreCard/index.ts +23 -23
  233. package/src/macros/vega/index.ts +55 -0
  234. package/src/macros/vega/metadata.ts +21 -0
  235. package/src/macros/vegalite/index.ts +50 -0
  236. package/src/macros/vegalite/metadata.ts +21 -0
  237. package/src/macros/xref/index.ts +73 -0
  238. package/src/macros/xref/metadata.ts +22 -0
  239. package/src/module-manager.ts +241 -69
  240. package/src/permissions/action-guard.ts +3 -3
  241. package/src/project-settings.ts +2 -11
  242. package/src/resources/card-type-resource.ts +100 -0
  243. package/src/resources/file-resource.ts +16 -4
  244. package/src/resources/folder-resource.ts +59 -2
  245. package/src/resources/graph-model-resource.ts +1 -1
  246. package/src/resources/graph-view-resource.ts +1 -1
  247. package/src/resources/report-resource.ts +1 -1
  248. package/src/resources/resource-object.ts +14 -0
  249. package/src/resources/template-resource.ts +1 -1
  250. package/src/resources/workflow-resource.ts +68 -13
  251. package/src/svg/index.ts +15 -0
  252. package/src/svg/lib.ts +31 -0
  253. package/src/svg/percentage.ts +97 -0
  254. package/src/svg/scoreCard.ts +88 -0
  255. package/src/types/queries.ts +8 -7
  256. package/src/types/string-pixel-width.d.ts +23 -0
  257. package/src/utils/card-utils.ts +13 -0
  258. package/src/utils/clingo-facts.ts +65 -3
  259. package/src/utils/clingo-parser.ts +31 -144
  260. package/src/utils/constants.ts +16 -0
  261. package/src/utils/csv.ts +1 -1
  262. package/src/utils/report.ts +45 -4
  263. package/src/utils/resource-utils.ts +9 -0
  264. package/src/utils/user-preferences.ts +32 -14
  265. package/src/utils/validate.ts +3 -3
@@ -11,8 +11,10 @@
11
11
  License along with this program. If not, see <https://www.gnu.org/licenses/>.
12
12
  */
13
13
 
14
- import { basename, join } from 'node:path';
15
- import { mkdir, rename, rm } from 'node:fs/promises';
14
+ import { basename, dirname, join, normalize } from 'node:path';
15
+ import { mkdir, readdir, readFile, rename, rm } from 'node:fs/promises';
16
+
17
+ import { writeFileSafe } from '../utils/file-utils.js';
16
18
 
17
19
  import type { ResourceFolderType } from '../interfaces/project-interfaces.js';
18
20
  import {
@@ -27,6 +29,7 @@ import {
27
29
  sortCards,
28
30
  } from './file-resource.js';
29
31
  import type { ResourceContent } from '../interfaces/resource-interfaces.js';
32
+ import { VALID_FOLDER_RESOURCE_FILES } from '../utils/constants.js';
30
33
 
31
34
  export {
32
35
  type Card,
@@ -73,6 +76,15 @@ export class FolderResource extends FileResource {
73
76
  await rm(this.internalFolder, { recursive: true, force: true });
74
77
  }
75
78
 
79
+ // Get (resource folder) type name
80
+ protected get getType() {
81
+ return super.getType;
82
+ }
83
+
84
+ protected get logger() {
85
+ return super.getLogger(this.getType);
86
+ }
87
+
76
88
  protected initialize(): void {
77
89
  super.initialize();
78
90
 
@@ -98,6 +110,51 @@ export class FolderResource extends FileResource {
98
110
  return super.show();
99
111
  }
100
112
 
113
+ /**
114
+ * Shows the content of a file in the resource.
115
+ * @param fileName Name of the file to show.
116
+ * @returns the content of the file.
117
+ */
118
+ public async showFile(fileName: string): Promise<string> {
119
+ const filePath = join(this.internalFolder, fileName);
120
+ return readFile(filePath, 'utf8');
121
+ }
122
+
123
+ /**
124
+ * Shows all file names in the resource.
125
+ * @returns all file names in the resource.
126
+ */
127
+ public async showFileNames(): Promise<string[]> {
128
+ const files = await readdir(this.internalFolder);
129
+ return files.filter((file) => VALID_FOLDER_RESOURCE_FILES.includes(file));
130
+ }
131
+
132
+ /**
133
+ * Updates a file in the resource.
134
+ * @param fileName The name of the file to update.
135
+ * @param changedContent The new content for the file.
136
+ */
137
+ public async updateFile(fileName: string, changedContent: string) {
138
+ const filePath = join(this.internalFolder, fileName);
139
+
140
+ // Do not allow updating file in other directories
141
+ const normalizedFilePath = normalize(filePath);
142
+ const normalizedInternalFilePath = normalize(this.internalFolder);
143
+ if (dirname(normalizedFilePath) !== normalizedInternalFilePath) {
144
+ throw new Error(`File '${fileName}' is not in the resource`);
145
+ }
146
+
147
+ // This makes sure that the file is in the resource folder.
148
+ if (basename(normalizedFilePath) !== fileName) {
149
+ throw new Error(`File '${fileName}' is not in the resource`);
150
+ }
151
+ // check if the file is whitelisted
152
+ if (!VALID_FOLDER_RESOURCE_FILES.includes(fileName)) {
153
+ throw new Error(`File '${fileName}' is not whitelisted`);
154
+ }
155
+
156
+ await writeFileSafe(filePath, changedContent, { flag: 'w' });
157
+ }
101
158
  /**
102
159
  * Updates resource.
103
160
  * @param key Key to modify
@@ -150,7 +150,7 @@ export class GraphModelResource extends FolderResource {
150
150
 
151
151
  await super.update(key, op);
152
152
 
153
- const content = { ...(this.content as GraphModel) };
153
+ const content = structuredClone(this.content) as GraphModel;
154
154
 
155
155
  if (key === 'name') {
156
156
  content.name = super.handleScalar(op) as string;
@@ -144,7 +144,7 @@ export class GraphViewResource extends FolderResource {
144
144
 
145
145
  await super.update(key, op);
146
146
 
147
- const content = { ...(this.content as GraphView) };
147
+ const content = structuredClone(this.content) as GraphView;
148
148
 
149
149
  if (key === 'name') {
150
150
  content.name = super.handleScalar(op) as string;
@@ -179,7 +179,7 @@ export class ReportResource extends FolderResource {
179
179
 
180
180
  await super.update(key, op);
181
181
 
182
- const content = { ...(this.content as ReportMetadata) };
182
+ const content = structuredClone(this.content) as ReportMetadata;
183
183
 
184
184
  if (key === 'name') {
185
185
  content.name = super.handleScalar(op) as string;
@@ -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 [];
@@ -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
+ };