Skip to content

[3/10] Scan samples across a time window and by user#50

Open
oliver-kriska wants to merge 4 commits into
appsignal:mainfrom
oliver-kriska:sample-window-and-user
Open

[3/10] Scan samples across a time window and by user#50
oliver-kriska wants to merge 4 commits into
appsignal:mainfrom
oliver-kriska:sample-window-and-user

Conversation

@oliver-kriska

Copy link
Copy Markdown

🤖 AI-generated, shared for review. This PR was written by Claude Code (AI). Before opening it I ran the project's gates locally — cargo +1.94.0 fmt --check, clippy -D warnings, and the full test suite — all green — and kept the change strictly additive (existing commands, output, and behaviour are untouched). Anything I couldn't confirm against the live API without a token is called out explicitly below. Feedback very welcome.

The forensic question is usually "what happened between T1 and T2" or "what did
this user hit", not "show me one incident". samples list previously required
an incident; it can now scan a window across incidents instead.

  • Omitting --incident and passing --start/--end scans recent incidents and
    collects the samples in that window. The GraphQL incidents query has no
    time-range filter, so scan_samples_in_window lists recent incidents (capped
    by --limit, default 20) and applies the window at the sample level, where
    samples(start:, end:) is supported. Anomaly/log incidents have no samples
    and are skipped.
  • --user keeps only samples whose user identity matches the given id, email,
    or substring (case-insensitive). Identity is resolved from the sample's
    overview/attributes and, failing that, its session/custom/params JSON, via a
    shared sample_analysis::sample_user helper that also feeds the digest.
  • --namespaces filters which incidents are scanned. --user also applies in
    single-incident mode. Existing single-incident samples list is unchanged.

📚 Part 3 of a 10-PR stack — builds on #49. Because cross-fork PRs target main, the diff here currently includes the ancestor commits; it shrinks to just this feature once #49 merges. Easiest to review in stack order (or review only the latest commit).

oliver-kriska and others added 4 commits June 18, 2026 17:27
Bring the AGENTS.md architecture section up to date with the current `src/`
tree (the `commands/logs/` split, `dashboards.rs`, `triggers.rs`, `about.rs`,
`project.rs`, `output.rs`, `error.rs`, `telemetry.rs`, `version_check.rs`,
`client_headers.rs`) and document two conventions that were implicit in the
code: how command output and user-facing errors flow through the `output`
module and `CliError`, and the end-to-end checklist for adding a new command.

Add a minimal CLAUDE.md that points at AGENTS.md as the source of truth, so
Claude Code sessions pick up the same conventions.

[skip changeset]

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`incidents show` only exposes incident-level aggregates. Add a `samples`
command that fetches the underlying transaction sample data behind an
incident — action, duration, queue time, params, and the exception backtrace —
which is what most incident triage actually works from.

- `samples show` fetches a single sample: the latest, one by id (--sample-id),
  or the one closest to a timestamp (--at). Targeting a timestamp matters
  because the "latest" sample often hides the one that triggered the incident.
- `samples list` returns an incident's samples, optionally narrowed with
  --start/--end/--limit.
- Both accept a full AppSignal incident or sample URL (or a bare sample id) as
  a positional argument, or the explicit --incident plus the usual
  --app-id/--app/--environment/--org flags, and both support --output json.

A new `appsignal_url` module parses every URL shape, matching
`/samples/timestamp/<ISO>` before a generic sample-id segment and URL-decoding
the timestamp. The fetch uses one GraphQL query that spreads both
`... on PerformanceIncident` and `... on ExceptionIncident`, and the sample
type is taken from the returned `__typename` rather than inferred from the URL
path, so an exception URL can never be miscategorised as performance.

The `timestamp`/`start`/`end` GraphQL variables are declared `DateTime` even
though ISO-8601 strings are sent; declaring them `String` returns an HTTP 400
type mismatch. Regression tests assert the `DateTime` declaration is present.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`samples show` printed a flat field dump; raw samples are large and mostly
noise. Render an analysed digest by default instead — the questions a responder
actually asks: what ran, how slow, who hit it, which queries dominated, was
there an N+1, and for errors what blew up and what led to it.

- New `sample_analysis` module distils a `Sample` into a `SampleAnalysis`:
  request overview + acting user (pulled from overview/attributes), a per-group
  performance breakdown (count / total / % / avg), the slowest events, a
  database rollup, slow queries (from timeline payload bodies), N+1 suspects
  (repeated timeline `digest`s, plus the `hasNPlusOne` flag), and for errors the
  exception, backtrace, causes, and breadcrumbs. It is pure and unit-tested on
  synthetic samples.
- `samples show` renders the digest by default, `--output json` adds the
  structured `analysis` object alongside the raw sample, and `--raw` prints the
  unprocessed sample. `samples list` is unchanged.
- The sample GraphQL selection gains the fields the analysis needs (timeline,
  group durations/allocations, params/session/custom data, breadcrumbs); only
  fields with known scalar types are selected to keep deserialization robust.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The forensic question is usually "what happened between T1 and T2" or "what did
this user hit", not "show me one incident". `samples list` previously required
an incident; it can now scan a window across incidents instead.

- Omitting `--incident` and passing `--start`/`--end` scans recent incidents and
  collects the samples in that window. The GraphQL `incidents` query has no
  time-range filter, so `scan_samples_in_window` lists recent incidents (capped
  by `--limit`, default 20) and applies the window at the sample level, where
  `samples(start:, end:)` is supported. Anomaly/log incidents have no samples
  and are skipped.
- `--user` keeps only samples whose user identity matches the given id, email,
  or substring (case-insensitive). Identity is resolved from the sample's
  overview/attributes and, failing that, its session/custom/params JSON, via a
  shared `sample_analysis::sample_user` helper that also feeds the digest.
- `--namespaces` filters which incidents are scanned. `--user` also applies in
  single-incident mode. Existing single-incident `samples list` is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@oliver-kriska oliver-kriska changed the title Scan samples across a time window and by user [3/10] Scan samples across a time window and by user Jun 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants