Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions packages/code-studio/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ async function getCorePlugins() {
FilterPluginConfig,
MarkdownPluginConfig,
LinkerPluginConfig,
SimplePivotPluginConfig,
WidgetLoaderPluginConfig,
} = dashboardCorePlugins;
return [
Expand All @@ -61,6 +62,7 @@ async function getCorePlugins() {
FilterPluginConfig,
MarkdownPluginConfig,
LinkerPluginConfig,
SimplePivotPluginConfig,
WidgetLoaderPluginConfig,
];
}
Expand Down
15 changes: 15 additions & 0 deletions packages/dashboard-core-plugins/src/SimplePivotPluginConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { PluginType, type WidgetPlugin } from '@deephaven/plugin';
import { dhTable } from '@deephaven/icons';
import type { dh } from '@deephaven/jsapi-types';
import { SimplePivotWidgetPlugin } from './SimplePivotWidgetPlugin';

const SimplePivotPluginConfig: WidgetPlugin<dh.Widget> = {
name: 'SimplePivotPanel',
title: 'SimplePivot',
type: PluginType.WIDGET_PLUGIN,
component: SimplePivotWidgetPlugin,
supportedTypes: 'simplepivot.SimplePivotTable',
icon: dhTable,
};

export default SimplePivotPluginConfig;
111 changes: 111 additions & 0 deletions packages/dashboard-core-plugins/src/SimplePivotWidgetPlugin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { useCallback } from 'react';
import { type WidgetComponentProps } from '@deephaven/plugin';
import { type dh as DhType } from '@deephaven/jsapi-types';
import IrisGrid, {
getSimplePivotColumnMap,
KEY_TABLE_PIVOT_COLUMN,
type KeyColumnArray,
type KeyTableSubscriptionData,
} from '@deephaven/iris-grid';
import { useApi } from '@deephaven/jsapi-bootstrap';
import { LoadingOverlay } from '@deephaven/components';
import { getErrorMessage } from '@deephaven/utils';
import {
useIrisGridSimplePivotModel,
type SimplePivotFetchResult,
} from './useIrisGridSimplePivotModel';

export function SimplePivotWidgetPlugin({
fetch,
}: WidgetComponentProps<DhType.Widget>): JSX.Element | null {
const dh = useApi();
const loadKeys = useCallback(
(keyTable: DhType.Table): Promise<KeyColumnArray> =>
new Promise((resolve, reject) => {
const pivotIdColumn = keyTable.findColumn(KEY_TABLE_PIVOT_COLUMN);
const columns = keyTable.columns.filter(
c => c.name !== KEY_TABLE_PIVOT_COLUMN
);
const subscription = keyTable.subscribe(keyTable.columns);
subscription.addEventListener<KeyTableSubscriptionData>(
dh.Table.EVENT_UPDATED,
e => {
subscription.close();
resolve(getSimplePivotColumnMap(e.detail, columns, pivotIdColumn));
}
);
}),
[dh]
);

const fetchTable = useCallback(
async function fetchModel() {
const pivotWidget = await fetch();
const schema = JSON.parse(pivotWidget.getDataAsString());

// The initial state is our keys to use for column headers
const keyTablePromise = pivotWidget.exportedObjects[0].fetch();
const columnMapPromise = keyTablePromise.then(loadKeys);

return new Promise<SimplePivotFetchResult>((resolve, reject) => {
// Add a listener for each pivot schema change, so we get the first update, with the table to render.
// Note that there is no await between this line and the pivotWidget being returned, or we would miss the first update
const removeEventListener = pivotWidget.addEventListener<DhType.Widget>(
dh.Widget.EVENT_MESSAGE,
async e => {
removeEventListener();
const data = e.detail.getDataAsString();
const response = JSON.parse(data === '' ? '{}' : data);
if (response.error != null) {
reject(new Error(response.error));
return;
}
// Get the object, and make sure the keytable is fetched and usable
const tables = e.detail.exportedObjects;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be checking the getDataAsString() to verify it's the message we're expecting?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example didn't have this check, but yes, we should probably verify.
e.detail.getDataAsString() seems to return an empty string on success. @niloc132 is that expected or should it be a parsable JSON instead?

const tableToRenderPromise = tables[0].fetch();
const totalsPromise =
tables.length === 2 ? tables[1].fetch() : Promise.resolve(null);

// Wait for all four promises to have resolved, then render the table. Note that after
// the first load, the keytable will remain loaded, we'll only wait for the main table,
// and optionally the totals table.
const fetchResult = await Promise.all([
tableToRenderPromise,
totalsPromise,
keyTablePromise,
columnMapPromise,
]).then(([table, totalsTable, keyTable, columnMap]) => ({
table,
totalsTable,
keyTable,
columnMap,
}));
resolve({ ...fetchResult, schema, pivotWidget });
}
);
});
},
[fetch, dh, loadKeys]
);

const fetchResult = useIrisGridSimplePivotModel(fetchTable);

if (fetchResult.status === 'loading') {
return <LoadingOverlay isLoading />;
}

if (fetchResult.status === 'error') {
return (
<LoadingOverlay
errorMessage={getErrorMessage(fetchResult.error)}
isLoading={false}
/>
);
}

const { model } = fetchResult;

return <IrisGrid model={model} />;
}

export default SimplePivotWidgetPlugin;
1 change: 1 addition & 0 deletions packages/dashboard-core-plugins/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export { default as MarkdownPluginConfig } from './MarkdownPluginConfig';
export { default as PandasPanelPlugin } from './PandasPanelPlugin';
export { default as PandasWidgetPlugin } from './PandasWidgetPlugin';
export { default as PandasPluginConfig } from './PandasPluginConfig';
export { default as SimplePivotPluginConfig } from './SimplePivotPluginConfig';
export { default as WidgetLoaderPlugin } from './WidgetLoaderPlugin';
export { default as WidgetLoaderPluginConfig } from './WidgetLoaderPluginConfig';
export { default as ControlType } from './controls/ControlType';
Expand Down
134 changes: 134 additions & 0 deletions packages/dashboard-core-plugins/src/useIrisGridSimplePivotModel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { type dh } from '@deephaven/jsapi-types';
import { useApi } from '@deephaven/jsapi-bootstrap';
import { useCallback, useEffect, useState } from 'react';
import {
type IrisGridModel,
IrisGridSimplePivotModel,
type KeyColumnArray,
type SimplePivotSchema,
} from '@deephaven/iris-grid';
import Log from '@deephaven/log';

const log = Log.module('useIrisGridSimplePivotModel');

export interface SimplePivotFetchResult {
columnMap: KeyColumnArray;
schema: SimplePivotSchema;
table: dh.Table;
keyTable: dh.Table;
totalsTable: dh.Table | null;
pivotWidget: dh.Widget;
}

export type IrisGridModelFetch = () => Promise<SimplePivotFetchResult>;

export type IrisGridModelFetchErrorResult = {
error: NonNullable<unknown>;
status: 'error';
};

export type IrisGridModelFetchLoadingResult = {
status: 'loading';
};

export type IrisGridModelFetchSuccessResult = {
status: 'success';
model: IrisGridModel;
};

export type IrisGridModelFetchResult = (
| IrisGridModelFetchErrorResult
| IrisGridModelFetchLoadingResult
| IrisGridModelFetchSuccessResult
) & {
reload: () => void;
};

/** Pass in a table `fetch` function, will load the model and handle any errors */
export function useIrisGridSimplePivotModel(
fetch: IrisGridModelFetch
): IrisGridModelFetchResult {
const dh = useApi();
const [model, setModel] = useState<IrisGridModel>();
const [error, setError] = useState<unknown>();
const [isLoading, setIsLoading] = useState(true);

log.debug('render useIrisGridSimplePivotModel', model, error);

// Close the model when component is unmounted
useEffect(
() => () => {
if (model) {
model.close();
}
},
[model]
);

const makeModel = useCallback(async () => {
log.debug('Fetching model');
const { columnMap, keyTable, pivotWidget, schema, table, totalsTable } =
await fetch();
log.debug('Fetching model before new Model');
return new IrisGridSimplePivotModel(
dh,
table,
keyTable,
totalsTable,
columnMap,
schema,
pivotWidget
);
}, [dh, fetch]);

const reload = useCallback(async () => {
setIsLoading(true);
setError(undefined);
try {
const newModel = await makeModel();
setModel(newModel);
setIsLoading(false);
} catch (e) {
setError(e);
setIsLoading(false);
}
}, [makeModel]);

useEffect(() => {
log.debug('useEffect makeModel');
let cancelled = false;
async function init() {
setIsLoading(true);
setError(undefined);
try {
const newModel = await makeModel();
if (!cancelled) {
setModel(newModel);
setIsLoading(false);
}
} catch (e) {
if (!cancelled) {
setError(e);
setIsLoading(false);
}
}
}

init();

return () => {
cancelled = true;
};
}, [makeModel]);

if (isLoading) {
return { reload, status: 'loading' };
}
if (error != null) {
return { error, reload, status: 'error' };
}
if (model != null) {
return { model, reload, status: 'success' };
}
throw new Error('Invalid state');
}
2 changes: 2 additions & 0 deletions packages/embed-widget/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,14 @@ async function getCorePlugins() {
GridPluginConfig,
PandasPluginConfig,
ChartPluginConfig,
SimplePivotPluginConfig,
WidgetLoaderPluginConfig,
} = dashboardCorePlugins;
return [
GridPluginConfig,
PandasPluginConfig,
ChartPluginConfig,
SimplePivotPluginConfig,
WidgetLoaderPluginConfig,
];
}
Expand Down
16 changes: 11 additions & 5 deletions packages/grid/src/Grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -562,12 +562,14 @@ class Grid extends PureComponent<GridProps, GridState> {
movedRows: currentStateMovedRows,
} = this.state;

const stateUpdates: Partial<GridState> = {};

if (prevPropMovedColumns !== movedColumns) {
this.setState({ movedColumns });
stateUpdates.movedColumns = movedColumns;
}

if (prevPropMovedRows !== movedRows) {
this.setState({ movedRows });
stateUpdates.movedRows = movedRows;
}

if (prevStateMovedColumns !== currentStateMovedColumns) {
Expand All @@ -587,14 +589,16 @@ class Grid extends PureComponent<GridProps, GridState> {
}

if (isStickyBottom !== prevIsStickyBottom) {
this.setState({ isStuckToBottom: false });
stateUpdates.isStuckToBottom = false;
}

if (isStickyRight !== prevIsStickyRight) {
this.setState({ isStuckToRight: false });
stateUpdates.isStuckToRight = false;
}

this.updateMetrics();
const updatedState = { ...this.state, ...stateUpdates };

this.updateMetrics(updatedState);

this.requestUpdateCanvas();

Expand All @@ -603,6 +607,8 @@ class Grid extends PureComponent<GridProps, GridState> {
if (this.validateSelection()) {
this.checkSelectionChange(prevState);
}

this.setState(updatedState);
}

componentWillUnmount(): void {
Expand Down
Loading
Loading