diff --git a/crates/app/src/session/ui/shared/mod.rs b/crates/app/src/session/ui/shared/mod.rs index e8fa50f08..316841b04 100644 --- a/crates/app/src/session/ui/shared/mod.rs +++ b/crates/app/src/session/ui/shared/mod.rs @@ -405,6 +405,17 @@ impl SessionShared { changed } + /// Removes all filters from this session and tracks recent-session dirtiness once. + /// + /// Returns `true` when at least one filter was removed. + pub fn unapply_all_filters(&mut self, registry: &mut FilterRegistry) -> bool { + let changed = self.filters.unapply_all_filters(registry); + if changed { + self.bump_recent_revision(); + } + changed + } + /// Rebinds an applied filter row to a new registry id and tracks recent-session dirtiness. pub fn rebind_filter(&mut self, current_id: &Uuid, next_id: Uuid) -> bool { let changed = self.filters.rebind_filter(current_id, next_id); @@ -453,6 +464,17 @@ impl SessionShared { changed } + /// Removes all search values from this session and tracks recent-session dirtiness once. + /// + /// Returns `true` when at least one search value was removed. + pub fn unapply_all_search_values(&mut self, registry: &mut FilterRegistry) -> bool { + let changed = self.filters.unapply_all_search_values(registry); + if changed { + self.bump_recent_revision(); + } + changed + } + /// Rebinds an applied search-value row to a new registry id and tracks recent-session dirtiness. pub fn rebind_search_value(&mut self, current_id: &Uuid, next_id: Uuid) -> bool { let changed = self.filters.rebind_search_value(current_id, next_id); @@ -594,6 +616,56 @@ mod tests { assert_eq!(shared.recent_revision(), baseline + 2); } + #[test] + fn unapply_all_filters_bumps_recent_revision_once() { + let mut shared = new_shared(); + let mut registry = FilterRegistry::default(); + let first_filter = FilterDefinition::new(SearchFilter::plain("first")); + let first_id = registry.add_filter(first_filter); + let second_filter = FilterDefinition::new(SearchFilter::plain("second")); + let second_id = registry.add_filter(second_filter); + shared.apply_filter(&mut registry, first_id); + shared.apply_filter(&mut registry, second_id); + let baseline = shared.recent_revision(); + + assert!(shared.unapply_all_filters(&mut registry)); + + assert!(shared.filters.filter_entries.is_empty()); + assert_eq!(registry.filter_usage_count(&first_id), 0); + assert_eq!(registry.filter_usage_count(&second_id), 0); + assert_eq!(registry.filters_map().len(), 2); + assert_eq!(shared.recent_revision(), baseline + 1); + + assert!(!shared.unapply_all_filters(&mut registry)); + assert_eq!(shared.recent_revision(), baseline + 1); + } + + #[test] + fn unapply_all_search_values_bumps_recent_revision_once() { + let mut shared = new_shared(); + let mut registry = FilterRegistry::default(); + let first_filter = SearchFilter::plain("first=(\\d+)").regex(true); + let first_value = SearchValueDefinition::new(first_filter); + let first_id = registry.add_search_value(first_value); + let second_filter = SearchFilter::plain("second=(\\d+)").regex(true); + let second_value = SearchValueDefinition::new(second_filter); + let second_id = registry.add_search_value(second_value); + shared.apply_search_value(&mut registry, first_id); + shared.apply_search_value(&mut registry, second_id); + let baseline = shared.recent_revision(); + + assert!(shared.unapply_all_search_values(&mut registry)); + + assert!(shared.filters.search_value_entries.is_empty()); + assert_eq!(registry.search_value_usage_count(&first_id), 0); + assert_eq!(registry.search_value_usage_count(&second_id), 0); + assert_eq!(registry.search_value_map().len(), 2); + assert_eq!(shared.recent_revision(), baseline + 1); + + assert!(!shared.unapply_all_search_values(&mut registry)); + assert_eq!(shared.recent_revision(), baseline + 1); + } + #[test] fn drop_search_updates_counts() { let mut shared = new_shared(); diff --git a/crates/app/src/session/ui/shared/searching/filters.rs b/crates/app/src/session/ui/shared/searching/filters.rs index e0daeb8af..41bb5f1f2 100644 --- a/crates/app/src/session/ui/shared/searching/filters.rs +++ b/crates/app/src/session/ui/shared/searching/filters.rs @@ -338,6 +338,19 @@ impl FiltersState { false } + /// Removes all filters from the session without deleting registry definitions. + /// + /// Returns `true` when at least one filter was removed. + pub fn unapply_all_filters(&mut self, registry: &mut FilterRegistry) -> bool { + let changed = !self.filter_entries.is_empty(); + let session_id = self.session_id; + for item in self.filter_entries.drain(..) { + registry.unapply_filter_from_session(item.id, session_id); + } + + changed + } + /// Swaps the registry definition behind an applied filter while keeping the /// session-local row state intact. /// @@ -422,6 +435,19 @@ impl FiltersState { false } + /// Removes all search values from the session without deleting registry definitions. + /// + /// Returns `true` when at least one search value was removed. + pub fn unapply_all_search_values(&mut self, registry: &mut FilterRegistry) -> bool { + let changed = !self.search_value_entries.is_empty(); + let session_id = self.session_id; + for item in self.search_value_entries.drain(..) { + registry.unapply_search_value_from_session(item.id, session_id); + } + + changed + } + /// Swaps the registry definition behind an applied search value while /// keeping the session-local row state intact. /// diff --git a/crates/app/src/session/ui/side_panel/filters.rs b/crates/app/src/session/ui/side_panel/filters.rs index a3620c6aa..0048c43a2 100644 --- a/crates/app/src/session/ui/side_panel/filters.rs +++ b/crates/app/src/session/ui/side_panel/filters.rs @@ -43,9 +43,11 @@ enum FilterPanelAction { ToggleFilter(Uuid, bool), EditFilterFlags(Uuid, FilterFlags), RemoveFilter(Uuid), + RemoveAllFilters, MoveFilterToValue(Uuid), ToggleSearchValue(Uuid, bool), RemoveSearchValue(Uuid), + RemoveAllSearchValues, MoveValueToFilter(Uuid), /// Requests the parent session to capture the current filters and charts. CapturePreset, @@ -341,6 +343,11 @@ impl FiltersUi { ui.close(); } + if ui.button("Remove All Filters").clicked() { + *side_action = Some(FilterPanelAction::RemoveAllFilters); + ui.close(); + } + let mut move_btn = ui .add_enabled( row.search_value_eligibility.is_eligible(), @@ -435,6 +442,11 @@ impl FiltersUi { ui.close(); } + if ui.button("Remove All Charts").clicked() { + *side_action = Some(FilterPanelAction::RemoveAllSearchValues); + ui.close(); + } + if ui.button("Move to Filter").clicked() { *side_action = Some(FilterPanelAction::MoveValueToFilter(row.id)); ui.close(); @@ -1076,6 +1088,20 @@ impl FiltersUi { .into_iter() .for_each(|cmd| _ = actions.try_send_command(&self.cmd_tx, cmd)); } + FilterPanelAction::RemoveAllFilters => { + self.filter_edit_state = None; + if self + .selected_item + .is_some_and(|item| matches!(item, SelectedSidebarItem::Filter(_))) + { + self.selected_item = None; + } + shared.unapply_all_filters(registry); + shared + .sync_search(registry, SearchSyncTarget::Filter) + .into_iter() + .for_each(|cmd| _ = actions.try_send_command(&self.cmd_tx, cmd)); + } FilterPanelAction::MoveFilterToValue(filter_id) => { self.clear_filter_edit_for(filter_id); let was_applied = shared.filters.is_filter_applied(&filter_id); @@ -1114,6 +1140,20 @@ impl FiltersUi { .into_iter() .for_each(|cmd| _ = actions.try_send_command(&self.cmd_tx, cmd)); } + FilterPanelAction::RemoveAllSearchValues => { + self.search_value_edit_state = None; + if self + .selected_item + .is_some_and(|item| matches!(item, SelectedSidebarItem::SearchValue(_))) + { + self.selected_item = None; + } + shared.unapply_all_search_values(registry); + shared + .sync_search(registry, SearchSyncTarget::SearchValue) + .into_iter() + .for_each(|cmd| _ = actions.try_send_command(&self.cmd_tx, cmd)); + } FilterPanelAction::MoveValueToFilter(value_id) => { self.clear_search_value_edit_for(value_id); let was_applied = shared.filters.is_search_value_applied(&value_id);