lazy histograms for groups list

This commit is contained in:
Robin Appelman 2025-08-16 22:44:45 +02:00
commit 06704ebc6a
4 changed files with 91 additions and 51 deletions

2
.gitignore vendored
View file

@ -3,5 +3,5 @@ target
.env
result
*.log
profile.json
profile.*
*.out.*

View file

@ -10,25 +10,25 @@ use ratatui::prelude::{StatefulWidget, Widget};
use ratatui::text::Text;
use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
pub struct LogsByIdentifier<'a> {
pub struct DistinctLogs<'a> {
lines: &'a [&'a LogLine<'a>],
filter: &'a Filter,
grouping: DistinctLogGrouping,
}
pub fn logs_by_identifier<'a>(
pub fn distinct_logs<'a>(
lines: &'a [&'a LogLine<'a>],
filter: &'a Filter,
grouping: DistinctLogGrouping,
) -> LogsByIdentifier<'a> {
LogsByIdentifier {
) -> DistinctLogs<'a> {
DistinctLogs {
lines,
filter,
grouping,
}
}
impl StatefulWidget for LogsByIdentifier<'_> {
impl StatefulWidget for DistinctLogs<'_> {
type State = ScrollbarTableState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State)

View file

@ -1,66 +1,106 @@
use crate::app::Filter;
use crate::grouping::{GroupingResult, GroupingUi, LogGrouping};
use crate::ui::style::TABLE_HEADER_STYLE;
use crate::ui::table::ScrollbarTable;
use crate::ui::table::{ScrollbarTable, ScrollbarTableState};
use ratatui::prelude::*;
use ratatui::widgets::{Cell, Row};
use std::borrow::Cow;
use std::ops::RangeInclusive;
use time::OffsetDateTime;
pub fn grouping_list<'a>(
items: &[LogGrouping<'a>],
ui: &GroupingUi,
pub struct GroupingList<'a, 'b> {
items: &'b [LogGrouping<'a>],
ui: &'b GroupingUi,
time_range: RangeInclusive<OffsetDateTime>,
filter: &Filter,
) -> ScrollbarTable<'a> {
let header = ui
.header
.iter()
.copied()
.chain([("Time", Alignment::Left), ("Count", Alignment::Left)]);
filter: &'b Filter,
}
let header = header
.map(|(text, align)| Text::from(text).alignment(align))
.map(Cell::from)
.collect::<Row>()
.style(TABLE_HEADER_STYLE)
.height(1);
let items: Vec<Row<'a>> = items
.iter()
.filter(|result| result.matches(filter))
.map(|result| grouped_row(result, time_range.clone()))
.collect();
ScrollbarTable::new(
pub fn grouping_list<'a, 'b>(
items: &'b [LogGrouping<'a>],
ui: &'b GroupingUi,
time_range: RangeInclusive<OffsetDateTime>,
filter: &'b Filter,
) -> GroupingList<'a, 'b> {
GroupingList {
items,
ui.widths
ui,
time_range,
filter,
}
}
impl StatefulWidget for GroupingList<'_, '_> {
type State = ScrollbarTableState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State)
where
Self: Sized,
{
let header = self
.ui
.header
.iter()
.copied()
.chain([Constraint::Length(10), Constraint::Min(10)]),
)
.header(header)
.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);
let items: Vec<_> = self
.items
.iter()
.filter(|result| result.matches(self.filter))
.enumerate()
.map(|(i, result)| {
grouped_row(
result,
self.time_range.clone(),
i.abs_diff(state.selected()) < 100,
)
})
.collect();
let table = ScrollbarTable::new(
items,
self.ui
.widths
.iter()
.copied()
.chain([Constraint::Length(10), Constraint::Min(10)]),
)
.header(header);
table.render(area, buf, state);
}
}
fn grouped_row<'a>(
grouping: &LogGrouping<'a>,
time_range: RangeInclusive<OffsetDateTime>,
is_in_view: bool,
) -> 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).to_string()),
Cow::from(grouping.count().to_string()),
]);
Row::new(columns).height(match_result.height() as u16)
if is_in_view {
if let Some(match_result) = &grouping.result {
let grouping_columns = match_result.render();
let columns = grouping_columns.chain([
Cow::from(grouping.sparkline(time_range).to_string()),
Cow::from(grouping.count().to_string()),
]);
Row::new(columns).height(match_result.height() as u16)
} else {
Row::new([
Text::from(grouping.name.unwrap_or_default()),
Text::from(""),
Text::from(""),
Text::from(grouping.sparkline(time_range).to_string()),
Text::from(grouping.count().to_string()),
])
}
} else {
Row::new([
Text::from(grouping.name.unwrap_or_default()),
Text::from(""),
Text::from(""),
Text::from(grouping.sparkline(time_range).to_string()),
Text::from(grouping.count().to_string()),
])
Row::default()
}
}

View file

@ -1,7 +1,7 @@
use crate::app::App;
use crate::error::UiError;
use crate::grouping::LogGrouping;
use crate::ui::by_identifier::logs_by_identifier;
use crate::ui::distinct_logs::distinct_logs;
use crate::ui::error_list::error_list;
use crate::ui::footer::footer;
use crate::ui::grouping_list::grouping_list;
@ -28,7 +28,7 @@ use std::io;
use std::io::stdout;
use std::panic::{set_hook, take_hook};
mod by_identifier;
mod distinct_logs;
mod error_list;
mod footer;
mod grouping_list;
@ -186,7 +186,7 @@ fn ui(frame: &mut Frame, app: &App, state: &mut UiState) {
..
}) => {
frame.render_stateful_widget(
logs_by_identifier(lines, filter, *grouping),
distinct_logs(lines, filter, *grouping),
layout[0].union(layout[1]),
table_state,
);