mirror of
https://codeberg.org/icewind/logsmash.git
synced 2026-06-04 02:24:11 +02:00
work towards generic grouping
This commit is contained in:
parent
4f9e5df792
commit
3943407b9b
13 changed files with 309 additions and 202 deletions
|
|
@ -27,7 +27,7 @@ pub fn footer<'a>(app: &App<'a>, params: FooterParams<'a>) -> Table<'a> {
|
|||
Table::new(
|
||||
[Row::new([
|
||||
Text::from(help(page)),
|
||||
Text::from(format!("{} unmatched items", app.unmatched.count())),
|
||||
Text::from(format!("{} unmatched items", app.unmatched_count)),
|
||||
Text::from(format!("{} parse errors", app.error_count())),
|
||||
])],
|
||||
widths,
|
||||
|
|
|
|||
59
src/ui/grouping_list.rs
Normal file
59
src/ui/grouping_list.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
use crate::app::Filter;
|
||||
use crate::grouping::{GroupedLine, GroupingResult, LogGrouping};
|
||||
use crate::ui::style::TABLE_HEADER_STYLE;
|
||||
use crate::ui::table::ScrollbarTable;
|
||||
use ratatui::prelude::*;
|
||||
use ratatui::widgets::{Cell, Row};
|
||||
use std::borrow::Cow;
|
||||
use std::iter::once;
|
||||
use std::ops::RangeInclusive;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
pub fn grouping_list<'a, G: GroupingResult>(
|
||||
all: &'a LogGrouping<'a, G>,
|
||||
items: &'a [LogGrouping<'a, G>],
|
||||
time_range: RangeInclusive<OffsetDateTime>,
|
||||
filter: &Filter,
|
||||
) -> ScrollbarTable<'a> {
|
||||
let header = G::Lines::HEADER
|
||||
.iter()
|
||||
.copied()
|
||||
.chain([("Time", Alignment::Left), ("Count", Alignment::Left)]);
|
||||
|
||||
let header = header
|
||||
.map(|(text, align)| Text::from(text).alignment(align))
|
||||
.map(Cell::from)
|
||||
.collect::<Row>()
|
||||
.style(TABLE_HEADER_STYLE)
|
||||
.height(1);
|
||||
|
||||
ScrollbarTable::new(
|
||||
once(all)
|
||||
.chain(items.iter().filter(|result| result.matches(filter)))
|
||||
.map(|result| grouped_row(result, time_range.clone())),
|
||||
G::Lines::WIDTHS,
|
||||
)
|
||||
.header(header)
|
||||
}
|
||||
|
||||
fn grouped_row<'a, G: GroupingResult>(
|
||||
grouping: &'a LogGrouping<'a, G>,
|
||||
time_range: RangeInclusive<OffsetDateTime>,
|
||||
) -> Row<'a> {
|
||||
if let Some(match_result) = &grouping.result {
|
||||
let grouping_columns = match_result.render();
|
||||
let columns = grouping_columns.chain([
|
||||
Cow::from(grouping.sparkline(time_range)),
|
||||
Cow::from(grouping.count().to_string()),
|
||||
]);
|
||||
Row::new(columns).height(match_result.len() as u16)
|
||||
} else {
|
||||
Row::new([
|
||||
Text::from(grouping.name.unwrap_or_default()),
|
||||
Text::from(""),
|
||||
Text::from(""),
|
||||
Text::from(grouping.sparkline(time_range)),
|
||||
Text::from(grouping.count().to_string()),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
use crate::app::{App, Filter, LogMatch};
|
||||
use crate::ui::style::TABLE_HEADER_STYLE;
|
||||
use crate::ui::table::ScrollbarTable;
|
||||
use itertools::Either;
|
||||
use ratatui::prelude::*;
|
||||
use ratatui::widgets::{Cell, Row};
|
||||
use std::fmt::Write;
|
||||
use std::iter::{empty, once};
|
||||
|
||||
pub fn match_list<'a>(app: &'a App<'a>, filter: &Filter) -> ScrollbarTable<'a> {
|
||||
let header = [
|
||||
Text::from("Statement"),
|
||||
Text::from("File"),
|
||||
Text::from("Line").alignment(Alignment::Right),
|
||||
Text::from("Time"),
|
||||
Text::from("Count"),
|
||||
]
|
||||
.into_iter()
|
||||
.map(Cell::from)
|
||||
.collect::<Row>()
|
||||
.style(TABLE_HEADER_STYLE)
|
||||
.height(1);
|
||||
|
||||
let widths = [
|
||||
Constraint::Percentage(70),
|
||||
Constraint::Percentage(30),
|
||||
Constraint::Min(6),
|
||||
Constraint::Length(10),
|
||||
Constraint::Min(10),
|
||||
];
|
||||
|
||||
let all = log_row(&app.all, app, "All lines");
|
||||
let unmatched = if app.unmatched.count() == 0 {
|
||||
Either::Right(empty())
|
||||
} else {
|
||||
Either::Left(once(log_row(&app.unmatched, app, "Unmatched lines")))
|
||||
};
|
||||
|
||||
ScrollbarTable::new(
|
||||
once(all)
|
||||
.chain(
|
||||
app.matches
|
||||
.iter()
|
||||
.filter(|result| result.matches(filter))
|
||||
.map(|result| log_row(result, app, "")),
|
||||
)
|
||||
.chain(unmatched),
|
||||
widths,
|
||||
)
|
||||
.header(header)
|
||||
}
|
||||
|
||||
fn log_row<'a>(result: &'a LogMatch, app: &'a App, name: &'static str) -> Row<'a> {
|
||||
if let Some(match_result) = &result.result {
|
||||
let mut message = String::new();
|
||||
let mut paths = String::new();
|
||||
let mut lines = String::new();
|
||||
for statement in match_result.iter() {
|
||||
writeln!(&mut message, "{}", statement.message()).ok();
|
||||
writeln!(&mut paths, "{}", statement.path()).ok();
|
||||
writeln!(&mut lines, "{}", statement.line()).ok();
|
||||
}
|
||||
Row::new([
|
||||
Text::from(message),
|
||||
Text::from(paths),
|
||||
Text::from(lines).alignment(Alignment::Right),
|
||||
Text::from(result.sparkline(app)),
|
||||
Text::from(result.count().to_string()),
|
||||
])
|
||||
.height(match_result.count() as u16)
|
||||
} else {
|
||||
Row::new([
|
||||
Text::from(name),
|
||||
Text::from(""),
|
||||
Text::from(""),
|
||||
Text::from(result.sparkline(app)),
|
||||
Text::from(result.count().to_string()),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
use crate::app::App;
|
||||
use crate::error::UiError;
|
||||
use crate::matcher::MatchResult;
|
||||
use crate::ui::error_list::error_list;
|
||||
use crate::ui::footer::footer;
|
||||
use crate::ui::grouped_logs::grouped_logs;
|
||||
use crate::ui::grouping_list::grouping_list;
|
||||
use crate::ui::histogram::UiHistogram;
|
||||
use crate::ui::input::handle_events;
|
||||
use crate::ui::match_list::match_list;
|
||||
use crate::ui::single_log::single_log;
|
||||
use crate::ui::single_match::grouped_lines;
|
||||
use crate::ui::state::{
|
||||
|
|
@ -27,9 +28,9 @@ use std::panic::{set_hook, take_hook};
|
|||
mod error_list;
|
||||
mod footer;
|
||||
mod grouped_logs;
|
||||
mod grouping_list;
|
||||
mod histogram;
|
||||
mod input;
|
||||
mod match_list;
|
||||
mod single_log;
|
||||
mod single_match;
|
||||
mod state;
|
||||
|
|
@ -116,16 +117,18 @@ fn ui(frame: &mut Frame, app: &App, state: &mut UiState) {
|
|||
}) => {
|
||||
let selected = table_state.selected();
|
||||
let histogram = if selected == 0 {
|
||||
app.all.histogram(app)
|
||||
} else if selected < app.matches.len() + 1 {
|
||||
let log_match = &app.matches[selected - 1];
|
||||
log_match.histogram(app)
|
||||
app.all.histogram(app.time_range())
|
||||
} else {
|
||||
app.unmatched.histogram(app)
|
||||
let log_match = &app.matches[selected - 1];
|
||||
log_match.histogram(app.time_range())
|
||||
};
|
||||
|
||||
frame.render_widget(UiHistogram::new(histogram), layout[0]);
|
||||
frame.render_stateful_widget(match_list(app, filter), layout[1], table_state);
|
||||
frame.render_stateful_widget(
|
||||
grouping_list::<MatchResult>(&app.all, &app.matches, app.time_range(), filter),
|
||||
layout[1],
|
||||
table_state,
|
||||
);
|
||||
frame.render_widget(footer(app, state.footer_params()), layout[2]);
|
||||
}
|
||||
UiState::Match(MatchState {
|
||||
|
|
@ -141,7 +144,10 @@ fn ui(frame: &mut Frame, app: &App, state: &mut UiState) {
|
|||
&result.grouped[selected - 1]
|
||||
};
|
||||
|
||||
frame.render_widget(UiHistogram::new(selected_group.histogram(app)), layout[0]);
|
||||
frame.render_widget(
|
||||
UiHistogram::new(selected_group.histogram(app.time_range())),
|
||||
layout[0],
|
||||
);
|
||||
frame.render_stateful_widget(
|
||||
grouped_lines(app, result, filter),
|
||||
layout[1],
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
use crate::app::{App, Filter, LineSet, LogMatch};
|
||||
use crate::app::{App, Filter, LineSet};
|
||||
use crate::grouping::LogGrouping;
|
||||
use crate::matcher::MatchResult;
|
||||
use crate::ui::style::TABLE_HEADER_STYLE;
|
||||
use crate::ui::table::{ScrollbarTable, ScrollbarTableState};
|
||||
use ratatui::buffer::Buffer;
|
||||
|
|
@ -7,10 +9,12 @@ use ratatui::prelude::StatefulWidget;
|
|||
use ratatui::text::Text;
|
||||
use ratatui::widgets::{Cell, Row};
|
||||
use std::iter::once;
|
||||
use std::ops::RangeInclusive;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
pub fn grouped_lines<'a>(
|
||||
app: &'a App<'a>,
|
||||
log_match: &'a LogMatch,
|
||||
log_match: &'a LogGrouping<'a, MatchResult>,
|
||||
filter: &'a Filter,
|
||||
) -> SingleMatchTable<'a> {
|
||||
SingleMatchTable {
|
||||
|
|
@ -22,7 +26,7 @@ pub fn grouped_lines<'a>(
|
|||
|
||||
pub struct SingleMatchTable<'a> {
|
||||
app: &'a App<'a>,
|
||||
log_match: &'a LogMatch<'a>,
|
||||
log_match: &'a LogGrouping<'a, MatchResult>,
|
||||
filter: &'a Filter,
|
||||
}
|
||||
|
||||
|
|
@ -59,7 +63,7 @@ impl StatefulWidget for SingleMatchTable<'_> {
|
|||
Text::from("All lines"),
|
||||
Text::from(""),
|
||||
Text::from(""),
|
||||
Text::from(self.log_match.sparkline(self.app)),
|
||||
Text::from(self.log_match.sparkline(self.app.time_range())),
|
||||
Text::from(self.log_match.count().to_string()),
|
||||
]))
|
||||
.chain(
|
||||
|
|
@ -68,7 +72,11 @@ impl StatefulWidget for SingleMatchTable<'_> {
|
|||
.filter(|group| group.matches(self.filter))
|
||||
.enumerate()
|
||||
.map(|(i, group)| {
|
||||
group_row(self.app, group, i.abs_diff(state.selected()) < 100)
|
||||
group_row(
|
||||
self.app.time_range(),
|
||||
group,
|
||||
i.abs_diff(state.selected()) < 100,
|
||||
)
|
||||
}),
|
||||
),
|
||||
widths,
|
||||
|
|
@ -78,7 +86,11 @@ impl StatefulWidget for SingleMatchTable<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
fn group_row<'a>(app: &'a App, group: &'a LineSet, is_in_view: bool) -> Row<'a> {
|
||||
fn group_row<'a>(
|
||||
time_range: RangeInclusive<OffsetDateTime>,
|
||||
group: &'a LineSet,
|
||||
is_in_view: bool,
|
||||
) -> Row<'a> {
|
||||
if is_in_view {
|
||||
let line = group.lines[0];
|
||||
|
||||
|
|
@ -86,7 +98,7 @@ fn group_row<'a>(app: &'a App, group: &'a LineSet, is_in_view: bool) -> Row<'a>
|
|||
Text::from(line.level.as_str()),
|
||||
Text::from(line.app.as_ref()),
|
||||
Text::from(line.display()),
|
||||
Text::from(group.sparkline(app)),
|
||||
Text::from(group.sparkline(time_range)),
|
||||
Text::from(group.len().to_string()),
|
||||
])
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
use crate::app::{App, Filter, LogMatch, EMPTY_FILTER};
|
||||
use crate::app::{App, Filter, EMPTY_FILTER};
|
||||
use crate::error::ParseError;
|
||||
use crate::grouping::LogGrouping;
|
||||
use crate::logfile::logline::{FullLogLine, LogLine};
|
||||
use crate::matcher::MatchResult;
|
||||
use crate::ui::footer::FooterParams;
|
||||
use crate::ui::input::{PopMode, UiEvent};
|
||||
use crate::ui::table::ScrollbarTableState;
|
||||
|
|
@ -45,19 +47,16 @@ impl<'a> MatchListState<'a> {
|
|||
fn enter(self, selected: usize, app: &'a App) -> UiState<'a> {
|
||||
let result = if selected == 0 {
|
||||
&app.all
|
||||
} else if selected <= app.matches.len() {
|
||||
if self.filter.is_empty() {
|
||||
&app.matches[selected - 1]
|
||||
} else {
|
||||
app.matches
|
||||
.iter()
|
||||
.filter(|log_match| log_match.matches(&self.filter))
|
||||
.nth(selected - 1)
|
||||
.unwrap_or(&app.unmatched)
|
||||
}
|
||||
} else if self.filter.is_empty() {
|
||||
&app.matches[selected - 1]
|
||||
} else {
|
||||
&app.unmatched
|
||||
app.matches
|
||||
.iter()
|
||||
.filter(|log_match| log_match.matches(&self.filter))
|
||||
.nth(selected - 1)
|
||||
.unwrap_or(app.matches.last().unwrap())
|
||||
};
|
||||
|
||||
let table_state = ScrollbarTableState::new(result.grouped.len() + 1);
|
||||
UiState::Match(MatchState {
|
||||
result,
|
||||
|
|
@ -77,7 +76,7 @@ impl PartialEq for MatchListState<'_> {
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct MatchState<'a> {
|
||||
pub result: &'a LogMatch<'a>,
|
||||
pub result: &'a LogGrouping<'a, MatchResult>,
|
||||
pub table_state: ScrollbarTableState,
|
||||
pub previous: Box<UiState<'a>>,
|
||||
pub filter: Filter,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue