@crvouga/atlas 1.16.0 → 1.18.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 +13 -1
- package/dist/reports/src/artifact-query.d.ts +14 -2
- package/dist/reports/src/artifact-query.js +13 -4
- package/dist/reports/src/evidence-detail.js +7 -4
- package/dist/reports/src/evidence-types.d.ts +4 -0
- package/dist/reports/src/recording-receipt.d.ts +23 -0
- package/dist/reports/src/recording-receipt.js +16 -0
- package/dist/reports/src/recording-verify.d.ts +74 -0
- package/dist/reports/src/recording-verify.js +142 -0
- package/dist/reports/src/report-cli.d.ts +2 -2
- package/dist/reports/src/report-cli.js +53 -3
- package/dist/reports/src/test-catalog.d.ts +20 -1
- package/dist/reports/src/test-catalog.js +31 -9
- package/dist/schema/src/api.d.ts +113 -0
- package/dist/schema/src/api.js +17 -3
- package/dist/schema/src/cli-results.d.ts +51 -1
- package/dist/schema/src/cli-results.js +25 -1
- package/dist/server/src/openapi.js +1 -0
- package/docs/agents.md +1 -0
- package/docs/extending.md +1 -1
- package/docs/reference/cli.md +11 -2
- package/docs/reference/http-api.md +2 -0
- package/docs/reference/reports.md +202 -9
- package/docs/reference/schema.md +19 -1
- package/docs/rich-evidence.md +32 -5
- package/docs/server.md +1 -1
- package/docs/ui.md +8 -0
- package/llms-full.txt +56 -9
- package/package.json +1 -1
- package/packages/schema/schemas/effective-delta.schema.json +8 -0
- package/packages/schema/schemas/effective-evidence.schema.json +8 -0
- package/packages/schema/schemas/evidence-page.schema.json +4 -0
- package/packages/schema/schemas/layer-rows.schema.json +4 -0
- package/packages/schema/schemas/report-run.schema.json +74 -0
- package/packages/schema/schemas/report-runs.schema.json +86 -0
- package/packages/schema/schemas/review-collection.schema.json +4 -0
- package/packages/schema/schemas/review-manifest.schema.json +4 -0
- package/packages/schema/schemas/test-catalog.schema.json +30 -0
- package/packages/schema/schemas/test-history.schema.json +48 -0
- package/packages/schema/schemas/verify-recordings-result.schema.json +97 -0
- package/packages/server/openapi.json +318 -0
- package/packages/visualizer/src/evidence/applicability-view.tsx +46 -20
- package/packages/visualizer/src/evidence/applicability.test.ts +68 -1
- package/packages/visualizer/src/evidence/applicability.ts +121 -26
- package/packages/visualizer/src/explorer/ArtifactsView.tsx +3 -2
- package/packages/visualizer/src/explorer/Explorer.tsx +96 -8
- package/packages/visualizer/src/explorer/RunsView.tsx +28 -3
- package/packages/visualizer/src/explorer/TestDetail.tsx +14 -1
- package/packages/visualizer/src/explorer/applicability.ts +43 -0
- package/packages/visualizer/src/explorer/filters.tsx +11 -3
- package/packages/visualizer/src/explorer/search.ts +20 -6
|
@@ -93,15 +93,39 @@ function ownerText(video: VideoRef): string {
|
|
|
93
93
|
return 'the test';
|
|
94
94
|
}
|
|
95
95
|
const UNAVAILABLE = 'Listed in the report, but its file was never stored, so it cannot be played.';
|
|
96
|
-
|
|
96
|
+
const MISSING = 'The run says it stored this file, but its bundle no longer holds it, so it cannot be played.';
|
|
97
|
+
/** The unavailable references of a video summary and why they cannot be watched: never stored, or stored and then gone. */
|
|
97
98
|
function Unavailable({ videos }: { videos: VideoCounts }) {
|
|
98
99
|
if (!videos.unavailable) return null;
|
|
100
|
+
const missing = Math.min(videos.missing ?? 0, videos.unavailable);
|
|
99
101
|
return (
|
|
100
102
|
<Text component="div" size="xs" c="dimmed" data-video-unavailable>
|
|
101
|
-
<IconAlertTriangle size={16} aria-hidden="true" /> {plural(videos.unavailable, 'video', 'videos')} unavailable.
|
|
103
|
+
<IconAlertTriangle size={16} aria-hidden="true" /> {plural(videos.unavailable, 'video', 'videos')} unavailable.{' '}
|
|
104
|
+
{missing > 0 && <Text span inherit data-video-missing>{missing === videos.unavailable ? MISSING : `${plural(missing, 'video', 'videos')}: ${MISSING}`}{' '}</Text>}
|
|
105
|
+
{missing < videos.unavailable && UNAVAILABLE}
|
|
102
106
|
</Text>
|
|
103
107
|
);
|
|
104
108
|
}
|
|
109
|
+
/** Words for a run's recording completeness, apart from how its tests went. */
|
|
110
|
+
const COMPLETENESS_WORDS: Record<string, string> = { complete: 'complete', incomplete: 'incomplete', 'none-expected': 'none expected', unknown: 'unknown' };
|
|
111
|
+
/** A run's recording completeness in its row: how many expected recordings were published, said in words and independent of its outcomes. Nothing when its layers declared no expectations (unknown is not shown as complete). */
|
|
112
|
+
function RunRecordings({ run }: { run: RunSummary }) {
|
|
113
|
+
const recordings = run.recordings;
|
|
114
|
+
if (!recordings) return null;
|
|
115
|
+
const word = COMPLETENESS_WORDS[recordings.state] ?? 'unknown';
|
|
116
|
+
const text = recordings.expected > 0 ? `Recordings ${recordings.published} of ${recordings.expected}: ${word}` : `Recordings: ${word}`;
|
|
117
|
+
return (
|
|
118
|
+
<Badge
|
|
119
|
+
size="sm"
|
|
120
|
+
variant="light"
|
|
121
|
+
color={recordings.state === 'incomplete' ? 'orange' : 'gray'}
|
|
122
|
+
data-recordings={recordings.state}
|
|
123
|
+
title={recordings.state === 'incomplete' ? 'Some expected recordings were not published. The test outcomes are unaffected.' : undefined}
|
|
124
|
+
>
|
|
125
|
+
{text}
|
|
126
|
+
</Badge>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
105
129
|
/** A run's videos in its row: a link to the recordings, how many references they come from, and what cannot be played. */
|
|
106
130
|
function RunVideos({ run }: { run: RunSummary }) {
|
|
107
131
|
const videos = run.videos;
|
|
@@ -210,7 +234,7 @@ const RUN_SORTS = [
|
|
|
210
234
|
{ id: 'relevance', label: 'Best match' }
|
|
211
235
|
] as const;
|
|
212
236
|
/** What a filter is called when a page says a server could not apply it. */
|
|
213
|
-
const FILTER_WORDS: Partial<Record<FilterName, string>> = { artifactTag: 'tag', type: 'media type', label: 'label', domain: 'domain', spec: 'spec' };
|
|
237
|
+
const FILTER_WORDS: Partial<Record<FilterName, string>> = { artifactTag: 'tag', type: 'media type', label: 'label', domain: 'domain', spec: 'spec', recordings: 'recording completeness' };
|
|
214
238
|
/** Says which filters the list is not narrowed by, because the server that answered ignored them. */
|
|
215
239
|
export function NotApplied({ names, noun }: { names: readonly FilterName[]; noun: string }) {
|
|
216
240
|
if (!names.length) return null;
|
|
@@ -383,6 +407,7 @@ function RunTable() {
|
|
|
383
407
|
<Table.Td>{formatMs(run.spanMs)}</Table.Td>
|
|
384
408
|
<Table.Td>
|
|
385
409
|
<RunVideos run={run} />
|
|
410
|
+
<RunRecordings run={run} />
|
|
386
411
|
</Table.Td>
|
|
387
412
|
<Table.Td data-tags>{run.artifactTags?.map((tag) => `#${tag}`).join(' ') || '—'}</Table.Td>
|
|
388
413
|
<Table.Td>
|
|
@@ -13,12 +13,15 @@ import { environmentLabel } from '@crvouga/atlas/reports/layer-axes';
|
|
|
13
13
|
import { formatMs, runKey, type TestResultEntry, type TestSummary } from '@crvouga/atlas/reports/test-catalog';
|
|
14
14
|
import { Link } from '@tanstack/react-router';
|
|
15
15
|
import { lazy, Suspense, useState } from 'react';
|
|
16
|
+
import { assessTest } from '../evidence/applicability';
|
|
17
|
+
import { ApplicabilitySummary } from '../evidence/applicability-view';
|
|
16
18
|
import { CheckDetail } from '../evidence/check-detail';
|
|
17
19
|
import { ago, exactTime, plural, shortSha, statusWord, useNow } from '../evidence/format';
|
|
18
20
|
import { useEvidence } from '../evidence/evidence-context';
|
|
19
21
|
import { Origin, StatusMark } from '../evidence/marks';
|
|
20
22
|
import { outcomeHeadline, selectedResult } from '../evidence/outcome';
|
|
21
23
|
import { useTestHistory } from './catalog';
|
|
24
|
+
import { releaseOf } from './applicability';
|
|
22
25
|
import { patched, useExplorerNavigate, useExplorerSearch } from './navigate';
|
|
23
26
|
import { Badges, runName } from './parts';
|
|
24
27
|
import { Reliability } from './Reliability';
|
|
@@ -129,7 +132,10 @@ function Scope({ pinned, slot }: { pinned: PinnedResult; slot: CheckSlot | undef
|
|
|
129
132
|
export function TestDetail({ test, slot }: { test: TestSummary; slot: CheckSlot | undefined }) {
|
|
130
133
|
const now = useNow();
|
|
131
134
|
const history = useTestHistory(test.key);
|
|
132
|
-
const
|
|
135
|
+
const search = useExplorerSearch();
|
|
136
|
+
const { layer, source } = search;
|
|
137
|
+
const release = releaseOf(search);
|
|
138
|
+
const applicability = assessTest(test, release);
|
|
133
139
|
const go = useExplorerNavigate();
|
|
134
140
|
const pinned = usePinnedResult(layer, test.key);
|
|
135
141
|
const [recording, setRecording] = useState(false);
|
|
@@ -238,6 +244,13 @@ export function TestDetail({ test, slot }: { test: TestSummary; slot: CheckSlot
|
|
|
238
244
|
|
|
239
245
|
<Scope pinned={pinned} slot={slot} />
|
|
240
246
|
|
|
247
|
+
<Box aria-label="Evidence applicability" component="section" mb="lg">
|
|
248
|
+
<Title order={3} mb="sm" c="dimmed">
|
|
249
|
+
Does this evidence apply?
|
|
250
|
+
</Title>
|
|
251
|
+
<ApplicabilitySummary assessment={applicability} release={release} />
|
|
252
|
+
</Box>
|
|
253
|
+
|
|
241
254
|
<Box aria-label="Current results" component="section" mb="lg">
|
|
242
255
|
<Title order={3} mb="sm" c="dimmed">
|
|
243
256
|
Current result per environment
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Explorer evidence applicability
|
|
3
|
+
*
|
|
4
|
+
* The explorer's part of evidence applicability: the release a reader names and the stale-or-unverified filter, held in
|
|
5
|
+
* the explorer's address (`release`, `releaseSha`, `releasePlatform`, `evidence=stale`, the same names a spec page uses),
|
|
6
|
+
* and the reading of every test of the catalog against them. The reading is the one a feature page makes
|
|
7
|
+
* (`evidence/applicability.ts`); it never changes a test's status.
|
|
8
|
+
*/
|
|
9
|
+
import { useMemo } from 'react';
|
|
10
|
+
import type { TestSummary } from '@crvouga/atlas/reports/test-catalog';
|
|
11
|
+
|
|
12
|
+
import { assessTest, type Assessment, type ReleaseIdentity } from '../evidence/applicability';
|
|
13
|
+
import type { ApplicabilityState } from '../evidence/applicability-view';
|
|
14
|
+
import { useExplorerNavigate, useExplorerSearch } from './navigate';
|
|
15
|
+
import type { ExplorerSearch } from './search';
|
|
16
|
+
|
|
17
|
+
/** The release an explorer address names; every part is optional. */
|
|
18
|
+
export function releaseOf(search: ExplorerSearch): ReleaseIdentity {
|
|
19
|
+
return {
|
|
20
|
+
...(search.release ? { build: search.release } : {}),
|
|
21
|
+
...(search.releaseSha ? { sha: search.releaseSha } : {}),
|
|
22
|
+
...(search.releasePlatform ? { platform: search.releasePlatform } : {})
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Each test's evidence read against the promise and the release, by test key. */
|
|
27
|
+
export function assessTests(tests: readonly TestSummary[], release: ReleaseIdentity): ReadonlyMap<string, Assessment> {
|
|
28
|
+
return new Map(tests.map((test) => [test.key, assessTest(test, release)] as const));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The release and the evidence filter of the explorer's address, with the ways to change them. */
|
|
32
|
+
export function useExplorerApplicability(): ApplicabilityState {
|
|
33
|
+
const search = useExplorerSearch();
|
|
34
|
+
const go = useExplorerNavigate();
|
|
35
|
+
const { release: build, releaseSha: sha, releasePlatform: platform } = search;
|
|
36
|
+
const release = useMemo(() => releaseOf({ release: build, releaseSha: sha, releasePlatform: platform }), [build, sha, platform]);
|
|
37
|
+
return {
|
|
38
|
+
release,
|
|
39
|
+
onlyStale: search.evidence === 'stale',
|
|
40
|
+
setRelease: (next) => go({ release: next.build || undefined, releaseSha: next.sha || undefined, releasePlatform: next.platform || undefined }, { replace: true }),
|
|
41
|
+
setOnlyStale: (on) => go({ evidence: on ? 'stale' : undefined }, { replace: true })
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @module Run and artifact filters
|
|
3
3
|
*
|
|
4
4
|
* The filter bar of the Runs and Artifacts tabs: words, artifact tag, media type, label, environment, branch, outcome,
|
|
5
|
-
* feature domain and a time range, each offering the values the report has with counts in the scope of the list. What is
|
|
5
|
+
* feature domain and a time range (and, for runs, the completeness of their recordings, apart from their outcome), each offering the values the report has with counts in the scope of the list. What is
|
|
6
6
|
* chosen shows as removable chips and is the address, so copying it or reloading restores the same view; named views keep
|
|
7
7
|
* a query to open again. Choices combine with AND across the controls and OR within one.
|
|
8
8
|
*/
|
|
@@ -18,7 +18,7 @@ import { patched, useExplorerNavigate } from './navigate';
|
|
|
18
18
|
import { filtered, joined, NO_FILTERS, toggled, values, type ExplorerSearch } from './search';
|
|
19
19
|
import { filtersOf, isView, readViews, writeViews, type SavedView } from './views';
|
|
20
20
|
|
|
21
|
-
type Param = 'artifactTag' | 'type' | 'label' | 'env' | 'branch' | 'outcome' | 'domain' | 'producer';
|
|
21
|
+
type Param = 'artifactTag' | 'type' | 'label' | 'env' | 'branch' | 'outcome' | 'recordings' | 'domain' | 'producer';
|
|
22
22
|
|
|
23
23
|
const NOUN: Record<Param, string> = {
|
|
24
24
|
artifactTag: 'Tag',
|
|
@@ -27,13 +27,18 @@ const NOUN: Record<Param, string> = {
|
|
|
27
27
|
env: 'Environment',
|
|
28
28
|
branch: 'Branch',
|
|
29
29
|
outcome: 'Outcome',
|
|
30
|
+
recordings: 'Recordings',
|
|
30
31
|
domain: 'Domain',
|
|
31
32
|
producer: 'Producer'
|
|
32
33
|
};
|
|
33
34
|
|
|
35
|
+
/** How the completeness of a run's recordings reads. */
|
|
36
|
+
const COMPLETENESS: Record<string, string> = { complete: 'Complete', incomplete: 'Incomplete', 'none-expected': 'None expected', unknown: 'Unknown' };
|
|
37
|
+
|
|
34
38
|
/** How a chosen value reads: tags with their hash, environments by name. */
|
|
35
39
|
function shown(param: Param, value: string): string {
|
|
36
40
|
if (param === 'artifactTag') return `#${value}`;
|
|
41
|
+
if (param === 'recordings') return COMPLETENESS[value] ?? value;
|
|
37
42
|
if (param === 'env') return environmentLabel(value);
|
|
38
43
|
if (param === 'domain' && value === '(no spec)') return 'No spec';
|
|
39
44
|
return value;
|
|
@@ -178,7 +183,7 @@ export function FilterBar({
|
|
|
178
183
|
const [draft, setDraft] = useState(search.q ?? '');
|
|
179
184
|
// The address may change from elsewhere (a link, a saved view, back): follow it.
|
|
180
185
|
useEffect(() => setDraft((current) => (current.trim() === (search.q ?? '').trim() ? current : (search.q ?? ''))), [search.q]);
|
|
181
|
-
const params: Param[] = ['artifactTag', 'type', 'label', 'env', 'branch', 'outcome', ...(tab === 'artifacts' ? (['domain'] as const) : []), 'producer'];
|
|
186
|
+
const params: Param[] = ['artifactTag', 'type', 'label', 'env', 'branch', 'outcome', ...(tab === 'runs' ? (['recordings'] as const) : []), ...(tab === 'artifacts' ? (['domain'] as const) : []), 'producer'];
|
|
182
187
|
const chips = [
|
|
183
188
|
...params.flatMap((param) =>
|
|
184
189
|
values(search[param]).map((value) => ({ label: `${NOUN[param]}: ${shown(param, value)}`, clear: { [param]: toggled(search[param], value) } as never }))
|
|
@@ -233,6 +238,9 @@ export function FilterBar({
|
|
|
233
238
|
<Facet param="env" facets={facets.environments} search={search} />
|
|
234
239
|
<Facet param="branch" facets={facets.branches} search={search} />
|
|
235
240
|
<Facet param="outcome" facets={facets.outcomes} search={search} />
|
|
241
|
+
{tab === 'runs' && (facets.recordings || values(search.recordings).length > 0) && (
|
|
242
|
+
<Facet param="recordings" facets={facets.recordings ?? []} search={search} />
|
|
243
|
+
)}
|
|
236
244
|
{tab === 'artifacts' && facets.domains && <Facet param="domain" facets={facets.domains} search={search} />}
|
|
237
245
|
{facets.producers.length > 1 && <Facet param="producer" facets={facets.producers} search={search} />}
|
|
238
246
|
<TextInput type="date" label="From" value={search.since ?? ''} onChange={(event) => go({ since: event.target.value || undefined })} />
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* or `\\`, and the router encodes `#` and spaces. Anything unreadable is dropped, never an error.
|
|
8
8
|
*
|
|
9
9
|
* The Runs and Artifacts tabs share the tests' `q`, `env`, `producer`, `domain` and `spec` and add `artifactTag`,
|
|
10
|
-
* `type`, `label`, `branch`, `outcome`, `since` and `until`, so a link to one tab's filters opens the same query on another.
|
|
10
|
+
* `type`, `label`, `branch`, `outcome`, `since` and `until` (and the Runs tab alone `recordings`, the completeness of its recordings), so a link to one tab's filters opens the same query on another.
|
|
11
11
|
*/
|
|
12
12
|
import { joinList, splitList, type ArtifactFilter } from '@crvouga/atlas/reports/artifact-query';
|
|
13
13
|
import type { ReliabilityScope } from '@crvouga/atlas/reports/reliability';
|
|
@@ -19,8 +19,8 @@ export const TEST_GROUPS = ['spec', 'domain', 'environment', 'status', 'producer
|
|
|
19
19
|
/** One of {@link TEST_GROUPS}. */
|
|
20
20
|
export type TestGroup = (typeof TEST_GROUPS)[number];
|
|
21
21
|
|
|
22
|
-
/** The orders the explorer offers: `relevance` (best match first, only with words to match) and those of the catalog. */
|
|
23
|
-
export const EXPLORER_SORTS = ['relevance', ...TEST_SORTS] as const;
|
|
22
|
+
/** The orders the explorer offers: `relevance` (best match first, only with words to match), `evidence` (tests whose evidence is stale or unverified first, worked out in the viewer) and those of the catalog. */
|
|
23
|
+
export const EXPLORER_SORTS = ['relevance', 'evidence', ...TEST_SORTS] as const;
|
|
24
24
|
/** One of {@link EXPLORER_SORTS}. */
|
|
25
25
|
export type ExplorerSort = (typeof EXPLORER_SORTS)[number];
|
|
26
26
|
|
|
@@ -55,12 +55,20 @@ export const ExplorerSearchFields = {
|
|
|
55
55
|
branch: text,
|
|
56
56
|
/** Outcomes of a run or recorded result (`passed`, `failed`…). */
|
|
57
57
|
outcome: text,
|
|
58
|
+
/** Recording completeness of a run (`complete`, `incomplete`, `none-expected`, `unknown`): runs only, apart from the outcome. */
|
|
59
|
+
recordings: text,
|
|
58
60
|
/** Only records from this day (`YYYY-MM-DD`, the viewer's local time). */
|
|
59
61
|
since: text,
|
|
60
62
|
/** Only records until the end of this day (`YYYY-MM-DD`, local time). */
|
|
61
63
|
until: text,
|
|
62
64
|
/** Only the tests a CI run recorded (a `RunSummary.key`). Not `run`, which already names a recorded statechart run. */
|
|
63
65
|
inRun: text,
|
|
66
|
+
/** `stale`: only tests whose evidence is stale or unverified (not that applies to the current promise and, when one is named, the release). Never a test status. */
|
|
67
|
+
evidence: z.enum(['stale']).optional().catch(undefined),
|
|
68
|
+
/** The release evidence is read against: its build, commit and platform (the names a spec page uses). A build such as `90` is read from a typed link as a number: coerced back to its text. */
|
|
69
|
+
release: text,
|
|
70
|
+
releaseSha: text,
|
|
71
|
+
releasePlatform: text,
|
|
64
72
|
group: z.enum(TEST_GROUPS).optional().catch(undefined),
|
|
65
73
|
sort: z.enum(EXPLORER_SORTS).optional().catch(undefined),
|
|
66
74
|
tab: z.enum(['runs', 'artifacts', 'collections', 'compare', 'health']).optional().catch(undefined),
|
|
@@ -124,9 +132,14 @@ export type ExplorerSearch = {
|
|
|
124
132
|
readonly label?: string;
|
|
125
133
|
readonly branch?: string;
|
|
126
134
|
readonly outcome?: string;
|
|
135
|
+
readonly recordings?: string;
|
|
127
136
|
readonly since?: string;
|
|
128
137
|
readonly until?: string;
|
|
129
138
|
readonly inRun?: string;
|
|
139
|
+
readonly evidence?: 'stale';
|
|
140
|
+
readonly release?: string;
|
|
141
|
+
readonly releaseSha?: string;
|
|
142
|
+
readonly releasePlatform?: string;
|
|
130
143
|
readonly group?: TestGroup;
|
|
131
144
|
readonly sort?: ExplorerSort;
|
|
132
145
|
readonly tab?: 'runs' | 'artifacts' | 'collections' | 'compare' | 'health';
|
|
@@ -189,7 +202,7 @@ export function queryOf(search: ExplorerSearch): TestQuery {
|
|
|
189
202
|
...(values(search.scope).length ? { scope: values(search.scope) } : {}),
|
|
190
203
|
artifactTag: values(search.artifactTag),
|
|
191
204
|
...(search.inherited ? { inherited: true } : {}),
|
|
192
|
-
sort: search.sort === 'relevance' || !search.sort ? 'spec' : search.sort
|
|
205
|
+
sort: search.sort === 'relevance' || search.sort === 'evidence' || !search.sort ? 'spec' : search.sort
|
|
193
206
|
};
|
|
194
207
|
}
|
|
195
208
|
|
|
@@ -200,7 +213,7 @@ export function explorerSearch(search: Record<string, unknown>): ExplorerSearch
|
|
|
200
213
|
|
|
201
214
|
/** True when any filter or search narrows the list. */
|
|
202
215
|
export function narrowed(search: ExplorerSearch): boolean {
|
|
203
|
-
return !!(search.q || search.status || search.env || search.producer || search.domain || search.spec || search.tag || search.scope || search.artifactTag || search.inRun || search.video);
|
|
216
|
+
return !!(search.q || search.status || search.env || search.producer || search.domain || search.spec || search.tag || search.scope || search.artifactTag || search.inRun || search.video || search.evidence);
|
|
204
217
|
}
|
|
205
218
|
|
|
206
219
|
/** The reliability scope an address asks for, in the shape `reliability` reads: days are the viewer's local days. */
|
|
@@ -219,7 +232,7 @@ export function reliabilityScopeOf(search: ExplorerSearch): ReliabilityScope {
|
|
|
219
232
|
}
|
|
220
233
|
|
|
221
234
|
/** The filters of the Runs and Artifacts tabs, in the address. */
|
|
222
|
-
export const FILTER_FIELDS = ['q', 'artifactTag', 'inherited', 'type', 'label', 'env', 'producer', 'branch', 'outcome', 'domain', 'spec', 'since', 'until', 'video'] as const;
|
|
235
|
+
export const FILTER_FIELDS = ['q', 'artifactTag', 'inherited', 'type', 'label', 'env', 'producer', 'branch', 'outcome', 'recordings', 'domain', 'spec', 'since', 'until', 'video'] as const;
|
|
223
236
|
|
|
224
237
|
/** A patch that removes every filter of the Runs and Artifacts tabs. */
|
|
225
238
|
export const NO_FILTERS = Object.fromEntries(FILTER_FIELDS.map((field) => [field, undefined])) as { [K in (typeof FILTER_FIELDS)[number]]?: undefined };
|
|
@@ -253,6 +266,7 @@ export function filterOf(search: ExplorerSearch): ArtifactFilter {
|
|
|
253
266
|
producer: list(search.producer),
|
|
254
267
|
branch: list(search.branch),
|
|
255
268
|
outcome: list(search.outcome),
|
|
269
|
+
recordings: list(search.recordings),
|
|
256
270
|
domain: list(search.domain),
|
|
257
271
|
spec: list(search.spec),
|
|
258
272
|
since,
|