Command processing (1/n)
Summary: *Stack summary*: this stack refactors plugin management actions to perform them in a dispatcher rather than in the root reducer (store.tsx) as all of these actions has side effects. To do that, we store requested plugin management actions (install/update/uninstall, star/unstar) in a queue which is then handled by pluginManager dispatcher. This dispatcher then dispatches all required state updates. *Diff summary*: implemented basic plugin action queue processing. Reviewed By: mweststrate Differential Revision: D26164945 fbshipit-source-id: 5d8ad9b4d7b1300e92883d24a71da9ca1f85b183
This commit is contained in:
committed by
Facebook GitHub Bot
parent
f10f963ff1
commit
8efdde08c4
@@ -53,7 +53,7 @@ import {ContentContainer} from './sandy-chrome/ContentContainer';
|
|||||||
import {Alert, Typography} from 'antd';
|
import {Alert, Typography} from 'antd';
|
||||||
import {InstalledPluginDetails} from 'plugin-lib';
|
import {InstalledPluginDetails} from 'plugin-lib';
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
import {activatePlugin} from './reducers/pluginManager';
|
import {loadPlugin} from './reducers/pluginManager';
|
||||||
import {produce} from 'immer';
|
import {produce} from 'immer';
|
||||||
import {reportUsage} from './utils/metrics';
|
import {reportUsage} from './utils/metrics';
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ type DispatchFromProps = {
|
|||||||
setPluginState: (payload: {pluginKey: string; state: any}) => void;
|
setPluginState: (payload: {pluginKey: string; state: any}) => void;
|
||||||
setStaticView: (payload: StaticView) => void;
|
setStaticView: (payload: StaticView) => void;
|
||||||
starPlugin: typeof starPlugin;
|
starPlugin: typeof starPlugin;
|
||||||
activatePlugin: typeof activatePlugin;
|
loadPlugin: typeof loadPlugin;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = StateFromProps & DispatchFromProps & OwnProps;
|
type Props = StateFromProps & DispatchFromProps & OwnProps;
|
||||||
@@ -381,7 +381,7 @@ class PluginContainer extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reloadPlugin() {
|
reloadPlugin() {
|
||||||
const {activatePlugin, latestInstalledVersion} = this.props;
|
const {loadPlugin, latestInstalledVersion} = this.props;
|
||||||
if (latestInstalledVersion) {
|
if (latestInstalledVersion) {
|
||||||
reportUsage(
|
reportUsage(
|
||||||
'plugin-auto-update:alert:reloadClicked',
|
'plugin-auto-update:alert:reloadClicked',
|
||||||
@@ -390,7 +390,7 @@ class PluginContainer extends PureComponent<Props, State> {
|
|||||||
},
|
},
|
||||||
latestInstalledVersion.id,
|
latestInstalledVersion.id,
|
||||||
);
|
);
|
||||||
activatePlugin({
|
loadPlugin({
|
||||||
plugin: latestInstalledVersion,
|
plugin: latestInstalledVersion,
|
||||||
enable: false,
|
enable: false,
|
||||||
notifyIfFailed: true,
|
notifyIfFailed: true,
|
||||||
@@ -619,6 +619,6 @@ export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
|||||||
selectPlugin,
|
selectPlugin,
|
||||||
setStaticView,
|
setStaticView,
|
||||||
starPlugin,
|
starPlugin,
|
||||||
activatePlugin,
|
loadPlugin: loadPlugin,
|
||||||
},
|
},
|
||||||
)(PluginContainer);
|
)(PluginContainer);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import path from 'path';
|
|||||||
import tmp from 'tmp';
|
import tmp from 'tmp';
|
||||||
import {promisify} from 'util';
|
import {promisify} from 'util';
|
||||||
import {reportPlatformFailures, reportUsage} from '../utils/metrics';
|
import {reportPlatformFailures, reportUsage} from '../utils/metrics';
|
||||||
import {activatePlugin, pluginInstalled} from '../reducers/pluginManager';
|
import {loadPlugin, pluginInstalled} from '../reducers/pluginManager';
|
||||||
import {showErrorNotification} from '../utils/notifications';
|
import {showErrorNotification} from '../utils/notifications';
|
||||||
|
|
||||||
// Adapter which forces node.js implementation for axios instead of browser implementation
|
// Adapter which forces node.js implementation for axios instead of browser implementation
|
||||||
@@ -130,7 +130,7 @@ async function handlePluginDownload(
|
|||||||
}
|
}
|
||||||
if (pluginIsDisabledForAllConnectedClients(store.getState(), plugin)) {
|
if (pluginIsDisabledForAllConnectedClients(store.getState(), plugin)) {
|
||||||
dispatch(
|
dispatch(
|
||||||
activatePlugin({
|
loadPlugin({
|
||||||
plugin: installedPlugin,
|
plugin: installedPlugin,
|
||||||
enable: startedByUser,
|
enable: startedByUser,
|
||||||
notifyIfFailed: startedByUser,
|
notifyIfFailed: startedByUser,
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
import {Store} from '../reducers/index';
|
import {Store} from '../reducers/index';
|
||||||
import {Logger} from '../fb-interfaces/Logger';
|
import {Logger} from '../fb-interfaces/Logger';
|
||||||
import {
|
import {
|
||||||
pluginActivationHandled,
|
LoadPluginActionPayload,
|
||||||
|
pluginCommandsProcessed,
|
||||||
registerInstalledPlugins,
|
registerInstalledPlugins,
|
||||||
} from '../reducers/pluginManager';
|
} from '../reducers/pluginManager';
|
||||||
import {
|
import {
|
||||||
@@ -22,7 +23,6 @@ import {sideEffect} from '../utils/sideEffect';
|
|||||||
import {requirePlugin} from './plugins';
|
import {requirePlugin} from './plugins';
|
||||||
import {registerPluginUpdate} from '../reducers/connections';
|
import {registerPluginUpdate} from '../reducers/connections';
|
||||||
import {showErrorNotification} from '../utils/notifications';
|
import {showErrorNotification} from '../utils/notifications';
|
||||||
import {reportUsage} from '../utils/metrics';
|
|
||||||
|
|
||||||
const maxInstalledPluginVersionsToKeep = 2;
|
const maxInstalledPluginVersionsToKeep = 2;
|
||||||
|
|
||||||
@@ -44,21 +44,27 @@ export default (store: Store, _logger: Logger) => {
|
|||||||
sideEffect(
|
sideEffect(
|
||||||
store,
|
store,
|
||||||
{name: 'handlePluginActivation', throttleMs: 1000, fireImmediately: true},
|
{name: 'handlePluginActivation', throttleMs: 1000, fireImmediately: true},
|
||||||
(state) => state.pluginManager.pluginActivationQueue,
|
(state) => state.pluginManager.pluginCommandsQueue,
|
||||||
(queue, store) => {
|
(queue, store) => {
|
||||||
for (const request of queue) {
|
for (const command of queue) {
|
||||||
try {
|
switch (command.type) {
|
||||||
reportUsage(
|
case 'LOAD_PLUGIN':
|
||||||
'plugin:activate',
|
loadPlugin(store, command.payload);
|
||||||
{
|
break;
|
||||||
version: request.plugin.version,
|
default:
|
||||||
enable: request.enable ? '1' : '0',
|
console.error('Unexpected plugin command', command);
|
||||||
notifyIfFailed: request.notifyIfFailed ? '1' : '0',
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
store.dispatch(pluginCommandsProcessed(queue.length));
|
||||||
},
|
},
|
||||||
request.plugin.id,
|
|
||||||
);
|
);
|
||||||
const plugin = requirePlugin(request.plugin);
|
};
|
||||||
const enablePlugin = request.enable;
|
|
||||||
|
function loadPlugin(store: Store, payload: LoadPluginActionPayload) {
|
||||||
|
try {
|
||||||
|
const plugin = requirePlugin(payload.plugin);
|
||||||
|
const enablePlugin = payload.enable;
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
registerPluginUpdate({
|
registerPluginUpdate({
|
||||||
plugin,
|
plugin,
|
||||||
@@ -67,17 +73,13 @@ export default (store: Store, _logger: Logger) => {
|
|||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(
|
console.error(
|
||||||
`Failed to activate plugin ${request.plugin.title} v${request.plugin.version}`,
|
`Failed to activate plugin ${payload.plugin.title} v${payload.plugin.version}`,
|
||||||
err,
|
err,
|
||||||
);
|
);
|
||||||
if (request.notifyIfFailed) {
|
if (payload.notifyIfFailed) {
|
||||||
showErrorNotification(
|
showErrorNotification(
|
||||||
`Failed to load plugin "${request.plugin.title}" v${request.plugin.version}`,
|
`Failed to load plugin "${payload.plugin.title}" v${payload.plugin.version}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
store.dispatch(pluginActivationHandled(queue.length));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -19,15 +19,22 @@ import semver from 'semver';
|
|||||||
export type State = {
|
export type State = {
|
||||||
installedPlugins: Map<string, InstalledPluginDetails>;
|
installedPlugins: Map<string, InstalledPluginDetails>;
|
||||||
uninstalledPlugins: Set<string>;
|
uninstalledPlugins: Set<string>;
|
||||||
pluginActivationQueue: PluginActivationRequest[];
|
pluginCommandsQueue: PluginCommand[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PluginActivationRequest = {
|
export type PluginCommand = LoadPluginAction;
|
||||||
|
|
||||||
|
export type LoadPluginActionPayload = {
|
||||||
plugin: ActivatablePluginDetails;
|
plugin: ActivatablePluginDetails;
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
notifyIfFailed: boolean;
|
notifyIfFailed: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type LoadPluginAction = {
|
||||||
|
type: 'LOAD_PLUGIN';
|
||||||
|
payload: LoadPluginActionPayload;
|
||||||
|
};
|
||||||
|
|
||||||
export type Action =
|
export type Action =
|
||||||
| {
|
| {
|
||||||
type: 'REGISTER_INSTALLED_PLUGINS';
|
type: 'REGISTER_INSTALLED_PLUGINS';
|
||||||
@@ -43,18 +50,15 @@ export type Action =
|
|||||||
payload: InstalledPluginDetails;
|
payload: InstalledPluginDetails;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'ACTIVATE_PLUGINS';
|
type: 'PLUGIN_COMMANDS_PROCESSED';
|
||||||
payload: PluginActivationRequest[];
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: 'PLUGIN_ACTIVATION_HANDLED';
|
|
||||||
payload: number;
|
payload: number;
|
||||||
};
|
}
|
||||||
|
| LoadPluginAction;
|
||||||
|
|
||||||
const INITIAL_STATE: State = {
|
const INITIAL_STATE: State = {
|
||||||
installedPlugins: new Map<string, InstalledPluginDetails>(),
|
installedPlugins: new Map<string, InstalledPluginDetails>(),
|
||||||
uninstalledPlugins: new Set<string>(),
|
uninstalledPlugins: new Set<string>(),
|
||||||
pluginActivationQueue: [],
|
pluginCommandsQueue: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function reducer(
|
export default function reducer(
|
||||||
@@ -77,13 +81,16 @@ export default function reducer(
|
|||||||
draft.installedPlugins.set(plugin.name, plugin);
|
draft.installedPlugins.set(plugin.name, plugin);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (action.type === 'ACTIVATE_PLUGINS') {
|
} else if (action.type === 'LOAD_PLUGIN') {
|
||||||
return produce(state, (draft) => {
|
return produce(state, (draft) => {
|
||||||
draft.pluginActivationQueue.push(...action.payload);
|
draft.pluginCommandsQueue.push({
|
||||||
|
type: 'LOAD_PLUGIN',
|
||||||
|
payload: action.payload,
|
||||||
});
|
});
|
||||||
} else if (action.type === 'PLUGIN_ACTIVATION_HANDLED') {
|
});
|
||||||
|
} else if (action.type === 'PLUGIN_COMMANDS_PROCESSED') {
|
||||||
return produce(state, (draft) => {
|
return produce(state, (draft) => {
|
||||||
draft.pluginActivationQueue.splice(0, action.payload);
|
draft.pluginCommandsQueue.splice(0, action.payload);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return {...state};
|
return {...state};
|
||||||
@@ -107,12 +114,12 @@ export const pluginInstalled = (payload: InstalledPluginDetails): Action => ({
|
|||||||
payload,
|
payload,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const activatePlugin = (payload: PluginActivationRequest): Action => ({
|
export const loadPlugin = (payload: LoadPluginActionPayload): Action => ({
|
||||||
type: 'ACTIVATE_PLUGINS',
|
type: 'LOAD_PLUGIN',
|
||||||
payload: [payload],
|
payload,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const pluginActivationHandled = (payload: number): Action => ({
|
export const pluginCommandsProcessed = (payload: number): Action => ({
|
||||||
type: 'PLUGIN_ACTIVATION_HANDLED',
|
type: 'PLUGIN_COMMANDS_PROCESSED',
|
||||||
payload,
|
payload,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import {
|
|||||||
PluginDownloadStatus,
|
PluginDownloadStatus,
|
||||||
startPluginDownload,
|
startPluginDownload,
|
||||||
} from '../../reducers/pluginDownloads';
|
} from '../../reducers/pluginDownloads';
|
||||||
import {activatePlugin, uninstallPlugin} from '../../reducers/pluginManager';
|
import {loadPlugin, uninstallPlugin} from '../../reducers/pluginManager';
|
||||||
import {BundledPluginDetails} from 'plugin-lib';
|
import {BundledPluginDetails} from 'plugin-lib';
|
||||||
import {reportUsage} from '../../utils/metrics';
|
import {reportUsage} from '../../utils/metrics';
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ export const PluginList = memo(function PluginList({
|
|||||||
const plugin = downloadablePlugins.find((p) => p.id === id)!;
|
const plugin = downloadablePlugins.find((p) => p.id === id)!;
|
||||||
reportUsage('plugin:install', {version: plugin.version}, plugin.id);
|
reportUsage('plugin:install', {version: plugin.version}, plugin.id);
|
||||||
if (plugin.isBundled) {
|
if (plugin.isBundled) {
|
||||||
dispatch(activatePlugin({plugin, enable: true, notifyIfFailed: true}));
|
dispatch(loadPlugin({plugin, enable: true, notifyIfFailed: true}));
|
||||||
} else {
|
} else {
|
||||||
dispatch(startPluginDownload({plugin, startedByUser: true}));
|
dispatch(startPluginDownload({plugin, startedByUser: true}));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user