Skip to content

feat(time-series-aggregations): add the time-series-aggregations command group - #281

Open
jochil wants to merge 12 commits into
mainfrom
sc-600-add-tsa-support
Open

feat(time-series-aggregations): add the time-series-aggregations command group#281
jochil wants to merge 12 commits into
mainfrom
sc-600-add-tsa-support

Conversation

@jochil

@jochil jochil commented Sep 4, 2026

Copy link
Copy Markdown
Member

Adds dash0 time-series-aggregations (alias tsa) with the five standard CRUD subcommands, plus kind: Dash0TimeSeriesAggregation in apply and apply --since.

Closes #280. CLI slice of SC-481 (SC-600).

Three things that differ from other asset kinds

  • The origin label is required, and it is the only upsert key. The API rejects an aggregation that has no origin, so there is no server-assigned id to fall back to and the CLI never sends a POST. A missing origin fails before any API call.
  • Every endpoint needs the org admin role. That is stricter than any other asset type. The roundtrip scripts check the role first and skip when the token does not have it.
  • Origins are unique per org, but each aggregation lives in one dataset. So the same document cannot go to two datasets. The API answers with a bare 400 that names neither constraint, so the CLI adds a hint explaining why and suggests a per-dataset origin.

Two doc fixes found along the way

The idempotent-delete rule claimed that a missing asset errors without --force. It does not. dashboards, check-rules, views, and spam-filters all exit 0 for an id that does not exist, because those endpoints answer 2xx instead of 404. The rule now says so.

The identifier table also linked to a heading that does not exist.

Also in here

Two refactors that came out of reviewing the above.

The cross-dataset advice now goes through the \nHint: convention, so agent mode shows it in the hint field instead of leaving that field empty.

All nine update commands now share one update-key resolver, replacing nine copies that had drifted apart. Same wording everywhere, and it names only the identifiers the file really carries.

One thing #280 asks for that this does not do

#280 wants tsa delete <missing> to exit non-zero with a clean "not found". That needs a preflight GET, and I left it out deliberately.

I checked the four sibling endpoints first. Deleting a missing id without --force exits 0 on dashboards, check-rules, views, and spam-filters, because they answer 2xx. Adding a preflight here would make time series aggregations the only asset type that errors on a missing id, which is a worse inconsistency than the one it fixes. IsAlreadyDeleted stays wired, so this is already right if the API starts returning 404.

Everything else in #280 is done.

Verification

make lint, make test-unit, and make test-integration all pass, and every commit in the branch builds on its own.

Tested live against dash0-development with an admin token: create, reapply as a no-op, list, get, get -o yaml then apply as a no-op, a changed interval showing the right diff, the cross-dataset hint, and delete. A 58-case manual pass came back with no failures. Both roundtrip scripts pass with an admin token and skip without one.

@jochil
jochil force-pushed the sc-600-add-tsa-support branch 2 times, most recently from 495e1ce to 2e64850 Compare September 9, 2026 09:56
@jochil
jochil marked this pull request as ready for review September 9, 2026 12:20
@jochil
jochil requested a review from a team as a code owner September 9, 2026 12:20
This brings in the time series aggregation client. The Nix vendorHash changes in
the same commit, because it has to change whenever go.sum does.
…ands

Every `update` command carried its own copy of the same logic: take the id from
the argument or from the file, and reject an argument that matches neither. The
copies had drifted, so the same mistake produced a different message depending
on which asset kind you hit it with.

One helper in internal/asset now answers that for all of them. The wording is
the same everywhere, and it names only the identifiers the file really carries.
Both `create` and `apply` call this helper, so the two paths cannot drift apart.

Three behaviors are specific to this asset kind. The origin label is required
and is the only upsert key, so every write is a PUT to the origin and the CLI
never sends a POST. The diff clears the id, which no hand-written file ever
carries. And a reused origin gets an explanation, because the API answers a
cross-dataset collision with a bare 400 that names neither constraint.
Adds `dash0 time-series-aggregations` (alias `tsa`) with the five standard
subcommands. It is dataset-scoped, and every endpoint needs the org admin role.
A document without the origin label fails during validation, before the run
applies anything, because there is no id to fall back to.
…ests

Both scripts check for the admin role first and skip cleanly without it, so they
still pass on an ordinary token.
…e skill topic

This also corrects two existing docs. The idempotent-delete rule claimed that a
missing asset errors without `--force`. It does not, because those endpoints
answer 2xx. And the identifier table linked to a heading that does not exist.
@jochil
jochil force-pushed the sc-600-add-tsa-support branch from 2e64850 to 68b6d00 Compare September 9, 2026 12:28
fmt.Printf("Dataset: %s\n", dash0api.GetTimeSeriesAggregationDataset(aggregation))
fmt.Printf("Origin: %s\n", origin(aggregation))
fmt.Printf("Interval: %s\n", interval(aggregation))
fmt.Printf("Enabled: %t\n", aggregation.Spec.Enabled)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: GetTimeSeriesAggregation returns (nil, nil) on a 200 with a non-JSON Content-Type, so this panics. Every other accessor in the package guards it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

runGet now returns an error when the client hands back a nil aggregation. I also added a regression test for this

var flags asset.FileInputFlags

cmd := &cobra.Command{
Use: "update [origin-or-id] -f <file>",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: <origin-or-id> only works for a file exported via tsa get -o yaml. Origin is mandatory and ids are server-assigned, so ResolveUpdateKey takes the Origin != "" && ID == "" branch and rejects an id copied from tsa list. Long states the real rule; Use and the example at line 32 don't.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch. Updated Use and Long

Comment thread docs/commands.md Outdated
@@ -692,7 +692,7 @@ $ echo $?
```

Without `--force`, a missing asset surfaces as a non-zero exit with a "not found" error.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: contradicts the note this PR adds to cli-naming-conventions.md. This file is the copy synced to dash0.com/docs.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I corrected this in cli-naming-conventions.md

# Origins are unique per organization while each aggregation belongs to one
# dataset, so this is the error a user hits the first time they point one
# asset directory at a second dataset.
OTHER_DATASET="${DASH0_TSA_OTHER_DATASET:-default}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (non-blocking): if the API accepts the cross-dataset apply instead of returning 400, cleanup only deletes from the active dataset and the aggregation leaks into $OTHER_DATASET. Delete in both, or require DASH0_TSA_OTHER_DATASET to be set explicitly.

@jochil jochil Sep 11, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a new commit by deleting from both datasets in cleanup. I went with that rather than requiring DASH0_TSA_OTHER_DATASET, since that would turn step 5 from "skips politely" into "fails unless configured" for anyone running the suite, CI included. More than happy to switch if you'd prefer it strict.

…n an undecodable 200

A 200 whose body the client cannot decode as JSON leaves the aggregation nil
with a nil error, and the summary path reads aggregation.Spec directly rather
than through the nil-tolerant getters, so `tsa get` panicked.

The regression test needs the mock server to answer with a non-JSON content
type, so MockResponse grows an optional ContentType. It keeps defaulting to
application/json.
`Use` and the example offered `<origin-or-id>`, but an id only resolves when
the document carries that same id. Origins are mandatory and ids are
server-assigned, so a hand-written document has only an origin, and an id
copied from `tsa list` is rejected. The help now says origin, and `Long`
explains when an id also works.
commands.md still said a missing asset exits non-zero without `--force`, which
contradicts the note this branch adds to cli-naming-conventions.md. Most delete
endpoints answer 2xx, so `delete <typo>` exits 0 either way. This file is the
copy synced to dash0.com/docs, so the wrong version was the published one.
Step 5 expects the cross-dataset apply to be rejected. If the API ever accepts
it, the aggregation lands in the other dataset and the old cleanup could not
reach it, leaking an origin that the whole organization can see.
Same facts, fewer words, and no comment that only restates the name of the
thing below it. Test doc comments now say why the case exists instead of
repeating the test name. 348 comment lines down to 288.
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.

Add time series aggregation support

2 participants