1use std::collections::HashMap;
2use std::sync::Arc;
3
4use super::{
5 ColumnDef, ColumnFilter, FilterFnDef, FilteringFnSpec, GlobalFilterState, RowModel,
6 TableOptions,
7};
8
9pub type FacetKey = u64;
10pub type FacetCounts = HashMap<FacetKey, usize>;
11
12pub type FacetLabels<'a> = HashMap<FacetKey, &'a str>;
13
14pub fn faceted_row_model_excluding<'a, TData>(
15 pre_filtered: &RowModel<'a, TData>,
16 columns: &[ColumnDef<TData>],
17 column_filters: &[ColumnFilter],
18 global_filter: GlobalFilterState,
19 options: TableOptions,
20 filter_fns: &HashMap<Arc<str>, FilterFnDef>,
21 global_filter_fn: &FilteringFnSpec,
22 get_column_can_global_filter: Option<&dyn Fn(&ColumnDef<TData>, &TData) -> bool>,
23 exclude_column_id: Option<&str>,
24) -> RowModel<'a, TData> {
25 let other_filters: Vec<ColumnFilter> = column_filters
26 .iter()
27 .filter(|f| exclude_column_id.is_none_or(|id| f.column.as_ref() != id))
28 .cloned()
29 .collect();
30 super::filter_row_model(
31 pre_filtered,
32 columns,
33 &other_filters,
34 global_filter,
35 options,
36 filter_fns,
37 global_filter_fn,
38 get_column_can_global_filter,
39 )
40}
41
42pub fn faceted_unique_values<'a, TData>(
43 row_model: &RowModel<'a, TData>,
44 column: &ColumnDef<TData>,
45) -> FacetCounts {
46 let Some(facet_key_fn) = column.facet_key_fn.as_ref() else {
47 return FacetCounts::new();
48 };
49
50 let mut counts: FacetCounts = FacetCounts::new();
51 for &row_index in row_model.flat_rows() {
52 let Some(row) = row_model.row(row_index) else {
53 continue;
54 };
55 let key = (facet_key_fn)(row.original);
56 *counts.entry(key).or_insert(0) += 1;
57 }
58 counts
59}
60
61pub fn faceted_unique_value_labels<'a, TData>(
62 row_model: &RowModel<'a, TData>,
63 column: &ColumnDef<TData>,
64) -> FacetLabels<'a> {
65 let Some(facet_key_fn) = column.facet_key_fn.as_ref() else {
66 return FacetLabels::new();
67 };
68 let Some(facet_str_fn) = column.facet_str_fn.as_ref() else {
69 return FacetLabels::new();
70 };
71
72 let mut labels: FacetLabels<'a> = FacetLabels::new();
73 for &row_index in row_model.flat_rows() {
74 let Some(row) = row_model.row(row_index) else {
75 continue;
76 };
77 let key = (facet_key_fn)(row.original);
78 labels
79 .entry(key)
80 .or_insert_with(|| (facet_str_fn)(row.original));
81 }
82 labels
83}
84
85pub fn faceted_min_max_u64<'a, TData>(
86 row_model: &RowModel<'a, TData>,
87 column: &ColumnDef<TData>,
88) -> Option<(u64, u64)> {
89 let facet_key_fn = column.facet_key_fn.as_ref()?;
90
91 let mut iter = row_model
92 .flat_rows()
93 .iter()
94 .filter_map(|&i| row_model.row(i).map(|r| (facet_key_fn)(r.original)));
95
96 let first = iter.next()?;
97 let mut min = first;
98 let mut max = first;
99 for v in iter {
100 min = min.min(v);
101 max = max.max(v);
102 }
103 Some((min, max))
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use crate::table::{ColumnDef, ColumnFilter, Table, TableState, create_column_helper};
110 use std::sync::Arc;
111
112 #[derive(Debug, Clone)]
113 struct Item {
114 status_key: u64,
115 status_label: Arc<str>,
116 role_key: u64,
117 role_label: Arc<str>,
118 }
119
120 fn build_table(state: TableState) -> Table<'static, Item> {
121 let data: &'static [Item] = Box::leak(
122 vec![
123 Item {
124 status_key: 1,
125 status_label: "A".into(),
126 role_key: 10,
127 role_label: "X".into(),
128 },
129 Item {
130 status_key: 2,
131 status_label: "B".into(),
132 role_key: 10,
133 role_label: "X".into(),
134 },
135 Item {
136 status_key: 1,
137 status_label: "A".into(),
138 role_key: 20,
139 role_label: "Y".into(),
140 },
141 ]
142 .into_boxed_slice(),
143 );
144
145 let helper = create_column_helper::<Item>();
146 let status = ColumnDef::new("status")
147 .filter_by(|it: &Item, q| it.status_label.as_ref() == q)
148 .facet_key_by(|it| it.status_key)
149 .facet_str_by(|it| it.status_label.as_ref());
150 let role = helper
151 .accessor("role", |it| it.role_key)
152 .filter_by(|it: &Item, q| it.role_label.as_ref() == q)
153 .facet_key_by(|it| it.role_key)
154 .facet_str_by(|it| it.role_label.as_ref());
155
156 Table::builder(data)
157 .columns(vec![status, role])
158 .state(state)
159 .build()
160 }
161
162 #[test]
163 fn faceted_row_model_excludes_own_column_filter() {
164 let mut state = TableState::default();
165 state.column_filters = vec![
166 ColumnFilter {
167 column: "status".into(),
168 value: serde_json::Value::from("A"),
169 },
170 ColumnFilter {
171 column: "role".into(),
172 value: serde_json::Value::from("X"),
173 },
174 ];
175 state.global_filter = None;
176
177 let table = build_table(state);
178
179 let model = faceted_row_model_excluding(
181 table.pre_filtered_row_model(),
182 table.columns(),
183 &table.state().column_filters,
184 table.state().global_filter.clone(),
185 TableOptions::default(),
186 &HashMap::new(),
187 &FilteringFnSpec::Auto,
188 None,
189 Some("status"),
190 );
191 let counts = faceted_unique_values(&model, table.column("status").unwrap());
192 assert_eq!(counts.get(&1).copied(), Some(1));
193 assert_eq!(counts.get(&2).copied(), Some(1));
194
195 let labels = faceted_unique_value_labels(&model, table.column("status").unwrap());
196 assert_eq!(labels.get(&1).copied(), Some("A"));
197 assert_eq!(labels.get(&2).copied(), Some("B"));
198 }
199
200 #[test]
201 fn faceted_min_max_uses_flat_rows() {
202 let mut state = TableState::default();
203 state.column_filters = Vec::new();
204 state.global_filter = None;
205
206 let table = build_table(state);
207 let model = table.pre_filtered_row_model();
208 let (min, max) = faceted_min_max_u64(model, table.column("status").unwrap()).unwrap();
209 assert_eq!((min, max), (1, 2));
210 }
211}