Skip to content

Commit 05d5aba

Browse files
committed
Sort on-call pages newest first
1 parent 894da8f commit 05d5aba

5 files changed

Lines changed: 56 additions & 6 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ list of commands as built.
123123
| API Domain | Status | Pup Commands | Notes |
124124
|------------|--------|--------------|-------|
125125
| Incidents || `incidents list`, `incidents get`, `incidents attachments`, `incidents settings`, `incidents handles`, `incidents postmortem-templates` | Incident management with settings, handles, and postmortem templates |
126-
| On-Call || `on-call teams` (CRUD, memberships with roles), `on-call pages` (list, get, create) | Team management and on-call page access |
126+
| On-Call || `on-call teams` (CRUD, memberships with roles), `on-call pages` (list, get, create) | Team management and newest-first on-call page access |
127127
| Case Management || `cases` (create, search, assign, archive, projects, jira, servicenow, move) | Complete case management with Jira/ServiceNow linking |
128128
| Error Tracking || `error-tracking issues search`, `error-tracking issues get` | Error issue search and details |
129129
| Service Catalog || `service-catalog list`, `service-catalog get` | Service registry management |

docs/COMMANDS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ pup <domain> <subgroup> <action> [options] # Nested commands
3939
| downtime | list, get, cancel | src/commands/downtime.rs ||
4040
| tags | list, get, add, update, delete | src/commands/tags.rs ||
4141
| events | post, list, search, get | src/commands/events.rs ||
42-
| on-call | teams (CRUD, memberships), pages (list, get, create) | src/commands/on_call.rs ||
42+
| on-call | teams (CRUD, memberships), pages (newest-first list, get, create) | src/commands/on_call.rs ||
4343
| audit-logs | list, search | src/commands/audit_logs.rs ||
4444
| api-keys | list, get, create, delete | src/commands/api_keys.rs ||
4545
| app-keys | list, get, create, update, delete | src/commands/app_keys.rs ||
@@ -188,7 +188,7 @@ pup infrastructure hosts list
188188

189189
### Operations & Incident Response
190190
- **incidents** - Incident management (list, get, attachments, settings, handles, postmortem-templates)
191-
- **on-call** - Team management (create, update, delete teams; manage memberships with roles) and pages (list, get, create)
191+
- **on-call** - Team management (create, update, delete teams; manage memberships with roles) and pages (newest-first list, get, create)
192192
- **cases** - Case management (create, search, assign, archive, unarchive, update, projects, jira, servicenow, move)
193193
- **hamr** - High Availability Multi-Region connections
194194
- **fleet** - Fleet Automation (agents, deployments, schedules, tracers, clusters, instrumented-pods)

src/commands/on_call.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -480,14 +480,16 @@ pub async fn pages_list(
480480
team: Option<&str>,
481481
responder: Option<&str>,
482482
page_size: u32,
483+
sort: &str,
483484
) -> Result<()> {
484485
if !(1..=1000).contains(&page_size) {
485486
anyhow::bail!("invalid page_size: {page_size}. Expected a value from 1 to 1000");
486487
}
488+
validate_pages_sort(sort)?;
487489

488490
let page_size = page_size.to_string();
489491
let team_filter = team.map(|t| format!("team:{t}"));
490-
let mut query = vec![("page[size]", page_size.as_str())];
492+
let mut query = vec![("page[size]", page_size.as_str()), ("sort", sort)];
491493
if let Some(filter) = team_filter.as_deref() {
492494
query.push(("filter", filter));
493495
}
@@ -503,6 +505,15 @@ pub async fn pages_list(
503505
formatter::output(cfg, &resp)
504506
}
505507

508+
fn validate_pages_sort(sort: &str) -> Result<()> {
509+
match sort {
510+
"created_at" | "-created_at" => Ok(()),
511+
other => {
512+
anyhow::bail!("invalid --sort value: {other:?}\nExpected: created_at, -created_at")
513+
}
514+
}
515+
}
516+
506517
fn filter_pages_by_responder(resp: &mut serde_json::Value, responder: &str) {
507518
if let Some(pages) = resp
508519
.get_mut("data")
@@ -952,14 +963,15 @@ mod tests {
952963
.mock("GET", "/api/unstable/on-call/pages")
953964
.match_query(mockito::Matcher::AllOf(vec![
954965
mockito::Matcher::UrlEncoded("page[size]".into(), "42".into()),
966+
mockito::Matcher::UrlEncoded("sort".into(), "-created_at".into()),
955967
mockito::Matcher::UrlEncoded("filter".into(), "team:core-platform".into()),
956968
]))
957969
.with_status(200)
958970
.with_header("content-type", "application/json")
959971
.with_body(r#"{"data": []}"#)
960972
.create_async()
961973
.await;
962-
let result = super::pages_list(&cfg, Some("core-platform"), None, 42).await;
974+
let result = super::pages_list(&cfg, Some("core-platform"), None, 42, "-created_at").await;
963975
assert!(result.is_ok(), "pages_list failed: {:?}", result.err());
964976
mock.assert_async().await;
965977
cleanup_env();
@@ -969,7 +981,7 @@ mod tests {
969981
async fn test_on_call_pages_list_rejects_invalid_page_size() {
970982
let _lock = lock_env().await;
971983
let cfg = test_config("http://unused.local");
972-
let result = super::pages_list(&cfg, None, None, 0).await;
984+
let result = super::pages_list(&cfg, None, None, 0, "-created_at").await;
973985
assert!(result.is_err());
974986
assert!(result
975987
.unwrap_err()
@@ -978,6 +990,19 @@ mod tests {
978990
cleanup_env();
979991
}
980992

993+
#[tokio::test]
994+
async fn test_on_call_pages_list_rejects_invalid_sort() {
995+
let _lock = lock_env().await;
996+
let cfg = test_config("http://unused.local");
997+
let result = super::pages_list(&cfg, None, None, 100, "started_at").await;
998+
assert!(result.is_err());
999+
assert!(result
1000+
.unwrap_err()
1001+
.to_string()
1002+
.contains("invalid --sort value"));
1003+
cleanup_env();
1004+
}
1005+
9811006
#[test]
9821007
fn test_page_has_responder_matches_and_rejects() {
9831008
let page = serde_json::json!({

src/main.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6812,6 +6812,14 @@ enum OnCallPagesActions {
68126812
team: Option<String>,
68136813
#[arg(long, help = "Filter by responder user id (client-side)")]
68146814
responder: Option<String>,
6815+
#[arg(
6816+
long,
6817+
allow_hyphen_values = true,
6818+
value_parser = ["created_at", "-created_at"],
6819+
default_value = "-created_at",
6820+
help = "Sort field (created_at or -created_at; defaults to newest first)"
6821+
)]
6822+
sort: String,
68156823
#[arg(
68166824
long,
68176825
default_value_t = 1000,
@@ -14512,13 +14520,15 @@ async fn main_inner() -> anyhow::Result<()> {
1451214520
OnCallPagesActions::List {
1451314521
team,
1451414522
responder,
14523+
sort,
1451514524
page_size,
1451614525
} => {
1451714526
commands::on_call::pages_list(
1451814527
&cfg,
1451914528
team.as_deref(),
1452014529
responder.as_deref(),
1452114530
page_size,
14531+
&sort,
1452214532
)
1452314533
.await?;
1452414534
}

src/test_commands.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ fn test_read_only_guard_on_call_pages_list() {
9999
"core-platform",
100100
"--responder",
101101
"user-1",
102+
"--sort",
103+
"-created_at",
102104
])
103105
.unwrap();
104106
let leaf = crate::get_leaf_subcommand_name(&matches).unwrap();
@@ -119,6 +121,19 @@ fn test_on_call_pages_list_rejects_invalid_page_size() {
119121
assert!(result.is_err());
120122
}
121123

124+
#[test]
125+
fn test_on_call_pages_list_rejects_invalid_sort() {
126+
let result = crate::Cli::command().try_get_matches_from([
127+
"pup",
128+
"on-call",
129+
"pages",
130+
"list",
131+
"--sort",
132+
"started_at",
133+
]);
134+
assert!(result.is_err());
135+
}
136+
122137
#[test]
123138
fn test_read_only_guard_nested_write() {
124139
let matches = crate::Cli::command()

0 commit comments

Comments
 (0)