From 6a6876d6f0550390fafa1400685737f51e1ce942 Mon Sep 17 00:00:00 2001 From: Matt Quinn Date: Mon, 31 Aug 2026 13:33:02 -0400 Subject: [PATCH 1/3] fix(alerts): Remove deprecated transactions dataset --- apps/cli-docs/src/fragments/commands/alert.md | 4 +-- .../skills/sentry-cli/references/alert.md | 10 +++--- .../cli/src/commands/alert/metrics/create.ts | 9 +++--- .../cli/src/commands/alert/metrics/edit.ts | 4 +-- .../cli/src/commands/alert/mutation-utils.ts | 17 ++++------ .../test/commands/alert/metrics/edit.test.ts | 6 ++-- .../commands/alert/mutation-utils.test.ts | 19 +++-------- packages/cli/test/lib/api/alerts.test.ts | 32 +++++++++---------- 8 files changed, 44 insertions(+), 57 deletions(-) diff --git a/apps/cli-docs/src/fragments/commands/alert.md b/apps/cli-docs/src/fragments/commands/alert.md index 1d47e382b0..ee32b001f3 100644 --- a/apps/cli-docs/src/fragments/commands/alert.md +++ b/apps/cli-docs/src/fragments/commands/alert.md @@ -53,8 +53,8 @@ sentry alert issues delete my-org/my-project/12345 --dry-run sentry alert metrics create my-org \ --name "P95 Latency" \ --query "environment:prod" \ - --aggregate "p95(transaction.duration)" \ - --dataset transactions \ + --aggregate "p95(span.duration)" \ + --dataset spans \ --time-window 5 \ --trigger '{"alertThreshold":500,"actions":[{"id":"sentry.mail.actions.NotifyEmailAction","targetType":"Team","targetIdentifier":1}]}' ``` diff --git a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/alert.md b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/alert.md index 72efe505e1..efeb30c27d 100644 --- a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/alert.md +++ b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/alert.md @@ -154,8 +154,8 @@ Create a metric alert rule **Flags:** - `--name - Rule name` - `--query - Metric query filter string` -- `--aggregate - Aggregate expression (for example count(), p95(transaction.duration))` -- `--dataset - Dataset: errors (error-events), transactions (transaction-like), sessions, events, spans, metrics` +- `--aggregate - Aggregate expression (for example count(), p95(span.duration))` +- `--dataset - Dataset: errors (error-events), sessions, events, spans, metrics` - `--time-window - Evaluation window in minutes` - `-t, --trigger ... - Trigger object JSON (repeatable, or pass one JSON array)` - `-p, --project ... - Project slug filter (repeatable or comma-separated)` @@ -170,8 +170,8 @@ Create a metric alert rule sentry alert metrics create my-org \ --name "P95 Latency" \ --query "environment:prod" \ - --aggregate "p95(transaction.duration)" \ - --dataset transactions \ + --aggregate "p95(span.duration)" \ + --dataset spans \ --time-window 5 \ --trigger '{"alertThreshold":500,"actions":[{"id":"sentry.mail.actions.NotifyEmailAction","targetType":"Team","targetIdentifier":1}]}' ``` @@ -201,7 +201,7 @@ Edit a metric alert rule - `--status - active or disabled` - `--query - Metric query filter` - `--aggregate - Aggregate expression` -- `--dataset - Dataset: errors (error-events), transactions (transaction-like), sessions, events, spans, metrics` +- `--dataset - Dataset: errors (error-events), sessions, events, spans, metrics` - `--time-window - Evaluation window in minutes` - `-t, --trigger ... - Trigger object JSON (repeatable, or pass one JSON array)` - `-p, --project ... - Project slug filter (repeatable or comma-separated)` diff --git a/packages/cli/src/commands/alert/metrics/create.ts b/packages/cli/src/commands/alert/metrics/create.ts index f46c65e888..933dee2654 100644 --- a/packages/cli/src/commands/alert/metrics/create.ts +++ b/packages/cli/src/commands/alert/metrics/create.ts @@ -85,8 +85,8 @@ export const createCommand = buildCommand({ " --environment, --owner\n\n" + "Examples:\n" + " sentry alert metrics create my-org --name 'P95 latency' \\\n" + - " --query 'environment:prod' --aggregate 'p95(transaction.duration)' \\\n" + - " --dataset transactions --time-window 5 \\\n" + + " --query 'environment:prod' --aggregate 'p95(span.duration)' \\\n" + + " --dataset spans --time-window 5 \\\n" + ' --trigger \'{"alertThreshold":500,"actions":[{"id":"sentry.mail.actions.NotifyEmailAction","targetType":"Team","targetIdentifier":1}]}\'\n\n' + " sentry alert metrics create my-org --name 'Error volume' \\\n" + " --query 'event.type:error' --aggregate 'count()' --dataset errors \\\n" + @@ -122,14 +122,13 @@ export const createCommand = buildCommand({ aggregate: { kind: "parsed", parse: String, - brief: - "Aggregate expression (for example count(), p95(transaction.duration))", + brief: "Aggregate expression (for example count(), p95(span.duration))", }, dataset: { kind: "parsed", parse: String, brief: - "Dataset: errors (error-events), transactions (transaction-like), sessions, events, spans, metrics", + "Dataset: errors (error-events), sessions, events, spans, metrics", }, "time-window": { kind: "parsed", diff --git a/packages/cli/src/commands/alert/metrics/edit.ts b/packages/cli/src/commands/alert/metrics/edit.ts index 23c1136e83..0d6637f5b6 100644 --- a/packages/cli/src/commands/alert/metrics/edit.ts +++ b/packages/cli/src/commands/alert/metrics/edit.ts @@ -166,7 +166,7 @@ export const editCommand = buildCommand({ "Examples:\n" + " sentry alert metrics edit my-org/9 --name 'Error budget'\n" + " sentry alert metrics edit my-org/9 --status disabled\n" + - " sentry alert metrics edit my-org/9 --time-window 15 --dataset transactions", + " sentry alert metrics edit my-org/9 --time-window 15 --dataset spans", }, output: { human: formatEdited, @@ -213,7 +213,7 @@ export const editCommand = buildCommand({ parse: String, optional: true, brief: - "Dataset: errors (error-events), transactions (transaction-like), sessions, events, spans, metrics", + "Dataset: errors (error-events), sessions, events, spans, metrics", }, "time-window": { kind: "parsed", diff --git a/packages/cli/src/commands/alert/mutation-utils.ts b/packages/cli/src/commands/alert/mutation-utils.ts index f800a9206f..f4742c425c 100644 --- a/packages/cli/src/commands/alert/mutation-utils.ts +++ b/packages/cli/src/commands/alert/mutation-utils.ts @@ -7,7 +7,6 @@ import { ValidationError } from "../../lib/errors.js"; const ISSUE_MATCH_MODES = new Set(["all", "any"]); const METRIC_DATASET_VALUES = new Set([ "errors", - "transactions", "sessions", "events", "spans", @@ -19,9 +18,9 @@ const METRIC_DATASET_VALUES = new Set([ * * Only aliases that resolve to a value already accepted by the metric alert API * are listed here: the singular forms (`error` → `errors`) and the dashboard - * terminology for the error/transaction datasets (`error-events` → `errors`, - * `transaction-like` → `transactions`). This lets users copying from dashboard - * docs avoid a validation error without changing which dataset is sent. + * terminology for the error dataset (`error-events` → `errors`). This lets + * users copying from dashboard docs avoid a validation error without + * changing which dataset is sent. * * Names that denote a *distinct* dataset in the metric alert path — e.g. * `tracemetrics`, `metricsenhanced`, `eap`, `events_analytics_platform` — are @@ -33,12 +32,10 @@ const METRIC_DATASET_VALUES = new Set([ const METRIC_DATASET_ALIASES: Record = { // Singular forms error: "errors", - transaction: "transactions", session: "sessions", metric: "metrics", - // Dashboard terminology for the error/transaction datasets + // Dashboard terminology for the error dataset "error-events": "errors", - "transaction-like": "transactions", }; const METRIC_TIME_WINDOWS = new Set([ 1, 5, 10, 15, 30, 60, 120, 240, 360, 720, 1440, @@ -203,9 +200,9 @@ export function normalizeProjectList( * Normalise a user-provided `--dataset` value to the canonical metric alert * dataset name accepted by the Sentry API. * - * Resolves known aliases (e.g. `error-events` → `errors`, `transaction` → - * `transactions`) so that values copied from dashboard docs or using singular - * forms work without manual translation. + * Resolves known aliases (e.g. `error-events` → `errors`) so that values copied + * from dashboard docs or using singular forms work without manual + * translation. */ export function normalizeMetricDataset(dataset: string): string { const lower = dataset.trim().toLowerCase(); diff --git a/packages/cli/test/commands/alert/metrics/edit.test.ts b/packages/cli/test/commands/alert/metrics/edit.test.ts index f03cf7811f..c409304c21 100644 --- a/packages/cli/test/commands/alert/metrics/edit.test.ts +++ b/packages/cli/test/commands/alert/metrics/edit.test.ts @@ -98,7 +98,7 @@ describe("alert metrics edit", () => { status: 1, query: "event.type:error environment:prod", aggregate: "count()", - dataset: "transactions", + dataset: "spans", timeWindow: 15, triggers: [{ alertThreshold: 200, actions: [{ id: "notify" }] }], }); @@ -114,7 +114,7 @@ describe("alert metrics edit", () => { status: "disabled", query: "event.type:error environment:prod", aggregate: "count()", - dataset: "transactions", + dataset: "spans", "time-window": 15, trigger: ['{"alertThreshold":200,"actions":[{"id":"notify"}]}'], json: true, @@ -128,7 +128,7 @@ describe("alert metrics edit", () => { status: 1, query: "event.type:error environment:prod", aggregate: "count()", - dataset: "transactions", + dataset: "spans", timeWindow: 15, triggers: [{ alertThreshold: 200, actions: [{ id: "notify" }] }], }); diff --git a/packages/cli/test/commands/alert/mutation-utils.test.ts b/packages/cli/test/commands/alert/mutation-utils.test.ts index 86a29a112f..e92f1d14c4 100644 --- a/packages/cli/test/commands/alert/mutation-utils.test.ts +++ b/packages/cli/test/commands/alert/mutation-utils.test.ts @@ -212,23 +212,20 @@ describe("normalizeProjectList", () => { describe("normalizeMetricDataset", () => { test.each([ ["error", "errors"], - ["transaction", "transactions"], ["session", "sessions"], ["metric", "metrics"], ["error-events", "errors"], - ["transaction-like", "transactions"], ])('maps alias "%s" to "%s"', (input, expected) => { expect(normalizeMetricDataset(input)).toBe(expected); }); test("is case-insensitive and trims whitespace", () => { expect(normalizeMetricDataset("ERROR-EVENTS")).toBe("errors"); - expect(normalizeMetricDataset(" Transaction-Like ")).toBe("transactions"); + expect(normalizeMetricDataset(" Error-Events ")).toBe("errors"); }); test.each([ "errors", - "transactions", "sessions", "events", "spans", @@ -254,14 +251,7 @@ describe("normalizeMetricDataset", () => { }); describe("validateMetricDataset", () => { - const valid = [ - "errors", - "transactions", - "sessions", - "events", - "spans", - "metrics", - ]; + const valid = ["errors", "sessions", "events", "spans", "metrics"]; for (const dataset of valid) { test(`passes for "${dataset}"`, () => { @@ -272,7 +262,6 @@ describe("validateMetricDataset", () => { test.each([ "error", "error-events", - "transaction-like", "METRIC", ])('passes for alias "%s"', (dataset) => { expect(() => validateMetricDataset(dataset)).not.toThrow(); @@ -282,7 +271,9 @@ describe("validateMetricDataset", () => { "tracemetrics", "eap", "events_analytics_platform", - ])('throws for non-aliased dataset "%s"', (dataset) => { + "transactions", + "transaction-like", + ])('throws for obsolete or non-aliased dataset "%s"', (dataset) => { expect(() => validateMetricDataset(dataset)).toThrow(ValidationError); }); diff --git a/packages/cli/test/lib/api/alerts.test.ts b/packages/cli/test/lib/api/alerts.test.ts index 40a1e7646a..5e42d82caf 100644 --- a/packages/cli/test/lib/api/alerts.test.ts +++ b/packages/cli/test/lib/api/alerts.test.ts @@ -130,8 +130,8 @@ function metricDetector(overrides: Record) { dateCreated: "2026-01-01T00:00:00Z", dataSources: [ { - aggregate: "p95(transaction.duration)", - dataset: "transactions", + aggregate: "p95(span.duration)", + dataset: "spans", query: "environment:prod", // Detectors expose the window in seconds; 300s == 5m. timeWindow: 300, @@ -159,8 +159,8 @@ describe("listMetricAlertsPaginated", () => { id: "9", name: "P95 latency", status: 0, - aggregate: "p95(transaction.duration)", - dataset: "transactions", + aggregate: "p95(span.duration)", + dataset: "spans", query: "environment:prod", // 300s from the detector payload is normalized to 5 minutes. timeWindow: 5, @@ -221,8 +221,8 @@ describe("getMetricAlertRule", () => { queryObj: { snubaQuery: { aggregate: "p75(measurements.lcp)", - dataset: "transactions", - query: "transaction.op:pageload", + dataset: "spans", + query: "span.op:pageload", timeWindow: 600, }, }, @@ -234,8 +234,8 @@ describe("getMetricAlertRule", () => { const rule = await getMetricAlertRule("test-org", "9"); expect(rule.aggregate).toBe("p75(measurements.lcp)"); - expect(rule.dataset).toBe("transactions"); - expect(rule.query).toBe("transaction.op:pageload"); + expect(rule.dataset).toBe("spans"); + expect(rule.query).toBe("span.op:pageload"); expect(rule.timeWindow).toBe(10); }); @@ -393,8 +393,8 @@ describe("createMetricAlertRule", () => { type: "metric_issue", dataSources: [ { - aggregate: "p95(transaction.duration)", - dataset: "transactions", + aggregate: "p95(span.duration)", + dataset: "spans", query: "environment:prod", queryType: 1, eventTypes: ["trace_item_span"], @@ -418,8 +418,8 @@ describe("createMetricAlertRule", () => { const created = await createMetricAlertRule("test-org", { name: "P95 latency", query: "environment:prod", - aggregate: "p95(transaction.duration)", - dataset: "transactions", + aggregate: "p95(span.duration)", + dataset: "spans", timeWindow: 5, environment: "prod", triggers: [{ alertThreshold: 500, actions: [{ id: "notify" }] }], @@ -480,8 +480,8 @@ describe("getMetricAlertRuleDocument", () => { expect(doc).toMatchObject({ id: "9", name: "Baseline", - aggregate: "p95(transaction.duration)", - dataset: "transactions", + aggregate: "p95(span.duration)", + dataset: "spans", timeWindow: 5, }); }); @@ -506,8 +506,8 @@ describe("putMetricAlertRule", () => { const updated = await putMetricAlertRule("test-org", "9", { name: "Renamed", query: "environment:prod", - aggregate: "p95(transaction.duration)", - dataset: "transactions", + aggregate: "p95(span.duration)", + dataset: "spans", timeWindow: 5, status: 1, triggers: [{ alertThreshold: 500, actions: [{ id: "notify" }] }], From f3b8e0bf54c98ad9a442226ce477da01406e8c1e Mon Sep 17 00:00:00 2001 From: "jared-outpost[bot]" Date: Fri, 4 Sep 2026 09:52:43 +0000 Subject: [PATCH 2/3] feat(alerts): route transaction dataset requests to spans The transactions dataset was removed from alerts. Rather than hard-fail requests that still pass --dataset transaction(s), route them to the spans dataset and add is_transaction:true to the query so existing use cases keep working. Print a gentle tip nudging users toward --dataset spans directly. Also add a span -> spans alias for smoother transition. --- .../skills/sentry-cli/references/alert.md | 4 +- .../cli/src/commands/alert/metrics/create.ts | 16 +++- .../cli/src/commands/alert/metrics/edit.ts | 26 +++++-- .../cli/src/commands/alert/mutation-utils.ts | 74 ++++++++++++++++++- .../commands/alert/metrics/create.test.ts | 44 +++++++++++ .../test/commands/alert/metrics/edit.test.ts | 52 +++++++++++++ .../commands/alert/mutation-utils.test.ts | 46 ++++++++++++ 7 files changed, 245 insertions(+), 17 deletions(-) diff --git a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/alert.md b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/alert.md index efeb30c27d..60f3eabf68 100644 --- a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/alert.md +++ b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/alert.md @@ -155,7 +155,7 @@ Create a metric alert rule - `--name - Rule name` - `--query - Metric query filter string` - `--aggregate - Aggregate expression (for example count(), p95(span.duration))` -- `--dataset - Dataset: errors (error-events), sessions, events, spans, metrics` +- `--dataset - Dataset: errors (error-events), sessions, events, spans, metrics (transaction(s) routes to spans)` - `--time-window - Evaluation window in minutes` - `-t, --trigger ... - Trigger object JSON (repeatable, or pass one JSON array)` - `-p, --project ... - Project slug filter (repeatable or comma-separated)` @@ -201,7 +201,7 @@ Edit a metric alert rule - `--status - active or disabled` - `--query - Metric query filter` - `--aggregate - Aggregate expression` -- `--dataset - Dataset: errors (error-events), sessions, events, spans, metrics` +- `--dataset - Dataset: errors (error-events), sessions, events, spans, metrics (transaction(s) routes to spans)` - `--time-window - Evaluation window in minutes` - `-t, --trigger ... - Trigger object JSON (repeatable, or pass one JSON array)` - `-p, --project ... - Project slug filter (repeatable or comma-separated)` diff --git a/packages/cli/src/commands/alert/metrics/create.ts b/packages/cli/src/commands/alert/metrics/create.ts index 933dee2654..b66404a21b 100644 --- a/packages/cli/src/commands/alert/metrics/create.ts +++ b/packages/cli/src/commands/alert/metrics/create.ts @@ -9,13 +9,14 @@ import { createMetricAlertRule } from "../../../lib/api-client.js"; import { parseOrgProjectArg } from "../../../lib/arg-parsing.js"; import { buildCommand, numberParser } from "../../../lib/command.js"; import { ContextError, ValidationError } from "../../../lib/errors.js"; +import { warning } from "../../../lib/formatters/colors.js"; import { CommandOutput } from "../../../lib/formatters/output.js"; import { DRY_RUN_ALIASES, DRY_RUN_FLAG } from "../../../lib/mutate-command.js"; import { resolveOrg } from "../../../lib/resolve-target.js"; import { - normalizeMetricDataset, normalizeProjectList, parseJsonObjectList, + resolveMetricDataset, validateMetricDataset, validateMetricTimeWindow, validateMetricTriggers, @@ -128,7 +129,7 @@ export const createCommand = buildCommand({ kind: "parsed", parse: String, brief: - "Dataset: errors (error-events), sessions, events, spans, metrics", + "Dataset: errors (error-events), sessions, events, spans, metrics (transaction(s) routes to spans)", }, "time-window": { kind: "parsed", @@ -177,9 +178,16 @@ export const createCommand = buildCommand({ throw new ValidationError("aggregate cannot be empty.", "aggregate"); } - const dataset = normalizeMetricDataset(flags.dataset); + const { + dataset, + query, + notice: datasetNotice, + } = resolveMetricDataset(flags.dataset, flags.query); validateMetricDataset(dataset); validateMetricTimeWindow(flags["time-window"]); + if (datasetNotice) { + this.stderr.write(`${warning(`Tip: ${datasetNotice}`)}\n`); + } const triggers = parseJsonObjectList(flags.trigger, "trigger"); validateMetricTriggers(triggers); @@ -195,7 +203,7 @@ export const createCommand = buildCommand({ const body: Record = { name: flags.name, - query: flags.query, + query, aggregate: flags.aggregate, dataset, timeWindow: flags["time-window"], diff --git a/packages/cli/src/commands/alert/metrics/edit.ts b/packages/cli/src/commands/alert/metrics/edit.ts index 0d6637f5b6..b1aa3e6dd0 100644 --- a/packages/cli/src/commands/alert/metrics/edit.ts +++ b/packages/cli/src/commands/alert/metrics/edit.ts @@ -11,6 +11,7 @@ import { } from "../../../lib/api-client.js"; import { buildCommand, numberParser } from "../../../lib/command.js"; import { ValidationError } from "../../../lib/errors.js"; +import { warning } from "../../../lib/formatters/colors.js"; import { CommandOutput } from "../../../lib/formatters/output.js"; import { resolveOrgOptionalProjectFromArg } from "../../../lib/resolve-target.js"; import { @@ -18,6 +19,7 @@ import { normalizeProjectList, parseJsonObjectList, parseStatusFlag, + resolveMetricDataset, statusToMetricValue, validateMetricDataset, validateMetricTimeWindow, @@ -76,7 +78,8 @@ function validateMetricEditFlags(flags: EditFlags): void { function applyMetricCoreFields( body: Record, flags: EditFlags -): Record { +): string | undefined { + let notice: string | undefined; if (flags.name !== undefined) { body.name = flags.name; } @@ -93,15 +96,21 @@ function applyMetricCoreFields( body.aggregate = flags.aggregate; } if (flags.dataset !== undefined) { - const dataset = normalizeMetricDataset(flags.dataset); - validateMetricDataset(dataset); - body.dataset = dataset; + // Resolve against the effective query (the just-applied --query flag, or the + // rule's existing query) so a `transaction(s)` request routes to `spans` + // with `is_transaction:true` added even when --query is not also passed. + const currentQuery = typeof body.query === "string" ? body.query : ""; + const resolved = resolveMetricDataset(flags.dataset, currentQuery); + validateMetricDataset(resolved.dataset); + body.dataset = resolved.dataset; + body.query = resolved.query; + notice = resolved.notice; } if (flags["time-window"] !== undefined) { validateMetricTimeWindow(flags["time-window"]); body.timeWindow = flags["time-window"]; } - return body; + return notice; } function applyMetricOptionalFields( @@ -213,7 +222,7 @@ export const editCommand = buildCommand({ parse: String, optional: true, brief: - "Dataset: errors (error-events), sessions, events, spans, metrics", + "Dataset: errors (error-events), sessions, events, spans, metrics (transaction(s) routes to spans)", }, "time-window": { kind: "parsed", @@ -271,9 +280,12 @@ export const editCommand = buildCommand({ const body = { ...(await getMetricAlertRuleDocument(orgSlug, rule.id)), } as Record; - applyMetricCoreFields(body, flags); + const datasetNotice = applyMetricCoreFields(body, flags); applyMetricOptionalFields(body, flags); validateMetricBody(body, flags); + if (datasetNotice) { + this.stderr.write(`${warning(`Tip: ${datasetNotice}`)}\n`); + } const updated = await putMetricAlertRule(orgSlug, rule.id, body); yield new CommandOutput({ ...updated, diff --git a/packages/cli/src/commands/alert/mutation-utils.ts b/packages/cli/src/commands/alert/mutation-utils.ts index f4742c425c..8f70603392 100644 --- a/packages/cli/src/commands/alert/mutation-utils.ts +++ b/packages/cli/src/commands/alert/mutation-utils.ts @@ -17,10 +17,10 @@ const METRIC_DATASET_VALUES = new Set([ * Maps user-provided dataset aliases to canonical metric alert dataset values. * * Only aliases that resolve to a value already accepted by the metric alert API - * are listed here: the singular forms (`error` → `errors`) and the dashboard - * terminology for the error dataset (`error-events` → `errors`). This lets - * users copying from dashboard docs avoid a validation error without - * changing which dataset is sent. + * are listed here: the singular forms (`error` → `errors`, `span` → `spans`) + * and the dashboard terminology for the error dataset (`error-events` → + * `errors`). This lets users copying from dashboard docs avoid a validation + * error without changing which dataset is sent. * * Names that denote a *distinct* dataset in the metric alert path — e.g. * `tracemetrics`, `metricsenhanced`, `eap`, `events_analytics_platform` — are @@ -28,15 +28,30 @@ const METRIC_DATASET_VALUES = new Set([ * silently send the wrong dataset in the create/edit payload (the CLI's * canonical `metrics` is session/crash-rate, not trace metrics), with no * validation error to catch it. + * + * The deprecated `transaction`/`transactions` datasets are handled separately + * by {@link resolveMetricDataset} — they route to `spans` with an added + * `is_transaction:true` filter rather than a plain rename, so they are not + * listed here. */ const METRIC_DATASET_ALIASES: Record = { // Singular forms error: "errors", session: "sessions", metric: "metrics", + span: "spans", // Dashboard terminology for the error dataset "error-events": "errors", }; + +/** Deprecated dataset names that now route to `spans` with `is_transaction:true`. */ +const TRANSACTION_DATASET_NAMES = new Set(["transaction", "transactions"]); + +/** Query filter appended when migrating a `transaction(s)` request to `spans`. */ +const IS_TRANSACTION_FILTER = "is_transaction:true"; + +/** Splits a query string on runs of whitespace. */ +const QUERY_TOKEN_SEPARATOR = /\s+/; const METRIC_TIME_WINDOWS = new Set([ 1, 5, 10, 15, 30, 60, 120, 240, 360, 720, 1440, ]); @@ -209,6 +224,57 @@ export function normalizeMetricDataset(dataset: string): string { return METRIC_DATASET_ALIASES[lower] ?? lower; } +/** + * Result of resolving a `--dataset` value, accounting for the deprecated + * `transaction(s)` datasets that now route to `spans`. + */ +export type ResolvedMetricDataset = { + /** Canonical dataset to send to the API. */ + readonly dataset: string; + /** Query with `is_transaction:true` appended when routed off `transaction(s)`. */ + readonly query: string; + /** A gentle one-line nudge to show the user, or `undefined` when none applies. */ + readonly notice?: string; +}; + +/** + * Append the `is_transaction:true` filter to a query, unless it is already + * present. Keeps existing filters intact and avoids duplicate tokens. + */ +function appendIsTransactionFilter(query: string): string { + const trimmed = query.trim(); + const tokens = trimmed.length > 0 ? trimmed.split(QUERY_TOKEN_SEPARATOR) : []; + if (tokens.includes(IS_TRANSACTION_FILTER)) { + return trimmed; + } + return [...tokens, IS_TRANSACTION_FILTER].join(" "); +} + +/** + * Resolve a user-provided `--dataset` (and its accompanying `--query`) to the + * dataset/query actually sent to the API. + * + * The `transactions` dataset was removed from alerts; the same use cases are + * served by `spans` filtered to transaction-like spans. Rather than hard-fail a + * `transaction(s)` request, route it to `spans` and add `is_transaction:true` + * to the query so existing muscle memory keeps working, returning a `notice` + * that nudges the user toward the canonical form. + */ +export function resolveMetricDataset( + dataset: string, + query: string +): ResolvedMetricDataset { + const lower = dataset.trim().toLowerCase(); + if (TRANSACTION_DATASET_NAMES.has(lower)) { + return { + dataset: "spans", + query: appendIsTransactionFilter(query), + notice: `The '${lower}' dataset is no longer supported for alerts. Routing to the 'spans' dataset with '${IS_TRANSACTION_FILTER}' added to the query. Use --dataset spans directly to silence this notice.`, + }; + } + return { dataset: normalizeMetricDataset(dataset), query }; +} + /** Validate that `dataset` is one of the allowed Sentry metric alert dataset values. */ export function validateMetricDataset(dataset: string): void { const normalized = normalizeMetricDataset(dataset); diff --git a/packages/cli/test/commands/alert/metrics/create.test.ts b/packages/cli/test/commands/alert/metrics/create.test.ts index 400c13ffe3..470b565a7f 100644 --- a/packages/cli/test/commands/alert/metrics/create.test.ts +++ b/packages/cli/test/commands/alert/metrics/create.test.ts @@ -321,4 +321,48 @@ describe("alert metrics create", () => { projects: ["backend"], }); }); + + test("routes --dataset transactions to spans with is_transaction:true and a tip", async () => { + const context = createContext(); + resolveOrgSpy.mockResolvedValue({ org: "test-org" }); + createSpy.mockResolvedValue({ + id: "77", + name: "Metric Rule", + status: 0, + }); + const func = (await createCommand.loader()) as unknown as ( + this: unknown, + flags: CreateFlags, + arg: string + ) => Promise; + + await func.call( + context, + { + name: "Metric Rule", + query: "environment:prod", + aggregate: "p95(span.duration)", + dataset: "transactions", + "time-window": 5, + trigger: ['{"alertThreshold":100,"actions":[{"id":"notify"}]}'], + project: ["backend"], + "dry-run": false, + json: true, + }, + "test-org" + ); + + expect(createSpy).toHaveBeenCalledWith("test-org", { + name: "Metric Rule", + query: "environment:prod is_transaction:true", + aggregate: "p95(span.duration)", + dataset: "spans", + timeWindow: 5, + triggers: [{ alertThreshold: 100, actions: [{ id: "notify" }] }], + projects: ["backend"], + }); + expect(context.stderr.write).toHaveBeenCalledWith( + expect.stringContaining("is_transaction:true") + ); + }); }); diff --git a/packages/cli/test/commands/alert/metrics/edit.test.ts b/packages/cli/test/commands/alert/metrics/edit.test.ts index c409304c21..8b6f0d5563 100644 --- a/packages/cli/test/commands/alert/metrics/edit.test.ts +++ b/packages/cli/test/commands/alert/metrics/edit.test.ts @@ -232,4 +232,56 @@ describe("alert metrics edit", () => { expect.objectContaining({ query: "" }) ); }); + + test("routes --dataset transactions to spans with is_transaction:true and a tip", async () => { + const context = createContext(); + resolveSpy.mockResolvedValue({ org: "test-org" }); + getRuleSpy.mockResolvedValue(sampleRule); + getDocSpy.mockResolvedValue({ + id: "9", + name: "Metric Rule", + status: 0, + query: "environment:prod", + aggregate: "count()", + dataset: "errors", + timeWindow: 5, + triggers: [{ alertThreshold: 100, actions: [{ id: "notify" }] }], + }); + putSpy.mockResolvedValue({ + id: "9", + name: "Metric Rule", + status: 0, + query: "environment:prod is_transaction:true", + aggregate: "count()", + dataset: "spans", + timeWindow: 5, + triggers: [{ alertThreshold: 100, actions: [{ id: "notify" }] }], + }); + const func = (await editCommand.loader()) as unknown as ( + this: unknown, + flags: EditFlags, + arg: string + ) => Promise; + + await func.call( + context, + { + dataset: "transactions", + json: true, + }, + "test-org/9" + ); + + expect(putSpy).toHaveBeenCalledWith( + "test-org", + "9", + expect.objectContaining({ + dataset: "spans", + query: "environment:prod is_transaction:true", + }) + ); + expect(context.stderr.write).toHaveBeenCalledWith( + expect.stringContaining("is_transaction:true") + ); + }); }); diff --git a/packages/cli/test/commands/alert/mutation-utils.test.ts b/packages/cli/test/commands/alert/mutation-utils.test.ts index e92f1d14c4..79fa4dc9b4 100644 --- a/packages/cli/test/commands/alert/mutation-utils.test.ts +++ b/packages/cli/test/commands/alert/mutation-utils.test.ts @@ -6,6 +6,7 @@ import { parseJsonObjectList, parseMatchMode, parseStatusFlag, + resolveMetricDataset, statusToMetricValue, triggerLogicType, validateIssueRuleArrays, @@ -214,6 +215,7 @@ describe("normalizeMetricDataset", () => { ["error", "errors"], ["session", "sessions"], ["metric", "metrics"], + ["span", "spans"], ["error-events", "errors"], ])('maps alias "%s" to "%s"', (input, expected) => { expect(normalizeMetricDataset(input)).toBe(expected); @@ -250,6 +252,49 @@ describe("normalizeMetricDataset", () => { }); }); +describe("resolveMetricDataset", () => { + test("passes canonical datasets through with the query untouched", () => { + expect(resolveMetricDataset("spans", "environment:prod")).toEqual({ + dataset: "spans", + query: "environment:prod", + }); + }); + + test("resolves aliases without a notice", () => { + expect(resolveMetricDataset("span", "environment:prod")).toEqual({ + dataset: "spans", + query: "environment:prod", + }); + }); + + test.each([ + "transaction", + "transactions", + "Transactions", + " TRANSACTION ", + ])('routes "%s" to spans and adds is_transaction:true with a notice', (input) => { + const result = resolveMetricDataset(input, "environment:prod"); + expect(result.dataset).toBe("spans"); + expect(result.query).toBe("environment:prod is_transaction:true"); + expect(result.notice).toContain("spans"); + expect(result.notice).toContain("is_transaction:true"); + }); + + test("adds the filter to an empty query", () => { + const result = resolveMetricDataset("transactions", ""); + expect(result.dataset).toBe("spans"); + expect(result.query).toBe("is_transaction:true"); + }); + + test("does not duplicate an existing is_transaction:true filter", () => { + const result = resolveMetricDataset( + "transactions", + "environment:prod is_transaction:true" + ); + expect(result.query).toBe("environment:prod is_transaction:true"); + }); +}); + describe("validateMetricDataset", () => { const valid = ["errors", "sessions", "events", "spans", "metrics"]; @@ -263,6 +308,7 @@ describe("validateMetricDataset", () => { "error", "error-events", "METRIC", + "span", ])('passes for alias "%s"', (dataset) => { expect(() => validateMetricDataset(dataset)).not.toThrow(); }); From 6e63cd9e4ccc5584ec1ba42db98bdc58b694f762 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 4 Sep 2026 09:53:41 +0000 Subject: [PATCH 3/3] chore: regenerate docs --- .../skills/sentry-cli/references/agent-conversation.md | 2 +- .../sentry-cli/skills/sentry-cli/references/dashboard.md | 2 +- .../plugins/sentry-cli/skills/sentry-cli/references/event.md | 2 +- .../sentry-cli/skills/sentry-cli/references/explore.md | 2 +- .../sentry-cli/skills/sentry-cli/references/feedback.md | 2 +- .../plugins/sentry-cli/skills/sentry-cli/references/issue.md | 4 ++-- .../plugins/sentry-cli/skills/sentry-cli/references/log.md | 2 +- .../plugins/sentry-cli/skills/sentry-cli/references/replay.md | 2 +- .../plugins/sentry-cli/skills/sentry-cli/references/span.md | 2 +- .../plugins/sentry-cli/skills/sentry-cli/references/trace.md | 4 ++-- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/agent-conversation.md b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/agent-conversation.md index e7fa5ccd3b..a9fff08682 100644 --- a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/agent-conversation.md +++ b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/agent-conversation.md @@ -18,7 +18,7 @@ List recent agent conversations **Flags:** - `-n, --limit - Number of conversations (1-1000) - (default: "25")` - `-q, --query - Search query` -- `-t, --period - Time range: "7d", "2026-07-01..2026-08-01", ">=2026-07-01" - (default: "7d")` +- `-t, --period - Time range: "7d", "2026-08-01..2026-09-01", ">=2026-08-01" - (default: "7d")` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-c, --cursor - Navigate pages: "next", "prev", "first" (or raw cursor string)` diff --git a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md index 305f9c494a..e83b705c9b 100644 --- a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md +++ b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md @@ -42,7 +42,7 @@ View a dashboard - `-w, --web - Open in browser` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-r, --refresh - Auto-refresh interval in seconds (default: 60, min: 10)` -- `-t, --period - Time range: "7d", "2026-07-01..2026-08-01", ">=2026-07-01"` +- `-t, --period - Time range: "7d", "2026-08-01..2026-09-01", ">=2026-08-01"` - `--renderer - Graphics renderer (defaults to auto; falls back to auto when unavailable) - (default: "auto")` **Examples:** diff --git a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/event.md b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/event.md index e831b344ec..adce7c3e29 100644 --- a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/event.md +++ b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/event.md @@ -37,7 +37,7 @@ List events for an issue - `-n, --limit - Number of events (1-1000) - (default: "25")` - `-q, --query - Search query (Sentry search syntax)` - `--full - Include full event body (stacktraces)` -- `-t, --period - Time range: "7d", "2026-07-01..2026-08-01", ">=2026-07-01" - (default: "7d")` +- `-t, --period - Time range: "7d", "2026-08-01..2026-09-01", ">=2026-08-01" - (default: "7d")` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-c, --cursor - Navigate pages: "next", "prev", "first" (or raw cursor string)` diff --git a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/explore.md b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/explore.md index a0ab45d3f6..9e2338cfcd 100644 --- a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/explore.md +++ b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/explore.md @@ -24,7 +24,7 @@ Query aggregate event data (Explore) - `-s, --sort - Sort field (prefix with - for desc, e.g., "-count()")` - `-e, --environment ... - Environment filter (repeatable, comma-separated)` - `-n, --limit - Number of rows (1-1000) - (default: "25")` -- `-t, --period - Time range: "7d", "2026-07-01..2026-08-01", ">=2026-07-01" - (default: "24h")` +- `-t, --period - Time range: "7d", "2026-08-01..2026-09-01", ">=2026-08-01" - (default: "24h")` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-c, --cursor - Navigate pages: "next", "prev", "first" (or raw cursor string)` diff --git a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/feedback.md b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/feedback.md index ae6dd5281d..0e5397ec8c 100644 --- a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/feedback.md +++ b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/feedback.md @@ -19,7 +19,7 @@ List and search User Feedback - `--status - Mailbox: unresolved, resolved, spam, or all - (default: "unresolved")` - `-n, --limit - Number of feedback items (1-1000) - (default: "25")` - `-q, --query - Search query (Sentry issue search syntax)` -- `-t, --period - Time range: "7d", "2026-07-01..2026-08-01", ">=2026-07-01" - (default: "14d")` +- `-t, --period - Time range: "7d", "2026-08-01..2026-09-01", ">=2026-08-01" - (default: "14d")` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-c, --cursor - Navigate pages: "next", "prev", "first" (or raw cursor string)` diff --git a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/issue.md b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/issue.md index 00386311d1..d1c554d385 100644 --- a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/issue.md +++ b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/issue.md @@ -19,7 +19,7 @@ List issues in a project - `-q, --query - Search query (Sentry syntax, implicit AND, no OR operator)` - `-n, --limit - Maximum number of issues to list - (default: "25")` - `-s, --sort - Sort by: recommended, date, new, freq, user (default: recommended on sentry.io, else date)` -- `-t, --period - Time range: "7d", "2026-07-01..2026-08-01", ">=2026-07-01" - (default: "90d")` +- `-t, --period - Time range: "7d", "2026-08-01..2026-09-01", ">=2026-08-01" - (default: "90d")` - `-c, --cursor - Pagination cursor (use "next" for next page, "prev" for previous)` - `--compact - Single-line rows for compact output (auto-detects if omitted)` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` @@ -92,7 +92,7 @@ List events for a specific issue - `-n, --limit - Number of events (1-1000) - (default: "25")` - `-q, --query - Search query (Sentry search syntax)` - `--full - Include full event body (stacktraces)` -- `-t, --period - Time range: "7d", "2026-07-01..2026-08-01", ">=2026-07-01" - (default: "7d")` +- `-t, --period - Time range: "7d", "2026-08-01..2026-09-01", ">=2026-08-01" - (default: "7d")` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-c, --cursor - Navigate pages: "next", "prev", "first" (or raw cursor string)` diff --git a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/log.md b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/log.md index 3cffc06556..28d0d0eabb 100644 --- a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/log.md +++ b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/log.md @@ -19,7 +19,7 @@ List logs from a project - `-n, --limit - Number of log entries (1-1000) - (default: "100")` - `-q, --query - Filter query (e.g., "severity:error", "project:backend", "project:[a,b]")` - `-f, --follow - Stream logs (optionally specify poll interval in seconds)` -- `-t, --period - Time range: "7d", "2026-07-01..2026-08-01", ">=2026-07-01"` +- `-t, --period - Time range: "7d", "2026-08-01..2026-09-01", ">=2026-08-01"` - `-s, --sort - Sort order: "newest" (default) or "oldest" - (default: "newest")` - `--fresh - Bypass cache, re-detect projects, and fetch fresh data` diff --git a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/replay.md b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/replay.md index 4b89e399f5..c444ffaa2a 100644 --- a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/replay.md +++ b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/replay.md @@ -20,7 +20,7 @@ List recent Session Replays - `-q, --query - Search query (Sentry replay search syntax)` - `-e, --environment ... - Filter by environment (repeatable, comma-separated)` - `-s, --sort - Sort by: date, oldest, duration, errors, activity, or a raw replay sort field - (default: "date")` -- `-t, --period - Time range: "7d", "2026-07-01..2026-08-01", ">=2026-07-01" - (default: "7d")` +- `-t, --period - Time range: "7d", "2026-08-01..2026-09-01", ">=2026-08-01" - (default: "7d")` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-c, --cursor - Navigate pages: "next", "prev", "first" (or raw cursor string)` diff --git a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/span.md b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/span.md index f9dc233099..ff4cb412a3 100644 --- a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/span.md +++ b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/span.md @@ -19,7 +19,7 @@ List spans in a project or trace - `-n, --limit - Number of spans (<=1000) - (default: "25")` - `-q, --query - Filter spans (e.g., "op:db", "project:backend", "project:[cli,api]")` - `-s, --sort - Sort order: date, duration - (default: "date")` -- `-t, --period - Time range: "7d", "2026-07-01..2026-08-01", ">=2026-07-01" - (default: "7d")` +- `-t, --period - Time range: "7d", "2026-08-01..2026-09-01", ">=2026-08-01" - (default: "7d")` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-c, --cursor - Navigate pages: "next", "prev", "first" (or raw cursor string)` diff --git a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/trace.md b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/trace.md index 0072977777..53bb750bbf 100644 --- a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/trace.md +++ b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/trace.md @@ -19,7 +19,7 @@ List recent traces in a project - `-n, --limit - Number of traces (1-1000) - (default: "25")` - `-q, --query - Search query (Sentry search syntax)` - `-s, --sort - Sort by: date, duration - (default: "date")` -- `-t, --period - Time range: "7d", "2026-07-01..2026-08-01", ">=2026-07-01" - (default: "7d")` +- `-t, --period - Time range: "7d", "2026-08-01..2026-09-01", ">=2026-08-01" - (default: "7d")` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-c, --cursor - Navigate pages: "next", "prev", "first" (or raw cursor string)` @@ -91,7 +91,7 @@ View logs associated with a trace **Flags:** - `-w, --web - Open trace in browser` -- `-t, --period - Time range: "7d", "2026-07-01..2026-08-01", ">=2026-07-01" - (default: "14d")` +- `-t, --period - Time range: "7d", "2026-08-01..2026-09-01", ">=2026-08-01" - (default: "14d")` - `-n, --limit - Number of log entries (<=1000) - (default: "100")` - `-q, --query - Filter query (e.g., "severity:error", "project:backend", "project:[a,b]")` - `-s, --sort - Sort order: "newest" (default) or "oldest" - (default: "newest")`