Skip to content

Commit

Permalink
Allow disabling dynamic plugins via query parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
vojtechszocs committed Jul 2, 2021
1 parent fd5d080 commit 234a4f7
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 36 deletions.
3 changes: 1 addition & 2 deletions frontend/@types/console/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,8 @@ declare interface Window {
store?: {}; // Redux store
pluginStore?: {}; // Console plugin store
loadPluginEntry?: Function;
loadPluginFromURL?: Function;
Cypress?: {};
api: {};
api: {}; // Console dynamic plugin APIs
}

// TODO: Remove when upgrading to TypeScript 4.1.2+, which has a type for RelativeTimeFormat.
Expand Down
6 changes: 6 additions & 0 deletions frontend/packages/console-dynamic-plugin-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,12 @@ as enabled. Updating Console operator config triggers a new rollout of the Conso
Bridge reads the computed list of plugins upon its startup and injects this list into Console web page
via `SERVER_FLAGS` object.

## Disabling plugins in the browser

Console users can disable specific or all dynamic plugins that would normally get loaded upon Console
startup via `disable-plugins` query parameter. The value of this parameter is either a comma separated
list of plugin names (disable specific plugins) or an empty string (disable all plugins).

## Runtime constraints and specifics

- Loading multiple plugins with the same `name` (but with a different `version`) is not allowed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ export const initConsolePlugins = _.once(
registerPluginEntryCallback(pluginStore);
exposePluginAPI();

// Load all dynamic plugins which are currently enabled on the cluster
window.SERVER_FLAGS.consolePlugins.forEach((pluginName) => {
pluginStore.getAllowedDynamicPluginNames().forEach((pluginName) => {
loadAndEnablePlugin(pluginName, pluginStore, () => {
// TODO(vojtech): add new entry into the notification drawer
pluginStore.registerFailedDynamicPlugin(pluginName);
Expand Down
42 changes: 26 additions & 16 deletions frontend/packages/console-plugin-sdk/src/__tests__/store.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,8 @@ describe('PluginStore', () => {
expect(store.getAllExtensions()).toEqual(staticPluginExtensions);
});

it('starts off with no information on dynamic plugins', () => {
const store = new PluginStore();
it('initializes dynamic plugin information based on allowedDynamicPluginNames', () => {
const store = new PluginStore([], ['Test1', 'Test2']);

const {
dynamicPluginExtensions,
Expand All @@ -331,7 +331,17 @@ describe('PluginStore', () => {
expect(failedDynamicPluginNames.size).toBe(0);

expect(store.getAllExtensions()).toEqual([]);
expect(store.getDynamicPluginInfo()).toEqual([]);
expect(store.getAllowedDynamicPluginNames()).toEqual(['Test1', 'Test2']);
expect(store.getDynamicPluginInfo()).toEqual([
{
status: 'Pending',
pluginName: 'Test1',
},
{
status: 'Pending',
pluginName: 'Test2',
},
]);
});
});

Expand All @@ -347,7 +357,7 @@ describe('PluginStore', () => {
],
},
],
new Set(['Test1', 'Test2']),
['Test1', 'Test2'],
);

expect(store.getAllExtensions()).toEqual([
Expand Down Expand Up @@ -457,7 +467,7 @@ describe('PluginStore', () => {
});

it('invokes the listener when extensions in use or dynamic plugin information changes', () => {
const store = new PluginStore([], new Set(['Test']));
const store = new PluginStore([], ['Test']);
const manifest = getPluginManifest('Test', '1.2.3', [{ type: 'Foo', properties: {} }]);

const listeners = [jest.fn(), jest.fn()];
Expand All @@ -479,7 +489,7 @@ describe('PluginStore', () => {

describe('addDynamicPlugin', () => {
it('adds the given plugin into loadedDynamicPlugins', () => {
const store = new PluginStore([], new Set(['Test']));
const store = new PluginStore([], ['Test']);

const manifest = getPluginManifest('Test', '1.2.3', [
{
Expand Down Expand Up @@ -560,7 +570,7 @@ describe('PluginStore', () => {
});

it('does nothing if a plugin with the same ID is already registered', () => {
const store = new PluginStore([], new Set(['Test']));
const store = new PluginStore([], ['Test']);
const manifest1 = getPluginManifest('Test', '1.2.3', [{ type: 'Foo', properties: {} }]);
const manifest2 = getPluginManifest('Test', '1.2.3', [{ type: 'Bar', properties: {} }]);

Expand Down Expand Up @@ -591,7 +601,7 @@ describe('PluginStore', () => {
});

it('does nothing if the plugin is not listed via allowedDynamicPluginNames', () => {
const store = new PluginStore([], new Set(['Test1', 'Test2']));
const store = new PluginStore([], ['Test1', 'Test2']);
const manifest = getPluginManifest('Test', '1.2.3', [{ type: 'Foo', properties: {} }]);

store.addDynamicPlugin('Test@1.2.3', manifest, [{ type: 'Foo', properties: {} }]);
Expand All @@ -602,7 +612,7 @@ describe('PluginStore', () => {
});

it('does nothing if the plugin is already marked as failed', () => {
const store = new PluginStore([], new Set(['Test']));
const store = new PluginStore([], ['Test']);
const manifest = getPluginManifest('Test', '1.2.3', [{ type: 'Foo', properties: {} }]);

store.registerFailedDynamicPlugin('Test');
Expand All @@ -616,7 +626,7 @@ describe('PluginStore', () => {

describe('setDynamicPluginEnabled', () => {
it('recomputes dynamic extensions and calls all registered listeners', () => {
const store = new PluginStore([], new Set(['Test1', 'Test2']));
const store = new PluginStore([], ['Test1', 'Test2']);
const manifest1 = getPluginManifest('Test1', '1.2.3', [{ type: 'Foo', properties: {} }]);
const manifest2 = getPluginManifest('Test2', '2.3.4', [{ type: 'Bar', properties: {} }]);

Expand Down Expand Up @@ -703,7 +713,7 @@ describe('PluginStore', () => {
});

it('does nothing if the plugin is not loaded', () => {
const store = new PluginStore([], new Set(['Test']));
const store = new PluginStore([], ['Test']);
const manifest = getPluginManifest('Test', '1.2.3', [{ type: 'Foo', properties: {} }]);

store.addDynamicPlugin('Test@1.2.3', manifest, [{ type: 'Foo', properties: {} }]);
Expand All @@ -724,7 +734,7 @@ describe('PluginStore', () => {
});

it('does nothing if the plugin is already enabled or disabled', () => {
const store = new PluginStore([], new Set(['Test']));
const store = new PluginStore([], ['Test']);
const manifest = getPluginManifest('Test', '1.2.3', [{ type: 'Foo', properties: {} }]);

store.addDynamicPlugin('Test@1.2.3', manifest, [{ type: 'Foo', properties: {} }]);
Expand Down Expand Up @@ -772,7 +782,7 @@ describe('PluginStore', () => {

describe('registerFailedDynamicPlugin', () => {
it('adds the given plugin name to failedDynamicPluginNames', () => {
const store = new PluginStore([], new Set(['Test']));
const store = new PluginStore([], ['Test']);

const listeners = [jest.fn(), jest.fn()];
listeners.forEach((l) => store.subscribe(l));
Expand All @@ -795,7 +805,7 @@ describe('PluginStore', () => {
});

it('does nothing if the plugin is not listed via allowedDynamicPluginNames', () => {
const store = new PluginStore([], new Set(['Test1', 'Test2']));
const store = new PluginStore([], ['Test1', 'Test2']);

const listeners = [jest.fn(), jest.fn()];
listeners.forEach((l) => store.subscribe(l));
Expand All @@ -812,7 +822,7 @@ describe('PluginStore', () => {
});

it('does nothing if the plugin is already loaded', () => {
const store = new PluginStore([], new Set(['Test']));
const store = new PluginStore([], ['Test']);
const manifest = getPluginManifest('Test', '1.2.3', [{ type: 'Foo', properties: {} }]);

store.addDynamicPlugin('Test@1.2.3', manifest, [{ type: 'Foo', properties: {} }]);
Expand All @@ -834,7 +844,7 @@ describe('PluginStore', () => {

describe('getDynamicPluginInfo', () => {
it('returns plugin runtime information for all known dynamic plugins', () => {
const store = new PluginStore([], new Set(['Test1', 'Test2']));
const store = new PluginStore([], ['Test1', 'Test2']);
const manifest1 = getPluginManifest('Test1', '1.2.3', [{ type: 'Foo', properties: {} }]);

expect(store.getDynamicPluginInfo()).toEqual([
Expand Down
13 changes: 9 additions & 4 deletions frontend/packages/console-plugin-sdk/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ export class PluginStore {
// Extensions contributed by dynamic plugins (loaded from remote hosts at runtime)
private dynamicPluginExtensions: LoadedExtension[] = [];

private readonly allowedDynamicPluginNames: Set<string>;

// Dynamic plugins that were loaded successfully
private readonly loadedDynamicPlugins = new Map<string, LoadedDynamicPlugin>();

Expand All @@ -60,23 +62,26 @@ export class PluginStore {

private readonly listeners: VoidFunction[] = [];

constructor(
staticPlugins: ActivePlugin[] = [],
private readonly allowedDynamicPluginNames: Set<string> = new Set(),
) {
constructor(staticPlugins: ActivePlugin[] = [], allowedDynamicPluginNames: string[] = []) {
this.staticPluginExtensions = _.flatMap(
staticPlugins.map((p) =>
p.extensions.map((e, index) =>
Object.freeze(augmentExtension(sanitizeExtension({ ...e }), p.name, p.name, index)),
),
),
);

this.allowedDynamicPluginNames = new Set(allowedDynamicPluginNames);
}

getAllExtensions() {
return [...this.staticPluginExtensions, ...this.dynamicPluginExtensions];
}

getAllowedDynamicPluginNames() {
return Array.from(this.allowedDynamicPluginNames);
}

subscribe(listener: VoidFunction): VoidFunction {
let isSubscribed = true;
this.listeners.push(listener);
Expand Down
6 changes: 3 additions & 3 deletions frontend/public/components/utils/link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ export const getNamespace = (path: string): string => {
export const getURLSearchParams = () => {
const all: any = {};
const params = new URLSearchParams(window.location.search);
// The URLSearchParams type definition does not include `entries()`.
// https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/entries
for (const [k, v] of (params as any).entries()) {

for (const [k, v] of params.entries()) {
all[k] = v;
}

return all;
};

Expand Down
29 changes: 20 additions & 9 deletions frontend/public/plugins.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
import * as _ from 'lodash-es';
import { PluginStore } from '@console/plugin-sdk/src/store';
import { ActivePlugin } from '@console/plugin-sdk/src/typings';
import { loadPluginFromURL } from '@console/dynamic-plugin-sdk/src/runtime/plugin-loader';
import { getURLSearchParams } from './components/utils/link';

const getEnabledDynamicPluginNames = () => {
const allPluginNames = window.SERVER_FLAGS.consolePlugins;
const disabledPlugins = getURLSearchParams()['disable-plugins'];

if (disabledPlugins === '') {
return [];
} else if (!disabledPlugins) {
return allPluginNames;
}

const disabledPluginNames = _.compact(disabledPlugins.split(','));
return allPluginNames.filter((pluginName) => !disabledPluginNames.includes(pluginName));
};

// The '@console/active-plugins' module is generated during a webpack build,
// so we use dynamic require() instead of the usual static import statement.
Expand All @@ -9,22 +24,18 @@ const activePlugins =
? (require('@console/active-plugins').default as ActivePlugin[])
: [];

export const pluginStore = new PluginStore(
activePlugins,
new Set(window.SERVER_FLAGS.consolePlugins),
);
const dynamicPluginNames = getEnabledDynamicPluginNames();

export const pluginStore = new PluginStore(activePlugins, dynamicPluginNames);

if (process.env.NODE_ENV !== 'production') {
// Expose Console plugin store for debugging
window.pluginStore = pluginStore;

// Expose function to load dynamic plugins directly from URLs
window.loadPluginFromURL = loadPluginFromURL;
}

if (process.env.NODE_ENV !== 'test') {
// eslint-disable-next-line no-console
console.info(`Static plugins: [${activePlugins.map((p) => p.name).join(', ')}]`);
// eslint-disable-next-line no-console
console.info(`Dynamic plugins: [${window.SERVER_FLAGS.consolePlugins.join(', ')}]`);
console.info(`Dynamic plugins: [${dynamicPluginNames.join(', ')}]`);
}

0 comments on commit 234a4f7

Please sign in to comment.