Skip to content

feat(change-stories): add pup change-stories list command group#558

Draft
bmarengo wants to merge 1 commit into
DataDog:mainfrom
bmarengo:feat/add-change-stories
Draft

feat(change-stories): add pup change-stories list command group#558
bmarengo wants to merge 1 commit into
DataDog:mainfrom
bmarengo:feat/add-change-stories

Conversation

@bmarengo
Copy link
Copy Markdown

@bmarengo bmarengo commented Jun 4, 2026

What does this PR do?

Wires a new top-level command group that calls the change-stories /api/unstable/change-stories/cli wrapper endpoint and emits the payload through an agent-friendly formatter + envelope pipeline.

The pup command intentionally exposes a reduced parameter surface (service, env, from, to, story_types, filter_tags, token_limit) to match the wrapper endpoint while the upstream /api/unstable/ contract stabilizes. Timestamps are required and reuse the parsing methods so agents reading just --help produce inputs the backend accepts.

Testing Report

Positive / Happy Path Tests

Cross-validation of pup's output against curl's output to the main change-stories endpoint - all in the staging environment.
All 20 tests passed.

ID Description Key flags pup count curl count Match Truncated
TC-01 Basic minimal --service change-stories --from 2025-05-01T00:00:00Z --to 2025-06-03T00:00:00Z 43 43 True
TC-02 --env staging + --env staging 43 43 True
TC-03 --env prod + --env prod 24 24 True
TC-04 Relative --from 7d --to now --from 7d --to now 34 False
TC-05 Relative --from 30m --to now --from 30m --to now 0–1 False
TC-06 Unix second timestamps --from 1746057600 --to 1748908800 43 43 True
TC-07 Unix millisecond timestamps --from 1746057600000 --to 1748908800000 43 43 True
TC-08 --story-types deployment + --story-types deployment 36 36 False
TC-09 --story-types feature_flag + --story-types feature_flag 2 2 False
TC-10 --story-types kubernetes + --story-types kubernetes 36 36 False
TC-11 --story-types watchdog + --story-types watchdog 8 8 False
TC-12 Multi --story-types (repeated flags) --story-types deployment --story-types kubernetes 46 46 True
TC-13 Multi --story-types (comma-delimited) --story-types deployment,kubernetes 46 46 ✅ (== TC-12) True
TC-14 --token-limit 500 (triggers truncation) + --token-limit 500 3 3 True
TC-15 --token-limit 999999 (no truncation) + --token-limit 999999 376 376 False
TC-16 primary tag filter + --filter-tags "datacenter:us1.staging.dog" 7 7 True
TC-17 primary tag filter + --filter-tags "datacenter:us2.staging.dog" 2 2 True
TC-18 --story-types scale (no data) + --story-types scale 0 0 False
TC-19 Non-existent service --service this-service-does-not-exist-xyz123 0 0 False
TC-20 Narrow 1-hour window --from 2025-06-02T12:00:00Z --to 2025-06-02T13:00:00Z 2 2 False
TC-21 Different service (ai_gateway) --service ai_gateway 54 54 True

Negative / Validation Error Tests

All 14 tests produced non-zero exit codes with the expected error messages.

ID Description Exit Error source Pattern matched
TC-N01 --from 2025-05-01 (bare date) 1 Client-side unable to parse time
TC-N02 --to 2025-06-03 (bare date) 1 Client-side unable to parse time
TC-N03 --from "not-a-time" (garbage) 1 Client-side unable to parse
TC-N04 Inverted range (from > to) 1 Server HTTP 400 end_ts must be greater than start_ts
TC-N05 Equal timestamps (from == to) 1 Server HTTP 400 end_ts must be greater than start_ts
TC-N06 Service starts with digit (123service) 1 Server HTTP 400 Service name must start with a letter
TC-N07 Service contains * 1 Server HTTP 400 excludesall
TC-N08 Service contains {} 1 Server HTTP 400 excludesall
TC-N09 Empty --service "" 1 Server HTTP 400 required
TC-N10 --story-types invalid_type_xyz 1 Server HTTP 400 oneof
TC-N11 Missing --service 2 CLI (clap) required arguments were not provided
TC-N12 Missing --from 2 CLI (clap) required arguments were not provided
TC-N13 Missing --to 2 CLI (clap) required arguments were not provided

Output Format Tests

ID Description Expected Result
TC-F01 Default JSON Valid JSON with stories array
TC-F02 --output yaml Valid YAML, first line stories:
TC-F03 --output table Table with +--- borders, not JSON
TC-F04 --agent envelope status=success, data.stories, metadata.command="change-stories list"
TC-F05 --agent + --token-limit 500 (truncation) data.truncated=true and metadata.truncated=true

Timestamp Format Tests

These tests verified every documented accepted and rejected format for --from / --to.

ID Format Input example Result
TC-T01 RFC 3339 absolute 2025-05-01T00:00:00Z ✅ Accepted (43 stories)
TC-T02 Unix seconds (10-digit) 1746057600 ✅ Accepted (43 stories)
TC-T03 Unix milliseconds (13-digit) 1746057600000 ✅ Accepted (43 stories)
TC-T04 Relative short 7d ✅ Accepted
TC-T05 Relative short 30m ✅ Accepted
TC-T06 Relative long 2hours ✅ Accepted
TC-T07 Relative long 30min ✅ Accepted
TC-T08 Relative with space "2 hours" ✅ Accepted
TC-T09 NOW (uppercase) NOW ✅ Accepted
TC-T10 Negative prefix stripped -7d ✅ Accepted (after allow_hyphen_values fix)
TC-T11 Equivalence: unix-secs == RFC 3339 1746057600 vs 2025-05-01T00:00:00Z ✅ Same count (43)
TC-T12 Equivalence: unix-ms == RFC 3339 1746057600000 vs 2025-05-01T00:00:00Z ✅ Same count (43)
TC-T13 Bare date rejected (regression) 2025-05-01 ✅ Correctly rejected: unable to parse
TC-T14 Plain string rejected "yesterday" ✅ Correctly rejected: unable to parse
TC-T15 Inverted range --from 2025-06-03 --to 2025-05-01 ✅ Correctly rejected: end_ts must be greater

Contributing Guidelines Compliance

Evaluated against every MUST item in docs/REVIEW.md, docs/CONTRIBUTING.md, and docs/TESTING.md.

# Rule Status Detail
1 Code Reuse ✅ Pass Uses client::raw_get, formatter::format_and_print, formatter::Metadata, util::parse_time_to_unix_millis. No new helpers invented for existing functionality.
2 Test Coverage ✅ Pass 8 unit tests: happy path, all filters, repeated story_types wire format, agent envelope, relative time accepted, HTTP 400 error, invalid --from rejected (names flag), invalid --to rejected (names flag). Both positive and negative paths covered.
3 Security ✅ Pass No credentials in errors. Client-side parse errors name the flag but not the value. No malicious code. All external calls go through client::raw_get which uses HTTPS.
4 Dependency Hygiene ✅ Pass No new entries in Cargo.toml. The only Cargo.toml change is a datadog-api-client rev bump, which is an existing pinned dependency. cargo audit is not installed; no new crates were introduced.
5 Complexity ✅ Pass list() is 67 lines with a single level of nesting. #[allow(clippy::too_many_arguments)] follows established codebase pattern (9 other command files use it). No traits, generics, or builder patterns introduced.
6 PR Hygiene ✅ Pass Branch feat/add-change-stories. One feature commit (feat(change-stories): add \pup change-stories list` command group`). Conventional commit format. Unrelated code not touched.
7 Rust Standards ✅ Pass cargo fmt --check → exit 0. cargo clippy -- -D warnings → exit 0. anyhow::anyhow! used with context for both timestamp errors. Idiomatic Option/Result throughout.
8 Tests pass ✅ Pass cargo test commands::change_stories → 8 passed, 0 failed.
9 Documentation ✅ Pass COMMANDS.md and EXAMPLES.md both updated. ARCHITECTURE.md updated.

Wires a new top-level command group that calls the change-stories
`/api/unstable/change-stories/cli` wrapper endpoint and emits the
agent-friendly payload through the existing formatter + envelope
pipeline.

The pup command intentionally exposes a reduced parameter surface
(service, env, from, to, story_types, primary_tag, token_limit) to
match the wrapper endpoint while the upstream /api/unstable/
contract stabilizes. Timestamps are required and reuse the parsing methods
so agents reading just `--help` produce inputs the backend accepts.

Ticket: CTR-2908

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
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.

1 participant