Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions application/apps/indexer/gui/application/src/host/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,11 +192,13 @@ impl Host {
);
}

let filters = &registry.filters;
let state = session.capture_opened_recent_state(filters);
let snapshot = recent_registration.into_snapshot(state);
if let Some(recent_registration) = recent_registration {
let filters = &registry.filters;
let state = session.capture_opened_recent_state(filters);
let snapshot = recent_registration.into_snapshot(state);

self.storage.recent_sessions.register_session(snapshot);
self.storage.recent_sessions.register_session(snapshot);
}
}
HostMessage::MultiFilesSetup(state) => self
.state
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ mod tests {
id: session_id,
title: "test".to_owned(),
parser: ParserNames::Text,
raw_export_supported: false,
};

SessionShared::new(session_info, observe_op)
Expand Down
47 changes: 46 additions & 1 deletion application/apps/indexer/gui/application/src/session/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use session_core::state::IndexedNavigation;
use stypes::GrabbedElement;
use uuid::Uuid;

use crate::host::ui::session_setup::state::sources::StreamConfig;
use crate::host::ui::{
session_setup::state::sources::StreamConfig, storage::RecentSessionStateSnapshot,
};
use crate::session::{error::SessionError, types::attachment};

/// Represents session specific commands to be sent from UI to session service.
Expand Down Expand Up @@ -98,6 +100,27 @@ pub enum SessionCommand {
/// configuration of the current session as basis
StartSessionWithSource { source_uuid: String },

/// Export raw source data for the selected target.
ExportRaw {
operation_id: Uuid,
destination: PathBuf,
target: ExportTarget,
},

/// Export rendered text logs for the selected target.
ExportText {
operation_id: Uuid,
destination: PathBuf,
target: ExportTarget,
options: Box<TextExportOptions>,
},

/// Export current search results to a generated source and open it in a new session tab.
OpenSearchResultsAsNewTab {
operation_id: Uuid,
restore_state: RecentSessionStateSnapshot,
},

/// Cancel the running operation with the given id.
CancelOperation { id: Uuid },
/// Gracefully terminate the session service.
Expand All @@ -109,3 +132,25 @@ pub enum AttachSource {
Files(Vec<PathBuf>),
Stream(Box<StreamConfig>),
}

#[derive(Debug)]
pub enum ExportTarget {
/// All stream rows in the main table.
All,
/// Current indexed lower-table rows.
Indexed,
/// Original stream row positions selected by the UI.
Rows(Vec<u64>),
}

/// Options for rendered text export.
#[derive(Debug, Clone)]
pub enum TextExportOptions {
/// Export full rendered rows without column filtering.
FullRows,
/// Export selected table columns joined by the provided delimiter.
Table {
columns: Vec<usize>,
delimiter: String,
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//! Shared session logic used by UI and service layers.

pub mod search_results_tab;
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
//! Shared mode selection for opening exported search results as a new session tab.

use stypes::{FileFormat, ObserveOrigin};

use crate::host::common::parsers::ParserNames;

/// Export/reopen strategy for search results opened as a generated tab.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SearchResultsTabMode {
/// Export rendered text and reopen it as plain text while keeping the generic tab label.
PreserveText,
/// Export raw DLT bytes and reopen them with the original DLT parser configuration.
PreserveDltBinary,
/// Export rendered text and reopen it as a plain text tab.
Text,
}

impl SearchResultsTabMode {
/// Resolves the export/reopen strategy for the provided parser and observed sources.
pub fn resolve_from<'a>(
parser: ParserNames,
origins: impl IntoIterator<Item = &'a ObserveOrigin>,
) -> Self {
// Raw exports are only preserved when the exported bytes are a valid source for the
// same parser.
// * PCAP/PCAPNG exports are not valid PCAP containers for both DLT and SomeIP
// * streams cannot be replayed from exported ranges.
// * Plugin sources open as rendered text for now.

let mut origins = origins.into_iter();
let Some(first_origin) = origins.next() else {
debug_assert!(
false,
"search-results tab mode requires at least one origin"
);

return SearchResultsTabMode::Text;
};

let Some(format) = first_file_format(first_origin) else {
return SearchResultsTabMode::Text;
};

match (parser, format) {
(ParserNames::Text, FileFormat::Text) => SearchResultsTabMode::PreserveText,
(ParserNames::Dlt, FileFormat::Binary) => SearchResultsTabMode::PreserveDltBinary,
_ => SearchResultsTabMode::Text,
}
}

/// Returns the context-menu label for this mode.
pub const fn context_menu_label(self) -> &'static str {
match self {
SearchResultsTabMode::PreserveText | SearchResultsTabMode::PreserveDltBinary => {
"Open Search Results as New Tab"
}
SearchResultsTabMode::Text => "Open Search Results as New Text Tab",
}
}
}

/// Returns the file format that participates in mode selection.
fn first_file_format(origin: &ObserveOrigin) -> Option<FileFormat> {
match origin {
ObserveOrigin::File(_, format, _) => Some(*format),
ObserveOrigin::Concat(items) => {
let Some((_, format, _)) = items.first() else {
debug_assert!(false, "concat origin requires at least one source");
return None;
};
Some(*format)
}
ObserveOrigin::Stream(_, _) => None,
}
}

#[cfg(test)]
mod tests {
use std::path::PathBuf;

use stypes::{FileFormat, ObserveOrigin, TCPTransportConfig, Transport, UDPTransportConfig};

use super::*;

fn file(format: FileFormat) -> ObserveOrigin {
ObserveOrigin::File(String::from("source"), format, PathBuf::from("source.log"))
}

fn concat(format: FileFormat) -> ObserveOrigin {
ObserveOrigin::Concat(vec![
(String::from("first"), format, PathBuf::from("first.log")),
(
String::from("second"),
FileFormat::PcapNG,
PathBuf::from("second.pcapng"),
),
])
}

fn stream() -> ObserveOrigin {
ObserveOrigin::Stream(
String::from("stream"),
Transport::TCP(TCPTransportConfig {
bind_addr: String::from("127.0.0.1:5555"),
}),
)
}

#[test]
fn text_file_preserves_text() {
assert_eq!(
SearchResultsTabMode::resolve_from(ParserNames::Text, &[file(FileFormat::Text)]),
SearchResultsTabMode::PreserveText
);
}

#[test]
fn text_concat_preserves_text() {
assert_eq!(
SearchResultsTabMode::resolve_from(ParserNames::Text, &[concat(FileFormat::Text)]),
SearchResultsTabMode::PreserveText
);
}

#[test]
fn text_stream_opens_text() {
assert_eq!(
SearchResultsTabMode::resolve_from(ParserNames::Text, &[stream()]),
SearchResultsTabMode::Text
);
}

#[test]
fn dlt_binary_file_preserves_binary() {
assert_eq!(
SearchResultsTabMode::resolve_from(ParserNames::Dlt, &[file(FileFormat::Binary)]),
SearchResultsTabMode::PreserveDltBinary
);
}

#[test]
fn dlt_binary_concat_preserves_binary() {
assert_eq!(
SearchResultsTabMode::resolve_from(ParserNames::Dlt, &[concat(FileFormat::Binary)]),
SearchResultsTabMode::PreserveDltBinary
);
}

#[test]
fn dlt_pcap_opens_text() {
for format in [FileFormat::PcapLegacy, FileFormat::PcapNG] {
assert_eq!(
SearchResultsTabMode::resolve_from(ParserNames::Dlt, &[file(format)]),
SearchResultsTabMode::Text
);
}
}

#[test]
fn dlt_stream_opens_text() {
assert_eq!(
SearchResultsTabMode::resolve_from(ParserNames::Dlt, &[stream()]),
SearchResultsTabMode::Text
);
}

#[test]
fn someip_sources_open_text() {
let origins = [
file(FileFormat::Binary),
file(FileFormat::PcapNG),
ObserveOrigin::Stream(
String::from("stream"),
Transport::UDP(UDPTransportConfig {
bind_addr: String::from("127.0.0.1:5555"),
multicast: Vec::new(),
}),
),
];

for origin in origins {
assert_eq!(
SearchResultsTabMode::resolve_from(ParserNames::SomeIP, &[origin]),
SearchResultsTabMode::Text
);
}
}

#[test]
fn plugin_sources_open_text() {
for origin in [file(FileFormat::Text), file(FileFormat::Binary), stream()] {
assert_eq!(
SearchResultsTabMode::resolve_from(ParserNames::Plugins, &[origin]),
SearchResultsTabMode::Text
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ impl ServiceSenders {

evaluate_send_res(&self.egui_ctx, res)
}

/// Create [`SharedSenders`] by cloning the channels shared with child sessions.
pub fn get_shared_senders(&self) -> SharedSenders {
SharedSenders::new(
self.host_message_tx.clone(),
self.notification_tx.clone(),
self.egui_ctx.clone(),
)
}
}

/// Initialize communication channels for session application.
Expand Down
14 changes: 11 additions & 3 deletions application/apps/indexer/gui/application/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{
};

pub mod command;
pub mod common;
pub mod communication;
pub mod error;
pub mod message;
Expand Down Expand Up @@ -52,19 +53,26 @@ pub struct SessionUiInit {
/// Recent-session runtime state needed by one live session.
#[derive(Debug)]
pub struct RecentSessionRuntimeInit {
/// Optional tracking data for sessions that participate in recent-session storage.
pub tracking: Option<RecentSessionTrackingInit>,
/// Additional startup observe operations attached before first render.
pub additional_observe_ops: Vec<ObserveOperation>,
}

/// Recent-session tracking data for one live session.
#[derive(Debug)]
pub struct RecentSessionTrackingInit {
/// Stable identity of the recent-session entry owned by this live session.
pub source_key: Arc<str>,
/// Whether this source shape supports recent bookmark persistence.
pub supports_bookmarks: bool,
/// Additional startup observe operations attached before first render.
pub additional_observe_ops: Vec<ObserveOperation>,
}

/// Host-owned recent-session data associated with a spawned session.
#[derive(Debug)]
pub struct SpawnedRecentSession {
/// Static recent-session metadata owned by the host.
pub registration: RecentSessionRegistration,
pub registration: Option<RecentSessionRegistration>,
/// Optional session state to restore on startup.
pub restore_state: Option<RecentSessionStateSnapshot>,
}
Loading