Skip to content

feat(cli): Complete dataset fields and aggregates in shell completion - #1782

Merged
BYK merged 2 commits into
mainfrom
feat/cli-dataset-field-completion
Nov 1, 2026
Merged

BYK merged 2 commits into
mainfrom
feat/cli-dataset-field-completion

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

This follows up #1759. The field and aggregate flags that were still text now complete the fields of the dataset that --dataset names, plus its aggregate functions.

Now completing

  • explore --field / --sort: fields, then fn(). Once a function name is typed in full, it also completes fn(field) for every field, so p95<TAB> inserts p95( and lists p95(span.duration) and the rest. p95(span.d<TAB> finishes the call.
  • alert metrics create|edit --aggregate: the same calls, without bare fields.
  • dashboard widget add|edit --query / --sort: the shorthand form, fn and then fn:field. These used to complete every dataset's function names. Now they complete only the names of the widget's dataset, with the aliases (spm, tpm, …) that resolve to them.
  • dashboard widget add|edit --group-by: fields.

Where the values come from

  • Spans, logs, and trace metrics: attribute keys, as the new cache kinds span-attribute, log-attribute, and metric-attribute. They are seeded from GET /organizations/{org}/trace-items/attributes/. The seed table can now also match on query parameters, so the dataset parameter decides which kind a response fills. The stored value is the attribute's key, because the API queries by key (tags[foo,number] for custom non-string attributes). I checked this against as_attribute_key in src/sentry/api/endpoints/organization_trace_item_attributes.py in getsentry/sentry. The description is the attribute type, with the display name in front of it when the name differs from the key.
  • Errors: the cached issue tag keys (tag-key).
  • Replays: the fixed lists the CLI already validates against. --field uses listSupportedReplayFields() and --sort uses REPLAY_SORT_FIELDS.
  • Aggregate functions: aggregateFunctionsOf in types/dashboard.ts. validateAggregateNames now uses the same function, and it replaces AGGREGATE_FUNCTION_NAMES.

How it works

  • A new DatasetCompletion tag on the parser describes what completes for each dataset, the command's --dataset aliases, and its default dataset.
  • The generator writes this per flag. To keep the JSON smaller, it is keyed only by --name; aliases are mapped back to it at completion time.
  • The completer now gets flagValues from the words typed before the cursor, so it sees --dataset spans, -d spans, and --dataset=spans.
  • With no --dataset, a command falls back to its default: errors for explore, spans for widget add. widget edit keeps the existing widget's dataset, and alert has no default, so those two complete every dataset.
  • The fast path still makes no API calls and loads nothing new.

Still completing nothing

  • A descending -field: a word that starts with - completes flag names.
  • Trace metric aggregates (sum(value,metric,type,unit)): they name a metric rather than a field. Only trace metric fields complete.
  • Crash-rate alerts (--dataset metrics|sessions) and issue / preprod-app-size widgets.
  • Spans, logs, and trace metrics complete nothing until sentry attribute list has run for the org, since that command is the only caller of the attributes endpoint. Errors complete nothing until some issue details have been fetched.

Open questions

  • The CLI has no local validator for alert --aggregate, so it reuses the widget function lists: errors functions for errors/events, spans functions for spans/logs. Should alerts get their own, narrower list?
  • Should explore (or a light background call) also seed attribute keys, so completion does not depend on running attribute list first?
  • Each kind keeps at most 500 cached entries per org, the existing limit. Orgs with many custom span attributes will only see the most recently seen 500.

via @BYK.

--

View Session in Sentry

sentry-junior Bot and others added 2 commits November 1, 2026 16:43
`explore --field`/`--sort`, `dashboard widget add|edit --group-by`/
`--query`/`--sort`, and `alert metrics create|edit --aggregate` now
complete the fields of the dataset `--dataset` names, and its aggregate
functions.

Attribute keys are cached per org and dataset (`span-attribute`,
`log-attribute`, `metric-attribute`) from the trace-items attributes
endpoint, matched on its `dataset` query parameter. Errors use the cached
issue tag keys; replays use the fixed fields the CLI renders. Aggregate
functions come from `aggregateFunctionsOf`, now shared with
`validateAggregateNames`.

The completer reads `--dataset` (by name, alias, or `--dataset=`) from
the words typed before it; commands without a default, like
`dashboard widget edit`, complete every dataset.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
…d-completion

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
@vercel

vercel Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
sentry-local Ready Ready Preview Nov 1, 2026 4:46pm UTC

Request Review

@BYK
BYK merged commit a4c1e58 into main Nov 1, 2026
26 checks passed
@BYK
BYK deleted the feat/cli-dataset-field-completion branch November 1, 2026 17:01
@sentry-junior

sentry-junior Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor Author

This merged before the follow-ups to your answers landed, so they're in #1785:

  • Alert --aggregate now has its own per-dataset lists, taken from the getsentry/sentry validator. @sentry/api has no enum for them. Sources are cited in the PR.
  • Inside fn(…), fields match by segment, with fuzzy matching only within a segment: span.d → span.duration, never span.op.
  • I didn't add a background attribute-key fetch. Explore gets no keys from calls it already makes, and the endpoint returns ~6k keys (~1 MB raw, ~0.45 s) in name order, so the 500-key cache would keep the wrong ones. Numbers are in feat(cli): Narrow alert aggregates and match fields by segment #1785.
  • The 500 cap is unchanged.

BYK added a commit that referenced this pull request Nov 1, 2026
Follow-up to #1782, which was merged before these changes landed. It
implements the answers to that PR's design questions.

## Alert `--aggregate` completes only what metric alerts accept

`alert metrics create|edit --aggregate` no longer reuses the widget
function lists. Each `--dataset` gets the aggregates the Sentry backend
accepts for it:

| `--dataset` | Completes |
| --- | --- |
| `spans` (and `transaction(s)`) | `EAP_FUNCTIONS`: `count`,
`count_unique`, `avg`, `p50`–`p99`, `p100`, `max`, `min`, `sum`, `epm`,
`eps`, `failure_count`, `failure_rate`, `apdex`, `user_misery`, called
on cached span attribute keys |
| `logs` | the `EAP_FUNCTIONS` that logs define: no formulas and no
`p100`, called on cached log attribute keys |
| `errors` / `events` | `count()`, `count_unique(user)`,
`count_unique(release)`, `count_unique(dist)` |
| `metrics` / `sessions` (crash rate) |
`percentage(sessions_crashed,sessions)`,
`percentage(users_crashed,users)` |

A new `DatasetFields.aggregates` holds whole aggregates that aren't a
function of one field.

Sources (getsentry/sentry master @ `063a6cb2`):
- `src/sentry/snuba/snuba_query_validator.py` `_validate_aggregate`
(L253) calls `check_aggregate_column_support` and
`translate_aggregate_field`. For EAP it skips the SnQL query check
(L361).
- `src/sentry/incidents/logic.py`:
- `EAP_FUNCTIONS` (L140). For these functions,
`get_column_from_aggregate` (L179) skips column validation on EAP
datasets.
- `SUPPORTED_COLUMNS` and `TRANSLATABLE_COLUMNS` (L118, L124).
`check_aggregate_column_support` (L214) checks columns against these
lists, so errors can't take arbitrary tag keys.
- `src/sentry/snuba/entity_subscription.py`:
- `PerformanceSpansEAPRpcEntitySubscription.build_rpc_request` resolves
the aggregate with `Spans` or `OurLogs` definitions, depending on the
event type.
- `CRASH_RATE_ALERT_AGGREGATE_RE` (L59) and
`determine_crash_rate_alert_entity` restrict crash rate to the
percentages above.
- `src/sentry/search/eap/spans/aggregates.py` and `formulas.py` define
every `EAP_FUNCTIONS` entry.
`src/sentry/search/eap/ourlogs/aggregates.py` defines no `p100`, and
`ourlogs/definitions.py` sets `formulas={}`.
- The UI agrees: `static/app/views/detectors/datasetConfig/errors.tsx`
offers `count` and `count_unique(user)`, and `releases.tsx` maps
`crash_free_rate(session|user)` to the two percentages.

**`@sentry/api` has no list to use.** 0.428.0 types detector
`data_sources` as `Array<unknown>`. The only aggregates it mentions are
in prose examples from
`workflow_engine/endpoints/validators/api_docs_help_text.py`, and those
are out of date: they list `percentile(measurements.lcp,x)`, but EAP
spans don't define `percentile`. Exposing the aggregates upstream would
be worthwhile. The natural form is an `aggregate` schema on the
`SnubaQueryValidator` data source, with an enum or pattern per
`dataset`/`eventTypes`. Clients could then stop copying `EAP_FUNCTIONS`
and the crash-rate regex.

Caveat: the errors list is the practical set, not an exhaustive
allowlist. Errors accept any Discover function whose column is empty or
in the column allowlist, provided the test query runs. Completion offers
what the UI offers, plus the other allowed columns.

## Fields match one `.` segment at a time

`fuzzyMatchSegments` matches the Nth typed segment against the
candidate's Nth segment, using `fuzzyMatch` search mode. A candidate
must have a segment for each typed one, and may have more after them.
So:
- `avg(span.d` → `avg(span.duration)`, never `avg(span.op)`
- `avg(span.duraton` → `avg(span.duration)` (typo within the segment)
- `span` → every `span.*`

This applies to every field completion (`explore --field/--sort`, widget
`--group-by/--query/--sort`, alert `--aggregate`), both bare and inside
`fn(` or `fn:`. Field completion now matches values only. It no longer
matches attribute type labels such as `number`.

## Background attribute-key fetch for explore: not added

`explore` doesn't get attribute keys from any call it already makes.
`/events/` meta covers only the requested fields, and the error path
(`toExploreFieldError`) fetches nothing. A separate fetch costs too much
for what the cache would keep. I measured `GET
/organizations/sentry/trace-items/attributes/` on sentry.io (3 runs
each, curl, gzip):

| dataset | keys | gzip | raw JSON | latency |
| --- | --- | --- | --- | --- |
| spans | 6,007 | ~72 KB | ~1.0 MB | 0.40–0.48 s |
| logs | 6,114 | ~51 KB | ~0.97 MB | 0.45–0.55 s |
| tracemetrics | 963 | ~10 KB | ~115 KB | 0.29–0.70 s |
| spans, `per_page=100` | ≤100 per type | ~5 KB | — | 0.30–2.2 s |

The cache keeps 500 keys per kind per org. The server returns keys in
name order, so the first rows are keys like `0`, `1`, `AGP_VERSION`, and
the kept 500 would miss `span.*`. A small page has the same problem. The
fetch also adds a request that the process must either wait for before
exiting or abandon. `sentry attribute list` still seeds the cache.

The 500 cap is unchanged.

## Checks

- `pnpm exec tsc --noEmit` (packages/cli): pass
- `pnpm run lint`: pass
- `vitest run` on `test/lib/complete.test.ts`, `test/lib/fuzzy.test.ts`,
`test/lib/db/completion-cache.test.ts`,
`test/lib/completion-coverage.test.ts`,
`test/lib/api/completion-seed.test.ts`, `test/lib/introspect.test.ts`,
`test/commands/alert`, `test/commands/explore.test.ts`,
`test/commands/dashboard`: 28 files, 659 tests, all pass
- The full `test/lib` suite didn't finish within the sandbox's 15-minute
limit, so it wasn't run to completion.

<!-- junior-request-attribution:start -->
via @BYK.
<!-- junior-request-attribution:end -->

<!-- junior-session-footer:start -->
<!-- junior-conversation-id:agent%3Ad44a6d899e425504b37476161fdb6ebe -->

--

[View Session in
Sentry](https://sentry.sentry.io/explore/conversations/agent%3Ad44a6d899e425504b37476161fdb6ebe/?project=4510944073809921)

<!-- junior-session-footer:end -->

Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
Co-authored-by: BYK <126780+BYK@users.noreply.github.com>
sentry-junior Bot added a commit that referenced this pull request Nov 2, 2026
Resolve the widget/add.ts import conflict by keeping both
DashboardWidgetChangeOutputSchema (output schema) and DEFAULT_WIDGET_TYPE
(dataset-field completers from #1782).

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>

This branch was successfully deployed

1 active deployment
Preview — 145caaa1 Deployed Nov 1, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: medium PR risk score: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant