Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add atuin stats --filter-mode #1737

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
110 changes: 92 additions & 18 deletions atuin-client/src/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ pub trait Database: Send + Sync + 'static {
max: Option<usize>,
unique: bool,
include_deleted: bool,
range: Option<(OffsetDateTime, OffsetDateTime)>,
) -> Result<Vec<History>>;
async fn range(&self, from: OffsetDateTime, to: OffsetDateTime) -> Result<Vec<History>>;

Expand Down Expand Up @@ -285,6 +286,7 @@ impl Database for Sqlite {
max: Option<usize>,
unique: bool,
include_deleted: bool,
range: Option<(OffsetDateTime, OffsetDateTime)>,
) -> Result<Vec<History>> {
debug!("listing history");

Expand Down Expand Up @@ -318,6 +320,11 @@ impl Database for Sqlite {
query.limit(max);
}

if let Some((from, to)) = range {
query.and_where_ge("timestamp", from.unix_timestamp_nanos() as i64);
query.and_where_le("timestamp", to.unix_timestamp_nanos() as i64);
}

let query = query.sql().expect("bug in list query. please report");

let res = sqlx::query(&query)
Expand Down Expand Up @@ -730,13 +737,7 @@ mod test {
query: &str,
expected: usize,
) -> Result<Vec<History>> {
let context = Context {
hostname: "test:host".to_string(),
session: "beepboopiamasession".to_string(),
cwd: "/home/ellie".to_string(),
host_id: "test-host".to_string(),
git_root: None,
};
let context = new_context();

let results = db
.search(
Expand Down Expand Up @@ -774,9 +775,23 @@ mod test {
assert_eq!(commands, expected_commands);
}

async fn new_history_item(db: &mut impl Database, cmd: &str) -> Result<()> {
fn new_context() -> Context {
Context {
hostname: "test:host".to_string(),
session: "beepboopiamasession".to_string(),
cwd: "/home/ellie".to_string(),
host_id: "test-host".to_string(),
git_root: None,
}
}

async fn new_history_item(
db: &mut impl Database,
cmd: &str,
timestamp: Option<OffsetDateTime>,
) -> Result<()> {
let mut captured: History = History::capture()
.timestamp(OffsetDateTime::now_utc())
.timestamp(timestamp.unwrap_or_else(|| OffsetDateTime::now_utc()))
.command(cmd)
.cwd("/home/ellie")
.build()
Expand All @@ -790,10 +805,61 @@ mod test {
db.save(&captured).await
}

#[tokio::test(flavor = "multi_thread")]
async fn test_list_range() {
let mut db = Sqlite::new("sqlite::memory:", 0.1).await.unwrap();

let timestamp = OffsetDateTime::from_unix_timestamp(1708330400).unwrap();

new_history_item(&mut db, "ls /home/ellie", Some(timestamp))
.await
.unwrap();
new_history_item(&mut db, "ls /home/frank", None)
.await
.unwrap();

let context = new_context();

let all = db
.list(&[], &context, None, true, true, None)
.await
.unwrap();
assert_eq!(all.len(), 2);

let range = Some((timestamp, timestamp));

assert_eq!(
db.list(&[], &context, None, false, true, range)
.await
.unwrap()
.len(),
1
);
}

#[tokio::test(flavor = "multi_thread")]
async fn test_range() {
let mut db = Sqlite::new("sqlite::memory:", 0.1).await.unwrap();

let timestamp = OffsetDateTime::from_unix_timestamp(1708330400).unwrap();

new_history_item(&mut db, "ls /home/ellie", Some(timestamp))
.await
.unwrap();
new_history_item(&mut db, "ls /home/frank", None)
.await
.unwrap();

let range = db.range(timestamp, timestamp).await.unwrap();
assert_eq!(range.len(), 1);
}

#[tokio::test(flavor = "multi_thread")]
async fn test_search_prefix() {
let mut db = Sqlite::new("sqlite::memory:", 0.1).await.unwrap();
new_history_item(&mut db, "ls /home/ellie").await.unwrap();
new_history_item(&mut db, "ls /home/ellie", None)
.await
.unwrap();

assert_search_eq(&db, SearchMode::Prefix, FilterMode::Global, "ls", 1)
.await
Expand All @@ -809,7 +875,9 @@ mod test {
#[tokio::test(flavor = "multi_thread")]
async fn test_search_fulltext() {
let mut db = Sqlite::new("sqlite::memory:", 0.1).await.unwrap();
new_history_item(&mut db, "ls /home/ellie").await.unwrap();
new_history_item(&mut db, "ls /home/ellie", None)
.await
.unwrap();

assert_search_eq(&db, SearchMode::FullText, FilterMode::Global, "ls", 1)
.await
Expand All @@ -825,10 +893,16 @@ mod test {
#[tokio::test(flavor = "multi_thread")]
async fn test_search_fuzzy() {
let mut db = Sqlite::new("sqlite::memory:", 0.1).await.unwrap();
new_history_item(&mut db, "ls /home/ellie").await.unwrap();
new_history_item(&mut db, "ls /home/frank").await.unwrap();
new_history_item(&mut db, "cd /home/Ellie").await.unwrap();
new_history_item(&mut db, "/home/ellie/.bin/rustup")
new_history_item(&mut db, "ls /home/ellie", None)
.await
.unwrap();
new_history_item(&mut db, "ls /home/frank", None)
.await
.unwrap();
new_history_item(&mut db, "cd /home/Ellie", None)
.await
.unwrap();
new_history_item(&mut db, "/home/ellie/.bin/rustup", None)
.await
.unwrap();

Expand Down Expand Up @@ -917,8 +991,8 @@ mod test {
let mut db = Sqlite::new("sqlite::memory:", 0.1).await.unwrap();
// test ordering of results: we should choose the first, even though it happened longer ago.

new_history_item(&mut db, "curl").await.unwrap();
new_history_item(&mut db, "corburl").await.unwrap();
new_history_item(&mut db, "curl", None).await.unwrap();
new_history_item(&mut db, "corburl", None).await.unwrap();

// if fuzzy reordering is on, it should come back in a more sensible order
assert_search_commands(
Expand Down Expand Up @@ -950,7 +1024,7 @@ mod test {

let mut db = Sqlite::new("sqlite::memory:", 0.1).await.unwrap();
for _i in 1..10000 {
new_history_item(&mut db, "i am a duplicated command")
new_history_item(&mut db, "i am a duplicated command", None)
.await
.unwrap();
}
Expand Down
2 changes: 1 addition & 1 deletion atuin-client/src/history/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ impl HistoryStore {
pb.enable_steady_tick(Duration::from_millis(500));

pb.set_message("Fetching history from old database");
let history = db.list(&[], &context, None, false, true).await?;
let history = db.list(&[], &context, None, false, true, None).await?;

pb.set_message("Fetching history already in store");
let store_ids = self.history_ids().await?;
Expand Down
2 changes: 1 addition & 1 deletion atuin/src/command/client/history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ impl Cmd {
};

let history = db
.list(&filters, &context, None, false, include_deleted)
.list(&filters, &context, None, false, include_deleted, None)
.await?;

print_list(
Expand Down
9 changes: 8 additions & 1 deletion atuin/src/command/client/search/engines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,14 @@ pub trait SearchEngine: Send + Sync + 'static {
async fn query(&mut self, state: &SearchState, db: &mut dyn Database) -> Result<Vec<History>> {
if state.input.as_str().is_empty() {
Ok(db
.list(&[state.filter_mode], &state.context, Some(200), true, false)
.list(
&[state.filter_mode],
&state.context,
Some(200),
true,
false,
None,
)
.await?
.into_iter()
.collect::<Vec<_>>())
Expand Down
27 changes: 19 additions & 8 deletions atuin/src/command/client/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use interim::parse_date_string;
use atuin_client::{
database::{current_context, Database},
history::History,
settings::Settings,
settings::{FilterMode, Settings},
};
use time::{Duration, OffsetDateTime, Time};

Expand All @@ -22,6 +22,10 @@ pub struct Cmd {
/// How many top commands to list
#[arg(long, short, default_value = "10")]
count: usize,

/// Filter commands stats [global, host, session, directory, workspace]
#[arg(long = "filter-mode")]
filter_mode: Option<FilterMode>,
}

fn compute_stats(settings: &Settings, history: &[History], count: usize) -> (usize, usize) {
Expand Down Expand Up @@ -94,32 +98,39 @@ impl Cmd {
self.period.join(" ")
};

let filter = self.filter_mode.map(|f| vec![f]).unwrap_or_default();

let now = OffsetDateTime::now_utc().to_offset(settings.timezone.0);
let last_night = now.replace_time(Time::MIDNIGHT);

let history = if words.as_str() == "all" {
db.list(&[], &context, None, false, false).await?
let range = if words.as_str() == "all" {
None
} else if words.trim() == "today" {
let start = last_night;
let end = start + Duration::days(1);
db.range(start, end).await?
Some((start, end))
} else if words.trim() == "month" {
let end = last_night;
let start = end - Duration::days(31);
db.range(start, end).await?
Some((start, end))
} else if words.trim() == "week" {
let end = last_night;
let start = end - Duration::days(7);
db.range(start, end).await?
Some((start, end))
} else if words.trim() == "year" {
let end = last_night;
let start = end - Duration::days(365);
db.range(start, end).await?
Some((start, end))
} else {
let start = parse_date_string(&words, now, settings.dialect.into())?;
let end = start + Duration::days(1);
db.range(start, end).await?
Some((start, end))
};

let history = db
.list(filter.as_slice(), &context, None, false, false, range)
.await?;

compute_stats(settings, &history, self.count);
Ok(())
}
Expand Down