@crvouga/atlas 0.13.0 → 0.15.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/dist/playwright-reporter/src/index.d.ts +11 -0
- package/dist/playwright-reporter/src/index.js +4 -2
- package/dist/reports/src/artifact-tag-store.d.ts +20 -0
- package/dist/reports/src/artifact-tag-store.js +70 -0
- package/dist/reports/src/artifact-tags.d.ts +96 -0
- package/dist/reports/src/artifact-tags.js +96 -0
- package/dist/reports/src/evidence-detail.d.ts +1 -1
- package/dist/reports/src/evidence-detail.js +6 -2
- package/dist/reports/src/evidence-types.d.ts +5 -0
- package/dist/reports/src/layer-merge.d.ts +1 -1
- package/dist/reports/src/layer-merge.js +3 -2
- package/dist/reports/src/layer-types.d.ts +3 -0
- package/dist/reports/src/publish-checks.js +1 -0
- package/dist/reports/src/report-cli.d.ts +1 -1
- package/dist/reports/src/report-cli.js +30 -2
- package/dist/reports/src/reporter.d.ts +1 -1
- package/dist/reports/src/reports-explorer-route.d.ts +8 -0
- package/dist/reports/src/reports-route.d.ts +8 -0
- package/dist/reports/src/test-catalog.d.ts +31 -27
- package/dist/reports/src/test-catalog.js +73 -12
- package/dist/schema/src/api.d.ts +128 -1
- package/dist/schema/src/api.js +38 -4
- package/dist/schema/src/layer.d.ts +59 -0
- package/dist/schema/src/layer.js +28 -1
- package/dist/server/src/app.js +32 -1
- package/dist/server/src/openapi.d.ts +1 -1
- package/dist/server/src/openapi.js +18 -2
- package/docs/extending.md +3 -0
- package/docs/integrating.md +8 -0
- package/docs/reference/cli.md +7 -0
- package/docs/reference/http-api.md +48 -0
- package/docs/reference/playwright-reporter.md +6 -6
- package/docs/reference/reports.md +235 -8
- package/docs/reference/schema.md +52 -1
- package/docs/report-contract.md +1 -0
- package/docs/rich-evidence.md +28 -0
- package/docs/server.md +24 -0
- package/llms-full.txt +119 -0
- package/package.json +1 -1
- package/packages/schema/schemas/check-detail.schema.json +28 -0
- package/packages/schema/schemas/effective-delta.schema.json +44 -0
- package/packages/schema/schemas/effective-evidence.schema.json +44 -0
- package/packages/schema/schemas/evidence-page.schema.json +37 -0
- package/packages/schema/schemas/layer-rows.schema.json +7 -0
- package/packages/schema/schemas/layer.schema.json +100 -0
- package/packages/schema/schemas/publish-checks.schema.json +65 -0
- package/packages/schema/schemas/report-run.schema.json +17 -1
- package/packages/schema/schemas/report-runs.schema.json +17 -1
- package/packages/schema/schemas/test-catalog.schema.json +42 -0
- package/packages/schema/schemas/test-history.schema.json +28 -0
- package/packages/server/openapi.json +886 -3
- package/packages/visualizer/src/evidence/artifact-tags.tsx +285 -0
- package/packages/visualizer/src/evidence/artifacts.tsx +18 -12
- package/packages/visualizer/src/explorer/RunsView.tsx +23 -4
- package/packages/visualizer/src/explorer/parts.tsx +2 -1
|
@@ -281,6 +281,12 @@ export type AtlasPlaywrightReporterOptions = {
|
|
|
281
281
|
readonly branch?: string;
|
|
282
282
|
/** Where to read the CI run. Default the GitHub Actions run (or the CI's own run URL variable). */
|
|
283
283
|
readonly runUrl?: string;
|
|
284
|
+
/**
|
|
285
|
+
* Names this execution, for a local or manual run with no CI run URL: every shard and project that gets the same `id`
|
|
286
|
+
* is one run, and the next invocation gets another. Default: `ATLAS_RUN_ID` (and `ATLAS_RUN_TITLE`) when set. Only
|
|
287
|
+
* groups and labels runs; it never changes what a check is or which result wins.
|
|
288
|
+
*/
|
|
289
|
+
readonly run?: { readonly id: string; readonly title?: string; readonly startedAtMs?: number; readonly endedAtMs?: number };
|
|
284
290
|
/** The spec a test belongs to, relative to the repository. Default: the `.feature` of a playwright-bdd test, else the test file. */
|
|
285
291
|
readonly specPath?: (test: TestCase) => string | undefined;
|
|
286
292
|
/** Paths are relative to this directory. Default the git root above Playwright's `rootDir`, else `rootDir`. */
|
|
@@ -290,12 +296,6 @@ export type AtlasPlaywrightReporterOptions = {
|
|
|
290
296
|
/** A larger file is not uploaded; the check says so in an annotation. Default 50 MiB. */
|
|
291
297
|
readonly maxAttachmentBytes?: number;
|
|
292
298
|
/** The most timeline steps recorded per result. Default 500. */
|
|
293
|
-
readonly maxSteps?: number;
|
|
294
|
-
/** Record the project's spec files (path, domain, kind, title) in every layer, so a server can group checks by spec without a published spec set. Default true. */
|
|
295
|
-
readonly specs?: boolean;
|
|
296
|
-
/** Send what has finished this often, in ms. Default 5000 (every layer repeats the spec list). */
|
|
297
|
-
readonly flushMs?: number;
|
|
298
|
-
/** Send at once when this many checks are waiting. Default 50 (their attachments are held in memory until sent). */
|
|
299
299
|
…
|
|
300
300
|
```
|
|
301
301
|
|
|
@@ -4,6 +4,209 @@ Layered evidence for any kind of spec: publishing layers, stores (folder, S3, an
|
|
|
4
4
|
|
|
5
5
|
> Generated from the doc comments of `packages/reports/src` by `bun run docs`. Do not edit: change the comment next to the code.
|
|
6
6
|
|
|
7
|
+
### Reviewer tags in a store
|
|
8
|
+
|
|
9
|
+
Source: `packages/reports/src/artifact-tag-store.ts`. Import: `@crvouga/atlas/reports/artifact-tag-store`
|
|
10
|
+
|
|
11
|
+
Where a reviewer's tags for attachments are kept: one small immutable object per edit under
|
|
12
|
+
`curation/artifact-tags/`, named by time and a random id so editors never overwrite one another, and never changed
|
|
13
|
+
or deleted afterwards. The layers they are about are not touched. The list of edits is the audit trail: who changed
|
|
14
|
+
which tags of which attachment, and when. `foldArtifactTags` turns it into the tags that stand now.
|
|
15
|
+
|
|
16
|
+
See docs/server.md#tagging-artifacts
|
|
17
|
+
|
|
18
|
+
#### `ARTIFACT_TAGS_PREFIX`
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
export const ARTIFACT_TAGS_PREFIX = …
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The prefix of every recorded edit in a store.
|
|
25
|
+
|
|
26
|
+
#### `ArtifactTagError`
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
export class ArtifactTagError extends Error
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The edit was not acceptable (`status` is the HTTP status to answer).
|
|
33
|
+
|
|
34
|
+
#### `recordArtifactTags`
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
export async function recordArtifactTags(store: ObjectStore, input: unknown, by: string, atMs: number): Promise<ArtifactTagEntry>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Validates an edit (`ArtifactTagEditSchema`), normalizes its tags and records it as a new object in `store`, by
|
|
41
|
+
`by` at `atMs`. Throws {@link ArtifactTagError} for a body that is not an edit, a tag that is not a tag (empty, or
|
|
42
|
+
over 64 characters) or an edit that changes nothing. Recording the same edit twice records two entries.
|
|
43
|
+
|
|
44
|
+
#### `readArtifactTagEntries`
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
export async function readArtifactTagEntries(store: ObjectStore): Promise<ArtifactTagEntry[]>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Every recorded edit in `store`, oldest first. An object that cannot be read as an edit is skipped (unknown, never
|
|
51
|
+
guessed at). A store with none gives an empty list. Transport errors throw.
|
|
52
|
+
|
|
53
|
+
### Artifact tags
|
|
54
|
+
|
|
55
|
+
Source: `packages/reports/src/artifact-tags.ts`. Import: `@crvouga/atlas/reports/artifact-tags`
|
|
56
|
+
|
|
57
|
+
The tags of one attachment (a recording, a screenshot, a trace): how a tag is normalized so `demo` and `#demo`
|
|
58
|
+
are the same tag, which tags an attachment shows (its producer's, its reviewers', and those it inherits from its
|
|
59
|
+
check and run), and how a reviewer's edits fold into the tags that stand now. Pure functions, safe in a browser.
|
|
60
|
+
|
|
61
|
+
Tags describe evidence; they never decide a status, a slot or a winner. A producer writes `tags` on the attachment
|
|
62
|
+
inside an immutable layer. A reviewer's tags live apart, as an append-only list of edits that the server records
|
|
63
|
+
with who made each and when (`artifact-tag-store.ts`); the layer is never rewritten.
|
|
64
|
+
|
|
65
|
+
See docs/rich-evidence.md#tagging-artifacts-and-demos
|
|
66
|
+
|
|
67
|
+
#### `MAX_TAG_LENGTH`
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
export const MAX_TAG_LENGTH = …
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The longest a normalized tag may be, in characters.
|
|
74
|
+
|
|
75
|
+
#### `normalizeTag`
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
export function normalizeTag(raw: string): string | null
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Normalizes a tag so equal tags compare equal: Unicode NFC, any leading `#` removed, surrounding whitespace
|
|
82
|
+
trimmed, letters lower-cased and each run of whitespace inside turned into one `-`. Every other character is kept,
|
|
83
|
+
so `Smoke`, `#smoke` and ` smoke ` are `smoke`, and `team/payments` or `release=2` stay as they are. Returns
|
|
84
|
+
`null` for a tag that is empty after that (`''`, `'#'`, `' '`) or longer than {@link MAX_TAG_LENGTH}.
|
|
85
|
+
|
|
86
|
+
**Example**
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
normalizeTag('#Demo'); // 'demo'
|
|
90
|
+
normalizeTag('release candidate'); // 'release-candidate'
|
|
91
|
+
normalizeTag('#'); // null
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### `normalizeTags`
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
export function normalizeTags(raw: Iterable<string> | undefined): string[]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Normalizes every tag with {@link normalizeTag}, dropping the ones that are not tags and the repeats; the first of each keeps its place.
|
|
101
|
+
|
|
102
|
+
#### `displayTag`
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
export const displayTag = …
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The tag as a person sees it: `#demo`.
|
|
109
|
+
|
|
110
|
+
#### `artifactRef`
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
export function artifactRef(attachment: Pick<Attachment, 'id' | 'bundleKey' | 'name'>): string
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
How an edit names an attachment: `id:<id>` when it has an `id`, else `key:<bundleKey>` (its bytes), else
|
|
117
|
+
`name:<name>`. It is stable for a layer, which never changes. Two attachments in one check with the same bytes and
|
|
118
|
+
no `id` share a reference, and so share reviewer tags; give them an `id` to tag them apart.
|
|
119
|
+
|
|
120
|
+
#### `ArtifactTarget`
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
export type ArtifactTarget = {
|
|
124
|
+
readonly layer: string;
|
|
125
|
+
readonly check: string;
|
|
126
|
+
readonly kind?: string | undefined;
|
|
127
|
+
readonly artifact: string;
|
|
128
|
+
};
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Where a reviewer's tags are about: one attachment of one check of one archived layer.
|
|
132
|
+
|
|
133
|
+
#### `ArtifactTagEntry`
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
export type ArtifactTagEntry = {
|
|
137
|
+
readonly id: string;
|
|
138
|
+
readonly target: ArtifactTarget;
|
|
139
|
+
readonly add: readonly string[];
|
|
140
|
+
readonly remove: readonly string[];
|
|
141
|
+
readonly by: string;
|
|
142
|
+
readonly atMs: number;
|
|
143
|
+
};
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
One recorded edit. Entries are never changed or removed.
|
|
147
|
+
|
|
148
|
+
#### `targetKey`
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
export const targetKey = …
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
A string that is equal for equal targets (the part `kind` is optional and then does not matter).
|
|
155
|
+
|
|
156
|
+
#### `ReviewerTag`
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
export type ReviewerTag = { readonly tag: string; readonly by: string; readonly atMs: number };
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
A tag a reviewer added: who added it last, and when.
|
|
163
|
+
|
|
164
|
+
#### `foldArtifactTags`
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
export function foldArtifactTags(entries: readonly ArtifactTagEntry[]): Map<string, ReviewerTag[]>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Folds the recorded edits (in any order) into the reviewer tags that stand now, by target (see {@link targetKey}).
|
|
171
|
+
Edits apply oldest first, ties by `id`; a tag added, removed and added again stands, with the time and author of
|
|
172
|
+
the last add. A target whose tags were all removed is absent. Entries that are not understood are ignored.
|
|
173
|
+
|
|
174
|
+
#### `TagSource`
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
export type TagSource = 'artifact' | 'reviewer' | 'inherited';
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Where a shown tag comes from: the attachment's own producer (`artifact`), a reviewer after the upload (`reviewer`), or the check or run around it (`inherited`).
|
|
181
|
+
|
|
182
|
+
#### `ArtifactTag`
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
export type ArtifactTag = { readonly tag: string; readonly source: TagSource; readonly by?: string; readonly atMs?: number };
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
One tag an attachment shows, with its source and, for a reviewer's tag, who added it and when.
|
|
189
|
+
|
|
190
|
+
#### `artifactTagsOf`
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
export function artifactTagsOf(attachment: Pick<Attachment, 'tags'>, options: { readonly reviewer?: readonly ReviewerTag[]; readonly inherited?: Iterable<string> } = {}): ArtifactTag[]
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The tags one attachment shows, normalized and without repeats: its producer's own (`artifact`), then the
|
|
197
|
+
reviewers' (`reviewer`), then those of its check and run that it does not have itself (`inherited`). A tag it has
|
|
198
|
+
more than once is shown once, by the first source above.
|
|
199
|
+
|
|
200
|
+
#### `hasArtifactTag`
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
export function hasArtifactTag(tags: readonly ArtifactTag[], wanted: string, options: { readonly inherited?: boolean } = {}): boolean
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Does an attachment match a tag (either spelling)? An exact query (`inherited: false`, the default) matches only the
|
|
207
|
+
tags the attachment has itself, from its producer or a reviewer; an inherited query also matches the tags it
|
|
208
|
+
inherits from its check and run. The two differ for a recording that is untagged under a tagged run.
|
|
209
|
+
|
|
7
210
|
### Reading the files attachments name
|
|
8
211
|
|
|
9
212
|
Source: `packages/reports/src/attachment-files.ts`. Import: `@crvouga/atlas/reports/attachment-files`
|
|
@@ -1222,7 +1425,7 @@ read when someone asks for it. Undefined when there is none.
|
|
|
1222
1425
|
|
|
1223
1426
|
Several holders (for example a check and its layer) are summed: `attachments` also counts those on steps and
|
|
1224
1427
|
attempts, `steps` counts only each holder's own top-level steps, tags are de-duplicated, and for a repeated
|
|
1225
|
-
label the later holder wins. `roles` are the attachments' roles (or, without one, what their media type or file
|
|
1428
|
+
label the later holder wins. `artifactTags` are the attachments' own normalized tags and are never inherited. `roles` are the attachments' roles (or, without one, what their media type or file
|
|
1226
1429
|
name implies), sorted and distinct.
|
|
1227
1430
|
|
|
1228
1431
|
#### `roleOfType`
|
|
@@ -1418,6 +1621,8 @@ export type EvidenceSummary = {
|
|
|
1418
1621
|
readonly attempts: number;
|
|
1419
1622
|
readonly links: number;
|
|
1420
1623
|
readonly tags: readonly string[];
|
|
1624
|
+
/** The normalized tags of the attachments themselves (`#demo` is `demo`), sorted and distinct. Absent when no attachment is tagged. Unlike `tags` they are never inherited from a check or run. */
|
|
1625
|
+
readonly artifactTags?: readonly string[];
|
|
1421
1626
|
readonly labels: Readonly<Record<string, string>>;
|
|
1422
1627
|
/** The distinct roles of its attachments (or what their media types say), to show what kind of evidence there is. */
|
|
1423
1628
|
readonly roles: readonly string[];
|
|
@@ -1454,6 +1659,8 @@ export type LayerHeader = {
|
|
|
1454
1659
|
readonly parameters: LayerParameters;
|
|
1455
1660
|
/** `modified`: recorded from a checkout with uncommitted changes; `unknown`: origin not captured; absent: clean. Advisory; never changes a status. */
|
|
1456
1661
|
readonly freshness?: 'unknown' | 'modified';
|
|
1662
|
+
/** The execution the producer named for it (one local or manual invocation), or absent. Grouping and display only; never part of the slot. */
|
|
1663
|
+
readonly run?: LayerRun;
|
|
1457
1664
|
/** Where this layer's recorded timelines and captured files are read from, or null when it has none. */
|
|
1458
1665
|
readonly recording: RecordingAddress | null;
|
|
1459
1666
|
/** From a spec run that has not been collected into a layer yet. */
|
|
@@ -3560,7 +3767,7 @@ Drops each mutable layer whose execution is already archived as an immutable one
|
|
|
3560
3767
|
export function layerExecutionKey(layer: LayerSummary): string
|
|
3561
3768
|
```
|
|
3562
3769
|
|
|
3563
|
-
A string equal for the mutable and the archived address of one execution: its producer, sha, time and
|
|
3770
|
+
A string equal for the mutable and the archived address of one execution: its producer, sha, time, parts and, when the producer named one, its execution id.
|
|
3564
3771
|
Object key order is ignored at every depth, including check evidence and extensions; array order is preserved.
|
|
3565
3772
|
This is an advisory execution identity, not the content digest that addresses an immutable layer in storage.
|
|
3566
3773
|
|
|
@@ -3725,6 +3932,8 @@ export type LayerSummary = LayerExtras & {
|
|
|
3725
3932
|
readonly environment?: string | null;
|
|
3726
3933
|
/** Provenance: the CI run that produced the layer, or `null`. Never part of the slot. */
|
|
3727
3934
|
readonly runUrl: string | null;
|
|
3935
|
+
/** The execution (one invocation of a local or manual run) the layer belongs to, when the producer named one. Grouping and display only; never part of the slot. Absent: the run is told by `runUrl`, else `branch@sha`. */
|
|
3936
|
+
readonly run?: LayerRun;
|
|
3728
3937
|
/** Free choices that make a result its own slot. The `environment` parameter is read as the environment (see `layerPlace`). */
|
|
3729
3938
|
readonly parameters: LayerParameters;
|
|
3730
3939
|
/** The specs the layer was recorded against, with fingerprints of their text. */
|
|
@@ -5935,7 +6144,7 @@ See docs/server.md#streaming-a-run-as-it-happens
|
|
|
5935
6144
|
#### `ReporterIdentity`
|
|
5936
6145
|
|
|
5937
6146
|
```ts
|
|
5938
|
-
export type ReporterIdentity = Pick<PublishChecksInput, 'producer' | 'kind' | 'title' | 'sha' | 'branch' | 'runUrl' | 'environment' | 'parameters' | 'freshness' | 'specs' | 'tags' | 'labels' | 'links' | 'attachments' | 'annotations' | 'extensions'>;
|
|
6147
|
+
export type ReporterIdentity = Pick<PublishChecksInput, 'producer' | 'kind' | 'title' | 'sha' | 'branch' | 'runUrl' | 'run' | 'environment' | 'parameters' | 'freshness' | 'specs' | 'tags' | 'labels' | 'links' | 'attachments' | 'annotations' | 'extensions'>;
|
|
5939
6148
|
```
|
|
5940
6149
|
|
|
5941
6150
|
What every layer of one stream shares: who is reporting, from where. Whatever `publishChecks` takes, except the checks and the time.
|
|
@@ -7510,6 +7719,10 @@ export type TestSummary = {
|
|
|
7510
7719
|
readonly outcomes: readonly TestOutcome[];
|
|
7511
7720
|
readonly stats: TestStats;
|
|
7512
7721
|
readonly tags: readonly string[];
|
|
7722
|
+
/** The normalized tags of the attachments of its current results (`demo`, shown as `#demo`). Never inherited from a check or run; the `tags` above are. Absent in documents of older servers. */
|
|
7723
|
+
readonly artifactTags?: readonly string[];
|
|
7724
|
+
/** The normalized tags its attachments inherit from their check and run (those of the results that have attachments), apart from `artifactTags`. What an inherited artifact query also matches. */
|
|
7725
|
+
readonly inheritedArtifactTags?: readonly string[];
|
|
7513
7726
|
readonly labels: Readonly<Record<string, string>>;
|
|
7514
7727
|
/** What its current results carry: attachments, steps, retries, and the kinds of attachment. */
|
|
7515
7728
|
readonly evidence: { readonly attachments: number; readonly steps: number; readonly attempts: number; readonly roles: readonly string[] };
|
|
@@ -7600,6 +7813,8 @@ export type CatalogFacets = {
|
|
|
7600
7813
|
readonly domains: readonly FacetValue[];
|
|
7601
7814
|
readonly specs: readonly (CatalogSpec & { readonly tests: number; readonly counts: StatusCounts })[];
|
|
7602
7815
|
readonly tags: readonly FacetValue[];
|
|
7816
|
+
/** The tags of attachments, normalized. Absent in documents of older servers. */
|
|
7817
|
+
readonly artifactTags?: readonly FacetValue[];
|
|
7603
7818
|
readonly branches: readonly FacetValue[];
|
|
7604
7819
|
readonly labels: Readonly<Record<string, readonly FacetValue[]>>;
|
|
7605
7820
|
};
|
|
@@ -7637,6 +7852,10 @@ export type TestQuery = {
|
|
|
7637
7852
|
/** Spec paths (`specs/...`). */
|
|
7638
7853
|
readonly spec?: readonly string[];
|
|
7639
7854
|
readonly tag?: readonly string[];
|
|
7855
|
+
/** Only tests with an attachment carrying this tag (`demo` and `#demo` are the same), not counting tags inherited from a check or run: the exact artifact query. `tag` is the inherited one. */
|
|
7856
|
+
readonly artifactTag?: readonly string[];
|
|
7857
|
+
/** With `artifactTag`: also match the tags an attachment inherits from its check and run (a recording without a tag of its own under a run tagged `demo`). Default false: the exact query. */
|
|
7858
|
+
readonly inherited?: boolean;
|
|
7640
7859
|
/** Only tests recorded in this run (a key from `runList`). */
|
|
7641
7860
|
readonly run?: string;
|
|
7642
7861
|
readonly sort?: TestSort;
|
|
@@ -7685,8 +7904,14 @@ export type TestHistoryDocument = {
|
|
|
7685
7904
|
|
|
7686
7905
|
```ts
|
|
7687
7906
|
export type RunSummary = {
|
|
7688
|
-
/** Stable key: the run URL,
|
|
7907
|
+
/** Stable key: `run:<id>` for an execution a producer named, else the run URL, else `branch@sha`. */
|
|
7689
7908
|
readonly key: string;
|
|
7909
|
+
/** The execution id a producer named for it, when it did. Layers that share it are one run, however many uploads they took; another invocation at the same commit has another. */
|
|
7910
|
+
readonly runId?: string;
|
|
7911
|
+
/** The human title the producer gave the execution (the newest layer's). Presentation only: the key, the commit and `freshness` are unaffected. */
|
|
7912
|
+
readonly title?: string;
|
|
7913
|
+
/** `modified` when any of its layers was recorded from a checkout with uncommitted changes, else `unknown` when any origin was not captured; absent when every layer is from a clean commit. Advisory; never changes a status. */
|
|
7914
|
+
readonly freshness?: 'unknown' | 'modified';
|
|
7690
7915
|
readonly runUrl: string | null;
|
|
7691
7916
|
readonly branch: string | null;
|
|
7692
7917
|
readonly sha: string | null;
|
|
@@ -7708,7 +7933,8 @@ export type RunSummary = {
|
|
|
7708
7933
|
};
|
|
7709
7934
|
```
|
|
7710
7935
|
|
|
7711
|
-
A
|
|
7936
|
+
A run: the layers that name the same execution (`run.id`), else share a CI run URL, else a branch and commit. When,
|
|
7937
|
+
where, and how its tests went.
|
|
7712
7938
|
|
|
7713
7939
|
#### `RunList`
|
|
7714
7940
|
|
|
@@ -7759,10 +7985,11 @@ export type RunDetail = {
|
|
|
7759
7985
|
#### `runKey`
|
|
7760
7986
|
|
|
7761
7987
|
```ts
|
|
7762
|
-
export function runKey(layer: Pick<LayerHeader, 'runUrl' | 'branch' | 'sha'>): string
|
|
7988
|
+
export function runKey(layer: Pick<LayerHeader, 'runUrl' | 'branch' | 'sha'> & { readonly run?: { readonly id: string } }): string
|
|
7763
7989
|
```
|
|
7764
7990
|
|
|
7765
|
-
The run a layer belongs to: its run URL, else `branch@sha`, else
|
|
7991
|
+
The run a layer belongs to: the execution its producer named (`run:<id>`), else its run URL, else `branch@sha`, else
|
|
7992
|
+
`local`. Layers with the same key are one run. Only grouping: it is never part of the slot a result fills.
|
|
7766
7993
|
|
|
7767
7994
|
#### `TestHistory`
|
|
7768
7995
|
|
|
@@ -7909,7 +8136,7 @@ One run with every result it recorded, or null when no run has that key.
|
|
|
7909
8136
|
export function parseTestQuery(params: URLSearchParams): TestQuery
|
|
7910
8137
|
```
|
|
7911
8138
|
|
|
7912
|
-
Reads a `TestQuery` from URL search parameters: `q`, `sort`, `offset`, `limit`, `run`, and repeatable or comma-separated `status`, `environment`, `producer`, `domain`, `spec`, `tag`.
|
|
8139
|
+
Reads a `TestQuery` from URL search parameters: `q`, `sort`, `offset`, `limit`, `run`, and repeatable or comma-separated `status`, `environment`, `producer`, `domain`, `spec`, `tag`, `artifactTag`, and `inherited=1`.
|
|
7913
8140
|
|
|
7914
8141
|
#### `formatMs`
|
|
7915
8142
|
|
package/docs/reference/schema.md
CHANGED
|
@@ -185,6 +185,38 @@ export const ChecksResultSchema = …
|
|
|
185
185
|
|
|
186
186
|
`POST /api/checks` answered: the layer that was written.
|
|
187
187
|
|
|
188
|
+
#### `ArtifactTargetSchema`
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
export const ArtifactTargetSchema = …
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Which attachment a reviewer's tags belong to: one attachment of one check of one archived layer.
|
|
195
|
+
|
|
196
|
+
#### `ArtifactTagEditSchema`
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
export const ArtifactTagEditSchema = …
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The body of `POST /api/reports/artifact-tags`: tags to add to and to remove from one attachment. At least one tag overall.
|
|
203
|
+
|
|
204
|
+
#### `ArtifactTagEntrySchema`
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
export const ArtifactTagEntrySchema = …
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
One recorded edit of an attachment's reviewer tags. Entries are never changed or removed: they are the audit trail.
|
|
211
|
+
|
|
212
|
+
#### `ArtifactTagsSchema`
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
export const ArtifactTagsSchema = …
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`GET /api/reports/artifact-tags`: every recorded edit, oldest first. Fold them with `foldArtifactTags`.
|
|
219
|
+
|
|
188
220
|
#### `HelloEventSchema`
|
|
189
221
|
|
|
190
222
|
```ts
|
|
@@ -258,7 +290,7 @@ export const TestHistorySchema = …
|
|
|
258
290
|
export const ReportRunSchema = …
|
|
259
291
|
```
|
|
260
292
|
|
|
261
|
-
A run: the layers that share a run URL
|
|
293
|
+
A run: the layers that name the same execution (else share a run URL, else a branch and commit), with what they recorded.
|
|
262
294
|
|
|
263
295
|
#### `RunListSchema`
|
|
264
296
|
|
|
@@ -643,6 +675,25 @@ Why a layer's origin is advisory: `modified` (recorded from a checkout with unco
|
|
|
643
675
|
(origin not captured). Absent on a layer from a clean commit. It never changes a status. A closed vocabulary: it
|
|
644
676
|
changes only with a new `schemaVersion`.
|
|
645
677
|
|
|
678
|
+
#### `LayerRunSchema`
|
|
679
|
+
|
|
680
|
+
```ts
|
|
681
|
+
export const LayerRunSchema = …
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
The execution a layer belongs to, named by the producer: one invocation of a local or manual run, which several
|
|
685
|
+
layers (shards, batches, producers) may share. Presentation and history grouping only: it never takes part in the
|
|
686
|
+
slot a result fills, in which result wins, or in a status. Absent, a run is told by its CI run URL, else `branch@sha`.
|
|
687
|
+
Within one project, layers that carry the same `id` are one run; a later invocation gets another `id`.
|
|
688
|
+
|
|
689
|
+
#### `LayerRun`
|
|
690
|
+
|
|
691
|
+
```ts
|
|
692
|
+
export type LayerRun = z.output<typeof LayerRunSchema>;
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
The execution of a layer, parsed from `LayerRunSchema`.
|
|
696
|
+
|
|
646
697
|
#### `MeasurementSchema`
|
|
647
698
|
|
|
648
699
|
```ts
|
package/docs/report-contract.md
CHANGED
|
@@ -82,6 +82,7 @@ Zod schema as `LayerDocumentSchema` from `@crvouga/atlas/schema`.
|
|
|
82
82
|
| `environment` | The client checked, or absent. Normalised to a lowercase slug: `iOS` → `ios`, `Desktop Web` → `desktop-web`, `iphone` → `ios`, `mobile browser` → `mobile-web`. Any other name is kept. Part of the slot. |
|
|
83
83
|
| `parameters` | Other string choices that make a result its own slot, such as `{ "device": "iphone-15" }`. A parameter named `environment` is read as the environment. Part of the slot. |
|
|
84
84
|
| `freshness` | `modified`: recorded from a checkout with uncommitted changes. `unknown`: origin not captured. Advisory only; see [Statuses](#statuses-and-the-gate). |
|
|
85
|
+
| `run` | Optional `{ id, title?, startedAtMs?, endedAtMs? }`: the execution a local or manual producer names. Layers with the same `id` in one project are one run in the run list, however many shards or batches uploaded them; a later invocation at the same branch and commit gets another `id`. Without it a run is its `runUrl`, else `branch@sha`, as before. `title` is for people. Grouping and display only: it is **never part of the slot**, never decides a winner or a status, and never hides `sha`, `branch` or `freshness`. A `run` a reader cannot read is treated as absent. |
|
|
85
86
|
| `specs` | The specs the layer was recorded against, with a `fingerprint` of their text, so a reader can warn when a spec changed since. Optional. |
|
|
86
87
|
| `parts[].kind` | An open category (`behavior`, `performance`, `accessibility`, …). A new kind needs no change to any reader. Part of the slot. |
|
|
87
88
|
| `parts[].checks[]` | `id` (required, stable), `title`, `status`, `detail`, `specPath`, `measurements`, `durationMs`. |
|
package/docs/rich-evidence.md
CHANGED
|
@@ -26,6 +26,7 @@ decided exactly as before ([the report contract](report-contract.md)). Evidence
|
|
|
26
26
|
| `steps` (`parentId`, `offsetMs`, `durationMs`, `status`, `kind`) | check, attempt | Draws the check's timeline: nested steps, when each started, how long it took, which one failed. |
|
|
27
27
|
| `attempts` | check | Every try of a retried check, with its own status, detail, steps and attachments. A check that passed after failing is flaky. |
|
|
28
28
|
| `tags`, `labels` | check, layer | Filters and groups (`smoke`, `flaky`, an owner, a browser). |
|
|
29
|
+
| `tags` | attachment | Marks one recording or screenshot, for example `demo`: shown as a `#demo` chip, and searchable. See [Tagging artifacts and demos](#tagging-artifacts-and-demos). |
|
|
29
30
|
| `annotations`, `links` | check, step, attempt, layer | Notes, warnings and issue links next to the result. |
|
|
30
31
|
| `specPath` | check | The spec a check belongs to (a `.feature`, a test file), relative to the repository. Checks are grouped by it. |
|
|
31
32
|
| `specs[]` (`path`, `domain`, `kind`, `title`) | layer | Names each spec and its group (`domain`), so a server can group checks by spec with a title even when no [spec set](integrating.md#tell-atlas-which-spec-a-check-belongs-to) has been published. |
|
|
@@ -34,6 +35,33 @@ decided exactly as before ([the report contract](report-contract.md)). Evidence
|
|
|
34
35
|
Send `sha`, `branch` and `runUrl` on every layer too. They never change a result, but they tie results to a commit
|
|
35
36
|
and to the CI run that produced them.
|
|
36
37
|
|
|
38
|
+
## Tagging artifacts and demos
|
|
39
|
+
|
|
40
|
+
To mark one recording as a demo, tag the attachment itself. A tag on a check says something about the result; a tag on
|
|
41
|
+
an attachment says something about that file, and no Gherkin tag, label, environment or producer name is needed:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
{ name: 'checkout.webm', role: 'video', tags: ['demo'], labels: { audience: 'business' } }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The viewer shows it as a `#demo` chip on the artifact card. Selecting the chip filters the result's evidence to the
|
|
48
|
+
attachments with that tag, and says how many match. Tags describe evidence; they never change a status or a slot.
|
|
49
|
+
|
|
50
|
+
- **One spelling.** A tag is normalized before it is compared or stored: a leading `#` is dropped, letters are
|
|
51
|
+
lower-cased, each run of whitespace becomes `-`, and surrounding space is trimmed, so `demo`, `#demo` and `#Demo ` are
|
|
52
|
+
`demo`. Empty tags and tags over 64 characters are not tags and are ignored (or refused, when a reviewer enters them).
|
|
53
|
+
Everything else is a valid tag (`team/payments`, `release=2`), and repeats are shown once.
|
|
54
|
+
- **Inherited.** An attachment is also searchable by the tags of its check and its run. The viewer says `inherited` on
|
|
55
|
+
such a chip, and a switch decides whether the filter counts them: a recording without a tag of its own under a run
|
|
56
|
+
tagged `demo` is found by the inherited query and not by the exact one. In the API,
|
|
57
|
+
`GET /api/reports/tests?artifactTag=demo` is exact and adding `&inherited=1` includes inherited tags.
|
|
58
|
+
- **Reviewers.** After upload, a person with a publish token can add tags to an attachment (the viewer's *Add tag*
|
|
59
|
+
button) and remove the ones a reviewer added. Those tags are not written into the layer, which is immutable: the
|
|
60
|
+
server keeps them as an audit trail (who, when, which tags) and the viewer shows them as `added by <name>`, apart
|
|
61
|
+
from the producer's tags. See [Tagging artifacts](server.md#tagging-artifacts). A producer's own tags stay as uploaded.
|
|
62
|
+
- **Older layers.** A layer without attachment tags reads exactly as before. Tags on an attachment that cannot be read
|
|
63
|
+
(not a list of strings) are dropped, and the attachment, check and layer stay.
|
|
64
|
+
|
|
37
65
|
## Labels describe, parameters split
|
|
38
66
|
|
|
39
67
|
A result's **slot** is its producer, check id, part kind, spec, environment and **parameters**. The newest completed
|
package/docs/server.md
CHANGED
|
@@ -216,6 +216,26 @@ against a server unchanged. A client uploads **in dependency order**: blobs, the
|
|
|
216
216
|
entry that makes it visible. The server checks each step and says no to a catalog entry whose manifest is not
|
|
217
217
|
there (`409`), so nothing half-uploaded is ever shown.
|
|
218
218
|
|
|
219
|
+
## Tagging artifacts
|
|
220
|
+
|
|
221
|
+
A reviewer can mark a recording `#demo` after it was uploaded without uploading it again. The layer is immutable, so the
|
|
222
|
+
tag is kept beside it: each edit is one small object under `curation/artifact-tags/` in the write backend (or the live
|
|
223
|
+
store of a relay), named by time, never changed or removed. It records the attachment (layer, check, and the attachment's
|
|
224
|
+
`id`, else its bytes' key, else its name), the normalized tags added and removed, the name of the token that made the
|
|
225
|
+
edit, and the server's time. Nothing about the layer, its results or the report's winners changes.
|
|
226
|
+
|
|
227
|
+
- `GET /api/reports/artifact-tags` (read) returns every edit, oldest first. Fold them to get the tags that stand now: the
|
|
228
|
+
last edit of a tag wins.
|
|
229
|
+
- `POST /api/reports/artifact-tags` (publish, `Authorization: Bearer`) records an edit. A read token, an anonymous reader
|
|
230
|
+
and the browser's sign-in box are refused (`403`/`401`) like any upload, so a read-only visitor can see the tags and
|
|
231
|
+
not change them. The viewer asks for a token when a reviewer adds a tag and keeps it only in that tab.
|
|
232
|
+
- Producer tags (`tags` on the attachment) and reviewer tags are kept apart everywhere: the viewer says who added a
|
|
233
|
+
reviewer tag and when, and a reviewer can remove only reviewer tags. `capabilities.artifactTags` on `/api/server` says
|
|
234
|
+
a server has this.
|
|
235
|
+
|
|
236
|
+
See [Tagging artifacts and demos](rich-evidence.md#tagging-artifacts-and-demos) for the tag rules. Reviewer tags are
|
|
237
|
+
shown in the viewer's evidence; the tests API (`artifactTag`) searches the producers' tags.
|
|
238
|
+
|
|
219
239
|
## Streaming
|
|
220
240
|
|
|
221
241
|
`GET /api/events` is a server-sent-events stream:
|
|
@@ -330,6 +350,10 @@ curl "$ATLAS_SERVER_URL/api/projects/$ATLAS_PROJECT/reports/tests?format=markdow
|
|
|
330
350
|
| `GET /api/projects/{project}/reports/tests/health` | read | Why specs, domains and counts disagree. |
|
|
331
351
|
| `GET /api/reports/tests/history` | read | One test and every result recorded for it. |
|
|
332
352
|
| `GET /api/projects/{project}/reports/tests/history` | read | One test and every result recorded for it. |
|
|
353
|
+
| `GET /api/reports/artifact-tags` | read | Reviewer tags of attachments: every recorded edit. |
|
|
354
|
+
| `GET /api/projects/{project}/reports/artifact-tags` | read | Reviewer tags of attachments: every recorded edit. |
|
|
355
|
+
| `POST /api/reports/artifact-tags` | publish | Tag or untag one attachment after upload. |
|
|
356
|
+
| `POST /api/projects/{project}/reports/artifact-tags` | publish | Tag or untag one attachment after upload. |
|
|
333
357
|
| `GET /api/reports/runs` | read | The runs that recorded the report. |
|
|
334
358
|
| `GET /api/projects/{project}/reports/runs` | read | The runs that recorded the report. |
|
|
335
359
|
| `GET /api/reports/runs/detail` | read | Every test one run recorded. |
|