archsight 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/docs/computed_annotations.md +7 -5
- data/docs/licenses.md +1 -2
- data/docs/modeling.md +5 -1
- data/docs/pages.md +63 -6
- data/docs/search.md +17 -0
- data/lib/archsight/annotations/annotation.rb +5 -4
- data/lib/archsight/annotations/computed.rb +5 -1
- data/lib/archsight/annotations/relation_resolver.rb +96 -79
- data/lib/archsight/cli.rb +3 -2
- data/lib/archsight/database.rb +2 -1
- data/lib/archsight/documentation.rb +2 -1
- data/lib/archsight/export/confluence/exporter.rb +11 -2
- data/lib/archsight/export/confluence/storage.rb +99 -8
- data/lib/archsight/export/confluence/tables.rb +78 -0
- data/lib/archsight/helpers/fenced_blocks.rb +61 -0
- data/lib/archsight/helpers/requirements_blocks.rb +90 -0
- data/lib/archsight/helpers/view_blocks.rb +108 -0
- data/lib/archsight/helpers/wiki_links.rb +39 -5
- data/lib/archsight/helpers.rb +3 -0
- data/lib/archsight/import/handlers/go_grapher.rb +4 -1
- data/lib/archsight/import/handlers/go_module_parser.rb +4 -1
- data/lib/archsight/linter.rb +25 -3
- data/lib/archsight/mcp/base.rb +38 -0
- data/lib/archsight/requirements.rb +70 -0
- data/lib/archsight/resources/analysis.rb +2 -1
- data/lib/archsight/resources/application_component.rb +5 -3
- data/lib/archsight/resources/application_interface.rb +4 -2
- data/lib/archsight/resources/application_service.rb +4 -3
- data/lib/archsight/resources/base.rb +27 -12
- data/lib/archsight/resources/business_actor.rb +5 -3
- data/lib/archsight/resources/business_product.rb +4 -3
- data/lib/archsight/resources/business_requirement.rb +4 -3
- data/lib/archsight/resources/compliance_evidence.rb +4 -2
- data/lib/archsight/resources/data_object.rb +2 -1
- data/lib/archsight/resources/import.rb +5 -2
- data/lib/archsight/resources/page.rb +4 -2
- data/lib/archsight/resources/technology_artifact.rb +4 -3
- data/lib/archsight/resources/technology_node.rb +1 -1
- data/lib/archsight/resources/technology_service.rb +4 -0
- data/lib/archsight/resources/technology_system_software.rb +4 -0
- data/lib/archsight/resources/view.rb +2 -1
- data/lib/archsight/version.rb +1 -1
- data/lib/archsight/view_table.rb +102 -0
- data/lib/archsight/web/api/json_helpers.rb +7 -5
- data/lib/archsight/web/api/openapi/spec.yaml +135 -2
- data/lib/archsight/web/api/requirements_helpers.rb +26 -0
- data/lib/archsight/web/api/routes.rb +19 -0
- data/lib/archsight/web/application.rb +6 -1
- data/lib/archsight/web/public/vue/ApiDocsPage-D-cPRZCT.js +1 -0
- data/lib/archsight/web/public/vue/ApiDocsPage-DZ0fa5-h.css +1 -0
- data/lib/archsight/web/public/vue/{DocPage-uaT8CdFm.js → DocPage-DK6vNDbF.js} +1 -1
- data/lib/archsight/web/public/vue/EditorPage-BoJpQaVw.js +35 -0
- data/lib/archsight/web/public/vue/EditorPage-CbG4mc9T.css +1 -0
- data/lib/archsight/web/public/vue/ErrorPage-Ck9izEUS.css +1 -0
- data/lib/archsight/web/public/vue/ErrorPage-PGyjdtEf.js +2 -0
- data/lib/archsight/web/public/vue/GraphView-BLiKR4zP.js +1 -0
- data/lib/archsight/web/public/vue/GraphView-BvWAbUAl.css +1 -0
- data/lib/archsight/web/public/vue/HomePage-C0lR8i2C.js +2 -0
- data/lib/archsight/web/public/vue/InstanceRouter-60Tt3ZNM.css +1 -0
- data/lib/archsight/web/public/vue/InstanceRouter-D3W2jJHV.js +1 -0
- data/lib/archsight/web/public/vue/KindList-BlsaRBNO.js +1 -0
- data/lib/archsight/web/public/vue/PageView-9MgHtrgl.js +1 -0
- data/lib/archsight/web/public/vue/QueryError-3EqjpbyV.css +1 -0
- data/lib/archsight/web/public/vue/QueryError-D1FL1xgA.js +1 -0
- data/lib/archsight/web/public/vue/ResourceList-B0FLaFR3.css +1 -0
- data/lib/archsight/web/public/vue/ResourceList-vkgFyeOY.js +2 -0
- data/lib/archsight/web/public/vue/SearchResults-Cl3O_OEr.js +1 -0
- data/lib/archsight/web/public/vue/SearchResults-DiW5XVYW.css +1 -0
- data/lib/archsight/web/public/vue/WikiPage-C-8SG67a.css +1 -0
- data/lib/archsight/web/public/vue/WikiPage-CeCQBTDS.js +13 -0
- data/lib/archsight/web/public/vue/architecture-7GRP2DOG-BfA1TPxQ.js +1 -0
- data/lib/archsight/web/public/vue/cynefin-OW5HDTMX-BmgdUKVD.js +1 -0
- data/lib/archsight/web/public/vue/eventmodeling-NTZA5JFV-DbXDvAWF.js +1 -0
- data/lib/archsight/web/public/vue/gitGraph-4MIJSDKK-BkVhrKS1.js +1 -0
- data/lib/archsight/web/public/vue/index-D7m61Ahx.js +3 -0
- data/lib/archsight/web/public/vue/index-Dbx3MXWG.css +1 -0
- data/lib/archsight/web/public/vue/info-A6RAGUB7-BIlCVuUY.js +1 -0
- data/lib/archsight/web/public/vue/mermaid-BjEi5URd.js +3334 -0
- data/lib/archsight/web/public/vue/packet-AYTQ26CC-eoQTjY3j.js +1 -0
- data/lib/archsight/web/public/vue/pie-WAS4IAKB-DEOotGhh.js +1 -0
- data/lib/archsight/web/public/vue/radar-RG4KPBEZ-C2yucuUN.js +1 -0
- data/lib/archsight/web/public/vue/railroad-74A4TZTK-CzONK5VY.js +1 -0
- data/lib/archsight/web/public/vue/railroad-abnf-HS5TGJTU-9zRPsDsx.js +1 -0
- data/lib/archsight/web/public/vue/railroad-ebnf-LZEXJU2U-BJ4dkz9l.js +1 -0
- data/lib/archsight/web/public/vue/railroad-peg-WCYAUIDC-DFy_rqAj.js +1 -0
- data/lib/archsight/web/public/vue/treeView-Q6P3EWNA-2cq5CyD2.js +1 -0
- data/lib/archsight/web/public/vue/treemap-WGGIJYW6-DK-e9Ez4.js +1 -0
- data/lib/archsight/web/public/vue/{useGraphviz-C71SdG-N.js → useGraphviz-DweKV7Kg.js} +1 -1
- data/lib/archsight/web/public/vue/wardley-WFR3VGLG-BQwqUqfB.js +1 -0
- data/lib/archsight/web/public/vue.html +3 -3
- data/lib/archsight.rb +2 -0
- metadata +49 -40
- data/lib/archsight/web/public/vue/ApiDocsPage-C0y953v0.css +0 -1
- data/lib/archsight/web/public/vue/ApiDocsPage-C_4tAWis.js +0 -1
- data/lib/archsight/web/public/vue/EditorPage-C557BJC-.js +0 -35
- data/lib/archsight/web/public/vue/EditorPage-df5N-p21.css +0 -1
- data/lib/archsight/web/public/vue/ErrorPage-Vdebmife.js +0 -2
- data/lib/archsight/web/public/vue/ErrorPage-uMDnfY5_.css +0 -1
- data/lib/archsight/web/public/vue/GraphView-BduUql2N.js +0 -1
- data/lib/archsight/web/public/vue/GraphView-Cj2V2stN.css +0 -1
- data/lib/archsight/web/public/vue/HomePage-BHUTg8Ap.js +0 -2
- data/lib/archsight/web/public/vue/InstanceRouter-1lSigA58.js +0 -1
- data/lib/archsight/web/public/vue/InstanceRouter-Di7f3Rya.css +0 -1
- data/lib/archsight/web/public/vue/KindList-BDZs0j6d.js +0 -1
- data/lib/archsight/web/public/vue/PageView-BYzJDwof.js +0 -1
- data/lib/archsight/web/public/vue/ResourceList-CnbhU9wm.js +0 -1
- data/lib/archsight/web/public/vue/ResourceList-xyBwu7fh.css +0 -1
- data/lib/archsight/web/public/vue/SearchResults-CNhf6VOx.js +0 -1
- data/lib/archsight/web/public/vue/SearchResults-DOHzqAy3.css +0 -1
- data/lib/archsight/web/public/vue/WikiPage-DbmWkM7W.js +0 -13
- data/lib/archsight/web/public/vue/WikiPage-GV67QmNJ.css +0 -1
- data/lib/archsight/web/public/vue/architecture-TIHT7OUA-Bf-MWFmn.js +0 -1
- data/lib/archsight/web/public/vue/cynefin-VYW2F7L2-CkKt8qqs.js +0 -1
- data/lib/archsight/web/public/vue/eventmodeling-45OFAUF4-NwSTYPHj.js +0 -1
- data/lib/archsight/web/public/vue/gitGraph-TEB2WS4Q-DLRegrRG.js +0 -1
- data/lib/archsight/web/public/vue/index-CyVWObLU.js +0 -3
- data/lib/archsight/web/public/vue/index-DtKeHT3S.css +0 -1
- data/lib/archsight/web/public/vue/info-DKCQHKI2-EV39NzoN.js +0 -1
- data/lib/archsight/web/public/vue/mermaid-BMkGfnhm.js +0 -3279
- data/lib/archsight/web/public/vue/packet-7NZHBO7P-USljV3MZ.js +0 -1
- data/lib/archsight/web/public/vue/pie-RZYD4A2V-DChciBW2.js +0 -1
- data/lib/archsight/web/public/vue/radar-I7S5WNFK-CgGJdW1t.js +0 -1
- data/lib/archsight/web/public/vue/railroad-3IZDKUUU-Dy43vF5c.js +0 -1
- data/lib/archsight/web/public/vue/railroad-abnf-AHOZXSZD-BhsTH-sE.js +0 -1
- data/lib/archsight/web/public/vue/railroad-ebnf-EBAXGLYW-BBTd_u0u.js +0 -1
- data/lib/archsight/web/public/vue/railroad-peg-LSFZ7HO6-BiNTEzf0.js +0 -1
- data/lib/archsight/web/public/vue/treeView-QDETBFTQ-BfnCcf-q.js +0 -1
- data/lib/archsight/web/public/vue/treemap-6X3UGDF4-BsAORhvk.js +0 -1
- data/lib/archsight/web/public/vue/wardley-OPB4EBWU-DQvnEhRj.js +0 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Archsight
|
|
4
|
+
# The result of a view as a plain table (columns and rows of text), for places that cannot run the frontend, such
|
|
5
|
+
# as the Confluence export. It follows what the web UI shows for a View (`ViewResults.vue`, `ResourceList.vue`):
|
|
6
|
+
# a Name column, a Kind column unless the view is `list:name`, one column per `view/fields` entry that is not
|
|
7
|
+
# `name` or `kind`, rows sorted by `view/sort`.
|
|
8
|
+
module ViewTable
|
|
9
|
+
# Rows beyond this are cut (the table says how many); a Confluence page is no place for thousands of rows
|
|
10
|
+
LIMIT = 200
|
|
11
|
+
|
|
12
|
+
# @param as [Symbol] :text, :status or :priority (a requirement's, shown as a lozenge) or :markdown
|
|
13
|
+
# @param resources [Array] resources the cell names (shown one per line, linked where a target exists)
|
|
14
|
+
Cell = Struct.new(:text, :resources, :as, keyword_init: true) do
|
|
15
|
+
def initialize(text: "", resources: [], as: :text) = super
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @param title [String] shown above the table, may be empty
|
|
19
|
+
# @param columns [Array<String>] header texts
|
|
20
|
+
# @param rows [Array<Array<Cell>>] at most LIMIT
|
|
21
|
+
# @param total [Integer] number of rows before cutting
|
|
22
|
+
Table = Struct.new(:title, :columns, :rows, :total, keyword_init: true) do
|
|
23
|
+
def cut = total - rows.length
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
IDENTITY_FIELDS = %w[name kind].freeze
|
|
27
|
+
|
|
28
|
+
module_function
|
|
29
|
+
|
|
30
|
+
# @raise [Archsight::Query::QueryError] if the query does not parse
|
|
31
|
+
def build(database, query:, fields: [], sort: [], show_kind: true, title: "", limit: LIMIT)
|
|
32
|
+
resources = sort_resources(Archsight::Query.parse(query).filter(database), sort)
|
|
33
|
+
columns = fields.reject { |f| IDENTITY_FIELDS.include?(f) }
|
|
34
|
+
header = ["Name", (show_kind ? "Kind" : nil), *columns.map { |f| column_title(f) }].compact
|
|
35
|
+
rows = resources.first(limit).map do |resource|
|
|
36
|
+
[Cell.new(text: resource.name, resources: [resource]),
|
|
37
|
+
(Cell.new(text: kind_of(resource)) if show_kind),
|
|
38
|
+
*columns.map { |f| Cell.new(text: resource.annotations[f].to_s) }].compact
|
|
39
|
+
end
|
|
40
|
+
Table.new(title: title, columns: header, rows: rows, total: resources.length)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# The table of a ```view block (see Helpers::ViewBlocks)
|
|
44
|
+
# @raise [Helpers::ViewBlocks::Error, Archsight::Query::QueryError]
|
|
45
|
+
def from_block(database, source)
|
|
46
|
+
spec = Helpers::ViewBlocks.parse(source)
|
|
47
|
+
build(database, query: spec[:query], fields: spec[:fields], sort: spec[:sort], show_kind: spec[:type] == "list:name+kind", title: spec[:title])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# The table of a View resource
|
|
51
|
+
# @raise [Helpers::ViewBlocks::Error, Archsight::Query::QueryError]
|
|
52
|
+
def from_view(database, view)
|
|
53
|
+
annotations = view.annotations
|
|
54
|
+
query = annotations["view/query"].to_s.strip
|
|
55
|
+
raise Helpers::ViewBlocks::Error, "view #{view.name} has no query" if query.empty?
|
|
56
|
+
|
|
57
|
+
build(database, query: query, fields: list(annotations["view/fields"]), sort: list(annotations["view/sort"]),
|
|
58
|
+
show_kind: (annotations["view/type"] || "list:name+kind") == "list:name+kind", title: view.name)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# "scc/language/Go/loc" -> "Go loc", "activity/createdAt" -> "Activity created At" (as ResourceList.vue titles columns)
|
|
62
|
+
def column_title(key)
|
|
63
|
+
segments = key.split("/")
|
|
64
|
+
title = segments.length >= 2 ? "#{segments[-2]} #{segments[-1]}" : segments.last.to_s
|
|
65
|
+
title.gsub(/([a-z])([A-Z])/, '\1 \2').sub(/\A./, &:upcase)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def kind_of(resource) = resource.class.name.split("::").last
|
|
69
|
+
|
|
70
|
+
def list(value) = value.to_s.split(",").map(&:strip).reject(&:empty?)
|
|
71
|
+
|
|
72
|
+
# `view/sort` fields: name, kind or an annotation, "-" for descending; numbers inside text compare as numbers.
|
|
73
|
+
# Without a sort the order is by name, like the search API.
|
|
74
|
+
def sort_resources(resources, sort)
|
|
75
|
+
return resources.sort_by(&:name) if sort.empty?
|
|
76
|
+
|
|
77
|
+
resources.sort { |left, right| compare(left, right, sort) }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def compare(left, right, sort)
|
|
81
|
+
sort.each do |field|
|
|
82
|
+
key = field.delete_prefix("-")
|
|
83
|
+
order = natural(value_of(left, key)) <=> natural(value_of(right, key))
|
|
84
|
+
return field.start_with?("-") ? -order : order unless order.zero?
|
|
85
|
+
end
|
|
86
|
+
0
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def value_of(resource, key)
|
|
90
|
+
case key
|
|
91
|
+
when "name" then resource.name
|
|
92
|
+
when "kind" then kind_of(resource)
|
|
93
|
+
else resource.annotations[key].to_s
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# A key for sorting text with numbers in it the way people expect (item2 < item10, case-insensitive first)
|
|
98
|
+
def natural(text)
|
|
99
|
+
[text.scan(/\d+|\D+/).map { |part| part.match?(/\A\d/) ? [0, part.to_i] : [1, part.downcase] }, text]
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -130,13 +130,11 @@ module Archsight::Web::API::JsonHelpers
|
|
|
130
130
|
end
|
|
131
131
|
|
|
132
132
|
def build_count_response(query, results, query_time_ms)
|
|
133
|
-
by_kind = results.group_by { |r| r.class.to_s.split("::").last }
|
|
134
|
-
.transform_values(&:length)
|
|
135
133
|
{
|
|
136
134
|
query: query,
|
|
137
135
|
total: results.length,
|
|
138
136
|
query_time_ms: query_time_ms,
|
|
139
|
-
by_kind:
|
|
137
|
+
by_kind: Archsight::MCP.count_by_kind(results)
|
|
140
138
|
}
|
|
141
139
|
end
|
|
142
140
|
|
|
@@ -144,10 +142,13 @@ module Archsight::Web::API::JsonHelpers
|
|
|
144
142
|
limit, offset = parse_pagination_params
|
|
145
143
|
output = parse_output_param
|
|
146
144
|
sorted = results.sort_by(&:name)
|
|
147
|
-
|
|
145
|
+
# `kind` narrows the hits to one kind; by_kind always counts all hits so the facets stay complete
|
|
146
|
+
kind = params[:kind].to_s
|
|
147
|
+
visible = kind.empty? ? sorted : sorted.select { |r| Archsight::MCP.kind_of(r) == kind }
|
|
148
|
+
pagination = paginate(visible, limit: limit, offset: offset)
|
|
148
149
|
|
|
149
150
|
instances = pagination[:items].map do |r|
|
|
150
|
-
resource_summary(r, output: output, omit_kind: false)
|
|
151
|
+
resource_summary(r, output: output, omit_kind: false).merge(highlights: Archsight::MCP.highlights(r))
|
|
151
152
|
end
|
|
152
153
|
|
|
153
154
|
{
|
|
@@ -157,6 +158,7 @@ module Archsight::Web::API::JsonHelpers
|
|
|
157
158
|
offset: pagination[:offset],
|
|
158
159
|
count: instances.length,
|
|
159
160
|
query_time_ms: query_time_ms,
|
|
161
|
+
by_kind: Archsight::MCP.count_by_kind(results),
|
|
160
162
|
instances: instances
|
|
161
163
|
}
|
|
162
164
|
end
|
|
@@ -650,7 +650,12 @@ paths:
|
|
|
650
650
|
See the API description for full query language documentation.
|
|
651
651
|
|
|
652
652
|
Response includes: kind, name, metadata, spec (no relations/references).
|
|
653
|
-
Additional fields: query, query_time_ms, by_kind
|
|
653
|
+
Additional fields: query, query_time_ms, by_kind.
|
|
654
|
+
|
|
655
|
+
Every hit carries `highlights`: the values of the kind's summary attributes (annotations marked
|
|
656
|
+
`summary: true` in the kind definition, at most 3 per kind, computed ones included) that are set.
|
|
657
|
+
`by_kind` counts all hits of the query per kind, also when `kind` narrows the list, so a client can show
|
|
658
|
+
facets and let the user pick one.
|
|
654
659
|
operationId: search
|
|
655
660
|
tags:
|
|
656
661
|
- Search
|
|
@@ -674,6 +679,13 @@ paths:
|
|
|
674
679
|
orphan:
|
|
675
680
|
value: '<- none'
|
|
676
681
|
summary: Find orphan resources
|
|
682
|
+
- name: kind
|
|
683
|
+
in: query
|
|
684
|
+
required: false
|
|
685
|
+
description: Only return hits of this kind (`total` and paging follow; `by_kind` still counts all hits)
|
|
686
|
+
schema:
|
|
687
|
+
type: string
|
|
688
|
+
example: TechnologyArtifact
|
|
677
689
|
- $ref: '#/components/parameters/limit'
|
|
678
690
|
- $ref: '#/components/parameters/offset'
|
|
679
691
|
- $ref: '#/components/parameters/output'
|
|
@@ -691,6 +703,88 @@ paths:
|
|
|
691
703
|
schema:
|
|
692
704
|
$ref: '#/components/schemas/QueryError'
|
|
693
705
|
|
|
706
|
+
/api/v1/requirements:
|
|
707
|
+
get:
|
|
708
|
+
summary: Business requirements of a selection of resources
|
|
709
|
+
description: |
|
|
710
|
+
The business requirements that the resources selected by `of` realize (`implemented`), partially realize
|
|
711
|
+
(`partial`) or plan (`planned`), merged over all of them: one entry per requirement with the best status and
|
|
712
|
+
the resources it comes from (`by`). Sorted by priority (must, should, may, none), then name. `story` is
|
|
713
|
+
rendered HTML.
|
|
714
|
+
operationId: getRequirements
|
|
715
|
+
tags:
|
|
716
|
+
- Search
|
|
717
|
+
parameters:
|
|
718
|
+
- name: of
|
|
719
|
+
in: query
|
|
720
|
+
required: true
|
|
721
|
+
description: Query selecting the resources whose requirements are listed
|
|
722
|
+
schema:
|
|
723
|
+
type: string
|
|
724
|
+
example: 'ApplicationService: name =~ "Backup"'
|
|
725
|
+
- name: priority
|
|
726
|
+
in: query
|
|
727
|
+
required: false
|
|
728
|
+
description: Comma separated list, only requirements with one of these priorities
|
|
729
|
+
schema:
|
|
730
|
+
type: string
|
|
731
|
+
example: must,should
|
|
732
|
+
- name: status
|
|
733
|
+
in: query
|
|
734
|
+
required: false
|
|
735
|
+
description: Comma separated list, only requirements with one of these statuses
|
|
736
|
+
schema:
|
|
737
|
+
type: string
|
|
738
|
+
example: implemented,partial
|
|
739
|
+
responses:
|
|
740
|
+
'200':
|
|
741
|
+
description: The requirements
|
|
742
|
+
content:
|
|
743
|
+
application/json:
|
|
744
|
+
schema:
|
|
745
|
+
type: object
|
|
746
|
+
properties:
|
|
747
|
+
query:
|
|
748
|
+
type: string
|
|
749
|
+
total:
|
|
750
|
+
type: integer
|
|
751
|
+
query_time_ms:
|
|
752
|
+
type: number
|
|
753
|
+
requirements:
|
|
754
|
+
type: array
|
|
755
|
+
items:
|
|
756
|
+
type: object
|
|
757
|
+
properties:
|
|
758
|
+
name:
|
|
759
|
+
type: string
|
|
760
|
+
status:
|
|
761
|
+
type: string
|
|
762
|
+
enum: [implemented, partial, planned]
|
|
763
|
+
priority:
|
|
764
|
+
type: string
|
|
765
|
+
nullable: true
|
|
766
|
+
story:
|
|
767
|
+
type: string
|
|
768
|
+
nullable: true
|
|
769
|
+
by:
|
|
770
|
+
type: array
|
|
771
|
+
items:
|
|
772
|
+
type: object
|
|
773
|
+
properties:
|
|
774
|
+
kind:
|
|
775
|
+
type: string
|
|
776
|
+
name:
|
|
777
|
+
type: string
|
|
778
|
+
status:
|
|
779
|
+
type: string
|
|
780
|
+
enum: [implemented, partial, planned]
|
|
781
|
+
'400':
|
|
782
|
+
description: Invalid query, or an unknown priority or status
|
|
783
|
+
content:
|
|
784
|
+
application/json:
|
|
785
|
+
schema:
|
|
786
|
+
$ref: '#/components/schemas/QueryError'
|
|
787
|
+
|
|
694
788
|
/dot:
|
|
695
789
|
get:
|
|
696
790
|
summary: Get full architecture graph as DOT
|
|
@@ -1225,6 +1319,40 @@ components:
|
|
|
1225
1319
|
items:
|
|
1226
1320
|
$ref: '#/components/schemas/Kind'
|
|
1227
1321
|
|
|
1322
|
+
Highlight:
|
|
1323
|
+
type: object
|
|
1324
|
+
required:
|
|
1325
|
+
- key
|
|
1326
|
+
- title
|
|
1327
|
+
- value
|
|
1328
|
+
properties:
|
|
1329
|
+
key:
|
|
1330
|
+
type: string
|
|
1331
|
+
description: Annotation key, exactly as the query language takes it
|
|
1332
|
+
example: activity/status
|
|
1333
|
+
title:
|
|
1334
|
+
type: string
|
|
1335
|
+
description: Display title of the annotation
|
|
1336
|
+
example: Activity Status
|
|
1337
|
+
value:
|
|
1338
|
+
description: The value; an array for list annotations, a number for Integer/Float typed ones
|
|
1339
|
+
oneOf:
|
|
1340
|
+
- type: string
|
|
1341
|
+
- type: number
|
|
1342
|
+
- type: array
|
|
1343
|
+
items:
|
|
1344
|
+
type: string
|
|
1345
|
+
format:
|
|
1346
|
+
type: string
|
|
1347
|
+
nullable: true
|
|
1348
|
+
description: Rendering hint of the annotation (tag_word, tag_list, markdown, link, ...)
|
|
1349
|
+
example: tag_word
|
|
1350
|
+
type:
|
|
1351
|
+
type: string
|
|
1352
|
+
nullable: true
|
|
1353
|
+
description: Short name of the value type when the annotation declares one (Integer, Float, Time, Person, ...)
|
|
1354
|
+
example: Integer
|
|
1355
|
+
|
|
1228
1356
|
Instance:
|
|
1229
1357
|
type: object
|
|
1230
1358
|
required:
|
|
@@ -1247,6 +1375,11 @@ components:
|
|
|
1247
1375
|
type: object
|
|
1248
1376
|
additionalProperties: true
|
|
1249
1377
|
description: Instance specification
|
|
1378
|
+
highlights:
|
|
1379
|
+
type: array
|
|
1380
|
+
description: Values of the kind's summary attributes that are set (search results only)
|
|
1381
|
+
items:
|
|
1382
|
+
$ref: '#/components/schemas/Highlight'
|
|
1250
1383
|
relations:
|
|
1251
1384
|
type: object
|
|
1252
1385
|
additionalProperties:
|
|
@@ -1307,7 +1440,7 @@ components:
|
|
|
1307
1440
|
type: object
|
|
1308
1441
|
additionalProperties:
|
|
1309
1442
|
type: integer
|
|
1310
|
-
description: Result counts by kind (count mode only)
|
|
1443
|
+
description: Result counts by kind (search and count mode; all hits of the query, not only the current page)
|
|
1311
1444
|
instances:
|
|
1312
1445
|
type: array
|
|
1313
1446
|
items:
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Archsight; end
|
|
4
|
+
module Archsight::Web; end
|
|
5
|
+
module Archsight::Web::API; end
|
|
6
|
+
|
|
7
|
+
# Parameters and JSON of `GET /api/v1/requirements` (see Archsight::Requirements)
|
|
8
|
+
module Archsight::Web::API::RequirementsHelpers
|
|
9
|
+
# @param name [Symbol] a comma separated list parameter, every value of which must be one of `allowed`
|
|
10
|
+
def csv_param(name, allowed)
|
|
11
|
+
values = params[name].to_s.split(",").map(&:strip).reject(&:empty?)
|
|
12
|
+
invalid = values - allowed
|
|
13
|
+
json_error("Parameter '#{name}' must be #{allowed.join(", ")}, not '#{invalid.first}'", status: 400, error_type: "BadRequest") unless invalid.empty?
|
|
14
|
+
|
|
15
|
+
values
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def build_requirements_response(query, requirements, query_time_ms)
|
|
19
|
+
{
|
|
20
|
+
query: query,
|
|
21
|
+
total: requirements.length,
|
|
22
|
+
query_time_ms: query_time_ms,
|
|
23
|
+
requirements: requirements.map { |r| r.merge(story: r[:story] && markdown(r[:story])) }
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -4,6 +4,7 @@ require "sinatra/base"
|
|
|
4
4
|
require "sinatra/extension"
|
|
5
5
|
require_relative "json_helpers"
|
|
6
6
|
require_relative "page_helpers"
|
|
7
|
+
require_relative "requirements_helpers"
|
|
7
8
|
require_relative "../../assets"
|
|
8
9
|
|
|
9
10
|
module Archsight; end
|
|
@@ -16,6 +17,7 @@ module Archsight::Web::API::Routes
|
|
|
16
17
|
|
|
17
18
|
helpers Archsight::Web::API::JsonHelpers
|
|
18
19
|
helpers Archsight::Web::API::PageHelpers
|
|
20
|
+
helpers Archsight::Web::API::RequirementsHelpers
|
|
19
21
|
|
|
20
22
|
# Rendering is CPU work, previews are typed, not pasted
|
|
21
23
|
MAX_DIAGRAM_SOURCE = 100_000
|
|
@@ -110,6 +112,23 @@ module Archsight::Web::API::Routes
|
|
|
110
112
|
end
|
|
111
113
|
end
|
|
112
114
|
|
|
115
|
+
# GET /api/v1/requirements - The business requirements of the resources a query selects (see Archsight::Requirements)
|
|
116
|
+
get "/api/v1/requirements" do
|
|
117
|
+
query = params[:of]
|
|
118
|
+
json_error("Query parameter 'of' is required", status: 400, error_type: "BadRequest") unless query
|
|
119
|
+
|
|
120
|
+
priority = csv_param(:priority, Archsight::Requirements::PRIORITIES)
|
|
121
|
+
status = csv_param(:status, Archsight::Requirements::STATUSES.values)
|
|
122
|
+
start_time = Time.now
|
|
123
|
+
|
|
124
|
+
begin
|
|
125
|
+
requirements = Archsight::Requirements.collect(db, of: query, priority: priority, status: status)
|
|
126
|
+
json_response(build_requirements_response(query, requirements, ((Time.now - start_time) * 1000).round(2)))
|
|
127
|
+
rescue Archsight::Query::QueryError => e
|
|
128
|
+
json_error(e.message, status: 400, error_type: "QueryError", query: query)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
113
132
|
# GET /api/v1/assets/*path - Images and draw.io diagrams that markdown embeds. The path is relative to the
|
|
114
133
|
# resources directory; only files of the allowed types inside it are ever served, never anything outside of
|
|
115
134
|
# it and never resource definitions (see Archsight::Assets). Outside, missing and not-served-type all
|
|
@@ -140,6 +140,9 @@ class Archsight::Web::Application < Sinatra::Base
|
|
|
140
140
|
html = Archsight::Helpers::AssetImages.rewrite(html, base_dir: base, resources_dir: Archsight.resources_dir) if base
|
|
141
141
|
# ```asd blocks become placeholders until the text passes below are done (see DiagramBlocks)
|
|
142
142
|
html, diagrams = Archsight::Helpers::DiagramBlocks.extract(html, resolver: Archsight::Helpers::ResourceResolver.new(db))
|
|
143
|
+
# so are ```view and ```requirements blocks (see ViewBlocks, RequirementsBlocks)
|
|
144
|
+
html, views = Archsight::Helpers::ViewBlocks.extract(html)
|
|
145
|
+
html, requirements = Archsight::Helpers::RequirementsBlocks.extract(html)
|
|
143
146
|
|
|
144
147
|
# Resolve relative URLs if we have a git URL (for repository READMEs)
|
|
145
148
|
if git_url && (base_url = github_raw_base_url(git_url))
|
|
@@ -157,7 +160,9 @@ class Archsight::Web::Application < Sinatra::Base
|
|
|
157
160
|
# Convert [[Target]] / [[Target|label]] wiki-style links to page and resource links
|
|
158
161
|
html = Archsight::Helpers::Embeds.new(db).render(html)
|
|
159
162
|
html = Archsight::Helpers::WikiLinks.new(db).render(Archsight::Helpers::Macros.render(html, context: Archsight::Helpers::Macros::Context.new(db, page)))
|
|
160
|
-
Archsight::Helpers::DiagramBlocks.restore(html, diagrams)
|
|
163
|
+
html = Archsight::Helpers::DiagramBlocks.restore(html, diagrams)
|
|
164
|
+
html = Archsight::Helpers::ViewBlocks.restore(html, views)
|
|
165
|
+
Archsight::Helpers::RequirementsBlocks.restore(html, requirements)
|
|
161
166
|
end
|
|
162
167
|
|
|
163
168
|
# Generate asset path with cache-busting query string based on file mtime
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{$ as e,B as t,H as n,M as r,T as i,X as a,Y as o,j as s,k as c,t as l,x as u}from"./index-D7m61Ahx.js";var d={class:`api-docs-page`},f={key:0,class:`pico-color-red-500`},p=l({__name:`ApiDocsPage`,setup(l){let p=u(),m=o(null),h=o(null);function g(e){return new Promise((t,n)=>{if(window.Redoc)return t();let r=document.createElement(`script`);r.src=e,r.onload=t,r.onerror=()=>n(Error(`Failed to load ReDoc`)),document.head.appendChild(r)})}return t(async()=>{try{await g(`https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js`),window.Redoc.init(`/api/v1/openapi.yaml`,{},m.value)}catch(e){h.value=e.message}}),(t,o)=>(n(),r(`div`,d,[c(`a`,{href:`#`,class:`back-link`,onClick:o[0]||=i(e=>a(p).back(),[`prevent`])},`← Back`),h.value?(n(),r(`div`,f,e(h.value),1)):s(``,!0),c(`div`,{ref_key:`container`,ref:m},null,512)]))}},[[`__scopeId`,`data-v-4823b89b`]]);export{p as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.back-link[data-v-4823b89b]{z-index:100;font-size:var(--fs-sm);background:#fff;border:1px solid #ddd;border-radius:4px;padding:6px 14px;text-decoration:none;position:fixed;top:12px;right:12px;box-shadow:0 1px 4px #0000001a}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{A as e,H as t,K as n,M as r,Y as i,k as a,s as o}from"./index-D7m61Ahx.js";import{t as s}from"./WikiPage-CeCQBTDS.js";var c={key:1},l={__name:`DocPage`,props:{filename:String},setup(l){let u=l,d=i(null);function f(e,t){let n=new DOMParser().parseFromString(e,`text/html`).body,r=n.querySelector(`:scope > article`)||n,i=r.querySelector(`h1`),a=i?.textContent.trim()||t.split(`/`).pop().replace(/[-_]/g,` `);i?.remove();let o=[...r.querySelectorAll(`h2[id], h3[id], h4[id]`)].map(e=>({level:Number(e.tagName[1]),id:e.id,text:e.textContent.trim()})),s=[{title:`Documentation`}];return t.startsWith(`resources/`)&&s.push({title:`Resource types`}),{title:a,html:r.innerHTML,toc:o.length>=2?o:[],breadcrumb:s}}async function p(){let e=await o(u.filename);d.value=e?f(e,u.filename):{title:`Not found`,html:`<p>Documentation not found.</p>`,toc:[],breadcrumb:[{title:`Documentation`}]},document.title=`${d.value.title} - Archsight`}return n(()=>u.filename,p,{immediate:!0}),(n,i)=>d.value?(t(),e(s,{key:0,title:d.value.title,html:d.value.html,breadcrumb:d.value.breadcrumb,toc:d.value.toc},null,8,[`title`,`html`,`breadcrumb`,`toc`])):(t(),r(`article`,c,[...i[0]||=[a(`p`,null,`Loading...`,-1)]]))}};export{l as default};
|