@cyberismo/data-handler 0.0.2

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 (306) hide show
  1. package/LICENSE +702 -0
  2. package/dist/card-metadata-updater.d.ts +33 -0
  3. package/dist/card-metadata-updater.js +121 -0
  4. package/dist/card-metadata-updater.js.map +1 -0
  5. package/dist/command-handler.d.ts +96 -0
  6. package/dist/command-handler.js +557 -0
  7. package/dist/command-handler.js.map +1 -0
  8. package/dist/command-manager.d.ts +43 -0
  9. package/dist/command-manager.js +73 -0
  10. package/dist/command-manager.js.map +1 -0
  11. package/dist/commands/calculate.d.ts +86 -0
  12. package/dist/commands/calculate.js +444 -0
  13. package/dist/commands/calculate.js.map +1 -0
  14. package/dist/commands/create.d.ts +114 -0
  15. package/dist/commands/create.js +389 -0
  16. package/dist/commands/create.js.map +1 -0
  17. package/dist/commands/edit.d.ts +37 -0
  18. package/dist/commands/edit.js +99 -0
  19. package/dist/commands/edit.js.map +1 -0
  20. package/dist/commands/export-site.d.ts +45 -0
  21. package/dist/commands/export-site.js +301 -0
  22. package/dist/commands/export-site.js.map +1 -0
  23. package/dist/commands/export.d.ts +53 -0
  24. package/dist/commands/export.js +251 -0
  25. package/dist/commands/export.js.map +1 -0
  26. package/dist/commands/import.d.ts +53 -0
  27. package/dist/commands/import.js +133 -0
  28. package/dist/commands/import.js.map +1 -0
  29. package/dist/commands/index.d.ts +26 -0
  30. package/dist/commands/index.js +27 -0
  31. package/dist/commands/index.js.map +1 -0
  32. package/dist/commands/move.d.ts +55 -0
  33. package/dist/commands/move.js +341 -0
  34. package/dist/commands/move.js.map +1 -0
  35. package/dist/commands/remove.d.ts +38 -0
  36. package/dist/commands/remove.js +192 -0
  37. package/dist/commands/remove.js.map +1 -0
  38. package/dist/commands/rename.d.ts +46 -0
  39. package/dist/commands/rename.js +289 -0
  40. package/dist/commands/rename.js.map +1 -0
  41. package/dist/commands/show.d.ts +124 -0
  42. package/dist/commands/show.js +345 -0
  43. package/dist/commands/show.js.map +1 -0
  44. package/dist/commands/transition.d.ts +27 -0
  45. package/dist/commands/transition.js +92 -0
  46. package/dist/commands/transition.js.map +1 -0
  47. package/dist/commands/update.d.ts +29 -0
  48. package/dist/commands/update.js +64 -0
  49. package/dist/commands/update.js.map +1 -0
  50. package/dist/commands/validate.d.ts +143 -0
  51. package/dist/commands/validate.js +689 -0
  52. package/dist/commands/validate.js.map +1 -0
  53. package/dist/containers/card-container.d.ts +44 -0
  54. package/dist/containers/card-container.js +282 -0
  55. package/dist/containers/card-container.js.map +1 -0
  56. package/dist/containers/project/project-paths.d.ts +46 -0
  57. package/dist/containers/project/project-paths.js +105 -0
  58. package/dist/containers/project/project-paths.js.map +1 -0
  59. package/dist/containers/project/resource-collector.d.ts +86 -0
  60. package/dist/containers/project/resource-collector.js +331 -0
  61. package/dist/containers/project/resource-collector.js.map +1 -0
  62. package/dist/containers/project.d.ts +351 -0
  63. package/dist/containers/project.js +896 -0
  64. package/dist/containers/project.js.map +1 -0
  65. package/dist/containers/template.d.ts +108 -0
  66. package/dist/containers/template.js +433 -0
  67. package/dist/containers/template.js.map +1 -0
  68. package/dist/exceptions/index.d.ts +19 -0
  69. package/dist/exceptions/index.js +26 -0
  70. package/dist/exceptions/index.js.map +1 -0
  71. package/dist/index.d.ts +16 -0
  72. package/dist/index.js +15 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/interfaces/adoc.d.ts +12 -0
  75. package/dist/interfaces/adoc.js +13 -0
  76. package/dist/interfaces/adoc.js.map +1 -0
  77. package/dist/interfaces/macros.d.ts +45 -0
  78. package/dist/interfaces/macros.js +13 -0
  79. package/dist/interfaces/macros.js.map +1 -0
  80. package/dist/interfaces/project-interfaces.d.ts +121 -0
  81. package/dist/interfaces/project-interfaces.js +21 -0
  82. package/dist/interfaces/project-interfaces.js.map +1 -0
  83. package/dist/interfaces/request-status-interfaces.d.ts +28 -0
  84. package/dist/interfaces/request-status-interfaces.js +20 -0
  85. package/dist/interfaces/request-status-interfaces.js.map +1 -0
  86. package/dist/interfaces/resource-interfaces.d.ts +117 -0
  87. package/dist/interfaces/resource-interfaces.js +20 -0
  88. package/dist/interfaces/resource-interfaces.js.map +1 -0
  89. package/dist/macros/base-macro.d.ts +31 -0
  90. package/dist/macros/base-macro.js +126 -0
  91. package/dist/macros/base-macro.js.map +1 -0
  92. package/dist/macros/common.d.ts +17 -0
  93. package/dist/macros/common.js +23 -0
  94. package/dist/macros/common.js.map +1 -0
  95. package/dist/macros/createCards/index.d.ts +36 -0
  96. package/dist/macros/createCards/index.js +35 -0
  97. package/dist/macros/createCards/index.js.map +1 -0
  98. package/dist/macros/createCards/metadata.d.ts +14 -0
  99. package/dist/macros/createCards/metadata.js +18 -0
  100. package/dist/macros/createCards/metadata.js.map +1 -0
  101. package/dist/macros/graph/index.d.ts +29 -0
  102. package/dist/macros/graph/index.js +91 -0
  103. package/dist/macros/graph/index.js.map +1 -0
  104. package/dist/macros/graph/metadata.d.ts +14 -0
  105. package/dist/macros/graph/metadata.js +18 -0
  106. package/dist/macros/graph/metadata.js.map +1 -0
  107. package/dist/macros/index.d.ts +93 -0
  108. package/dist/macros/index.js +237 -0
  109. package/dist/macros/index.js.map +1 -0
  110. package/dist/macros/report/index.d.ts +26 -0
  111. package/dist/macros/report/index.js +70 -0
  112. package/dist/macros/report/index.js.map +1 -0
  113. package/dist/macros/report/metadata.d.ts +14 -0
  114. package/dist/macros/report/metadata.js +18 -0
  115. package/dist/macros/report/metadata.js.map +1 -0
  116. package/dist/macros/scoreCard/index.d.ts +30 -0
  117. package/dist/macros/scoreCard/index.js +38 -0
  118. package/dist/macros/scoreCard/index.js.map +1 -0
  119. package/dist/macros/scoreCard/metadata.d.ts +14 -0
  120. package/dist/macros/scoreCard/metadata.js +18 -0
  121. package/dist/macros/scoreCard/metadata.js.map +1 -0
  122. package/dist/macros/task-queue.d.ts +46 -0
  123. package/dist/macros/task-queue.js +69 -0
  124. package/dist/macros/task-queue.js.map +1 -0
  125. package/dist/module-manager.d.ts +62 -0
  126. package/dist/module-manager.js +350 -0
  127. package/dist/module-manager.js.map +1 -0
  128. package/dist/permissions/action-guard.d.ts +28 -0
  129. package/dist/permissions/action-guard.js +61 -0
  130. package/dist/permissions/action-guard.js.map +1 -0
  131. package/dist/project-settings.d.ts +42 -0
  132. package/dist/project-settings.js +120 -0
  133. package/dist/project-settings.js.map +1 -0
  134. package/dist/resources/array-handler.d.ts +28 -0
  135. package/dist/resources/array-handler.js +116 -0
  136. package/dist/resources/array-handler.js.map +1 -0
  137. package/dist/resources/card-type-resource.d.ts +72 -0
  138. package/dist/resources/card-type-resource.js +334 -0
  139. package/dist/resources/card-type-resource.js.map +1 -0
  140. package/dist/resources/create-defaults.d.ts +81 -0
  141. package/dist/resources/create-defaults.js +184 -0
  142. package/dist/resources/create-defaults.js.map +1 -0
  143. package/dist/resources/field-type-resource.d.ts +88 -0
  144. package/dist/resources/field-type-resource.js +411 -0
  145. package/dist/resources/field-type-resource.js.map +1 -0
  146. package/dist/resources/file-resource.d.ts +50 -0
  147. package/dist/resources/file-resource.js +301 -0
  148. package/dist/resources/file-resource.js.map +1 -0
  149. package/dist/resources/folder-resource.d.ts +66 -0
  150. package/dist/resources/folder-resource.js +100 -0
  151. package/dist/resources/folder-resource.js.map +1 -0
  152. package/dist/resources/graph-model-resource.d.ts +78 -0
  153. package/dist/resources/graph-model-resource.js +164 -0
  154. package/dist/resources/graph-model-resource.js.map +1 -0
  155. package/dist/resources/graph-view-resource.d.ts +78 -0
  156. package/dist/resources/graph-view-resource.js +163 -0
  157. package/dist/resources/graph-view-resource.js.map +1 -0
  158. package/dist/resources/link-type-resource.d.ts +62 -0
  159. package/dist/resources/link-type-resource.js +150 -0
  160. package/dist/resources/link-type-resource.js.map +1 -0
  161. package/dist/resources/report-resource.d.ts +77 -0
  162. package/dist/resources/report-resource.js +171 -0
  163. package/dist/resources/report-resource.js.map +1 -0
  164. package/dist/resources/resource-object.d.ts +108 -0
  165. package/dist/resources/resource-object.js +147 -0
  166. package/dist/resources/resource-object.js.map +1 -0
  167. package/dist/resources/template-resource.d.ts +82 -0
  168. package/dist/resources/template-resource.js +173 -0
  169. package/dist/resources/template-resource.js.map +1 -0
  170. package/dist/resources/workflow-resource.d.ts +67 -0
  171. package/dist/resources/workflow-resource.js +156 -0
  172. package/dist/resources/workflow-resource.js.map +1 -0
  173. package/dist/types/queries.d.ts +142 -0
  174. package/dist/types/queries.js +16 -0
  175. package/dist/types/queries.js.map +1 -0
  176. package/dist/utils/card-utils.d.ts +34 -0
  177. package/dist/utils/card-utils.js +78 -0
  178. package/dist/utils/card-utils.js.map +1 -0
  179. package/dist/utils/clingo-fact-builder.d.ts +58 -0
  180. package/dist/utils/clingo-fact-builder.js +126 -0
  181. package/dist/utils/clingo-fact-builder.js.map +1 -0
  182. package/dist/utils/clingo-facts.d.ts +97 -0
  183. package/dist/utils/clingo-facts.js +352 -0
  184. package/dist/utils/clingo-facts.js.map +1 -0
  185. package/dist/utils/clingo-parser.d.ts +59 -0
  186. package/dist/utils/clingo-parser.js +403 -0
  187. package/dist/utils/clingo-parser.js.map +1 -0
  188. package/dist/utils/clingo-program-builder.d.ts +39 -0
  189. package/dist/utils/clingo-program-builder.js +57 -0
  190. package/dist/utils/clingo-program-builder.js.map +1 -0
  191. package/dist/utils/common-utils.d.ts +24 -0
  192. package/dist/utils/common-utils.js +47 -0
  193. package/dist/utils/common-utils.js.map +1 -0
  194. package/dist/utils/constants.d.ts +18 -0
  195. package/dist/utils/constants.js +27 -0
  196. package/dist/utils/constants.js.map +1 -0
  197. package/dist/utils/csv.d.ts +18 -0
  198. package/dist/utils/csv.js +45 -0
  199. package/dist/utils/csv.js.map +1 -0
  200. package/dist/utils/file-utils.d.ts +69 -0
  201. package/dist/utils/file-utils.js +158 -0
  202. package/dist/utils/file-utils.js.map +1 -0
  203. package/dist/utils/json.d.ts +61 -0
  204. package/dist/utils/json.js +108 -0
  205. package/dist/utils/json.js.map +1 -0
  206. package/dist/utils/lexorank.d.ts +59 -0
  207. package/dist/utils/lexorank.js +159 -0
  208. package/dist/utils/lexorank.js.map +1 -0
  209. package/dist/utils/log-utils.d.ts +40 -0
  210. package/dist/utils/log-utils.js +109 -0
  211. package/dist/utils/log-utils.js.map +1 -0
  212. package/dist/utils/random.d.ts +19 -0
  213. package/dist/utils/random.js +34 -0
  214. package/dist/utils/random.js.map +1 -0
  215. package/dist/utils/resource-utils.d.ts +45 -0
  216. package/dist/utils/resource-utils.js +137 -0
  217. package/dist/utils/resource-utils.js.map +1 -0
  218. package/dist/utils/sanitize-svg.d.ts +18 -0
  219. package/dist/utils/sanitize-svg.js +38 -0
  220. package/dist/utils/sanitize-svg.js.map +1 -0
  221. package/dist/utils/user-preferences.d.ts +64 -0
  222. package/dist/utils/user-preferences.js +106 -0
  223. package/dist/utils/user-preferences.js.map +1 -0
  224. package/dist/utils/validate.d.ts +26 -0
  225. package/dist/utils/validate.js +53 -0
  226. package/dist/utils/validate.js.map +1 -0
  227. package/dist/utils/value-utils.d.ts +58 -0
  228. package/dist/utils/value-utils.js +181 -0
  229. package/dist/utils/value-utils.js.map +1 -0
  230. package/package.json +67 -0
  231. package/src/card-metadata-updater.ts +182 -0
  232. package/src/command-handler.ts +686 -0
  233. package/src/command-manager.ts +99 -0
  234. package/src/commands/calculate.ts +591 -0
  235. package/src/commands/create.ts +559 -0
  236. package/src/commands/edit.ts +123 -0
  237. package/src/commands/export-site.ts +356 -0
  238. package/src/commands/export.ts +315 -0
  239. package/src/commands/import.ts +169 -0
  240. package/src/commands/index.ts +42 -0
  241. package/src/commands/move.ts +451 -0
  242. package/src/commands/remove.ts +244 -0
  243. package/src/commands/rename.ts +378 -0
  244. package/src/commands/show.ts +442 -0
  245. package/src/commands/transition.ts +127 -0
  246. package/src/commands/update.ts +76 -0
  247. package/src/commands/validate.ts +962 -0
  248. package/src/containers/card-container.ts +378 -0
  249. package/src/containers/project/project-paths.ts +127 -0
  250. package/src/containers/project/resource-collector.ts +379 -0
  251. package/src/containers/project.ts +1135 -0
  252. package/src/containers/template.ts +573 -0
  253. package/src/exceptions/index.ts +29 -0
  254. package/src/index.ts +33 -0
  255. package/src/interfaces/adoc.ts +18 -0
  256. package/src/interfaces/macros.ts +54 -0
  257. package/src/interfaces/project-interfaces.ts +208 -0
  258. package/src/interfaces/request-status-interfaces.ts +30 -0
  259. package/src/interfaces/resource-interfaces.ts +179 -0
  260. package/src/macros/base-macro.ts +176 -0
  261. package/src/macros/common.ts +24 -0
  262. package/src/macros/createCards/index.ts +57 -0
  263. package/src/macros/createCards/metadata.ts +21 -0
  264. package/src/macros/graph/index.ts +130 -0
  265. package/src/macros/graph/metadata.ts +21 -0
  266. package/src/macros/index.ts +321 -0
  267. package/src/macros/report/index.ts +88 -0
  268. package/src/macros/report/metadata.ts +21 -0
  269. package/src/macros/scoreCard/index.ts +55 -0
  270. package/src/macros/scoreCard/metadata.ts +21 -0
  271. package/src/macros/task-queue.ts +79 -0
  272. package/src/module-manager.ts +443 -0
  273. package/src/permissions/action-guard.ts +77 -0
  274. package/src/project-settings.ts +140 -0
  275. package/src/resources/array-handler.ts +141 -0
  276. package/src/resources/card-type-resource.ts +455 -0
  277. package/src/resources/create-defaults.ts +216 -0
  278. package/src/resources/field-type-resource.ts +533 -0
  279. package/src/resources/file-resource.ts +433 -0
  280. package/src/resources/folder-resource.ts +140 -0
  281. package/src/resources/graph-model-resource.ts +205 -0
  282. package/src/resources/graph-view-resource.ts +199 -0
  283. package/src/resources/link-type-resource.ts +191 -0
  284. package/src/resources/report-resource.ts +224 -0
  285. package/src/resources/resource-object.ts +246 -0
  286. package/src/resources/template-resource.ts +210 -0
  287. package/src/resources/workflow-resource.ts +205 -0
  288. package/src/types/queries.ts +149 -0
  289. package/src/utils/card-utils.ts +83 -0
  290. package/src/utils/clingo-fact-builder.ts +167 -0
  291. package/src/utils/clingo-facts.ts +550 -0
  292. package/src/utils/clingo-parser.ts +519 -0
  293. package/src/utils/clingo-program-builder.ts +71 -0
  294. package/src/utils/common-utils.ts +54 -0
  295. package/src/utils/constants.ts +32 -0
  296. package/src/utils/csv.ts +53 -0
  297. package/src/utils/file-utils.ts +182 -0
  298. package/src/utils/json.ts +118 -0
  299. package/src/utils/lexorank.ts +180 -0
  300. package/src/utils/log-utils.ts +127 -0
  301. package/src/utils/random.ts +37 -0
  302. package/src/utils/resource-utils.ts +180 -0
  303. package/src/utils/sanitize-svg.ts +46 -0
  304. package/src/utils/user-preferences.ts +126 -0
  305. package/src/utils/validate.ts +66 -0
  306. package/src/utils/value-utils.ts +189 -0
@@ -0,0 +1,455 @@
1
+ /**
2
+ Cyberismo
3
+ Copyright © Cyberismo Ltd and contributors 2024
4
+
5
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation.
6
+
7
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
8
+
9
+ You should have received a copy of the GNU Affero General Public
10
+ License along with this program. If not, see <https://www.gnu.org/licenses/>.
11
+ */
12
+ import type {
13
+ CardType,
14
+ CustomField,
15
+ LinkType,
16
+ } from '../interfaces/resource-interfaces.js';
17
+ import { FieldTypeResource } from './field-type-resource.js';
18
+ import {
19
+ type AddOperation,
20
+ type Card,
21
+ type ChangeOperation,
22
+ DefaultContent,
23
+ FileResource,
24
+ type Operation,
25
+ type Project,
26
+ type RemoveOperation,
27
+ ResourcesFrom,
28
+ type ResourceName,
29
+ resourceName,
30
+ resourceNameToString,
31
+ sortCards,
32
+ } from './file-resource.js';
33
+ import { LinkTypeResource } from './link-type-resource.js';
34
+ import { Template } from '../containers/template.js';
35
+ import { Validate } from '../commands/index.js';
36
+
37
+ /**
38
+ * Card type resource class.
39
+ */
40
+ export class CardTypeResource extends FileResource {
41
+ constructor(project: Project, name: ResourceName) {
42
+ super(project, name, 'cardTypes');
43
+
44
+ this.contentSchemaId = 'cardTypeSchema';
45
+ this.contentSchema = super.contentSchemaContent(this.contentSchemaId);
46
+
47
+ this.initialize();
48
+ this.setContainerValues();
49
+ }
50
+
51
+ // Returns cards that have this card type.
52
+ private async cardsWithCardType(cards: Card[]): Promise<string[]> {
53
+ const resourceName = resourceNameToString(this.resourceName);
54
+ return cards
55
+ .filter((card) => card.metadata?.cardType === resourceName)
56
+ .map((card) => card.key);
57
+ }
58
+
59
+ // Collects affected cards.
60
+ private async collectCards(cardContent: object) {
61
+ async function filteredCards(
62
+ cardSource: Promise<Card[]>,
63
+ cardTypeName: string,
64
+ ): Promise<Card[]> {
65
+ const cards = await cardSource;
66
+ return cards.filter((card) => card.metadata?.cardType === cardTypeName);
67
+ }
68
+
69
+ // Collect both project cards ...
70
+ const projectCardsPromise = filteredCards(
71
+ this.project.cards(this.project.paths.cardRootFolder, cardContent),
72
+ this.content.name,
73
+ );
74
+ // ... and cards from each template that would be affected.
75
+ const templates = await this.project.templates(ResourcesFrom.localOnly);
76
+ const templateCardsPromises = templates.map((template) => {
77
+ const templateObject = new Template(this.project, template);
78
+ return filteredCards(
79
+ templateObject.cards('', cardContent),
80
+ this.content.name,
81
+ );
82
+ });
83
+ // Return all affected cards
84
+ const cards = (
85
+ await Promise.all([projectCardsPromise, ...templateCardsPromises])
86
+ ).reduce((accumulator, value) => accumulator.concat(value), []);
87
+ return cards;
88
+ }
89
+
90
+ // If custom fields change, cards need to be updated.
91
+ // Rename change changes key names in cards.
92
+ private async handleCustomFieldsChange<Type>(op: Operation<Type>) {
93
+ const cardContent = {
94
+ metadata: true,
95
+ content: true,
96
+ };
97
+
98
+ if (op && op.name === 'rank') return;
99
+
100
+ // Collect both project cards and template cards.
101
+ const cards = await this.collectCards(cardContent);
102
+
103
+ if (op && op.name === 'change') {
104
+ const from = (op as ChangeOperation<string>).target;
105
+ const to = (op as ChangeOperation<string>).to;
106
+
107
+ // Then update them all parallel.
108
+ const promises: Promise<void>[] = [];
109
+ for (const card of cards) {
110
+ promises.push(this.updateCardMetadata(card, from, to));
111
+ }
112
+ await Promise.all(promises);
113
+ } else if (op && op.name === 'add') {
114
+ // todo: target can be string here as well? Fix at some point
115
+ const item = (op as AddOperation<Type>).target as CustomField;
116
+ await this.handleAddNewField(cards, item);
117
+ } else if (op && op.name === 'remove') {
118
+ // todo: target can be string here as well? Fix at some point
119
+ const item = (op as RemoveOperation<Type>).target as CustomField;
120
+ await this.handleRemoveField(cards, item);
121
+ }
122
+ }
123
+
124
+ // When new field is added, add it all affected cards with 'null' value.
125
+ private async handleAddNewField(cards: Card[], item: CustomField) {
126
+ for (const card of cards) {
127
+ if (card.metadata) {
128
+ card.metadata[item.name] = null;
129
+ await this.project.updateCardMetadata(card, card.metadata);
130
+ }
131
+ }
132
+ }
133
+
134
+ // When resource name changes.
135
+ private async handleNameChange(existingName: string) {
136
+ const current = this.content as CardType;
137
+ const prefixes = await this.project.projectPrefixes();
138
+ if (current.customFields) {
139
+ current.customFields.map(
140
+ (field) =>
141
+ (field.name = this.updatePrefixInResourceName(field.name, prefixes)),
142
+ );
143
+ }
144
+ if (current.alwaysVisibleFields) {
145
+ current.alwaysVisibleFields = current.alwaysVisibleFields.map((item) =>
146
+ this.updatePrefixInResourceName(item, prefixes),
147
+ );
148
+ }
149
+ if (current.optionallyVisibleFields) {
150
+ current.optionallyVisibleFields = current.optionallyVisibleFields.map(
151
+ (item) => this.updatePrefixInResourceName(item, prefixes),
152
+ );
153
+ }
154
+ current.workflow = this.updatePrefixInResourceName(
155
+ current.workflow,
156
+ prefixes,
157
+ );
158
+ await Promise.all([
159
+ super.updateHandleBars(existingName, this.content.name),
160
+ super.updateCalculations(existingName, this.content.name),
161
+ ]);
162
+ // Finally, write updated content.
163
+ await this.write();
164
+ }
165
+
166
+ // When a field is removed, remove it from all affected cards.
167
+ private async handleRemoveField(cards: Card[], item: CustomField) {
168
+ for (const card of cards) {
169
+ if (card.metadata) {
170
+ delete card.metadata[item.name];
171
+ await this.project.updateCardMetadata(card, card.metadata);
172
+ }
173
+ }
174
+ }
175
+
176
+ // Remove value from array.
177
+ // todo: make it as generic and move to utils
178
+ private removeValue(array: string[], value: string) {
179
+ const index = array.findIndex((element) => element === value);
180
+ if (index !== -1) {
181
+ array.splice(index, 1);
182
+ }
183
+ }
184
+
185
+ // Return link types that use this card type.
186
+ private async relevantLinkTypes(): Promise<string[]> {
187
+ const resourceName = resourceNameToString(this.resourceName);
188
+ const allLinkTypes = await this.project.linkTypes(ResourcesFrom.all);
189
+
190
+ const linkTypeNames = await Promise.all(
191
+ allLinkTypes.map(async (linkType) => {
192
+ const metadata = await this.project.resource<LinkType>(linkType.name);
193
+ if (!metadata) return null;
194
+
195
+ const isRelevant =
196
+ metadata.destinationCardTypes.includes(resourceName) ||
197
+ metadata.sourceCardTypes.includes(resourceName);
198
+
199
+ return isRelevant ? linkType.name : null;
200
+ }),
201
+ );
202
+
203
+ return linkTypeNames.filter((name): name is string => name !== null);
204
+ }
205
+
206
+ // If value from 'customFields' is removed, remove it also from 'optionallyVisible' and 'alwaysVisible' arrays.
207
+ private removeValueFromOtherArrays<Type>(op: Operation<Type>) {
208
+ // Update target can be a string, or an object. Of object, fetch only 'name'
209
+ // todo: fetching 'name' or using string as name could be function in resource base class.
210
+ const target = (op as RemoveOperation<Type>).target as Type;
211
+ let field = undefined;
212
+ if (target['name' as keyof Type]) {
213
+ field = { name: target['name' as keyof Type] };
214
+ }
215
+ const fieldName = (field ? field.name : target) as string;
216
+ this.removeValue(this.data.alwaysVisibleFields, fieldName);
217
+ this.removeValue(this.data.optionallyVisibleFields, fieldName);
218
+ }
219
+
220
+ // Sets content container values to be either '[]' or with proper values.
221
+ private setContainerValues() {
222
+ const content = this.content as CardType;
223
+ if (content.customFields) {
224
+ for (const item of content.customFields) {
225
+ // Set "isCalculated" if it is missing; default = false
226
+ if (item.isCalculated === undefined) {
227
+ item.isCalculated = false;
228
+ }
229
+ // Fetch "displayName" from field type if it is missing.
230
+ if (item.name && item.displayName === undefined) {
231
+ const fieldType = new FieldTypeResource(
232
+ this.project,
233
+ resourceName(item.name),
234
+ );
235
+ const fieldTypeContent = fieldType.data;
236
+ if (fieldTypeContent) {
237
+ item.displayName = fieldTypeContent.displayName;
238
+ }
239
+ } else if (!item.name) {
240
+ console.error(
241
+ `Custom field '${item.name}' is missing mandatory 'name' in card type '${content.name}'`,
242
+ );
243
+ return undefined;
244
+ }
245
+ }
246
+ } else {
247
+ content.customFields = [];
248
+ }
249
+ if (!content.alwaysVisibleFields) {
250
+ content.alwaysVisibleFields = [];
251
+ }
252
+ if (!content.optionallyVisibleFields) {
253
+ content.optionallyVisibleFields = [];
254
+ }
255
+ this.content = content;
256
+ }
257
+
258
+ // Updates dependent link types.
259
+ private async updateLinkTypes(oldName: string): Promise<void> {
260
+ const linkTypes = await this.project.linkTypes(ResourcesFrom.localOnly);
261
+
262
+ const updatePromises = linkTypes.map(async (linkType) => {
263
+ const object = new LinkTypeResource(
264
+ this.project,
265
+ resourceName(linkType.name),
266
+ );
267
+
268
+ const data = object.data;
269
+ const updates: Promise<void>[] = [];
270
+
271
+ const cardTypeFields: Array<
272
+ keyof Pick<LinkType, 'destinationCardTypes' | 'sourceCardTypes'>
273
+ > = ['destinationCardTypes', 'sourceCardTypes'];
274
+
275
+ for (const field of cardTypeFields) {
276
+ if (data && data[field].includes(oldName)) {
277
+ const op: ChangeOperation<string> = {
278
+ name: 'change',
279
+ target: oldName,
280
+ to: this.content.name,
281
+ } as ChangeOperation<string>;
282
+ updates.push(object.update(field, op));
283
+ }
284
+ }
285
+
286
+ if (updates.length > 0) {
287
+ await Promise.all(updates);
288
+ }
289
+ });
290
+ await Promise.all(updatePromises);
291
+ }
292
+
293
+ // Update changed custom fields to cards
294
+ private async updateCardMetadata(card: Card, from: string, to: string) {
295
+ if (card.metadata?.cardType && card.metadata?.cardType.length > 0) {
296
+ if (card.metadata && Object.keys(card.metadata).includes(from)) {
297
+ delete Object.assign(card.metadata, {
298
+ [to]: card.metadata[from],
299
+ })[from];
300
+
301
+ await this.project.updateCardMetadata(card, card.metadata);
302
+ }
303
+ }
304
+ }
305
+
306
+ /**
307
+ * Creates a new card type object. Base class writes the object to disk automatically.
308
+ * @param workflowName Workflow name that this card type uses.
309
+ */
310
+ public async createCardType(workflowName: string) {
311
+ if (!workflowName) {
312
+ throw new Error(
313
+ `Cannot create cardType without providing workflow for it`,
314
+ );
315
+ }
316
+ const validWorkflowName = await Validate.getInstance().validResourceName(
317
+ 'workflows',
318
+ resourceNameToString(resourceName(workflowName)),
319
+ await this.project.projectPrefixes(),
320
+ );
321
+ const workflow = await this.project.resource(workflowName);
322
+ if (!workflow) {
323
+ throw new Error(
324
+ `Workflow '${workflowName}' does not exist in the project`,
325
+ );
326
+ }
327
+ const content = DefaultContent.cardType(
328
+ resourceNameToString(this.resourceName),
329
+ validWorkflowName,
330
+ );
331
+ return super.create(content);
332
+ }
333
+
334
+ /**
335
+ * Returns content data.
336
+ */
337
+ public get data(): CardType {
338
+ return super.data as CardType;
339
+ }
340
+
341
+ /**
342
+ * Deletes file(s) from disk and clears out the memory resident object.
343
+ */
344
+ public async delete() {
345
+ return super.delete();
346
+ }
347
+
348
+ /**
349
+ * Renames resource metadata file and renames memory resident object 'name'.
350
+ * @param newName New name for the resource.
351
+ */
352
+ public async rename(newName: ResourceName) {
353
+ const existingName = this.content.name;
354
+ await super.rename(newName);
355
+ return this.handleNameChange(existingName);
356
+ }
357
+
358
+ /**
359
+ * Shows metadata of the resource.
360
+ * @returns card type metadata.
361
+ */
362
+ public async show(): Promise<CardType> {
363
+ return super.show() as Promise<CardType>;
364
+ }
365
+
366
+ /**
367
+ * Updates card type resource.
368
+ * @param key Key to modify
369
+ * @param op Operation to perform on 'key'
370
+ */
371
+ public async update<Type>(key: string, op: Operation<Type>) {
372
+ const nameChange = key === 'name';
373
+ const customFieldsChange = key === 'customFields';
374
+ const existingName = this.content.name;
375
+ await super.update(key, op);
376
+
377
+ const content = this.content as CardType;
378
+ if (key === 'name') {
379
+ content.name = super.handleScalar(op) as string;
380
+ } else if (key === 'alwaysVisibleFields') {
381
+ content.alwaysVisibleFields = super.handleArray(
382
+ op,
383
+ key,
384
+ content.alwaysVisibleFields as Type[],
385
+ ) as string[];
386
+ } else if (key === 'optionallyVisibleFields') {
387
+ content.optionallyVisibleFields = super.handleArray(
388
+ op,
389
+ key,
390
+ content.optionallyVisibleFields as Type[],
391
+ ) as string[];
392
+ } else if (key === 'workflow') {
393
+ content.workflow = super.handleScalar(op) as string;
394
+ } else if (key === 'customFields') {
395
+ content.customFields = super.handleArray(
396
+ op,
397
+ key,
398
+ content.customFields as Type[],
399
+ ) as CustomField[];
400
+ if (op.name === 'remove') {
401
+ this.removeValueFromOtherArrays(op);
402
+ }
403
+ } else {
404
+ throw new Error(`Unknown property '${key}' for CardType`);
405
+ }
406
+
407
+ await super.postUpdate(content, key, op);
408
+
409
+ // Renaming this card type causes that references to its name must be updated.
410
+ if (nameChange) {
411
+ await this.handleNameChange(existingName);
412
+ await this.updateLinkTypes(existingName);
413
+ } else if (customFieldsChange) {
414
+ return this.handleCustomFieldsChange(op as ChangeOperation<string>);
415
+ }
416
+ }
417
+
418
+ /**
419
+ * List where card type is used. This includes card metadata, card content, calculations and link type resources.
420
+ * Always returns card key references first, then any resource references and finally calculation references.
421
+ *
422
+ * @param cards Optional. Check these cards for usage of this resource. If undefined, will check all cards.
423
+ * @returns array of card keys, resource names and calculation filenames that refer this resource.
424
+ */
425
+ public async usage(cards?: Card[]): Promise<string[]> {
426
+ const allCards = cards ?? (await super.cards());
427
+ const [
428
+ cardsWithCardType,
429
+ cardContentReferences,
430
+ relevantLinkTypes,
431
+ calculations,
432
+ ] = await Promise.all([
433
+ this.cardsWithCardType(allCards),
434
+ super.usage(allCards),
435
+ this.relevantLinkTypes(),
436
+ super.calculations(),
437
+ ]);
438
+ const cardReferences = [
439
+ ...cardsWithCardType,
440
+ ...cardContentReferences,
441
+ ].sort(sortCards);
442
+
443
+ // Using Set to avoid duplicate cards
444
+ return [
445
+ ...new Set([...cardReferences, ...relevantLinkTypes, ...calculations]),
446
+ ];
447
+ }
448
+
449
+ /**
450
+ * Validates the resource. If object is invalid, throws.
451
+ */
452
+ public async validate(content?: object) {
453
+ return super.validate(content);
454
+ }
455
+ }
@@ -0,0 +1,216 @@
1
+ /**
2
+ Cyberismo
3
+ Copyright © Cyberismo Ltd and contributors 2024
4
+
5
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation.
6
+
7
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
8
+
9
+ You should have received a copy of the GNU Affero General Public
10
+ License along with this program. If not, see <https://www.gnu.org/licenses/>.
11
+ */
12
+
13
+ import type { Card } from '../interfaces/project-interfaces.js';
14
+ import type {
15
+ CardType,
16
+ DataType,
17
+ FieldType,
18
+ GraphModelMetadata,
19
+ GraphViewMetadata,
20
+ Link,
21
+ LinkType,
22
+ ReportMetadata,
23
+ TemplateMetadata,
24
+ Workflow,
25
+ } from '../interfaces/resource-interfaces.js';
26
+ import { WorkflowCategory } from '../interfaces/resource-interfaces.js';
27
+ import { FIRST_RANK, getRankAfter, sortItems } from '../utils/lexorank.js';
28
+
29
+ // Helper function to get latest card rank from a set of cards.
30
+ function latestRank(cards: Card[]): string {
31
+ // Only use cards that have 'rank'.
32
+ const filteredCards = cards.filter(
33
+ (c) => c.metadata?.rank !== undefined || c.metadata?.rank !== '',
34
+ );
35
+
36
+ let latestRank = sortItems(filteredCards, (c) => c.metadata?.rank || '').pop()
37
+ ?.metadata?.rank;
38
+
39
+ if (!latestRank) {
40
+ latestRank = FIRST_RANK;
41
+ }
42
+
43
+ const newRank = getRankAfter(latestRank as string);
44
+ latestRank = newRank;
45
+ return latestRank;
46
+ }
47
+
48
+ /**
49
+ * Provides default values for resources and cards.
50
+ */
51
+ export abstract class DefaultContent {
52
+ /**
53
+ * Returns card with default content. Card is automatically ranked last, if siblings are provided.
54
+ * @param cardType Card type; custom values from card type are set to null.
55
+ * @param siblings Optional. If given, content will have been ranked last.
56
+ * @returns card with default content.
57
+ */
58
+ static card(cardType: CardType, siblings?: Card[]) {
59
+ return Object.assign(
60
+ {
61
+ title: 'Untitled',
62
+ cardType: cardType.name,
63
+ workflowState: '',
64
+ rank: siblings ? latestRank(siblings) : '',
65
+ },
66
+ ...cardType.customFields
67
+ .filter((field) => !field.isCalculated)
68
+ .map((field) => ({ [field.name]: null })),
69
+ );
70
+ }
71
+
72
+ /**
73
+ * Default content for card type.
74
+ * @param cardTypeName card type name
75
+ * @param workflowName workflow name
76
+ * @returns Default content for card type.
77
+ */
78
+ static cardType(cardTypeName: string, workflowName: string): CardType {
79
+ return {
80
+ name: cardTypeName,
81
+ workflow: workflowName,
82
+ customFields: [],
83
+ alwaysVisibleFields: [],
84
+ optionallyVisibleFields: [],
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Default content for field type.
90
+ * @param fieldTypeName field type name
91
+ * @param dataType data type for the field type
92
+ * @returns Default content for field type.
93
+ */
94
+ static fieldType(fieldTypeName: string, dataType: DataType): FieldType {
95
+ const value = {
96
+ name: fieldTypeName,
97
+ dataType: dataType,
98
+ } as FieldType;
99
+ if (dataType === 'enum') {
100
+ value.enumValues = [{ enumValue: 'value1' }, { enumValue: 'value2' }];
101
+ }
102
+ return value;
103
+ }
104
+
105
+ /**
106
+ * Default content for graph model.
107
+ * @param graphModelName graph model name
108
+ * @returns Default content for graph model.
109
+ */
110
+ static graphModel(graphModelName: string): GraphModelMetadata {
111
+ return {
112
+ name: graphModelName,
113
+ displayName: '',
114
+ description: '',
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Default content for graph view.
120
+ * @param graphViewName graph view name
121
+ * @returns Default content for graph view.
122
+ */
123
+ static graphView(graphViewName: string): GraphViewMetadata {
124
+ return {
125
+ name: graphViewName,
126
+ displayName: '',
127
+ description: '',
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Default content for link.
133
+ * @param cardKey card key id
134
+ * @param linkTypeName link type name
135
+ * @returns Default content for link.
136
+ */
137
+ static link(cardKey: string, linkTypeName: string): Link {
138
+ return {
139
+ linkType: linkTypeName,
140
+ cardKey: cardKey,
141
+ };
142
+ }
143
+
144
+ /**
145
+ * Default content for link type.
146
+ * @param linkTypeName link type name
147
+ * @returns Default content for link type.
148
+ */
149
+ static linkType(linkTypeName: string): LinkType {
150
+ return {
151
+ name: linkTypeName,
152
+ outboundDisplayName: linkTypeName,
153
+ inboundDisplayName: linkTypeName,
154
+ sourceCardTypes: [],
155
+ destinationCardTypes: [],
156
+ enableLinkDescription: false,
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Default content for report type.
162
+ * @param reportName report name
163
+ * @returns Default content for report type.
164
+ */
165
+ static report(reportName: string): ReportMetadata {
166
+ return {
167
+ name: reportName,
168
+ displayName: '',
169
+ description: '',
170
+ category: 'Uncategorised report',
171
+ };
172
+ }
173
+
174
+ /**
175
+ * Default template content
176
+ * @returns Default template content
177
+ */
178
+ public static template(templateName: string): TemplateMetadata {
179
+ return {
180
+ name: templateName,
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Default content for workflow JSON values.
186
+ * @param {string} workflowName workflow name
187
+ * @returns Default content for workflow JSON values.
188
+ */
189
+ public static workflow(workflowName: string): Workflow {
190
+ return {
191
+ name: workflowName,
192
+ states: [
193
+ { name: 'Draft', category: WorkflowCategory.initial },
194
+ { name: 'Approved', category: WorkflowCategory.closed },
195
+ { name: 'Deprecated', category: WorkflowCategory.closed },
196
+ ],
197
+ transitions: [
198
+ {
199
+ name: 'Create',
200
+ fromState: [''],
201
+ toState: 'Draft',
202
+ },
203
+ {
204
+ name: 'Approve',
205
+ fromState: ['Draft'],
206
+ toState: 'Approved',
207
+ },
208
+ {
209
+ name: 'Archive',
210
+ fromState: ['*'],
211
+ toState: 'Deprecated',
212
+ },
213
+ ],
214
+ };
215
+ }
216
+ }