archsight 0.1.2 → 0.1.3
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 +4 -4
- data/README.md +26 -5
- data/lib/archsight/analysis/executor.rb +112 -0
- data/lib/archsight/analysis/result.rb +174 -0
- data/lib/archsight/analysis/sandbox.rb +319 -0
- data/lib/archsight/analysis.rb +11 -0
- data/lib/archsight/annotations/architecture_annotations.rb +2 -2
- data/lib/archsight/cli.rb +163 -0
- data/lib/archsight/database.rb +6 -2
- data/lib/archsight/helpers/analysis_renderer.rb +83 -0
- data/lib/archsight/helpers/formatting.rb +95 -0
- data/lib/archsight/helpers.rb +20 -4
- data/lib/archsight/import/concurrent_progress.rb +341 -0
- data/lib/archsight/import/executor.rb +466 -0
- data/lib/archsight/import/git_analytics.rb +626 -0
- data/lib/archsight/import/handler.rb +263 -0
- data/lib/archsight/import/handlers/github.rb +161 -0
- data/lib/archsight/import/handlers/gitlab.rb +202 -0
- data/lib/archsight/import/handlers/jira_base.rb +189 -0
- data/lib/archsight/import/handlers/jira_discover.rb +161 -0
- data/lib/archsight/import/handlers/jira_metrics.rb +179 -0
- data/lib/archsight/import/handlers/openapi_schema_parser.rb +279 -0
- data/lib/archsight/import/handlers/repository.rb +439 -0
- data/lib/archsight/import/handlers/rest_api.rb +293 -0
- data/lib/archsight/import/handlers/rest_api_index.rb +183 -0
- data/lib/archsight/import/progress.rb +91 -0
- data/lib/archsight/import/registry.rb +54 -0
- data/lib/archsight/import/shared_file_writer.rb +67 -0
- data/lib/archsight/import/team_matcher.rb +195 -0
- data/lib/archsight/import.rb +14 -0
- data/lib/archsight/resources/analysis.rb +91 -0
- data/lib/archsight/resources/application_component.rb +2 -2
- data/lib/archsight/resources/application_service.rb +12 -12
- data/lib/archsight/resources/business_product.rb +12 -12
- data/lib/archsight/resources/data_object.rb +1 -1
- data/lib/archsight/resources/import.rb +79 -0
- data/lib/archsight/resources/technology_artifact.rb +23 -2
- data/lib/archsight/version.rb +1 -1
- data/lib/archsight/web/api/docs.rb +17 -0
- data/lib/archsight/web/api/json_helpers.rb +164 -0
- data/lib/archsight/web/api/openapi/spec.yaml +500 -0
- data/lib/archsight/web/api/routes.rb +101 -0
- data/lib/archsight/web/application.rb +66 -43
- data/lib/archsight/web/doc/import.md +458 -0
- data/lib/archsight/web/doc/index.md.erb +1 -0
- data/lib/archsight/web/public/css/artifact.css +10 -0
- data/lib/archsight/web/public/css/graph.css +14 -0
- data/lib/archsight/web/public/css/instance.css +489 -0
- data/lib/archsight/web/views/api_docs.erb +19 -0
- data/lib/archsight/web/views/partials/artifact/_project_estimate.haml +14 -8
- data/lib/archsight/web/views/partials/instance/_analysis_detail.haml +74 -0
- data/lib/archsight/web/views/partials/instance/_analysis_result.haml +64 -0
- data/lib/archsight/web/views/partials/instance/_detail.haml +7 -3
- data/lib/archsight/web/views/partials/instance/_import_detail.haml +87 -0
- data/lib/archsight/web/views/partials/instance/_relations.haml +4 -4
- data/lib/archsight/web/views/partials/layout/_content.haml +4 -0
- data/lib/archsight/web/views/partials/layout/_navigation.haml +6 -5
- metadata +78 -1
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "archsight/import"
|
|
4
|
+
|
|
5
|
+
# Matches git contributors to BusinessActor teams
|
|
6
|
+
#
|
|
7
|
+
# Uses team member email and name patterns from BusinessActor annotations
|
|
8
|
+
# to match top contributors from git history to teams.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# matcher = Archsight::Import::TeamMatcher.new(database)
|
|
12
|
+
# result = matcher.analyze(top_contributors)
|
|
13
|
+
# # => { maintainer: "Team:Engineering", contributors: ["Team:QA", "Team:Ops"] }
|
|
14
|
+
class Archsight::Import::TeamMatcher
|
|
15
|
+
# Teams to ignore when matching (bots, unknown, etc.)
|
|
16
|
+
IGNORED_TEAMS = %w[Bot:Team No:Team Team:Unknown Team:Bot].freeze
|
|
17
|
+
|
|
18
|
+
def initialize(database)
|
|
19
|
+
@database = database
|
|
20
|
+
@teams = load_teams
|
|
21
|
+
@email_to_team = build_email_index
|
|
22
|
+
@name_to_team = build_name_index
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Analyze top contributors and return team assignments
|
|
26
|
+
#
|
|
27
|
+
# @param top_contributors [Array<Hash>] List of contributors with "name", "email", "commits" keys
|
|
28
|
+
# @return [Hash] { maintainer: String?, contributors: [String] }
|
|
29
|
+
def analyze(top_contributors)
|
|
30
|
+
return { maintainer: nil, contributors: [] } if top_contributors.nil? || top_contributors.empty?
|
|
31
|
+
|
|
32
|
+
team_commits = Hash.new(0)
|
|
33
|
+
|
|
34
|
+
top_contributors.each do |contributor|
|
|
35
|
+
team = match_contributor(contributor["name"], contributor["email"])
|
|
36
|
+
next unless team
|
|
37
|
+
next if IGNORED_TEAMS.include?(team)
|
|
38
|
+
|
|
39
|
+
team_commits[team] += contributor["commits"]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
return { maintainer: nil, contributors: [] } if team_commits.empty?
|
|
43
|
+
|
|
44
|
+
# Sort by commits descending
|
|
45
|
+
sorted = team_commits.sort_by { |_, commits| -commits }
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
maintainer: sorted.first&.first,
|
|
49
|
+
contributors: sorted.drop(1).map(&:first)
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Match a single contributor to a team
|
|
54
|
+
#
|
|
55
|
+
# @param name [String] Contributor name
|
|
56
|
+
# @param email [String] Contributor email
|
|
57
|
+
# @return [String, nil] Team name or nil if no match
|
|
58
|
+
def match_contributor(name, email)
|
|
59
|
+
return nil if name.nil? && email.nil?
|
|
60
|
+
|
|
61
|
+
# Try exact email match first (most reliable)
|
|
62
|
+
if email
|
|
63
|
+
normalized_email = email.to_s.downcase.strip
|
|
64
|
+
team = @email_to_team[normalized_email]
|
|
65
|
+
return team if team
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Try name match (less reliable, but useful for LDAP-style names)
|
|
69
|
+
if name
|
|
70
|
+
normalized_name = normalize_name(name)
|
|
71
|
+
team = @name_to_team[normalized_name]
|
|
72
|
+
return team if team
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def load_teams
|
|
81
|
+
return {} unless @database
|
|
82
|
+
|
|
83
|
+
begin
|
|
84
|
+
@database.instances_by_kind("BusinessActor")&.values || []
|
|
85
|
+
rescue StandardError
|
|
86
|
+
[]
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def build_email_index
|
|
91
|
+
index = {}
|
|
92
|
+
|
|
93
|
+
@teams.each do |team|
|
|
94
|
+
team_name = team.name
|
|
95
|
+
|
|
96
|
+
# Extract emails from team/members annotation
|
|
97
|
+
members = team.annotations["team/members"]
|
|
98
|
+
parse_email_list(members).each do |email|
|
|
99
|
+
index[email.downcase] = team_name
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Extract email from team/lead annotation
|
|
103
|
+
lead = team.annotations["team/lead"]
|
|
104
|
+
parse_email_list(lead).each do |email|
|
|
105
|
+
index[email.downcase] = team_name
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
index
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def build_name_index
|
|
113
|
+
index = {}
|
|
114
|
+
|
|
115
|
+
@teams.each do |team|
|
|
116
|
+
team_name = team.name
|
|
117
|
+
|
|
118
|
+
# Extract names from team/members annotation
|
|
119
|
+
members = team.annotations["team/members"]
|
|
120
|
+
parse_name_list(members).each do |name|
|
|
121
|
+
normalized = normalize_name(name)
|
|
122
|
+
index[normalized] = team_name if normalized && !normalized.empty?
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Extract name from team/lead annotation
|
|
126
|
+
lead = team.annotations["team/lead"]
|
|
127
|
+
parse_name_list(lead).each do |name|
|
|
128
|
+
normalized = normalize_name(name)
|
|
129
|
+
index[normalized] = team_name if normalized && !normalized.empty?
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
index
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Parse email addresses from team annotation
|
|
137
|
+
# Supports formats: "Name <email>", "email", or comma/newline separated lists
|
|
138
|
+
def parse_email_list(value)
|
|
139
|
+
return [] if value.nil? || value.empty?
|
|
140
|
+
|
|
141
|
+
emails = []
|
|
142
|
+
|
|
143
|
+
# Split by comma or newline
|
|
144
|
+
value.split(/[,\n]/).each do |entry|
|
|
145
|
+
entry = entry.strip
|
|
146
|
+
next if entry.empty?
|
|
147
|
+
|
|
148
|
+
# Try to extract email from "Name <email>" format
|
|
149
|
+
if (match = entry.match(/<([^>]+)>/))
|
|
150
|
+
emails << match[1].strip
|
|
151
|
+
elsif entry.include?("@")
|
|
152
|
+
# Plain email address
|
|
153
|
+
emails << entry
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
emails
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Parse names from team annotation
|
|
161
|
+
# Supports formats: "Name <email>", "First Last", or comma/newline separated lists
|
|
162
|
+
def parse_name_list(value)
|
|
163
|
+
return [] if value.nil? || value.empty?
|
|
164
|
+
|
|
165
|
+
names = []
|
|
166
|
+
|
|
167
|
+
# Split by comma or newline
|
|
168
|
+
value.split(/[,\n]/).each do |entry|
|
|
169
|
+
entry = entry.strip
|
|
170
|
+
next if entry.empty?
|
|
171
|
+
|
|
172
|
+
# Try to extract name from "Name <email>" format
|
|
173
|
+
if (match = entry.match(/^([^<]+)</))
|
|
174
|
+
name = match[1].strip
|
|
175
|
+
names << name unless name.empty?
|
|
176
|
+
elsif !entry.include?("@")
|
|
177
|
+
# Plain name (no email)
|
|
178
|
+
names << entry
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
names
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Normalize name for matching
|
|
186
|
+
# Converts to lowercase, removes extra spaces, handles common variations
|
|
187
|
+
def normalize_name(name)
|
|
188
|
+
return nil if name.nil?
|
|
189
|
+
|
|
190
|
+
name.to_s
|
|
191
|
+
.downcase
|
|
192
|
+
.gsub(/\s+/, " ")
|
|
193
|
+
.strip
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Define import module namespaces
|
|
4
|
+
module Archsight
|
|
5
|
+
module Import
|
|
6
|
+
module Handlers
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
require_relative "import/registry"
|
|
12
|
+
require_relative "import/handler"
|
|
13
|
+
require_relative "import/executor"
|
|
14
|
+
# Handlers are loaded by CLI's require_import_handlers method
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Analysis represents a declarative analysis script that validates or reports on architecture resources
|
|
4
|
+
class Archsight::Resources::Analysis < Archsight::Resources::Base
|
|
5
|
+
include_annotations :generated
|
|
6
|
+
|
|
7
|
+
description <<~MD
|
|
8
|
+
Represents a declarative analysis script for validating or reporting on architecture resources.
|
|
9
|
+
|
|
10
|
+
## Definition
|
|
11
|
+
|
|
12
|
+
An Analysis is a tool-specific resource type that defines Ruby scripts to run
|
|
13
|
+
against the architecture database. Scripts execute in a sandboxed environment
|
|
14
|
+
with access to instance traversal, annotation access, and reporting methods.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
Use Analysis to represent:
|
|
19
|
+
|
|
20
|
+
- Validation rules (team ownership, naming conventions)
|
|
21
|
+
- Architecture compliance checks
|
|
22
|
+
- Metrics collection and aggregation
|
|
23
|
+
- Custom reports and dashboards
|
|
24
|
+
|
|
25
|
+
## Script Environment
|
|
26
|
+
|
|
27
|
+
Scripts have access to:
|
|
28
|
+
|
|
29
|
+
- `each_instance(kind)` - iterate over all instances of a kind
|
|
30
|
+
- `instances(kind)` - get array of instances
|
|
31
|
+
- `instance(kind, name)` - get specific instance
|
|
32
|
+
- `outgoing(inst, kind)` / `incoming(inst, kind)` - relation traversal
|
|
33
|
+
- `outgoing_transitive(inst, kind)` / `incoming_transitive(inst, kind)` - transitive relations
|
|
34
|
+
- `annotation(inst, key)` - get annotation value
|
|
35
|
+
- `name(inst)` / `kind(inst)` - get instance metadata
|
|
36
|
+
- `query(query_string)` - execute a query
|
|
37
|
+
- `report(data, title:)` - output findings
|
|
38
|
+
- `warning(msg)` / `error(msg)` / `info(msg)` - log messages
|
|
39
|
+
MD
|
|
40
|
+
|
|
41
|
+
icon "clipboard-check"
|
|
42
|
+
layer "other"
|
|
43
|
+
|
|
44
|
+
# Handler selection (for future extensibility)
|
|
45
|
+
annotation "analysis/handler",
|
|
46
|
+
description: "Script handler type (only 'ruby' currently supported)",
|
|
47
|
+
title: "Handler",
|
|
48
|
+
enum: %w[ruby]
|
|
49
|
+
|
|
50
|
+
# Script content
|
|
51
|
+
annotation "analysis/script",
|
|
52
|
+
description: "Ruby script to execute in sandboxed environment",
|
|
53
|
+
title: "Script",
|
|
54
|
+
sidebar: false
|
|
55
|
+
|
|
56
|
+
# Description
|
|
57
|
+
annotation "analysis/description",
|
|
58
|
+
description: "Human-readable description of what this analysis validates",
|
|
59
|
+
title: "Description"
|
|
60
|
+
|
|
61
|
+
# Execution configuration
|
|
62
|
+
annotation "analysis/timeout",
|
|
63
|
+
description: "Maximum execution time (e.g., '30s', '5m')",
|
|
64
|
+
title: "Timeout"
|
|
65
|
+
|
|
66
|
+
# Output configuration
|
|
67
|
+
annotation "analysis/output",
|
|
68
|
+
description: "Output mode for results",
|
|
69
|
+
title: "Output",
|
|
70
|
+
enum: %w[console file]
|
|
71
|
+
|
|
72
|
+
annotation "analysis/outputPath",
|
|
73
|
+
description: "File path for output (when output mode is 'file')",
|
|
74
|
+
title: "Output Path",
|
|
75
|
+
sidebar: false
|
|
76
|
+
|
|
77
|
+
# Enabled flag
|
|
78
|
+
annotation "analysis/enabled",
|
|
79
|
+
description: "Whether this analysis is enabled",
|
|
80
|
+
title: "Enabled",
|
|
81
|
+
enum: %w[true false]
|
|
82
|
+
|
|
83
|
+
# Pattern annotation for custom configuration
|
|
84
|
+
annotation "analysis/config/*",
|
|
85
|
+
description: "Custom configuration values for the analysis script",
|
|
86
|
+
sidebar: false
|
|
87
|
+
|
|
88
|
+
# Dependency relation (analyses can depend on other analyses or imports)
|
|
89
|
+
relation :dependsOn, :imports, :Import
|
|
90
|
+
relation :dependsOn, :analyses, :Analysis
|
|
91
|
+
end
|
|
@@ -182,9 +182,9 @@ class Archsight::Resources::ApplicationComponent < Archsight::Resources::Base
|
|
|
182
182
|
%w[scc/estimatedCost scc/estimatedScheduleMonths scc/estimatedPeople].each do |anno_key|
|
|
183
183
|
computed_annotation anno_key,
|
|
184
184
|
title: "Total #{anno_key.split("/").last.split(/(?=[A-Z])/).map(&:capitalize).join(" ")}",
|
|
185
|
-
description: "Total estimated #{anno_key.split("/").last} from related
|
|
185
|
+
description: "Total estimated #{anno_key.split("/").last} from related repositories",
|
|
186
186
|
type: Integer do
|
|
187
|
-
sum(outgoing_transitive(:
|
|
187
|
+
sum(outgoing_transitive('TechnologyArtifact: artifact/type == "repo"'), anno_key)
|
|
188
188
|
end
|
|
189
189
|
end
|
|
190
190
|
|
|
@@ -39,45 +39,45 @@ class Archsight::Resources::ApplicationService < Archsight::Resources::Base
|
|
|
39
39
|
# Computed Annotations
|
|
40
40
|
computed_annotation "repository/artifacts/total",
|
|
41
41
|
title: "Total Git Repositories",
|
|
42
|
-
description: "Number of related git repositories
|
|
42
|
+
description: "Number of related git repositories",
|
|
43
43
|
type: Integer do
|
|
44
|
-
|
|
44
|
+
count(outgoing_transitive('TechnologyArtifact: artifact/type == "repo"'))
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
computed_annotation "repository/artifacts/active",
|
|
48
48
|
title: "Active Git Repositories",
|
|
49
|
-
description: "Number of
|
|
49
|
+
description: "Number of active git repositories",
|
|
50
50
|
type: Integer do
|
|
51
|
-
|
|
51
|
+
count(outgoing_transitive('TechnologyArtifact: artifact/type == "repo" & activity/status == "active"'))
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
computed_annotation "repository/artifacts/abandoned",
|
|
55
55
|
title: "Abandoned Git Repositories",
|
|
56
|
-
description: "Number of
|
|
56
|
+
description: "Number of abandoned git repositories",
|
|
57
57
|
type: Integer do
|
|
58
|
-
|
|
58
|
+
count(outgoing_transitive('TechnologyArtifact: artifact/type == "repo" & activity/status == "abandoned"'))
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
computed_annotation "repository/artifacts/highBusFactor",
|
|
62
62
|
title: "High Bus Factor Repositories",
|
|
63
|
-
description: "Number of active git repositories with high bus factor
|
|
63
|
+
description: "Number of active git repositories with high bus factor",
|
|
64
64
|
type: Integer do
|
|
65
|
-
|
|
65
|
+
count(outgoing_transitive('TechnologyArtifact: artifact/type == "repo" & activity/busFactor == "high"'))
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
computed_annotation "repository/artifacts/archived",
|
|
69
69
|
title: "Archived Repositories",
|
|
70
|
-
description: "Number of archived git repositories
|
|
70
|
+
description: "Number of archived git repositories",
|
|
71
71
|
type: Integer do
|
|
72
|
-
|
|
72
|
+
count(outgoing_transitive('TechnologyArtifact: artifact/type == "repo" & activity/status == "archived"'))
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
%w[scc/estimatedCost scc/estimatedScheduleMonths scc/estimatedPeople].each do |anno_key|
|
|
76
76
|
computed_annotation anno_key,
|
|
77
77
|
title: "Total #{anno_key.split("/").last.split(/(?=[A-Z])/).map(&:capitalize).join(" ")}",
|
|
78
|
-
description: "Total estimated #{anno_key.split("/").last} from related
|
|
78
|
+
description: "Total estimated #{anno_key.split("/").last} from related repositories",
|
|
79
79
|
type: Integer do
|
|
80
|
-
sum(outgoing_transitive(:
|
|
80
|
+
sum(outgoing_transitive('TechnologyArtifact: artifact/type == "repo"'), anno_key)
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
83
|
|
|
@@ -32,45 +32,45 @@ class Archsight::Resources::BusinessProduct < Archsight::Resources::Base
|
|
|
32
32
|
|
|
33
33
|
computed_annotation "repository/artifacts/total",
|
|
34
34
|
title: "Total Git Repositories",
|
|
35
|
-
description: "Number of related git repositories
|
|
35
|
+
description: "Number of related git repositories",
|
|
36
36
|
type: Integer do
|
|
37
|
-
|
|
37
|
+
count(outgoing_transitive('TechnologyArtifact: artifact/type == "repo"'))
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
computed_annotation "repository/artifacts/active",
|
|
41
41
|
title: "Active Git Repositories",
|
|
42
|
-
description: "Number of
|
|
42
|
+
description: "Number of active git repositories",
|
|
43
43
|
type: Integer do
|
|
44
|
-
|
|
44
|
+
count(outgoing_transitive('TechnologyArtifact: artifact/type == "repo" & activity/status == "active"'))
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
computed_annotation "repository/artifacts/abandoned",
|
|
48
48
|
title: "Abandoned Git Repositories",
|
|
49
|
-
description: "Number of
|
|
49
|
+
description: "Number of abandoned git repositories",
|
|
50
50
|
type: Integer do
|
|
51
|
-
|
|
51
|
+
count(outgoing_transitive('TechnologyArtifact: artifact/type == "repo" & activity/status == "abandoned"'))
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
computed_annotation "repository/artifacts/highBusFactor",
|
|
55
55
|
title: "High Bus Factor Repositories",
|
|
56
|
-
description: "Number of active git repositories with high bus factor
|
|
56
|
+
description: "Number of active git repositories with high bus factor",
|
|
57
57
|
type: Integer do
|
|
58
|
-
|
|
58
|
+
count(outgoing_transitive('TechnologyArtifact: artifact/type == "repo" & activity/busFactor == "high"'))
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
computed_annotation "repository/artifacts/archived",
|
|
62
62
|
title: "Archived Repositories",
|
|
63
|
-
description: "Number of archived git repositories
|
|
63
|
+
description: "Number of archived git repositories",
|
|
64
64
|
type: Integer do
|
|
65
|
-
|
|
65
|
+
count(outgoing_transitive('TechnologyArtifact: artifact/type == "repo" & activity/status == "archived"'))
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
%w[scc/estimatedCost scc/estimatedScheduleMonths scc/estimatedPeople].each do |anno_key|
|
|
69
69
|
computed_annotation anno_key,
|
|
70
70
|
title: "Total #{anno_key.split("/").last.split(/(?=[A-Z])/).map(&:capitalize).join(" ")}",
|
|
71
|
-
description: "Total estimated #{anno_key.split("/").last} from related
|
|
71
|
+
description: "Total estimated #{anno_key.split("/").last} from related repositories",
|
|
72
72
|
type: Integer do
|
|
73
|
-
sum(outgoing_transitive(:
|
|
73
|
+
sum(outgoing_transitive('TechnologyArtifact: artifact/type == "repo"'), anno_key)
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
|
|
@@ -38,7 +38,7 @@ class Archsight::Resources::DataObject < Archsight::Resources::Base
|
|
|
38
38
|
annotation "data/visibility",
|
|
39
39
|
description: "API visibility level",
|
|
40
40
|
title: "Visibility",
|
|
41
|
-
enum: %w[public private]
|
|
41
|
+
enum: %w[public private internal]
|
|
42
42
|
|
|
43
43
|
annotation "generated/variants",
|
|
44
44
|
description: "OpenAPI schema variants compacted into this DataObject",
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Import represents a data import task that generates architecture resources
|
|
4
|
+
class Archsight::Resources::Import < Archsight::Resources::Base
|
|
5
|
+
include_annotations :generated
|
|
6
|
+
|
|
7
|
+
description <<~MD
|
|
8
|
+
Represents an import task that generates architecture resources.
|
|
9
|
+
|
|
10
|
+
## Definition
|
|
11
|
+
|
|
12
|
+
An Import is a tool-specific resource type that defines how to synchronize
|
|
13
|
+
external data sources (GitLab, GitHub, ArgoCD, etc.) into the architecture
|
|
14
|
+
database. Imports can depend on other imports to ensure proper execution order.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
Use Import to represent:
|
|
19
|
+
|
|
20
|
+
- Repository synchronization tasks
|
|
21
|
+
- API data imports
|
|
22
|
+
- Cluster discovery
|
|
23
|
+
- External system integration
|
|
24
|
+
|
|
25
|
+
## Multi-Stage Imports
|
|
26
|
+
|
|
27
|
+
Imports can generate other Import resources, enabling multi-stage workflows:
|
|
28
|
+
|
|
29
|
+
1. GitLab import runs, discovers repositories
|
|
30
|
+
2. Generates Import resources for each repository
|
|
31
|
+
3. Repository imports run after GitLab import completes
|
|
32
|
+
4. Each repository import generates TechnologyArtifact resources
|
|
33
|
+
MD
|
|
34
|
+
|
|
35
|
+
icon "download"
|
|
36
|
+
layer "other"
|
|
37
|
+
|
|
38
|
+
# Handler selection
|
|
39
|
+
annotation "import/handler",
|
|
40
|
+
description: "Handler class name to execute this import",
|
|
41
|
+
title: "Handler", enum: %w[
|
|
42
|
+
gitlab github repository
|
|
43
|
+
rest-api rest-api-index
|
|
44
|
+
jira-discover jira-metrics
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
# Output configuration
|
|
48
|
+
annotation "import/outputPath",
|
|
49
|
+
description: "Output file path relative to resources root (e.g., 'imports/generated/gitlab.yaml')",
|
|
50
|
+
title: "Output Path"
|
|
51
|
+
|
|
52
|
+
# Control flags
|
|
53
|
+
annotation "import/enabled",
|
|
54
|
+
description: "Whether this import is enabled",
|
|
55
|
+
title: "Enabled",
|
|
56
|
+
enum: %w[true false]
|
|
57
|
+
|
|
58
|
+
annotation "import/priority",
|
|
59
|
+
description: "Execution priority (lower runs first among ready imports)",
|
|
60
|
+
title: "Priority",
|
|
61
|
+
type: Integer
|
|
62
|
+
|
|
63
|
+
annotation "import/cacheTime",
|
|
64
|
+
description: "Cache duration (e.g., '30m', '1h', '24h', '7d'). Set to 'never' to always run.",
|
|
65
|
+
title: "Cache Time"
|
|
66
|
+
|
|
67
|
+
# Handler-specific configuration (pattern annotation)
|
|
68
|
+
annotation "import/config/*",
|
|
69
|
+
description: "Handler-specific configuration values",
|
|
70
|
+
sidebar: false
|
|
71
|
+
|
|
72
|
+
# Generated resources relations - auto-tracked by handlers
|
|
73
|
+
# Dependencies are derived from the inverse of generates - if A generates B, B depends on A
|
|
74
|
+
relation :generates, :technologyArtifacts, :TechnologyArtifact
|
|
75
|
+
relation :generates, :applicationInterfaces, :ApplicationInterface
|
|
76
|
+
relation :generates, :dataObjects, :DataObject
|
|
77
|
+
relation :generates, :imports, :Import
|
|
78
|
+
relation :generates, :businessActors, :BusinessActor
|
|
79
|
+
end
|
|
@@ -72,8 +72,12 @@ class Archsight::Resources::TechnologyArtifact < Archsight::Resources::Base
|
|
|
72
72
|
annotation "activity/status",
|
|
73
73
|
description: "Repository activity status",
|
|
74
74
|
title: "Activity Status",
|
|
75
|
-
enum: %w[active abandoned bot-only archived],
|
|
75
|
+
enum: %w[active abandoned bot-only archived inaccessible empty no-code],
|
|
76
76
|
list: true
|
|
77
|
+
annotation "activity/reason",
|
|
78
|
+
description: "Reason for activity status (for non-standard statuses)",
|
|
79
|
+
title: "Status Reason",
|
|
80
|
+
sidebar: false
|
|
77
81
|
annotation "activity/busFactor",
|
|
78
82
|
description: "Bus factor assessment",
|
|
79
83
|
title: "Bus Factor",
|
|
@@ -83,6 +87,10 @@ class Archsight::Resources::TechnologyArtifact < Archsight::Resources::Base
|
|
|
83
87
|
description: "Date of first commit (repository creation)",
|
|
84
88
|
title: "Created",
|
|
85
89
|
type: Time
|
|
90
|
+
annotation "activity/lastHumanCommit",
|
|
91
|
+
description: "Date of last human commit (last activity)",
|
|
92
|
+
title: "Last Human Commit",
|
|
93
|
+
type: Time
|
|
86
94
|
annotation "agentic/tools",
|
|
87
95
|
description: "Agentic tools detected in repository",
|
|
88
96
|
title: "Agentic Tools",
|
|
@@ -121,7 +129,20 @@ class Archsight::Resources::TechnologyArtifact < Archsight::Resources::Base
|
|
|
121
129
|
annotation "repository/visibility",
|
|
122
130
|
description: "Repository visibility classification",
|
|
123
131
|
title: "Visibility",
|
|
124
|
-
enum: %w[internal open-source public]
|
|
132
|
+
enum: %w[private internal open-source public]
|
|
133
|
+
annotation "repository/recentTags",
|
|
134
|
+
description: "Recent git tags (releases)",
|
|
135
|
+
title: "Recent Tags",
|
|
136
|
+
format: :tag_list,
|
|
137
|
+
sidebar: false
|
|
138
|
+
annotation "repository/accessible",
|
|
139
|
+
description: "Whether repository is accessible for cloning",
|
|
140
|
+
title: "Accessible",
|
|
141
|
+
enum: %w[true false]
|
|
142
|
+
annotation "repository/error",
|
|
143
|
+
description: "Error message if repository is not accessible",
|
|
144
|
+
title: "Access Error",
|
|
145
|
+
sidebar: false
|
|
125
146
|
annotation "deployment/images",
|
|
126
147
|
description: "OCI container image names published by this repository",
|
|
127
148
|
title: "Container Images",
|
data/lib/archsight/version.rb
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sinatra/base"
|
|
4
|
+
require "sinatra/extension"
|
|
5
|
+
|
|
6
|
+
module Archsight; end
|
|
7
|
+
module Archsight::Web; end
|
|
8
|
+
module Archsight::Web::API; end
|
|
9
|
+
|
|
10
|
+
# Documentation UI for the REST API
|
|
11
|
+
module Archsight::Web::API::Docs
|
|
12
|
+
extend Sinatra::Extension
|
|
13
|
+
|
|
14
|
+
get "/api/docs" do
|
|
15
|
+
erb :api_docs
|
|
16
|
+
end
|
|
17
|
+
end
|