Skip to content

Commit 4b22fbd

Browse files
committed
feat: separate detail fields between common and not
1 parent 8e91e6f commit 4b22fbd

File tree

3 files changed

+113
-27
lines changed

3 files changed

+113
-27
lines changed

src/app.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::{
2+
hash::{DefaultHasher, Hash, Hasher},
23
path::PathBuf,
34
sync::{Arc, Mutex},
45
};
@@ -182,7 +183,9 @@ impl LogViewerApp {
182183
return;
183184
};
184185

185-
let Some(selected_values) = data.selected_row_data_as_slice() else {
186+
let Some(selected_values) =
187+
data.selected_row_data_as_slice(self.data_display_options.common_fields())
188+
else {
186189
ui.label("No row Selected");
187190
return;
188191
};
@@ -511,3 +514,9 @@ fn expanding_content(ui: &mut egui::Ui) {
511514
(2.0, ui.visuals().text_color()),
512515
);
513516
}
517+
518+
pub fn calculate_hash<T: Hash + ?Sized>(t: &T) -> u64 {
519+
let mut s = DefaultHasher::new();
520+
t.hash(&mut s);
521+
s.finish()
522+
}

src/app/data.rs

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
use std::collections::BTreeMap;
1+
use std::collections::{BTreeMap, BTreeSet};
22

33
use anyhow::Context;
44

5+
use super::calculate_hash;
6+
57
// TODO 1: Create access method that returns enum indicating value or not
68
// TODO 2: Create an iterator that allows for selection of first fields to show if present
79

@@ -15,7 +17,13 @@ pub struct Data {
1517
pub struct LogRow {
1618
data: BTreeMap<String, serde_json::Value>,
1719
#[serde(skip)]
18-
cached_display_list: Option<Vec<(String, String)>>,
20+
cached_display_list: Option<CachedDisplayInfo>,
21+
}
22+
23+
#[derive(Default, Debug, PartialEq, Eq)]
24+
struct CachedDisplayInfo {
25+
data: Vec<(String, String)>,
26+
common_fields_hash: u64,
1927
}
2028

2129
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
@@ -25,7 +33,7 @@ pub enum FieldContent<'a> {
2533
}
2634

2735
impl<'a> FieldContent<'a> {
28-
const TEXT_FOR_EMPTY: &'static str = "[ --- ]";
36+
pub const TEXT_FOR_EMPTY: &'static str = "[ --- ]";
2937

3038
pub fn display(&self) -> String {
3139
// TODO 4: Revisit implementation to see if a more efficient way can be found (should be benchmarked to see if it's worth it)
@@ -50,25 +58,60 @@ impl LogRow {
5058
}
5159
}
5260

53-
pub fn as_slice(&mut self) -> &[(String, String)] {
54-
// TODO 1: Return FieldContent
61+
pub fn as_slice(&mut self, common_fields: &BTreeSet<String>) -> &[(String, String)] {
62+
self.ensure_cache_is_populated(common_fields);
63+
64+
&self
65+
.cached_display_list
66+
.get_or_insert_with(|| {
67+
unreachable!("should have been initialized above if it was empty")
68+
})
69+
.data
70+
}
71+
72+
fn ensure_cache_is_populated(&mut self, common_fields: &BTreeSet<String>) {
73+
let common_fields_hash = calculate_hash(common_fields);
74+
75+
if let Some(cache) = self.cached_display_list.as_ref() {
76+
if cache.common_fields_hash != common_fields_hash {
77+
// Hash changed cache no longer valid
78+
self.cached_display_list = None;
79+
}
80+
}
81+
5582
if self.cached_display_list.is_none() {
56-
let value = self
83+
// Build data for sorting
84+
let mut data: Vec<(bool, (String, String))> = self
85+
.data
5786
.iter()
58-
.map(|(k, v)| (k.clone(), FieldContent::Present(v).display())) // Use display to keep formatting consistent
87+
.map(|(k, v)| {
88+
(
89+
common_fields.contains(k),
90+
(k.clone(), FieldContent::Present(v).display()),
91+
)
92+
}) // Use display to keep formatting consistent
5993
.collect();
60-
self.cached_display_list = Some(value);
61-
}
6294

63-
self.cached_display_list.get_or_insert_with(|| {
64-
unreachable!("should have been initialized above if it was empty")
65-
})
66-
}
67-
68-
fn iter(&self) -> impl Iterator<Item = (&String, &serde_json::Value)> {
69-
// TODO 4: Determine if here is actually value in making the "main_fields" show first
70-
// TODO 4: Determine if memory wasted here is worth trying to figure out how to use references instead
71-
self.data.iter()
95+
// Add separator for common fields
96+
data.push((
97+
true,
98+
(
99+
format!(" {}", FieldContent::TEXT_FOR_EMPTY), // prefixed with a leading space so it should end up at top of the common section
100+
FieldContent::TEXT_FOR_EMPTY.to_string(),
101+
),
102+
));
103+
104+
// Sort data based on common fields (to group them at the bottom)
105+
data.sort_unstable();
106+
107+
// Remove extra info for sorting
108+
let data = data.into_iter().map(|x| x.1).collect();
109+
110+
self.cached_display_list = Some(CachedDisplayInfo {
111+
data,
112+
common_fields_hash,
113+
});
114+
}
72115
}
73116
}
74117

@@ -77,9 +120,12 @@ impl Data {
77120
&self.rows
78121
}
79122

80-
pub fn selected_row_data_as_slice(&mut self) -> Option<&[(String, String)]> {
123+
pub fn selected_row_data_as_slice(
124+
&mut self,
125+
common_fields: &BTreeSet<String>,
126+
) -> Option<&[(String, String)]> {
81127
let selected_row_index = self.selected_row?;
82-
Some(self.rows[selected_row_index].as_slice())
128+
Some(self.rows[selected_row_index].as_slice(common_fields))
83129
}
84130
}
85131

src/app/data_display_options.rs

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
use std::collections::BTreeSet;
2+
13
#[derive(serde::Deserialize, serde::Serialize, Debug, PartialEq, Eq)]
24
pub struct DataDisplayOptions {
35
main_list_fields: Vec<String>,
6+
7+
/// Lists fields to show last as they are not unique to a request
8+
common_fields: BTreeSet<String>,
9+
410
/// The field to use to highlight other related log entries
511
///
612
/// WARNING: This must be a valid index into the list as this is assumed in method implementations
@@ -14,18 +20,43 @@ impl DataDisplayOptions {
1420
pub fn emphasize_if_matching_field_idx(&self) -> &Option<usize> {
1521
&self.emphasize_if_matching_field_idx
1622
}
23+
pub fn common_fields(&self) -> &BTreeSet<String> {
24+
&self.common_fields
25+
}
1726
}
1827

1928
impl Default for DataDisplayOptions {
2029
fn default() -> Self {
2130
Self {
2231
// TODO 3: Add ability to show, select and reorder selected fields
23-
main_list_fields: vec![
24-
"time".into(),
25-
"request_id".into(),
26-
"otel.name".into(),
27-
"msg".into(),
28-
],
32+
main_list_fields: ["time", "request_id", "otel.name", "msg"]
33+
.into_iter()
34+
.map(String::from)
35+
.collect(),
36+
common_fields: [
37+
"elapsed_milliseconds",
38+
"file",
39+
"hostname",
40+
"http.flavor",
41+
"http.host",
42+
"http.method",
43+
"http.route",
44+
"http.scheme",
45+
"http.target",
46+
"http.user_agent",
47+
"level",
48+
"line",
49+
"name",
50+
"otel.kind",
51+
"pid",
52+
"request_id",
53+
"target",
54+
"time",
55+
"v",
56+
]
57+
.into_iter()
58+
.map(String::from)
59+
.collect(),
2960
emphasize_if_matching_field_idx: Some(1),
3061
}
3162
}

0 commit comments

Comments
 (0)