Skip to content

Commit

Permalink
feat: Consolidate and normalize plugin types (#1456)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mattrunyon authored Sep 8, 2023
1 parent 81cdd65 commit 43a782d
Show file tree
Hide file tree
Showing 24 changed files with 425 additions and 222 deletions.
26 changes: 26 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/app-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@deephaven/jsapi-types": "file:../jsapi-types",
"@deephaven/jsapi-utils": "file:../jsapi-utils",
"@deephaven/log": "file:../log",
"@deephaven/plugin": "file:../plugin",
"@deephaven/react-hooks": "file:../react-hooks",
"@deephaven/utils": "file:../utils",
"@paciolan/remote-component": "2.13.0",
Expand Down
12 changes: 9 additions & 3 deletions packages/app-utils/src/components/AuthBootstrap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@ export type AuthBootstrapProps = {

/** Core auth plugins that are always loaded */
const CORE_AUTH_PLUGINS = new Map([
['@deephaven/auth-plugins.AuthPluginParent', AuthPluginParent],
['@deephaven/auth-plugins.AuthPluginPsk', AuthPluginPsk],
['@deephaven/auth-plugins.AuthPluginAnonymous', AuthPluginAnonymous],
[
'@deephaven/auth-plugins.AuthPluginParent',
{ AuthPlugin: AuthPluginParent },
],
['@deephaven/auth-plugins.AuthPluginPsk', { AuthPlugin: AuthPluginPsk }],
[
'@deephaven/auth-plugins.AuthPluginAnonymous',
{ AuthPlugin: AuthPluginAnonymous },
],
]);

/**
Expand Down
108 changes: 41 additions & 67 deletions packages/app-utils/src/plugins/PluginUtils.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import React, { ForwardRefExoticComponent } from 'react';
import Log from '@deephaven/log';
import {
AuthPlugin,
AuthPluginComponent,
type PluginModule,
type AuthPlugin,
type AuthPluginComponent,
isAuthPlugin,
} from '@deephaven/auth-plugins';
import Log from '@deephaven/log';
import RemoteComponent from './RemoteComponent';
LegacyAuthPlugin,
LegacyPlugin,
Plugin,
PluginType,
isLegacyAuthPlugin,
isLegacyPlugin,
} from '@deephaven/plugin';
import loadRemoteModule from './loadRemoteModule';

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

// A PluginModule. This interface should have new fields added to it from different levels of plugins.
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface PluginModule {}

export type PluginModuleMap = Map<string, PluginModule>;

export type PluginManifestPluginInfo = {
Expand All @@ -24,46 +25,14 @@ export type PluginManifestPluginInfo = {

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

/**
* Load a component plugin from the server.
* @param baseURL Base URL of the plugin server
* @param pluginName Name of the component plugin to load
* @returns A lazily loaded JSX.Element from the plugin
*/
export function loadComponentPlugin(
baseURL: URL,
pluginName: string
): ForwardRefExoticComponent<React.RefAttributes<unknown>> {
const pluginUrl = new URL(`${pluginName}.js`, baseURL);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const Plugin: any = React.forwardRef((props, ref) => (
<RemoteComponent
url={pluginUrl.href}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
render={({ err, Component }: { err: unknown; Component: any }) => {
if (err != null && err !== '') {
const errorMessage = `Error loading plugin ${pluginName} from ${pluginUrl} due to ${err}`;
log.error(errorMessage);
return <div className="error-message">{`${errorMessage}`}</div>;
}
// eslint-disable-next-line react/jsx-props-no-spreading
return <Component ref={ref} {...props} />;
}}
/>
));
Plugin.pluginName = pluginName;
Plugin.displayName = 'Plugin';
return Plugin;
}

/**
* Imports a commonjs plugin module from the provided URL
* @param pluginUrl The URL of the plugin to load
* @returns The loaded module
*/
export async function loadModulePlugin(
pluginUrl: string
): Promise<PluginModule> {
): Promise<LegacyPlugin | { default: Plugin }> {
const myModule = await loadRemoteModule(pluginUrl);
return myModule;
}
Expand All @@ -86,7 +55,7 @@ export async function loadJson(jsonUrl: string): Promise<PluginManifest> {
}

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

log.debug('Plugin manifest loaded:', manifest);
const pluginPromises: Promise<PluginModule>[] = [];
const pluginPromises: Promise<LegacyPlugin | { default: Plugin }>[] = [];
for (let i = 0; i < manifest.plugins.length; i += 1) {
const { name, main } = manifest.plugins[i];
const pluginMainUrl = `${modulePluginsUrl}/${name}/${main}`;
Expand All @@ -115,7 +84,10 @@ export async function loadModulePlugins(
const module = pluginModules[i];
const { name } = manifest.plugins[i];
if (module.status === 'fulfilled') {
pluginMap.set(name, module.value);
pluginMap.set(
name,
isLegacyPlugin(module.value) ? module.value : module.value.default
);
} else {
log.error(`Unable to load plugin ${name}`, module.reason);
}
Expand Down Expand Up @@ -147,28 +119,30 @@ export function getAuthHandlers(
export function getAuthPluginComponent(
pluginMap: PluginModuleMap,
authConfigValues: Map<string, string>,
corePlugins?: Map<string, AuthPlugin>
corePlugins = new Map<string, AuthPlugin | LegacyAuthPlugin>()
): AuthPluginComponent {
const authHandlers = getAuthHandlers(authConfigValues);
// Filter out all the plugins that are auth plugins, and then map them to [pluginName, AuthPlugin] pairs
// Uses some pretty disgusting casting, because TypeScript wants to treat it as an (string | AuthPlugin)[] array instead
// User plugins take priority over core plugins
const authPlugins = (
[...pluginMap.entries()].filter(
([, plugin]: [string, { AuthPlugin?: AuthPlugin }]) =>
isAuthPlugin(plugin.AuthPlugin)
) as [string, { AuthPlugin: AuthPlugin }][]
).map(([name, plugin]) => [name, plugin.AuthPlugin]) as [
string,
AuthPlugin,
][];

// Add all the core plugins in priority
authPlugins.push(...(corePlugins ?? []));
[...pluginMap.entries(), ...corePlugins.entries()].filter(
([, plugin]) => isAuthPlugin(plugin) || isLegacyAuthPlugin(plugin)
) as [string, AuthPlugin | LegacyAuthPlugin][]
).map(([name, plugin]) => {
if (isLegacyAuthPlugin(plugin)) {
return {
type: PluginType.AUTH_PLUGIN,
name,
component: plugin.AuthPlugin.Component,
isAvailable: plugin.AuthPlugin.isAvailable,
};
}

// Filter the available auth plugins
return plugin;
});

const availableAuthPlugins = authPlugins.filter(([name, authPlugin]) =>
authPlugin.isAvailable(authHandlers, authConfigValues)
// Filter the available auth plugins
const availableAuthPlugins = authPlugins.filter(({ isAvailable }) =>
isAvailable(authHandlers, authConfigValues)
);

if (availableAuthPlugins.length === 0) {
Expand All @@ -178,12 +152,12 @@ export function getAuthPluginComponent(
} else if (availableAuthPlugins.length > 1) {
log.warn(
'More than one login plugin available, will use the first one: ',
availableAuthPlugins.map(([name]) => name).join(', ')
availableAuthPlugins.map(({ name }) => name).join(', ')
);
}

const [loginPluginName, NewLoginPlugin] = availableAuthPlugins[0];
log.info('Using LoginPlugin', loginPluginName);
const { name, component } = availableAuthPlugins[0];
log.info('Using LoginPlugin', name);

return NewLoginPlugin.Component;
return component;
}
1 change: 1 addition & 0 deletions packages/app-utils/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
{ "path": "../jsapi-types" },
{ "path": "../jsapi-utils" },
{ "path": "../log" },
{ "path": "../plugin" },
{ "path": "../react-hooks" },
{ "path": "../utils" }
]
Expand Down
1 change: 1 addition & 0 deletions packages/code-studio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@deephaven/jsapi-types": "file:../jsapi-types",
"@deephaven/jsapi-utils": "file:../jsapi-utils",
"@deephaven/log": "file:../log",
"@deephaven/plugin": "file:../plugin",
"@deephaven/pouch-storage": "file:../pouch-storage",
"@deephaven/react-hooks": "file:../react-hooks",
"@deephaven/redux": "file:../redux",
Expand Down
51 changes: 31 additions & 20 deletions packages/code-studio/src/main/AppMainContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ import {
Link,
ColumnSelectionValidator,
getDashboardConnection,
TablePlugin,
IrisGridPanelMetadata,
isIrisGridPanelMetadata,
isLegacyIrisGridPanelMetadata,
Expand Down Expand Up @@ -96,6 +95,15 @@ import {
import { PromiseUtils } from '@deephaven/utils';
import GoldenLayout from '@deephaven/golden-layout';
import type { ItemConfigType } from '@deephaven/golden-layout';
import {
type DashboardPlugin,
isDashboardPlugin,
type TablePluginComponent,
isTablePlugin,
type LegacyDashboardPlugin,
isLegacyTablePlugin,
isLegacyDashboardPlugin,
} from '@deephaven/plugin';
import JSZip from 'jszip';
import SettingsMenu from '../settings/SettingsMenu';
import AppControlsMenu from './AppControlsMenu';
Expand Down Expand Up @@ -632,20 +640,18 @@ export class AppMainContainer extends Component<
* @param pluginName The name of the plugin to load
* @returns An element from the plugin
*/
handleLoadTablePlugin(pluginName: string): TablePlugin {
handleLoadTablePlugin(pluginName: string): TablePluginComponent {
const { plugins } = this.props;

// First check if we have any plugin modules loaded that match the TablePlugin.
const pluginModule = plugins.get(pluginName);
if (
pluginModule != null &&
(pluginModule as { TablePlugin: ReactElement }).TablePlugin != null
) {
return (
pluginModule as {
TablePlugin: TablePlugin;
}
).TablePlugin;
if (pluginModule != null) {
if (isTablePlugin(pluginModule)) {
return pluginModule.component;
}
if (isLegacyTablePlugin(pluginModule)) {
return pluginModule.TablePlugin;
}
}

const errorMessage = `Unable to find table plugin ${pluginName}.`;
Expand Down Expand Up @@ -721,7 +727,7 @@ export class AppMainContainer extends Component<
id: string
): DehydratedDashboardPanelProps & {
getDownloadWorker: () => Promise<ServiceWorker>;
loadPlugin: (pluginName: string) => TablePlugin;
loadPlugin: (pluginName: string) => TablePluginComponent;
localDashboardId: string;
makeModel: () => Promise<IrisGridModel>;
} {
Expand Down Expand Up @@ -785,14 +791,19 @@ export class AppMainContainer extends Component<
});
}

getDashboardPlugins = memoize((plugins: DeephavenPluginModuleMap) =>
(
[...plugins.entries()].filter(
([, plugin]: [string, { DashboardPlugin?: typeof React.Component }]) =>
plugin.DashboardPlugin != null
) as [string, { DashboardPlugin: typeof React.Component }][]
).map(([name, { DashboardPlugin }]) => <DashboardPlugin key={name} />)
);
getDashboardPlugins = memoize((plugins: DeephavenPluginModuleMap) => {
const legacyPlugins = (
[...plugins.entries()].filter(([, plugin]) =>
isLegacyDashboardPlugin(plugin)
) as [string, LegacyDashboardPlugin][]
).map(([name, { DashboardPlugin: DPlugin }]) => <DPlugin key={name} />);

return legacyPlugins.concat(
[...plugins.values()]
.filter<DashboardPlugin>(isDashboardPlugin)
.map(({ component: DPlugin, name }) => <DPlugin key={name} />)
);
});

render(): ReactElement {
const { activeTool, plugins, user, workspace, serverConfigValues } =
Expand Down
1 change: 1 addition & 0 deletions packages/code-studio/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
{ "path": "../jsapi-utils" },
{ "path": "../log" },
{ "path": "../app-utils" },
{ "path": "../plugin" },
{ "path": "../pouch-storage" },
{ "path": "../react-hooks" },
{ "path": "../redux" },
Expand Down
Loading

0 comments on commit 43a782d

Please sign in to comment.