From aa37f0fbfbf3333a3a909d55156caaf559684893 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 25 Jul 2026 11:03:44 +0000 Subject: [PATCH] Wire up autocomplete for free-text filter fields FilterStateMachine has carried a field_values map and a set_field_values setter for autocompleting non-primary free-text fields, but nothing ever called the setter, so the map was always empty and value_candidates_for_field returned no suggestions for those fields. Populate it in sync_panel alongside the existing set_primary_values calls, so owners and tags (Dag), operator (TaskInstance) and endpoint (AirflowConfig) suggest the values actually present in the loaded data. Candidates are the individual values rather than the joined display string, since that is what a user types: owners renders as "alice, bob" but you filter with 'owners:alice'. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_0127jzUgqqy8JVX5RydfQxh8 --- src/app/state/sync.rs | 56 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/app/state/sync.rs b/src/app/state/sync.rs index 53899206..137b6ff7 100644 --- a/src/app/state/sync.rs +++ b/src/app/state/sync.rs @@ -1,5 +1,13 @@ use super::{App, Panel}; +/// Collect autocomplete candidates: sorted, de-duplicated, blanks dropped. +fn distinct(values: impl Iterator) -> Vec { + let mut values: Vec = values.filter(|v| !v.is_empty()).collect(); + values.sort_unstable(); + values.dedup(); + values +} + impl App { /// Sync a specific panel's data from `environment_state`. pub fn sync_panel(&mut self, panel: &Panel) { @@ -15,6 +23,23 @@ impl App { .map(|d| d.dag_id.to_string()) .collect(); self.dags.table.filter.set_primary_values("dag_id", dag_ids); + // Free-text fields autocomplete from the values actually present. + let owners = distinct( + self.dags + .table + .all + .iter() + .flat_map(|d| d.owners.iter().cloned()), + ); + self.dags.table.filter.set_field_values("owners", owners); + let tags = distinct( + self.dags + .table + .all + .iter() + .flat_map(|d| d.tags.iter().map(|t| t.name.clone())), + ); + self.dags.table.filter.set_field_values("tags", tags); self.dags.table.apply_filter(); } Panel::DAGRun => { @@ -57,6 +82,17 @@ impl App { .table .filter .set_primary_values("task_id", task_ids); + let operators = distinct( + self.task_instances + .table + .all + .iter() + .filter_map(|ti| ti.operator.clone()), + ); + self.task_instances + .table + .filter + .set_field_values("operator", operators); self.task_instances.table.apply_filter(); } else { self.task_instances.table.all.clear(); @@ -88,7 +124,27 @@ impl App { .table .filter .set_primary_values("name", config_names); + let endpoints = distinct(self.configs.table.all.iter().map(|c| c.endpoint.clone())); + self.configs + .table + .filter + .set_field_values("endpoint", endpoints); } } } } + +#[cfg(test)] +mod tests { + use super::distinct; + + #[test] + fn distinct_sorts_dedups_and_drops_blanks() { + let got = distinct( + ["bob", "alice", "bob", "", "alice"] + .into_iter() + .map(str::to_string), + ); + assert_eq!(got, vec!["alice", "bob"]); + } +}