Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,6 @@ jobs:
build_emscripten_wheel:
runs-on: ${{ matrix.os }}
needs: [lint_and_docs]
# if: ${{ startsWith(github.ref, 'refs/tags/v') }}
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -701,9 +700,9 @@ jobs:
# PSP_USE_CCACHE: 1

test_python_sdist:
if: startsWith(github.ref, 'refs/tags/v') || github.ref_name == 'master'
needs: [build_and_test_jupyterlab, build_and_test_rust]
runs-on: ${{ matrix.os }}

strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -795,6 +794,7 @@ jobs:
# `-'
benchmark_python:
needs: [build_python, build_js]
if: startsWith(github.ref, 'refs/tags/v') || github.ref_name == 'master'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
Expand Down Expand Up @@ -854,6 +854,7 @@ jobs:
# `--' '
benchmark_js:
needs: [build_js]
if: startsWith(github.ref, 'refs/tags/v') || github.ref_name == 'master'
strategy:
matrix:
os: [ubuntu-22.04]
Expand Down
130 changes: 104 additions & 26 deletions cpp/perspective/src/cpp/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,9 @@ ServerResources::get_table_ids() {
PSP_READ_LOCK(m_write_lock);
std::vector<t_id> vec;
for (auto const& imap : m_tables) {
vec.push_back(imap.first);
if (!m_deleted_tables.contains(imap.first)) {
vec.push_back(imap.first);
}
}

return vec;
Expand Down Expand Up @@ -495,6 +497,7 @@ ServerResources::delete_table(const t_id& id) {
if (m_table_to_view.find(id) == m_table_to_view.end()) {
m_tables.erase(id);
m_dirty_tables.erase(id);
m_deleted_tables.erase(id);
} else {
PSP_COMPLAIN_AND_ABORT("Cannot delete table with views");
}
Expand Down Expand Up @@ -706,7 +709,7 @@ ServerResources::is_table_dirty(const t_id& id) {
}

void
ServerResources::drop_client(const std::uint32_t client_id) {
ServerResources::drop_client(std::uint32_t client_id) {
if (m_client_to_view.contains(client_id)) {

// Load-bearing copy
Expand All @@ -729,6 +732,30 @@ ServerResources::drop_client(const std::uint32_t client_id) {
m_on_hosted_tables_update_subs = subs;
}

std::uint32_t
ServerResources::get_table_view_count(const t_id& table_id) {
const auto ret = m_table_to_view.find(table_id);
return std::distance(ret, m_table_to_view.end());
}

void
ServerResources::mark_table_deleted(
const t_id& table_id, std::uint32_t client_id, std::uint32_t msg_id
) {
m_deleted_tables[table_id] = Subscription{msg_id, client_id};
}

bool
ServerResources::is_table_deleted(const t_id& table_id) {
return get_table_view_count(table_id) == 0
&& m_deleted_tables.contains(table_id);
}

Subscription
ServerResources::get_table_deleted_client(const t_id& table_id) {
return m_deleted_tables[table_id];
}

std::uint32_t
ProtoServer::new_session() {
return m_client_id++;
Expand Down Expand Up @@ -2393,33 +2420,42 @@ ProtoServer::_handle_request(std::uint32_t client_id, Request&& req) {
break;
}
case proto::Request::kTableDeleteReq: {
m_resources.delete_table(req.entity_id());
const auto is_immediate = req.table_delete_req().is_immediate();
if (is_immediate
|| m_resources.get_table_view_count(req.entity_id()) == 0) {
m_resources.delete_table(req.entity_id());

for (const auto& sub :
m_resources.get_table_on_delete_sub(req.entity_id())) {
proto::Response resp;
resp.mutable_table_on_delete_resp();
resp.set_msg_id(sub.id);
resp.set_entity_id(req.entity_id());
ProtoServerResp<proto::Response> resp2;
resp2.data = std::move(resp);
resp2.client_id = sub.client_id;
proto_resp.emplace_back(std::move(resp2));
}

for (const auto& sub :
m_resources.get_table_on_delete_sub(req.entity_id())) {
proto::Response resp;
resp.mutable_table_on_delete_resp();
resp.set_msg_id(sub.id);
resp.set_entity_id(req.entity_id());
ProtoServerResp<proto::Response> resp2;
resp2.data = std::move(resp);
resp2.client_id = sub.client_id;
proto_resp.emplace_back(std::move(resp2));
}

proto::Response resp;
resp.mutable_table_delete_resp();
push_resp(std::move(resp));
resp.mutable_table_delete_resp();
push_resp(std::move(resp));

// notify `on_hosted_tables_update` listeners
auto subscriptions = m_resources.get_on_hosted_tables_update_sub();
for (auto& subscription : subscriptions) {
Response out;
out.set_msg_id(subscription.id);
ProtoServerResp<ProtoServer::Response> resp2;
resp2.data = std::move(out);
resp2.client_id = subscription.client_id;
proto_resp.emplace_back(std::move(resp2));
// notify `on_hosted_tables_update` listeners
auto subscriptions =
m_resources.get_on_hosted_tables_update_sub();
for (auto& subscription : subscriptions) {
Response out;
out.set_msg_id(subscription.id);
ProtoServerResp<ProtoServer::Response> resp2;
resp2.data = std::move(out);
resp2.client_id = subscription.client_id;
proto_resp.emplace_back(std::move(resp2));
}
} else {
m_resources.mark_table_deleted(
req.entity_id(), client_id, req.msg_id()
);
}

break;
Expand All @@ -2437,10 +2473,52 @@ ProtoServer::_handle_request(std::uint32_t client_id, Request&& req) {
proto_resp.emplace_back(std::move(resp2));
}

const auto table_id =
m_resources.get_table_id_for_view(req.entity_id());
m_resources.delete_view(client_id, req.entity_id());
proto::Response resp;
resp.mutable_view_delete_resp();
push_resp(std::move(resp));
if (m_resources.is_table_deleted(table_id)) {
Subscription deleted_id =
m_resources.get_table_deleted_client(table_id);

m_resources.delete_table(table_id);
for (const auto& sub :
m_resources.get_table_on_delete_sub(table_id)) {
proto::Response resp;
resp.mutable_table_on_delete_resp();
resp.set_msg_id(sub.id);
resp.set_entity_id(table_id);
ProtoServerResp<proto::Response> resp2;
resp2.data = std::move(resp);
resp2.client_id = sub.client_id;
proto_resp.emplace_back(std::move(resp2));
}

// Notify the client which initially called delete
proto::Response resp;
resp.mutable_table_delete_resp();
resp.set_msg_id(deleted_id.id);
resp.set_entity_id(table_id);
ProtoServerResp<proto::Response> resp2;
resp2.data = std::move(resp);
resp2.client_id = deleted_id.client_id;
proto_resp.emplace_back(std::move(resp2));

// notify `on_hosted_tables_update` listeners
auto subscriptions =
m_resources.get_on_hosted_tables_update_sub();
for (auto& subscription : subscriptions) {
Response out;
out.set_msg_id(subscription.id);
ProtoServerResp<ProtoServer::Response> resp2;
resp2.data = std::move(out);
resp2.client_id = subscription.client_id;
proto_resp.emplace_back(std::move(resp2));
}
}
// const auto table_id =
break;
}
case proto::Request::kViewRemoveOnUpdateReq: {
Expand Down
10 changes: 9 additions & 1 deletion cpp/perspective/src/include/perspective/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,14 @@ namespace server {
std::vector<std::pair<std::shared_ptr<Table>, const std::string>>
get_dirty_tables();
bool is_table_dirty(const t_id& id);
void drop_client(const std::uint32_t);
void drop_client(std::uint32_t);

std::uint32_t get_table_view_count(const t_id& table_id);
void mark_table_deleted(
const t_id& table_id, std::uint32_t client_id, std::uint32_t msg_id
);
bool is_table_deleted(const t_id& table_id);
Subscription get_table_deleted_client(const t_id& table_id);

protected:
tsl::hopscotch_map<t_id, t_id> m_view_to_table;
Expand All @@ -604,6 +611,7 @@ namespace server {
std::vector<Subscription> m_on_hosted_tables_update_subs;

tsl::hopscotch_set<t_id> m_dirty_tables;
tsl::hopscotch_map<t_id, Subscription> m_deleted_tables;

#ifdef PSP_PARALLEL_FOR
std::shared_mutex m_write_lock;
Expand Down
4 changes: 3 additions & 1 deletion cpp/protos/perspective.proto
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,9 @@ message MakeTableReq {
message MakeTableResp {}

// `Table::delete`
message TableDeleteReq {}
message TableDeleteReq {
bool is_immediate = 1;
}
message TableDeleteResp {}

// `Table::on_delete`
Expand Down
1 change: 1 addition & 0 deletions docs/md/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
- [Loading data from a virtual `Table`](./how_to/javascript/loading_virtual_data.md)
- [Saving and restoring UI state](./how_to/javascript/save_restore.md)
- [Listening for events](./how_to/javascript/events.md)
- [React Component](./how_to/javascript/react.md)
- [Python](./how_to/python.md)
- [Installation](./how_to/python/installation.md)
- [Loading data into a `Table`](./how_to/python/table.md)
Expand Down
19 changes: 19 additions & 0 deletions docs/md/how_to/javascript/react.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# React Component

We provide a React wrapper to prevent common issues and mistakes associated with
using the perspective-viewer web component in the context of React.

Before trying this example, please take a look at
[how to bootstrap perspective](./importing.md).

A simple example:

```typescript
{{#include ../../../../examples/react-example/src/index.tsx:63:}}
```

This adds a perspective table to the provider at the root of the app and allows
us to create viewers referencing those tables anywhere within that context. Any
views or viewers associated with the React component are automatically cleaned
up as part of the lifecycle of the component, but tables are still the
responsibility of the caller to cleanup currently.
45 changes: 23 additions & 22 deletions examples/react-example/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,29 @@
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

const esbuild = require("esbuild");
const fs = require("fs");
const path = require("path");
import esbuild from "esbuild";
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
import { dirname } from "path";

async function build() {
await esbuild.build({
entryPoints: ["src/index.tsx"],
outdir: "dist",
format: "esm",
bundle: true,
target: "es2022",
loader: {
".arrow": "file",
".wasm": "file",
},
assetNames: "[name]",
});
const __dirname = dirname(fileURLToPath(import.meta.url));

fs.writeFileSync(
path.join(__dirname, "dist/index.html"),
fs.readFileSync(path.join(__dirname, "src/index.html")).toString()
);
}
await esbuild.build({
entryPoints: ["src/index.tsx"],
outdir: "dist",
format: "esm",
bundle: true,
sourcemap: "linked",
target: "es2022",
loader: {
".arrow": "file",
".wasm": "file",
},
assetNames: "[name]",
});

build();
fs.writeFileSync(
path.join(__dirname, "dist/index.html"),
fs.readFileSync(path.join(__dirname, "src/index.html")).toString()
);
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

fn main() -> Result<(), anyhow::Error> {
// Pass the `perspective_client` links metadata flag so that the `inherit_docs`
// macro can find the path to the docs.
if let Ok(x) = std::env::var("DEP_PERSPECTIVE_CLIENT_DOCS_PATH") {
println!("cargo:rustc-env=PERSPECTIVE_CLIENT_DOCS_PATH={}", x);
}
declare module "*.wasm" {
const content: string;
export default content;
}

Ok(())
declare module "*.arrow" {
const content: string;
export default content;
}
8 changes: 5 additions & 3 deletions examples/react-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"private": true,
"version": "3.6.1",
"description": "An example app built using `@finos/perspective-viewer`.",
"type": "module",
"scripts": {
"build": "node build.js",
"start": "node build.js && http-server dist"
Expand All @@ -14,9 +15,10 @@
"@finos/perspective-viewer": "workspace:^",
"@finos/perspective-viewer-d3fc": "workspace:^",
"@finos/perspective-viewer-datagrid": "workspace:^",
"superstore-arrow": "^3.0.0",
"react": "^18",
"react-dom": "^18"
"@finos/perspective-react": "workspace:^",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"superstore-arrow": "^3.0.0"
},
"devDependencies": {
"esbuild": "^0.14.54",
Expand Down
Loading
Loading