Skip to content

Commit d6f8420

Browse files
authored
Merge pull request #3049 from finos/granular-features
Better `features` support
2 parents cac88bd + fc55c13 commit d6f8420

File tree

39 files changed

+564
-639
lines changed

39 files changed

+564
-639
lines changed

packages/perspective-jupyterlab/src/js/view.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export class PerspectiveView extends DOMWidgetView {
8585
});
8686
}
8787
);
88-
await this.perspective_client.init();
88+
8989
const tableName = this.model.get("table_name");
9090
if (!tableName) throw new Error("table_name not set in model");
9191
const table = this.perspective_client

packages/perspective-viewer-datagrid/src/js/data_listener/format_tree_header.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ import { format_cell } from "./format_cell.js";
2020
* @param {*} row_headers
2121
* @param {*} regularTable
2222
*/
23-
export function* format_tree_header(paths = [], row_headers, regularTable) {
23+
export function* format_tree_header_row_path(
24+
paths = [],
25+
row_headers,
26+
regularTable
27+
) {
2428
const plugins = regularTable[PRIVATE_PLUGIN_SYMBOL];
2529
for (let path of paths) {
2630
path = ["TOTAL", ...path];
@@ -44,3 +48,23 @@ export function* format_tree_header(paths = [], row_headers, regularTable) {
4448
yield path;
4549
}
4650
}
51+
52+
export function* format_tree_header(paths = [], row_headers, regularTable) {
53+
const plugins = regularTable[PRIVATE_PLUGIN_SYMBOL];
54+
for (let path of paths) {
55+
const new_path = [""];
56+
for (const idx in path) {
57+
new_path.push(
58+
format_cell.call(
59+
this,
60+
row_headers[idx],
61+
path[idx],
62+
plugins,
63+
true
64+
)
65+
);
66+
}
67+
68+
yield path;
69+
}
70+
}

packages/perspective-viewer-datagrid/src/js/data_listener/index.js

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212

1313
import { PRIVATE_PLUGIN_SYMBOL } from "../model";
1414
import { format_cell } from "./format_cell.js";
15-
import { format_tree_header } from "./format_tree_header.js";
15+
import {
16+
format_tree_header,
17+
format_tree_header_row_path,
18+
} from "./format_tree_header.js";
1619

1720
/**
1821
* Creates a new DataListener, suitable for passing to `regular-table`'s
@@ -86,15 +89,20 @@ export function createDataListener(viewer) {
8689
}
8790

8891
this._last_window = new_window;
89-
this._ids = columns.__ID__;
92+
this._ids =
93+
columns.__ID__ ||
94+
Array(y1 - y0)
95+
.fill()
96+
.map((_, index) => [index + y0]);
97+
9098
this._reverse_columns = this._column_paths
9199
.slice(x0, x1)
92100
.reduce((acc, x, i) => {
93101
acc.set(x, i);
94102
return acc;
95103
}, new Map());
96104

97-
this._reverse_ids = this._ids.reduce((acc, x, i) => {
105+
this._reverse_ids = this._ids?.reduce((acc, x, i) => {
98106
acc.set(x?.join("|"), i);
99107
return acc;
100108
}, new Map());
@@ -108,12 +116,6 @@ export function createDataListener(viewer) {
108116
column_paths = [];
109117

110118
const is_settings_open = viewer.hasAttribute("settings");
111-
112-
// if (this._config.split_by?.length > 0) {
113-
// this._column_paths
114-
// }
115-
116-
// for (const path of this._column_paths.slice(x0, x1)) {
117119
for (
118120
let ipath = x0;
119121
ipath < Math.min(x1, this._column_paths.length);
@@ -143,6 +145,7 @@ export function createDataListener(viewer) {
143145
}
144146
})
145147
);
148+
146149
metadata.push(column);
147150
if (is_settings_open) {
148151
path_parts.push("");
@@ -167,19 +170,27 @@ export function createDataListener(viewer) {
167170
last_reverse_columns = this._reverse_columns;
168171
}
169172

170-
return {
173+
const is_row_path = columns.__ROW_PATH__ !== undefined;
174+
const row_headers = Array.from(
175+
(is_row_path
176+
? format_tree_header_row_path
177+
: format_tree_header
178+
).call(
179+
this,
180+
columns.__ROW_PATH__,
181+
this._config.group_by,
182+
regularTable
183+
)
184+
);
185+
186+
const num_row_headers = row_headers[0]?.length;
187+
188+
const result = {
171189
num_column_headers: column_headers[0]?.length || 1,
172-
num_row_headers: this._config.group_by.length + 1,
190+
num_row_headers,
173191
num_rows: this._num_rows,
174192
num_columns: this._column_paths.length,
175-
row_headers: Array.from(
176-
format_tree_header.call(
177-
this,
178-
columns.__ROW_PATH__,
179-
this._config.group_by,
180-
regularTable
181-
)
182-
),
193+
row_headers,
183194
column_headers,
184195
data,
185196
metadata,
@@ -188,5 +199,7 @@ export function createDataListener(viewer) {
188199
this._config.split_by.length
189200
),
190201
};
202+
203+
return result;
191204
};
192205
}

packages/perspective-viewer-datagrid/src/js/event_handlers/row_select_click.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export async function selectionListener(
2929
return;
3030
}
3131

32-
const id = this._ids[meta.y - meta.y0];
32+
const id = this._ids?.[meta.y - meta.y0];
3333
if (meta && meta.y >= 0) {
3434
const selected = selected_rows_map.get(regularTable);
3535
const key_match =

packages/perspective-viewer-datagrid/src/js/model/create.js

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -116,26 +116,15 @@ export async function createModel(regular, table, view, extend = {}) {
116116
type_changed;
117117
}
118118

119-
// Extract the entire expression string as typed by the user, so we can
120-
// feed it into `validate_expressions` and get back the data types for
121-
// each column without it being affected by a pivot.
122-
const expressions = Array.isArray(config.expressions)
123-
? Object.fromEntries(
124-
config.expressions.map((expr) => [expr[0], expr[1]])
125-
)
126-
: config.expressions;
127-
128119
const [
129120
table_schema,
130-
validated_expressions,
131121
num_rows,
132122
schema,
133123
expression_schema,
134124
column_paths,
135125
_edit_port,
136126
] = await Promise.all([
137127
table.schema(),
138-
table.validate_expressions(expressions),
139128
view.num_rows(),
140129
view.schema(),
141130
view.expression_schema(),
@@ -170,7 +159,7 @@ export async function createModel(regular, table, view, extend = {}) {
170159
const _schema = { ...schema, ...expression_schema };
171160
const _table_schema = {
172161
...table_schema,
173-
...validated_expressions.expression_schema,
162+
...expression_schema,
174163
};
175164

176165
const _column_paths = column_paths.filter((path) => {

rust/perspective-client/perspective.proto

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,13 +214,26 @@ message GetFeaturesResp {
214214
bool group_by = 1;
215215
bool split_by = 2;
216216
bool expressions = 3;
217-
map<uint32, ColumnTypeOptions> filter_ops = 4;
217+
bool on_update = 4;
218+
bool sort = 5;
219+
map<uint32, ColumnTypeOptions> filter_ops = 6;
220+
map<uint32, AggregateOptions> aggregates = 7;
218221

219222
message ColumnTypeOptions {
220223
repeated string options = 1;
221224
}
225+
226+
message AggregateOptions {
227+
repeated AggregateArgs aggregates = 1;
228+
}
229+
230+
message AggregateArgs {
231+
string name = 1;
232+
repeated ColumnType args = 2;
233+
}
222234
}
223235

236+
224237
// `Client::get_hosted_tables`
225238
message GetHostedTablesReq {
226239
bool subscribe = 1;

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

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ impl<U: Copy + 'static> SystemInfo<U> {
8888

8989
/// Metadata about what features are supported by the `Server` to which this
9090
/// [`Client`] connects.
91-
#[derive(Clone, Default)]
91+
#[derive(Clone, Debug, Default)]
9292
pub struct Features(Arc<GetFeaturesResp>);
9393

9494
impl Deref for Features {
@@ -376,21 +376,6 @@ impl Client {
376376
Ok(id)
377377
}
378378

379-
pub async fn init(&self) -> ClientResult<()> {
380-
let msg = Request {
381-
msg_id: self.gen_id(),
382-
entity_id: "".to_owned(),
383-
client_req: Some(ClientReq::GetFeaturesReq(GetFeaturesReq {})),
384-
};
385-
386-
*self.features.lock().await = Some(Features(Arc::new(match self.oneshot(&msg).await? {
387-
ClientResp::GetFeaturesResp(features) => Ok(features),
388-
resp => Err(resp),
389-
}?)));
390-
391-
Ok(())
392-
}
393-
394379
/// Generate a message ID unique to this client.
395380
pub(crate) fn gen_id(&self) -> u32 {
396381
self.id_gen.next()
@@ -461,14 +446,25 @@ impl Client {
461446
.map_err(|_| ClientError::Unknown(format!("Internal error for req {req}")))
462447
}
463448

464-
pub(crate) fn get_features(&self) -> ClientResult<Features> {
465-
let features = self
466-
.features
467-
.try_lock()
468-
.ok_or(ClientError::NotInitialized)?
469-
.as_ref()
470-
.ok_or(ClientError::NotInitialized)?
471-
.clone();
449+
pub(crate) async fn get_features(&self) -> ClientResult<Features> {
450+
let mut guard = self.features.lock().await;
451+
let features = if let Some(features) = &*guard {
452+
features.clone()
453+
} else {
454+
let msg = Request {
455+
msg_id: self.gen_id(),
456+
entity_id: "".to_owned(),
457+
client_req: Some(ClientReq::GetFeaturesReq(GetFeaturesReq {})),
458+
};
459+
460+
let features = Features(Arc::new(match self.oneshot(&msg).await? {
461+
ClientResp::GetFeaturesResp(features) => Ok(features),
462+
resp => Err(resp),
463+
}?));
464+
465+
*guard = Some(features.clone());
466+
features
467+
};
472468

473469
Ok(features)
474470
}

0 commit comments

Comments
 (0)