Skip to content
Draft
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ dist-ssr
*.njsproj
*.sln
*.sw?

# Playwright
playwright-report/
test-results/
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v18.13.0
v22.13.1
1,693 changes: 1,005 additions & 688 deletions package-lock.json

Large diffs are not rendered by default.

20 changes: 13 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,32 @@
"start": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
"preview": "vite preview",
"test": "playwright test",
"test:ui": "playwright test --ui"
},
"dependencies": {
"@deephaven/components": "^0.102.0",
"@deephaven/dashboard": "^0.102.0",
"@deephaven/iris-grid": "^0.102.0",
"@deephaven/jsapi-shim": "^0.102.0",
"@deephaven/jsapi-types": "^1.0.0-dev0.39.5",
"@deephaven/jsapi-utils": "^0.102.0",
"@deephaven-enterprise/jsapi-types": "1.20240723.147-beta",
"@deephaven/components": "^0.106.2",
"@deephaven/dashboard": "^0.106.2",
"@deephaven/iris-grid": "^0.106.2",
"@deephaven/js-plugin-ag-grid": "^0.5.2",
"@deephaven/jsapi-bootstrap": "^0.106.2",
"@deephaven/jsapi-shim": "^0.106.0",
"@deephaven/jsapi-types": "^1.0.0-dev0.39.5",
"@deephaven/jsapi-utils": "^0.106.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^6.24.0"
},
"devDependencies": {
"@playwright/test": "^1.57.0",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.9",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitejs/plugin-react": "^4.0.3",
"ag-grid-community": "^34.3.1",
"eslint": "^8.45.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
Expand Down
20 changes: 20 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
testDir: "./tests",
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: "html",
use: {
baseURL: "http://localhost:5173",
trace: "on-first-retry",
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
],
});
36 changes: 21 additions & 15 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import { useCallback, useEffect, useState } from "react";
import { LoadingOverlay } from "@deephaven/components"; // Use the loading spinner from the Deephaven components package
import {
IrisGrid,
IrisGridModel,
IrisGridModelFactory,
} from "@deephaven/iris-grid"; // iris-grid is used to display Deephaven tables
import dh from "@deephaven/jsapi-shim"; // Import the shim to use the JS API
import type {
ConsoleConfig,
Expand All @@ -17,10 +12,12 @@ import "./App.scss"; // Styles for in this app
import {
clientConnected,
getCorePlusApi,
getGridModelByQueryName,
getQuery,
getTableByQueryName,
getWebsocketUrl,
isCorePlusWorkerKind,
} from "./Utils";
import DeephavenAgGridComponent from "./DeephavenAgGridComponent";

const API_URL = import.meta.env.VITE_DEEPHAVEN_API_URL ?? "";

Expand All @@ -40,7 +37,7 @@ const enterpriseApi = dh as EnterpriseDhType;
*/
async function createGridModel(
client: EnterpriseClient
): Promise<IrisGridModel> {
): Promise<DhType.Table> {
// Create a new session... API is currently undocumented and subject to change in future revisions
const ide: Ide = new enterpriseApi.Ide(client);

Expand Down Expand Up @@ -128,7 +125,8 @@ async function createGridModel(

const table = await session.getObject(definition);

return IrisGridModelFactory.makeModel(coreApi, table);
return table;
// return IrisGridModelFactory.makeModel(coreApi, table);
}

const dhConsole = await ide.createConsole(config);
Expand Down Expand Up @@ -156,7 +154,8 @@ async function createGridModel(

const table = await session.getObject(definition);

return IrisGridModelFactory.makeModel(enterpriseApi, table);
return table;
// return IrisGridModelFactory.makeModel(enterpriseApi, table);
}

/**
Expand All @@ -168,10 +167,13 @@ async function createGridModel(
* See create-react-app docs for how to update these env vars: https://create-react-app.dev/docs/adding-custom-environment-variables/
*/
function App() {
const [model, setModel] = useState<IrisGridModel>();
const [table, setTable] = useState<
DhType.Table | DhType.coreplus.pivot.PivotTable
>();
const [error, setError] = useState<string>();
const [isLoading, setIsLoading] = useState(true);
const [client, setClient] = useState<EnterpriseClient>();
const [coreApi, setCoreApi] = useState<typeof DhType>();

const initApp = useCallback(async () => {
try {
Expand All @@ -195,12 +197,16 @@ function App() {
const queryName = searchParams.get("queryName");
const tableName = searchParams.get("tableName");

const query = await getQuery(client, queryName ?? "");
const coreApi = await getCorePlusApi(query.designated?.jsApiUrl ?? "");
setCoreApi(coreApi);

// If a table name was specified, load that table. Otherwise, create a new table.
const newModel = await (queryName && tableName
? getGridModelByQueryName(client, queryName, tableName)
const newTable = await (queryName && tableName
? getTableByQueryName(client, queryName, tableName)
: createGridModel(client));

setModel(newModel);
setTable(newTable);

console.log("Table successfully loaded!");
} catch (e) {
Expand All @@ -221,11 +227,11 @@ function App() {
};
}, [client]);

const isLoaded = model != null;
const isLoaded = table != null && coreApi != null;

return (
<div className="App">
{isLoaded && <IrisGrid model={model} />}
{isLoaded && <DeephavenAgGridComponent api={coreApi} table={table} />}
{!isLoaded && (
<LoadingOverlay
isLoaded={isLoaded}
Expand Down
141 changes: 141 additions & 0 deletions src/DeephavenAgGridComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import React, { useCallback, useEffect, useMemo, useState } from "react";
import type { dh as DhType } from "@deephaven/jsapi-types";
import {
AgGridDhTheme,
AgGridView,
AgGridViewProps,
getDefaultProps,
isPivotTable,
TREE_NODE_KEY,
} from "@deephaven/js-plugin-ag-grid";
import { ApiContext } from "@deephaven/jsapi-bootstrap";
import { Grid } from "@deephaven/components";
import {
type FilterModel,
type GridApi,
themeQuartz,
type SelectionChangedEvent,
} from "ag-grid-community";

function DeephavenAgGridComponent({
api,
table,
}: {
api: typeof DhType;
table: DhType.Table | DhType.coreplus.pivot.PivotTable;
}) {
const themeParams = useMemo(() => AgGridDhTheme.getThemeParams(), []);

const theme = useMemo(
() => themeQuartz.withParams(themeParams),
[themeParams],
);

const [sourceTable, setSourceTable] = useState<DhType.Table>();
const [gridApi, setGridApi] = useState<GridApi>();
const handleSelectionChanged = useCallback(
async (event: SelectionChangedEvent) => {
const { selectedNodes } = event;
if (selectedNodes == null || selectedNodes.length === 0) {
return;
}

// Build an AG Grid filter model to filter the source table to just these selected rows
// We need to collect unique values per column, then build a combined filter model
const columnValues: Record<string, Set<string>> = {};

selectedNodes.forEach((node) => {
const { data } = node;
const treeNodeKey = data[TREE_NODE_KEY];
if (isPivotTable(table)) {
// If it's a pivot table, we need to look at the row sources to build the filters
// Only up to the depth of this selection
for (let depth = 0; depth <= treeNodeKey.depth - 2; depth += 1) {
const rowSource = table.rowSources[depth];
const key = data[rowSource.name];

if (!columnValues[rowSource.name]) {
columnValues[rowSource.name] = new Set();
}
columnValues[rowSource.name].add(key);
}
} else {
throw new Error("Other table types not yet supported");
}
});

// Build the filter model using text filters with OR conditions for multiple values
const filterModel: FilterModel = {};
Object.entries(columnValues).forEach(([columnName, values]) => {
const valuesArray = Array.from(values);
if (valuesArray.length === 1) {
// Single value: use simple text filter with equals
filterModel[columnName] = {
filterType: "text",
type: "equals",
filter: valuesArray[0],
};
} else {
// Multiple values: use combined filter with OR operator
filterModel[columnName] = {
filterType: "text",
operator: "OR",
conditions: valuesArray.map((value) => ({
filterType: "text",
type: "equals",
filter: value,
})),
};
}
});

// Apply the filter model via the AG Grid API
gridApi?.setFilterModel(filterModel);
},
[gridApi, table],
);
const agGridProps: AgGridViewProps["agGridProps"] = useMemo(
() => ({
...getDefaultProps(),
rowSelection: {
mode: "multiRow",
},
onSelectionChanged: handleSelectionChanged,
theme,
}),
[handleSelectionChanged, theme],
);

const sourceProps: AgGridViewProps["agGridProps"] = useMemo(
() => ({
...getDefaultProps(),
onModelUpdated: (event) => {
setGridApi(event.api);
},
theme,
}),
[theme],
);

useEffect(() => {
async function getSourceTable() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const newSourceTable = await (table as any).getSourceTable();
setSourceTable(newSourceTable);
}
getSourceTable();
}, [table]);

return (
<ApiContext.Provider value={api}>
<Grid height="100%" width="100%" columns="1fr 1fr" rows="1fr">
<AgGridView table={table} agGridProps={agGridProps} />
{sourceTable != null && (
<AgGridView table={sourceTable} agGridProps={sourceProps} />
)}
</Grid>
</ApiContext.Provider>
);
}

export default DeephavenAgGridComponent;
Loading