vegafusion_core/planning/
plan.rs1use crate::error::Result;
2use crate::planning::extract::extract_server_data;
3use crate::planning::fuse::fuse_datasets;
4use crate::planning::lift_facet_aggregations::lift_facet_aggregations;
5use crate::planning::optimize_server::split_data_url_nodes;
6use crate::planning::projection_pushdown::projection_pushdown;
7use crate::planning::split_domain_data::split_domain_data;
8use crate::planning::stitch::{stitch_specs, CommPlan};
9use crate::planning::stringify_local_datetimes::stringify_local_datetimes;
10use crate::planning::strip_encodings::strip_encodings;
11use crate::planning::unsupported_data_warning::add_unsupported_data_warnings;
12use crate::proto::gen::pretransform::{
13 pre_transform_spec_warning::WarningType, PlannerWarning, PreTransformSpecWarning,
14};
15use crate::spec::chart::ChartSpec;
16use crate::task_graph::graph::ScopedVariable;
17use serde::{Deserialize, Serialize};
18
19#[derive(Clone, Serialize, Deserialize)]
20pub struct PreTransformSpecWarningSpec {
21 #[serde(rename = "type")]
22 pub typ: String,
23 pub message: String,
24}
25
26impl From<&PreTransformSpecWarning> for PreTransformSpecWarningSpec {
27 fn from(warning: &PreTransformSpecWarning) -> Self {
28 match warning.warning_type.as_ref().unwrap() {
29 WarningType::RowLimit(_) => {
30 PreTransformSpecWarningSpec {
31 typ: "RowLimitExceeded".to_string(),
32 message: "Some datasets in resulting Vega specification have been truncated to the provided row limit".to_string()
33 }
34 }
35 WarningType::BrokenInteractivity(_) => {
36 PreTransformSpecWarningSpec {
37 typ: "BrokenInteractivity".to_string(),
38 message: "Some interactive features may have been broken in the resulting Vega specification".to_string()
39 }
40 }
41 WarningType::Unsupported(_) => {
42 PreTransformSpecWarningSpec {
43 typ: "Unsupported".to_string(),
44 message: "Unable to pre-transform any datasets in the Vega specification".to_string()
45 }
46 }
47 WarningType::Planner(warning) => {
48 PreTransformSpecWarningSpec {
49 typ: "Planner".to_string(),
50 message: warning.message.clone()
51 }
52 }
53 }
54 }
55}
56
57impl From<&PlannerWarning> for PreTransformSpecWarning {
58 fn from(value: &PlannerWarning) -> Self {
59 PreTransformSpecWarning {
60 warning_type: Some(WarningType::Planner(PlannerWarning {
61 message: value.message.clone(),
62 })),
63 }
64 }
65}
66
67#[derive(Clone, Debug, Serialize, Deserialize)]
68pub enum PlannerWarnings {
69 StringifyDatetimeMixedUsage(String),
70 UnsupportedTransforms(String),
71}
72
73impl PlannerWarnings {
74 pub fn message(&self) -> String {
75 match &self {
76 PlannerWarnings::StringifyDatetimeMixedUsage(message) => message.clone(),
77 PlannerWarnings::UnsupportedTransforms(message) => message.clone(),
78 }
79 }
80}
81
82#[derive(Debug, Clone)]
83pub struct PlannerConfig {
84 pub split_domain_data: bool,
85 pub split_url_data_nodes: bool,
86 pub stringify_local_datetimes: bool,
87 pub projection_pushdown: bool,
88 pub extract_inline_data: bool,
89 pub extract_server_data: bool,
90 pub allow_client_to_server_comms: bool,
91 pub fuse_datasets: bool,
92 pub lift_facet_aggregations: bool,
93 pub client_only_vars: Vec<ScopedVariable>,
94 pub keep_variables: Vec<ScopedVariable>,
95 pub strip_description_encoding: bool,
96 pub strip_aria_encoding: bool,
97 pub strip_tooltip_encoding: bool,
98}
99
100impl Default for PlannerConfig {
101 fn default() -> Self {
102 Self {
103 split_domain_data: true,
104 split_url_data_nodes: true,
105 stringify_local_datetimes: false,
106 projection_pushdown: true,
107 extract_inline_data: false,
108 extract_server_data: true,
109 allow_client_to_server_comms: true,
110 fuse_datasets: true,
111 lift_facet_aggregations: true,
112 client_only_vars: Default::default(),
113 keep_variables: Default::default(),
114 strip_description_encoding: true,
115 strip_aria_encoding: true,
116 strip_tooltip_encoding: false,
117 }
118 }
119}
120
121impl PlannerConfig {
122 pub fn pre_transformed_spec_config(
123 preserve_interactivity: bool,
124 keep_variables: Vec<ScopedVariable>,
125 ) -> PlannerConfig {
126 PlannerConfig {
127 stringify_local_datetimes: true,
128 extract_inline_data: true,
129 allow_client_to_server_comms: !preserve_interactivity,
130 keep_variables,
131 ..Default::default()
132 }
133 }
134}
135
136#[derive(Debug, Clone)]
137pub struct SpecPlan {
138 pub server_spec: ChartSpec,
139 pub client_spec: ChartSpec,
140 pub comm_plan: CommPlan,
141 pub warnings: Vec<PlannerWarnings>,
142}
143
144impl SpecPlan {
145 pub fn try_new(full_spec: &ChartSpec, config: &PlannerConfig) -> Result<Self> {
146 let mut warnings: Vec<PlannerWarnings> = Vec::new();
147
148 add_unsupported_data_warnings(full_spec, config, &mut warnings)?;
150
151 let mut client_spec = full_spec.clone();
152
153 let domain_dataset_fields = if config.split_domain_data {
155 split_domain_data(&mut client_spec)?
156 } else {
157 Default::default()
158 };
159
160 if config.lift_facet_aggregations {
162 lift_facet_aggregations(&mut client_spec, config)?;
163 }
164
165 strip_encodings(&mut client_spec, config)?;
167
168 if config.projection_pushdown {
171 projection_pushdown(&mut client_spec)?;
172 }
173
174 if !config.extract_server_data {
175 Ok(Self {
177 server_spec: Default::default(),
178 client_spec,
179 comm_plan: Default::default(),
180 warnings,
181 })
182 } else {
183 let mut task_scope = client_spec.to_task_scope()?;
184 let input_client_spec = client_spec.clone();
185 let mut server_spec = extract_server_data(&mut client_spec, &mut task_scope, config)?;
186 let mut comm_plan = stitch_specs(
187 &task_scope,
188 &mut server_spec,
189 &mut client_spec,
190 config.keep_variables.as_slice(),
191 )?;
192
193 if !config.allow_client_to_server_comms && !comm_plan.client_to_server.is_empty() {
194 let mut config = config.clone();
197 config
198 .client_only_vars
199 .extend(comm_plan.client_to_server.clone());
200
201 client_spec = input_client_spec;
202 server_spec = extract_server_data(&mut client_spec, &mut task_scope, &config)?;
203 comm_plan = stitch_specs(
204 &task_scope,
205 &mut server_spec,
206 &mut client_spec,
207 config.keep_variables.as_slice(),
208 )?;
209 }
210
211 if config.fuse_datasets {
212 let mut do_not_fuse = config.keep_variables.clone();
213 do_not_fuse.extend(comm_plan.server_to_client.clone());
214 fuse_datasets(&mut server_spec, do_not_fuse.as_slice())?;
215 }
216
217 if config.split_url_data_nodes {
218 split_data_url_nodes(&mut server_spec)?;
219 }
220
221 if config.stringify_local_datetimes {
222 stringify_local_datetimes(
223 &mut server_spec,
224 &mut client_spec,
225 &comm_plan,
226 &domain_dataset_fields,
227 )?;
228 }
229
230 Ok(Self {
231 server_spec,
232 client_spec,
233 comm_plan,
234 warnings,
235 })
236 }
237 }
238}