Skip to content

Commit 43a782d

Browse files
authored
feat: Consolidate and normalize plugin types (#1456)
Fixes #1454. Needs #1451 Still some cleanup that could be done around `DashboardPlugin`. I think instead of static metadata on the component (`displayName` is ok since it's part of React) those items should probably be part of the config object.
1 parent 81cdd65 commit 43a782d

24 files changed

Lines changed: 425 additions & 222 deletions

package-lock.json

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/app-utils/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"@deephaven/jsapi-types": "file:../jsapi-types",
3636
"@deephaven/jsapi-utils": "file:../jsapi-utils",
3737
"@deephaven/log": "file:../log",
38+
"@deephaven/plugin": "file:../plugin",
3839
"@deephaven/react-hooks": "file:../react-hooks",
3940
"@deephaven/utils": "file:../utils",
4041
"@paciolan/remote-component": "2.13.0",

packages/app-utils/src/components/AuthBootstrap.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,15 @@ export type AuthBootstrapProps = {
2121

2222
/** Core auth plugins that are always loaded */
2323
const CORE_AUTH_PLUGINS = new Map([
24-
['@deephaven/auth-plugins.AuthPluginParent', AuthPluginParent],
25-
['@deephaven/auth-plugins.AuthPluginPsk', AuthPluginPsk],
26-
['@deephaven/auth-plugins.AuthPluginAnonymous', AuthPluginAnonymous],
24+
[
25+
'@deephaven/auth-plugins.AuthPluginParent',
26+
{ AuthPlugin: AuthPluginParent },
27+
],
28+
['@deephaven/auth-plugins.AuthPluginPsk', { AuthPlugin: AuthPluginPsk }],
29+
[
30+
'@deephaven/auth-plugins.AuthPluginAnonymous',
31+
{ AuthPlugin: AuthPluginAnonymous },
32+
],
2733
]);
2834

2935
/**
Lines changed: 41 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
1-
import React, { ForwardRefExoticComponent } from 'react';
1+
import Log from '@deephaven/log';
22
import {
3-
AuthPlugin,
4-
AuthPluginComponent,
3+
type PluginModule,
4+
type AuthPlugin,
5+
type AuthPluginComponent,
56
isAuthPlugin,
6-
} from '@deephaven/auth-plugins';
7-
import Log from '@deephaven/log';
8-
import RemoteComponent from './RemoteComponent';
7+
LegacyAuthPlugin,
8+
LegacyPlugin,
9+
Plugin,
10+
PluginType,
11+
isLegacyAuthPlugin,
12+
isLegacyPlugin,
13+
} from '@deephaven/plugin';
914
import loadRemoteModule from './loadRemoteModule';
1015

1116
const log = Log.module('@deephaven/app-utils.PluginUtils');
1217

13-
// A PluginModule. This interface should have new fields added to it from different levels of plugins.
14-
// eslint-disable-next-line @typescript-eslint/no-empty-interface
15-
export interface PluginModule {}
16-
1718
export type PluginModuleMap = Map<string, PluginModule>;
1819

1920
export type PluginManifestPluginInfo = {
@@ -24,46 +25,14 @@ export type PluginManifestPluginInfo = {
2425

2526
export type PluginManifest = { plugins: PluginManifestPluginInfo[] };
2627

27-
/**
28-
* Load a component plugin from the server.
29-
* @param baseURL Base URL of the plugin server
30-
* @param pluginName Name of the component plugin to load
31-
* @returns A lazily loaded JSX.Element from the plugin
32-
*/
33-
export function loadComponentPlugin(
34-
baseURL: URL,
35-
pluginName: string
36-
): ForwardRefExoticComponent<React.RefAttributes<unknown>> {
37-
const pluginUrl = new URL(`${pluginName}.js`, baseURL);
38-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
39-
const Plugin: any = React.forwardRef((props, ref) => (
40-
<RemoteComponent
41-
url={pluginUrl.href}
42-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
43-
render={({ err, Component }: { err: unknown; Component: any }) => {
44-
if (err != null && err !== '') {
45-
const errorMessage = `Error loading plugin ${pluginName} from ${pluginUrl} due to ${err}`;
46-
log.error(errorMessage);
47-
return <div className="error-message">{`${errorMessage}`}</div>;
48-
}
49-
// eslint-disable-next-line react/jsx-props-no-spreading
50-
return <Component ref={ref} {...props} />;
51-
}}
52-
/>
53-
));
54-
Plugin.pluginName = pluginName;
55-
Plugin.displayName = 'Plugin';
56-
return Plugin;
57-
}
58-
5928
/**
6029
* Imports a commonjs plugin module from the provided URL
6130
* @param pluginUrl The URL of the plugin to load
6231
* @returns The loaded module
6332
*/
6433
export async function loadModulePlugin(
6534
pluginUrl: string
66-
): Promise<PluginModule> {
35+
): Promise<LegacyPlugin | { default: Plugin }> {
6736
const myModule = await loadRemoteModule(pluginUrl);
6837
return myModule;
6938
}
@@ -86,7 +55,7 @@ export async function loadJson(jsonUrl: string): Promise<PluginManifest> {
8655
}
8756

8857
/**
89-
* Load all plugin modules available.
58+
* Load all plugin modules available based on the manifest file at the provided base URL
9059
* @param modulePluginsUrl The base URL of the module plugins to load
9160
* @returns A map from the name of the plugin to the plugin module that was loaded
9261
*/
@@ -102,7 +71,7 @@ export async function loadModulePlugins(
10271
}
10372

10473
log.debug('Plugin manifest loaded:', manifest);
105-
const pluginPromises: Promise<PluginModule>[] = [];
74+
const pluginPromises: Promise<LegacyPlugin | { default: Plugin }>[] = [];
10675
for (let i = 0; i < manifest.plugins.length; i += 1) {
10776
const { name, main } = manifest.plugins[i];
10877
const pluginMainUrl = `${modulePluginsUrl}/${name}/${main}`;
@@ -115,7 +84,10 @@ export async function loadModulePlugins(
11584
const module = pluginModules[i];
11685
const { name } = manifest.plugins[i];
11786
if (module.status === 'fulfilled') {
118-
pluginMap.set(name, module.value);
87+
pluginMap.set(
88+
name,
89+
isLegacyPlugin(module.value) ? module.value : module.value.default
90+
);
11991
} else {
12092
log.error(`Unable to load plugin ${name}`, module.reason);
12193
}
@@ -147,28 +119,30 @@ export function getAuthHandlers(
147119
export function getAuthPluginComponent(
148120
pluginMap: PluginModuleMap,
149121
authConfigValues: Map<string, string>,
150-
corePlugins?: Map<string, AuthPlugin>
122+
corePlugins = new Map<string, AuthPlugin | LegacyAuthPlugin>()
151123
): AuthPluginComponent {
152124
const authHandlers = getAuthHandlers(authConfigValues);
153-
// Filter out all the plugins that are auth plugins, and then map them to [pluginName, AuthPlugin] pairs
154-
// Uses some pretty disgusting casting, because TypeScript wants to treat it as an (string | AuthPlugin)[] array instead
125+
// User plugins take priority over core plugins
155126
const authPlugins = (
156-
[...pluginMap.entries()].filter(
157-
([, plugin]: [string, { AuthPlugin?: AuthPlugin }]) =>
158-
isAuthPlugin(plugin.AuthPlugin)
159-
) as [string, { AuthPlugin: AuthPlugin }][]
160-
).map(([name, plugin]) => [name, plugin.AuthPlugin]) as [
161-
string,
162-
AuthPlugin,
163-
][];
164-
165-
// Add all the core plugins in priority
166-
authPlugins.push(...(corePlugins ?? []));
127+
[...pluginMap.entries(), ...corePlugins.entries()].filter(
128+
([, plugin]) => isAuthPlugin(plugin) || isLegacyAuthPlugin(plugin)
129+
) as [string, AuthPlugin | LegacyAuthPlugin][]
130+
).map(([name, plugin]) => {
131+
if (isLegacyAuthPlugin(plugin)) {
132+
return {
133+
type: PluginType.AUTH_PLUGIN,
134+
name,
135+
component: plugin.AuthPlugin.Component,
136+
isAvailable: plugin.AuthPlugin.isAvailable,
137+
};
138+
}
167139

168-
// Filter the available auth plugins
140+
return plugin;
141+
});
169142

170-
const availableAuthPlugins = authPlugins.filter(([name, authPlugin]) =>
171-
authPlugin.isAvailable(authHandlers, authConfigValues)
143+
// Filter the available auth plugins
144+
const availableAuthPlugins = authPlugins.filter(({ isAvailable }) =>
145+
isAvailable(authHandlers, authConfigValues)
172146
);
173147

174148
if (availableAuthPlugins.length === 0) {
@@ -178,12 +152,12 @@ export function getAuthPluginComponent(
178152
} else if (availableAuthPlugins.length > 1) {
179153
log.warn(
180154
'More than one login plugin available, will use the first one: ',
181-
availableAuthPlugins.map(([name]) => name).join(', ')
155+
availableAuthPlugins.map(({ name }) => name).join(', ')
182156
);
183157
}
184158

185-
const [loginPluginName, NewLoginPlugin] = availableAuthPlugins[0];
186-
log.info('Using LoginPlugin', loginPluginName);
159+
const { name, component } = availableAuthPlugins[0];
160+
log.info('Using LoginPlugin', name);
187161

188-
return NewLoginPlugin.Component;
162+
return component;
189163
}

packages/app-utils/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
{ "path": "../jsapi-types" },
1515
{ "path": "../jsapi-utils" },
1616
{ "path": "../log" },
17+
{ "path": "../plugin" },
1718
{ "path": "../react-hooks" },
1819
{ "path": "../utils" }
1920
]

packages/code-studio/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"@deephaven/jsapi-types": "file:../jsapi-types",
3131
"@deephaven/jsapi-utils": "file:../jsapi-utils",
3232
"@deephaven/log": "file:../log",
33+
"@deephaven/plugin": "file:../plugin",
3334
"@deephaven/pouch-storage": "file:../pouch-storage",
3435
"@deephaven/react-hooks": "file:../react-hooks",
3536
"@deephaven/redux": "file:../redux",

packages/code-studio/src/main/AppMainContainer.tsx

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ import {
5858
Link,
5959
ColumnSelectionValidator,
6060
getDashboardConnection,
61-
TablePlugin,
6261
IrisGridPanelMetadata,
6362
isIrisGridPanelMetadata,
6463
isLegacyIrisGridPanelMetadata,
@@ -96,6 +95,15 @@ import {
9695
import { PromiseUtils } from '@deephaven/utils';
9796
import GoldenLayout from '@deephaven/golden-layout';
9897
import type { ItemConfigType } from '@deephaven/golden-layout';
98+
import {
99+
type DashboardPlugin,
100+
isDashboardPlugin,
101+
type TablePluginComponent,
102+
isTablePlugin,
103+
type LegacyDashboardPlugin,
104+
isLegacyTablePlugin,
105+
isLegacyDashboardPlugin,
106+
} from '@deephaven/plugin';
99107
import JSZip from 'jszip';
100108
import SettingsMenu from '../settings/SettingsMenu';
101109
import AppControlsMenu from './AppControlsMenu';
@@ -632,20 +640,18 @@ export class AppMainContainer extends Component<
632640
* @param pluginName The name of the plugin to load
633641
* @returns An element from the plugin
634642
*/
635-
handleLoadTablePlugin(pluginName: string): TablePlugin {
643+
handleLoadTablePlugin(pluginName: string): TablePluginComponent {
636644
const { plugins } = this.props;
637645

638646
// First check if we have any plugin modules loaded that match the TablePlugin.
639647
const pluginModule = plugins.get(pluginName);
640-
if (
641-
pluginModule != null &&
642-
(pluginModule as { TablePlugin: ReactElement }).TablePlugin != null
643-
) {
644-
return (
645-
pluginModule as {
646-
TablePlugin: TablePlugin;
647-
}
648-
).TablePlugin;
648+
if (pluginModule != null) {
649+
if (isTablePlugin(pluginModule)) {
650+
return pluginModule.component;
651+
}
652+
if (isLegacyTablePlugin(pluginModule)) {
653+
return pluginModule.TablePlugin;
654+
}
649655
}
650656

651657
const errorMessage = `Unable to find table plugin ${pluginName}.`;
@@ -721,7 +727,7 @@ export class AppMainContainer extends Component<
721727
id: string
722728
): DehydratedDashboardPanelProps & {
723729
getDownloadWorker: () => Promise<ServiceWorker>;
724-
loadPlugin: (pluginName: string) => TablePlugin;
730+
loadPlugin: (pluginName: string) => TablePluginComponent;
725731
localDashboardId: string;
726732
makeModel: () => Promise<IrisGridModel>;
727733
} {
@@ -785,14 +791,19 @@ export class AppMainContainer extends Component<
785791
});
786792
}
787793

788-
getDashboardPlugins = memoize((plugins: DeephavenPluginModuleMap) =>
789-
(
790-
[...plugins.entries()].filter(
791-
([, plugin]: [string, { DashboardPlugin?: typeof React.Component }]) =>
792-
plugin.DashboardPlugin != null
793-
) as [string, { DashboardPlugin: typeof React.Component }][]
794-
).map(([name, { DashboardPlugin }]) => <DashboardPlugin key={name} />)
795-
);
794+
getDashboardPlugins = memoize((plugins: DeephavenPluginModuleMap) => {
795+
const legacyPlugins = (
796+
[...plugins.entries()].filter(([, plugin]) =>
797+
isLegacyDashboardPlugin(plugin)
798+
) as [string, LegacyDashboardPlugin][]
799+
).map(([name, { DashboardPlugin: DPlugin }]) => <DPlugin key={name} />);
800+
801+
return legacyPlugins.concat(
802+
[...plugins.values()]
803+
.filter<DashboardPlugin>(isDashboardPlugin)
804+
.map(({ component: DPlugin, name }) => <DPlugin key={name} />)
805+
);
806+
});
796807

797808
render(): ReactElement {
798809
const { activeTool, plugins, user, workspace, serverConfigValues } =

packages/code-studio/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
{ "path": "../jsapi-utils" },
2828
{ "path": "../log" },
2929
{ "path": "../app-utils" },
30+
{ "path": "../plugin" },
3031
{ "path": "../pouch-storage" },
3132
{ "path": "../react-hooks" },
3233
{ "path": "../redux" },

0 commit comments

Comments
 (0)