summaryrefslogtreecommitdiffstats
path: root/atuin-client/src
diff options
context:
space:
mode:
authorEllie Huxtable <ellie@elliehuxtable.com>2024-01-12 16:02:08 +0000
committerGitHub <noreply@github.com>2024-01-12 16:02:08 +0000
commit99249ea319fca96ace8f3f4962534dc7a4bc5923 (patch)
tree61103701633c6e4afec21d1108faddbc20e90d9e /atuin-client/src
parenta60d8934c5dfcf0679dfa5f4de5c2080dfd32a5f (diff)
feat: Add interactive command inspector (#1296)
* Begin work on command inspector This is a separate pane in the interactive mode that allows for exploration and inspecting of specific commands. I've restructured things a bit. It made logical sense that things were nested under commands, however the whole point of `atuin` is to provide commands. Breaking things out like this enables a bit less crazy nesting as we add more functionality to things like interactive search. I'd like to add a few more interactive things and it was starting to feel very cluttered * Some vague tab things * functioning inspector with stats * add interactive delete to inspector * things * clippy * borders * sus * revert restructure for another pr * Revert "sus" This reverts commit d4bae8cf614d93b728621f7985cf4e387b6dc113.
Diffstat (limited to 'atuin-client/src')
-rw-r--r--atuin-client/src/database.rs121
-rw-r--r--atuin-client/src/history.rs20
2 files changed, 141 insertions, 0 deletions
diff --git a/atuin-client/src/database.rs b/atuin-client/src/database.rs
index 6a6f5991..376c7b75 100644
--- a/atuin-client/src/database.rs
+++ b/atuin-client/src/database.rs
@@ -18,6 +18,8 @@ use sqlx::{
};
use time::OffsetDateTime;
+use crate::history::HistoryStats;
+
use super::{
history::History,
ordering,
@@ -109,6 +111,8 @@ pub trait Database: Send + Sync + 'static {
async fn query_history(&self, query: &str) -> Result<Vec<History>>;
async fn all_with_count(&self) -> Result<Vec<(History, i32)>>;
+
+ async fn stats(&self, h: &History) -> Result<HistoryStats>;
}
// Intended for use on a developer machine and not a sync server.
@@ -562,6 +566,123 @@ impl Database for Sqlite {
Ok(())
}
+
+ async fn stats(&self, h: &History) -> Result<HistoryStats> {
+ // We select the previous in the session by time
+ let mut prev = SqlBuilder::select_from("history");
+ prev.field("*")
+ .and_where("timestamp < ?1")
+ .and_where("session = ?2")
+ .order_by("timestamp", true)
+ .limit(1);
+
+ let mut next = SqlBuilder::select_from("history");
+ next.field("*")
+ .and_where("timestamp > ?1")
+ .and_where("session = ?2")
+ .order_by("timestamp", false)
+ .limit(1);
+
+ let mut total = SqlBuilder::select_from("history");
+ total.field("count(1)").and_where("command = ?1");
+
+ let mut average = SqlBuilder::select_from("history");
+ average.field("avg(duration)").and_where("command = ?1");
+
+ let mut exits = SqlBuilder::select_from("history");
+ exits
+ .fields(&["exit", "count(1) as count"])
+ .and_where("command = ?1")
+ .group_by("exit");
+
+ // rewrite the following with sqlbuilder
+ let mut day_of_week = SqlBuilder::select_from("history");
+ day_of_week
+ .fields(&[
+ "strftime('%w', ROUND(timestamp / 1000000000), 'unixepoch') AS day_of_week",
+ "count(1) as count",
+ ])
+ .and_where("command = ?1")
+ .group_by("day_of_week");
+
+ // Intentionally format the string with 01 hardcoded. We want the average runtime for the
+ // _entire month_, but will later parse it as a datetime for sorting
+ // Sqlite has no datetime so we cannot do it there, and otherwise sorting will just be a
+ // string sort, which won't be correct.
+ let mut duration_over_time = SqlBuilder::select_from("history");
+ duration_over_time
+ .fields(&[
+ "strftime('01-%m-%Y', ROUND(timestamp / 1000000000), 'unixepoch') AS month_year",
+ "avg(duration) as duration",
+ ])
+ .and_where("command = ?1")
+ .group_by("month_year")
+ .having("duration > 0");
+
+ let prev = prev.sql().expect("issue in stats previous query");
+ let next = next.sql().expect("issue in stats next query");
+ let total = total.sql().expect("issue in stats average query");
+ let average = average.sql().expect("issue in stats previous query");
+ let exits = exits.sql().expect("issue in stats exits query");
+ let day_of_week = day_of_week.sql().expect("issue in stats day of week query");
+ let duration_over_time = duration_over_time
+ .sql()
+ .expect("issue in stats duration over time query");
+
+ let prev = sqlx::query(&prev)
+ .bind(h.timestamp.unix_timestamp_nanos() as i64)
+ .bind(&h.session)
+ .map(Self::query_history)
+ .fetch_optional(&self.pool)
+ .await?;
+
+ let next = sqlx::query(&next)
+ .bind(h.timestamp.unix_timestamp_nanos() as i64)
+ .bind(&h.session)
+ .map(Self::query_history)
+ .fetch_optional(&self.pool)
+ .await?;
+
+ let total: (i64,) = sqlx::query_as(&total)
+ .bind(&h.command)
+ .fetch_one(&self.pool)
+ .await?;
+
+ let average: (f64,) = sqlx::query_as(&average)
+ .bind(&h.command)
+ .fetch_one(&self.pool)
+ .await?;
+
+ let exits: Vec<(i64, i64)> = sqlx::query_as(&exits)
+ .bind(&h.command)
+ .fetch_all(&self.pool)
+ .await?;
+
+ let day_of_week: Vec<(String, i64)> = sqlx::query_as(&day_of_week)
+ .bind(&h.command)
+ .fetch_all(&self.pool)
+ .await?;
+
+ let duration_over_time: Vec<(String, f64)> = sqlx::query_as(&duration_over_time)
+ .bind(&h.command)
+ .fetch_all(&self.pool)
+ .await?;
+
+ let duration_over_time = duration_over_time
+ .iter()
+ .map(|f| (f.0.clone(), f.1.round() as i64))
+ .collect();
+
+ Ok(HistoryStats {
+ next,
+ previous: prev,
+ total: total.0 as u64,
+ average_duration: average.0 as u64,
+ exits,
+ day_of_week,
+ duration_over_time,
+ })
+ }
}
#[cfg(test)]
diff --git a/atuin-client/src/history.rs b/atuin-client/src/history.rs
index 8c312dc2..0147e25b 100644
--- a/atuin-client/src/history.rs
+++ b/atuin-client/src/history.rs
@@ -71,6 +71,26 @@ pub struct History {
pub deleted_at: Option<OffsetDateTime>,
}
+#[derive(Debug, Clone, PartialEq, Eq, sqlx::FromRow)]
+pub struct HistoryStats {
+ /// The command that was ran after this one in the session
+ pub next: Option<History>,
+ ///
+ /// The command that was ran before this one in the session
+ pub previous: Option<History>,
+
+ /// How many times has this command been ran?
+ pub total: u64,
+
+ pub average_duration: u64,
+
+ pub exits: Vec<(i64, i64)>,
+
+ pub day_of_week: Vec<(String, i64)>,
+
+ pub duration_over_time: Vec<(String, i64)>,
+}
+
impl History {
#[allow(clippy::too_many_arguments)]
fn new(