@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
|
@@ -1,64 +1,80 @@
|
|
|
1
|
+
import { SpecLogo } from '../components/SpecLogo';
|
|
1
2
|
import type { SpecEvidence } from '@crvouga/atlas/reports/evidence-view';
|
|
2
3
|
import { NO_EVIDENCE } from '@crvouga/atlas/reports/evidence-view';
|
|
4
|
+
import { Box, Flex, Group, Stack, Text, Title } from '@mantine/core';
|
|
3
5
|
import type { ReactNode } from 'react';
|
|
4
|
-
|
|
5
6
|
import type { SpecKind } from '../spec-kind';
|
|
6
7
|
import { useEvidence } from './evidence-context';
|
|
7
|
-
import styles from './evidence.module.css';
|
|
8
8
|
import { useNow } from './format';
|
|
9
9
|
import { CountLine, Meter, Origin } from './marks';
|
|
10
|
-
|
|
11
10
|
/** A spec's name, where it lives, and what the combined report holds for it. */
|
|
12
|
-
export function SpecHeader({
|
|
11
|
+
export function SpecHeader({
|
|
12
|
+
title,
|
|
13
|
+
file,
|
|
14
|
+
kind,
|
|
15
|
+
entry,
|
|
16
|
+
planned,
|
|
17
|
+
children
|
|
18
|
+
}: {
|
|
19
|
+
title: string;
|
|
20
|
+
file: string;
|
|
21
|
+
kind: SpecKind;
|
|
22
|
+
entry: SpecEvidence | undefined;
|
|
23
|
+
planned?: number;
|
|
24
|
+
children?: ReactNode;
|
|
25
|
+
}) {
|
|
13
26
|
const { backends, ready, unavailable, pending } = useEvidence();
|
|
14
27
|
const now = useNow();
|
|
15
28
|
const summary = entry?.summary ?? NO_EVIDENCE;
|
|
16
|
-
const newest = entry?.slots
|
|
29
|
+
const newest = entry?.slots
|
|
30
|
+
.flatMap((slot) => slot.outcomes)
|
|
31
|
+
.reduce<(typeof entry.slots)[number]['outcomes'][number] | null>(
|
|
32
|
+
(best, result) => (!best || result.layer.recordedAtMs > best.layer.recordedAtMs ? result : best),
|
|
33
|
+
null
|
|
34
|
+
);
|
|
17
35
|
const missing = planned === undefined ? 0 : Math.max(0, planned - summary.total);
|
|
18
36
|
return (
|
|
19
|
-
<header
|
|
20
|
-
<
|
|
21
|
-
|
|
37
|
+
<Box component="header" mb="lg">
|
|
38
|
+
<Text component="div" c="dimmed">
|
|
39
|
+
specs/{file}
|
|
40
|
+
</Text>
|
|
41
|
+
<Group gap="sm" mb="sm" wrap="nowrap">
|
|
42
|
+
<SpecLogo kind={kind} />
|
|
43
|
+
<Title order={1}>{title}</Title>
|
|
44
|
+
</Group>
|
|
22
45
|
{children}
|
|
23
|
-
<
|
|
46
|
+
<Stack gap="sm" miw={0}>
|
|
24
47
|
<Meter counts={summary.counts} missing={missing} />
|
|
25
|
-
<
|
|
48
|
+
<Flex gap="xs" align="center" wrap="wrap">
|
|
26
49
|
{unavailable ? (
|
|
27
|
-
<span
|
|
50
|
+
<Text span inherit>
|
|
51
|
+
Recorded results are unavailable.
|
|
52
|
+
</Text>
|
|
28
53
|
) : !ready ? (
|
|
29
|
-
<
|
|
54
|
+
<Text role="status" span inherit>
|
|
55
|
+
Loading recorded results…
|
|
56
|
+
</Text>
|
|
30
57
|
) : summary.status === 'none' ? (
|
|
31
|
-
<span
|
|
58
|
+
<Text span inherit>
|
|
59
|
+
{pending > 0 ? 'Loading recorded results…' : `No completed test report for this ${kind.one} yet.`}
|
|
60
|
+
</Text>
|
|
32
61
|
) : (
|
|
33
62
|
<>
|
|
34
63
|
<CountLine summary={summary} noun={kind.check} nouns={`${kind.check}s`} />
|
|
35
|
-
{missing > 0 &&
|
|
64
|
+
{missing > 0 && (
|
|
65
|
+
<Text span inherit>
|
|
66
|
+
{missing} without a result
|
|
67
|
+
</Text>
|
|
68
|
+
)}
|
|
36
69
|
</>
|
|
37
70
|
)}
|
|
38
|
-
</
|
|
71
|
+
</Flex>
|
|
39
72
|
{newest && (
|
|
40
|
-
<
|
|
73
|
+
<Flex gap="xs" align="center" wrap="wrap" c="dimmed">
|
|
41
74
|
Latest from <Origin layer={newest.layer} backends={backends} now={now} />
|
|
42
|
-
</
|
|
75
|
+
</Flex>
|
|
43
76
|
)}
|
|
44
|
-
</
|
|
45
|
-
</
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** The document as written, folded away under what was understood from it. */
|
|
50
|
-
export function SourceText({ text, label = 'Source', open = false }: { text: string; label?: string; open?: boolean }) {
|
|
51
|
-
return (
|
|
52
|
-
<details className={styles.source} open={open}>
|
|
53
|
-
<summary>{label}</summary>
|
|
54
|
-
<pre className={styles.code}>
|
|
55
|
-
{text.split('\n').map((line, index) => (
|
|
56
|
-
<span id={`line-${index + 1}`} key={index}>
|
|
57
|
-
{line}
|
|
58
|
-
{'\n'}
|
|
59
|
-
</span>
|
|
60
|
-
))}
|
|
61
|
-
</pre>
|
|
62
|
-
</details>
|
|
77
|
+
</Stack>
|
|
78
|
+
</Box>
|
|
63
79
|
);
|
|
64
80
|
}
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import { addSummaries, NO_EVIDENCE, type SpecSummary } from '@crvouga/atlas/reports/evidence-view';
|
|
2
|
+
import { Box, Button, Group, NavLink, Stack, Text, Title } from '@mantine/core';
|
|
2
3
|
import { Link, useSearch } from '@tanstack/react-router';
|
|
3
4
|
import { useMemo } from 'react';
|
|
4
|
-
|
|
5
5
|
import { folderOf, outlinePath, specFile, type SpecKind } from '../spec-kind';
|
|
6
6
|
import { useUiStore, type ResultFilter } from '../state/ui-store';
|
|
7
7
|
import { useEvidence } from './evidence-context';
|
|
8
|
-
import styles from './evidence.module.css';
|
|
9
8
|
import { StatusMark, Tally } from './marks';
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const FILTERS: readonly {
|
|
10
|
+
readonly id: ResultFilter;
|
|
11
|
+
readonly label: string;
|
|
12
|
+
readonly keeps: (summary: SpecSummary) => boolean;
|
|
13
|
+
}[] = [
|
|
12
14
|
{ id: 'all', label: 'All', keeps: () => true },
|
|
13
15
|
{ id: 'results', label: 'Results', keeps: (summary) => summary.status !== 'none' },
|
|
14
16
|
{ id: 'running', label: 'Running', keeps: (summary) => summary.counts.running > 0 },
|
|
15
17
|
{ id: 'failed', label: 'Failing', keeps: (summary) => summary.counts.failed > 0 },
|
|
16
18
|
{ id: 'none', label: 'None', keeps: (summary) => summary.status === 'none' }
|
|
17
19
|
];
|
|
18
|
-
|
|
19
20
|
/** Every spec of one kind, grouped by folder, each with what the combined report holds for it. */
|
|
20
21
|
export function SpecIndex({ kind }: { kind: SpecKind }) {
|
|
21
22
|
const evidence = useEvidence();
|
|
@@ -24,7 +25,12 @@ export function SpecIndex({ kind }: { kind: SpecKind }) {
|
|
|
24
25
|
const setFilter = useUiStore((s) => s.setResultFilter);
|
|
25
26
|
const open = typeof search.spec === 'string' ? specFile(search.spec) : '';
|
|
26
27
|
const rows = useMemo(
|
|
27
|
-
() =>
|
|
28
|
+
() =>
|
|
29
|
+
evidence.files.filter(kind.accepts).map((file) => ({
|
|
30
|
+
file,
|
|
31
|
+
title: evidence.titleOf(file),
|
|
32
|
+
summary: evidence.view.specs.get(outlinePath(file))?.summary ?? NO_EVIDENCE
|
|
33
|
+
})),
|
|
28
34
|
// `titleOf` reads the outline the view was built from.
|
|
29
35
|
[evidence.files, evidence.view, kind]
|
|
30
36
|
);
|
|
@@ -36,40 +42,63 @@ export function SpecIndex({ kind }: { kind: SpecKind }) {
|
|
|
36
42
|
return [...byFolder];
|
|
37
43
|
}, [visible]);
|
|
38
44
|
return (
|
|
39
|
-
<
|
|
40
|
-
<
|
|
45
|
+
<Box aria-label={`${kind.label} ${kind.many}`} component="nav" p="md">
|
|
46
|
+
<Group role="group" aria-label="Show" gap="xs" mb="md">
|
|
41
47
|
{FILTERS.map((entry) => (
|
|
42
|
-
<
|
|
43
|
-
{entry.
|
|
44
|
-
|
|
48
|
+
<Button
|
|
49
|
+
key={entry.id}
|
|
50
|
+
type="button"
|
|
51
|
+
aria-pressed={filter === entry.id}
|
|
52
|
+
onClick={() => setFilter(entry.id)}
|
|
53
|
+
variant={filter === entry.id ? 'light' : 'subtle'}
|
|
54
|
+
>
|
|
55
|
+
{entry.label}{' '}
|
|
56
|
+
<Text span inherit>
|
|
57
|
+
{rows.filter((row) => entry.keeps(row.summary)).length}
|
|
58
|
+
</Text>
|
|
59
|
+
</Button>
|
|
45
60
|
))}
|
|
46
|
-
</
|
|
47
|
-
{evidence.filesPending && <
|
|
48
|
-
{evidence.filesError && <
|
|
61
|
+
</Group>
|
|
62
|
+
{evidence.filesPending && <Text component="div">Loading…</Text>}
|
|
63
|
+
{evidence.filesError && <Text component="div">The file list could not be loaded.</Text>}
|
|
49
64
|
{!evidence.filesPending && !evidence.filesError && visible.length === 0 && (
|
|
50
|
-
<
|
|
51
|
-
{rows.length === 0
|
|
52
|
-
|
|
65
|
+
<Text component="div">
|
|
66
|
+
{rows.length === 0
|
|
67
|
+
? `No ${kind.many} in this spec set.`
|
|
68
|
+
: filter === 'running'
|
|
69
|
+
? `No running ${kind.many}.`
|
|
70
|
+
: filter === 'failed'
|
|
71
|
+
? `No failing ${kind.many}.`
|
|
72
|
+
: filter === 'results'
|
|
73
|
+
? `No ${kind.many} with results yet.`
|
|
74
|
+
: `Every ${kind.one} has results.`}
|
|
75
|
+
</Text>
|
|
53
76
|
)}
|
|
54
77
|
{groups.map(([folder, entries]) => (
|
|
55
|
-
<
|
|
56
|
-
<
|
|
57
|
-
<span
|
|
78
|
+
<Box key={folder || kind.id} component="section" mb="lg">
|
|
79
|
+
<Title order={3} mb="sm">
|
|
80
|
+
<Text span inherit>
|
|
81
|
+
{folder || kind.label}
|
|
82
|
+
</Text>
|
|
58
83
|
<Tally summary={addSummaries(entries.map((row) => row.summary))} />
|
|
59
|
-
</
|
|
60
|
-
<
|
|
84
|
+
</Title>
|
|
85
|
+
<Stack gap="xs">
|
|
61
86
|
{entries.map((row) => (
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
87
|
+
<NavLink
|
|
88
|
+
key={row.file}
|
|
89
|
+
label={row.title}
|
|
90
|
+
title={row.file}
|
|
91
|
+
active={open === row.file}
|
|
92
|
+
leftSection={<StatusMark status={row.summary.status} size={14} />}
|
|
93
|
+
rightSection={row.summary.status !== 'none' ? <Tally summary={row.summary} /> : undefined}
|
|
94
|
+
data-result={row.summary.status}
|
|
95
|
+
aria-current={open === row.file ? 'page' : undefined}
|
|
96
|
+
renderRoot={(props) => <Link {...props} to="/specs/$kind" params={{ kind: kind.id }} search={{ spec: row.file }} />}
|
|
97
|
+
/>
|
|
69
98
|
))}
|
|
70
|
-
</
|
|
71
|
-
</
|
|
99
|
+
</Stack>
|
|
100
|
+
</Box>
|
|
72
101
|
))}
|
|
73
|
-
</
|
|
102
|
+
</Box>
|
|
74
103
|
);
|
|
75
104
|
}
|
|
@@ -1,24 +1,32 @@
|
|
|
1
|
+
import { Alert, Button, Container, Text, Title } from '@mantine/core';
|
|
1
2
|
import { useQuery } from '@tanstack/react-query';
|
|
2
3
|
import { useNavigate, useParams, useSearch } from '@tanstack/react-router';
|
|
3
4
|
import { lazy, Suspense } from 'react';
|
|
4
|
-
|
|
5
5
|
import { keys, specKey, useAdapter } from '../data/queries';
|
|
6
6
|
import { outlinePath, specFile, specKind } from '../spec-kind';
|
|
7
7
|
import type { SelectCheck } from './check-detail';
|
|
8
8
|
import { useEvidence } from './evidence-context';
|
|
9
|
-
import styles from './evidence.module.css';
|
|
10
9
|
import { Overview } from './overview';
|
|
11
|
-
|
|
12
10
|
// Each kind of spec has a page with a parser of its own (Gherkin, Quint, DMN); a page loads only the one it shows.
|
|
13
11
|
const DocumentPage = lazy(() => import('./document-page').then((m) => ({ default: m.DocumentPage })));
|
|
14
12
|
const FeaturePage = lazy(() => import('./feature-page').then((m) => ({ default: m.FeaturePage })));
|
|
15
13
|
const ModelPage = lazy(() => import('./model-page').then((m) => ({ default: m.ModelPage })));
|
|
16
14
|
const RecordingPage = lazy(() => import('./recording-page').then((m) => ({ default: m.RecordingPage })));
|
|
17
|
-
|
|
18
15
|
function Loading({ children }: { children: React.ReactNode }) {
|
|
19
|
-
return
|
|
16
|
+
return (
|
|
17
|
+
<Suspense
|
|
18
|
+
fallback={
|
|
19
|
+
<Container>
|
|
20
|
+
<Text role="status" c="dimmed">
|
|
21
|
+
Loading…
|
|
22
|
+
</Text>
|
|
23
|
+
</Container>
|
|
24
|
+
}
|
|
25
|
+
>
|
|
26
|
+
{children}
|
|
27
|
+
</Suspense>
|
|
28
|
+
);
|
|
20
29
|
}
|
|
21
|
-
|
|
22
30
|
/** One spec tab: its overview, one of its documents, or a recording opened from that document. */
|
|
23
31
|
export function SpecPage() {
|
|
24
32
|
const params = useParams({ from: '/specs/$kind' });
|
|
@@ -29,26 +37,37 @@ export function SpecPage() {
|
|
|
29
37
|
const kind = specKind(params.kind);
|
|
30
38
|
const file = search.spec ? specFile(search.spec) : '';
|
|
31
39
|
const readable = kind !== null && file.length > 0 && kind.accepts(file);
|
|
32
|
-
const text = useQuery({
|
|
33
|
-
|
|
40
|
+
const text = useQuery({
|
|
41
|
+
queryKey: keys.specFile(specKey(adapter), file),
|
|
42
|
+
queryFn: () => adapter.specFile(file),
|
|
43
|
+
enabled: readable,
|
|
44
|
+
retry: false
|
|
45
|
+
});
|
|
34
46
|
if (!kind) {
|
|
35
47
|
return (
|
|
36
|
-
<article
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
<Container component="article" size="xl" w="100%" py="xl">
|
|
49
|
+
<Title order={1} mb="sm">
|
|
50
|
+
No such kind of spec
|
|
51
|
+
</Title>
|
|
52
|
+
<Text component="div" c="dimmed">
|
|
53
|
+
Pick a tab above.
|
|
54
|
+
</Text>
|
|
55
|
+
</Container>
|
|
40
56
|
);
|
|
41
57
|
}
|
|
42
58
|
if (!file) return <Overview kind={kind} />;
|
|
43
59
|
if (!readable) {
|
|
44
60
|
return (
|
|
45
|
-
<article
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
61
|
+
<Container component="article" size="xl" w="100%" py="xl">
|
|
62
|
+
<Title order={1} mb="sm">
|
|
63
|
+
{kind.label}
|
|
64
|
+
</Title>
|
|
65
|
+
<Text component="div" c="dimmed">
|
|
66
|
+
This file is not a {kind.one}.
|
|
67
|
+
</Text>
|
|
68
|
+
</Container>
|
|
49
69
|
);
|
|
50
70
|
}
|
|
51
|
-
|
|
52
71
|
const entry = evidence.view.specs.get(outlinePath(file));
|
|
53
72
|
const select: SelectCheck = (patch) => {
|
|
54
73
|
const value = <T,>(next: T | null | undefined, current: T | undefined) => (next === undefined ? current : (next ?? undefined));
|
|
@@ -63,7 +82,6 @@ export function SpecPage() {
|
|
|
63
82
|
})
|
|
64
83
|
});
|
|
65
84
|
};
|
|
66
|
-
|
|
67
85
|
if (search.view === 'recording' && search.check) {
|
|
68
86
|
const slot = entry?.slots.find((candidate) => candidate.id === search.check);
|
|
69
87
|
// The way back already names the spec; the heading is the check itself.
|
|
@@ -83,17 +101,30 @@ export function SpecPage() {
|
|
|
83
101
|
</Loading>
|
|
84
102
|
);
|
|
85
103
|
}
|
|
86
|
-
if (text.isPending)
|
|
104
|
+
if (text.isPending)
|
|
105
|
+
return (
|
|
106
|
+
<Container component="article" size="xl" w="100%" py="xl">
|
|
107
|
+
<Text role="status" component="div" c="dimmed">
|
|
108
|
+
Loading the {kind.one}…
|
|
109
|
+
</Text>
|
|
110
|
+
</Container>
|
|
111
|
+
);
|
|
87
112
|
if (text.isError) {
|
|
88
113
|
return (
|
|
89
|
-
<article
|
|
90
|
-
<
|
|
114
|
+
<Container component="article" size="xl" w="100%" py="xl">
|
|
115
|
+
<Alert role="alert" color={'red'} my="sm">
|
|
91
116
|
The {kind.one} could not be read.{' '}
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
117
|
+
<Button type="button" onClick={() => void text.refetch()} variant="subtle">
|
|
118
|
+
Try again
|
|
119
|
+
</Button>
|
|
120
|
+
</Alert>
|
|
121
|
+
</Container>
|
|
95
122
|
);
|
|
96
123
|
}
|
|
97
124
|
const page = { file, text: text.data, kind, entry, selection: { check: search.check, layer: search.layer }, select };
|
|
98
|
-
return
|
|
125
|
+
return (
|
|
126
|
+
<Loading>
|
|
127
|
+
{kind.id === 'gherkin' ? <FeaturePage {...page} /> : kind.id === 'quint' ? <ModelPage {...page} /> : <DocumentPage {...page} />}
|
|
128
|
+
</Loading>
|
|
129
|
+
);
|
|
99
130
|
}
|
|
@@ -1,21 +1,45 @@
|
|
|
1
1
|
import type { CheckSlot } from '@crvouga/atlas/reports/evidence-view';
|
|
2
|
+
import { Button, Group, Text } from '@mantine/core';
|
|
2
3
|
import { useUiStore } from '../state/ui-store';
|
|
3
|
-
import styles from './evidence.module.css';
|
|
4
|
-
|
|
5
4
|
/** Filter actual test rows as well as the spec index. Counts always describe the whole document. */
|
|
6
5
|
export function TestFilter({ slots }: { slots: readonly CheckSlot[] }) {
|
|
7
|
-
const filter = useUiStore(s => s.resultFilter);
|
|
8
|
-
const setFilter = useUiStore(s => s.setResultFilter);
|
|
9
|
-
const running = slots.filter(slot => slot.status === 'running').length;
|
|
10
|
-
return
|
|
11
|
-
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
const filter = useUiStore((s) => s.resultFilter);
|
|
7
|
+
const setFilter = useUiStore((s) => s.setResultFilter);
|
|
8
|
+
const running = slots.filter((slot) => slot.status === 'running').length;
|
|
9
|
+
return (
|
|
10
|
+
<>
|
|
11
|
+
<Group role="group" aria-label="Test status" gap="sm" my="md">
|
|
12
|
+
<Button
|
|
13
|
+
type="button"
|
|
14
|
+
aria-pressed={filter !== 'running'}
|
|
15
|
+
onClick={() => setFilter('all')}
|
|
16
|
+
variant={filter !== 'running' ? 'light' : 'subtle'}
|
|
17
|
+
>
|
|
18
|
+
All tests{' '}
|
|
19
|
+
<Text span inherit>
|
|
20
|
+
{slots.length}
|
|
21
|
+
</Text>
|
|
22
|
+
</Button>
|
|
23
|
+
<Button
|
|
24
|
+
type="button"
|
|
25
|
+
aria-pressed={filter === 'running'}
|
|
26
|
+
onClick={() => setFilter('running')}
|
|
27
|
+
variant={filter === 'running' ? 'light' : 'subtle'}
|
|
28
|
+
>
|
|
29
|
+
Running{' '}
|
|
30
|
+
<Text span inherit>
|
|
31
|
+
{running}
|
|
32
|
+
</Text>
|
|
33
|
+
</Button>
|
|
34
|
+
</Group>
|
|
35
|
+
{filter === 'running' && running === 0 && (
|
|
36
|
+
<Text role="status" component="div" c="dimmed">
|
|
37
|
+
No running tests.
|
|
38
|
+
</Text>
|
|
39
|
+
)}
|
|
40
|
+
</>
|
|
41
|
+
);
|
|
17
42
|
}
|
|
18
|
-
|
|
19
43
|
export function useRunningFilter() {
|
|
20
|
-
return useUiStore(s => s.resultFilter === 'running');
|
|
44
|
+
return useUiStore((s) => s.resultFilter === 'running');
|
|
21
45
|
}
|