read infos from package.json

Summary:
Adding the properties from a plugin's `package.json` as static properties to the class.
The name from `package.json` is used as it's `id`.

This allows us in the future to add meta information about a plugin to it's package.json and still use the data inside the app.

Reviewed By: priteshrnandgaonkar

Differential Revision: D13417288

fbshipit-source-id: 3d0a62d4cb0115153cce1aaee677b9680fefebf4
This commit is contained in:
Daniel Büchele
2018-12-18 08:27:25 -08:00
committed by Facebook Github Bot
parent e23da69db9
commit 6ffc027051
9 changed files with 54 additions and 47 deletions

View File

@@ -472,7 +472,7 @@ class NotificationItem extends Component<ItemProps, ItemState> {
const items = []; const items = [];
if (props.onHidePlugin && props.plugin) { if (props.onHidePlugin && props.plugin) {
items.push({ items.push({
label: `Hide ${props.plugin.title} plugin`, label: `Hide ${props.plugin.title || props.plugin.id} plugin`,
click: this.props.onHidePlugin, click: this.props.onHidePlugin,
}); });
} }

View File

@@ -120,9 +120,8 @@ class PluginContainer extends PureComponent<Props> {
<React.Fragment> <React.Fragment>
<Container key="plugin"> <Container key="plugin">
<ErrorBoundary <ErrorBoundary
heading={`Plugin "${ heading={`Plugin "${activePlugin.title ||
activePlugin.title 'Unknown'}" encountered an error during render`}
}" encountered an error during render`}
logger={this.props.logger}> logger={this.props.logger}>
{React.createElement(activePlugin, props)} {React.createElement(activePlugin, props)}
</ErrorBoundary> </ErrorBoundary>

View File

@@ -9,9 +9,6 @@ import {createTablePlugin} from '../createTablePlugin.js';
import {FlipperPlugin} from '../plugin.js'; import {FlipperPlugin} from '../plugin.js';
const PROPS = { const PROPS = {
title: 'Plugin Title',
id: 'pluginID',
icon: 'icon',
method: 'method', method: 'method',
resetMethod: 'resetMethod', resetMethod: 'resetMethod',
columns: {}, columns: {},
@@ -25,24 +22,6 @@ test('createTablePlugin returns FlipperPlugin', () => {
expect(tablePlugin.prototype).toBeInstanceOf(FlipperPlugin); expect(tablePlugin.prototype).toBeInstanceOf(FlipperPlugin);
}); });
test('Plugin ID is set', () => {
const id = 'pluginID';
const tablePlugin = createTablePlugin({...PROPS, id});
expect(tablePlugin.id).toBe(id);
});
test('Plugin title is set', () => {
const title = 'My Plugin Title';
const tablePlugin = createTablePlugin({...PROPS, title});
expect(tablePlugin.title).toBe(title);
});
test('Plugin icon is set', () => {
const icon = 'icon';
const tablePlugin = createTablePlugin({...PROPS, icon});
expect(tablePlugin.icon).toBe(icon);
});
test('persistedStateReducer is resetting data', () => { test('persistedStateReducer is resetting data', () => {
const resetMethod = 'resetMethod'; const resetMethod = 'resetMethod';
const tablePlugin = createTablePlugin({...PROPS, resetMethod}); const tablePlugin = createTablePlugin({...PROPS, resetMethod});

View File

@@ -146,11 +146,11 @@ class PluginSidebarListItem extends Component<{
<ListItem active={isActive} onClick={this.props.onClick}> <ListItem active={isActive} onClick={this.props.onClick}>
<PluginIcon <PluginIcon
isActive={isActive} isActive={isActive}
name={plugin.icon} name={plugin.icon || 'apps'}
backgroundColor={iconColor} backgroundColor={iconColor}
color={colors.white} color={colors.white}
/> />
<PluginName>{plugin.title}</PluginName> <PluginName>{plugin.title || plugin.id}</PluginName>
</ListItem> </ListItem>
); );
} }
@@ -228,7 +228,11 @@ class MainSidebar extends PureComponent<MainSidebarProps> {
}> }>
<PluginIcon <PluginIcon
color={colors.light50} color={colors.light50}
name={numNotifications > 0 ? NotificationsHub.icon : 'bell-null'} name={
numNotifications > 0
? NotificationsHub.icon || 'bell'
: 'bell-null'
}
isActive={selectedPlugin === NotificationsHub.id} isActive={selectedPlugin === NotificationsHub.id}
/> />
<PluginName <PluginName

View File

@@ -22,17 +22,14 @@ type RowData = {
id: ID, id: ID,
}; };
type Props<T> = {| type Props<T> = {
title: string,
id: string,
icon: string,
method: string, method: string,
resetMethod?: string, resetMethod?: string,
columns: TableColumns, columns: TableColumns,
columnSizes: TableColumnSizes, columnSizes: TableColumnSizes,
renderSidebar: (row: T) => any, renderSidebar: (row: T) => any,
buildRow: (row: T) => any, buildRow: (row: T) => any,
|}; };
type PersistedState<T> = {| type PersistedState<T> = {|
rows: TableRows, rows: TableRows,
@@ -59,9 +56,6 @@ type State = {|
export function createTablePlugin<T: RowData>(props: Props<T>) { export function createTablePlugin<T: RowData>(props: Props<T>) {
// $FlowFixMe persistedStateReducer is fine to accept payload of type T, because it is of type RowData // $FlowFixMe persistedStateReducer is fine to accept payload of type T, because it is of type RowData
return class extends FlipperPlugin<State, *, PersistedState<T>> { return class extends FlipperPlugin<State, *, PersistedState<T>> {
static title = props.title;
static id = props.id;
static icon = props.icon;
static keyboardActions = ['clear', 'createPaste']; static keyboardActions = ['clear', 'createPaste'];
static defaultPersistedState: PersistedState<T> = { static defaultPersistedState: PersistedState<T> = {
@@ -170,7 +164,7 @@ export function createTablePlugin<T: RowData>(props: Props<T>) {
return ( return (
<FlexColumn grow={true}> <FlexColumn grow={true}>
<SearchableTable <SearchableTable
key={props.id} key={this.constructor.id}
rowLineHeight={28} rowLineHeight={28}
floating={false} floating={false}
multiline={true} multiline={true}

View File

@@ -7,4 +7,6 @@
import {FlipperPlugin} from '../../plugin.js'; import {FlipperPlugin} from '../../plugin.js';
export default class extends FlipperPlugin {} export default class extends FlipperPlugin {
static id = 'Static ID';
}

View File

@@ -18,6 +18,7 @@ import reducers from '../../reducers/index.js';
import Logger from '../../fb-stubs/Logger.js'; import Logger from '../../fb-stubs/Logger.js';
import configureStore from 'redux-mock-store'; import configureStore from 'redux-mock-store';
import {TEST_PASSING_GK, TEST_FAILING_GK} from '../../fb-stubs/GK'; import {TEST_PASSING_GK, TEST_FAILING_GK} from '../../fb-stubs/GK';
import TestPlugin from './TestPlugin';
const mockStore = configureStore([])(reducers(undefined, {type: 'INIT'})); const mockStore = configureStore([])(reducers(undefined, {type: 'INIT'}));
const logger = new Logger(); const logger = new Logger();
@@ -111,11 +112,17 @@ test('requirePlugin returns null for invalid requires', () => {
}); });
test('requirePlugin loads plugin', () => { test('requirePlugin loads plugin', () => {
const name = 'pluginID';
const homepage = 'https://fb.workplace.com/groups/230455004101832/';
const plugin = requirePlugin(require)({ const plugin = requirePlugin(require)({
name: 'pluginID', name,
homepage,
out: path.join(__dirname, 'TestPlugin.js'), out: path.join(__dirname, 'TestPlugin.js'),
// $FlowFixMe Electron require returns default exports wrapped in an object });
}).default; // $FlowFixMe
expect(plugin.prototype).toBeInstanceOf(FlipperPlugin);
expect(plugin.prototype).toBeInstanceOf(FlipperPlugin); // $FlowFixMe
expect(plugin.homepage).toBe(homepage);
// $FlowFixMe
expect(plugin.id).toBe(TestPlugin.id);
}); });

View File

@@ -32,6 +32,7 @@ export default (store: Store, logger: Logger) => {
window.Flipper = Flipper; window.Flipper = Flipper;
const disabled = checkDisabled(); const disabled = checkDisabled();
const initialPlugins: Array< const initialPlugins: Array<
Class<FlipperPlugin<> | FlipperDevicePlugin<>>, Class<FlipperPlugin<> | FlipperDevicePlugin<>>,
> = [...getBundledPlugins(), ...getDynamicPlugins()] > = [...getBundledPlugins(), ...getDynamicPlugins()]
@@ -113,10 +114,27 @@ export function requirePlugin(
pluginDefinition: PluginDefinition, pluginDefinition: PluginDefinition,
): ?Class<FlipperPlugin<> | FlipperDevicePlugin<>> => { ): ?Class<FlipperPlugin<> | FlipperDevicePlugin<>> => {
try { try {
const plugin = requireFunction(pluginDefinition.out); let plugin = requireFunction(pluginDefinition.out);
if (plugin.default) {
plugin = plugin.default;
}
if (!plugin.prototype instanceof FlipperBasePlugin) { if (!plugin.prototype instanceof FlipperBasePlugin) {
throw new Error(`Plugin ${plugin.name} is not a FlipperBasePlugin`); throw new Error(`Plugin ${plugin.name} is not a FlipperBasePlugin`);
} }
// set values from package.json as static variables on class
Object.keys(pluginDefinition).forEach(key => {
if (key === 'name') {
plugin.id = plugin.id || pluginDefinition.name;
} else if (key === 'id') {
throw new Error(
'Field "id" not allowed in package.json. The plugin\'s name will be used as ID"',
);
} else {
plugin[key] = plugin[key] || pluginDefinition[key];
}
});
return plugin; return plugin;
} catch (e) { } catch (e) {
console.error(pluginDefinition, e); console.error(pluginDefinition, e);

View File

@@ -49,9 +49,13 @@ export class FlipperBasePlugin<
Actions = *, Actions = *,
PersistedState = *, PersistedState = *,
> extends React.Component<Props<PersistedState>, State> { > extends React.Component<Props<PersistedState>, State> {
static title: string = 'Unknown'; static title: ?string = null;
static id: string = 'Unknown'; static id: string = '';
static icon: string = 'apps'; static icon: ?string = null;
static bugs: ?{
email?: string,
url?: string,
} = null;
static keyboardActions: ?KeyboardActions; static keyboardActions: ?KeyboardActions;
static screenshot: ?string; static screenshot: ?string;
static defaultPersistedState: PersistedState; static defaultPersistedState: PersistedState;
@@ -78,7 +82,7 @@ export class FlipperBasePlugin<
onKeyboardAction: ?(action: string) => void; onKeyboardAction: ?(action: string) => void;
toJSON() { toJSON() {
return `<${this.constructor.name}#${this.constructor.title}>`; return `<${this.constructor.name}#${this.constructor.id}>`;
} }
// methods to be overriden by plugins // methods to be overriden by plugins