perspective_client/config/
view_config.rs

1// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
3// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
4// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
5// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
6// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7// ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
8// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9// ┃ This file is part of the Perspective library, distributed under the terms ┃
10// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
13use std::collections::HashMap;
14
15use serde::{Deserialize, Serialize};
16use ts_rs::TS;
17
18use super::aggregates::*;
19use super::expressions::*;
20use super::filters::*;
21use super::sort::*;
22use crate::proto;
23use crate::proto::columns_update;
24
25#[derive(Clone, Debug, Deserialize, Default, PartialEq, Serialize)]
26#[serde(deny_unknown_fields)]
27pub struct ViewConfig {
28    #[serde(default)]
29    pub group_by: Vec<String>,
30
31    #[serde(default)]
32    pub split_by: Vec<String>,
33
34    #[serde(default)]
35    pub sort: Vec<Sort>,
36
37    #[serde(default)]
38    pub filter: Vec<Filter>,
39
40    #[serde(skip_serializing_if = "is_default_value")]
41    #[serde(default)]
42    pub filter_op: FilterReducer,
43
44    #[serde(default)]
45    pub expressions: Expressions,
46
47    #[serde(default)]
48    pub columns: Vec<Option<String>>,
49
50    #[serde(default)]
51    pub aggregates: HashMap<String, Aggregate>,
52
53    #[serde(skip_serializing_if = "Option::is_none")]
54    #[serde(default)]
55    pub group_by_depth: Option<u32>,
56}
57
58fn is_default_value<A: Default + PartialEq>(value: &A) -> bool {
59    value == &A::default()
60}
61
62#[derive(Clone, Debug, Deserialize, Default, Serialize, TS)]
63#[serde(deny_unknown_fields)]
64pub struct ViewConfigUpdate {
65    #[serde(skip_serializing_if = "Option::is_none")]
66    #[serde(default)]
67    #[ts(optional)]
68    pub group_by: Option<Vec<String>>,
69
70    #[serde(skip_serializing_if = "Option::is_none")]
71    #[serde(default)]
72    #[ts(optional)]
73    pub split_by: Option<Vec<String>>,
74
75    #[serde(skip_serializing_if = "Option::is_none")]
76    #[serde(default)]
77    #[ts(optional)]
78    pub columns: Option<Vec<Option<String>>>,
79
80    #[serde(skip_serializing_if = "Option::is_none")]
81    #[serde(default)]
82    #[ts(optional)]
83    pub filter: Option<Vec<Filter>>,
84
85    #[serde(skip_serializing_if = "Option::is_none")]
86    #[serde(default)]
87    #[ts(optional)]
88    pub filter_op: Option<FilterReducer>,
89
90    #[serde(skip_serializing_if = "Option::is_none")]
91    #[serde(default)]
92    #[ts(optional)]
93    pub sort: Option<Vec<Sort>>,
94
95    #[serde(skip_serializing_if = "Option::is_none")]
96    #[serde(default)]
97    #[ts(optional)]
98    pub expressions: Option<Expressions>,
99
100    #[serde(skip_serializing_if = "Option::is_none")]
101    #[serde(default)]
102    #[ts(optional)]
103    pub aggregates: Option<HashMap<String, Aggregate>>,
104
105    #[serde(skip_serializing)]
106    #[serde(default)]
107    #[ts(optional)]
108    pub group_by_depth: Option<u32>,
109}
110
111impl From<ViewConfigUpdate> for proto::ViewConfig {
112    fn from(value: ViewConfigUpdate) -> Self {
113        proto::ViewConfig {
114            group_by: value.group_by.unwrap_or_default(),
115            split_by: value.split_by.unwrap_or_default(),
116            columns: value.columns.map(|x| proto::ColumnsUpdate {
117                opt_columns: Some(columns_update::OptColumns::Columns(
118                    proto::columns_update::Columns {
119                        columns: x.into_iter().flatten().collect(),
120                    },
121                )),
122            }),
123            filter: value
124                .filter
125                .unwrap_or_default()
126                .into_iter()
127                .map(|x| x.into())
128                .collect(),
129            filter_op: value
130                .filter_op
131                .map(proto::view_config::FilterReducer::from)
132                .unwrap_or_default() as i32,
133            sort: value
134                .sort
135                .unwrap_or_default()
136                .into_iter()
137                .map(|x| x.into())
138                .collect(),
139            expressions: value.expressions.unwrap_or_default().0,
140            aggregates: value
141                .aggregates
142                .unwrap_or_default()
143                .into_iter()
144                .map(|(x, y)| (x, y.into()))
145                .collect(),
146            group_by_depth: value.group_by_depth,
147        }
148    }
149}
150
151impl From<FilterReducer> for proto::view_config::FilterReducer {
152    fn from(value: FilterReducer) -> Self {
153        match value {
154            FilterReducer::And => proto::view_config::FilterReducer::And,
155            FilterReducer::Or => proto::view_config::FilterReducer::Or,
156        }
157    }
158}
159
160impl From<proto::view_config::FilterReducer> for FilterReducer {
161    fn from(value: proto::view_config::FilterReducer) -> Self {
162        match value {
163            proto::view_config::FilterReducer::And => FilterReducer::And,
164            proto::view_config::FilterReducer::Or => FilterReducer::Or,
165        }
166    }
167}
168
169impl From<ViewConfig> for ViewConfigUpdate {
170    fn from(value: ViewConfig) -> Self {
171        ViewConfigUpdate {
172            group_by: Some(value.group_by),
173            split_by: Some(value.split_by),
174            columns: Some(value.columns),
175            filter: Some(value.filter),
176            filter_op: Some(value.filter_op),
177            sort: Some(value.sort),
178            expressions: Some(value.expressions),
179            aggregates: Some(value.aggregates),
180            group_by_depth: value.group_by_depth,
181        }
182    }
183}
184
185impl From<proto::ViewConfig> for ViewConfig {
186    fn from(value: proto::ViewConfig) -> Self {
187        ViewConfig {
188            group_by: value.group_by,
189            split_by: value.split_by,
190            columns: match value.columns.unwrap().opt_columns {
191                Some(columns_update::OptColumns::Columns(x)) => {
192                    x.columns.into_iter().map(Some).collect()
193                },
194                _ => {
195                    panic!("Expected columns in ViewConfig")
196                },
197            },
198            filter: value.filter.into_iter().map(|x| x.into()).collect(),
199            filter_op: proto::view_config::FilterReducer::try_from(value.filter_op)
200                .unwrap_or_default()
201                .into(),
202            sort: value.sort.into_iter().map(|x| x.into()).collect(),
203            expressions: Expressions(value.expressions),
204            aggregates: value
205                .aggregates
206                .into_iter()
207                .map(|(x, y)| (x, y.into()))
208                .collect(),
209            group_by_depth: value.group_by_depth,
210        }
211    }
212}
213
214impl ViewConfig {
215    fn _apply<T>(field: &mut T, update: Option<T>) -> bool {
216        match update {
217            None => false,
218            Some(update) => {
219                *field = update;
220                true
221            },
222        }
223    }
224
225    pub fn reset(&mut self, reset_expressions: bool) {
226        let mut config = Self::default();
227        if !reset_expressions {
228            config.expressions = self.expressions.clone();
229        }
230        std::mem::swap(self, &mut config);
231    }
232
233    /// Apply `ViewConfigUpdate` to a `ViewConfig`, ignoring any fields in
234    /// `update` which were unset.
235    pub fn apply_update(&mut self, update: ViewConfigUpdate) -> bool {
236        let mut changed = false;
237        changed = Self::_apply(&mut self.group_by, update.group_by) || changed;
238        changed = Self::_apply(&mut self.split_by, update.split_by) || changed;
239        changed = Self::_apply(&mut self.columns, update.columns) || changed;
240        changed = Self::_apply(&mut self.filter, update.filter) || changed;
241        changed = Self::_apply(&mut self.sort, update.sort) || changed;
242        changed = Self::_apply(&mut self.aggregates, update.aggregates) || changed;
243        changed = Self::_apply(&mut self.expressions, update.expressions) || changed;
244        changed
245    }
246
247    pub fn is_aggregated(&self) -> bool {
248        !self.group_by.is_empty()
249    }
250
251    pub fn is_column_expression_in_use(&self, name: &str) -> bool {
252        let name = name.to_owned();
253        self.group_by.contains(&name)
254            || self.split_by.contains(&name)
255            || self.sort.iter().any(|x| x.0 == name)
256            || self.filter.iter().any(|x| x.column() == name)
257            || self.columns.contains(&Some(name))
258    }
259
260    /// `ViewConfig` carries additional metadata in the form of `None` columns
261    /// which are filtered befor ebeing passed to the engine, but whose position
262    /// is a placeholder for Viewer functionality. `is_equivalent` tests
263    /// equivalency from the perspective of the engine.
264    pub fn is_equivalent(&self, other: &Self) -> bool {
265        let _self = self.clone();
266        let _self = ViewConfig {
267            columns: _self.columns.into_iter().filter(|x| x.is_some()).collect(),
268            .._self
269        };
270
271        let _other = other.clone();
272        let _other = ViewConfig {
273            columns: _other.columns.into_iter().filter(|x| x.is_some()).collect(),
274            ..other.clone()
275        };
276
277        _self == _other
278    }
279}