Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

Commit

Error groups link work items

An error group names the work item made for it by its number in the workspace (item). One linked before keeps its old issue number as legacy_issue, whose address still leads to the work item it became.

G1T-Session:
ses_01m566357hejh9yeqjbetx23f3
syntaqxcommitted Parent9fc1a52Browse files
6 files+47−100/6 viewed
+9−3
220220 last_trace_id: string | null;
221221 /** resolved, ignored or open. A resolved group that happens again opens again. */
222222 state: "open" | "resolved" | "ignored";
223− /** The issue made for it, so an agent can take it. */
224− issue_number: number | null;
223+ /** The work item made for it, by number in the workspace, so an agent can take it. */
224+ item: number | null;
225+ /**
226+ * One linked before issues became work items: its old number in the
227+ * project's repository, whose address (`/<owner>/<repo>/issues/<n>`)
228+ * still leads to the work item. Null once `item` is set.
229+ */
230+ legacy_issue: number | null;
225231 };
226232
227233 /** What every list query can narrow by. */
334340 traces(args: ProjectRef & TraceQuery): Promise<Page<TraceSummary>>;
335341 trace(args: ProjectRef & { trace_id: string }): Promise<TraceDetail | null>;
336342 errors(args: ProjectRef & TelemetryFilter & { state?: ErrorGroup["state"] | null }): Promise<ErrorGroup[]>;
337− setError(args: ProjectRef & { fingerprint: string; state?: ErrorGroup["state"]; issue_number?: number | null }): Promise<ErrorGroup | null>;
343+ setError(args: ProjectRef & { fingerprint: string; state?: ErrorGroup["state"]; item?: number | null }): Promise<ErrorGroup | null>;
338344 metrics(args: ProjectRef): Promise<MetricInfo[]>;
339345 metric(args: ProjectRef & TelemetryFilter & { name: string; aggregate?: "avg" | "sum" | "min" | "max" | "p50" | "p95" | "p99" | null; group_by?: string | null; step_s?: number | null }): Promise<MetricSeries>;
340346 /** What is in the store: services, environments and attribute keys seen, for filters. */
+1−1
331331 case "errors":
332332 return s.errors(args);
333333 case "set_error":
334− return s.setError(String(args.fingerprint), { state: args.state as ErrorGroup["state"] | undefined, issue_number: args.issue_number });
334+ return s.setError(String(args.fingerprint), { state: args.state as ErrorGroup["state"] | undefined, item: args.item });
335335 case "metrics":
336336 return s.metrics();
337337 case "metric":
+23−0
1+import assert from "node:assert/strict";
2+import { DatabaseSync } from "node:sqlite";
3+import test from "node:test";
4+
5+import { MIGRATIONS } from "./schema.ts";
6+
7+test("an error group linked to an issue keeps its old number when issues become work items", () => {
8+ const db = new DatabaseSync(":memory:");
9+ for (const step of MIGRATIONS.slice(0, -1)) db.exec(step);
10+ db.exec(
11+ `INSERT INTO error_groups (fingerprint, name, message, service, first_us, last_us, issue_number)
12+ VALUES ('fp_1', 'TypeError', 'boom', 'web', 1, 2, 7), ('fp_2', 'TypeError', 'bang', 'web', 1, 2, NULL)`,
13+ );
14+ db.exec(MIGRATIONS.at(-1)!);
15+ const rows = db.prepare("SELECT fingerprint, item, legacy_issue FROM error_groups ORDER BY fingerprint").all();
16+ assert.deepEqual(
17+ rows.map((row) => ({ ...row })),
18+ [
19+ { fingerprint: "fp_1", item: null, legacy_issue: 7 },
20+ { fingerprint: "fp_2", item: null, legacy_issue: null },
21+ ],
22+ );
23+});
+7−0
199199 ingested_bytes INTEGER NOT NULL DEFAULT 0
200200 );
201201 `,
202+ // Issues became work items, numbered in the workspace: an error group
203+ // links one by that number. One linked before keeps its old issue
204+ // number, whose address still leads to the work item it became.
205+ `
206+ ALTER TABLE error_groups RENAME COLUMN issue_number TO legacy_issue;
207+ ALTER TABLE error_groups ADD COLUMN item INTEGER;
208+ `,
202209 ];
203210
204211 /**
+2−2
142142 assert.equal(groups.length, 1);
143143 assert.equal(groups[0].count, 2);
144144 assert.equal(groups[0].first_deployment_id, "dep_1");
145− store.setError(groups[0].fingerprint, { state: "resolved", issue_number: 12 });
145+ store.setError(groups[0].fingerprint, { state: "resolved", item: 12 });
146146 await ingestTail(store, [appEvent("bbbbbbbbbbbbbbb3", { outcome: "exception", exception: "Cannot read 'z' of user 3" })]);
147147 groups = store.errors({});
148148 assert.equal(groups[0].state, "open");
149− assert.equal(groups[0].issue_number, 12);
149+ assert.equal(groups[0].item, 12);
150150 const failed = store.requests({ errors_only: true });
151151 assert.equal(failed.items.length, 3);
152152 // An app that threw answered with an error page, whichever half said so.
+5−4
573573 this.sql.exec("DELETE FROM spans WHERE start_us < ?", keep);
574574 this.sql.exec("DELETE FROM metric_points WHERE time_us < ?", keep);
575575 this.sql.exec("DELETE FROM rollups WHERE minute < ?", Math.floor((now - ROLLUP_DAYS * DAY_US) / MINUTE_US));
576− this.sql.exec("DELETE FROM error_groups WHERE last_us < ? AND issue_number IS NULL", now - ROLLUP_DAYS * DAY_US);
576+ this.sql.exec("DELETE FROM error_groups WHERE last_us < ? AND item IS NULL AND legacy_issue IS NULL", now - ROLLUP_DAYS * DAY_US);
577577 this.sql.exec("DELETE FROM usage_days WHERE day < ?", day(now - 40 * DAY_US));
578578 // Over its size, the store sheds a day at a time from the oldest.
579579 for (let i = 0; i < 30 && this.state.storage.sql.databaseSize > MAX_BYTES; i++) {
876876 return this.rows(`SELECT * FROM error_groups ${clauses.length ? `WHERE ${clauses.join(" AND ")}` : ""} ORDER BY last_us DESC LIMIT 200`, ...args).map(errorOf);
877877 }
878878
879− setError(fp: string, change: { state?: ErrorGroup["state"]; issue_number?: number | null }): ErrorGroup | null {
879+ setError(fp: string, change: { state?: ErrorGroup["state"]; item?: number | null }): ErrorGroup | null {
880880 if (change.state && !["open", "resolved", "ignored"].includes(change.state)) throw new Error("State is open, resolved or ignored.");
881881 if (change.state) this.sql.exec("UPDATE error_groups SET state = ? WHERE fingerprint = ?", change.state, fp);
882− if (change.issue_number !== undefined) this.sql.exec("UPDATE error_groups SET issue_number = ? WHERE fingerprint = ?", change.issue_number, fp);
882+ if (change.item !== undefined) this.sql.exec("UPDATE error_groups SET item = ?, legacy_issue = NULL WHERE fingerprint = ?", change.item, fp);
883883 const row = this.rows("SELECT * FROM error_groups WHERE fingerprint = ?", fp)[0];
884884 return row ? errorOf(row) : null;
885885 }
11281128 last_request_id: strOrNull(r.last_request_id),
11291129 last_trace_id: strOrNull(r.last_trace_id),
11301130 state: String(r.state) as ErrorGroup["state"],
1131− issue_number: numOrNull(r.issue_number),
1131+ item: numOrNull(r.item),
1132+ legacy_issue: numOrNull(r.legacy_issue),
11321133 };
11331134 }
11341135

Comments

Sign in to comment.