Skip to content

Commit 503ded3

Browse files
committed
Add configurable columns_config schema
Signed-off-by: Andrew Stein <steinlink@gmail.com>
1 parent aa756ab commit 503ded3

78 files changed

Lines changed: 2961 additions & 2892 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

examples/blocks/src/dataset/index.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,13 @@
182182
if (state.table) {
183183
// await viewer.eject();
184184
// await window.psp_workspace.removeTable("superstore");
185+
for (const viewer of window.psp_workspace.children) {
186+
const cfg = await viewer.save();
187+
if (cfg.table === "superstore") {
188+
await viewer.eject();
189+
}
190+
}
191+
185192
await state.table.then((x) => x.delete({ lazy: true }));
186193
state.table = undefined;
187194
}

rust/perspective-client/src/rust/virtual_server/data.rs

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,13 @@ impl VirtualDataSlice {
578578
///
579579
/// When `group_by` is active, extracts `__GROUPING_ID__` and
580580
/// `__ROW_PATH_N__` columns to build `self.row_path`, then removes
581-
/// them from the output `RecordBatch`.
581+
/// `__GROUPING_ID__` from the output `RecordBatch`. The
582+
/// `__ROW_PATH_N__` columns are *kept* in the frozen batch so
583+
/// downstream Arrow IPC consumers (`with_typed_arrays`, used by
584+
/// viewer-charts to drive its categorical/numeric axis resolvers
585+
/// and tree-hierarchy walkers) see them inline — matching the
586+
/// native `perspective-server`'s `to_arrow` output when
587+
/// `emit_legacy_row_path_names: false`.
582588
///
583589
/// When `split_by` is active, renames data columns by replacing `_`
584590
/// with `|` (the DuckDB PIVOT separator).
@@ -660,7 +666,22 @@ impl VirtualDataSlice {
660666
let mut new_arrays: Vec<ArrayRef> = Vec::new();
661667
for (col_idx, field) in schema.fields().iter().enumerate() {
662668
let name = field.name();
663-
if name == "__GROUPING_ID__" || name.starts_with("__ROW_PATH_") {
669+
// `__GROUPING_ID__` is an internal SQL-rollup discriminator
670+
// (used in Phase A above to decide which row-path levels
671+
// belong to each row). No JS consumer reads it, so it's
672+
// dropped from the frozen batch.
673+
//
674+
// `__ROW_PATH_N__` columns are kept. Phase A copied their
675+
// values into `self.row_path` for the JSON sidecar paths
676+
// (`render_to_columns_json`, `render_to_rows`), but
677+
// viewer-charts' `with_typed_arrays` callback needs the
678+
// per-level columns inline in the Arrow stream — its
679+
// categorical-axis resolver, numeric-position lookup, and
680+
// tree hierarchy walker all do `columns.get(\`__ROW_PATH_${n}__\`)`.
681+
// Keeping the columns here lets `render_to_arrow_ipc`
682+
// serialize them naturally, matching native
683+
// `perspective-server`'s `to_arrow` output.
684+
if name == "__GROUPING_ID__" {
664685
continue;
665686
}
666687

@@ -801,6 +822,25 @@ impl VirtualDataSlice {
801822
let arr = col.as_any().downcast_ref::<Int32Array>().unwrap();
802823
VirtualDataCell::Integer(Some(arr.value(row_idx)))
803824
},
825+
DataType::Int64 => {
826+
// TODO ????
827+
let arr = col.as_any().downcast_ref::<Int64Array>().unwrap();
828+
VirtualDataCell::Float(Some(arr.value(row_idx) as f64))
829+
},
830+
DataType::Time64(TimeUnit::Microsecond) => {
831+
let arr = col
832+
.as_any()
833+
.downcast_ref::<Time64MicrosecondArray>()
834+
.unwrap();
835+
VirtualDataCell::Float(Some(arr.value(row_idx) as f64))
836+
},
837+
DataType::Timestamp(TimeUnit::Microsecond, _) => {
838+
let arr = col
839+
.as_any()
840+
.downcast_ref::<Time64MicrosecondArray>()
841+
.unwrap();
842+
VirtualDataCell::Datetime(Some(arr.value(row_idx) * 1000))
843+
},
804844
DataType::Timestamp(TimeUnit::Millisecond, _) => {
805845
let arr = col
806846
.as_any()
@@ -914,6 +954,20 @@ impl VirtualDataSlice {
914954
.collect::<Vec<_>>(),
915955
)?
916956
},
957+
DataType::Int64 => {
958+
let arr = col.as_any().downcast_ref::<Int64Array>().unwrap();
959+
serde_json::to_value(
960+
(0..num_rows)
961+
.map(|i| {
962+
if arr.is_null(i) {
963+
None
964+
} else {
965+
Some(arr.value(i) as f64)
966+
}
967+
})
968+
.collect::<Vec<_>>(),
969+
)?
970+
},
917971
DataType::Timestamp(TimeUnit::Millisecond, _) => {
918972
let arr = col
919973
.as_any()
@@ -931,6 +985,23 @@ impl VirtualDataSlice {
931985
.collect::<Vec<_>>(),
932986
)?
933987
},
988+
DataType::Time64(TimeUnit::Microsecond) => {
989+
let arr = col
990+
.as_any()
991+
.downcast_ref::<Time64MicrosecondArray>()
992+
.unwrap();
993+
serde_json::to_value(
994+
(0..num_rows)
995+
.map(|i| {
996+
if arr.is_null(i) {
997+
None
998+
} else {
999+
Some(arr.value(i) as f64)
1000+
}
1001+
})
1002+
.collect::<Vec<_>>(),
1003+
)?
1004+
},
9341005
DataType::Date32 => {
9351006
let arr = col.as_any().downcast_ref::<Date32Array>().unwrap();
9361007
serde_json::to_value(

rust/perspective-js/src/rust/virtual_server.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ impl VirtualServerHandler for JsServerHandler {
372372

373373
let handler = self.0.clone();
374374
let view_id = view_id.to_string();
375-
let config_value = serde_wasm_bindgen::to_value(config).unwrap();
375+
let config_value = JsValue::from_serde_ext(config).unwrap();
376376
let config = config.clone();
377377
Box::pin(async move {
378378
let this = JsServerHandler(handler);
@@ -493,7 +493,7 @@ impl VirtualServerHandler for JsServerHandler {
493493
let handler = self.0.clone();
494494
let view_id = view_id.to_string();
495495
let column_name = column_name.to_string();
496-
let config_js = serde_wasm_bindgen::to_value(config).unwrap();
496+
let config_js = JsValue::from_serde_ext(config).unwrap();
497497
Box::pin(async move {
498498
let this = JsServerHandler(handler);
499499
let args = Array::new();
@@ -518,8 +518,8 @@ impl VirtualServerHandler for JsServerHandler {
518518
let handler = self.0.clone();
519519
let view_id = view_id.to_string();
520520
let window: JsViewPort = viewport.clone().into();
521-
let config_value = serde_wasm_bindgen::to_value(config).unwrap();
522-
let window_value = serde_wasm_bindgen::to_value(&window).unwrap();
521+
let config_value = JsValue::from_serde_ext(config).unwrap();
522+
let window_value = JsValue::from_serde_ext(&window).unwrap();
523523
let schema_value = JsValue::from_serde_ext(&schema).unwrap();
524524

525525
Box::pin(async move {

rust/perspective-js/src/ts/virtual_servers/duckdb.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ function duckdbTypeToPsp(name: string): ColumnType {
115115
return "string";
116116
}
117117

118+
if (name.startsWith("time")) {
119+
return "float";
120+
}
121+
118122
console.warn(`Unknown type '${name}'`);
119123
return "string";
120124
}

rust/perspective-viewer/src/rust/components/column_dropdown.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ impl ColumnDropDownElement {
9191
let width = target.get_bounding_client_rect().width();
9292
ApiFuture::spawn(async move {
9393
if !exclude.contains(&input) {
94-
let is_expr = session.validate_expr(&input).await?.is_none();
94+
let is_expr = crate::queries::validate_expr(&session, &input)
95+
.await?
96+
.is_none();
9597
if is_expr {
9698
values.push(InPlaceColumn::Expression(Expression::new(
9799
None,

rust/perspective-viewer/src/rust/components/column_selector.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ use crate::components::containers::scroll_panel_item::ScrollPanelItem;
4545
use crate::css;
4646
use crate::dragdrop::*;
4747
use crate::presentation::ColumnLocator;
48+
use crate::queries::{
49+
ActiveColumnState, ActiveColumnStateData, ColumnsIteratorSet, can_render_column_styles,
50+
};
4851
use crate::renderer::*;
4952
use crate::session::drag_drop_update::*;
5053
use crate::session::*;
51-
use crate::tasks::{
52-
ActiveColumnState, ActiveColumnStateData, ColumnsIteratorSet, can_render_column_styles,
53-
};
5454
use crate::utils::*;
5555

5656
#[derive(Properties)]

rust/perspective-viewer/src/rust/components/column_selector/active_column.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ use crate::components::type_icon::TypeIcon;
2626
use crate::dragdrop::*;
2727
use crate::js::plugin::*;
2828
use crate::presentation::ColumnLocator;
29+
use crate::queries::*;
2930
use crate::renderer::*;
3031
use crate::session::*;
31-
use crate::tasks::*;
3232
use crate::utils::*;
3333

3434
#[derive(Clone, Properties)]

rust/perspective-viewer/src/rust/components/column_selector/filter_column.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ use crate::components::containers::select::*;
2525
use crate::components::filter_dropdown::FilterDropDownElement;
2626
use crate::components::style::LocalStyle;
2727
use crate::components::type_icon::TypeIcon;
28+
use crate::css;
2829
use crate::dragdrop::*;
2930
use crate::renderer::*;
3031
use crate::session::*;
3132
use crate::utils::*;
32-
use crate::{css, maybe};
3333

3434
#[derive(Clone, Properties)]
3535
pub struct FilterColumnProps {
@@ -101,11 +101,11 @@ impl Component for FilterColumn {
101101
.unwrap_or_else(|| "".to_owned());
102102

103103
this.filter_ops = Rc::new(
104-
maybe! {
105-
Some(get_filter_ops(&ctx.props().metadata, col_type?)?
104+
try {
105+
get_filter_ops(&ctx.props().metadata, col_type?)?
106106
.into_iter()
107107
.map(SelectItem::Option)
108-
.collect::<Vec<_>>())
108+
.collect::<Vec<_>>()
109109
}
110110
.unwrap_or_default(),
111111
);
@@ -189,11 +189,11 @@ impl Component for FilterColumn {
189189
if col_type != old_col_type {
190190
changed = true;
191191
self.filter_ops = Rc::new(
192-
maybe! {
193-
Some(get_filter_ops(&ctx.props().metadata, col_type?)?
192+
try {
193+
get_filter_ops(&ctx.props().metadata, col_type?)?
194194
.into_iter()
195195
.map(SelectItem::Option)
196-
.collect::<Vec<_>>())
196+
.collect::<Vec<_>>()
197197
}
198198
.unwrap_or_default(),
199199
);

rust/perspective-viewer/src/rust/components/column_settings_sidebar.rs

Lines changed: 26 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,11 @@ use crate::components::editable_header::EditableHeaderProps;
3333
use crate::components::expression_editor::ExpressionEditorProps;
3434
use crate::components::style::LocalStyle;
3535
use crate::components::type_icon::TypeIconType;
36-
use crate::custom_events::CustomEvents;
3736
use crate::presentation::{ColumnLocator, ColumnSettingsTab, Presentation};
37+
use crate::queries::{can_render_column_styles, locator_name_or_default, locator_view_type};
3838
use crate::renderer::Renderer;
3939
use crate::session::{Session, SessionMetadataRc};
40-
use crate::tasks::{
41-
EditExpression, HasCustomEvents, HasPresentation, HasRenderer, HasSession,
42-
can_render_column_styles, locator_name_or_default, locator_view_type,
43-
};
40+
use crate::tasks::{delete_expr, save_expr, update_expr};
4441
use crate::utils::PtrEqRc;
4542
use crate::*;
4643

@@ -64,13 +61,13 @@ pub struct ColumnSettingsPanelProps {
6461
/// View config snapshot — threaded from `SessionProps`.
6562
pub view_config: PtrEqRc<ViewConfig>,
6663

64+
/// Per-column stats snapshot — threaded from `SessionProps`.
65+
pub column_stats: PtrEqRc<std::collections::HashMap<String, crate::session::ColumnStats>>,
66+
6767
/// Selected theme name, threaded for PortalModal consumers.
6868
pub selected_theme: Option<String>,
6969

7070
// State
71-
#[derivative(Debug = "ignore")]
72-
pub custom_events: CustomEvents,
73-
7471
#[derivative(Debug = "ignore")]
7572
pub presentation: Presentation,
7673

@@ -88,34 +85,11 @@ impl PartialEq for ColumnSettingsPanelProps {
8885
&& self.plugin_name == other.plugin_name
8986
&& self.metadata == other.metadata
9087
&& self.view_config == other.view_config
88+
&& self.column_stats == other.column_stats
9189
&& self.selected_theme == other.selected_theme
9290
}
9391
}
9492

95-
impl HasCustomEvents for ColumnSettingsPanelProps {
96-
fn custom_events(&self) -> &CustomEvents {
97-
&self.custom_events
98-
}
99-
}
100-
101-
impl HasPresentation for ColumnSettingsPanelProps {
102-
fn presentation(&self) -> &Presentation {
103-
&self.presentation
104-
}
105-
}
106-
107-
impl HasRenderer for ColumnSettingsPanelProps {
108-
fn renderer(&self) -> &Renderer {
109-
&self.renderer
110-
}
111-
}
112-
113-
impl HasSession for ColumnSettingsPanelProps {
114-
fn session(&self) -> &Session {
115-
&self.session
116-
}
117-
}
118-
11993
#[derive(Debug)]
12094
pub enum ColumnSettingsPanelMsg {
12195
SetExprValue(Rc<String>),
@@ -239,11 +213,20 @@ impl Component for ColumnSettingsPanel {
239213
ColumnLocator::Table(_) => {
240214
tracing::error!("Tried to save non-expression column!")
241215
},
242-
ColumnLocator::Expression(name) => {
243-
ctx.props().update_expr(name.clone(), new_expr)
244-
},
216+
ColumnLocator::Expression(name) => update_expr(
217+
&ctx.props().session,
218+
&ctx.props().renderer,
219+
&ctx.props().presentation,
220+
name.clone(),
221+
new_expr,
222+
),
245223
ColumnLocator::NewExpression => {
246-
if let Err(err) = ctx.props().save_expr(new_expr) {
224+
if let Err(err) = save_expr(
225+
&ctx.props().session,
226+
&ctx.props().renderer,
227+
&ctx.props().presentation,
228+
new_expr,
229+
) {
247230
tracing::warn!("{}", err);
248231
}
249232
},
@@ -258,7 +241,12 @@ impl Component for ColumnSettingsPanel {
258241
},
259242
ColumnSettingsPanelMsg::OnDelete(()) => {
260243
if ctx.props().selected_column.is_saved_expr() {
261-
ctx.props().delete_expr(&self.column_name).unwrap_or_log();
244+
delete_expr(
245+
&ctx.props().session,
246+
&ctx.props().renderer,
247+
&self.column_name,
248+
)
249+
.unwrap_or_log();
262250
}
263251

264252
ctx.props().on_close.emit(());
@@ -347,8 +335,8 @@ impl Component for ColumnSettingsPanel {
347335
group_by_depth: ctx.props().view_config.group_by.len() as u32,
348336
view_config: ctx.props().view_config.clone(),
349337
metadata: ctx.props().metadata.clone(),
338+
column_stats: ctx.props().column_stats.clone(),
350339
selected_theme: ctx.props().selected_theme.clone(),
351-
custom_events: ctx.props().custom_events.clone(),
352340
presentation: ctx.props().presentation.clone(),
353341
renderer: ctx.props().renderer.clone(),
354342
session: ctx.props().session.clone(),

0 commit comments

Comments
 (0)