1use crate::output::{emit, Report};
4use crate::reports::query::{self, DEFAULT_GROUP_BY};
5use crate::reports::ReportError;
6use crate::store::Scanner;
7
8use super::Env;
9
10pub fn run(env: &Env<'_>, group_by: Option<&str>) -> Result<Report, ReportError> {
12 let dims = query::parse_dimensions(group_by.unwrap_or(DEFAULT_GROUP_BY))?;
13 env.pre_ingest()?;
14 let report = query::build(&Scanner::new(env.paths.clone()), &env.ctx(), &dims)?;
15 emit(&report, env.json)?;
16 Ok(report)
17}
18
19#[cfg(test)]
20mod tests {
21 use super::*;
22 use crate::cli::TimeWindow;
23 use crate::config::Config;
24 use crate::reports::testkit::*;
25 use crate::store::StorePaths;
26
27 fn env<'a>(paths: &'a StorePaths, config: &'a Config) -> Env<'a> {
28 Env {
29 config,
30 paths,
31 window: TimeWindow::all(),
32 project: None,
33 json: false,
34 no_ingest: true,
35 include_sidechain: true,
36 }
37 }
38
39 #[test]
40 fn defaults_to_grouping_by_project() {
41 let (_dir, paths) = store(&[used("a", ms(2026, 8, 4, 8), "acme", "opus", 10, 1)]);
42 let config = Config::default();
43 let report = run(&env(&paths, &config), None).unwrap();
44 assert_eq!(report.name, "query");
45 assert_eq!(report.json_rows[0]["project"], "acme");
46 }
47
48 #[test]
49 fn an_unknown_dimension_fails_before_anything_is_scanned() {
50 let (_dir, paths) = store(&[used("a", ms(2026, 8, 4, 8), "acme", "opus", 10, 1)]);
51 let config = Config::default();
52 let err = run(&env(&paths, &config), Some("project,colour")).unwrap_err();
53 assert!(err.to_string().contains("colour"), "{err}");
54 }
55}