Skip to content

Commit 5068b01

Browse files
committed
feat: add ability to filter log rows
1 parent b009997 commit 5068b01

File tree

6 files changed

+397
-52
lines changed

6 files changed

+397
-52
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/target
22
/dist
3+
*.snap.new

src/app.rs

Lines changed: 160 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::{
66

77
#[cfg(not(target_arch = "wasm32"))]
88
use anyhow::{bail, Context};
9+
use data::filter::{Comparator, FieldSpecifier, FilterConfig, FilterOn};
910
use egui::{Align, KeyboardShortcut, Modifiers};
1011
use egui_extras::{Column, TableBuilder};
1112
use log::info;
@@ -28,6 +29,7 @@ pub struct LogViewerApp {
2829
shortcut_next: KeyboardShortcut,
2930
shortcut_first: KeyboardShortcut,
3031
shortcut_last: KeyboardShortcut,
32+
shortcut_unfilter: KeyboardShortcut,
3133

3234
#[serde(skip)]
3335
should_scroll: bool,
@@ -48,6 +50,7 @@ impl Default for LogViewerApp {
4850
shortcut_next: KeyboardShortcut::new(Modifiers::NONE, egui::Key::ArrowDown),
4951
shortcut_first: KeyboardShortcut::new(Modifiers::NONE, egui::Key::Home),
5052
shortcut_last: KeyboardShortcut::new(Modifiers::NONE, egui::Key::End),
53+
shortcut_unfilter: KeyboardShortcut::new(Modifiers::NONE, egui::Key::Escape),
5154
should_scroll: Default::default(),
5255
show_last_filename: true,
5356
}
@@ -137,16 +140,18 @@ impl LogViewerApp {
137140
// TODO 3: Figure out if calculating these values only once is worth it.
138141
// TODO 4: Remove hard coded "msg"
139142
let heights: Vec<f32> = data
140-
.rows()
141-
.iter()
143+
.rows_iter()
142144
.map(|x| {
143145
(1f32).max(x.field_value("msg").display().lines().count() as f32)
144146
* text_height
145147
})
146148
.collect();
147149
body.heterogeneous_rows(heights.into_iter(), |mut row| {
148150
let row_index = row.index();
149-
let log_row = &data.rows()[row_index];
151+
let log_row = &data
152+
.rows_iter()
153+
.nth(row_index)
154+
.expect("len was passed above should only be valid indices");
150155

151156
let emphasis_info = if let Some(selected_row) = data.selected_row {
152157
row.set_selected(selected_row == row_index);
@@ -157,7 +162,10 @@ impl LogViewerApp {
157162
&self.data_display_options.main_list_fields()[emphasis_field_idx];
158163
Some((
159164
emphasis_field_idx,
160-
data.rows()[selected_row].field_value(field_name),
165+
data.rows_iter()
166+
.nth(selected_row)
167+
.expect("selected row should always be a valid index")
168+
.field_value(field_name),
161169
))
162170
} else {
163171
None
@@ -444,52 +452,162 @@ impl LogViewerApp {
444452
if ui.input_mut(|i| i.consume_shortcut(&self.shortcut_last)) {
445453
self.move_selected_last();
446454
}
455+
456+
if ui.input_mut(|i| i.consume_shortcut(&self.shortcut_unfilter)) {
457+
if let Some(data) = self.data.as_mut() {
458+
data.unfilter();
459+
}
460+
}
447461
}
448462

449463
fn navigation_and_filtering_ui(&mut self, ui: &mut egui::Ui) {
450464
ui.horizontal(|ui| {
451-
ui.label("Nav:");
452-
if ui
453-
.button("⏪")
454-
.on_hover_text(format!(
455-
"First ({})",
456-
ui.ctx().format_shortcut(&self.shortcut_first)
457-
))
458-
.clicked()
459-
{
460-
self.move_selected_first();
465+
self.navigation_ui(ui);
466+
ui.separator();
467+
self.filtering_ui(ui);
468+
});
469+
}
470+
471+
fn filtering_ui(&mut self, ui: &mut egui::Ui) {
472+
if let Some(data) = self.data.as_mut() {
473+
ui.label("Filter:");
474+
let mut is_filter_enabled = data.filter.is_some();
475+
ui.checkbox(&mut is_filter_enabled, "");
476+
match (is_filter_enabled, data.filter.is_some()) {
477+
(false, false) | (true, true) => {} // Already match
478+
(true, false) => data.filter = Some(Default::default()),
479+
(false, true) => {
480+
data.unfilter();
481+
data.filter = None
482+
}
461483
}
462-
if ui
463-
.button("⬆")
464-
.on_hover_text(format!(
465-
"Previous ({})",
466-
ui.ctx().format_shortcut(&self.shortcut_prev)
467-
))
468-
.clicked()
469-
{
470-
self.move_selected_prev();
484+
let mut should_apply_filter = false;
485+
if is_filter_enabled {
486+
if ui.button("Apply").clicked() {
487+
should_apply_filter = true;
488+
}
489+
if data.is_filtered()
490+
&& ui
491+
.button("Unfilter")
492+
.on_hover_text(format!(
493+
"Clears Filter ({})",
494+
ui.ctx().format_shortcut(&self.shortcut_unfilter)
495+
))
496+
.clicked()
497+
{
498+
data.unfilter();
499+
}
471500
}
472-
if ui
473-
.button("⬇")
474-
.on_hover_text(format!(
475-
"Next ({})",
476-
ui.ctx().format_shortcut(&self.shortcut_next)
477-
))
478-
.clicked()
479-
{
480-
self.move_selected_next();
501+
502+
if let Some(filter) = data.filter.as_mut() {
503+
let FilterConfig {
504+
search_key,
505+
filter_on,
506+
comparator,
507+
} = filter;
508+
ui.label("Search Key: ");
509+
if ui.text_edit_singleline(search_key).lost_focus()
510+
&& ui.input(|i| i.key_pressed(egui::Key::Enter))
511+
{
512+
should_apply_filter = true;
513+
}
514+
515+
ui.spacing();
516+
egui::ComboBox::from_label("")
517+
.selected_text(format!("{}", comparator))
518+
.show_ui(ui, |ui| {
519+
ui.selectable_value(comparator, Comparator::LessThan, "Less than");
520+
ui.selectable_value(
521+
comparator,
522+
Comparator::LessThanEqual,
523+
"Less than equal",
524+
);
525+
ui.selectable_value(comparator, Comparator::Equal, "Equal");
526+
ui.selectable_value(comparator, Comparator::GreaterThan, "Greater than");
527+
ui.selectable_value(
528+
comparator,
529+
Comparator::GreaterThanEqual,
530+
"Greater than equal",
531+
);
532+
ui.selectable_value(comparator, Comparator::NotEqual, "Not equal");
533+
ui.selectable_value(comparator, Comparator::Contains, "Contains");
534+
ui.selectable_value(comparator, Comparator::NotContains, "Not contains");
535+
});
536+
537+
ui.spacing();
538+
let mut is_any = filter_on.is_any();
539+
ui.toggle_value(&mut is_any, "Any");
540+
if is_any && !filter_on.is_any() {
541+
// Toggled on
542+
*filter_on = FilterOn::Any;
543+
}
544+
545+
let mut is_field = filter_on.is_field();
546+
ui.toggle_value(&mut is_field, "Field");
547+
if is_field && !filter_on.is_field() {
548+
// Toggled on
549+
*filter_on = FilterOn::Field(Default::default());
550+
}
551+
552+
if let FilterOn::Field(FieldSpecifier { name }) = filter_on {
553+
ui.spacing();
554+
if ui
555+
.add(egui::TextEdit::singleline(name).hint_text("Name"))
556+
.lost_focus()
557+
&& ui.input(|i| i.key_pressed(egui::Key::Enter))
558+
{
559+
should_apply_filter = true;
560+
}
561+
}
481562
}
482-
if ui
483-
.button("⏩")
484-
.on_hover_text(format!(
485-
"Last ({})",
486-
ui.ctx().format_shortcut(&self.shortcut_last)
487-
))
488-
.clicked()
489-
{
490-
self.move_selected_last();
563+
if should_apply_filter {
564+
data.apply_filter(self.data_display_options.common_fields());
491565
}
492-
});
566+
}
567+
}
568+
569+
fn navigation_ui(&mut self, ui: &mut egui::Ui) {
570+
ui.label("Nav:");
571+
if ui
572+
.button("⏪")
573+
.on_hover_text(format!(
574+
"First ({})",
575+
ui.ctx().format_shortcut(&self.shortcut_first)
576+
))
577+
.clicked()
578+
{
579+
self.move_selected_first();
580+
}
581+
if ui
582+
.button("⬆")
583+
.on_hover_text(format!(
584+
"Previous ({})",
585+
ui.ctx().format_shortcut(&self.shortcut_prev)
586+
))
587+
.clicked()
588+
{
589+
self.move_selected_prev();
590+
}
591+
if ui
592+
.button("⬇")
593+
.on_hover_text(format!(
594+
"Next ({})",
595+
ui.ctx().format_shortcut(&self.shortcut_next)
596+
))
597+
.clicked()
598+
{
599+
self.move_selected_next();
600+
}
601+
if ui
602+
.button("⏩")
603+
.on_hover_text(format!(
604+
"Last ({})",
605+
ui.ctx().format_shortcut(&self.shortcut_last)
606+
))
607+
.clicked()
608+
{
609+
self.move_selected_last();
610+
}
493611
}
494612
fn data_load_ui(&mut self, ui: &mut egui::Ui) {
495613
ui.horizontal(|ui| {

0 commit comments

Comments
 (0)