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

Contracts: work items as a dataset

DatasetId::Issues is now WorkItems ("work_items"), in Rust and in the TypeScript mirror, for the API and the folios that read it.

G1T-Session:
ses_01m566353xebrscb7pf471bnn1
syntaqxcommitted Parentab5b7deBrowse files
6 files+27−270/6 viewed
+5−5
1515 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
1616 #[serde(rename_all = "snake_case")]
1717 pub enum DatasetId {
18− Issues,
18+ WorkItems,
1919 PullRequests,
2020 WorkflowRuns,
2121 Deployments,
2525
2626 impl DatasetId {
2727 pub const ALL: [DatasetId; 6] = [
28− DatasetId::Issues,
28+ DatasetId::WorkItems,
2929 DatasetId::PullRequests,
3030 DatasetId::WorkflowRuns,
3131 DatasetId::Deployments,
3535
3636 pub fn as_str(self) -> &'static str {
3737 match self {
38− DatasetId::Issues => "issues",
38+ DatasetId::WorkItems => "work_items",
3939 DatasetId::PullRequests => "pull_requests",
4040 DatasetId::WorkflowRuns => "workflow_runs",
4141 DatasetId::Deployments => "deployments",
267267 /// The catalog.
268268 pub const DATASETS: [DatasetSpec; 6] = [
269269 DatasetSpec {
270− id: DatasetId::Issues,
271− label: "Issues",
270+ id: DatasetId::WorkItems,
271+ label: "Work items",
272272 service: DatasetService::Work,
273273 needs: DatasetNeeds::Member,
274274 times: &["created_at", "closed_at"],
+16−16
33 "cases": [
44 { "query": { "dataset": "pull_requests", "measure": { "op": "count" }, "interval": "week", "group_by": "repo", "time": "merged_at", "range": "90d" }, "error": null },
55 { "query": { "dataset": "workflow_runs", "measure": { "op": "rate", "field": "succeeded" }, "filters": [{ "field": "workflow", "op": "in", "value": ["CI", "Deploy"] }] }, "error": null },
6− { "query": { "dataset": "issues", "measure": { "op": "p95", "field": "time_to_close_hours" }, "range": { "from": "2026-01-01", "to": "2026-04-01T00:00:00Z" }, "limit": 100 }, "error": null },
6+ { "query": { "dataset": "work_items", "measure": { "op": "p95", "field": "time_to_close_hours" }, "range": { "from": "2026-01-01", "to": "2026-04-01T00:00:00Z" }, "limit": 100 }, "error": null },
77 { "query": { "dataset": "deployments", "measure": { "op": "avg", "field": "duration_seconds" }, "filters": [{ "field": "duration_seconds", "op": "gte", "value": 30 }, { "field": "environment", "op": "eq", "value": "production" }] }, "error": null },
88 { "query": { "dataset": "spend", "measure": { "op": "sum", "field": "amount_micros" }, "group_by": "product", "interval": "month" }, "error": null },
99 { "query": { "dataset": "agent_sessions", "measure": { "op": "count" }, "group_by": "agent", "range": { "from": "2024-02-29", "to": "2024-03-01" } }, "error": null },
1010
11− { "query": { "dataset": "issues", "measure": { "op": "count", "field": "comments" } }, "error": "count takes no field." },
12− { "query": { "dataset": "issues", "measure": { "op": "rate" } }, "error": "rate needs a field." },
13− { "query": { "dataset": "issues", "measure": { "op": "rate", "field": "comments" } }, "error": "issues has no yes-or-no field comments to take the rate of." },
14− { "query": { "dataset": "issues", "measure": { "op": "sum" } }, "error": "sum needs a field." },
11+ { "query": { "dataset": "work_items", "measure": { "op": "count", "field": "comments" } }, "error": "count takes no field." },
12+ { "query": { "dataset": "work_items", "measure": { "op": "rate" } }, "error": "rate needs a field." },
13+ { "query": { "dataset": "work_items", "measure": { "op": "rate", "field": "comments" } }, "error": "work_items has no yes-or-no field comments to take the rate of." },
14+ { "query": { "dataset": "work_items", "measure": { "op": "sum" } }, "error": "sum needs a field." },
1515 { "query": { "dataset": "pull_requests", "measure": { "op": "avg", "field": "merged" } }, "error": "pull_requests has no number field merged." },
1616 { "query": { "dataset": "pull_requests", "measure": { "op": "count" }, "group_by": "author" }, "error": "pull_requests can't be grouped by author." },
1717 { "query": { "dataset": "pull_requests", "measure": { "op": "count" }, "time": "deployed_at" }, "error": "pull_requests has no time field deployed_at." },
2121 { "query": { "dataset": "workflow_runs", "measure": { "op": "count" }, "filters": [{ "field": "duration_seconds", "op": "in", "value": ["1"] }] }, "error": "duration_seconds is a number: filter it with eq, neq, gte or lte." },
2222 { "query": { "dataset": "workflow_runs", "measure": { "op": "count" }, "filters": [{ "field": "duration_seconds", "op": "lte", "value": "60" }] }, "error": "lte on duration_seconds takes a number." },
2323 { "query": { "dataset": "workflow_runs", "measure": { "op": "count" }, "filters": [{ "field": "secret", "op": "eq", "value": "x" }] }, "error": "workflow_runs can't be filtered by secret." },
24− { "query": { "dataset": "issues", "measure": { "op": "count" }, "range": { "from": "2026-02-30", "to": "2026-03-10" } }, "error": "A range's from and to are dates or RFC 3339 UTC times." },
25− { "query": { "dataset": "issues", "measure": { "op": "count" }, "range": { "from": "2026-03-10", "to": "2026-03-10" } }, "error": "A range's from comes before its to." },
26− { "query": { "dataset": "issues", "measure": { "op": "count" }, "range": { "from": "2025-01-01", "to": "2026-01-03" } }, "error": "A range is at most 366 days." },
27− { "query": { "dataset": "issues", "measure": { "op": "count" }, "range": { "from": "yesterday", "to": "today" } }, "error": "A range's from and to are dates or RFC 3339 UTC times." },
28− { "query": { "dataset": "issues", "measure": { "op": "count" }, "limit": 101 }, "error": "limit is between 1 and 100." },
29− { "query": { "dataset": "issues", "measure": { "op": "count" }, "limit": 0 }, "error": "limit is between 1 and 100." },
30− { "query": { "dataset": "issues", "measure": { "op": "count" }, "filters": [
24+ { "query": { "dataset": "work_items", "measure": { "op": "count" }, "range": { "from": "2026-02-30", "to": "2026-03-10" } }, "error": "A range's from and to are dates or RFC 3339 UTC times." },
25+ { "query": { "dataset": "work_items", "measure": { "op": "count" }, "range": { "from": "2026-03-10", "to": "2026-03-10" } }, "error": "A range's from comes before its to." },
26+ { "query": { "dataset": "work_items", "measure": { "op": "count" }, "range": { "from": "2025-01-01", "to": "2026-01-03" } }, "error": "A range is at most 366 days." },
27+ { "query": { "dataset": "work_items", "measure": { "op": "count" }, "range": { "from": "yesterday", "to": "today" } }, "error": "A range's from and to are dates or RFC 3339 UTC times." },
28+ { "query": { "dataset": "work_items", "measure": { "op": "count" }, "limit": 101 }, "error": "limit is between 1 and 100." },
29+ { "query": { "dataset": "work_items", "measure": { "op": "count" }, "limit": 0 }, "error": "limit is between 1 and 100." },
30+ { "query": { "dataset": "work_items", "measure": { "op": "count" }, "filters": [
3131 { "field": "repo", "op": "eq", "value": "a" }, { "field": "repo", "op": "eq", "value": "b" }, { "field": "repo", "op": "eq", "value": "c" },
3232 { "field": "repo", "op": "eq", "value": "d" }, { "field": "repo", "op": "eq", "value": "e" }, { "field": "repo", "op": "eq", "value": "f" },
3333 { "field": "repo", "op": "eq", "value": "g" }, { "field": "repo", "op": "eq", "value": "h" }, { "field": "repo", "op": "eq", "value": "i" },
3535 ] }, "error": "A query takes at most 10 filters." },
3636
3737 { "query": { "dataset": "secrets", "measure": { "op": "count" } }, "error": "*" },
38− { "query": { "dataset": "issues", "measure": { "op": "median" } }, "error": "*" },
39− { "query": { "dataset": "issues" }, "error": "*" },
40− { "query": { "dataset": "issues", "measure": { "op": "count" }, "range": "1y" }, "error": "*" },
41− { "query": { "dataset": "issues", "measure": { "op": "count" }, "interval": "hour" }, "error": "*" },
38+ { "query": { "dataset": "work_items", "measure": { "op": "median" } }, "error": "*" },
39+ { "query": { "dataset": "work_items" }, "error": "*" },
40+ { "query": { "dataset": "work_items", "measure": { "op": "count" }, "range": "1y" }, "error": "*" },
41+ { "query": { "dataset": "work_items", "measure": { "op": "count" }, "interval": "hour" }, "error": "*" },
4242 { "query": "SELECT * FROM issues", "error": "*" }
4343 ]
4444 }
+1−1
2020 });
2121
2222 test("a parsed query keeps only what the catalog knows", () => {
23− const parsed = parseDatasetQuery({ dataset: "issues", measure: { op: "count" }, sql: "DROP TABLE issues", group_by: "repo" });
23+ const parsed = parseDatasetQuery({ dataset: "work_items", measure: { op: "count" }, sql: "DROP TABLE issues", group_by: "repo" });
2424 assert.ok("query" in parsed);
2525 assert.equal("sql" in parsed.query, false);
2626 assert.equal(parsed.query.group_by, "repo");
+3−3
1515 * Wire shapes are snake_case.
1616 */
1717
18−export type DatasetId = "issues" | "pull_requests" | "workflow_runs" | "deployments" | "spend" | "agent_sessions";
18+export type DatasetId = "work_items" | "pull_requests" | "workflow_runs" | "deployments" | "spend" | "agent_sessions";
1919
20−export const DATASET_IDS: readonly DatasetId[] = ["issues", "pull_requests", "workflow_runs", "deployments", "spend", "agent_sessions"];
20+export const DATASET_IDS: readonly DatasetId[] = ["work_items", "pull_requests", "workflow_runs", "deployments", "spend", "agent_sessions"];
2121
2222 /** How rows are summed up. `count` takes no field; `rate` takes a rate field; the rest a measure field. */
2323 export type DatasetMeasureOp = "count" | "sum" | "avg" | "p50" | "p95" | "rate";
9797
9898 /** The catalog. One dataset per line: the Rust mirror test reads it that way. */
9999 export const DATASETS: Record<DatasetId, DatasetSpec> = {
100− issues: { label: "Issues", service: "work", needs: "member", times: ["created_at", "closed_at"], dimensions: ["repo", "label", "state", "author_kind", "assignee_kind", "milestone"], measures: ["time_to_close_hours", "comments"], rates: ["closed"] },
100+ work_items: { label: "Work items", service: "work", needs: "member", times: ["created_at", "closed_at"], dimensions: ["repo", "label", "state", "author_kind", "assignee_kind", "milestone"], measures: ["time_to_close_hours", "comments"], rates: ["closed"] },
101101 pull_requests: { label: "Pull requests", service: "work", needs: "member", times: ["created_at", "merged_at", "closed_at"], dimensions: ["repo", "label", "state", "author_kind", "base_branch"], measures: ["cycle_time_hours", "time_to_first_review_hours", "review_count", "additions", "deletions", "changed_files"], rates: ["merged"] },
102102 workflow_runs: { label: "Workflow runs", service: "actions", needs: "member", times: ["started_at", "completed_at"], dimensions: ["repo", "workflow", "branch", "event", "conclusion", "runner_kind"], measures: ["duration_seconds", "queue_seconds"], rates: ["succeeded"] },
103103 deployments: { label: "Deployments", service: "deployments", needs: "member", times: ["created_at"], dimensions: ["repo", "project", "environment", "state"], measures: ["duration_seconds", "time_to_restore_hours"], rates: ["failed"] },
+1−1
129129 const preset = typeof range === "string" && ["7d", "30d", "90d"].includes(range);
130130 if (!preset && !(isObject(range) && typeof range.from === "string" && typeof range.to === "string")) return "range is 7d, 30d, 90d or { from, to }.";
131131 if (isObject(range)) {
132− const error = queryError({ dataset: "issues", measure: { op: "count" }, range: { from: range.from as string, to: range.to as string } });
132+ const error = queryError({ dataset: "work_items", measure: { op: "count" }, range: { from: range.from as string, to: range.to as string } });
133133 if (error) return error;
134134 }
135135 if (filters.repos != null && !(Array.isArray(filters.repos) && filters.repos.every((repo) => typeof repo === "string"))) return "repos is a list of owner/name.";
+1−1
120120 client.thread(ws, viewer, f, { op: "resolve", thread_id: "thr_1" }),
121121 client.threads(ws, viewer, f),
122122 client.queryTile(ws, viewer, f, "t1"),
123− client.queryDataset(ws, viewer, { dataset: "issues", measure: { op: "count" } }),
123+ client.queryDataset(ws, viewer, { dataset: "work_items", measure: { op: "count" } }),
124124 client.markCurrent(ws, viewer, f),
125125 client.reindex(ws, viewer),
126126 ]);

Comments

Sign in to comment.