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,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sinatra/base"
|
|
4
|
+
require "sinatra/extension"
|
|
5
|
+
require_relative "json_helpers"
|
|
6
|
+
|
|
7
|
+
module Archsight; end
|
|
8
|
+
module Archsight::Web; end
|
|
9
|
+
module Archsight::Web::API; end
|
|
10
|
+
|
|
11
|
+
# REST API routes for Archsight
|
|
12
|
+
module Archsight::Web::API::Routes
|
|
13
|
+
extend Sinatra::Extension
|
|
14
|
+
|
|
15
|
+
helpers Archsight::Web::API::JsonHelpers
|
|
16
|
+
|
|
17
|
+
# GET /api/v1/kinds - List all resource kinds with counts
|
|
18
|
+
get "/api/v1/kinds" do
|
|
19
|
+
kinds = build_kinds_list
|
|
20
|
+
total = kinds.sum { |k| k[:instance_count] }
|
|
21
|
+
|
|
22
|
+
json_response(
|
|
23
|
+
{ total: kinds.length, total_instances: total, kinds: kinds }
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# GET /api/v1/kinds/:kind - List instances of a kind (paginated)
|
|
28
|
+
get "/api/v1/kinds/:kind" do
|
|
29
|
+
kind = params[:kind]
|
|
30
|
+
klass = Archsight::Resources[kind]
|
|
31
|
+
json_error("Kind '#{kind}' not found", status: 404, error_type: "NotFound") unless klass
|
|
32
|
+
|
|
33
|
+
instances = db.instances_by_kind(kind).values.sort_by(&:name)
|
|
34
|
+
limit, offset = parse_pagination_params
|
|
35
|
+
output = parse_output_param
|
|
36
|
+
pagination = paginate(instances, limit: limit, offset: offset)
|
|
37
|
+
|
|
38
|
+
resources = pagination[:items].map do |inst|
|
|
39
|
+
resource_summary(inst, output: output, omit_kind: true)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
json_response(build_list_response(kind, pagination, resources))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# GET /api/v1/kinds/:kind/instances/:name - Get instance details with relations
|
|
46
|
+
get "/api/v1/kinds/:kind/instances/:name" do
|
|
47
|
+
kind = params[:kind]
|
|
48
|
+
name = params[:name]
|
|
49
|
+
|
|
50
|
+
klass = Archsight::Resources[kind]
|
|
51
|
+
json_error("Kind '#{kind}' not found", status: 404, error_type: "NotFound") unless klass
|
|
52
|
+
|
|
53
|
+
instance = db.instance_by_kind(kind, name)
|
|
54
|
+
json_error("Instance '#{name}' not found", status: 404, error_type: "NotFound") unless instance
|
|
55
|
+
|
|
56
|
+
json_response(build_instance_response(kind, instance))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# GET /api/v1/search - Search with query language
|
|
60
|
+
get "/api/v1/search" do
|
|
61
|
+
query = params[:q]
|
|
62
|
+
json_error("Query parameter 'q' is required", status: 400, error_type: "BadRequest") unless query
|
|
63
|
+
|
|
64
|
+
start_time = Time.now
|
|
65
|
+
|
|
66
|
+
begin
|
|
67
|
+
parsed_query = Archsight::Query.parse(query)
|
|
68
|
+
results = parsed_query.filter(db)
|
|
69
|
+
query_time_ms = ((Time.now - start_time) * 1000).round(2)
|
|
70
|
+
output = parse_output_param
|
|
71
|
+
|
|
72
|
+
if output == "count"
|
|
73
|
+
json_response(build_count_response(query, results, query_time_ms))
|
|
74
|
+
else
|
|
75
|
+
json_response(build_search_response(query, results, parsed_query, query_time_ms))
|
|
76
|
+
end
|
|
77
|
+
rescue Archsight::Query::QueryError => e
|
|
78
|
+
json_error(e.message, status: 400, error_type: "QueryError", query: query)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# GET /api/v1/openapi.yaml - OpenAPI specification
|
|
83
|
+
get "/api/v1/openapi.yaml" do
|
|
84
|
+
content_type "text/yaml"
|
|
85
|
+
spec_path = File.join(__dir__, "openapi", "spec.yaml")
|
|
86
|
+
File.read(spec_path)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Convenience aliases with .json suffix
|
|
90
|
+
get "/kinds.json" do
|
|
91
|
+
call env.merge("PATH_INFO" => "/api/v1/kinds")
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
get "/kinds/:kind.json" do
|
|
95
|
+
call env.merge("PATH_INFO" => "/api/v1/kinds/#{params[:kind]}")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
get "/kinds/:kind/instances/:name.json" do
|
|
99
|
+
call env.merge("PATH_INFO" => "/api/v1/kinds/#{params[:kind]}/instances/#{params[:name]}")
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -14,6 +14,8 @@ require_relative "../documentation"
|
|
|
14
14
|
require_relative "../query"
|
|
15
15
|
require_relative "../resources"
|
|
16
16
|
require_relative "../mcp"
|
|
17
|
+
require_relative "api/routes"
|
|
18
|
+
require_relative "api/docs"
|
|
17
19
|
|
|
18
20
|
# Define the Web namespace before the class definition
|
|
19
21
|
module Archsight::Web; end
|
|
@@ -33,6 +35,22 @@ class Archsight::Web::Application < Sinatra::Base
|
|
|
33
35
|
dur = (Time.new - start) * 1000
|
|
34
36
|
puts format("== done %0.2f ms", dur) if database.verbose
|
|
35
37
|
end
|
|
38
|
+
|
|
39
|
+
# Configure application for the given environment
|
|
40
|
+
# @param env [Symbol] :development or :production
|
|
41
|
+
# @param logging [Boolean, nil] Override default logging setting
|
|
42
|
+
def configure_environment!(env, logging: nil)
|
|
43
|
+
set :environment, env
|
|
44
|
+
|
|
45
|
+
if env == :production
|
|
46
|
+
set :quiet, true
|
|
47
|
+
set :server_settings, { Silent: true }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Determine logging: CLI override > env default (prod=true, dev=false)
|
|
51
|
+
enable_logging = logging.nil? ? (env == :production) : logging
|
|
52
|
+
use Rack::CommonLogger, $stdout if enable_logging
|
|
53
|
+
end
|
|
36
54
|
end
|
|
37
55
|
|
|
38
56
|
configure do
|
|
@@ -40,6 +58,7 @@ class Archsight::Web::Application < Sinatra::Base
|
|
|
40
58
|
set :public_folder, File.join(__dir__, "public")
|
|
41
59
|
set :haml, format: :html5
|
|
42
60
|
set :server, :puma
|
|
61
|
+
set :reload_enabled, true
|
|
43
62
|
end
|
|
44
63
|
|
|
45
64
|
# MCP Server setup
|
|
@@ -63,11 +82,27 @@ class Archsight::Web::Application < Sinatra::Base
|
|
|
63
82
|
|
|
64
83
|
helpers Archsight::GraphvisHelper, Archsight::GraphvisRenderer, Archsight::Helpers
|
|
65
84
|
|
|
85
|
+
# Register API modules
|
|
86
|
+
register Archsight::Web::API::Routes
|
|
87
|
+
register Archsight::Web::API::Docs
|
|
88
|
+
|
|
66
89
|
helpers do
|
|
67
90
|
def db
|
|
68
91
|
Archsight::Web::Application.database
|
|
69
92
|
end
|
|
70
93
|
|
|
94
|
+
def reload_enabled?
|
|
95
|
+
settings.reload_enabled
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def production?
|
|
99
|
+
settings.environment == :production
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def development?
|
|
103
|
+
settings.environment == :development
|
|
104
|
+
end
|
|
105
|
+
|
|
71
106
|
# Render markdown to HTML with optional URL resolution for repository content
|
|
72
107
|
# @param data [String] Markdown content
|
|
73
108
|
# @param git_url [String, nil] Git URL for resolving relative paths (e.g., for README images)
|
|
@@ -98,25 +133,6 @@ class Archsight::Web::Application < Sinatra::Base
|
|
|
98
133
|
end
|
|
99
134
|
end
|
|
100
135
|
|
|
101
|
-
def to_dollar(num)
|
|
102
|
-
# Round to 2 decimals first (important for floating‑point edge cases)
|
|
103
|
-
rounded = (num * 100).round / 100.0
|
|
104
|
-
# Insert commas every three digits left of the decimal point
|
|
105
|
-
parts = format("%.2f", rounded).split(".")
|
|
106
|
-
parts[0] = parts[0].reverse.scan(/\d{1,3}/).join(",").reverse
|
|
107
|
-
"$#{parts.join(".")}"
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def http_git(repo_url)
|
|
111
|
-
repo_url.gsub(/.git$/, "")
|
|
112
|
-
.gsub(":", "/")
|
|
113
|
-
.gsub("git@", "https://")
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def number_with_delimiter(num)
|
|
117
|
-
num.to_s.reverse.scan(/\d{1,3}/).join(",").reverse
|
|
118
|
-
end
|
|
119
|
-
|
|
120
136
|
# Generate asset path with cache-busting query string based on file mtime
|
|
121
137
|
def asset_path(path)
|
|
122
138
|
file_path = File.join(settings.public_folder, path)
|
|
@@ -128,30 +144,9 @@ class Archsight::Web::Application < Sinatra::Base
|
|
|
128
144
|
end
|
|
129
145
|
end
|
|
130
146
|
|
|
131
|
-
#
|
|
132
|
-
def
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
time = timestamp.is_a?(Time) ? timestamp : Time.parse(timestamp.to_s)
|
|
136
|
-
seconds = (Time.now - time).to_i
|
|
137
|
-
|
|
138
|
-
units = [
|
|
139
|
-
[60, "second"],
|
|
140
|
-
[60, "minute"],
|
|
141
|
-
[24, "hour"],
|
|
142
|
-
[7, "day"],
|
|
143
|
-
[4, "week"],
|
|
144
|
-
[12, "month"],
|
|
145
|
-
[Float::INFINITY, "year"]
|
|
146
|
-
]
|
|
147
|
-
|
|
148
|
-
value = seconds
|
|
149
|
-
units.each do |divisor, unit|
|
|
150
|
-
return "just now" if unit == "second" && value < 10
|
|
151
|
-
return "#{value} #{unit}#{"s" if value != 1} ago" if value < divisor
|
|
152
|
-
|
|
153
|
-
value /= divisor
|
|
154
|
-
end
|
|
147
|
+
# Wrapper for render_analysis_section that provides markdown renderer
|
|
148
|
+
def render_analysis_section(section)
|
|
149
|
+
Archsight::Helpers.render_analysis_section(section, markdown_renderer: method(:markdown))
|
|
155
150
|
end
|
|
156
151
|
end
|
|
157
152
|
|
|
@@ -160,6 +155,8 @@ class Archsight::Web::Application < Sinatra::Base
|
|
|
160
155
|
end
|
|
161
156
|
|
|
162
157
|
get "/reload" do
|
|
158
|
+
halt 404, "Reload is disabled" unless settings.reload_enabled
|
|
159
|
+
|
|
163
160
|
Archsight::Web::Application.reload!
|
|
164
161
|
if params["redirect"]&.start_with?("/")
|
|
165
162
|
redirect params["redirect"]
|
|
@@ -289,4 +286,30 @@ class Archsight::Web::Application < Sinatra::Base
|
|
|
289
286
|
content_type "text/plain"
|
|
290
287
|
create_graph_one(db, @kind, @instance, :draw_dot)
|
|
291
288
|
end
|
|
289
|
+
|
|
290
|
+
# Execute an Analysis and return HTML results
|
|
291
|
+
post "/kinds/Analysis/instances/:instance/execute" do
|
|
292
|
+
require "archsight/analysis"
|
|
293
|
+
|
|
294
|
+
@instance = params["instance"]
|
|
295
|
+
analysis = db.instance_by_kind("Analysis", @instance)
|
|
296
|
+
|
|
297
|
+
unless analysis
|
|
298
|
+
return haml_inline('.analysis-error
|
|
299
|
+
%i.iconoir-warning-triangle
|
|
300
|
+
Analysis not found: #{@instance}')
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
executor = Archsight::Analysis::Executor.new(db)
|
|
304
|
+
result = executor.execute(analysis)
|
|
305
|
+
|
|
306
|
+
haml :"partials/instance/_analysis_result", locals: { result: result }
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
private
|
|
310
|
+
|
|
311
|
+
# Helper for inline HAML rendering
|
|
312
|
+
def haml_inline(template)
|
|
313
|
+
haml template
|
|
314
|
+
end
|
|
292
315
|
end
|