Skip to content

Commit 34108f0

Browse files
authored
feat(results): Add results list command
1 parent fb56fd9 commit 34108f0

3 files changed

Lines changed: 132 additions & 8 deletions

File tree

src/command.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,18 +83,21 @@ pub enum Commands {
8383
command: SkillCommands,
8484
},
8585

86-
/// Retrieve a stored query result by ID
86+
/// Retrieve a stored query result by ID, or list recent results
8787
Results {
88-
/// Result ID
89-
result_id: String,
88+
/// Result ID (omit to use a subcommand)
89+
result_id: Option<String>,
9090

9191
/// Workspace ID (defaults to first workspace from login)
92-
#[arg(long)]
92+
#[arg(long, global = true)]
9393
workspace_id: Option<String>,
9494

9595
/// Output format
9696
#[arg(long, default_value = "table", value_parser = ["table", "json", "csv"])]
9797
format: String,
98+
99+
#[command(subcommand)]
100+
command: Option<ResultsCommands>,
98101
},
99102
}
100103

@@ -400,6 +403,24 @@ pub enum SkillCommands {
400403
Status,
401404
}
402405

406+
#[derive(Subcommand)]
407+
pub enum ResultsCommands {
408+
/// List stored query results
409+
List {
410+
/// Maximum number of results (default: 100, max: 1000)
411+
#[arg(long)]
412+
limit: Option<u32>,
413+
414+
/// Pagination offset
415+
#[arg(long)]
416+
offset: Option<u32>,
417+
418+
/// Output format
419+
#[arg(long, default_value = "table", value_parser = ["table", "json", "yaml"])]
420+
format: String,
421+
},
422+
}
423+
403424
#[derive(Subcommand)]
404425
pub enum TablesCommands {
405426
/// List all tables in a workspace

src/main.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ mod workspace;
1515

1616
use anstyle::AnsiColor;
1717
use clap::{Parser, builder::Styles};
18-
use command::{AuthCommands, Commands, ConnectionsCommands, ConnectionsCreateCommands, DatasetsCommands, SkillCommands, TablesCommands, WorkspaceCommands};
18+
use command::{AuthCommands, Commands, ConnectionsCommands, ConnectionsCreateCommands, DatasetsCommands, ResultsCommands, SkillCommands, TablesCommands, WorkspaceCommands};
1919

2020
#[derive(Parser)]
2121
#[command(name = "hotdata", version, about = concat!("HotData CLI - Command line interface for HotData (v", env!("CARGO_PKG_VERSION"), ")"), long_about = None, disable_version_flag = true)]
@@ -149,9 +149,22 @@ fn main() {
149149
}
150150
SkillCommands::Status => skill::status(),
151151
},
152-
Commands::Results { result_id, workspace_id, format } => {
152+
Commands::Results { result_id, workspace_id, format, command } => {
153153
let workspace_id = resolve_workspace(workspace_id);
154-
results::get(&result_id, &workspace_id, &format)
154+
match command {
155+
Some(ResultsCommands::List { limit, offset, format }) => {
156+
results::list(&workspace_id, limit, offset, &format)
157+
}
158+
None => {
159+
match result_id {
160+
Some(id) => results::get(&id, &workspace_id, &format),
161+
None => {
162+
eprintln!("error: provide a result ID or use 'results list'");
163+
std::process::exit(1);
164+
}
165+
}
166+
}
167+
}
155168
}
156169
},
157170
}

src/results.rs

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::config;
2-
use serde::Deserialize;
2+
use serde::{Deserialize, Serialize};
33
use serde_json::Value;
44

55
#[derive(Deserialize)]
@@ -26,6 +26,96 @@ fn value_to_string(v: &Value) -> String {
2626
}
2727
}
2828

29+
#[derive(Deserialize, Serialize)]
30+
struct ResultEntry {
31+
id: String,
32+
status: String,
33+
created_at: String,
34+
}
35+
36+
#[derive(Deserialize)]
37+
struct ListResponse {
38+
results: Vec<ResultEntry>,
39+
count: u64,
40+
has_more: bool,
41+
}
42+
43+
pub fn list(workspace_id: &str, limit: Option<u32>, offset: Option<u32>, format: &str) {
44+
let profile_config = match config::load("default") {
45+
Ok(c) => c,
46+
Err(e) => {
47+
eprintln!("{e}");
48+
std::process::exit(1);
49+
}
50+
};
51+
52+
let api_key = match &profile_config.api_key {
53+
Some(key) if key != "PLACEHOLDER" => key.clone(),
54+
_ => {
55+
eprintln!("error: not authenticated. Run 'hotdata auth login' to log in.");
56+
std::process::exit(1);
57+
}
58+
};
59+
60+
let mut url = format!("{}/results", profile_config.api_url);
61+
let mut params = vec![];
62+
if let Some(l) = limit { params.push(format!("limit={l}")); }
63+
if let Some(o) = offset { params.push(format!("offset={o}")); }
64+
if !params.is_empty() { url = format!("{url}?{}", params.join("&")); }
65+
66+
let client = reqwest::blocking::Client::new();
67+
let resp = match client
68+
.get(&url)
69+
.header("Authorization", format!("Bearer {api_key}"))
70+
.header("X-Workspace-Id", workspace_id)
71+
.send()
72+
{
73+
Ok(r) => r,
74+
Err(e) => {
75+
eprintln!("error connecting to API: {e}");
76+
std::process::exit(1);
77+
}
78+
};
79+
80+
if !resp.status().is_success() {
81+
use crossterm::style::Stylize;
82+
eprintln!("{}", crate::util::api_error(resp.text().unwrap_or_default()).red());
83+
std::process::exit(1);
84+
}
85+
86+
let body: ListResponse = match resp.json() {
87+
Ok(v) => v,
88+
Err(e) => {
89+
eprintln!("error parsing response: {e}");
90+
std::process::exit(1);
91+
}
92+
};
93+
94+
match format {
95+
"json" => println!("{}", serde_json::to_string_pretty(&body.results).unwrap()),
96+
"yaml" => print!("{}", serde_yaml::to_string(&body.results).unwrap()),
97+
"table" => {
98+
if body.results.is_empty() {
99+
use crossterm::style::Stylize;
100+
eprintln!("{}", "No results found.".dark_grey());
101+
} else {
102+
let rows: Vec<Vec<String>> = body.results.iter().map(|r| vec![
103+
r.id.clone(),
104+
r.status.clone(),
105+
crate::util::format_date(&r.created_at),
106+
]).collect();
107+
crate::table::print(&["ID", "STATUS", "CREATED AT"], &rows);
108+
}
109+
if body.has_more {
110+
let next = offset.unwrap_or(0) + body.count as u32;
111+
use crossterm::style::Stylize;
112+
eprintln!("{}", format!("showing {} results — use --offset {next} for more", body.count).dark_grey());
113+
}
114+
}
115+
_ => unreachable!(),
116+
}
117+
}
118+
29119
pub fn get(result_id: &str, workspace_id: &str, format: &str) {
30120
let profile_config = match config::load("default") {
31121
Ok(c) => c,

0 commit comments

Comments
 (0)