archsight 0.3.3 → 0.4.1

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 (193) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -1
  3. data/docs/archimate.md +21 -1
  4. data/docs/configuration.md +1 -0
  5. data/docs/diagram.md +15 -3
  6. data/docs/docker.md +4 -1
  7. data/docs/icons.md +8 -2
  8. data/docs/import.md +178 -55
  9. data/docs/index.md.erb +24 -18
  10. data/docs/modeling.md +645 -34
  11. data/docs/pages.md +46 -15
  12. data/docs/search.md +21 -1
  13. data/docs/togaf.md +2 -1
  14. data/lib/archsight/analysis/result.rb +8 -2
  15. data/lib/archsight/analysis/sandbox.rb +8 -0
  16. data/lib/archsight/analysis/typst_renderer.rb +245 -0
  17. data/lib/archsight/analysis.rb +1 -0
  18. data/lib/archsight/annotations/annotation.rb +30 -3
  19. data/lib/archsight/annotations/architecture_annotations.rb +47 -0
  20. data/lib/archsight/annotations/link_annotations.rb +15 -0
  21. data/lib/archsight/annotations/resource_reference.rb +12 -0
  22. data/lib/archsight/cli.rb +124 -23
  23. data/lib/archsight/database.rb +40 -0
  24. data/lib/archsight/diagram/cli.rb +50 -2
  25. data/lib/archsight/diagram/formatter.rb +112 -0
  26. data/lib/archsight/diagram/routing/edge_router/orthogonal_path.rb +38 -10
  27. data/lib/archsight/diagram/routing/edge_router.rb +29 -4
  28. data/lib/archsight/diagram/routing/edge_routing.rb +61 -5
  29. data/lib/archsight/diagram/style/theme.rb +7 -2
  30. data/lib/archsight/documentation.rb +4 -4
  31. data/lib/archsight/editor/page_source.rb +13 -1
  32. data/lib/archsight/export/confluence/analysis_output.rb +100 -0
  33. data/lib/archsight/export/confluence/client.rb +13 -0
  34. data/lib/archsight/export/confluence/diagram_links.rb +1 -1
  35. data/lib/archsight/export/confluence/exporter.rb +43 -14
  36. data/lib/archsight/export/confluence/labels.rb +25 -0
  37. data/lib/archsight/export/confluence/page_properties.rb +6 -9
  38. data/lib/archsight/export/confluence/storage.rb +63 -14
  39. data/lib/archsight/export/confluence/tables.rb +2 -3
  40. data/lib/archsight/git_source.rb +81 -0
  41. data/lib/archsight/helpers/asset_images.rb +3 -4
  42. data/lib/archsight/helpers/embeds.rb +18 -7
  43. data/lib/archsight/helpers/resource_resolver.rb +25 -0
  44. data/lib/archsight/import/executor.rb +140 -16
  45. data/lib/archsight/import/handler.rb +55 -6
  46. data/lib/archsight/import/handler_loader.rb +64 -0
  47. data/lib/archsight/import/handlers/cache_pruner.rb +30 -7
  48. data/lib/archsight/import/handlers/command.rb +120 -0
  49. data/lib/archsight/import/handlers/github.rb +4 -1
  50. data/lib/archsight/import/handlers/gitlab.rb +4 -1
  51. data/lib/archsight/import/handlers/go_dep_resolver.rb +10 -2
  52. data/lib/archsight/import/handlers/go_grapher.rb +102 -5
  53. data/lib/archsight/import/handlers/go_module_parser.rb +88 -0
  54. data/lib/archsight/import/handlers/grapher.rb +40 -1
  55. data/lib/archsight/import/handlers/repository.rb +3 -0
  56. data/lib/archsight/import/handlers/rest_api_index.rb +3 -0
  57. data/lib/archsight/import/registry.rb +22 -1
  58. data/lib/archsight/import.rb +89 -1
  59. data/lib/archsight/linter/structure.rb +239 -0
  60. data/lib/archsight/linter.rb +123 -16
  61. data/lib/archsight/markdown.rb +48 -0
  62. data/lib/archsight/openapi.rb +169 -0
  63. data/lib/archsight/page_loader.rb +12 -1
  64. data/lib/archsight/page_tree.rb +2 -3
  65. data/lib/archsight/query/evaluator.rb +13 -6
  66. data/lib/archsight/query/parser.rb +73 -6
  67. data/lib/archsight/query.rb +1 -1
  68. data/lib/archsight/references.rb +3 -3
  69. data/lib/archsight/renderer.rb +80 -23
  70. data/lib/archsight/resources/analysis.rb +3 -2
  71. data/lib/archsight/resources/application_component.rb +75 -7
  72. data/lib/archsight/resources/application_interface.rb +12 -0
  73. data/lib/archsight/resources/application_service.rb +12 -1
  74. data/lib/archsight/resources/base.rb +58 -5
  75. data/lib/archsight/resources/business_actor.rb +5 -0
  76. data/lib/archsight/resources/business_contract.rb +72 -0
  77. data/lib/archsight/resources/business_product.rb +12 -0
  78. data/lib/archsight/resources/business_role.rb +1 -0
  79. data/lib/archsight/resources/implementation_deliverable.rb +1 -0
  80. data/lib/archsight/resources/implementation_plateau.rb +8 -2
  81. data/lib/archsight/resources/implementation_work_package.rb +2 -1
  82. data/lib/archsight/resources/import.rb +37 -11
  83. data/lib/archsight/resources/location.rb +56 -0
  84. data/lib/archsight/resources/motivation_requirement.rb +6 -0
  85. data/lib/archsight/resources/motivation_stakeholder.rb +14 -0
  86. data/lib/archsight/resources/page.rb +4 -7
  87. data/lib/archsight/resources/strategy_capability.rb +9 -0
  88. data/lib/archsight/resources/technology_artifact.rb +11 -4
  89. data/lib/archsight/resources/technology_communication_network.rb +91 -0
  90. data/lib/archsight/resources/technology_device.rb +88 -0
  91. data/lib/archsight/resources/technology_equipment.rb +75 -0
  92. data/lib/archsight/resources/technology_facility.rb +70 -0
  93. data/lib/archsight/resources/technology_node.rb +50 -0
  94. data/lib/archsight/resources/technology_service.rb +33 -0
  95. data/lib/archsight/resources/technology_system_software.rb +28 -0
  96. data/lib/archsight/resources.rb +12 -0
  97. data/lib/archsight/version.rb +1 -1
  98. data/lib/archsight/web/api/analysis_helpers.rb +55 -0
  99. data/lib/archsight/web/api/docs.rb +2 -1
  100. data/lib/archsight/web/api/json_helpers.rb +3 -37
  101. data/lib/archsight/web/api/openapi/spec.yaml +146 -35
  102. data/lib/archsight/web/api/page_helpers.rb +1 -8
  103. data/lib/archsight/web/api/routes.rb +9 -4
  104. data/lib/archsight/web/application.rb +40 -4
  105. data/lib/archsight/web/editor/routes.rb +2 -1
  106. data/lib/archsight/web/public/vendor/redoc/LICENSE +22 -0
  107. data/lib/archsight/web/public/vendor/redoc/VERSION +1 -0
  108. data/lib/archsight/web/public/vendor/redoc/api.html +27 -0
  109. data/lib/archsight/web/public/vendor/redoc/api.js +19 -0
  110. data/lib/archsight/web/public/vendor/redoc/redoc.standalone.js +1851 -0
  111. data/lib/archsight/web/public/vendor/redoc/redoc.standalone.js.LICENSE.txt +102 -0
  112. data/lib/archsight/web/public/vue/DocPage-C2ubTXH7.js +1 -0
  113. data/lib/archsight/web/public/vue/EditorPage-Dso6PLMn.js +35 -0
  114. data/lib/archsight/web/public/vue/ErrorPage-DnZ6YJLi.js +2 -0
  115. data/lib/archsight/web/public/vue/GraphView-CTl03Yre.js +1 -0
  116. data/lib/archsight/web/public/vue/HomePage-C339nihQ.js +2 -0
  117. data/lib/archsight/web/public/vue/InstanceRouter-DFW7tOrD.js +1 -0
  118. data/lib/archsight/web/public/vue/InstanceRouter-ZaCcjlIX.css +1 -0
  119. data/lib/archsight/web/public/vue/KindList-C0oaM3CF.js +1 -0
  120. data/lib/archsight/web/public/vue/KindList-yYcRT2BV.css +1 -0
  121. data/lib/archsight/web/public/vue/PageView-B93woeN3.css +1 -0
  122. data/lib/archsight/web/public/vue/PageView-OZB6G9Wu.js +1 -0
  123. data/lib/archsight/web/public/vue/QueryError-C2xLkCEm.js +1 -0
  124. data/lib/archsight/web/public/vue/ResourceList-D75O87zG.css +1 -0
  125. data/lib/archsight/web/public/vue/ResourceList-DooWFwQP.js +2 -0
  126. data/lib/archsight/web/public/vue/SearchResults-BK-HjFnv.js +1 -0
  127. data/lib/archsight/web/public/vue/{SearchResults-BewvsfOc.css → SearchResults-BV6UHriJ.css} +1 -1
  128. data/lib/archsight/web/public/vue/WikiPage-B2FYmeHL.js +3 -0
  129. data/lib/archsight/web/public/vue/WikiPage-TpmM1VAF.css +1 -0
  130. data/lib/archsight/web/public/vue/architecture-WOLXFQ4H-BGN-TV5r.js +1 -0
  131. data/lib/archsight/web/public/vue/cynefin-EF2NZ3EQ-CZd6g_tq.js +1 -0
  132. data/lib/archsight/web/public/vue/eventmodeling-K75KTNOO-CYnbEx8O.js +1 -0
  133. data/lib/archsight/web/public/vue/gitGraph-VSP46ZUC-DialfQSk.js +1 -0
  134. data/lib/archsight/web/public/vue/graphviz-1lWBhpEX.js +3 -0
  135. data/lib/archsight/web/public/vue/index-BLSE5DSv.css +1 -0
  136. data/lib/archsight/web/public/vue/index-CLuLTE5I.js +3 -0
  137. data/lib/archsight/web/public/vue/info-OHQRW6UA-BuhOW7ss.js +1 -0
  138. data/lib/archsight/web/public/vue/katex-B_JcIwqj.js +257 -0
  139. data/lib/archsight/web/public/vue/mermaid-C3bOamfn.js +3999 -0
  140. data/lib/archsight/web/public/vue/packet-JDAUHWVQ-CUeRz7w4.js +1 -0
  141. data/lib/archsight/web/public/vue/pie-XZMESJXO-DnuewJI6.js +1 -0
  142. data/lib/archsight/web/public/vue/radar-ABXABTNO-Bi9l8bcv.js +1 -0
  143. data/lib/archsight/web/public/vue/railroad-I3PHUGI6-DSlxFbxk.js +1 -0
  144. data/lib/archsight/web/public/vue/railroad-abnf-LNEFI6M7-D6Kqu8wu.js +1 -0
  145. data/lib/archsight/web/public/vue/railroad-ebnf-DWYD2UWJ-MR7uCYT0.js +1 -0
  146. data/lib/archsight/web/public/vue/railroad-peg-JR7BVNR7-BL7aKIc-.js +1 -0
  147. data/lib/archsight/web/public/vue/rolldown-runtime-COiEfhsA.js +1 -0
  148. data/lib/archsight/web/public/vue/treeView-D4JQ5SDB-CamtB04D.js +1 -0
  149. data/lib/archsight/web/public/vue/treemap-SAJKECNS-DeQxu_HM.js +1 -0
  150. data/lib/archsight/web/public/vue/{useGraphviz-DlSnFeCL.js → useGraphviz-Dnm2pkon.js} +2 -2
  151. data/lib/archsight/web/public/vue/useHighlight-BH32C7yt.js +10 -0
  152. data/lib/archsight/web/public/vue/usePanZoom-HxgEsqq2.js +12 -0
  153. data/lib/archsight/web/public/vue/wardley-7MLQ67FV-BvKIB6Tv.js +1 -0
  154. data/lib/archsight/web/public/vue.html +5 -5
  155. metadata +68 -41
  156. data/lib/archsight/web/public/vue/ApiDocsPage-BgqnQgwa.js +0 -1
  157. data/lib/archsight/web/public/vue/ApiDocsPage-DZ0fa5-h.css +0 -1
  158. data/lib/archsight/web/public/vue/DocPage-CNO71nBH.js +0 -1
  159. data/lib/archsight/web/public/vue/EditorPage-DR0FCNTz.js +0 -35
  160. data/lib/archsight/web/public/vue/ErrorPage-BfYArv6s.js +0 -2
  161. data/lib/archsight/web/public/vue/GraphView-BVTW30Ak.js +0 -1
  162. data/lib/archsight/web/public/vue/HomePage-Wk9P4Pma.js +0 -2
  163. data/lib/archsight/web/public/vue/InstanceRouter-BV8ycydz.js +0 -1
  164. data/lib/archsight/web/public/vue/InstanceRouter-jeavM6PW.css +0 -1
  165. data/lib/archsight/web/public/vue/KindList-C8yMsN5J.js +0 -1
  166. data/lib/archsight/web/public/vue/PageView-BaN6TyJB.js +0 -1
  167. data/lib/archsight/web/public/vue/QueryError-D790wH-a.js +0 -1
  168. data/lib/archsight/web/public/vue/ResourceList-B0FLaFR3.css +0 -1
  169. data/lib/archsight/web/public/vue/ResourceList-D-66nas2.js +0 -2
  170. data/lib/archsight/web/public/vue/SearchResults-CHyqIerQ.js +0 -1
  171. data/lib/archsight/web/public/vue/WikiPage-D2svpk6B.js +0 -13
  172. data/lib/archsight/web/public/vue/WikiPage-DoFJ0dbW.css +0 -1
  173. data/lib/archsight/web/public/vue/architecture-7GRP2DOG-BfA1TPxQ.js +0 -1
  174. data/lib/archsight/web/public/vue/cynefin-OW5HDTMX-BmgdUKVD.js +0 -1
  175. data/lib/archsight/web/public/vue/eventmodeling-NTZA5JFV-DbXDvAWF.js +0 -1
  176. data/lib/archsight/web/public/vue/gitGraph-4MIJSDKK-BkVhrKS1.js +0 -1
  177. data/lib/archsight/web/public/vue/graphviz-SXWTgZH5.js +0 -3
  178. data/lib/archsight/web/public/vue/index-BXXkUK1V.js +0 -3
  179. data/lib/archsight/web/public/vue/index-Cov1SnzY.css +0 -1
  180. data/lib/archsight/web/public/vue/info-A6RAGUB7-BIlCVuUY.js +0 -1
  181. data/lib/archsight/web/public/vue/katex-CXMH3UgJ.js +0 -257
  182. data/lib/archsight/web/public/vue/mermaid-BjEi5URd.js +0 -3334
  183. data/lib/archsight/web/public/vue/packet-AYTQ26CC-eoQTjY3j.js +0 -1
  184. data/lib/archsight/web/public/vue/pie-WAS4IAKB-DEOotGhh.js +0 -1
  185. data/lib/archsight/web/public/vue/radar-RG4KPBEZ-C2yucuUN.js +0 -1
  186. data/lib/archsight/web/public/vue/railroad-74A4TZTK-CzONK5VY.js +0 -1
  187. data/lib/archsight/web/public/vue/railroad-abnf-HS5TGJTU-9zRPsDsx.js +0 -1
  188. data/lib/archsight/web/public/vue/railroad-ebnf-LZEXJU2U-BJ4dkz9l.js +0 -1
  189. data/lib/archsight/web/public/vue/railroad-peg-WCYAUIDC-DFy_rqAj.js +0 -1
  190. data/lib/archsight/web/public/vue/treeView-Q6P3EWNA-2cq5CyD2.js +0 -1
  191. data/lib/archsight/web/public/vue/treemap-WGGIJYW6-DK-e9Ez4.js +0 -1
  192. data/lib/archsight/web/public/vue/useHighlight-D5Gf8DLl.js +0 -10
  193. data/lib/archsight/web/public/vue/wardley-WFR3VGLG-BQwqUqfB.js +0 -1
data/docs/pages.md CHANGED
@@ -18,7 +18,9 @@ properties:
18
18
  Git Repository: https://git.example.com/org/language-strategy
19
19
  Ticket: "{jira:ARCH-42}"
20
20
  toc: yes
21
- confluence: https://confluence.example.com/spaces/ARCH/pages/12345/Language+Strategy
21
+ links:
22
+ confluence: https://confluence.example.com/spaces/ARCH/pages/12345/Language+Strategy
23
+ jira: https://jira.example.com/browse/ARCH-12
22
24
  ---
23
25
 
24
26
  # Language Strategy
@@ -34,7 +36,7 @@ confluence: https://confluence.example.com/spaces/ARCH/pages/12345/Language+Stra
34
36
  | `properties` | Everything else you want to record, as a mapping of `key: value` pairs (kept in order and shown in the page header). Values are inline markdown: links, emphasis, `{macros}` such as `{jira:KEY}` and `[[wiki links]]` work, bare URLs become links. In the API and in queries it is `page/properties`, one `Key: value` per line |
35
37
  | `status` | Free text, e.g. `rfc`, `wip`, `approved` |
36
38
  | `toc` | `yes` shows a table of contents |
37
- | `confluence` | URL of the corresponding Confluence page, shown as a link |
39
+ | `links` | A mapping of names to URLs: where the page lives in other systems (`confluence`, `jira`, `github`, ...), shown as links with the icon of the system. Each entry is the annotation `link/<name>` (`link/confluence`), the same links every other resource can have; a link has no direction, it is where the page comes from or where it is published to. The old `confluence: <url>` key still works, is reported as deprecated by `archsight lint` and is moved into `links` when the page is saved in the editor |
38
40
  | `name` | Optional unique name; defaults to the file name without `.md`. Files with the same name in different folders need an explicit `name` |
39
41
 
40
42
  Markdown files without a frontmatter block are ignored.
@@ -128,21 +130,27 @@ is served:
128
130
  such requests, so an image embedded in a diagram from an `https://` URL is not shown. Formulas are not typeset.
129
131
  - **Kubernetes**: binary files do not fit a ConfigMap (1 MB limit), provide the resources with git-sync or a volume.
130
132
 
131
- ## Embedding views and analyses
133
+ ## Embedding views, analyses and descriptions
132
134
 
133
135
  `![[View/Name]]` and `![[Analysis/Name]]` show the live content of a view (its result list) or an analysis (its
134
- result) inside a page, or in the description of any resource:
136
+ result) inside a page, or in the description of any resource. Any other resource, written `![[Kind/Name]]`, shows its
137
+ description (`architecture/description`), for example the goal a concept is written for:
135
138
 
136
139
  ```markdown
137
140
  ![[View/View:ServiceDependencies]]
138
141
  ![[Analysis/Analysis:Service:Count]]
142
+ ![[MotivationGoal/Goal:SimplifyArchitecture]]
139
143
  ```
140
144
 
145
+ The **Embed** button on the page of a resource, view or analysis copies its `![[Kind/Name]]`. An embedded description
146
+ is set off like a quote and links to its resource; `![[...]]` embeds inside it are shown as links (two descriptions
147
+ cannot embed each other without end). Pages cannot be embedded.
148
+
141
149
  The page is shown immediately. Each embed loads on its own, with a spinner, and an analysis runs when the page opens
142
- (it can be run again from the embed), so a slow query or script never delays the text around it. The embed
150
+ (reload the page to run it again), so a slow query or script never delays the text around it. The embed
143
151
  links to the view or analysis page. The server only writes a placeholder: rendering a page (`GET /api/v1/pages/{name}`)
144
152
  never runs a query or a script, and API or MCP consumers get `<div class="kind-embed" data-kind data-name>` with a plain
145
- link to the resource. Other kinds are not embeddable; an unknown name, another kind or an embed written inline in
153
+ link to the resource. An unknown name, a page or an embed written inline in
146
154
  a sentence (instead of on its own line) is shown as a marker or a link, and `archsight lint` reports embeds that do not
147
155
  resolve. Code blocks and inline code are left alone.
148
156
 
@@ -269,16 +277,26 @@ The form has the frontmatter keys as fields and the body as a markdown field (ri
269
277
  conflict if the file changed since the form was opened, and a file with broken frontmatter, without
270
278
  frontmatter or with a changed name is rejected before it is written.
271
279
 
272
- The saved frontmatter is rewritten in the order `title, tags, author, owner, status, toc, confluence`.
273
- `name` and keys the form does not know are kept; YAML comments inside the frontmatter are not.
280
+ The saved frontmatter is rewritten in the order `title, tags, author, owner, status, toc, created, updated, properties`.
281
+ `name`, `links` and keys the form does not know are kept (an old `confluence:` key becomes `links: { confluence: ... }`); YAML comments inside the frontmatter are not.
274
282
 
275
283
  Inside the rich editor, an ` ```asd ` block shows a live [diagram preview](/doc/diagram#preview-in-the-editor).
276
284
 
285
+ ### Migrating `confluence:` to `links`
286
+
287
+ `archsight lint` lists every page that still uses `confluence:` (or `page/confluence`) as a deprecation without failing.
288
+ Pages written as markdown can be moved with a find and replace over the resources directory; a page that has `links`
289
+ already keeps that value:
290
+
291
+ ```bash
292
+ find resources -name '*.md' -exec perl -0pi -e 's/^confluence:\s*(\S+)\s*$/links:\n confluence: $1/m' {} +
293
+ ```
294
+
277
295
  ## Exporting to Confluence
278
296
 
279
- `archsight export --to confluence [PAGE...]` publishes pages to the Confluence page named in their `confluence:` frontmatter
297
+ `archsight export --to confluence [PAGE...]` publishes pages to the Confluence page named in their `links.confluence` frontmatter
280
298
  (Confluence Data Center; a page URL such as `https://host/spaces/KEY/pages/12345/Title`, `.../pages/viewpage.action?pageId=12345`
281
- or `.../display/KEY/Title`). Without `PAGE` every page that has a `confluence:` link is exported.
299
+ or `.../display/KEY/Title`). Without `PAGE` every page that has a `confluence` link is exported.
282
300
 
283
301
  ```bash
284
302
  archsight export --to confluence -r resources # all linked pages
@@ -290,8 +308,13 @@ archsight export --to confluence handbook-home --force # overwrite change
290
308
  **Page properties**: the frontmatter is exported as a Page Properties table (the `details` macro) above the body, so Confluence
291
309
  keeps its property reports working: Document status (a status lozenge; `wip`/`draft` yellow, `rfc`/`review` blue,
292
310
  `approved`/`done`/`ga` green, `deprecated`/`rejected` red, anything else grey), Document owner, Document author, Teams (the
293
- `Team:<name>` tags), Tags and every entry of `properties`. People are written as their name. `created` and `updated` are not
294
- exported. The Confluence import (not part of Archsight) does the reverse, which is why `Team:` tags and `properties` exist.
311
+ `Team:<name>` tags) and every entry of `properties`. People are written as their name. `created` and `updated` are not
312
+ exported, and neither are the other tags: they are the page's labels (below). The Confluence import (not part of Archsight) does the reverse, which is why `Team:` tags and `properties` exist.
313
+
314
+ **Labels**: the tags of a page become its Confluence labels, so label macros and searches find the page. A label is
315
+ lowercase without spaces or `!#&()*,.:;<>?@[]^`, those become a dash (`Team:BSS` is the label `team-bss`). A tag that is
316
+ removed removes the label again, but only labels the export added itself (recorded in the marker): labels somebody set
317
+ in Confluence stay. Labels are not versioned, so a page whose labels alone change gets no new version.
295
318
 
296
319
  **Credentials** are a personal access token, set as `confluence.token` in the [configuration file](/doc/configuration)
297
320
  (`~/.config/archsight/archsight.yaml`, `--config PATH` for another file) or in `ARCHSIGHT_CONFLUENCE_TOKEN`. The host comes from
@@ -316,9 +339,9 @@ confluence:
316
339
  With `drawio` off nothing draw.io-specific is written, so any Confluence shows the diagrams. Rendering a `.drawio` needs the
317
340
  draw.io desktop CLI (`drawio`, or `ARCHSIGHT_DRAWIO_CLI`) on the machine that exports; the preview of an SVG needs
318
341
  `rsvg-convert` or that CLI. A diagram that cannot be rendered fails the page instead of leaving a blank diagram. Attachments an
319
- earlier export added and the page no longer uses are removed; attachments added by others are left alone. `[[links]]` and markdown links to pages (`[text](/pages/name)`) that have a Confluence link
342
+ earlier export added and the page no longer uses are removed (a draw.io diagram is attached under its name and a hash of its content, so a changed diagram is a new attachment, because the draw.io macro would keep showing the first version of an attachment it names); attachments added by others are left alone. `[[links]]` and markdown links to pages (`[text](/pages/name)`) that have a Confluence link
320
343
  become links to them, other links to Archsight (pages without a Confluence link, resources, searches) are plain text. A page with a broken image, a diagram that does not render or an invalid
321
- ```` ```view ````/```` ```requirements ```` block is not exported and reported as failed. The Confluence title is kept. The exported page uses the Confluence layout: the page properties top left, the note that the page is generated by Archsight top right, and the content below in a section of its own.
344
+ ```` ```view ````/```` ```requirements ```` block is not exported and reported as failed. The Confluence title is kept. The exported page uses the Confluence layout: the page properties top left, the note that the page is generated by Archsight top right, and the content below in a section of its own. When the resources are a git checkout with an `origin` remote, the note names the repository and links the source file to its page on the git host (GitHub `/blob/`, GitLab `/-/blob/`, on the checked out branch, or the commit when the HEAD is detached; other hosts get only the repository link, and so does a file git does not track). User names and tokens in the remote URL never appear in the link.
322
345
 
323
346
  **Views and requirements** are live in Archsight, so Confluence gets a regular table with the data of the moment of the export (every
324
347
  export writes the current data; a page whose tables did not change is `unchanged`): `![[View/Name]]` on a line of its own, a
@@ -326,7 +349,15 @@ export writes the current data; a page whose tables did not change is `unchanged
326
349
  the view (Name, Kind unless `list:name`, the `view/fields`, sorted by `view/sort`; times are the stored values, not "3 days ago") or
327
350
  the requirements table (status and priority as status lozenges: implemented green, partial yellow, planned blue; `must` red,
328
351
  `should` yellow, `may` grey), and names of pages that have a Confluence link as links. At most 200 rows are exported, a note says how many
329
- were left out. A `![[View/..]]` that is not alone on its line or names no view stays a note, and so does `![[Analysis/..]]` (it runs a script).
352
+ were left out. A `![[View/..]]` that is not alone on its line or names no view stays a note.
353
+
354
+ **Analyses** (`![[Analysis/Name]]` on a line of its own) are run at the export and their result is written into the page, the data
355
+ of the moment like a view: headings, text, tables and lists as Confluence content (`[[links]]` as for the page, `<br>` and newlines
356
+ in a cell break the line), `info`/`warning`/`error` messages as info, note and warning panels, code as a code macro. The result
357
+ holds no time or run time, so a page whose analysis gives the same result stays `unchanged`. A failing analysis is a note in the page,
358
+ an analysis that is not alone on its line or names no analysis stays a note.
359
+ An embedded description (`![[MotivationGoal/Name]]` on a line of its own) is text, not live content: it is written into the page
360
+ as it is in Archsight; embeds inside it stay notes.
330
361
 
331
362
  **Links in diagrams**: a node with `resource "Some Page"` links to the Confluence page of that wiki page (the page's own
332
363
  `confluence:` link) instead of its Archsight address, which means nothing in Confluence. A reference to a page without a
data/docs/search.md CHANGED
@@ -24,9 +24,17 @@ Type any word to search by name (case-insensitive regex match):
24
24
  name != "OldService" # not equal
25
25
  name in ("a", "b") # name is one of several options
26
26
 
27
+ Names often contain colons (`Archsight:CLI`) and so do [aliases](/doc/modeling#identity-and-links) (`ITGS:A001`). Typed
28
+ without spaces they are one word, found by name or alias like any other bare word:
29
+
30
+ Archsight:CLI # the resources named Archsight:CLI
31
+ ITGS:A001 # the resource with the alias ITGS:A001
32
+ ApplicationService:ITGS:A001 # the same, in one kind
33
+
27
34
  ## Kind Filter
28
35
 
29
- Prefix your query with `Kind:` to filter by resource type:
36
+ Prefix your query with `Kind:` to filter by resource type (a capitalised word that is a kind, followed by a space or
37
+ nothing; `Kind:Name` without a space is the kind and a name):
30
38
 
31
39
  TechnologyArtifact: # all of this kind
32
40
  TechnologyArtifact: activity/status == "active" # filtered
@@ -246,6 +254,18 @@ Active containerized services:
246
254
 
247
255
  TechnologyArtifact: activity/status == "active" & repository/artifacts == "container"
248
256
 
257
+ Everything that is on its way out:
258
+
259
+ lifecycle/status in ("deprecated", "sunset", "end-of-life")
260
+
261
+ Resources in use (a resource without a status is active, the annotation's default):
262
+
263
+ lifecycle/status == "active"
264
+
265
+ What is planned, not yet in use:
266
+
267
+ lifecycle/status == "planned"
268
+
249
269
  Services exposing APIs:
250
270
 
251
271
  ApplicationComponent: -> ApplicationInterface
data/docs/togaf.md CHANGED
@@ -123,13 +123,14 @@ The metamodel defines architectural entities and their relationships across all
123
123
  **Cross-Cutting:**
124
124
 
125
125
  - **Principle**, **Constraint**, **Requirement**, **Location**
126
+ - **Stakeholder**: `MotivationStakeholder`, with its concerns (`hasConcern`); the organisation unit or actor behind it is linked with `representedBy` (`BusinessActor`, `BusinessRole`). The stakeholder that has the concern is the source of a requirement, as recorded in the requirements repository
126
127
  - **Gap**, **Work Package**, **Deliverable**, **Plateau**, **Implementation Event**: the Implementation & Migration layer (`ImplementationGap`, `ImplementationWorkPackage`, `ImplementationDeliverable`, `ImplementationPlateau`, `ImplementationEvent`)
127
128
 
128
129
  **Governance:**
129
130
 
130
131
  - **Control**: a decision-making step with accountability and authority, applied to a process or function. ArchiMate has no element for it; Archsight models it as `BusinessControl`, which a `BusinessProcess` is `guidedBy`
131
132
 
132
- **Risk and security:** the Open Group overlay (*Modeling Enterprise Risk Management and Security with the ArchiMate Language*) maps threats to **Driver** (`MotivationDriver`), threat and loss events to **Business/Application/Technology Event**, risks and vulnerabilities to **Assessment** (`MotivationAssessment`), control objectives to **Goal**, control measures to **Requirement** and policies to **Principle** (`MotivationPrinciple`), each with a type annotation. See [Modeling Guide](modeling.md#security-and-risk). How relations are directed (top-down, DAG) is described under [Direction of Relations](modeling.md#direction-of-relations).
133
+ **Risk and security:** the Open Group overlay (*Modeling Enterprise Risk Management and Security with the ArchiMate Language*) maps threats to **Driver** (`MotivationDriver`), threat and loss events to **Business/Application/Technology Event**, risks and vulnerabilities to **Assessment** (`MotivationAssessment`), control objectives to **Goal**, control measures to **Requirement** and policies to **Principle** (`MotivationPrinciple`), each with a type annotation. See [Modeling Guide](modeling.md#security-and-risk). How relations are directed (top-down, DAG) is described under [Direction of Relations](modeling.md#direction-of-relations). Which of capability, product, service and component an element is follows the questions in [Choosing the Kind](modeling.md#choosing-the-kind).
133
134
 
134
135
  ### Critical Relationships
135
136
 
@@ -36,7 +36,8 @@ module Archsight
36
36
  @analysis.name
37
37
  end
38
38
 
39
- # @return [Boolean] true if any content sections exist (excluding messages)
39
+ # @return [Boolean] true if the script produced any output (tables, lists, text, ...; not counting messages).
40
+ # Output is not a problem (many analyses are reports); see #warnings?
40
41
  def has_findings?
41
42
  @sections.any? { |s| %i[table list text heading code].include?(s[:type]) }
42
43
  end
@@ -51,6 +52,11 @@ module Archsight
51
52
  @sections.count { |s| s[:type] == :message && s[:level] == :warning }
52
53
  end
53
54
 
55
+ # @return [Boolean] true if the script reported an error or a warning itself (`error(...)`, `warning(...)`)
56
+ def warnings?
57
+ (error_count + warning_count).positive?
58
+ end
59
+
54
60
  # Convert result to markdown (only script-generated content)
55
61
  # @param verbose [Boolean] Include detailed output
56
62
  # @return [String] Markdown formatted output
@@ -82,7 +88,7 @@ module Archsight
82
88
  def status_emoji
83
89
  return "❌" unless success?
84
90
 
85
- has_findings? ? "⚠️" : "✅"
91
+ warnings? ? "⚠️" : "✅"
86
92
  end
87
93
 
88
94
  # Formatted duration string
@@ -221,6 +221,14 @@ module Archsight
221
221
  @sections << { type: :code, content: content, lang: lang }
222
222
  end
223
223
 
224
+ # Attach structured data for document templates (archsight analyze --format typst --template).
225
+ # Not shown in the web UI or the terminal; a template renders it instead of the other sections.
226
+ # @param key [String, Symbol] Name the template looks the data up by
227
+ # @param value [Hash, Array, String, Numeric, Boolean, nil] JSON-like value
228
+ def data(key, value)
229
+ @sections << { type: :data, key: key.to_s, value: value }
230
+ end
231
+
224
232
  # Legacy output methods - still supported for backward compatibility
225
233
 
226
234
  # Report findings (legacy method - creates appropriate section based on data type)
@@ -0,0 +1,245 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../markdown"
4
+ require_relative "../diagram"
5
+
6
+ module Archsight
7
+ module Analysis
8
+ # Renders analysis results as one Typst document (https://typst.app).
9
+ #
10
+ # All text is emitted as Typst string literals (`#"..."`) and structure as
11
+ # function calls (`#strong[...]`), so no content can be mistaken for markup.
12
+ class TypstRenderer
13
+ ROW_LIMIT = 10
14
+ HEADING_OFFSET = 2 # section level 0 becomes a level-2 heading, the analysis name is level 1
15
+ LANGUAGE = /\A[A-Za-z0-9+#-]+\z/
16
+ MESSAGE_FILL = { error: "rgb(\"#fde8e8\")", warning: "rgb(\"#fff3cc\")", info: "rgb(\"#ddeeff\")" }.freeze
17
+ SYMBOLS = {
18
+ ndash: "–", mdash: "—", hellip: "…", laquo: "«", raquo: "»", laquo_space: "« ", raquo_space: " »",
19
+ lsquo: "‘", rsquo: "’", ldquo: "“", rdquo: "”"
20
+ }.freeze
21
+
22
+ PREAMBLE = <<~TYPST
23
+ #set document(title: "Archsight analysis")
24
+ #set page(paper: "a4", margin: 2cm, numbering: "1")
25
+ #set text(size: 10pt)
26
+ #show raw.where(block: true): it => block(fill: luma(245), inset: 8pt, radius: 3pt, width: 100%, it)
27
+ TYPST
28
+
29
+ # @param results [Array<Archsight::Analysis::Result>]
30
+ # @param verbose [Boolean] show every table row and list item and the error backtrace
31
+ # @param template [String, nil] Typst file to import instead of the built-in layout. It must define
32
+ # `archsight-report` (a show rule for the document) and `analysis-result` (one call per analysis,
33
+ # with the data sections as a dictionary and the other sections as body).
34
+ def initialize(results, verbose: false, template: nil)
35
+ @results = results
36
+ @verbose = verbose
37
+ @template = template
38
+ end
39
+
40
+ # @return [String] Typst source
41
+ def render
42
+ return [template_preamble, *@results.map { |result| template_result(result) }].join("\n") if @template
43
+
44
+ [PREAMBLE, title, summary, *@results.map { |result| render_result(result) }].join("\n")
45
+ end
46
+
47
+ private
48
+
49
+ def title
50
+ "#align(center)[#text(size: 20pt, weight: \"bold\")[#{str("Archsight analysis")}]]\n"
51
+ end
52
+
53
+ def summary
54
+ completed = @results.count(&:success?)
55
+ failed = @results.count(&:failed?)
56
+ with_warnings = @results.count { |result| result.success? && result.warnings? }
57
+ items = ["#{completed} completed"]
58
+ items << "#{failed} failed" if failed.positive?
59
+ items << "#{with_warnings} with warnings" if with_warnings.positive?
60
+ "#list(#{items.map { |item| "[#{str(item)}]" }.join(", ")})\n"
61
+ end
62
+
63
+ def template_preamble
64
+ counts = { count: @results.size, passed: @results.count(&:success?), failed: @results.count(&:failed?),
65
+ findings: @results.count(&:has_findings?) }
66
+ "#import #{lit(@template)}: *\n#show: archsight-report.with(#{named_args(counts)})\n"
67
+ end
68
+
69
+ # One `analysis-result` call: metadata and data as named arguments, the rendered sections as body
70
+ def template_result(result)
71
+ data = result.sections.select { |s| s[:type] == :data }.to_h { |s| [s[:key], s[:value]] }
72
+ body = []
73
+ body << error_block(result) if result.failed?
74
+ body.concat(result.sections.filter_map { |section| render_section(section) })
75
+ args = { name: result.name, description: description(result), state: state(result),
76
+ duration: result.duration_str, data: data }
77
+ "#analysis-result(#{named_args(args)})[\n#{body.join("\n\n")}\n]\n"
78
+ end
79
+
80
+ def description(result)
81
+ analysis = result.analysis
82
+ analysis.respond_to?(:annotations) ? analysis.annotations["analysis/description"] : nil
83
+ end
84
+
85
+ def state(result)
86
+ return "failed" if result.failed?
87
+
88
+ result.has_findings? ? "findings" : "passed"
89
+ end
90
+
91
+ def named_args(hash)
92
+ hash.map { |key, value| "#{key}: #{typst_value(value)}" }.join(", ")
93
+ end
94
+
95
+ # A Ruby value as a Typst literal: Hash -> dictionary (string keys), Array -> array, nil -> none
96
+ def typst_value(value)
97
+ case value
98
+ when Hash then value.empty? ? "(:)" : "(#{value.map { |k, v| "#{lit(k.to_s)}: #{typst_value(v)}" }.join(", ")})"
99
+ when Array then "(#{value.map { |v| typst_value(v) }.join(", ")}#{"," if value.size == 1})"
100
+ when Integer, true, false then value.to_s
101
+ when Float then value.finite? ? value.to_s : "none"
102
+ when nil then "none"
103
+ else lit(value.to_s)
104
+ end
105
+ end
106
+
107
+ def render_result(result)
108
+ parts = ["#pagebreak(weak: true)", "#heading(level: 1)[#{str(result.name)}]"]
109
+ status = result.failed? ? "failed" : "completed"
110
+ status += ", #{result.duration_str}" unless result.duration_str.empty?
111
+ parts << "#text(fill: gray)[#{str(status)}]"
112
+ parts << error_block(result) if result.failed?
113
+ parts.concat(result.sections.filter_map { |section| render_section(section) })
114
+ "#{parts.join("\n\n")}\n"
115
+ end
116
+
117
+ def error_block(result)
118
+ lines = [message_block(:error, "**Error:** #{result.error}")]
119
+ lines << "#raw(#{lit(result.error_backtrace.join("\n"))}, block: true)" if @verbose && result.error_backtrace&.any?
120
+ lines.join("\n\n")
121
+ end
122
+
123
+ def render_section(section)
124
+ case section[:type]
125
+ when :heading then "#heading(level: #{[section[:level] + HEADING_OFFSET, 6].min})[#{str(section[:text])}]"
126
+ when :text then markdown(section[:content])
127
+ when :table then table(section[:headers], section[:rows])
128
+ when :list then list(section[:items])
129
+ when :code then code(section)
130
+ when :message then message_block(section[:level], section[:message])
131
+ when :data then nil # only for templates, passed to analysis-result
132
+ end
133
+ end
134
+
135
+ def message_block(level, text)
136
+ fill = MESSAGE_FILL.fetch(level, MESSAGE_FILL[:info])
137
+ "#block(fill: #{fill}, inset: 8pt, radius: 3pt, width: 100%)[#{markdown(text, inline: true)}]"
138
+ end
139
+
140
+ # A template produces a document to hand out, so it gets every row
141
+ def full?
142
+ @verbose || !@template.nil?
143
+ end
144
+
145
+ def table(headers, rows)
146
+ shown = full? ? rows : rows.first(ROW_LIMIT)
147
+ head = headers.map { |header| "[#strong[#{str(header)}]]" }
148
+ cells = shown.flat_map { |row| headers.each_index.map { |i| "[#{str(row[i])}]" } }
149
+ out = "#table(columns: #{headers.size}, inset: 5pt, stroke: 0.5pt + luma(200), " \
150
+ "table.header(#{head.join(", ")}), #{cells.join(", ")})"
151
+ out += "\n\n#emph[#{str("...and #{rows.size - ROW_LIMIT} more rows")}]" if shown.size < rows.size
152
+ out
153
+ end
154
+
155
+ def list(items)
156
+ shown = full? ? items : items.first(ROW_LIMIT)
157
+ out = "#list(#{shown.map { |item| "[#{markdown(item, inline: true)}]" }.join(", ")})"
158
+ out += "\n\n#emph[#{str("...and #{items.size - ROW_LIMIT} more items")}]" if shown.size < items.size
159
+ out
160
+ end
161
+
162
+ def code(section)
163
+ return diagram(section[:content]) if section[:lang].to_s == "asd"
164
+
165
+ raw(section[:content], section[:lang])
166
+ end
167
+
168
+ def diagram(source)
169
+ svg = Archsight::Diagram.render(source)
170
+ "#image(bytes(#{lit(svg)}), format: \"svg\", width: 100%)"
171
+ rescue Archsight::Diagram::Error => e
172
+ [message_block(:error, "Diagram could not be rendered: #{e.message}"), raw(source, "asd")].join("\n\n")
173
+ end
174
+
175
+ def raw(source, lang)
176
+ lang_arg = lang.to_s.match?(LANGUAGE) ? ", lang: #{lit(lang)}" : ""
177
+ "#raw(#{lit(source.to_s.chomp)}, block: true#{lang_arg})"
178
+ end
179
+
180
+ # A Typst string literal, for use as a function argument
181
+ def lit(text)
182
+ escaped = text.to_s.delete("\r").gsub(/[\\"]/) { "\\#{Regexp.last_match(0)}" }.gsub("\n", "\\n")
183
+ "\"#{escaped}\""
184
+ end
185
+
186
+ # A Typst string literal as markup, for use inside content
187
+ def str(text)
188
+ "##{lit(text)}"
189
+ end
190
+
191
+ # ---- markdown -> Typst
192
+
193
+ def markdown(source, inline: false)
194
+ root = Archsight::Markdown.document(source).root
195
+ out = blocks(root.children)
196
+ inline ? out.gsub(/\n\n+/, " #linebreak() ") : out
197
+ end
198
+
199
+ def blocks(elements)
200
+ elements.filter_map { |node| block(node) }.join("\n\n")
201
+ end
202
+
203
+ def block(node)
204
+ case node.type
205
+ when :p then inlines(node.children)
206
+ when :header then "#heading(level: #{[node.options[:level] + 1, 6].min})[#{inlines(node.children)}]"
207
+ when :codeblock then raw(node.value, node.options[:lang])
208
+ when :blockquote then "#quote(block: true)[#{blocks(node.children)}]"
209
+ when :ul then "#list(#{node.children.map { |li| "[#{blocks(li.children)}]" }.join(", ")})"
210
+ when :ol then "#enum(#{node.children.map { |li| "[#{blocks(li.children)}]" }.join(", ")})"
211
+ when :hr then "#line(length: 100%)"
212
+ when :table then markdown_table(node)
213
+ when :blank, :html_element, :xml_comment then nil
214
+ else inlines(node.children) unless node.children.empty?
215
+ end
216
+ end
217
+
218
+ def markdown_table(node)
219
+ rows = node.children.flat_map(&:children)
220
+ cells = rows.map { |tr| tr.children.map { |td| "[#{inlines(td.children)}]" } }
221
+ width = cells.map(&:size).max || 1
222
+ "#table(columns: #{width}, inset: 5pt, stroke: 0.5pt + luma(200), #{cells.flatten.join(", ")})"
223
+ end
224
+
225
+ def inlines(elements)
226
+ elements.map { |node| inline(node) }.join
227
+ end
228
+
229
+ def inline(node)
230
+ case node.type
231
+ when :text then str(node.value)
232
+ when :em then "#emph[#{inlines(node.children)}]"
233
+ when :strong then "#strong[#{inlines(node.children)}]"
234
+ when :codespan then "#raw(#{lit(node.value)})"
235
+ when :a then "#link(#{lit(node.attr["href"])})[#{inlines(node.children)}]"
236
+ when :br then "#linebreak()"
237
+ when :entity then str(node.value.char)
238
+ when :smart_quote, :typographic_sym then str(SYMBOLS.fetch(node.value, node.value.to_s))
239
+ when :html_element then inlines(node.children)
240
+ else inlines(node.children)
241
+ end
242
+ end
243
+ end
244
+ end
245
+ end
@@ -9,3 +9,4 @@ end
9
9
  require_relative "analysis/result"
10
10
  require_relative "analysis/sandbox"
11
11
  require_relative "analysis/executor"
12
+ require_relative "analysis/typst_renderer"
@@ -2,10 +2,11 @@
2
2
 
3
3
  require_relative "email_recipient"
4
4
  require_relative "person"
5
+ require_relative "resource_reference"
5
6
 
6
7
  # Annotation represents a single annotation definition with its schema and behavior
7
8
  class Archsight::Annotations::Annotation
8
- attr_reader :key, :description, :filter, :format, :enum, :sidebar, :type, :summary, :editor
9
+ attr_reader :key, :description, :filter, :format, :enum, :sidebar, :type, :summary, :editor, :deprecated, :default
9
10
 
10
11
  def initialize(key, options = {})
11
12
  @key = key
@@ -18,6 +19,9 @@ class Archsight::Annotations::Annotation
18
19
  @summary = options.fetch(:summary, false)
19
20
  @editor = options.fetch(:editor, true)
20
21
  @type = options[:type]
22
+ @unique = options[:unique] == true
23
+ @deprecated = options[:deprecated]
24
+ @default = options[:default]
21
25
 
22
26
  # Auto-add filter if enum present
23
27
  @filter ||= :word if @enum
@@ -51,6 +55,22 @@ class Archsight::Annotations::Annotation
51
55
  @filter == :list
52
56
  end
53
57
 
58
+ # A unique annotation holds identifiers: no two resources of a kind share a value (checked by `archsight lint`)
59
+ def unique?
60
+ @unique
61
+ end
62
+
63
+ # A deprecated annotation still loads and works, but `archsight lint` reports every use with what to use instead
64
+ # (the `deprecated:` text)
65
+ def deprecated?
66
+ !@deprecated.nil?
67
+ end
68
+
69
+ # @return [Boolean] true if the value names another resource (`Name` or `Kind/Name`)
70
+ def resource?
71
+ @format == :resource
72
+ end
73
+
54
74
  # Summary annotations are returned with every search hit (at most MAX_SUMMARY per kind)
55
75
  def summary?
56
76
  @summary == true
@@ -122,9 +142,11 @@ class Archsight::Annotations::Annotation
122
142
  @format if code? || diagram?
123
143
  end
124
144
 
125
- # Example value for templates
145
+ # Example value for templates: the default, else the first value of the enum
126
146
  def example_value
127
- if @enum
147
+ if @default
148
+ @default
149
+ elsif @enum
128
150
  @enum.first || "TODO"
129
151
  elsif @type == Float
130
152
  0.0
@@ -144,6 +166,7 @@ class Archsight::Annotations::Annotation
144
166
  when "Float" then "Expected a float value"
145
167
  when "Archsight::Annotations::EmailRecipient" then 'Expected email format: "Name <email@domain.com>" or "email@domain.com"'
146
168
  when "Archsight::Annotations::Person" then 'Expected "Name", "Name <email@domain.com>" or "email@domain.com"'
169
+ when "Archsight::Annotations::ResourceReference" then "Expected a resource name of the same kind (Archsight:Web) or Kind/Name (ApplicationService/Archsight:Web)"
147
170
  else "Invalid value for type #{@type}"
148
171
  end
149
172
  end
@@ -184,6 +207,7 @@ class Archsight::Annotations::Annotation
184
207
  when "URI" then valid_uri?(string_value)
185
208
  when "Archsight::Annotations::EmailRecipient" then Archsight::Annotations::EmailRecipient.valid?(string_value)
186
209
  when "Archsight::Annotations::Person" then Archsight::Annotations::Person.valid?(string_value)
210
+ when "Archsight::Annotations::ResourceReference" then Archsight::Annotations::ResourceReference.valid?(string_value)
187
211
  else true
188
212
  end
189
213
  end
@@ -203,6 +227,9 @@ class Archsight::Annotations::Annotation
203
227
  end
204
228
 
205
229
  def derive_format
230
+ return :identifier if unique?
231
+ return :resource if @type == Archsight::Annotations::ResourceReference
232
+
206
233
  case @filter
207
234
  when :word then :tag_word
208
235
  when :list then :tag_list
@@ -4,7 +4,11 @@ require "uri"
4
4
 
5
5
  # Architecture module adds common architecture annotations to resource classes
6
6
  module Archsight::Annotations::Architecture
7
+ # in the order of a resource's life; a resource that sets none is `active` (the annotation's default)
8
+ LIFECYCLE_STATUSES = %w[planned active deprecated sunset end-of-life].freeze
9
+
7
10
  def self.included(base)
11
+ base.include Archsight::Annotations::Links
8
12
  base.class_eval do
9
13
  annotation "architecture/abbr",
10
14
  description: "Abbreviation or short name",
@@ -21,10 +25,53 @@ module Archsight::Annotations::Architecture
21
25
  description: "Documentation URL or reference",
22
26
  title: "Documentation",
23
27
  type: URI
28
+ annotation "architecture/aliases",
29
+ description: "Other identifiers of the resource (comma-separated), e.g. ITGS:A001; " \
30
+ "unique among the resources of a kind",
31
+ title: "Aliases",
32
+ filter: :list,
33
+ sidebar: false,
34
+ unique: true
24
35
  annotation "architecture/tags",
25
36
  description: "Comma-separated tags",
26
37
  filter: :list,
27
38
  title: "Tags"
28
39
  end
40
+ define_lifecycle(base)
41
+ end
42
+
43
+ def self.define_lifecycle(base)
44
+ base.class_eval do
45
+ annotation "lifecycle/status",
46
+ description: "Lifecycle state: planned (not yet in use), active (the default), deprecated (do not " \
47
+ "start new uses), sunset (being phased out) or end-of-life (no longer operated or supported)",
48
+ enum: LIFECYCLE_STATUSES,
49
+ default: "active",
50
+ title: "Lifecycle"
51
+ annotation "lifecycle/activeAt",
52
+ description: "When the resource goes (or went) into use (ISO 8601 date or time)",
53
+ title: "Active at",
54
+ validator: ->(value) { Archsight::Resources::Page.timestamp_error(value) }
55
+ annotation "lifecycle/deprecatedAt",
56
+ description: "When the resource was deprecated (ISO 8601 date or time)",
57
+ title: "Deprecated at",
58
+ validator: ->(value) { Archsight::Resources::Page.timestamp_error(value) }
59
+ annotation "lifecycle/sunsetAt",
60
+ description: "When the resource goes (or went) into sunset (ISO 8601 date or time)",
61
+ title: "Sunset at",
62
+ validator: ->(value) { Archsight::Resources::Page.timestamp_error(value) }
63
+ annotation "lifecycle/endOfLifeAt",
64
+ description: "When the resource reaches (or reached) end-of-life (ISO 8601 date or time)",
65
+ title: "End of life at",
66
+ validator: ->(value) { Archsight::Resources::Page.timestamp_error(value) }
67
+ annotation "lifecycle/replacedBy",
68
+ description: "The resource that replaces this one: a name for the same kind, Kind/Name for another (a component can be replaced by an ApplicationService/...)",
69
+ title: "Replaced by",
70
+ type: Archsight::Annotations::ResourceReference
71
+ annotation "lifecycle/reason",
72
+ description: "Why the resource is deprecated",
73
+ title: "Reason",
74
+ format: :markdown
75
+ end
29
76
  end
30
77
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Links module adds the links of a resource to other systems: `link/<name>` holds a URL, the name says which
4
+ # (confluence, jira, github, ...). A link has no direction; it can be where the resource comes from or where it
5
+ # was published to.
6
+ module Archsight::Annotations::Links
7
+ def self.included(base)
8
+ base.class_eval do
9
+ annotation "link/*",
10
+ description: "Links to the resource in other systems and to related documents (name and URL)",
11
+ sidebar: false,
12
+ validator: ->(value) { "'#{value}' is not an http(s) URL" unless value.match?(%r{\Ahttps?://\S+\z}) }
13
+ end
14
+ end
15
+ end