group by remote and user

This commit is contained in:
Robin Appelman 2025-08-16 23:00:06 +02:00
commit 181559d841
4 changed files with 92 additions and 21 deletions

View file

@ -1,14 +1,19 @@
mod app; mod app;
mod remote;
mod unique; mod unique;
mod url; mod url;
mod user;
use crate::app::{Filter, LineSet}; use crate::app::{Filter, LineSet};
use crate::grouping::app::{AppGrouping, APP_GROUPING_UI}; use crate::grouping::app::{AppGrouping, APP_GROUPING_UI};
use crate::grouping::remote::{RemoteGrouping, REMOTE_GROUPING_UI};
use crate::grouping::url::{UrlGrouping, URL_GROUPING_UI}; use crate::grouping::url::{UrlGrouping, URL_GROUPING_UI};
use crate::grouping::user::{UserGrouping, USER_GROUPING_UI};
use crate::logfile::LogLine; use crate::logfile::LogLine;
use crate::matcher::MatchResult; use crate::matcher::MatchResult;
use crate::timegraph::{SparkLine, TimeGraph}; use crate::timegraph::{SparkLine, TimeGraph};
use ahash::AHasher; use ahash::AHasher;
use derive_more::From;
use ratatui::layout::{Alignment, Constraint}; use ratatui::layout::{Alignment, Constraint};
use std::borrow::Cow; use std::borrow::Cow;
use std::cell::OnceCell; use std::cell::OnceCell;
@ -120,29 +125,13 @@ where
list list
} }
#[derive(PartialEq, Clone)] #[derive(PartialEq, Clone, From)]
pub enum Groupings<'logs> { pub enum Groupings<'logs> {
Match(MatchResult), Match(MatchResult),
Url(UrlGrouping<'logs>), Url(UrlGrouping<'logs>),
App(AppGrouping<'logs>), App(AppGrouping<'logs>),
} User(UserGrouping<'logs>),
Remote(RemoteGrouping<'logs>),
impl<'logs> From<MatchResult> for Groupings<'logs> {
fn from(value: MatchResult) -> Self {
Groupings::Match(value)
}
}
impl<'logs> From<UrlGrouping<'logs>> for Groupings<'logs> {
fn from(value: UrlGrouping<'logs>) -> Self {
Groupings::Url(value)
}
}
impl<'logs> From<AppGrouping<'logs>> for Groupings<'logs> {
fn from(value: AppGrouping<'logs>) -> Self {
Groupings::App(value)
}
} }
impl<'logs> GroupingResult<'logs> for Groupings<'logs> { impl<'logs> GroupingResult<'logs> for Groupings<'logs> {
@ -151,6 +140,8 @@ impl<'logs> GroupingResult<'logs> for Groupings<'logs> {
Groupings::Match(r) => r.matches(filter), Groupings::Match(r) => r.matches(filter),
Groupings::Url(r) => r.matches(filter), Groupings::Url(r) => r.matches(filter),
Groupings::App(r) => r.matches(filter), Groupings::App(r) => r.matches(filter),
Groupings::User(r) => r.matches(filter),
Groupings::Remote(r) => r.matches(filter),
} }
} }
@ -161,6 +152,8 @@ impl<'logs> GroupingResult<'logs> for Groupings<'logs> {
} }
Groupings::Url(r) => Box::new(r.render()), Groupings::Url(r) => Box::new(r.render()),
Groupings::App(r) => Box::new(r.render()), Groupings::App(r) => Box::new(r.render()),
Groupings::User(r) => Box::new(r.render()),
Groupings::Remote(r) => Box::new(r.render()),
} }
} }
} }
@ -169,11 +162,19 @@ impl<'logs> GroupingResult<'logs> for Groupings<'logs> {
pub enum GroupingOptions { pub enum GroupingOptions {
Url, Url,
App, App,
User,
Remote,
} }
impl GroupingOptions { impl GroupingOptions {
pub fn all() -> impl Iterator<Item = Self> { pub fn all() -> impl Iterator<Item = Self> {
[GroupingOptions::Url, GroupingOptions::App].into_iter() [
GroupingOptions::Url,
GroupingOptions::App,
GroupingOptions::User,
GroupingOptions::Remote,
]
.into_iter()
} }
pub fn group_key(&self, line: &LogLine) -> u64 { pub fn group_key(&self, line: &LogLine) -> u64 {
@ -181,6 +182,8 @@ impl GroupingOptions {
match self { match self {
GroupingOptions::Url => line.url.hash(&mut hasher), GroupingOptions::Url => line.url.hash(&mut hasher),
GroupingOptions::App => line.app.hash(&mut hasher), GroupingOptions::App => line.app.hash(&mut hasher),
GroupingOptions::User => line.user.hash(&mut hasher),
GroupingOptions::Remote => line.remote.hash(&mut hasher),
} }
hasher.finish() hasher.finish()
} }
@ -193,6 +196,12 @@ impl GroupingOptions {
GroupingOptions::App => Groupings::App(AppGrouping { GroupingOptions::App => Groupings::App(AppGrouping {
app: line.app.as_ref(), app: line.app.as_ref(),
}), }),
GroupingOptions::User => Groupings::User(UserGrouping {
user: line.user.as_str(),
}),
GroupingOptions::Remote => Groupings::Remote(RemoteGrouping {
remote: line.remote.as_str(),
}),
} }
} }
@ -200,6 +209,8 @@ impl GroupingOptions {
match self { match self {
GroupingOptions::Url => "Url", GroupingOptions::Url => "Url",
GroupingOptions::App => "App", GroupingOptions::App => "App",
GroupingOptions::User => "User",
GroupingOptions::Remote => "Remote",
} }
} }
@ -207,6 +218,8 @@ impl GroupingOptions {
match self { match self {
GroupingOptions::Url => URL_GROUPING_UI, GroupingOptions::Url => URL_GROUPING_UI,
GroupingOptions::App => APP_GROUPING_UI, GroupingOptions::App => APP_GROUPING_UI,
GroupingOptions::User => USER_GROUPING_UI,
GroupingOptions::Remote => REMOTE_GROUPING_UI,
} }
} }

29
src/grouping/remote.rs Normal file
View file

@ -0,0 +1,29 @@
use crate::app::Filter;
use crate::grouping::{GroupingResult, GroupingUi};
use ratatui::layout::{Alignment, Constraint};
use std::borrow::Cow;
#[derive(PartialEq, Clone)]
pub struct RemoteGrouping<'a> {
pub remote: &'a str,
}
pub const REMOTE_GROUPING_UI: GroupingUi = GroupingUi {
header: &[("Remote", Alignment::Left)],
widths: &[Constraint::Percentage(100)],
};
impl<'a> GroupingResult<'a> for RemoteGrouping<'a> {
fn matches(&self, filter: &Filter) -> bool {
if filter.is_empty() {
return true;
}
filter
.parts()
.all(|filter_part| filter_part.is_match(self.remote))
}
fn render(&self) -> impl Iterator<Item = Cow<'a, str>> {
[self.remote.into()].into_iter()
}
}

29
src/grouping/user.rs Normal file
View file

@ -0,0 +1,29 @@
use crate::app::Filter;
use crate::grouping::{GroupingResult, GroupingUi};
use ratatui::layout::{Alignment, Constraint};
use std::borrow::Cow;
#[derive(PartialEq, Clone)]
pub struct UserGrouping<'a> {
pub user: &'a str,
}
pub const USER_GROUPING_UI: GroupingUi = GroupingUi {
header: &[("User", Alignment::Left)],
widths: &[Constraint::Percentage(100)],
};
impl<'a> GroupingResult<'a> for UserGrouping<'a> {
fn matches(&self, filter: &Filter) -> bool {
if filter.is_empty() {
return true;
}
filter
.parts()
.all(|filter_part| filter_part.is_match(self.user))
}
fn render(&self) -> impl Iterator<Item = Cow<'a, str>> {
[self.user.into()].into_iter()
}
}

View file

@ -53,7 +53,7 @@ impl<'a> PartialEq for LogLine<'a> {
} }
} }
#[derive(Clone)] #[derive(Clone, Hash)]
pub enum LogUser { pub enum LogUser {
None, None,
User(TinyAsciiStr<64>), User(TinyAsciiStr<64>),