@crvouga/atlas 0.10.2 → 0.11.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 +6 -0
- package/README.md +7 -0
- package/dist/server/src/settings-page.d.ts +4 -4
- package/dist/server/src/settings-page.js +17 -78
- package/docs/reference/server.md +4 -4
- package/docs/ui.md +142 -0
- package/docs/workspaces.md +130 -0
- package/llms-full.txt +278 -0
- package/llms.txt +2 -0
- package/package.json +17 -10
- 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
|
@@ -1,94 +1,112 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { IconCheck, IconX, IconCircle } from '@tabler/icons-react';
|
|
2
|
+
import { Accordion, Alert, Anchor, Box, Container, Flex, List, Stack, Text, Title } from '@mantine/core';
|
|
3
|
+
|
|
2
4
|
import { Link } from '@tanstack/react-router';
|
|
3
5
|
import type { ReactNode } from 'react';
|
|
4
|
-
|
|
5
|
-
import { statusNote, type AtlasView, type StateView, type TransitionView } from '../data/model';
|
|
6
|
-
import { ChevronIcon } from '../components/icons';
|
|
6
|
+
import { IconChevronRight as ChevronIcon } from '@tabler/icons-react';
|
|
7
7
|
import { KIND_LABEL } from '../components/labels';
|
|
8
8
|
import { ScreenImage } from '../components/Screen';
|
|
9
9
|
import { StatusBadge, StatusDot } from '../components/Status';
|
|
10
|
+
import { statusNote, type AtlasView, type StateView, type TransitionView } from '../data/model';
|
|
11
|
+
import { mapScope } from '../layout/graph';
|
|
10
12
|
import { eventLooks, journeyLooks, stateLooks } from '../looks/collect';
|
|
11
13
|
import { Stage } from '../looks/Stage';
|
|
12
|
-
import { mapScope } from '../layout/graph';
|
|
13
14
|
import type { MapSelection } from '../map/ChartMap';
|
|
14
|
-
import styles from './views.module.css';
|
|
15
15
|
import { ChartTests } from './ChartTests';
|
|
16
|
-
|
|
16
|
+
import { ChartSource } from '../evidence/chart-source';
|
|
17
17
|
function Section({ title, children }: { title: string; children: ReactNode }) {
|
|
18
18
|
return (
|
|
19
|
-
<section
|
|
20
|
-
<
|
|
19
|
+
<Box component="section" mb="lg">
|
|
20
|
+
<Title order={2} mb="sm">
|
|
21
|
+
{title}
|
|
22
|
+
</Title>
|
|
21
23
|
{children}
|
|
22
|
-
</
|
|
24
|
+
</Box>
|
|
23
25
|
);
|
|
24
26
|
}
|
|
25
|
-
|
|
26
27
|
function EventLink({ t, view, direction }: { t: TransitionView; view: AtlasView; direction: 'in' | 'out' }) {
|
|
27
28
|
const other = direction === 'in' ? t.source : t.target;
|
|
28
29
|
const otherState = other ? view.states.get(other) : undefined;
|
|
29
30
|
return (
|
|
30
|
-
<
|
|
31
|
-
to="/chart/$chartId"
|
|
32
|
-
params={{ chartId: view.states.get(direction === 'in' ? t.target ?? t.source : t.source)?.chartId ?? t.chartId }}
|
|
33
|
-
search={(prev) => ({ run: prev.run, event: t.id, journey: prev.journey })}
|
|
34
|
-
className={styles.eventRow}
|
|
31
|
+
<Anchor
|
|
35
32
|
data-status={t.status}
|
|
33
|
+
underline="never"
|
|
34
|
+
renderRoot={(props) => (
|
|
35
|
+
<Link
|
|
36
|
+
{...props}
|
|
37
|
+
to="/chart/$chartId"
|
|
38
|
+
params={{ chartId: view.states.get(direction === 'in' ? (t.target ?? t.source) : t.source)?.chartId ?? t.chartId }}
|
|
39
|
+
search={(prev) => ({ run: prev.run, event: t.id, journey: prev.journey })}
|
|
40
|
+
/>
|
|
41
|
+
)}
|
|
36
42
|
>
|
|
37
|
-
<span>
|
|
43
|
+
<Text span inherit>
|
|
44
|
+
{t.event}
|
|
45
|
+
</Text>
|
|
38
46
|
{otherState && (
|
|
39
|
-
<span
|
|
47
|
+
<Text span inherit>
|
|
40
48
|
{direction === 'in' ? 'from' : 'to'} {otherState.name}
|
|
41
|
-
</
|
|
49
|
+
</Text>
|
|
42
50
|
)}
|
|
43
|
-
</
|
|
51
|
+
</Anchor>
|
|
44
52
|
);
|
|
45
53
|
}
|
|
46
|
-
|
|
47
54
|
function Developer({ rows }: { rows: [string, ReactNode][] }) {
|
|
48
55
|
return (
|
|
49
|
-
<
|
|
50
|
-
<
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
56
|
+
<Accordion variant="separated" my="lg">
|
|
57
|
+
<Accordion.Item value="developer">
|
|
58
|
+
<Accordion.Control>
|
|
59
|
+
<ChevronIcon aria-hidden="true" size={12} />
|
|
60
|
+
For developers
|
|
61
|
+
</Accordion.Control>
|
|
62
|
+
<Accordion.Panel>
|
|
63
|
+
<Box component="dl">
|
|
64
|
+
{rows
|
|
65
|
+
.filter(([, v]) => v !== null && v !== undefined && v !== '')
|
|
66
|
+
.map(([k, v]) => (
|
|
67
|
+
<Stack key={k} gap="sm">
|
|
68
|
+
<Box component="dt">{k}</Box>
|
|
69
|
+
<Box component="dd" m={0}>
|
|
70
|
+
{v}
|
|
71
|
+
</Box>
|
|
72
|
+
</Stack>
|
|
73
|
+
))}
|
|
74
|
+
</Box>
|
|
75
|
+
</Accordion.Panel>
|
|
76
|
+
</Accordion.Item>
|
|
77
|
+
</Accordion>
|
|
67
78
|
);
|
|
68
79
|
}
|
|
69
|
-
|
|
70
80
|
function Questions({ view, ids }: { view: AtlasView; ids: string[] }) {
|
|
71
81
|
if (ids.length === 0) return null;
|
|
72
82
|
return (
|
|
73
83
|
<Section title="Open questions">
|
|
74
|
-
<
|
|
84
|
+
<List spacing="xs" listStyleType="none">
|
|
75
85
|
{ids.map((id) => {
|
|
76
86
|
const q = view.questions.get(id);
|
|
77
87
|
if (!q) return null;
|
|
78
88
|
return (
|
|
79
|
-
<
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
89
|
+
<Alert key={id} color={'blue'} my="sm">
|
|
90
|
+
<Text span inherit fw={600}>
|
|
91
|
+
{q.title}
|
|
92
|
+
</Text>{' '}
|
|
93
|
+
{q.body}
|
|
94
|
+
{q.specToday && (
|
|
95
|
+
<Text span inherit c="dimmed">
|
|
96
|
+
Spec today: {q.specToday}
|
|
97
|
+
</Text>
|
|
98
|
+
)}
|
|
99
|
+
</Alert>
|
|
83
100
|
);
|
|
84
101
|
})}
|
|
85
|
-
</
|
|
102
|
+
</List>
|
|
86
103
|
</Section>
|
|
87
104
|
);
|
|
88
105
|
}
|
|
89
|
-
|
|
90
106
|
function ScreenDetails({ view, state }: { view: AtlasView; state: StateView }) {
|
|
91
|
-
const checks = state.result?.checks.length
|
|
107
|
+
const checks = state.result?.checks.length
|
|
108
|
+
? state.result.checks
|
|
109
|
+
: state.specChecks.map((check) => ({ check, passed: null as boolean | null, expected: undefined, actual: undefined }));
|
|
92
110
|
const note = statusNote(state.status);
|
|
93
111
|
const events = state.result?.businessEvents;
|
|
94
112
|
const screen = state.kind === 'screen' || state.kind === 'final';
|
|
@@ -96,111 +114,152 @@ function ScreenDetails({ view, state }: { view: AtlasView; state: StateView }) {
|
|
|
96
114
|
const outgoing = state.outgoing.map((id) => view.transitions.get(id)).filter((t): t is TransitionView => Boolean(t));
|
|
97
115
|
return (
|
|
98
116
|
<>
|
|
99
|
-
<
|
|
100
|
-
<
|
|
117
|
+
<Flex gap="xs" align="center" wrap="wrap">
|
|
118
|
+
<Title order={1} mb="sm">
|
|
119
|
+
{state.name}
|
|
120
|
+
</Title>
|
|
101
121
|
<StatusBadge status={state.status} />
|
|
102
|
-
{state.confidence === 'assumed' &&
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
122
|
+
{state.confidence === 'assumed' && (
|
|
123
|
+
<Text span inherit>
|
|
124
|
+
Assumed, not confirmed
|
|
125
|
+
</Text>
|
|
126
|
+
)}
|
|
127
|
+
</Flex>
|
|
128
|
+
{state.description && (
|
|
129
|
+
<Text component="div" c="dimmed">
|
|
130
|
+
{state.description}
|
|
131
|
+
</Text>
|
|
132
|
+
)}
|
|
133
|
+
{note && (
|
|
134
|
+
<Text component="div" c="dimmed">
|
|
135
|
+
{note}
|
|
136
|
+
</Text>
|
|
137
|
+
)}
|
|
106
138
|
<Stage
|
|
107
139
|
looks={stateLooks(state)}
|
|
108
140
|
prefer="pictures"
|
|
109
141
|
empty={
|
|
110
142
|
screen ? (
|
|
111
|
-
<
|
|
143
|
+
<Stack>
|
|
112
144
|
<ScreenImage key={state.name} state={state} size="full" description={false} lines={state.hints.length > 0} />
|
|
113
|
-
</
|
|
145
|
+
</Stack>
|
|
114
146
|
) : (
|
|
115
|
-
<
|
|
147
|
+
<Text c="dimmed">This part of the chart has no screen of its own.</Text>
|
|
116
148
|
)
|
|
117
149
|
}
|
|
118
150
|
/>
|
|
119
|
-
<
|
|
120
|
-
<
|
|
151
|
+
<Stack gap="sm">
|
|
152
|
+
<Title order={2} mb="sm">
|
|
153
|
+
Where they go next
|
|
154
|
+
</Title>
|
|
121
155
|
{outgoing.length > 0 ? (
|
|
122
|
-
<
|
|
156
|
+
<Stack gap="sm" miw={0}>
|
|
123
157
|
{outgoing.map((transition) => (
|
|
124
158
|
<EventLink key={transition.id} t={transition} view={view} direction="out" />
|
|
125
159
|
))}
|
|
126
|
-
</
|
|
160
|
+
</Stack>
|
|
127
161
|
) : (
|
|
128
|
-
<
|
|
162
|
+
<Text component="div" c="dimmed">
|
|
163
|
+
{state.deadEnd ? `This is where the journey stops: ${state.deadEnd}` : 'Nothing leaves this screen.'}
|
|
164
|
+
</Text>
|
|
129
165
|
)}
|
|
130
|
-
</
|
|
166
|
+
</Stack>
|
|
131
167
|
{checks.length > 0 && (
|
|
132
168
|
<Section title="What should be on the screen">
|
|
133
|
-
<
|
|
169
|
+
<List spacing="xs" listStyleType="none">
|
|
134
170
|
{checks.map((c) => (
|
|
135
|
-
<
|
|
136
|
-
<
|
|
137
|
-
{c.passed === null ?
|
|
138
|
-
</
|
|
139
|
-
<span>
|
|
171
|
+
<List.Item key={c.check} data-passed={c.passed === null ? 'unknown' : c.passed}>
|
|
172
|
+
<Text aria-hidden="true" span inherit>
|
|
173
|
+
{c.passed === null ? <IconCircle size={16} /> : c.passed ? <IconCheck size={16} /> : <IconX size={16} />}
|
|
174
|
+
</Text>
|
|
175
|
+
<Text span inherit>
|
|
140
176
|
{c.check}
|
|
141
177
|
{c.passed === false && (c.expected || c.actual) && (
|
|
142
|
-
<span
|
|
178
|
+
<Text span inherit c="dimmed">
|
|
143
179
|
Expected {c.expected ?? 'something else'}, saw {c.actual ?? 'nothing'}
|
|
144
|
-
</
|
|
180
|
+
</Text>
|
|
145
181
|
)}
|
|
146
|
-
</
|
|
147
|
-
</
|
|
182
|
+
</Text>
|
|
183
|
+
</List.Item>
|
|
148
184
|
))}
|
|
149
|
-
</
|
|
185
|
+
</List>
|
|
150
186
|
</Section>
|
|
151
187
|
)}
|
|
152
188
|
{(state.specBusinessEvents.length > 0 || events) && (
|
|
153
189
|
<Section title="What happens behind the scenes">
|
|
154
|
-
<
|
|
190
|
+
<List spacing="xs" listStyleType="none">
|
|
155
191
|
{(events?.expected ?? state.specBusinessEvents).map((e) => {
|
|
156
192
|
const missing = events?.missing.includes(e);
|
|
157
193
|
const noSignal = events?.noSignal.includes(e);
|
|
158
194
|
const seen = events?.observed.includes(e);
|
|
159
195
|
return (
|
|
160
|
-
<
|
|
161
|
-
<
|
|
162
|
-
{missing ?
|
|
163
|
-
</
|
|
164
|
-
<span>
|
|
196
|
+
<List.Item key={e} data-passed={missing ? false : seen ? true : 'unknown'}>
|
|
197
|
+
<Text aria-hidden="true" span inherit>
|
|
198
|
+
{missing ? <IconX size={16} /> : seen ? <IconCheck size={16} /> : <IconCircle size={16} />}
|
|
199
|
+
</Text>
|
|
200
|
+
<Text span inherit>
|
|
165
201
|
{e}
|
|
166
|
-
{missing &&
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
202
|
+
{missing && (
|
|
203
|
+
<Text span inherit c="dimmed">
|
|
204
|
+
Didn’t happen in this run
|
|
205
|
+
</Text>
|
|
206
|
+
)}
|
|
207
|
+
{noSignal && (
|
|
208
|
+
<Text span inherit c="dimmed">
|
|
209
|
+
Can’t be checked yet
|
|
210
|
+
</Text>
|
|
211
|
+
)}
|
|
212
|
+
</Text>
|
|
213
|
+
</List.Item>
|
|
170
214
|
);
|
|
171
215
|
})}
|
|
172
|
-
</
|
|
216
|
+
</List>
|
|
173
217
|
</Section>
|
|
174
218
|
)}
|
|
175
219
|
{state.knownIssues.length > 0 && (
|
|
176
220
|
<Section title="Known issues">
|
|
177
|
-
<
|
|
221
|
+
<List spacing="xs" listStyleType="none">
|
|
178
222
|
{state.knownIssues.map((n) => (
|
|
179
|
-
<
|
|
223
|
+
<Alert key={n.body} color={'blue'} my="sm">
|
|
180
224
|
{n.body}
|
|
181
|
-
</
|
|
225
|
+
</Alert>
|
|
182
226
|
))}
|
|
183
|
-
</
|
|
227
|
+
</List>
|
|
184
228
|
</Section>
|
|
185
229
|
)}
|
|
186
230
|
<Questions view={view} ids={state.questionIds} />
|
|
187
231
|
{incoming.length > 0 && (
|
|
188
232
|
<Section title="How users get here">
|
|
189
|
-
<
|
|
233
|
+
<Stack gap="sm" miw={0}>
|
|
190
234
|
{incoming.map((t) => (
|
|
191
235
|
<EventLink key={t.id} t={t} view={view} direction="in" />
|
|
192
236
|
))}
|
|
193
|
-
</
|
|
237
|
+
</Stack>
|
|
194
238
|
</Section>
|
|
195
239
|
)}
|
|
196
240
|
|
|
197
|
-
{state.deadEnd && outgoing.length > 0 &&
|
|
241
|
+
{state.deadEnd && outgoing.length > 0 && (
|
|
242
|
+
<Alert color={'blue'} my="sm">
|
|
243
|
+
This is where the journey stops: {state.deadEnd}
|
|
244
|
+
</Alert>
|
|
245
|
+
)}
|
|
198
246
|
<Developer
|
|
199
247
|
rows={[
|
|
200
248
|
['State ID', <code key="id">{state.name}</code>],
|
|
201
249
|
['Spec file', <code key="f">{state.file}</code>],
|
|
202
250
|
['Snapshot name', state.snapshot ? <code key="s">{state.snapshot}</code> : null],
|
|
203
|
-
[
|
|
251
|
+
[
|
|
252
|
+
'Sources',
|
|
253
|
+
state.sources.length ? (
|
|
254
|
+
<ul key="src">
|
|
255
|
+
{state.sources.map((s) => (
|
|
256
|
+
<li key={s}>
|
|
257
|
+
<code>{s}</code>
|
|
258
|
+
</li>
|
|
259
|
+
))}
|
|
260
|
+
</ul>
|
|
261
|
+
) : null
|
|
262
|
+
],
|
|
204
263
|
['Owner', state.owner],
|
|
205
264
|
['Tier', state.tier],
|
|
206
265
|
[
|
|
@@ -220,27 +279,42 @@ function ScreenDetails({ view, state }: { view: AtlasView; state: StateView }) {
|
|
|
220
279
|
</>
|
|
221
280
|
);
|
|
222
281
|
}
|
|
223
|
-
|
|
224
282
|
function EventDetails({ view, t }: { view: AtlasView; t: TransitionView }) {
|
|
225
283
|
const from = view.states.get(t.source);
|
|
226
284
|
const to = t.target ? view.states.get(t.target) : undefined;
|
|
227
285
|
const note = statusNote(t.status, t.result?.reason);
|
|
228
286
|
return (
|
|
229
287
|
<>
|
|
230
|
-
<
|
|
231
|
-
<
|
|
288
|
+
<Flex gap="xs" align="center" wrap="wrap">
|
|
289
|
+
<Title order={1} mb="sm">
|
|
290
|
+
{t.event}
|
|
291
|
+
</Title>
|
|
232
292
|
<StatusBadge status={t.status} />
|
|
233
|
-
</
|
|
234
|
-
<
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
293
|
+
</Flex>
|
|
294
|
+
<Text component="div" c="dimmed">
|
|
295
|
+
{KIND_LABEL[t.kind]}
|
|
296
|
+
</Text>
|
|
297
|
+
<Text component="div" c="dimmed">
|
|
298
|
+
From{' '}
|
|
299
|
+
<Text span inherit fw={600}>
|
|
300
|
+
{from?.name ?? t.source}
|
|
301
|
+
</Text>{' '}
|
|
302
|
+
to{' '}
|
|
303
|
+
<Text span inherit fw={600}>
|
|
304
|
+
{to?.name ?? 'a screen not in the spec'}
|
|
305
|
+
</Text>
|
|
306
|
+
.
|
|
307
|
+
</Text>
|
|
308
|
+
{note && (
|
|
309
|
+
<Text component="div" c="dimmed">
|
|
310
|
+
{note}
|
|
311
|
+
</Text>
|
|
312
|
+
)}
|
|
239
313
|
<Stage
|
|
240
314
|
looks={eventLooks(view, t)}
|
|
241
315
|
prefer="film"
|
|
242
316
|
notice={t.result?.clip.state === 'processing' ? 'The video of this event is still processing.' : null}
|
|
243
|
-
empty={<
|
|
317
|
+
empty={<Text c="dimmed">This event has no picture, video, or timeline in this run.</Text>}
|
|
244
318
|
/>
|
|
245
319
|
<Questions view={view} ids={t.questionIds} />
|
|
246
320
|
<Developer
|
|
@@ -253,45 +327,72 @@ function EventDetails({ view, t }: { view: AtlasView; t: TransitionView }) {
|
|
|
253
327
|
</>
|
|
254
328
|
);
|
|
255
329
|
}
|
|
256
|
-
|
|
257
330
|
function JourneyLooks({ view, journeyId }: { view: AtlasView; journeyId: string }) {
|
|
258
331
|
const looks = journeyLooks(view, journeyId);
|
|
259
332
|
if (looks.pictures.length === 0 && looks.films.length === 0 && !looks.moments.some((moment) => moment.thumb)) return null;
|
|
260
333
|
return <Stage looks={looks} prefer="timeline" />;
|
|
261
334
|
}
|
|
262
|
-
|
|
263
335
|
function JourneyDetails({ view, journeyId, chartId }: { view: AtlasView; journeyId: string; chartId: string }) {
|
|
264
336
|
const j = view.journeys.find((x) => x.id === journeyId);
|
|
265
|
-
if (!j)
|
|
337
|
+
if (!j)
|
|
338
|
+
return (
|
|
339
|
+
<Text component="div" c="dimmed">
|
|
340
|
+
This journey isn’t in the spec.
|
|
341
|
+
</Text>
|
|
342
|
+
);
|
|
266
343
|
return (
|
|
267
344
|
<>
|
|
268
|
-
<
|
|
269
|
-
<
|
|
345
|
+
<Flex gap="xs" align="center" wrap="wrap">
|
|
346
|
+
<Title order={1} mb="sm">
|
|
347
|
+
{j.name}
|
|
348
|
+
</Title>
|
|
270
349
|
<StatusBadge status={j.status} />
|
|
271
|
-
</
|
|
272
|
-
{j.description &&
|
|
273
|
-
|
|
350
|
+
</Flex>
|
|
351
|
+
{j.description && (
|
|
352
|
+
<Text component="div" c="dimmed">
|
|
353
|
+
{j.description}
|
|
354
|
+
</Text>
|
|
355
|
+
)}
|
|
356
|
+
{!j.replay.ok && (
|
|
357
|
+
<Text component="div" c="red.7">
|
|
358
|
+
The chart can’t follow this journey past “{j.replay.failedAt ?? j.replay.missingEnds.join(', ')}”.
|
|
359
|
+
</Text>
|
|
360
|
+
)}
|
|
274
361
|
<JourneyLooks view={view} journeyId={j.id} />
|
|
275
|
-
<
|
|
362
|
+
<List spacing="xs" listStyleType="none" type="ordered">
|
|
276
363
|
{j.steps.map((step, i) => {
|
|
277
364
|
const t = view.transitions.get(step.transitionIds[0] ?? '');
|
|
278
365
|
if (!t) return null;
|
|
279
|
-
const here =
|
|
366
|
+
const here =
|
|
367
|
+
view.transitions.get(t.id)?.chartId === chartId ||
|
|
368
|
+
[...view.transitions.values()].some((x) => x.carriedBy === t.id && x.chartId === chartId);
|
|
280
369
|
return (
|
|
281
|
-
<
|
|
282
|
-
<
|
|
283
|
-
|
|
284
|
-
|
|
370
|
+
<List.Item key={i} data-here={here}>
|
|
371
|
+
<Anchor
|
|
372
|
+
renderRoot={(props) => (
|
|
373
|
+
<Link
|
|
374
|
+
{...props}
|
|
375
|
+
to="/chart/$chartId"
|
|
376
|
+
params={{ chartId: t.chartId }}
|
|
377
|
+
search={(prev) => ({ run: prev.run, journey: j.id, event: t.id })}
|
|
378
|
+
/>
|
|
379
|
+
)}
|
|
380
|
+
>
|
|
381
|
+
<Text span inherit>
|
|
382
|
+
{i + 1}
|
|
383
|
+
</Text>
|
|
384
|
+
<Text data-status={t.status} span inherit>
|
|
385
|
+
{t.event}
|
|
386
|
+
</Text>
|
|
285
387
|
<StatusDot status={t.status} />
|
|
286
|
-
</
|
|
287
|
-
</
|
|
388
|
+
</Anchor>
|
|
389
|
+
</List.Item>
|
|
288
390
|
);
|
|
289
391
|
})}
|
|
290
|
-
</
|
|
392
|
+
</List>
|
|
291
393
|
</>
|
|
292
394
|
);
|
|
293
395
|
}
|
|
294
|
-
|
|
295
396
|
export function SidePanel({ view, chartId, selection }: { view: AtlasView; chartId: string; selection: MapSelection }) {
|
|
296
397
|
const state = selection.screen ? view.states.get(selection.screen) : undefined;
|
|
297
398
|
const transition = selection.event ? view.transitions.get(selection.event) : undefined;
|
|
@@ -307,25 +408,34 @@ export function SidePanel({ view, chartId, selection }: { view: AtlasView; chart
|
|
|
307
408
|
body = <JourneyDetails view={view} journeyId={selection.journey} chartId={chartId} />;
|
|
308
409
|
label = 'Journey';
|
|
309
410
|
} else {
|
|
310
|
-
body =
|
|
411
|
+
body = (
|
|
412
|
+
<Text component="div" c="dimmed">
|
|
413
|
+
This item isn’t in the spec any more. It may have been renamed or removed.
|
|
414
|
+
</Text>
|
|
415
|
+
);
|
|
311
416
|
label = 'Not found';
|
|
312
417
|
}
|
|
313
418
|
const scopeName = state?.name ?? transition?.source;
|
|
314
419
|
const scope = scopeName ? (mapScope(view, chartId, scopeName) ?? undefined) : undefined;
|
|
315
420
|
return (
|
|
316
|
-
<
|
|
421
|
+
<Container aria-label={label} component="article" size="xl" w="100%" py="xl">
|
|
317
422
|
{body}
|
|
318
423
|
<ChartTests view={view} chartId={chartId} />
|
|
319
|
-
<
|
|
320
|
-
|
|
321
|
-
params={{ chartId }}
|
|
322
|
-
search={(prev) => ({ run: prev.run, screen: undefined, event: undefined, journey: undefined, group: scope })}
|
|
323
|
-
activeOptions={{ includeSearch: true, explicitUndefined: true }}
|
|
424
|
+
{view.charts.get(chartId)?.file && <ChartSource file={view.charts.get(chartId)!.file} />}
|
|
425
|
+
<Anchor
|
|
324
426
|
aria-current={undefined}
|
|
325
|
-
|
|
427
|
+
renderRoot={(props) => (
|
|
428
|
+
<Link
|
|
429
|
+
{...props}
|
|
430
|
+
to="/chart/$chartId"
|
|
431
|
+
params={{ chartId }}
|
|
432
|
+
search={(prev) => ({ run: prev.run, screen: undefined, event: undefined, journey: undefined, group: scope })}
|
|
433
|
+
activeOptions={{ includeSearch: true, explicitUndefined: true }}
|
|
434
|
+
/>
|
|
435
|
+
)}
|
|
326
436
|
>
|
|
327
437
|
Show the map
|
|
328
|
-
</
|
|
329
|
-
</
|
|
438
|
+
</Anchor>
|
|
439
|
+
</Container>
|
|
330
440
|
);
|
|
331
441
|
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
.index {
|
|
2
|
-
background: var(--bg);
|
|
3
|
-
border-right: 1px solid var(--line);
|
|
4
|
-
overflow: auto;
|
|
5
|
-
padding: 12px 8px 32px;
|
|
6
|
-
min-height: 0;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
.index h2 {
|
|
10
|
-
margin: 14px 10px 6px;
|
|
11
|
-
font-size: 0.9375rem;
|
|
12
|
-
font-weight: 600;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.index h3 {
|
|
16
|
-
margin: 16px 10px 4px;
|
|
17
|
-
color: var(--ink-3);
|
|
18
|
-
font-size: 0.75rem;
|
|
19
|
-
font-weight: 600;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
.index ul {
|
|
23
|
-
list-style: none;
|
|
24
|
-
margin: 0;
|
|
25
|
-
padding: 0;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
.chart,
|
|
29
|
-
.row {
|
|
30
|
-
display: flex;
|
|
31
|
-
justify-content: space-between;
|
|
32
|
-
align-items: center;
|
|
33
|
-
gap: 10px;
|
|
34
|
-
padding: 6px 10px;
|
|
35
|
-
border-radius: 8px;
|
|
36
|
-
color: inherit;
|
|
37
|
-
font-size: 0.9rem;
|
|
38
|
-
text-decoration: none;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
.thumb {
|
|
42
|
-
flex: none;
|
|
43
|
-
width: 28px;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.name {
|
|
47
|
-
flex: 1;
|
|
48
|
-
min-width: 0;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.chart {
|
|
52
|
-
color: var(--ink-2);
|
|
53
|
-
font-weight: 600;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
.chart:hover,
|
|
57
|
-
.row:hover {
|
|
58
|
-
background: var(--bg-2);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.chart[aria-current='page'],
|
|
62
|
-
.row[aria-current='page'] {
|
|
63
|
-
background: var(--place-soft);
|
|
64
|
-
color: var(--ink);
|
|
65
|
-
font-weight: 600;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.status {
|
|
69
|
-
flex: none;
|
|
70
|
-
color: var(--ink-3);
|
|
71
|
-
font-size: 0.75rem;
|
|
72
|
-
font-weight: 400;
|
|
73
|
-
text-align: right;
|
|
74
|
-
white-space: nowrap;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
.row[data-status='failed'] .status {
|
|
78
|
-
color: var(--failed);
|
|
79
|
-
font-weight: 600;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
.row[data-status='flaky'] .status {
|
|
83
|
-
color: var(--flaky-ink);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.excluded {
|
|
87
|
-
padding: 6px 10px;
|
|
88
|
-
color: var(--ink-3);
|
|
89
|
-
font-size: 0.9rem;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
.empty {
|
|
93
|
-
margin: 4px 10px;
|
|
94
|
-
color: var(--ink-3);
|
|
95
|
-
font-size: 0.875rem;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
@media (max-width: 760px) {
|
|
99
|
-
.index {
|
|
100
|
-
flex: none;
|
|
101
|
-
max-height: none;
|
|
102
|
-
min-height: auto;
|
|
103
|
-
overflow: visible;
|
|
104
|
-
border-right: 0;
|
|
105
|
-
border-top: 1px solid var(--line);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
.activity { display: inline-flex; align-items: center; justify-content: center; width: 12px; height: 12px; flex: none; color: var(--place); }
|
|
2
|
-
.ring { box-sizing: border-box; width: 10px; height: 10px; border: 1.5px solid currentColor; border-right-color: transparent; border-bottom-color: transparent; border-radius: 50%; opacity: .65; animation: turn 1.8s linear infinite; }
|
|
3
|
-
@keyframes turn { to { transform: rotate(360deg); } }
|
|
4
|
-
@media (prefers-reduced-motion: reduce) { .ring { animation: none; } }
|