archsight 0.1.0

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 (122) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +24 -0
  3. data/CODE_OF_CONDUCT.md +10 -0
  4. data/CONTRIBUTING.md +186 -0
  5. data/Dockerfile +39 -0
  6. data/LICENSE.txt +201 -0
  7. data/README.md +170 -0
  8. data/SECURITY.md +27 -0
  9. data/exe/archsight +9 -0
  10. data/lib/archsight/annotations/aggregators.rb +109 -0
  11. data/lib/archsight/annotations/annotation.rb +168 -0
  12. data/lib/archsight/annotations/architecture_annotations.rb +59 -0
  13. data/lib/archsight/annotations/backup_annotations.rb +21 -0
  14. data/lib/archsight/annotations/computed.rb +264 -0
  15. data/lib/archsight/annotations/email_recipient.rb +35 -0
  16. data/lib/archsight/annotations/generated_annotations.rb +17 -0
  17. data/lib/archsight/annotations/git_annotations.rb +21 -0
  18. data/lib/archsight/annotations/relation_resolver.rb +160 -0
  19. data/lib/archsight/cli.rb +120 -0
  20. data/lib/archsight/configuration.rb +36 -0
  21. data/lib/archsight/database.rb +183 -0
  22. data/lib/archsight/documentation.rb +171 -0
  23. data/lib/archsight/graph.rb +113 -0
  24. data/lib/archsight/helpers.rb +210 -0
  25. data/lib/archsight/linter.rb +77 -0
  26. data/lib/archsight/mcp/analyze_resource_tool.rb +222 -0
  27. data/lib/archsight/mcp/base.rb +48 -0
  28. data/lib/archsight/mcp/query_tool.rb +113 -0
  29. data/lib/archsight/mcp/resource_doc_tool.rb +87 -0
  30. data/lib/archsight/mcp.rb +6 -0
  31. data/lib/archsight/query/ast.rb +279 -0
  32. data/lib/archsight/query/errors.rb +39 -0
  33. data/lib/archsight/query/evaluator.rb +707 -0
  34. data/lib/archsight/query/lexer.rb +289 -0
  35. data/lib/archsight/query/parser.rb +506 -0
  36. data/lib/archsight/query.rb +68 -0
  37. data/lib/archsight/renderer.rb +134 -0
  38. data/lib/archsight/resources/application_component.rb +346 -0
  39. data/lib/archsight/resources/application_interface.rb +54 -0
  40. data/lib/archsight/resources/application_service.rb +222 -0
  41. data/lib/archsight/resources/base.rb +300 -0
  42. data/lib/archsight/resources/business_actor.rb +195 -0
  43. data/lib/archsight/resources/business_constraint.rb +32 -0
  44. data/lib/archsight/resources/business_process.rb +37 -0
  45. data/lib/archsight/resources/business_product.rb +206 -0
  46. data/lib/archsight/resources/business_requirement.rb +56 -0
  47. data/lib/archsight/resources/compliance_evidence.rb +42 -0
  48. data/lib/archsight/resources/data_object.rb +49 -0
  49. data/lib/archsight/resources/motivation_goal.rb +37 -0
  50. data/lib/archsight/resources/motivation_outcome.rb +33 -0
  51. data/lib/archsight/resources/motivation_stakeholder.rb +38 -0
  52. data/lib/archsight/resources/strategy_capability.rb +38 -0
  53. data/lib/archsight/resources/technology_artifact.rb +154 -0
  54. data/lib/archsight/resources/technology_interface.rb +34 -0
  55. data/lib/archsight/resources/technology_node.rb +42 -0
  56. data/lib/archsight/resources/technology_service.rb +35 -0
  57. data/lib/archsight/resources/technology_system_software.rb +37 -0
  58. data/lib/archsight/resources/view.rb +51 -0
  59. data/lib/archsight/resources.rb +49 -0
  60. data/lib/archsight/template.rb +49 -0
  61. data/lib/archsight/version.rb +5 -0
  62. data/lib/archsight/web/application.rb +290 -0
  63. data/lib/archsight/web/doc/archimate.md +215 -0
  64. data/lib/archsight/web/doc/computed_annotations.md +316 -0
  65. data/lib/archsight/web/doc/icons.md +303 -0
  66. data/lib/archsight/web/doc/index.md.erb +74 -0
  67. data/lib/archsight/web/doc/modeling.md +200 -0
  68. data/lib/archsight/web/doc/search.md +227 -0
  69. data/lib/archsight/web/doc/togaf.md +255 -0
  70. data/lib/archsight/web/doc/tool.md +90 -0
  71. data/lib/archsight/web/public/css/artifact.css +985 -0
  72. data/lib/archsight/web/public/css/base.css +201 -0
  73. data/lib/archsight/web/public/css/graph.css +106 -0
  74. data/lib/archsight/web/public/css/highlight.min.css +10 -0
  75. data/lib/archsight/web/public/css/iconoir.css +22 -0
  76. data/lib/archsight/web/public/css/instance.css +329 -0
  77. data/lib/archsight/web/public/css/layout.css +421 -0
  78. data/lib/archsight/web/public/css/mermaid-layers.css +188 -0
  79. data/lib/archsight/web/public/css/pico.min.css +4 -0
  80. data/lib/archsight/web/public/favicon.ico +0 -0
  81. data/lib/archsight/web/public/img/archimate.png +0 -0
  82. data/lib/archsight/web/public/img/togaf-high-level.png +0 -0
  83. data/lib/archsight/web/public/js/graph-zoom.js +18 -0
  84. data/lib/archsight/web/public/js/highlight.min.js +3899 -0
  85. data/lib/archsight/web/public/js/htmx.min.js +1 -0
  86. data/lib/archsight/web/public/js/mermaid-init.js +88 -0
  87. data/lib/archsight/web/public/js/mermaid.min.js +2811 -0
  88. data/lib/archsight/web/public/js/sparkline.js +42 -0
  89. data/lib/archsight/web/public/js/svg-pan-zoom.min.js +3 -0
  90. data/lib/archsight/web/public/js/svg-zoom-controls.js +93 -0
  91. data/lib/archsight/web/views/index.haml +12 -0
  92. data/lib/archsight/web/views/partials/artifact/_activity.haml +55 -0
  93. data/lib/archsight/web/views/partials/artifact/_agentic.haml +25 -0
  94. data/lib/archsight/web/views/partials/artifact/_deployment.haml +29 -0
  95. data/lib/archsight/web/views/partials/artifact/_git_info.haml +16 -0
  96. data/lib/archsight/web/views/partials/artifact/_language_stats.haml +53 -0
  97. data/lib/archsight/web/views/partials/artifact/_links.haml +24 -0
  98. data/lib/archsight/web/views/partials/artifact/_project_estimate.haml +26 -0
  99. data/lib/archsight/web/views/partials/artifact/_repositories.haml +55 -0
  100. data/lib/archsight/web/views/partials/artifact/_team.haml +83 -0
  101. data/lib/archsight/web/views/partials/artifact/_workflow.haml +69 -0
  102. data/lib/archsight/web/views/partials/components/_activity.haml +37 -0
  103. data/lib/archsight/web/views/partials/components/_git.haml +17 -0
  104. data/lib/archsight/web/views/partials/components/_jira.haml +18 -0
  105. data/lib/archsight/web/views/partials/components/_languages.haml +29 -0
  106. data/lib/archsight/web/views/partials/components/_owner.haml +15 -0
  107. data/lib/archsight/web/views/partials/components/_repositories.haml +37 -0
  108. data/lib/archsight/web/views/partials/components/_status.haml +23 -0
  109. data/lib/archsight/web/views/partials/instance/_detail.haml +99 -0
  110. data/lib/archsight/web/views/partials/instance/_graph.haml +6 -0
  111. data/lib/archsight/web/views/partials/instance/_list.haml +84 -0
  112. data/lib/archsight/web/views/partials/instance/_relations.haml +43 -0
  113. data/lib/archsight/web/views/partials/instance/_requirements.haml +41 -0
  114. data/lib/archsight/web/views/partials/instance/_view_detail.haml +57 -0
  115. data/lib/archsight/web/views/partials/layout/_content.haml +40 -0
  116. data/lib/archsight/web/views/partials/layout/_error.haml +22 -0
  117. data/lib/archsight/web/views/partials/layout/_head.haml +24 -0
  118. data/lib/archsight/web/views/partials/layout/_navigation.haml +20 -0
  119. data/lib/archsight/web/views/partials/layout/_sidebar.haml +27 -0
  120. data/lib/archsight/web/views/search.haml +53 -0
  121. data/lib/archsight.rb +17 -0
  122. metadata +311 -0
@@ -0,0 +1,346 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ApplicationComponent a part of the ApplicationService
4
+ class Archsight::Resources::ApplicationComponent < Archsight::Resources::Base
5
+ include_annotations :git, :architecture, :generated, :backup
6
+
7
+ description <<~MD
8
+ Represents a logical part of an application service that can be deployed independently.
9
+
10
+ ## ArchiMate Definition
11
+
12
+ **Layer:** Application
13
+ **Aspect:** Active Structure
14
+
15
+ An application component represents an encapsulation of application functionality aligned
16
+ with implementation structure. Components represent the deployable units that together
17
+ form a service.
18
+
19
+ ## Usage
20
+
21
+ Use ApplicationComponent to represent:
22
+
23
+ - Microservices (api-server, worker, scheduler)
24
+ - Backend components
25
+ - Frontend applications
26
+ - Background workers
27
+ MD
28
+
29
+ icon "component"
30
+ layer "application"
31
+
32
+ # Architecture
33
+ annotation "architecture/kind",
34
+ description: "Architecture pattern or style",
35
+ title: "Architecture Kind",
36
+ enum: %w[3-tier hexagonal kubernetes-operator]
37
+ annotation "architecture/size",
38
+ description: "Architecture size classification",
39
+ title: "Architecture Size",
40
+ enum: %w[microservice monolith]
41
+
42
+ # Availability
43
+ annotation "availability/quality",
44
+ description: "Service availability quality target per year",
45
+ title: "Availability",
46
+ enum: ["no goal", "99.5", "99.95", "99.995", "99.9995"]
47
+
48
+ # Frontend
49
+ annotation "frontend/responsiveness",
50
+ description: "Frontend 99th percentile responsiveness target",
51
+ title: "Frontend Responsiveness (99p)",
52
+ enum: %w[10ms 100ms 1000ms 2s 5s 10s unresponsive]
53
+
54
+ # Persistence
55
+ annotation "persistence/provider",
56
+ description: "Persistence provider type",
57
+ title: "Persistence Provider",
58
+ enum: %w[self-hosted managed-service]
59
+ annotation "persistence/confidential",
60
+ description: "Persistence data confidentiality level",
61
+ title: "Persistence Confidentiality",
62
+ enum: %w[none simple-full-disk per-customer-key rotated-per-customer-key customer-provided-key]
63
+ annotation "persistence/failover",
64
+ description: "Persistence failover configuration",
65
+ title: "Persistence Failover",
66
+ enum: %w[none cold-standby hot-standby]
67
+
68
+ # Audit
69
+ annotation "audit/logging",
70
+ description: "Audit logging destination",
71
+ title: "Audit Logging",
72
+ filter: :word
73
+ annotation "audit/activity",
74
+ description: "Audit activity reliability",
75
+ title: "Audit Activity",
76
+ enum: %w[reliable unreliable]
77
+
78
+ # Security
79
+ annotation "security/handling",
80
+ description: "Security credential handling approach",
81
+ title: "Security Handling",
82
+ enum: ["none", "static-tokens", "revokable", "dynamic-tokens", "work/identity separation"]
83
+ annotation "security/kill-switch",
84
+ description: "Security kill-switch capability",
85
+ title: "Security Kill Switch",
86
+ filter: :word
87
+
88
+ # Abuse
89
+ annotation "abuse/query",
90
+ description: "Abuse query mechanism",
91
+ title: "Abuse Query",
92
+ enum: %w[none manual api]
93
+ annotation "abuse/response",
94
+ description: "Abuse response mechanism",
95
+ title: "Abuse Response",
96
+ enum: %w[none manual api]
97
+
98
+ # Lawful Interception
99
+ annotation "lawful/interception",
100
+ description: "Lawful interception handling capability",
101
+ title: "Lawful Interception",
102
+ enum: %w[none manual api]
103
+
104
+ # Configuration
105
+ annotation "config/handling",
106
+ description: "Configuration handling approach",
107
+ title: "Config Handling",
108
+ enum: ["no configuration", "configuration via file unencrypted secrets",
109
+ "config via ENV encrypted secrets"]
110
+
111
+ # Fault Tolerance
112
+ annotation "faultTolerance/quality",
113
+ description: "Fault tolerance quality level",
114
+ title: "Fault Tolerance",
115
+ enum: ["no fault tolerance", "survive server failure", "survive rack failure", "survive DC failure",
116
+ "regional architecture"]
117
+
118
+ # Supportability
119
+ annotation "supportability/quality",
120
+ description: "Supportability quality level",
121
+ title: "Supportability",
122
+ enum: ["no support", "support by dev", "support by ops", "support by api",
123
+ "self service support via a frontend"]
124
+
125
+ # Deployment (from ArgoCD/cluster)
126
+ annotation "deployment/images",
127
+ description: "Deployed container images",
128
+ title: "Container Images",
129
+ sidebar: false,
130
+ filter: :list
131
+ annotation "deployment/chart",
132
+ description: "Deployed Helm chart name",
133
+ title: "Helm Chart",
134
+ sidebar: false,
135
+ filter: :word
136
+ annotation "deployment/namespace",
137
+ description: "Kubernetes namespace",
138
+ title: "Namespace",
139
+ sidebar: false,
140
+ filter: :word
141
+ annotation "deployment/cluster",
142
+ description: "Target cluster name",
143
+ title: "Cluster",
144
+ filter: :word
145
+
146
+ # Computed Annotations
147
+ computed_annotation "repository/artifacts/total",
148
+ title: "Total Git Repositories",
149
+ description: "Number of related git repositories",
150
+ type: Integer do
151
+ count(outgoing_transitive('TechnologyArtifact: artifact/type == "repo"'))
152
+ end
153
+
154
+ computed_annotation "repository/artifacts/active",
155
+ title: "Active Git Repositories",
156
+ description: "Number of related git repositories",
157
+ type: Integer do
158
+ count(outgoing_transitive('TechnologyArtifact: artifact/type == "repo" & activity/status == "active"'))
159
+ end
160
+
161
+ computed_annotation "repository/artifacts/abandoned",
162
+ title: "Abandoned Git Repositories",
163
+ description: "Number of related git repositories",
164
+ type: Integer do
165
+ count(outgoing_transitive('TechnologyArtifact: artifact/type == "repo" & activity/status == "abandoned"'))
166
+ end
167
+
168
+ computed_annotation "repository/artifacts/highBusFactor",
169
+ title: "High Bus Factor Repositories",
170
+ description: "Number of active git repositories with high bus factor",
171
+ type: Integer do
172
+ count(outgoing_transitive('TechnologyArtifact: artifact/type == "repo" & activity/status == "active" & activity/busFactor == "high"'))
173
+ end
174
+
175
+ computed_annotation "repository/artifacts/archived",
176
+ title: "Archived Repositories",
177
+ description: "Number of archived git repositories",
178
+ type: Integer do
179
+ count(outgoing_transitive('TechnologyArtifact: artifact/type == "repo" & activity/status == "archived"'))
180
+ end
181
+
182
+ %w[scc/estimatedCost scc/estimatedScheduleMonths scc/estimatedPeople].each do |anno_key|
183
+ computed_annotation anno_key,
184
+ title: "Total #{anno_key.split("/").last.split(/(?=[A-Z])/).map(&:capitalize).join(" ")}",
185
+ description: "Total estimated #{anno_key.split("/").last} from related artifacts",
186
+ type: Integer do
187
+ sum(outgoing_transitive(:TechnologyArtifact), anno_key)
188
+ end
189
+ end
190
+
191
+ computed_annotation "scc/language",
192
+ title: "Primary Language",
193
+ list: true,
194
+ description: "Programming language with most lines of code across related artifacts",
195
+ filter: :word,
196
+ sidebar: true do
197
+ # Aggregate LOC per language across all related artifacts
198
+ artifacts = outgoing_transitive(:TechnologyArtifact)
199
+ loc_by_language = Hash.new(0)
200
+
201
+ artifacts.each do |artifact|
202
+ artifact.annotations.each do |key, value|
203
+ # Match annotations like scc/language/Java/loc
204
+ if key =~ %r{^scc/language/(.+)/loc$}
205
+ language = ::Regexp.last_match(1)
206
+ loc_by_language[language] += value.to_i
207
+ end
208
+ end
209
+ end
210
+
211
+ # Return the language with the most LOC
212
+ loc_by_language.max_by { |_, loc| loc }&.first
213
+ end
214
+
215
+ computed_annotation "activity/commits",
216
+ title: "Monthly Commits",
217
+ description: "Accumulated monthly commit counts across all related repositories (excluding community repos)",
218
+ sidebar: false do
219
+ artifacts = outgoing_transitive('TechnologyArtifact: artifact/type == "repo"')
220
+ next nil if artifacts.empty?
221
+
222
+ # Collect all commit arrays (they all end at the current month)
223
+ # Skip community repos - their commit data reflects upstream project activity, not IONOS work
224
+ commit_arrays = artifacts.map do |artifact|
225
+ next nil if artifact.annotations["repository/visibility"] == "open-source"
226
+
227
+ commits_str = artifact.annotations["activity/commits"]
228
+ next nil if commits_str.nil? || commits_str.empty?
229
+
230
+ commits_str.split(",").map(&:to_i)
231
+ end.compact
232
+
233
+ next nil if commit_arrays.empty?
234
+
235
+ # Find the maximum length (oldest repo determines the timeline)
236
+ max_length = commit_arrays.map(&:length).max
237
+
238
+ # Pad shorter arrays at the beginning with zeros and sum
239
+ result = Array.new(max_length, 0)
240
+ commit_arrays.each do |arr|
241
+ offset = max_length - arr.length
242
+ arr.each_with_index do |count, idx|
243
+ result[offset + idx] += count
244
+ end
245
+ end
246
+
247
+ result.join(",")
248
+ end
249
+
250
+ computed_annotation "activity/createdAt",
251
+ title: "Created",
252
+ description: "Earliest repository creation date across all related repositories (excluding community repos)",
253
+ type: Time do
254
+ artifacts = outgoing_transitive('TechnologyArtifact: artifact/type == "repo"')
255
+ next nil if artifacts.empty?
256
+
257
+ # Skip community repos - their creation dates reflect upstream project history, not IONOS work
258
+ dates = artifacts.map do |artifact|
259
+ next nil if artifact.annotations["repository/visibility"] == "open-source"
260
+
261
+ date_str = artifact.annotations["activity/createdAt"]
262
+ next nil if date_str.nil? || date_str.empty?
263
+
264
+ begin
265
+ Time.parse(date_str)
266
+ rescue StandardError
267
+ nil
268
+ end
269
+ end.compact
270
+
271
+ dates.min
272
+ end
273
+
274
+ computed_annotation "activity/contributors",
275
+ title: "Monthly Contributors",
276
+ description: "Accumulated monthly unique contributor counts across all related repositories (excluding community repos)",
277
+ sidebar: false do
278
+ artifacts = outgoing_transitive('TechnologyArtifact: artifact/type == "repo"')
279
+ next nil if artifacts.empty?
280
+
281
+ # Collect all contributor arrays (they all end at the current month)
282
+ # Skip community repos - their contributor data reflects upstream project, not IONOS team size
283
+ contrib_arrays = artifacts.map do |artifact|
284
+ next nil if artifact.annotations["repository/visibility"] == "open-source"
285
+
286
+ contrib_str = artifact.annotations["activity/contributors"]
287
+ next nil if contrib_str.nil? || contrib_str.empty?
288
+
289
+ contrib_str.split(",").map(&:to_i)
290
+ end.compact
291
+
292
+ next nil if contrib_arrays.empty?
293
+
294
+ # Find the maximum length (oldest repo determines the timeline)
295
+ max_length = contrib_arrays.map(&:length).max
296
+
297
+ # Pad shorter arrays at the beginning with zeros and sum
298
+ result = Array.new(max_length, 0)
299
+ contrib_arrays.each do |arr|
300
+ offset = max_length - arr.length
301
+ arr.each_with_index do |count, idx|
302
+ result[offset + idx] += count
303
+ end
304
+ end
305
+
306
+ result.join(",")
307
+ end
308
+
309
+ computed_annotation "activity/contributors/6m",
310
+ title: "Contributors (6 months)",
311
+ description: "Sum of unique contributors in the last 6 months across related repositories",
312
+ type: Integer do
313
+ artifacts = outgoing_transitive('TechnologyArtifact: artifact/type == "repo"')
314
+ next nil if artifacts.empty?
315
+
316
+ # Sum unique contributor counts from related artifacts (excluding open-source repos)
317
+ total = artifacts.sum do |artifact|
318
+ next 0 if artifact.annotations["repository/visibility"] == "open-source"
319
+
320
+ artifact.annotations["activity/contributors/6m"].to_i
321
+ end
322
+ total.positive? ? total : nil
323
+ end
324
+
325
+ computed_annotation "activity/contributors/total",
326
+ title: "Contributors (total)",
327
+ description: "Sum of unique contributors across related repositories",
328
+ type: Integer do
329
+ artifacts = outgoing_transitive('TechnologyArtifact: artifact/type == "repo"')
330
+ next nil if artifacts.empty?
331
+
332
+ # Sum unique contributor counts from related artifacts (excluding open-source repos)
333
+ total = artifacts.sum do |artifact|
334
+ next 0 if artifact.annotations["repository/visibility"] == "open-source"
335
+
336
+ artifact.annotations["activity/contributors/total"].to_i
337
+ end
338
+ total.positive? ? total : nil
339
+ end
340
+
341
+ relation :realizedThrough, :technologyArtifacts, :TechnologyArtifact
342
+ relation :realizedBy, :technologyComponents, :TechnologyNode
343
+ relation :servedBy, :technologyComponents, :TechnologySystemSoftware
344
+ relation :exposes, :applicationInterfaces, :ApplicationInterface
345
+ relation :dependsOn, :applicationInterfaces, :ApplicationInterface
346
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ApplicationInterface between ApplicationComponent
4
+ class Archsight::Resources::ApplicationInterface < Archsight::Resources::Base
5
+ include_annotations :git, :architecture, :generated
6
+
7
+ description <<~MD
8
+ Represents a point of access where application services are made available.
9
+
10
+ ## ArchiMate Definition
11
+
12
+ **Layer:** Application
13
+ **Aspect:** Active Structure (external)
14
+
15
+ An application interface represents a point of access where application services
16
+ are made available to a user, another application component, or a node. It exposes
17
+ application behavior to the environment.
18
+
19
+ ## Usage
20
+
21
+ Use ApplicationInterface to represent:
22
+
23
+ - REST APIs
24
+ - GraphQL endpoints
25
+ - gRPC services
26
+ - Message queues (producer/consumer interfaces)
27
+ - WebSocket endpoints
28
+ MD
29
+
30
+ icon "usb"
31
+ layer "application"
32
+
33
+ # API
34
+ annotation "api/responsiveness",
35
+ description: "API 99th percentile responsiveness target",
36
+ title: "API Responsiveness (99p)",
37
+ enum: %w[10ms 100ms 1000ms 2s 5s 10s unresponsive]
38
+ annotation "api/authenticationMethod",
39
+ description: "API authentication method",
40
+ title: "API Authentication Method",
41
+ enum: ["none", "hard coded", "token", "oidc"]
42
+ annotation "api/authenticationProvider",
43
+ description: "API authentication provider",
44
+ title: "API Authentication Provider",
45
+ enum: %w[eiam cloud custom]
46
+ annotation "api/authorization",
47
+ description: "API authorization mechanism",
48
+ title: "API Authorization",
49
+ enum: ["none", "hard coded", "pbac", "abac", "rbac"]
50
+
51
+ relation :servedBy, :technologyComponents, :TechnologyInterface
52
+ relation :realizes, :businessConstraints, :BusinessConstraint
53
+ relation :serves, :dataObjects, :DataObject
54
+ end
@@ -0,0 +1,222 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ApplicationService represents the high level application service that implements capabilities
4
+ class Archsight::Resources::ApplicationService < Archsight::Resources::Base
5
+ include_annotations :git, :architecture, :generated, :backup
6
+
7
+ description <<~MD
8
+ Represents a high-level application service that implements business capabilities.
9
+
10
+ ## ArchiMate Definition
11
+
12
+ **Layer:** Application
13
+ **Aspect:** Behavior
14
+
15
+ An application service represents an explicitly defined exposed application behavior.
16
+ It is the externally visible functionality provided by application components, grouping
17
+ multiple components that work together to deliver business value.
18
+
19
+ ## Usage
20
+
21
+ Use ApplicationService to represent:
22
+
23
+ - Complete product offerings (ManagedKubernetes, DBaaS PostgreSQL)
24
+ - Logical groupings of application components
25
+ - Services exposed to business processes
26
+ - APIs and their implementations as a cohesive unit
27
+ MD
28
+
29
+ icon "cube"
30
+ layer "application"
31
+
32
+ # Service plane classification
33
+ annotation "architecture/plane",
34
+ description: "Service plane classification (control manages resources, data handles traffic)",
35
+ title: "Service Plane",
36
+ enum: %w[control data],
37
+ list: true
38
+
39
+ # Computed Annotations
40
+ computed_annotation "repository/artifacts/total",
41
+ title: "Total Git Repositories",
42
+ description: "Number of related git repositories across all related application components",
43
+ type: Integer do
44
+ sum(outgoing_transitive(:ApplicationComponent), "repository/artifacts/total")
45
+ end
46
+
47
+ computed_annotation "repository/artifacts/active",
48
+ title: "Active Git Repositories",
49
+ description: "Number of related git repositories across all related application components",
50
+ type: Integer do
51
+ sum(outgoing_transitive(:ApplicationComponent), "repository/artifacts/active")
52
+ end
53
+
54
+ computed_annotation "repository/artifacts/abandoned",
55
+ title: "Abandoned Git Repositories",
56
+ description: "Number of related git repositories across all related application components",
57
+ type: Integer do
58
+ sum(outgoing_transitive(:ApplicationComponent), "repository/artifacts/abandoned")
59
+ end
60
+
61
+ computed_annotation "repository/artifacts/highBusFactor",
62
+ title: "High Bus Factor Repositories",
63
+ description: "Number of active git repositories with high bus factor across all related application components",
64
+ type: Integer do
65
+ sum(outgoing_transitive(:ApplicationComponent), "repository/artifacts/highBusFactor")
66
+ end
67
+
68
+ computed_annotation "repository/artifacts/archived",
69
+ title: "Archived Repositories",
70
+ description: "Number of archived git repositories across all related application components",
71
+ type: Integer do
72
+ sum(outgoing_transitive(:ApplicationComponent), "repository/artifacts/archived")
73
+ end
74
+
75
+ %w[scc/estimatedCost scc/estimatedScheduleMonths scc/estimatedPeople].each do |anno_key|
76
+ computed_annotation anno_key,
77
+ title: "Total #{anno_key.split("/").last.split(/(?=[A-Z])/).map(&:capitalize).join(" ")}",
78
+ description: "Total estimated #{anno_key.split("/").last} from related artifacts across all related application components",
79
+ type: Integer do
80
+ sum(outgoing_transitive(:ApplicationComponent), anno_key)
81
+ end
82
+ end
83
+
84
+ computed_annotation "scc/languages",
85
+ title: "Primary Languages",
86
+ list: true,
87
+ description: "Top 4 programming languages by lines of code across related artifacts",
88
+ filter: :list,
89
+ sidebar: true do
90
+ collect(outgoing_transitive(:ApplicationComponent), "scc/language")
91
+ end
92
+
93
+ computed_annotation "activity/commits",
94
+ title: "Monthly Commits",
95
+ description: "Accumulated monthly commit counts across all related application components",
96
+ sidebar: false do
97
+ components = outgoing_transitive(:ApplicationComponent)
98
+ next nil if components.empty?
99
+
100
+ # Collect all commit arrays from components (they all end at the current month)
101
+ # Use get() to trigger computation of computed annotations on ApplicationComponents
102
+ commit_arrays = components.map do |component|
103
+ commits_str = get(component, "activity/commits")
104
+ next nil if commits_str.nil? || commits_str.empty?
105
+
106
+ commits_str.split(",").map(&:to_i)
107
+ end.compact
108
+
109
+ next nil if commit_arrays.empty?
110
+
111
+ # Find the maximum length (oldest component determines the timeline)
112
+ max_length = commit_arrays.map(&:length).max
113
+
114
+ # Pad shorter arrays at the beginning with zeros and sum
115
+ result = Array.new(max_length, 0)
116
+ commit_arrays.each do |arr|
117
+ offset = max_length - arr.length
118
+ arr.each_with_index do |count, idx|
119
+ result[offset + idx] += count
120
+ end
121
+ end
122
+
123
+ result.join(",")
124
+ end
125
+
126
+ computed_annotation "activity/createdAt",
127
+ title: "Created",
128
+ description: "Earliest repository creation date across all related application components",
129
+ type: Time do
130
+ components = outgoing_transitive(:ApplicationComponent)
131
+ next nil if components.empty?
132
+
133
+ # Use get() to trigger computation of computed annotations on ApplicationComponents
134
+ dates = components.map do |component|
135
+ date_val = get(component, "activity/createdAt")
136
+ next nil if date_val.nil?
137
+
138
+ if date_val.is_a?(Time)
139
+ date_val
140
+ else
141
+ begin
142
+ Time.parse(date_val.to_s)
143
+ rescue StandardError
144
+ nil
145
+ end
146
+ end
147
+ end.compact
148
+
149
+ dates.min
150
+ end
151
+
152
+ computed_annotation "activity/contributors",
153
+ title: "Monthly Contributors",
154
+ description: "Accumulated monthly unique contributor counts across all related application components",
155
+ sidebar: false do
156
+ components = outgoing_transitive(:ApplicationComponent)
157
+ next nil if components.empty?
158
+
159
+ # Collect all contributor arrays from components (they all end at the current month)
160
+ # Use get() to trigger computation of computed annotations on ApplicationComponents
161
+ contrib_arrays = components.map do |component|
162
+ contrib_str = get(component, "activity/contributors")
163
+ next nil if contrib_str.nil? || contrib_str.empty?
164
+
165
+ contrib_str.split(",").map(&:to_i)
166
+ end.compact
167
+
168
+ next nil if contrib_arrays.empty?
169
+
170
+ # Find the maximum length (oldest component determines the timeline)
171
+ max_length = contrib_arrays.map(&:length).max
172
+
173
+ # Pad shorter arrays at the beginning with zeros and sum
174
+ result = Array.new(max_length, 0)
175
+ contrib_arrays.each do |arr|
176
+ offset = max_length - arr.length
177
+ arr.each_with_index do |count, idx|
178
+ result[offset + idx] += count
179
+ end
180
+ end
181
+
182
+ result.join(",")
183
+ end
184
+
185
+ computed_annotation "activity/contributors/6m",
186
+ title: "Contributors (6 months)",
187
+ description: "Sum of unique contributors in the last 6 months across related components",
188
+ type: Integer do
189
+ components = outgoing_transitive(:ApplicationComponent)
190
+ next nil if components.empty?
191
+
192
+ # Sum unique contributor counts from related components
193
+ total = components.sum do |component|
194
+ get(component, "activity/contributors/6m").to_i
195
+ end
196
+ total.positive? ? total : nil
197
+ end
198
+
199
+ computed_annotation "activity/contributors/total",
200
+ title: "Contributors (total)",
201
+ description: "Sum of unique contributors across related components",
202
+ type: Integer do
203
+ components = outgoing_transitive(:ApplicationComponent)
204
+ next nil if components.empty?
205
+
206
+ # Sum unique contributor counts from related components
207
+ total = components.sum do |component|
208
+ get(component, "activity/contributors/total").to_i
209
+ end
210
+ total.positive? ? total : nil
211
+ end
212
+
213
+ relation :realizedThrough, :applicationComponents, :ApplicationComponent
214
+ relation :servedBy, :businessActors, :BusinessActor
215
+ relation :servedBy, :technologyServices, :TechnologyService
216
+ relation :realizes, :businessConstraints, :BusinessConstraint
217
+ relation :realizes, :businessRequirements, :BusinessRequirement
218
+ relation :partiallyRealizes, :businessRequirements, :BusinessRequirement
219
+ relation :plans, :businessRequirements, :BusinessRequirement
220
+ relation :realizes, :dataObjects, :DataObject
221
+ relation :evidencedBy, :complianceEvidences, :ComplianceEvidence
222
+ end