@crvouga/atlas 0.10.2 → 0.12.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.
- package/CHANGELOG.md +12 -0
- package/README.md +7 -0
- package/dist/reports/src/catalog-health.d.ts +142 -0
- package/dist/reports/src/catalog-health.js +257 -0
- package/dist/reports/src/test-catalog.d.ts +8 -0
- package/dist/reports/src/test-catalog.js +5 -1
- package/dist/schema/src/api.d.ts +79 -0
- package/dist/schema/src/api.js +50 -0
- package/dist/server/src/app.js +9 -0
- package/dist/server/src/llms.js +1 -0
- package/dist/server/src/openapi.js +18 -2
- package/dist/server/src/settings-page.d.ts +4 -4
- package/dist/server/src/settings-page.js +17 -78
- package/docs/reference/http-api.md +34 -0
- package/docs/reference/reports.md +224 -0
- package/docs/reference/schema.md +10 -0
- package/docs/reference/server.md +4 -4
- package/docs/server.md +10 -0
- package/docs/ui.md +142 -0
- package/docs/workspaces.md +130 -0
- package/llms-full.txt +322 -0
- package/llms.txt +2 -0
- package/package.json +17 -10
- package/packages/schema/schemas/catalog-health.schema.json +400 -0
- package/packages/server/openapi.json +596 -0
- package/packages/visualizer/src/admin/SettingsPage.tsx +456 -0
- package/packages/visualizer/src/admin/main.tsx +11 -0
- package/packages/visualizer/src/app/AppShell.tsx +62 -38
- package/packages/visualizer/src/app/gazetteer.tsx +90 -83
- package/packages/visualizer/src/app/router.tsx +33 -15
- package/packages/visualizer/src/components/ActivityIndicator.tsx +4 -5
- package/packages/visualizer/src/components/DataIssues.tsx +36 -54
- package/packages/visualizer/src/components/Header.tsx +197 -125
- package/packages/visualizer/src/components/Legend.tsx +13 -41
- package/packages/visualizer/src/components/ProjectPicker.tsx +61 -26
- package/packages/visualizer/src/components/Screen.tsx +80 -71
- package/packages/visualizer/src/components/SpecLogo.tsx +15 -0
- package/packages/visualizer/src/components/Status.tsx +20 -34
- package/packages/visualizer/src/components/labels.tsx +8 -3
- package/packages/visualizer/src/components/spec-logos/APACHE-2.0.txt +206 -0
- package/packages/visualizer/src/components/spec-logos/CAMUNDA-MIT.txt +21 -0
- package/packages/visualizer/src/components/spec-logos/FILE-ICONS-ISC.txt +13 -0
- package/packages/visualizer/src/components/spec-logos/README.md +23 -0
- package/packages/visualizer/src/components/spec-logos/SIMPLE-ICONS-CC0.txt +30 -0
- package/packages/visualizer/src/components/spec-logos/alloy.svg +1 -0
- package/packages/visualizer/src/components/spec-logos/dmn.svg +3 -0
- package/packages/visualizer/src/components/spec-logos/gherkin.svg +1 -0
- package/packages/visualizer/src/components/spec-logos/openslo.svg +63 -0
- package/packages/visualizer/src/components/spec-logos/quint.svg +1 -0
- package/packages/visualizer/src/evidence/artifacts.tsx +263 -110
- package/packages/visualizer/src/evidence/chart-source.tsx +18 -0
- package/packages/visualizer/src/evidence/check-detail.tsx +283 -139
- package/packages/visualizer/src/evidence/decision-table.tsx +35 -26
- package/packages/visualizer/src/evidence/document-page.tsx +51 -29
- package/packages/visualizer/src/evidence/feature-page.tsx +121 -53
- package/packages/visualizer/src/evidence/marks.tsx +112 -79
- package/packages/visualizer/src/evidence/model-page.tsx +77 -46
- package/packages/visualizer/src/evidence/model-trace.tsx +55 -35
- package/packages/visualizer/src/evidence/overview.tsx +137 -69
- package/packages/visualizer/src/evidence/recording-page.tsx +70 -31
- package/packages/visualizer/src/evidence/recording.tsx +135 -68
- package/packages/visualizer/src/evidence/snapshot.tsx +99 -43
- package/packages/visualizer/src/evidence/source-text.tsx +33 -0
- package/packages/visualizer/src/evidence/sources-panel.tsx +138 -144
- package/packages/visualizer/src/evidence/spec-header.tsx +52 -36
- package/packages/visualizer/src/evidence/spec-index.tsx +61 -32
- package/packages/visualizer/src/evidence/spec-page.tsx +56 -25
- package/packages/visualizer/src/evidence/test-filter.tsx +38 -14
- package/packages/visualizer/src/explorer/Explorer.tsx +688 -261
- package/packages/visualizer/src/explorer/ExplorerIndex.tsx +136 -68
- package/packages/visualizer/src/explorer/RunsView.tsx +307 -94
- package/packages/visualizer/src/explorer/TestDetail.tsx +393 -118
- package/packages/visualizer/src/explorer/parts.tsx +35 -13
- package/packages/visualizer/src/find/Finder.tsx +106 -90
- package/packages/visualizer/src/find/open.ts +17 -20
- package/packages/visualizer/src/live/test-progress.tsx +60 -22
- package/packages/visualizer/src/looks/Stage.tsx +223 -110
- package/packages/visualizer/src/main.tsx +9 -11
- package/packages/visualizer/src/map/ChartMap.tsx +125 -69
- package/packages/visualizer/src/map/EventEdge.tsx +59 -37
- package/packages/visualizer/src/map/map.module.css +12 -546
- package/packages/visualizer/src/map/nodes.tsx +124 -70
- package/packages/visualizer/src/reachable.test.ts +3 -2
- package/packages/visualizer/src/spec-kind.ts +2 -2
- package/packages/visualizer/src/ui/grammars/ALLOY-MIT.txt +21 -0
- package/packages/visualizer/src/ui/grammars/QUINT-APACHE-2.0.txt +206 -0
- package/packages/visualizer/src/ui/grammars/README.md +14 -0
- package/packages/visualizer/src/ui/grammars/alloy.json +71 -0
- package/packages/visualizer/src/ui/grammars/quint.json +152 -0
- package/packages/visualizer/src/ui/highlight.ts +50 -0
- package/packages/visualizer/src/ui/theme.tsx +109 -0
- package/packages/visualizer/src/views/ChartPage.tsx +46 -26
- package/packages/visualizer/src/views/ChartTests.tsx +59 -22
- package/packages/visualizer/src/views/ProductPage.tsx +16 -15
- package/packages/visualizer/src/views/SidePanel.tsx +241 -131
- package/packages/visualizer/src/app/gazetteer.module.css +0 -107
- package/packages/visualizer/src/components/ActivityIndicator.module.css +0 -4
- package/packages/visualizer/src/components/DataIssues.module.css +0 -136
- package/packages/visualizer/src/components/Header.module.css +0 -114
- package/packages/visualizer/src/components/Legend.module.css +0 -84
- package/packages/visualizer/src/components/Screen.module.css +0 -312
- package/packages/visualizer/src/components/Status.module.css +0 -116
- package/packages/visualizer/src/components/icons.tsx +0 -156
- package/packages/visualizer/src/evidence/artifacts.module.css +0 -213
- package/packages/visualizer/src/evidence/evidence.module.css +0 -907
- package/packages/visualizer/src/evidence/recording.module.css +0 -460
- package/packages/visualizer/src/evidence/sources.module.css +0 -168
- package/packages/visualizer/src/evidence/spec-snapshot.tsx +0 -26
- package/packages/visualizer/src/explorer/explorer.module.css +0 -1238
- package/packages/visualizer/src/find/Finder.module.css +0 -168
- package/packages/visualizer/src/looks/stage.module.css +0 -300
- package/packages/visualizer/src/styles/global.css +0 -105
- package/packages/visualizer/src/styles/tokens.css +0 -80
- package/packages/visualizer/src/views/views.module.css +0 -546
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
|
|
3
|
+
"name": "quint",
|
|
4
|
+
"patterns": [
|
|
5
|
+
{
|
|
6
|
+
"include": "#hashbangLine"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"include": "#docComments"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"include": "#lineComments"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"include": "#blockComments"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"include": "#keywords"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"include": "#storage"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"include": "#constants"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"include": "#punctuation"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"include": "#operators"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"include": "#identifiers"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"include": "#strings"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"include": "#numbers"
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"repository": {
|
|
43
|
+
"identifiers": {
|
|
44
|
+
"patterns": [
|
|
45
|
+
{
|
|
46
|
+
"name": "meta.quint",
|
|
47
|
+
"match": "[a-zA-Z_]([a-zA-Z0-9_])*"
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
"keywords": {
|
|
52
|
+
"patterns": [
|
|
53
|
+
{
|
|
54
|
+
"name": "keyword.control.quint",
|
|
55
|
+
"match": "\\b(module|import|from|export|as|if|else|match|not|or|and|implies|iff|all|any|leadsTo|strongFair|weakFair)\\b"
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
"storage": {
|
|
60
|
+
"patterns": [
|
|
61
|
+
{
|
|
62
|
+
"name": "storage.modifier.quint",
|
|
63
|
+
"match": "\\b(type|assume|const|var|val|nondet|def|pure|action|temporal|run)\\b"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"name": "storage.type.quint",
|
|
67
|
+
"match": "\\b(Set|List|int|str|bool)\\b"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"name": "support.type.quint",
|
|
71
|
+
"match": "\\b(Map|Tup|Rec)\\b"
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
"operators": {
|
|
76
|
+
"patterns": [
|
|
77
|
+
{
|
|
78
|
+
"name": "keyword.operator.quint",
|
|
79
|
+
"match": "(=>|->|<=|>|<|=|!=|'|\\.|\\*|\\+|-|/|%|\\^)"
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
"strings": {
|
|
84
|
+
"name": "string.quoted.double.quint",
|
|
85
|
+
"begin": "\"",
|
|
86
|
+
"end": "\""
|
|
87
|
+
},
|
|
88
|
+
"numbers": {
|
|
89
|
+
"patterns": [
|
|
90
|
+
{
|
|
91
|
+
"name": "constant.numeric.quint",
|
|
92
|
+
"match": "-?(0x[0-9a-fA-F]([0-9a-fA-F]|_[0-9a-fA-F])*|0|[1-9]([0-9]|_[0-9])*)"
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
"constants": {
|
|
97
|
+
"patterns": [
|
|
98
|
+
{
|
|
99
|
+
"name": "constant.language.quint",
|
|
100
|
+
"match": "\\b(false|true|Bool|Int|Nat)\\b"
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
},
|
|
104
|
+
"hashbangLine": {
|
|
105
|
+
"patterns": [
|
|
106
|
+
{
|
|
107
|
+
"name": "comment.line.hashbang.quint",
|
|
108
|
+
"match": "\\A#!.*$"
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
"docComments": {
|
|
113
|
+
"patterns": [
|
|
114
|
+
{
|
|
115
|
+
"name": "comment.block.documentation.quint",
|
|
116
|
+
"match": "///.*$"
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
},
|
|
120
|
+
"punctuation": {
|
|
121
|
+
"patterns": [
|
|
122
|
+
{
|
|
123
|
+
"name": "keyword.operator.spread.quint",
|
|
124
|
+
"match": "\\.\\.\\."
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"name": "punctuation.accessor.quint",
|
|
128
|
+
"match": "::"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"name": "punctuation.separator.quint",
|
|
132
|
+
"match": "\\|"
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
},
|
|
136
|
+
"lineComments": {
|
|
137
|
+
"patterns": [
|
|
138
|
+
{
|
|
139
|
+
"name": "comment.line.double-slash.quint",
|
|
140
|
+
"match": "//.*$"
|
|
141
|
+
}
|
|
142
|
+
]
|
|
143
|
+
},
|
|
144
|
+
"blockComments": {
|
|
145
|
+
"name": "comment.block.quint",
|
|
146
|
+
"begin": "/\\*",
|
|
147
|
+
"end": "\\*/",
|
|
148
|
+
"patterns": []
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
"scopeName": "source.quint"
|
|
152
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createShikiAdapter } from '@mantine/code-highlight';
|
|
2
|
+
import type { HighlighterCore, LanguageInput, LanguageRegistration } from 'shiki/core';
|
|
3
|
+
|
|
4
|
+
const grammars: Record<string, () => Promise<LanguageInput>> = {
|
|
5
|
+
gherkin: () => import('shiki/langs/gherkin.mjs').then((module) => module.default),
|
|
6
|
+
yaml: () => import('shiki/langs/yaml.mjs').then((module) => module.default),
|
|
7
|
+
xml: () => import('shiki/langs/xml.mjs').then((module) => module.default),
|
|
8
|
+
json: () => import('shiki/langs/json.mjs').then((module) => module.default),
|
|
9
|
+
quint: () => import('./grammars/quint.json').then((module) => module.default as unknown as LanguageRegistration),
|
|
10
|
+
alloy: () => import('./grammars/alloy.json').then((module) => module.default as unknown as LanguageRegistration)
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
async function createContext() {
|
|
14
|
+
const [{ createHighlighterCore }, { createJavaScriptRegexEngine }] = await Promise.all([
|
|
15
|
+
import('shiki/core'),
|
|
16
|
+
import('shiki/engine/javascript')
|
|
17
|
+
]);
|
|
18
|
+
const highlighter = await createHighlighterCore({ langs: [], themes: [], engine: createJavaScriptRegexEngine() });
|
|
19
|
+
return {
|
|
20
|
+
getLoadedLanguages: () => highlighter.getLoadedLanguages(),
|
|
21
|
+
loadLanguage: (...languages: Parameters<HighlighterCore['loadLanguage']>) => highlighter.loadLanguage(...languages),
|
|
22
|
+
codeToHtml: (code: string, options: Parameters<HighlighterCore['codeToHtml']>[1]) =>
|
|
23
|
+
highlighter.codeToHtml(code, {
|
|
24
|
+
...options,
|
|
25
|
+
transformers: [
|
|
26
|
+
{
|
|
27
|
+
line(node, line) {
|
|
28
|
+
node.properties.id = `line-${line}`;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
})
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
let context: ReturnType<typeof createContext> | undefined;
|
|
36
|
+
/** One lazy, cached syntax engine; only a viewed file's grammar is downloaded. */
|
|
37
|
+
export const specHighlightAdapter = createShikiAdapter(() => (context ??= createContext()), {
|
|
38
|
+
resolveLanguage: (language) => grammars[language]
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/** Choose the actual notation, never a visually similar language. */
|
|
42
|
+
export function specLanguage(file: string): string {
|
|
43
|
+
if (/\.feature$/i.test(file)) return 'gherkin';
|
|
44
|
+
if (/\.qnt$/i.test(file)) return 'quint';
|
|
45
|
+
if (/\.als$/i.test(file)) return 'alloy';
|
|
46
|
+
if (/\.(dmn|scxml|xml)$/i.test(file)) return 'xml';
|
|
47
|
+
if (/\.ya?ml$/i.test(file)) return 'yaml';
|
|
48
|
+
if (/\.json$/i.test(file)) return 'json';
|
|
49
|
+
return 'text';
|
|
50
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/** @module Viewer design system
|
|
2
|
+
* The sole Mantine theme and provider for the report viewer and server settings.
|
|
3
|
+
*/
|
|
4
|
+
import { CodeHighlight, CodeHighlightTabs } from '@mantine/code-highlight';
|
|
5
|
+
import {
|
|
6
|
+
ActionIcon,
|
|
7
|
+
DEFAULT_THEME,
|
|
8
|
+
darken,
|
|
9
|
+
type MantineColorsTuple,
|
|
10
|
+
type CSSVariablesResolver,
|
|
11
|
+
Anchor,
|
|
12
|
+
Badge,
|
|
13
|
+
Button,
|
|
14
|
+
Card,
|
|
15
|
+
createTheme,
|
|
16
|
+
Drawer,
|
|
17
|
+
InputWrapper,
|
|
18
|
+
List,
|
|
19
|
+
MantineProvider,
|
|
20
|
+
Modal,
|
|
21
|
+
NativeSelect,
|
|
22
|
+
Paper,
|
|
23
|
+
Table,
|
|
24
|
+
Text,
|
|
25
|
+
Title,
|
|
26
|
+
Tooltip
|
|
27
|
+
} from '@mantine/core';
|
|
28
|
+
import type { ReactNode } from 'react';
|
|
29
|
+
// Retain Mantine's scales while making small outcome labels readable on light surfaces.
|
|
30
|
+
const readableScale = (scale: MantineColorsTuple) =>
|
|
31
|
+
scale.map((color, index) => (index >= 6 ? darken(color, 0.25) : color)) as unknown as MantineColorsTuple;
|
|
32
|
+
const cssVariablesResolver: CSSVariablesResolver = () => ({
|
|
33
|
+
variables: {},
|
|
34
|
+
light: { '--mantine-color-dimmed': 'var(--mantine-color-gray-7)' },
|
|
35
|
+
dark: {}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/** Atlas uses Mantine's blue scale for navigation and standard outcome colors for evidence. */
|
|
39
|
+
export const theme = createTheme({
|
|
40
|
+
primaryColor: 'blue',
|
|
41
|
+
primaryShade: 8,
|
|
42
|
+
autoContrast: true,
|
|
43
|
+
colors: {
|
|
44
|
+
green: readableScale(DEFAULT_THEME.colors.green),
|
|
45
|
+
red: readableScale(DEFAULT_THEME.colors.red),
|
|
46
|
+
orange: readableScale(DEFAULT_THEME.colors.orange)
|
|
47
|
+
},
|
|
48
|
+
defaultRadius: 'md',
|
|
49
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
50
|
+
fontFamilyMonospace: 'ui-monospace, SFMono-Regular, Menlo, monospace',
|
|
51
|
+
headings: { fontWeight: '650', sizes: { h1: { fontSize: '1.75rem' }, h2: { fontSize: '1.25rem' }, h3: { fontSize: '1rem' } } },
|
|
52
|
+
respectReducedMotion: true,
|
|
53
|
+
focusRing: 'auto',
|
|
54
|
+
components: {
|
|
55
|
+
CodeHighlight: CodeHighlight.extend({
|
|
56
|
+
defaultProps: { withLineNumbers: true, withFirstLineIndentation: true, defaultExpanded: true, withBorder: true, radius: 'md' }
|
|
57
|
+
}),
|
|
58
|
+
CodeHighlightTabs: CodeHighlightTabs.extend({
|
|
59
|
+
defaultProps: {
|
|
60
|
+
withLineNumbers: true,
|
|
61
|
+
withFirstLineIndentation: true,
|
|
62
|
+
defaultExpanded: true,
|
|
63
|
+
withBorder: true,
|
|
64
|
+
radius: 'md',
|
|
65
|
+
withExpandButton: true,
|
|
66
|
+
maxCollapsedHeight: '70vh'
|
|
67
|
+
}
|
|
68
|
+
}),
|
|
69
|
+
// Mantine List wraps labels in inline-flex; report rows need the available width.
|
|
70
|
+
// Keep this single supported Styles API adjustment here, not in each feature.
|
|
71
|
+
List: List.extend({ styles: { itemWrapper: { display: 'flex' }, itemLabel: { flex: 1, minWidth: 0, width: '100%' } } }),
|
|
72
|
+
Button: Button.extend({ defaultProps: { size: 'sm', variant: 'light' } }),
|
|
73
|
+
ActionIcon: ActionIcon.extend({ defaultProps: { size: 'lg', variant: 'subtle' } }),
|
|
74
|
+
Anchor: Anchor.extend({ defaultProps: { size: 'sm', underline: 'hover' } }),
|
|
75
|
+
Badge: Badge.extend({ defaultProps: { variant: 'light', tt: 'none' } }),
|
|
76
|
+
Paper: Paper.extend({ defaultProps: { p: 'md' } }),
|
|
77
|
+
Card: Card.extend({ defaultProps: { withBorder: true, padding: 'md' } }),
|
|
78
|
+
Text: Text.extend({ defaultProps: { size: 'sm' } }),
|
|
79
|
+
Title: Title.extend({ defaultProps: { textWrap: 'pretty' } }),
|
|
80
|
+
InputWrapper: InputWrapper.extend({ defaultProps: { size: 'sm' } }),
|
|
81
|
+
NativeSelect: NativeSelect.extend({ defaultProps: { size: 'sm' } }),
|
|
82
|
+
Modal: Modal.extend({
|
|
83
|
+
defaultProps: { centered: true, size: 'lg', overlayProps: { backgroundOpacity: 0.35 }, transitionProps: { duration: 120 } }
|
|
84
|
+
}),
|
|
85
|
+
Drawer: Drawer.extend({ defaultProps: { position: 'right', size: 'lg', padding: 'lg', transitionProps: { duration: 120 } } }),
|
|
86
|
+
Table: Table.extend({ defaultProps: { verticalSpacing: 'sm', horizontalSpacing: 'sm', highlightOnHover: true, tabularNums: true } }),
|
|
87
|
+
Tooltip: Tooltip.extend({ defaultProps: { openDelay: 300, withArrow: true } })
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
/** Use this provider at every UI root; the viewer currently has a light visual identity. */
|
|
91
|
+
export function AtlasThemeProvider({ children, env }: { children: ReactNode; env?: 'test' }) {
|
|
92
|
+
return (
|
|
93
|
+
<MantineProvider env={env} theme={theme} cssVariablesResolver={cssVariablesResolver} forceColorScheme="light">
|
|
94
|
+
{children}
|
|
95
|
+
</MantineProvider>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
/** Outcome colors shared by report badges, progress bars, and the graph. Unknown outcomes stay gray. */
|
|
99
|
+
export function statusColor(status: string): string {
|
|
100
|
+
return status === 'passed'
|
|
101
|
+
? 'green'
|
|
102
|
+
: status === 'failed'
|
|
103
|
+
? 'red'
|
|
104
|
+
: status === 'flaky'
|
|
105
|
+
? 'orange'
|
|
106
|
+
: status === 'running'
|
|
107
|
+
? 'blue'
|
|
108
|
+
: 'gray';
|
|
109
|
+
}
|
|
@@ -1,26 +1,32 @@
|
|
|
1
|
+
import { Anchor, Container, Flex, Stack, Text, Title } from '@mantine/core';
|
|
1
2
|
import { Link, useParams, useSearch } from '@tanstack/react-router';
|
|
2
|
-
|
|
3
3
|
import { useAtlas } from '../app/atlas-context';
|
|
4
4
|
import { mapFrame } from '../layout/graph';
|
|
5
5
|
import { ChartMap } from '../map/ChartMap';
|
|
6
|
-
import { SidePanel } from './SidePanel';
|
|
7
|
-
import styles from './views.module.css';
|
|
8
6
|
import { ChartTests } from './ChartTests';
|
|
9
|
-
|
|
7
|
+
import { ChartSource } from '../evidence/chart-source';
|
|
8
|
+
import { SidePanel } from './SidePanel';
|
|
10
9
|
export function ChartPage() {
|
|
11
10
|
const { chartId } = useParams({ from: '/chart/$chartId' });
|
|
12
11
|
const search = useSearch({ from: '/chart/$chartId' });
|
|
13
12
|
const { view } = useAtlas();
|
|
14
|
-
if (!view)
|
|
13
|
+
if (!view)
|
|
14
|
+
return (
|
|
15
|
+
<Container role="status" component="article" size="xl" w="100%" py="xl">
|
|
16
|
+
Loading the spec…
|
|
17
|
+
</Container>
|
|
18
|
+
);
|
|
15
19
|
if (!view.charts.has(chartId)) {
|
|
16
20
|
return (
|
|
17
|
-
<
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
<Container component="article" size="xl" w="100%" py="xl">
|
|
22
|
+
<Title order={1} mb="sm">
|
|
23
|
+
This chart isn’t in the spec
|
|
24
|
+
</Title>
|
|
25
|
+
<Text component="div" c="dimmed">
|
|
26
|
+
It may have been renamed or removed since this link was shared.
|
|
27
|
+
</Text>
|
|
28
|
+
<Anchor renderRoot={(props) => <Link {...props} to="/" search={(prev) => ({ run: prev.run })} />}>Back to the product map</Anchor>
|
|
29
|
+
</Container>
|
|
24
30
|
);
|
|
25
31
|
}
|
|
26
32
|
const selection = { screen: search.screen, event: search.event, journey: search.journey };
|
|
@@ -30,25 +36,39 @@ export function ChartPage() {
|
|
|
30
36
|
const frame = mapFrame(view, chartId, search.group ?? null);
|
|
31
37
|
const homeFocus = mapFrame(view, chartId, null).focus;
|
|
32
38
|
return (
|
|
33
|
-
<
|
|
34
|
-
<
|
|
35
|
-
<
|
|
36
|
-
|
|
39
|
+
<Stack gap="sm" p="md">
|
|
40
|
+
<Flex gap="xs" align="center" wrap="wrap">
|
|
41
|
+
<Title order={1} mb="sm">
|
|
42
|
+
{chart.name}
|
|
43
|
+
</Title>
|
|
44
|
+
{chart.description && <Text component="div">{chart.description}</Text>}
|
|
37
45
|
{frame.focus && (
|
|
38
|
-
<
|
|
39
|
-
<span>
|
|
46
|
+
<Text component="div">
|
|
47
|
+
<Text span inherit>
|
|
48
|
+
{frame.focus}
|
|
49
|
+
</Text>
|
|
40
50
|
{frame.wider && (
|
|
41
|
-
<
|
|
51
|
+
<Anchor
|
|
52
|
+
renderRoot={(props) => (
|
|
53
|
+
<Link
|
|
54
|
+
{...props}
|
|
55
|
+
to="/chart/$chartId"
|
|
56
|
+
params={{ chartId }}
|
|
57
|
+
search={(prev) => ({ run: prev.run, group: frame.wider && frame.wider !== homeFocus ? frame.wider : undefined })}
|
|
58
|
+
/>
|
|
59
|
+
)}
|
|
60
|
+
>
|
|
42
61
|
Wider map
|
|
43
|
-
</
|
|
62
|
+
</Anchor>
|
|
44
63
|
)}
|
|
45
|
-
</
|
|
64
|
+
</Text>
|
|
46
65
|
)}
|
|
47
|
-
</
|
|
48
|
-
<
|
|
66
|
+
</Flex>
|
|
67
|
+
<ChartSource file={chart.file} />
|
|
68
|
+
<Stack gap="sm">
|
|
49
69
|
<ChartMap key={`${chartId}:${search.group ?? ''}`} view={view} chartId={chartId} selection={selection} group={search.group} />
|
|
50
|
-
</
|
|
51
|
-
<ChartTests view={view} chartId={chartId}
|
|
52
|
-
</
|
|
70
|
+
</Stack>
|
|
71
|
+
<ChartTests view={view} chartId={chartId} />
|
|
72
|
+
</Stack>
|
|
53
73
|
);
|
|
54
74
|
}
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
+
import { Box, List, Title } from '@mantine/core';
|
|
1
2
|
import { lazy, Suspense, useState } from 'react';
|
|
2
3
|
import type { AtlasView } from '../data/model';
|
|
3
|
-
import { useEvidence } from '../evidence/evidence-context';
|
|
4
4
|
import { CheckDetail, ResultRow, type CheckSelection } from '../evidence/check-detail';
|
|
5
|
-
import {
|
|
5
|
+
import { useEvidence } from '../evidence/evidence-context';
|
|
6
6
|
import { Bounded } from '../evidence/marks';
|
|
7
|
+
import { TestFilter, useRunningFilter } from '../evidence/test-filter';
|
|
7
8
|
import { outlinePath } from '../spec-kind';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const RecordingPage = lazy(() => import('../evidence/recording-page').then(module => ({ default: module.RecordingPage })));
|
|
11
|
-
|
|
9
|
+
const RecordingPage = lazy(() => import('../evidence/recording-page').then((module) => ({ default: module.RecordingPage })));
|
|
12
10
|
/** Statechart test reports stay in the chart, including while their recordings are still arriving. */
|
|
13
11
|
export function ChartTests({ view, chartId, compact = false }: { view: AtlasView; chartId: string; compact?: boolean }) {
|
|
14
12
|
const evidence = useEvidence();
|
|
@@ -23,21 +21,60 @@ export function ChartTests({ view, chartId, compact = false }: { view: AtlasView
|
|
|
23
21
|
files.add(outlinePath(chart.file));
|
|
24
22
|
chart = chart.parent ? view.charts.get(chart.parent.chartId) : undefined;
|
|
25
23
|
}
|
|
26
|
-
const slots = [...files].flatMap(file => evidence.view.specs.get(file)?.slots ?? []);
|
|
27
|
-
const visible = slots.filter(slot => !runningOnly || slot.status === 'running');
|
|
24
|
+
const slots = [...files].flatMap((file) => evidence.view.specs.get(file)?.slots ?? []);
|
|
25
|
+
const visible = slots.filter((slot) => !runningOnly || slot.status === 'running');
|
|
28
26
|
if (!slots.length) return null;
|
|
29
|
-
return
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
27
|
+
return (
|
|
28
|
+
<Box aria-label="Chart tests" mah={compact ? '40vh' : undefined} p="md" component="section" mb="lg">
|
|
29
|
+
<Title order={3} mb="sm">
|
|
30
|
+
Tests
|
|
31
|
+
</Title>
|
|
32
|
+
<TestFilter slots={slots} />
|
|
33
|
+
<List spacing="xs" listStyleType="none">
|
|
34
|
+
<Bounded items={visible} size={40} more="More tests">
|
|
35
|
+
{(slot) => (
|
|
36
|
+
<ResultRow
|
|
37
|
+
key={slot.key}
|
|
38
|
+
label={slot.title}
|
|
39
|
+
slot={slot}
|
|
40
|
+
open={selectedKey === slot.key}
|
|
41
|
+
onToggle={() => {
|
|
42
|
+
setSelectedKey(selectedKey === slot.key ? null : slot.key);
|
|
43
|
+
setSelection({ check: slot.id });
|
|
44
|
+
setRecording(false);
|
|
45
|
+
setMoment(null);
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
{recording ? (
|
|
49
|
+
<Suspense fallback={<p role="status">Loading the recording…</p>}>
|
|
50
|
+
<RecordingPage
|
|
51
|
+
slot={slot}
|
|
52
|
+
layerId={selection.layer}
|
|
53
|
+
label={slot.title}
|
|
54
|
+
back="Test report"
|
|
55
|
+
moment={moment}
|
|
56
|
+
onMoment={setMoment}
|
|
57
|
+
onClose={() => setRecording(false)}
|
|
58
|
+
/>
|
|
59
|
+
</Suspense>
|
|
60
|
+
) : (
|
|
61
|
+
<CheckDetail
|
|
62
|
+
slot={slot}
|
|
63
|
+
layerId={selection.layer}
|
|
64
|
+
reads="steps"
|
|
65
|
+
select={(patch) => {
|
|
66
|
+
setSelection({ check: patch.check ?? slot.id, layer: patch.layer ?? undefined });
|
|
67
|
+
if (patch.view === 'recording') {
|
|
68
|
+
setRecording(true);
|
|
69
|
+
setMoment(null);
|
|
70
|
+
}
|
|
71
|
+
}}
|
|
72
|
+
/>
|
|
73
|
+
)}
|
|
74
|
+
</ResultRow>
|
|
75
|
+
)}
|
|
76
|
+
</Bounded>
|
|
77
|
+
</List>
|
|
78
|
+
</Box>
|
|
79
|
+
);
|
|
43
80
|
}
|
|
@@ -1,35 +1,36 @@
|
|
|
1
|
+
import { Container, Text, Title } from '@mantine/core';
|
|
1
2
|
import { Navigate, useSearch } from '@tanstack/react-router';
|
|
2
|
-
|
|
3
3
|
import { useAtlas } from '../app/atlas-context';
|
|
4
4
|
import type { AtlasView } from '../data/model';
|
|
5
|
-
import styles from './views.module.css';
|
|
6
|
-
|
|
7
5
|
function openingPlace(view: AtlasView) {
|
|
8
6
|
const screens = [...view.states.values()].filter((state) => state.kind === 'screen' || state.kind === 'final');
|
|
9
7
|
const withFailedEvent = screens.find((state) => state.outgoing.some((id) => view.transitions.get(id)?.status === 'failed'));
|
|
10
8
|
return withFailedEvent ?? screens.find((state) => state.status === 'failed') ?? screens[0] ?? null;
|
|
11
9
|
}
|
|
12
|
-
|
|
13
10
|
export function ProductPage() {
|
|
14
11
|
const { view } = useAtlas();
|
|
15
12
|
const search = useSearch({ strict: false });
|
|
16
13
|
const run = typeof search.run === 'string' ? search.run : undefined;
|
|
17
|
-
if (!view)
|
|
14
|
+
if (!view)
|
|
15
|
+
return (
|
|
16
|
+
<Container role="status" component="article" size="xl" w="100%" py="xl">
|
|
17
|
+
Loading the spec…
|
|
18
|
+
</Container>
|
|
19
|
+
);
|
|
18
20
|
const place = openingPlace(view);
|
|
19
21
|
if (!place) {
|
|
20
22
|
return (
|
|
21
|
-
<article
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
<Container component="article" size="xl" w="100%" py="xl">
|
|
24
|
+
<Title order={1} mb="sm">
|
|
25
|
+
{view.title}
|
|
26
|
+
</Title>
|
|
27
|
+
<Text component="div" c="dimmed">
|
|
28
|
+
This spec has no screens yet.
|
|
29
|
+
</Text>
|
|
30
|
+
</Container>
|
|
25
31
|
);
|
|
26
32
|
}
|
|
27
33
|
return (
|
|
28
|
-
<Navigate
|
|
29
|
-
to="/chart/$chartId"
|
|
30
|
-
params={{ chartId: place.chartId }}
|
|
31
|
-
search={{ ...(run ? { run } : {}), screen: place.name }}
|
|
32
|
-
replace
|
|
33
|
-
/>
|
|
34
|
+
<Navigate to="/chart/$chartId" params={{ chartId: place.chartId }} search={{ ...(run ? { run } : {}), screen: place.name }} replace />
|
|
34
35
|
);
|
|
35
36
|
}
|