Skip to content

Commit 4dffa69

Browse files
committed
Add single dataset get command
1 parent 3ad40dd commit 4dffa69

3 files changed

Lines changed: 218 additions & 85 deletions

File tree

src/command.rs

Lines changed: 18 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,19 @@ pub enum Commands {
1616

1717
/// Manage datasets
1818
Datasets {
19+
/// Dataset ID to show details
20+
id: Option<String>,
21+
22+
/// Workspace ID (defaults to first workspace from login)
23+
#[arg(long)]
24+
workspace_id: Option<String>,
25+
26+
/// Output format (used with dataset ID)
27+
#[arg(long, default_value = "table", value_parser = ["table", "json", "yaml"])]
28+
format: String,
29+
1930
#[command(subcommand)]
20-
command: DatasetsCommands,
31+
command: Option<DatasetsCommands>,
2132
},
2233

2334
/// Execute a SQL query
@@ -159,22 +170,16 @@ pub enum DatasetsCommands {
159170
#[arg(long)]
160171
workspace_id: Option<String>,
161172

162-
/// Output format
163-
#[arg(long, default_value = "yaml", value_parser = ["table", "json", "yaml"])]
164-
format: String,
165-
},
166-
167-
/// Get details for a specific dataset
168-
Get {
169-
/// Workspace ID (defaults to first workspace from login)
173+
/// Maximum number of results (default: 100, max: 1000)
170174
#[arg(long)]
171-
workspace_id: Option<String>,
175+
limit: Option<u32>,
172176

173-
/// Dataset ID
174-
dataset_id: String,
177+
/// Pagination offset
178+
#[arg(long)]
179+
offset: Option<u32>,
175180

176181
/// Output format
177-
#[arg(long, default_value = "yaml", value_parser = ["table", "json", "yaml"])]
182+
#[arg(long, default_value = "table", value_parser = ["table", "json", "yaml"])]
178183
format: String,
179184
},
180185

@@ -197,70 +202,6 @@ pub enum DatasetsCommands {
197202
file: Option<String>,
198203
},
199204

200-
/// Update a dataset in a workspace
201-
Update {
202-
/// Workspace ID (defaults to first workspace from login)
203-
#[arg(long)]
204-
workspace_id: Option<String>,
205-
206-
/// Dataset ID
207-
dataset_id: String,
208-
209-
/// New dataset name
210-
#[arg(long)]
211-
name: Option<String>,
212-
213-
/// New SQL query for the dataset
214-
#[arg(long)]
215-
query: Option<String>,
216-
217-
/// Output format
218-
#[arg(long, default_value = "yaml", value_parser = ["table", "json", "yaml"])]
219-
format: String,
220-
},
221-
222-
/// Delete a dataset from a workspace
223-
Delete {
224-
/// Workspace ID (defaults to first workspace from login)
225-
#[arg(long)]
226-
workspace_id: Option<String>,
227-
228-
/// Dataset ID
229-
dataset_id: String,
230-
},
231-
232-
/// Update the SQL query for a dataset
233-
UpdateSql {
234-
/// Workspace ID (defaults to first workspace from login)
235-
#[arg(long)]
236-
workspace_id: Option<String>,
237-
238-
/// Dataset ID
239-
dataset_id: String,
240-
241-
/// New SQL query for the dataset
242-
#[arg(long)]
243-
sql: String,
244-
245-
/// Output format
246-
#[arg(long, default_value = "yaml", value_parser = ["table", "json", "yaml"])]
247-
format: String,
248-
},
249-
250-
/// Execute a dataset
251-
Execute {
252-
/// Workspace ID (defaults to first workspace from login)
253-
#[arg(long)]
254-
workspace_id: Option<String>,
255-
256-
/// Dataset ID
257-
dataset_id: String,
258-
259-
/// Output format
260-
#[arg(long, default_value = "yaml", value_parser = ["table", "json", "yaml"])]
261-
format: String,
262-
},
263-
264205
}
265206

266207

src/datasets.rs

Lines changed: 179 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,42 @@
11
use crate::config;
22
use indicatif::{ProgressBar, ProgressStyle};
3-
use serde::Deserialize;
3+
use serde::{Deserialize, Serialize};
44
use serde_json::json;
55
use std::path::Path;
66

7-
#[derive(Deserialize)]
7+
#[derive(Deserialize, Serialize)]
88
struct Dataset {
99
id: String,
1010
label: String,
1111
table_name: String,
12+
created_at: String,
13+
updated_at: String,
14+
}
15+
16+
#[derive(Deserialize)]
17+
struct ListResponse {
18+
datasets: Vec<Dataset>,
19+
count: u64,
20+
has_more: bool,
21+
}
22+
23+
#[derive(Deserialize, Serialize)]
24+
struct Column {
25+
name: String,
26+
data_type: String,
27+
nullable: bool,
28+
}
29+
30+
#[derive(Deserialize, Serialize)]
31+
struct DatasetDetail {
32+
id: String,
33+
label: String,
34+
schema_name: String,
35+
table_name: String,
36+
source_type: String,
37+
created_at: String,
38+
updated_at: String,
39+
columns: Vec<Column>,
1240
}
1341

1442
struct FileType {
@@ -293,3 +321,152 @@ pub fn create(
293321
println!("label: {}", dataset.label);
294322
println!("table_name: {}", dataset.table_name);
295323
}
324+
325+
pub fn list(workspace_id: &str, limit: Option<u32>, offset: Option<u32>, format: &str) {
326+
let profile_config = match config::load("default") {
327+
Ok(c) => c,
328+
Err(e) => {
329+
eprintln!("{e}");
330+
std::process::exit(1);
331+
}
332+
};
333+
334+
let api_key = match &profile_config.api_key {
335+
Some(key) if key != "PLACEHOLDER" => key.clone(),
336+
_ => {
337+
eprintln!("error: not authenticated. Run 'hotdata auth login' to log in.");
338+
std::process::exit(1);
339+
}
340+
};
341+
342+
let mut url = format!("{}/datasets", profile_config.api_url);
343+
let mut params = vec![];
344+
if let Some(l) = limit { params.push(format!("limit={l}")); }
345+
if let Some(o) = offset { params.push(format!("offset={o}")); }
346+
if !params.is_empty() { url = format!("{url}?{}", params.join("&")); }
347+
348+
let client = reqwest::blocking::Client::new();
349+
let resp = match client
350+
.get(&url)
351+
.header("Authorization", format!("Bearer {api_key}"))
352+
.header("X-Workspace-Id", workspace_id)
353+
.send()
354+
{
355+
Ok(r) => r,
356+
Err(e) => {
357+
eprintln!("error connecting to API: {e}");
358+
std::process::exit(1);
359+
}
360+
};
361+
362+
if !resp.status().is_success() {
363+
use crossterm::style::Stylize;
364+
eprintln!("{}", api_error(resp.text().unwrap_or_default()).red());
365+
std::process::exit(1);
366+
}
367+
368+
let body: ListResponse = match resp.json() {
369+
Ok(v) => v,
370+
Err(e) => {
371+
eprintln!("error parsing response: {e}");
372+
std::process::exit(1);
373+
}
374+
};
375+
376+
match format {
377+
"json" => println!("{}", serde_json::to_string_pretty(&body.datasets).unwrap()),
378+
"yaml" => print!("{}", serde_yaml::to_string(&body.datasets).unwrap()),
379+
"table" => {
380+
let mut table = crate::util::make_table();
381+
table.set_header(["ID", "LABEL", "TABLE NAME", "CREATED AT"]);
382+
table.column_mut(1).unwrap().set_constraint(
383+
comfy_table::ColumnConstraint::UpperBoundary(comfy_table::Width::Fixed(30))
384+
);
385+
for d in &body.datasets {
386+
let created_at = d.created_at.split('.').next().unwrap_or(&d.created_at).replace('T', " ");
387+
table.add_row([&d.id, &d.label, &d.table_name, &created_at]);
388+
}
389+
println!("{table}");
390+
if body.has_more {
391+
let next = offset.unwrap_or(0) + body.count as u32;
392+
use crossterm::style::Stylize;
393+
eprintln!("{}", format!("showing {} results — use --offset {next} for more", body.count).dark_grey());
394+
}
395+
}
396+
_ => unreachable!(),
397+
}
398+
}
399+
400+
pub fn get(dataset_id: &str, workspace_id: &str, format: &str) {
401+
let profile_config = match config::load("default") {
402+
Ok(c) => c,
403+
Err(e) => {
404+
eprintln!("{e}");
405+
std::process::exit(1);
406+
}
407+
};
408+
409+
let api_key = match &profile_config.api_key {
410+
Some(key) if key != "PLACEHOLDER" => key.clone(),
411+
_ => {
412+
eprintln!("error: not authenticated. Run 'hotdata auth login' to log in.");
413+
std::process::exit(1);
414+
}
415+
};
416+
417+
let url = format!("{}/datasets/{dataset_id}", profile_config.api_url);
418+
let client = reqwest::blocking::Client::new();
419+
420+
let resp = match client
421+
.get(&url)
422+
.header("Authorization", format!("Bearer {api_key}"))
423+
.header("X-Workspace-Id", workspace_id)
424+
.send()
425+
{
426+
Ok(r) => r,
427+
Err(e) => {
428+
eprintln!("error connecting to API: {e}");
429+
std::process::exit(1);
430+
}
431+
};
432+
433+
if !resp.status().is_success() {
434+
use crossterm::style::Stylize;
435+
eprintln!("{}", api_error(resp.text().unwrap_or_default()).red());
436+
std::process::exit(1);
437+
}
438+
439+
let d: DatasetDetail = match resp.json() {
440+
Ok(v) => v,
441+
Err(e) => {
442+
eprintln!("error parsing response: {e}");
443+
std::process::exit(1);
444+
}
445+
};
446+
447+
match format {
448+
"json" => println!("{}", serde_json::to_string_pretty(&d).unwrap()),
449+
"yaml" => print!("{}", serde_yaml::to_string(&d).unwrap()),
450+
"table" => {
451+
let created_at = d.created_at.split('.').next().unwrap_or(&d.created_at).replace('T', " ");
452+
let updated_at = d.updated_at.split('.').next().unwrap_or(&d.updated_at).replace('T', " ");
453+
println!("id: {}", d.id);
454+
println!("label: {}", d.label);
455+
println!("schema: {}", d.schema_name);
456+
println!("table: {}", d.table_name);
457+
println!("source_type: {}", d.source_type);
458+
println!("created_at: {created_at}");
459+
println!("updated_at: {updated_at}");
460+
if !d.columns.is_empty() {
461+
println!();
462+
let mut table = crate::util::make_table();
463+
table.set_header(["COLUMN", "DATA TYPE", "NULLABLE"]);
464+
for col in &d.columns {
465+
table.add_row([&col.name, &col.data_type, &col.nullable.to_string()]);
466+
}
467+
println!("{table}");
468+
}
469+
}
470+
_ => unreachable!(),
471+
}
472+
}

src/main.rs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,28 @@ fn main() {
6161
AuthCommands::Status { profile } => auth::status(&profile),
6262
_ => eprintln!("not yet implemented"),
6363
},
64-
Commands::Datasets { command } => match command {
65-
DatasetsCommands::Create { workspace_id, label, table_name, file } => {
66-
let workspace_id = resolve_workspace(workspace_id);
67-
datasets::create(&workspace_id, label.as_deref(), table_name.as_deref(), file.as_deref())
64+
Commands::Datasets { id, workspace_id, format, command } => {
65+
let workspace_id = resolve_workspace(workspace_id);
66+
if let Some(id) = id {
67+
datasets::get(&id, &workspace_id, &format)
68+
} else {
69+
match command {
70+
Some(DatasetsCommands::List { workspace_id: ws, limit, offset, format }) => {
71+
let workspace_id = resolve_workspace(ws);
72+
datasets::list(&workspace_id, limit, offset, &format)
73+
}
74+
Some(DatasetsCommands::Create { workspace_id: ws, label, table_name, file }) => {
75+
let workspace_id = resolve_workspace(ws);
76+
datasets::create(&workspace_id, label.as_deref(), table_name.as_deref(), file.as_deref())
77+
}
78+
None => {
79+
use clap::CommandFactory;
80+
Cli::command().find_subcommand_mut("datasets").unwrap().print_help().unwrap();
81+
println!();
82+
}
83+
}
6884
}
69-
_ => eprintln!("not yet implemented"),
70-
},
85+
}
7186
Commands::Query { sql, workspace_id, connection, format } => {
7287
let workspace_id = resolve_workspace(workspace_id);
7388
query::execute(&sql, &workspace_id, connection.as_deref(), &format)

0 commit comments

Comments
 (0)