Skip to content

Commit

Permalink
refactor: Update types for TablePlugins (#1176)
Browse files Browse the repository at this point in the history
- We were just defining a bunch of unknowns, which is bad practice
- Instead define actual types, so makers of TablePlugins can use those
types effectively
- Add some details to the AuthPlugins readme
- Did this cleanup while working on
deephaven/deephaven-js-plugins#12
- Tested using the `table-example` plugin:
https://github.com/deephaven/deephaven-js-plugins/tree/main/plugins/table-example
  - Used the following snippet:
```
from deephaven import empty_table
from deephaven.jcompat import j_hashmap
t = empty_table(5).update(["x=i"])
t2 = t.j_table.withAttributes(j_hashmap({ "PluginName": "table-example" }))
```
- Also tested error message is shown if unable to load the table plugin:

![image](https://github.com/deephaven/web-client-ui/assets/4505624/b8fe8b96-4bf7-441b-bff2-5ab4f06ff8ac)
  • Loading branch information
mofojed committed Jul 11, 2023
1 parent 9573df8 commit 792faa1
Show file tree
Hide file tree
Showing 9 changed files with 255 additions and 54 deletions.
116 changes: 116 additions & 0 deletions packages/auth-plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,122 @@ Authentication plugins for Deephaven. Used by [AuthBootstrap](../app-utils/src/c
npm install --save @deephaven/auth-plugins
```

## Developing New Auth Plugins

Export an `AuthPlugin` from a module to register an authentication plugin. Authentication plugins must implement the [AuthPlugin interface](./src/AuthPlugin.ts#L32). Authentication plugins can display a UI which then triggers how to login.

The Web UI loads all plugins on initialization, and uses the first available authentication plugin for authenticating. A sequence diagram of this flow at a high level, where `AuthPlugin` is the first authentication plugin that returns true when the `isAvailable` method is called.

```mermaid
sequenceDiagram
participant U as User
participant W as Web UI
participant P as AuthPlugin
participant S as Server
U->>W: Open app
activate W
W->>S: Load plugin modules
S-->>W: PluginModule[]
W->>S: client.getAuthConfigValues()
S-->>W: Auth config [string, string][]
W->>W: Select first available AuthPlugin
deactivate W
W->>P: Login
P->>S: client.login()
S-->>P: Login success
P-->>W: Login success
```

## Examples

Below are some sequence diagrams for some of the included Auth Plugins.

#### Pre-shared Key ([AuthPluginPsk](./src/AuthPluginPsk.tsx))

```mermaid
sequenceDiagram
participant W as Web UI
participant P as AuthPluginPsk
participant J as JS API
W->>P: Login
alt Key in query string
P->>J: client.login(key)
else Prompt user for key
P->>P: Prompt for key
P->>J: client.login(key)
end
J-->>P: Login success
P-->>W: Login success
```

### Composite Password/Anonymous plugin

Composite plugin giving the user the choice of logging in with a password or logging in anonymously

```mermaid
sequenceDiagram
participant W as Web UI
participant CP as CompositePlugin
participant AP as AnonymousPlugin
participant PP as PasswordPlugin
participant J as JS API
W->>CP: Login
CP->>CP: Prompt for authentication method
activate CP
alt Password login
activate PP
loop Until success
PP->>PP: Show Login UI
PP->>J: client.login(password)
alt Login success
J-->>PP: Login success
else Login failure
J-->>PP: Login failure
PP->>PP: Show login error
end
end
PP-->>CP: Login success
deactivate PP
else Anonymous login
activate AP
AP->>J: client.login(anonymous)
J-->>AP: Login success
AP-->>CP: Login success
deactivate AP
end
CP-->>W: Login success
deactivate CP
```

#### Auth0

Translation of flow from https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow, showing which responsibilities login plugin handles. Note that the plugins need to be loaded initially prior to redirecting to the authorization prompt, and then again after redirecting back to the Web UI. For a specific example using Keycloak, see [AuthPluginKeycloak](https://github.com/deephaven/deephaven-js-plugins/tree/main/plugins/auth-keycloak).

```mermaid
sequenceDiagram
participant U as User
participant W as Web UI
participant S as Server
participant P as Auth0Plugin
participant T as Auth0 Tenant
participant J as JS API
U->>W: Open app
W->>W: Select first available AuthPlugin
W->>P: Login
P->>T: Authorization code request to /authorize
T->>U: Redirect to login/authorization prompt
U-->>T: Authenticate and Consent
T->>W: Authorization code
W->>W: Select first available AuthPlugin
W->>P: Login
P->>T: Authorization Code + Client ID + Client Secret to /oauth/token
T->>T: Validate Authorization Code + Client ID + Client Secret
T-->>P: ID Token and Access Token
P->>J: client.login(token)
J-->>P: Login success
P-->>W: Login success
```

# Legal Notices

Deephaven Data Labs and any contributors grant you a license to the content of this repository under the Apache 2.0 License, see the [LICENSE](../../LICENSE) file.
16 changes: 5 additions & 11 deletions packages/code-studio/src/main/AppMainContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import React, {
Component,
ReactElement,
RefObject,
ForwardRefExoticComponent,
} from 'react';
import classNames from 'classnames';
import memoize from 'memoize-one';
Expand Down Expand Up @@ -62,6 +61,7 @@ import {
IrisGridPanelProps,
ColumnSelectionValidator,
getDashboardConnection,
TablePlugin,
} from '@deephaven/dashboard-core-plugins';
import {
vsGear,
Expand Down Expand Up @@ -639,9 +639,7 @@ export class AppMainContainer extends Component<
* @param pluginName The name of the plugin to load
* @returns An element from the plugin
*/
handleLoadTablePlugin(
pluginName: string
): ForwardRefExoticComponent<React.RefAttributes<unknown>> {
handleLoadTablePlugin(pluginName: string): TablePlugin {
const { plugins } = this.props;

// First check if we have any plugin modules loaded that match the TablePlugin.
Expand All @@ -651,15 +649,13 @@ export class AppMainContainer extends Component<
(pluginModule as { TablePlugin: ReactElement }).TablePlugin != null
) {
return (pluginModule as {
TablePlugin: ForwardRefExoticComponent<React.RefAttributes<unknown>>;
TablePlugin: TablePlugin;
}).TablePlugin;
}

const errorMessage = `Unable to find table plugin ${pluginName}.`;
log.error(errorMessage);
return ((
<div className="error-message">{`${errorMessage}`}</div>
) as unknown) as ForwardRefExoticComponent<React.RefAttributes<unknown>>;
throw new Error(errorMessage);
}

startListeningForDisconnect() {
Expand Down Expand Up @@ -741,9 +737,7 @@ export class AppMainContainer extends Component<
type: string = dh.VariableType.TABLE
): T & {
getDownloadWorker: () => Promise<ServiceWorker>;
loadPlugin: (
pluginName: string
) => React.ForwardRefExoticComponent<React.RefAttributes<unknown>>;
loadPlugin: (pluginName: string) => TablePlugin;
localDashboardId: string;
makeModel: () => Promise<IrisGridModel>;
} {
Expand Down
2 changes: 1 addition & 1 deletion packages/components/src/context-actions/ContextActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class ContextActions extends Component<
element: Element,
clientX: number,
clientY: number,
actions: ContextAction[]
actions: ResolvableContextAction[]
): void {
if (actions.length === 0) {
return;
Expand Down
6 changes: 3 additions & 3 deletions packages/dashboard-core-plugins/src/GridPlugin.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { DragEvent, useCallback, useEffect, useMemo } from 'react';
import { DragEvent, useCallback, useEffect, useMemo } from 'react';
import {
assertIsDashboardPluginProps,
DashboardPluginComponentProps,
Expand All @@ -11,11 +11,11 @@ import { IrisGridModelFactory, IrisGridThemeType } from '@deephaven/iris-grid';
import { useApi } from '@deephaven/jsapi-bootstrap';
import type { Table, VariableDefinition } from '@deephaven/jsapi-types';
import shortid from 'shortid';
import { IrisGridPanel, IrisGridPanelProps } from './panels';
import { IrisGridPanel, IrisGridPanelProps, TablePlugin } from './panels';

export type GridPluginProps = Partial<DashboardPluginComponentProps> & {
getDownloadWorker?: () => Promise<ServiceWorker>;
loadPlugin?: (name: string) => ReturnType<typeof React.forwardRef>;
loadPlugin?: (name: string) => TablePlugin;
hydrate: PanelHydrateFunction<IrisGridPanelProps>;
theme?: Partial<IrisGridThemeType>;
};
Expand Down
46 changes: 19 additions & 27 deletions packages/dashboard-core-plugins/src/panels/IrisGridPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
IrisGrid,
IrisGridModel,
IrisGridUtils,
IrisGridTableModel,
isIrisGridTableModelTemplate,
ColumnName,
PendingDataMap,
Expand All @@ -39,6 +38,8 @@ import {
ChartBuilderSettings,
DehydratedIrisGridState,
ColumnHeaderGroup,
IrisGridContextMenuData,
IrisGridTableModel,
} from '@deephaven/iris-grid';
import {
AdvancedFilterOptions,
Expand All @@ -60,10 +61,12 @@ import {
CancelablePromise,
PromiseUtils,
} from '@deephaven/utils';
import { ContextAction, ContextMenuRoot } from '@deephaven/components';
import {
ContextMenuRoot,
ResolvableContextAction,
} from '@deephaven/components';
import type { Column, FilterCondition, Sort } from '@deephaven/jsapi-types';
import {
GridRangeIndex,
GridState,
ModelIndex,
ModelSizeMap,
Expand All @@ -80,6 +83,7 @@ import WidgetPanel from './WidgetPanel';
import './IrisGridPanel.scss';
import { Link, LinkColumn } from '../linker/LinkerUtils';
import IrisGridPanelTooltip from './IrisGridPanelTooltip';
import TablePlugin, { TablePluginElement } from './TablePlugin';

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

Expand Down Expand Up @@ -151,7 +155,7 @@ export interface IrisGridPanelProps {
getDownloadWorker: () => Promise<ServiceWorker>;

// Load a plugin defined by the table
loadPlugin: (pluginName: string) => Plugin;
loadPlugin: (pluginName: string) => TablePlugin;

theme: IrisGridThemeType;
}
Expand Down Expand Up @@ -191,7 +195,7 @@ interface IrisGridPanelState {
searchValue: string;
selectedSearchColumns?: readonly string[];
invertSearchColumns: boolean;
Plugin?: Plugin;
Plugin?: TablePlugin;
pluginFilters: readonly FilterCondition[];
pluginFetchColumns: readonly string[];
modelQueue: ModelQueue;
Expand Down Expand Up @@ -336,8 +340,7 @@ export class IrisGridPanel extends PureComponent<

irisGrid: RefObject<IrisGrid>;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
pluginRef: RefObject<any>;
pluginRef: RefObject<TablePluginElement>;

modelPromise?: CancelablePromise<IrisGridModel>;

Expand Down Expand Up @@ -390,8 +393,7 @@ export class IrisGridPanel extends PureComponent<

getPluginContent = memoize(
(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Plugin: any,
Plugin: TablePlugin | undefined,
model: IrisGridModel | undefined,
user: User,
workspace: Workspace,
Expand All @@ -411,18 +413,17 @@ export class IrisGridPanel extends PureComponent<
<Plugin
ref={this.pluginRef}
filter={this.handlePluginFilter}
// onFilter is deprecated
onFilter={this.handlePluginFilter}
fetchColumns={this.handlePluginFetchColumns}
// onFetchColumns is deprecated
onFetchColumns={this.handlePluginFetchColumns}
model={model}
table={model.table}
user={user}
panel={this}
workspace={workspace}
components={PLUGIN_COMPONENTS}
onStateChange={this.handlePluginStateChange}
pluginState={pluginState}
onFilter={this.handlePluginFilter}
onFetchColumns={this.handlePluginFetchColumns}
user={user}
workspace={workspace}
components={PLUGIN_COMPONENTS}
/>
</div>
);
Expand Down Expand Up @@ -630,17 +631,8 @@ export class IrisGridPanel extends PureComponent<
this.setState({ pluginFetchColumns });
}

handleContextMenu(obj: {
model: IrisGridModel;
value: unknown;
valueText: string | null;
column: Column;
rowIndex: GridRangeIndex;
columnIndex: GridRangeIndex;
modelRow: GridRangeIndex;
modelColumn: GridRangeIndex;
}): ContextAction {
return this.pluginRef.current?.getMenu?.(obj) ?? [];
handleContextMenu(data: IrisGridContextMenuData): ResolvableContextAction[] {
return this.pluginRef.current?.getMenu?.(data) ?? [];
}

isColumnSelectionValid(tableColumn: Column | null): boolean {
Expand Down
Loading

0 comments on commit 792faa1

Please sign in to comment.