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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +24 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/CONTRIBUTING.md +186 -0
- data/Dockerfile +39 -0
- data/LICENSE.txt +201 -0
- data/README.md +170 -0
- data/SECURITY.md +27 -0
- data/exe/archsight +9 -0
- data/lib/archsight/annotations/aggregators.rb +109 -0
- data/lib/archsight/annotations/annotation.rb +168 -0
- data/lib/archsight/annotations/architecture_annotations.rb +59 -0
- data/lib/archsight/annotations/backup_annotations.rb +21 -0
- data/lib/archsight/annotations/computed.rb +264 -0
- data/lib/archsight/annotations/email_recipient.rb +35 -0
- data/lib/archsight/annotations/generated_annotations.rb +17 -0
- data/lib/archsight/annotations/git_annotations.rb +21 -0
- data/lib/archsight/annotations/relation_resolver.rb +160 -0
- data/lib/archsight/cli.rb +120 -0
- data/lib/archsight/configuration.rb +36 -0
- data/lib/archsight/database.rb +183 -0
- data/lib/archsight/documentation.rb +171 -0
- data/lib/archsight/graph.rb +113 -0
- data/lib/archsight/helpers.rb +210 -0
- data/lib/archsight/linter.rb +77 -0
- data/lib/archsight/mcp/analyze_resource_tool.rb +222 -0
- data/lib/archsight/mcp/base.rb +48 -0
- data/lib/archsight/mcp/query_tool.rb +113 -0
- data/lib/archsight/mcp/resource_doc_tool.rb +87 -0
- data/lib/archsight/mcp.rb +6 -0
- data/lib/archsight/query/ast.rb +279 -0
- data/lib/archsight/query/errors.rb +39 -0
- data/lib/archsight/query/evaluator.rb +707 -0
- data/lib/archsight/query/lexer.rb +289 -0
- data/lib/archsight/query/parser.rb +506 -0
- data/lib/archsight/query.rb +68 -0
- data/lib/archsight/renderer.rb +134 -0
- data/lib/archsight/resources/application_component.rb +346 -0
- data/lib/archsight/resources/application_interface.rb +54 -0
- data/lib/archsight/resources/application_service.rb +222 -0
- data/lib/archsight/resources/base.rb +300 -0
- data/lib/archsight/resources/business_actor.rb +195 -0
- data/lib/archsight/resources/business_constraint.rb +32 -0
- data/lib/archsight/resources/business_process.rb +37 -0
- data/lib/archsight/resources/business_product.rb +206 -0
- data/lib/archsight/resources/business_requirement.rb +56 -0
- data/lib/archsight/resources/compliance_evidence.rb +42 -0
- data/lib/archsight/resources/data_object.rb +49 -0
- data/lib/archsight/resources/motivation_goal.rb +37 -0
- data/lib/archsight/resources/motivation_outcome.rb +33 -0
- data/lib/archsight/resources/motivation_stakeholder.rb +38 -0
- data/lib/archsight/resources/strategy_capability.rb +38 -0
- data/lib/archsight/resources/technology_artifact.rb +154 -0
- data/lib/archsight/resources/technology_interface.rb +34 -0
- data/lib/archsight/resources/technology_node.rb +42 -0
- data/lib/archsight/resources/technology_service.rb +35 -0
- data/lib/archsight/resources/technology_system_software.rb +37 -0
- data/lib/archsight/resources/view.rb +51 -0
- data/lib/archsight/resources.rb +49 -0
- data/lib/archsight/template.rb +49 -0
- data/lib/archsight/version.rb +5 -0
- data/lib/archsight/web/application.rb +290 -0
- data/lib/archsight/web/doc/archimate.md +215 -0
- data/lib/archsight/web/doc/computed_annotations.md +316 -0
- data/lib/archsight/web/doc/icons.md +303 -0
- data/lib/archsight/web/doc/index.md.erb +74 -0
- data/lib/archsight/web/doc/modeling.md +200 -0
- data/lib/archsight/web/doc/search.md +227 -0
- data/lib/archsight/web/doc/togaf.md +255 -0
- data/lib/archsight/web/doc/tool.md +90 -0
- data/lib/archsight/web/public/css/artifact.css +985 -0
- data/lib/archsight/web/public/css/base.css +201 -0
- data/lib/archsight/web/public/css/graph.css +106 -0
- data/lib/archsight/web/public/css/highlight.min.css +10 -0
- data/lib/archsight/web/public/css/iconoir.css +22 -0
- data/lib/archsight/web/public/css/instance.css +329 -0
- data/lib/archsight/web/public/css/layout.css +421 -0
- data/lib/archsight/web/public/css/mermaid-layers.css +188 -0
- data/lib/archsight/web/public/css/pico.min.css +4 -0
- data/lib/archsight/web/public/favicon.ico +0 -0
- data/lib/archsight/web/public/img/archimate.png +0 -0
- data/lib/archsight/web/public/img/togaf-high-level.png +0 -0
- data/lib/archsight/web/public/js/graph-zoom.js +18 -0
- data/lib/archsight/web/public/js/highlight.min.js +3899 -0
- data/lib/archsight/web/public/js/htmx.min.js +1 -0
- data/lib/archsight/web/public/js/mermaid-init.js +88 -0
- data/lib/archsight/web/public/js/mermaid.min.js +2811 -0
- data/lib/archsight/web/public/js/sparkline.js +42 -0
- data/lib/archsight/web/public/js/svg-pan-zoom.min.js +3 -0
- data/lib/archsight/web/public/js/svg-zoom-controls.js +93 -0
- data/lib/archsight/web/views/index.haml +12 -0
- data/lib/archsight/web/views/partials/artifact/_activity.haml +55 -0
- data/lib/archsight/web/views/partials/artifact/_agentic.haml +25 -0
- data/lib/archsight/web/views/partials/artifact/_deployment.haml +29 -0
- data/lib/archsight/web/views/partials/artifact/_git_info.haml +16 -0
- data/lib/archsight/web/views/partials/artifact/_language_stats.haml +53 -0
- data/lib/archsight/web/views/partials/artifact/_links.haml +24 -0
- data/lib/archsight/web/views/partials/artifact/_project_estimate.haml +26 -0
- data/lib/archsight/web/views/partials/artifact/_repositories.haml +55 -0
- data/lib/archsight/web/views/partials/artifact/_team.haml +83 -0
- data/lib/archsight/web/views/partials/artifact/_workflow.haml +69 -0
- data/lib/archsight/web/views/partials/components/_activity.haml +37 -0
- data/lib/archsight/web/views/partials/components/_git.haml +17 -0
- data/lib/archsight/web/views/partials/components/_jira.haml +18 -0
- data/lib/archsight/web/views/partials/components/_languages.haml +29 -0
- data/lib/archsight/web/views/partials/components/_owner.haml +15 -0
- data/lib/archsight/web/views/partials/components/_repositories.haml +37 -0
- data/lib/archsight/web/views/partials/components/_status.haml +23 -0
- data/lib/archsight/web/views/partials/instance/_detail.haml +99 -0
- data/lib/archsight/web/views/partials/instance/_graph.haml +6 -0
- data/lib/archsight/web/views/partials/instance/_list.haml +84 -0
- data/lib/archsight/web/views/partials/instance/_relations.haml +43 -0
- data/lib/archsight/web/views/partials/instance/_requirements.haml +41 -0
- data/lib/archsight/web/views/partials/instance/_view_detail.haml +57 -0
- data/lib/archsight/web/views/partials/layout/_content.haml +40 -0
- data/lib/archsight/web/views/partials/layout/_error.haml +22 -0
- data/lib/archsight/web/views/partials/layout/_head.haml +24 -0
- data/lib/archsight/web/views/partials/layout/_navigation.haml +20 -0
- data/lib/archsight/web/views/partials/layout/_sidebar.haml +27 -0
- data/lib/archsight/web/views/search.haml +53 -0
- data/lib/archsight.rb +17 -0
- metadata +311 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
- instance = db.instance_by_kind(@kind, @instance)
|
|
2
|
+
- return unless instance
|
|
3
|
+
- platforms = (instance.annotations['workflow/platforms'] || 'none').split(',').map(&:strip)
|
|
4
|
+
- types = (instance.annotations['workflow/types'] || 'none').split(',').map(&:strip)
|
|
5
|
+
|
|
6
|
+
- if platforms != ['none'] || types != ['none']
|
|
7
|
+
- has_generic_test = types.include?('test')
|
|
8
|
+
%tr
|
|
9
|
+
%th{scope: 'row'} CI/CD Platforms
|
|
10
|
+
%td
|
|
11
|
+
.workflow-platforms
|
|
12
|
+
%a.workflow-platform-item{**filter_link_attrs('workflow/platforms', 'github-actions')}
|
|
13
|
+
%input{type: 'checkbox', disabled: true, checked: platforms.include?('github-actions')}
|
|
14
|
+
%label GitHub Actions
|
|
15
|
+
%a.workflow-platform-item{**filter_link_attrs('workflow/platforms', 'gitlab-ci')}
|
|
16
|
+
%input{type: 'checkbox', disabled: true, checked: platforms.include?('gitlab-ci')}
|
|
17
|
+
%label GitLab CI
|
|
18
|
+
%a.workflow-platform-item{**filter_link_attrs('workflow/platforms', 'makefile')}
|
|
19
|
+
%input{type: 'checkbox', disabled: true, checked: platforms.include?('makefile')}
|
|
20
|
+
%label Makefile
|
|
21
|
+
%tr
|
|
22
|
+
%td.info-section-cell{colspan: 2}
|
|
23
|
+
%details{open: true}
|
|
24
|
+
%summary
|
|
25
|
+
%strong Workflow Types
|
|
26
|
+
.workflow-types
|
|
27
|
+
%div
|
|
28
|
+
.workflow-category
|
|
29
|
+
%strong Build & Deploy
|
|
30
|
+
%ul
|
|
31
|
+
%li
|
|
32
|
+
%input{type: 'checkbox', disabled: true, checked: types.include?('build')}
|
|
33
|
+
%a{**filter_link_attrs('workflow/types', 'build')} Build
|
|
34
|
+
%li
|
|
35
|
+
%input{type: 'checkbox', disabled: true, checked: types.include?('deploy')}
|
|
36
|
+
%a{**filter_link_attrs('workflow/types', 'deploy')} Deploy
|
|
37
|
+
.workflow-category
|
|
38
|
+
%strong Testing
|
|
39
|
+
%ul
|
|
40
|
+
%li
|
|
41
|
+
%input{type: 'checkbox', disabled: true, checked: types.include?('test')}
|
|
42
|
+
%a{**filter_link_attrs('workflow/types', 'test')} Test (generic)
|
|
43
|
+
%li{class: has_generic_test ? 'grayed-out' : ''}
|
|
44
|
+
%input{type: 'checkbox', disabled: true, checked: types.include?('unit-test')}
|
|
45
|
+
%a{**filter_link_attrs('workflow/types', 'unit-test')} Unit Tests
|
|
46
|
+
%li{class: has_generic_test ? 'grayed-out' : ''}
|
|
47
|
+
%input{type: 'checkbox', disabled: true, checked: types.include?('integration-test')}
|
|
48
|
+
%a{**filter_link_attrs('workflow/types', 'integration-test')} Integration Tests
|
|
49
|
+
%li{class: has_generic_test ? 'grayed-out' : ''}
|
|
50
|
+
%input{type: 'checkbox', disabled: true, checked: types.include?('smoke-test')}
|
|
51
|
+
%a{**filter_link_attrs('workflow/types', 'smoke-test')} Smoke Tests
|
|
52
|
+
.workflow-category
|
|
53
|
+
%strong Quality & Security
|
|
54
|
+
%ul
|
|
55
|
+
%li
|
|
56
|
+
%input{type: 'checkbox', disabled: true, checked: types.include?('lint')}
|
|
57
|
+
%a{**filter_link_attrs('workflow/types', 'lint')} Linting
|
|
58
|
+
%li
|
|
59
|
+
%input{type: 'checkbox', disabled: true, checked: types.include?('security-scan')}
|
|
60
|
+
%a{**filter_link_attrs('workflow/types', 'security-scan')} Security Scan
|
|
61
|
+
.workflow-category
|
|
62
|
+
%strong Automation
|
|
63
|
+
%ul
|
|
64
|
+
%li
|
|
65
|
+
%input{type: 'checkbox', disabled: true, checked: types.include?('dependency-update')}
|
|
66
|
+
%a{**filter_link_attrs("workflow/types", "dependency-update")} Dependency Updates
|
|
67
|
+
%li
|
|
68
|
+
%input{ type: "checkbox", disabled: true, checked: types.include?("ticket-creation") }
|
|
69
|
+
%a{ **filter_link_attrs("workflow/types", "ticket-creation") } Ticket Creation
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
-# Activity component - commits sparkline + trend (last 12 months)
|
|
2
|
+
- annotations = instance.annotations
|
|
3
|
+
- commits = annotations['activity/commits']
|
|
4
|
+
- status = annotations['activity/status']
|
|
5
|
+
- bus_factor = annotations['activity/busFactor']
|
|
6
|
+
- contributors_6m = annotations['activity/contributors/6m']
|
|
7
|
+
- if commits || status
|
|
8
|
+
- commit_values = commits ? commits.split(',').map(&:to_i).last(12) : []
|
|
9
|
+
- total_commits = commit_values.sum
|
|
10
|
+
-# Calculate trend: compare last 3 months vs previous 3 months
|
|
11
|
+
- recent_3m = commit_values.last(3).sum
|
|
12
|
+
- previous_3m = commit_values[-6, 3]&.sum || 0
|
|
13
|
+
- if previous_3m > 0
|
|
14
|
+
- trend_pct = ((recent_3m - previous_3m).to_f / previous_3m * 100).round
|
|
15
|
+
- else
|
|
16
|
+
- trend_pct = recent_3m > 0 ? 100 : 0
|
|
17
|
+
- trend_class = trend_pct > 10 ? 'positive' : (trend_pct < -10 ? 'negative' : 'neutral')
|
|
18
|
+
- tooltip_parts = []
|
|
19
|
+
- tooltip_parts << "#{total_commits} commits (12m)" if total_commits > 0
|
|
20
|
+
- tooltip_parts << "trend: #{recent_3m} vs #{previous_3m} (3m)"
|
|
21
|
+
- tooltip_parts << "#{contributors_6m} contributors" if contributors_6m
|
|
22
|
+
- tooltip_parts << "status: #{status}" if status
|
|
23
|
+
- tooltip_parts << "bus factor: #{bus_factor}" if bus_factor
|
|
24
|
+
- tooltip = tooltip_parts.join(', ')
|
|
25
|
+
.component-activity{title: tooltip}
|
|
26
|
+
- if commits && !commits.empty?
|
|
27
|
+
.activity-sparkline.sparkline-sm{'data-values' => commit_values.join(',')}
|
|
28
|
+
.component-trend{class: "trend-#{trend_class}"}
|
|
29
|
+
- if trend_pct.positive?
|
|
30
|
+
%i.iconoir-arrow-up
|
|
31
|
+
- elsif trend_pct.negative?
|
|
32
|
+
%i.iconoir-arrow-down
|
|
33
|
+
- else
|
|
34
|
+
%i.iconoir-minus
|
|
35
|
+
%span= "#{trend_pct.abs}%"
|
|
36
|
+
- else
|
|
37
|
+
%span.empty-value —
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-# Git component - repository link
|
|
2
|
+
- annotations = instance.annotations
|
|
3
|
+
- git_url = annotations['repository/git']
|
|
4
|
+
- visibility = annotations['repository/visibility']
|
|
5
|
+
- if git_url
|
|
6
|
+
- display_name = git_url.sub(%r{^https?://}, '').sub(/\.git$/, '')
|
|
7
|
+
- short_name = display_name.split('/').last(2).join('/')
|
|
8
|
+
- tooltip = git_url
|
|
9
|
+
- tooltip += " (#{visibility})" if visibility
|
|
10
|
+
%a.component-link{href: git_url, target: "_blank", title: tooltip}
|
|
11
|
+
- if git_url.include?("gitlab")
|
|
12
|
+
%i.iconoir-git-fork
|
|
13
|
+
- else
|
|
14
|
+
%i.iconoir-github
|
|
15
|
+
= short_name
|
|
16
|
+
- else
|
|
17
|
+
%span.empty-value —
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
-# Jira component - compact for tables
|
|
2
|
+
- annotations = instance.annotations
|
|
3
|
+
- jira_key = annotations['team/jira']
|
|
4
|
+
- jira_url = annotations['jira/projectUrl']
|
|
5
|
+
- jira_created = annotations['jira/issues/created']
|
|
6
|
+
- jira_resolved = annotations['jira/issues/resolved']
|
|
7
|
+
- if jira_key
|
|
8
|
+
.component-jira
|
|
9
|
+
%a.badge.badge-sm{href: jira_url, target: '_blank'}= jira_key
|
|
10
|
+
- if jira_created && jira_resolved
|
|
11
|
+
- created_avg = jira_created.split(',').map(&:to_i).sum / 6.0
|
|
12
|
+
- resolved_avg = jira_resolved.split(',').map(&:to_i).sum / 6.0
|
|
13
|
+
- backlog_change = (created_avg - resolved_avg).round(1)
|
|
14
|
+
- trend_class = backlog_change < -1 ? 'positive' : (backlog_change > 1 ? 'negative' : 'neutral')
|
|
15
|
+
.component-trend{class: "trend-#{trend_class}", title: "#{created_avg.round(1)} created/mo, #{resolved_avg.round(1)} resolved/mo"}
|
|
16
|
+
= "#{"+" if backlog_change.positive?}#{backlog_change}"
|
|
17
|
+
- else
|
|
18
|
+
%span.empty-value —
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
-# Languages component - bar visualization with tooltip details
|
|
2
|
+
- annotations = instance.annotations
|
|
3
|
+
- languages = annotations['scc/languages']
|
|
4
|
+
- if languages && !languages.empty?
|
|
5
|
+
- lang_data = []
|
|
6
|
+
- languages.split(',').each do |lang|
|
|
7
|
+
- loc_value = annotations["scc/language/#{lang.strip}/loc"]
|
|
8
|
+
- lang_data << { name: lang.strip, loc: loc_value ? loc_value.to_i : 0 }
|
|
9
|
+
- lang_data = lang_data.sort_by { |l| -l[:loc] }
|
|
10
|
+
- total_loc = lang_data.sum { |l| l[:loc] }
|
|
11
|
+
- if total_loc > 0
|
|
12
|
+
-# Build tooltip with language breakdown
|
|
13
|
+
- tooltip_parts = ["#{number_with_delimiter(total_loc)} loc"]
|
|
14
|
+
- lang_data.first(5).each { |l| tooltip_parts << "#{l[:name]}: #{number_with_delimiter(l[:loc])} (#{(l[:loc].to_f / total_loc * 100).round(1)}%)" }
|
|
15
|
+
- tooltip_parts << "..." if lang_data.size > 5
|
|
16
|
+
- tooltip = tooltip_parts.join(", ")
|
|
17
|
+
.component-language{title: tooltip}
|
|
18
|
+
.component-lang-bar
|
|
19
|
+
- lang_data.first(5).each_with_index do |lang, idx|
|
|
20
|
+
- pct = (lang[:loc].to_f / total_loc * 100).round(1)
|
|
21
|
+
- if pct > 0
|
|
22
|
+
.lang-bar-segment{class: "lang-#{idx}", style: "width: #{pct}%"}
|
|
23
|
+
- other_pct = 100 - lang_data.first(5).sum { |l| (l[:loc].to_f / total_loc * 100).round(1) }
|
|
24
|
+
- if other_pct.positive?
|
|
25
|
+
.lang-bar-segment.lang-other{ style: "width: #{other_pct}%" }
|
|
26
|
+
- else
|
|
27
|
+
%span.empty-value —
|
|
28
|
+
- else
|
|
29
|
+
%span.empty-value —
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-# Owner component - team/owner link
|
|
2
|
+
- annotations = instance.annotations
|
|
3
|
+
- owner = annotations['team/owner']
|
|
4
|
+
- if owner
|
|
5
|
+
- owner_instance = @database.find("BusinessActor", owner)
|
|
6
|
+
.component-owner{title: owner}
|
|
7
|
+
- if owner_instance
|
|
8
|
+
%a.component-link{ href: "/artifact/BusinessActor/#{owner}" }
|
|
9
|
+
%i.iconoir-community
|
|
10
|
+
= owner
|
|
11
|
+
- else
|
|
12
|
+
%i.iconoir-community
|
|
13
|
+
= owner
|
|
14
|
+
- else
|
|
15
|
+
%span.empty-value —
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
-# Repositories component - count + mini bar with all statuses
|
|
2
|
+
- annotations = instance.annotations
|
|
3
|
+
- total = annotations['repository/artifacts/total']&.to_i
|
|
4
|
+
- active = annotations['repository/artifacts/active']&.to_i || 0
|
|
5
|
+
- abandoned = annotations['repository/artifacts/abandoned']&.to_i || 0
|
|
6
|
+
- archived = annotations['repository/artifacts/archived']&.to_i || 0
|
|
7
|
+
- high_bus_factor = annotations['repository/artifacts/highBusFactor']&.to_i || 0
|
|
8
|
+
- if total && total > 0
|
|
9
|
+
- active_healthy = active - high_bus_factor
|
|
10
|
+
- other = total - active - abandoned - archived
|
|
11
|
+
- tooltip_parts = []
|
|
12
|
+
- tooltip_parts << "#{active_healthy} active" if active_healthy > 0
|
|
13
|
+
- tooltip_parts << "#{high_bus_factor} high bus factor" if high_bus_factor > 0
|
|
14
|
+
- tooltip_parts << "#{archived} archived" if archived > 0
|
|
15
|
+
- tooltip_parts << "#{other} other" if other > 0
|
|
16
|
+
- tooltip_parts << "#{abandoned} abandoned" if abandoned > 0
|
|
17
|
+
- tooltip = "#{total} repos: #{tooltip_parts.join(', ')}"
|
|
18
|
+
.component-repos{title: tooltip}
|
|
19
|
+
%span.component-count= total
|
|
20
|
+
.component-bar-mini
|
|
21
|
+
- if active_healthy > 0
|
|
22
|
+
- pct = (active_healthy.to_f / total * 100).round
|
|
23
|
+
.bar-segment.status-active{style: "width: #{pct}%"}
|
|
24
|
+
- if high_bus_factor > 0
|
|
25
|
+
- pct = (high_bus_factor.to_f / total * 100).round
|
|
26
|
+
.bar-segment.status-high-bus-factor{style: "width: #{pct}%"}
|
|
27
|
+
- if archived > 0
|
|
28
|
+
- pct = (archived.to_f / total * 100).round
|
|
29
|
+
.bar-segment.status-archived{style: "width: #{pct}%"}
|
|
30
|
+
- if other > 0
|
|
31
|
+
- pct = (other.to_f / total * 100).round
|
|
32
|
+
.bar-segment.status-other{style: "width: #{pct}%"}
|
|
33
|
+
- if abandoned.positive?
|
|
34
|
+
- pct = (abandoned.to_f / total * 100).round
|
|
35
|
+
.bar-segment.status-abandoned{ style: "width: #{pct}%" }
|
|
36
|
+
- else
|
|
37
|
+
%span.empty-value —
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
-# Status component - status + bus factor
|
|
2
|
+
- annotations = instance.annotations
|
|
3
|
+
- status = annotations['activity/status']
|
|
4
|
+
- bus_factor = annotations['activity/busFactor']
|
|
5
|
+
- contributors_6m = annotations['activity/contributors/6m']
|
|
6
|
+
- if status || bus_factor
|
|
7
|
+
- bus_factor_desc = case bus_factor
|
|
8
|
+
- when 'high' then 'High risk: >75% commits from one contributor'
|
|
9
|
+
- when 'medium' then 'Medium risk: 50-75% commits from one contributor'
|
|
10
|
+
- when 'low' then 'Low risk: No single contributor dominates'
|
|
11
|
+
- else nil
|
|
12
|
+
- tooltip_parts = []
|
|
13
|
+
- tooltip_parts << "Status: #{status}" if status
|
|
14
|
+
- tooltip_parts << bus_factor_desc if bus_factor_desc
|
|
15
|
+
- tooltip_parts << "#{contributors_6m} contributors (6m)" if contributors_6m
|
|
16
|
+
- tooltip = tooltip_parts.join(' | ')
|
|
17
|
+
.component-status{title: tooltip}
|
|
18
|
+
- if status
|
|
19
|
+
%span.component-badge{class: "status-#{status}"}= status
|
|
20
|
+
- if bus_factor && bus_factor != "low"
|
|
21
|
+
%span.component-badge{ class: "bus-factor-#{bus_factor}" }= "bus:#{bus_factor}"
|
|
22
|
+
- else
|
|
23
|
+
%span.empty-value —
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
- instance = db.instance_by_kind(@kind, @instance)
|
|
2
|
+
- unless instance
|
|
3
|
+
%article
|
|
4
|
+
%header
|
|
5
|
+
%h2 Not Found
|
|
6
|
+
%p
|
|
7
|
+
Instance
|
|
8
|
+
%strong= @instance
|
|
9
|
+
of kind
|
|
10
|
+
%strong= @kind
|
|
11
|
+
was not found.
|
|
12
|
+
- else
|
|
13
|
+
%article
|
|
14
|
+
%header
|
|
15
|
+
%h2
|
|
16
|
+
%i{class: "iconoir-#{instance.class.icon} icon-#{instance.class.layer}"}
|
|
17
|
+
.instance-title-text
|
|
18
|
+
%span.instance-name= @instance
|
|
19
|
+
%span.instance-kind-subtitle= @kind
|
|
20
|
+
- if instance.annotations['generated/script']
|
|
21
|
+
.generated-badge
|
|
22
|
+
%span.generated-script
|
|
23
|
+
Generated by
|
|
24
|
+
= instance.annotations['generated/script']
|
|
25
|
+
- if instance.annotations['generated/at']
|
|
26
|
+
%span.generated-time= time_ago(instance.annotations['generated/at'])
|
|
27
|
+
- if instance.has_relations?
|
|
28
|
+
.graph-container
|
|
29
|
+
#graphviz.canvas
|
|
30
|
+
!= graphviz_svg(create_graph_one(db, @kind, @instance), 'graphviz')
|
|
31
|
+
- if instance.annotations['architecture/description']
|
|
32
|
+
- git_url = instance.annotations['repository/git']
|
|
33
|
+
- if instance.has_relations?
|
|
34
|
+
%footer
|
|
35
|
+
!= markdown(instance.annotations['architecture/description'], git_url: git_url)
|
|
36
|
+
- else
|
|
37
|
+
!= markdown(instance.annotations['architecture/description'], git_url: git_url)
|
|
38
|
+
!= haml :'partials/instance/_requirements', locals: { instance: instance }
|
|
39
|
+
%article.documentation
|
|
40
|
+
%header
|
|
41
|
+
%h2 Details
|
|
42
|
+
%table
|
|
43
|
+
%thead
|
|
44
|
+
%tr
|
|
45
|
+
%th{scope: 'col'} Name
|
|
46
|
+
%th{scope: 'col'} Value
|
|
47
|
+
%tbody
|
|
48
|
+
!= haml :'partials/artifact/_git_info'
|
|
49
|
+
!= haml :'partials/artifact/_language_stats'
|
|
50
|
+
!= haml :'partials/artifact/_project_estimate'
|
|
51
|
+
!= haml :'partials/artifact/_repositories'
|
|
52
|
+
!= haml :'partials/artifact/_activity'
|
|
53
|
+
!= haml :'partials/artifact/_team'
|
|
54
|
+
!= haml :'partials/artifact/_deployment'
|
|
55
|
+
!= haml :'partials/artifact/_workflow'
|
|
56
|
+
!= haml :'partials/artifact/_agentic'
|
|
57
|
+
!= haml :'partials/artifact/_links'
|
|
58
|
+
- instance.annotations.each do |k, v|
|
|
59
|
+
- next if k == 'scc/languages' || k =~ /^scc\/language\/.+\/loc$/
|
|
60
|
+
- next if k =~ /^scc\/estimated(Cost|ScheduleMonths|People)$/
|
|
61
|
+
- next if k =~ /^activity\/(commits|contributors(\/(6m|total))?|status|busFactor|createdAt)$/
|
|
62
|
+
- next if k == 'architecture/description'
|
|
63
|
+
- next if k =~ /^repository\/(artifacts|git|visibility|)$/
|
|
64
|
+
- next if k.start_with?('repository/artifacts/')
|
|
65
|
+
- next if k == 'workflow/platforms'
|
|
66
|
+
- next if k == 'workflow/types'
|
|
67
|
+
- next if k == 'agentic/tools'
|
|
68
|
+
- next if k.start_with?('link/')
|
|
69
|
+
- next if k.start_with?('team/')
|
|
70
|
+
- next if k.start_with?('jira/')
|
|
71
|
+
- next if k.start_with?('generated/')
|
|
72
|
+
- annotation_def = Archsight::Resources[@kind].annotation_matching(k)
|
|
73
|
+
%tr
|
|
74
|
+
%th{scope: 'row', title: annotation_def&.description}= annotation_def&.title || k.split('/').last.capitalize
|
|
75
|
+
%td
|
|
76
|
+
- format = Archsight::Resources[@kind].annotation_format(k)
|
|
77
|
+
- case format
|
|
78
|
+
- when :markdown
|
|
79
|
+
!= markdown(v, git_url: git_url)
|
|
80
|
+
- when :tag_list
|
|
81
|
+
.instance-badges
|
|
82
|
+
- v.split(',').map(&:strip).each do |val|
|
|
83
|
+
- badge_class = case k
|
|
84
|
+
- when 'repository/artifacts' then "deployment-#{val}"
|
|
85
|
+
- when 'workflow/platforms' then "workflow-#{val}"
|
|
86
|
+
- when 'workflow/types' then "workflow-#{val}"
|
|
87
|
+
- when 'agentic/tools' then "agentic-#{val}"
|
|
88
|
+
- else ''
|
|
89
|
+
%a.badge.badge-info{class: badge_class, **filter_link_attrs(k, val)}
|
|
90
|
+
= val
|
|
91
|
+
- when :tag_word
|
|
92
|
+
%a.badge.badge-info{**filter_link_attrs(k, v)}
|
|
93
|
+
= v
|
|
94
|
+
- else
|
|
95
|
+
- if v.is_a?(String) && v.start_with?(%r{https?://})
|
|
96
|
+
%a{ href: v, target: "_blank" }= v
|
|
97
|
+
- else
|
|
98
|
+
= v
|
|
99
|
+
!= haml :"partials/instance/_relations", locals: { instance: instance }
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
-# Parameters:
|
|
2
|
+
-# instances: Array of resource instances to display
|
|
3
|
+
-# omit_kind: Boolean - hide kind column (default: false)
|
|
4
|
+
-# fields: Array of annotation keys to display as columns (optional, overrides list_annotations)
|
|
5
|
+
- omit_kind ||= false
|
|
6
|
+
- fields ||= nil
|
|
7
|
+
- if instances.nil? || instances.empty?
|
|
8
|
+
%p.empty-state
|
|
9
|
+
%i No resources found
|
|
10
|
+
- else
|
|
11
|
+
- sample_class = instances.first.class
|
|
12
|
+
-# Determine which annotations to show as columns
|
|
13
|
+
- if fields
|
|
14
|
+
-# Use provided fields - convert to annotation-like objects
|
|
15
|
+
-# Always create wrapper with the actual field key to ensure correct value lookups
|
|
16
|
+
-# (pattern annotations like scc/language/*/loc have the pattern as key, not the actual field)
|
|
17
|
+
- field_annotation = Struct.new(:key, :title, :description, :type)
|
|
18
|
+
- list_annotations = fields.map do |field|
|
|
19
|
+
- if field.start_with?('@')
|
|
20
|
+
-# Component field - renders a partial from partials/components/
|
|
21
|
+
- component_name = field[1..-1]
|
|
22
|
+
- field_annotation.new(field, component_name.capitalize, "Component: #{component_name}", :component)
|
|
23
|
+
- else
|
|
24
|
+
- annotation_def = sample_class.annotation_matching(field)
|
|
25
|
+
- segments = field.split('/')
|
|
26
|
+
-# Generate title: for paths like scc/language/C/loc, use "C Loc" (language name + last segment)
|
|
27
|
+
- if segments.length >= 2
|
|
28
|
+
- title = "#{segments[-2]} #{segments[-1]}".split(/(?=[A-Z])/).map(&:capitalize).join(' ')
|
|
29
|
+
- else
|
|
30
|
+
- title = segments.last.split(/(?=[A-Z])/).map(&:capitalize).join(' ')
|
|
31
|
+
- field_annotation.new(field, title, annotation_def&.description, annotation_def&.type)
|
|
32
|
+
- list_annotations.compact!
|
|
33
|
+
- elsif omit_kind
|
|
34
|
+
-# Auto-detect from list: true annotations
|
|
35
|
+
- list_annotations = sample_class.list_annotations
|
|
36
|
+
- if list_annotations.empty?
|
|
37
|
+
- tags_annotation = sample_class.annotation_matching('architecture/tags')
|
|
38
|
+
- list_annotations = [tags_annotation] if tags_annotation
|
|
39
|
+
- else
|
|
40
|
+
- list_annotations = []
|
|
41
|
+
- if list_annotations.any?
|
|
42
|
+
%table.resource-list-table
|
|
43
|
+
%thead
|
|
44
|
+
%tr
|
|
45
|
+
%th.col-name Name
|
|
46
|
+
- unless omit_kind
|
|
47
|
+
%th.col-kind Kind
|
|
48
|
+
- list_annotations.each do |annotation|
|
|
49
|
+
%th.col-annotation{title: annotation.description}= annotation.title
|
|
50
|
+
%tbody
|
|
51
|
+
- instances.each do |instance|
|
|
52
|
+
%tr.resource-list-row
|
|
53
|
+
%td.col-name
|
|
54
|
+
%a.instance-name{href: "/kinds/#{instance.klass}/instances/#{instance.name}"}
|
|
55
|
+
%i{class: "iconoir-#{instance.class.icon} icon-#{instance.class.layer}"}
|
|
56
|
+
= instance.name
|
|
57
|
+
- unless omit_kind
|
|
58
|
+
%td.col-kind
|
|
59
|
+
%span.instance-kind= instance.klass
|
|
60
|
+
- list_annotations.each do |annotation|
|
|
61
|
+
%td.col-annotation
|
|
62
|
+
- if annotation.type == :component
|
|
63
|
+
- component_name = annotation.key[1..-1]
|
|
64
|
+
!= haml :"partials/components/_#{component_name}", locals: { instance: instance }
|
|
65
|
+
- else
|
|
66
|
+
- value = instance.annotations[annotation.key] || instance.computed_annotation_value(annotation.key)
|
|
67
|
+
- if value
|
|
68
|
+
- if annotation.type == Time
|
|
69
|
+
- time_value = value.is_a?(Time) ? value : Time.parse(value.to_s)
|
|
70
|
+
%span{title: time_value.strftime("%Y-%m-%d %H:%M:%S")}= time_ago(value)
|
|
71
|
+
- else
|
|
72
|
+
= value
|
|
73
|
+
- else
|
|
74
|
+
%span.empty-value —
|
|
75
|
+
- else
|
|
76
|
+
%ul.search-instance-list
|
|
77
|
+
- instances.each do |instance|
|
|
78
|
+
%li.search-instance-item
|
|
79
|
+
.instance-main
|
|
80
|
+
%a.instance-name{href: "/kinds/#{instance.klass}/instances/#{instance.name}"}
|
|
81
|
+
%i{ class: "iconoir-#{instance.class.icon} icon-#{instance.class.layer}" }
|
|
82
|
+
= instance.name
|
|
83
|
+
- unless omit_kind
|
|
84
|
+
%span.instance-kind= instance.klass
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
- return unless instance.has_relations? || instance.references.any?
|
|
2
|
+
%article.relations-section
|
|
3
|
+
%header
|
|
4
|
+
%h2 Relations
|
|
5
|
+
.relations-grid
|
|
6
|
+
.relations-column.relations-outgoing
|
|
7
|
+
%h3
|
|
8
|
+
Outgoing
|
|
9
|
+
%i.iconoir-arrow-right
|
|
10
|
+
- if instance.has_relations?
|
|
11
|
+
- instance.relations_grouped.each do |verb, kinds|
|
|
12
|
+
- kinds.each do |kind, instances|
|
|
13
|
+
- kind_class = Archsight::Resources[kind]
|
|
14
|
+
.relation-group
|
|
15
|
+
.relation-kind
|
|
16
|
+
%i{class: "iconoir-#{kind_class&.icon || 'cube'} icon-#{kind_class&.layer || 'technology'}"}
|
|
17
|
+
%span= kind
|
|
18
|
+
.relation-verb= verb
|
|
19
|
+
.instance-badges
|
|
20
|
+
- instances.sort_by(&:name).each do |item|
|
|
21
|
+
%a.badge.badge-info{href: "/kinds/#{item.klass}/instances/#{item.name}"}
|
|
22
|
+
= item.name
|
|
23
|
+
- else
|
|
24
|
+
.relations-empty None
|
|
25
|
+
.relations-column.relations-incoming
|
|
26
|
+
%h3
|
|
27
|
+
%i.iconoir-arrow-right
|
|
28
|
+
Incoming
|
|
29
|
+
- if instance.references.any?
|
|
30
|
+
- instance.references_grouped.each do |kind, verbs|
|
|
31
|
+
- kind_class = Archsight::Resources[kind]
|
|
32
|
+
- verbs.each do |verb, instances|
|
|
33
|
+
.relation-group
|
|
34
|
+
.relation-kind
|
|
35
|
+
%i{class: "iconoir-#{kind_class&.icon || 'cube'} icon-#{kind_class&.layer || 'technology'}"}
|
|
36
|
+
%span= kind
|
|
37
|
+
.relation-verb= verb || "references"
|
|
38
|
+
.instance-badges
|
|
39
|
+
- instances.sort_by(&:name).each do |item|
|
|
40
|
+
%a.badge.badge-info{ href: "/kinds/#{item.klass}/instances/#{item.name}" }
|
|
41
|
+
= item.name
|
|
42
|
+
- else
|
|
43
|
+
.relations-empty None
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
- return unless instance
|
|
2
|
+
- realized = instance.relations(:realizes, :businessRequirements).map { |r| [r, 'implemented'] }
|
|
3
|
+
- partial = instance.relations(:partiallyRealizes, :businessRequirements).map { |r| [r, 'partial'] }
|
|
4
|
+
- planned = instance.relations(:plans, :businessRequirements).map { |r| [r, 'planned'] }
|
|
5
|
+
- all_requirements = realized + partial + planned
|
|
6
|
+
- if all_requirements.any?
|
|
7
|
+
%article.requirements-section
|
|
8
|
+
%header
|
|
9
|
+
%h2 Business Requirements
|
|
10
|
+
%table.requirements-table
|
|
11
|
+
%thead
|
|
12
|
+
%tr
|
|
13
|
+
%th
|
|
14
|
+
%th Name
|
|
15
|
+
%th Priority
|
|
16
|
+
%th Story
|
|
17
|
+
%tbody
|
|
18
|
+
- all_requirements.each do |req, status|
|
|
19
|
+
- status_icon = case status
|
|
20
|
+
- when 'implemented' then 'check-circle'
|
|
21
|
+
- when 'partial' then 'half-moon'
|
|
22
|
+
- when 'planned' then 'calendar'
|
|
23
|
+
- else 'circle'
|
|
24
|
+
- status_title = status.capitalize
|
|
25
|
+
%tr
|
|
26
|
+
%td.requirement-status
|
|
27
|
+
%i.requirement-status-icon{class: "iconoir-#{status_icon} status-#{status}", title: status_title}
|
|
28
|
+
%td
|
|
29
|
+
%a{href: "/kinds/BusinessRequirement/instances/#{req.name}"}= req.name
|
|
30
|
+
%td
|
|
31
|
+
- priority = req.annotations["requirement/priority"]
|
|
32
|
+
- if priority
|
|
33
|
+
%a.badge.badge-info{**search_link_attrs("BusinessRequirement: <- \"#{instance.name}\" & requirement/priority == \"#{priority}\"")}= priority
|
|
34
|
+
- else
|
|
35
|
+
%span.view-empty-value -
|
|
36
|
+
%td.requirement-story
|
|
37
|
+
- story = req.annotations["requirement/story"]
|
|
38
|
+
- if story
|
|
39
|
+
!= markdown(story)
|
|
40
|
+
- else
|
|
41
|
+
%span.view-empty-value -
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
- view_instance = db.instance_by_kind(@kind, @instance)
|
|
2
|
+
- return unless view_instance
|
|
3
|
+
- view_query = view_instance.annotations['view/query']
|
|
4
|
+
- view_description = view_instance.annotations['architecture/description']
|
|
5
|
+
- view_fields_raw = view_instance.annotations['view/fields'] || ''
|
|
6
|
+
- view_fields = view_fields_raw.split(',').map(&:strip).reject(&:empty?)
|
|
7
|
+
- view_type = view_instance.annotations['view/type'] || 'list:name+kind'
|
|
8
|
+
- show_kind = view_type == 'list:name+kind'
|
|
9
|
+
- view_sort_raw = view_instance.annotations['view/sort'] || ''
|
|
10
|
+
- view_sort_fields = view_sort_raw.split(',').map(&:strip).reject(&:empty?)
|
|
11
|
+
|
|
12
|
+
%article.view-header
|
|
13
|
+
%header
|
|
14
|
+
%h2
|
|
15
|
+
%i{class: "iconoir-#{view_instance.class.icon} icon-#{view_instance.class.layer}"}
|
|
16
|
+
= @instance
|
|
17
|
+
- if view_description
|
|
18
|
+
.view-description
|
|
19
|
+
!= markdown(view_description)
|
|
20
|
+
- if view_query
|
|
21
|
+
.view-query-display
|
|
22
|
+
%p.query-item
|
|
23
|
+
%span.label Query:
|
|
24
|
+
%code.query-value= view_query
|
|
25
|
+
|
|
26
|
+
- if view_query
|
|
27
|
+
- begin
|
|
28
|
+
- start_time = Time.now
|
|
29
|
+
- parsed_query = Archsight::Query.parse(view_query)
|
|
30
|
+
- results = parsed_query.filter(db)
|
|
31
|
+
- results = sort_instances(results, view_sort_fields)
|
|
32
|
+
- search_time_ms = ((Time.now - start_time) * 1000).round(2)
|
|
33
|
+
-# Hide kind if view_type is not 'list:name+kind' OR if query filters by a specific kind (redundant)
|
|
34
|
+
- show_kind = show_kind && parsed_query.kind_filter.nil?
|
|
35
|
+
|
|
36
|
+
%article.view-results
|
|
37
|
+
%header
|
|
38
|
+
%h3 Results
|
|
39
|
+
%span.view-result-meta
|
|
40
|
+
= results.size
|
|
41
|
+
= results.size == 1 ? "item" : "items"
|
|
42
|
+
in #{search_time_ms} ms
|
|
43
|
+
|
|
44
|
+
!= haml :"partials/instance/_list", locals: { instances: results, omit_kind: !show_kind, fields: view_fields.any? ? view_fields : nil }
|
|
45
|
+
|
|
46
|
+
- rescue Archsight::Query::QueryError => e
|
|
47
|
+
.search-error
|
|
48
|
+
.search-error-header
|
|
49
|
+
%i.iconoir-warning-triangle
|
|
50
|
+
Query Error
|
|
51
|
+
.search-error-message= e.message
|
|
52
|
+
.search-error-query
|
|
53
|
+
Query:
|
|
54
|
+
%code= view_query
|
|
55
|
+
- else
|
|
56
|
+
%p.view-empty-state
|
|
57
|
+
%em No query defined
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
.content
|
|
2
|
+
- if @doc_content
|
|
3
|
+
%article
|
|
4
|
+
!= @doc_content
|
|
5
|
+
- elsif @instances
|
|
6
|
+
!= haml :search
|
|
7
|
+
- elsif @kind && @instance
|
|
8
|
+
- if !Archsight::Resources[@kind]
|
|
9
|
+
%article
|
|
10
|
+
%header
|
|
11
|
+
%h2 Kind Not Found
|
|
12
|
+
%p
|
|
13
|
+
Kind
|
|
14
|
+
%strong= @kind
|
|
15
|
+
does not exist.
|
|
16
|
+
- elsif @kind == "View"
|
|
17
|
+
!= haml :'partials/instance/_view_detail'
|
|
18
|
+
- else
|
|
19
|
+
!= haml :'partials/instance/_detail'
|
|
20
|
+
- elsif @kind && !@instance
|
|
21
|
+
- if !Archsight::Resources[@kind]
|
|
22
|
+
%article
|
|
23
|
+
%header
|
|
24
|
+
%h2 Kind Not Found
|
|
25
|
+
%p
|
|
26
|
+
Kind
|
|
27
|
+
%strong= @kind
|
|
28
|
+
does not exist.
|
|
29
|
+
- else
|
|
30
|
+
- kind_snake = @kind.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
|
31
|
+
%article
|
|
32
|
+
%header
|
|
33
|
+
%h2
|
|
34
|
+
%i{class: "iconoir-#{Archsight::Resources[@kind].icon} icon-#{Archsight::Resources[@kind].layer}"}
|
|
35
|
+
= @kind
|
|
36
|
+
%a.kind-help{href: "/doc/resources/#{kind_snake}", title: "Documentation for #{@kind}"}
|
|
37
|
+
%i.iconoir-help-circle
|
|
38
|
+
!= haml :"partials/instance/_list", locals: { instances: db.instances_by_kind(@kind).values.sort_by(&:name), omit_kind: true }
|
|
39
|
+
- elsif !@kind
|
|
40
|
+
!= haml :"partials/instance/_graph"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
%header.container
|
|
2
|
+
%article.error-header
|
|
3
|
+
%h3
|
|
4
|
+
%i.iconoir-warning-triangle
|
|
5
|
+
Error
|
|
6
|
+
%p.error-message= @error.message
|
|
7
|
+
%p.error-location
|
|
8
|
+
%i.iconoir-page
|
|
9
|
+
= relative_error_path(@error.ref.path)
|
|
10
|
+
%span.error-line-badge Line #{@error.ref.line_no}
|
|
11
|
+
%a.reload-btn{href: "/reload?redirect=#{params["redirect"]}"}
|
|
12
|
+
%i.iconoir-refresh
|
|
13
|
+
Reload
|
|
14
|
+
|
|
15
|
+
%main.container
|
|
16
|
+
.error-code-block
|
|
17
|
+
%pre
|
|
18
|
+
%code
|
|
19
|
+
- error_context_lines(@error.ref.path, @error.ref.line_no).each do |line|
|
|
20
|
+
- line_class = line[:selected] ? "error-line" : "code-line"
|
|
21
|
+
- escaped_content = ERB::Util.html_escape(line[:content])
|
|
22
|
+
!= "<span class='#{line_class}'><span class='line-number'>#{line[:line_no].to_s.rjust(4)}</span>#{escaped_content}</span>\n"
|