diff --git a/src/NotificationsHub.tsx b/src/NotificationsHub.tsx index f43b5945b..7bf52285d 100644 --- a/src/NotificationsHub.tsx +++ b/src/NotificationsHub.tsx @@ -34,6 +34,7 @@ import {selectPlugin} from './reducers/connections'; import {State as StoreState} from './reducers/index'; import textContent from './utils/textContent'; import createPaste from './fb-stubs/createPaste'; +import {getPluginTitle} from './utils/pluginUtils'; type OwnProps = { onClear: () => void; @@ -429,7 +430,7 @@ class NotificationItem extends Component< const items = []; if (props.onHidePlugin && props.plugin) { items.push({ - label: `Hide ${props.plugin.title || props.plugin.id} plugin`, + label: `Hide ${getPluginTitle(props.plugin)} plugin`, click: this.props.onHidePlugin, }); } @@ -534,7 +535,7 @@ class NotificationItem extends Component< {action && ( )} @@ -543,7 +544,7 @@ class NotificationItem extends Component< )} {onHidePlugin && ( )} diff --git a/src/chrome/ExportDataPluginSheet.tsx b/src/chrome/ExportDataPluginSheet.tsx index bb192a8fc..502adf536 100644 --- a/src/chrome/ExportDataPluginSheet.tsx +++ b/src/chrome/ExportDataPluginSheet.tsx @@ -58,12 +58,12 @@ const Container = styled(FlexColumn)({ }); type State = { - availablePluginsToExport: Array; + availablePluginsToExport: Array<{id: string; label: string}>; }; class ExportDataPluginSheet extends Component { state: State = {availablePluginsToExport: []}; - static getDerivedStateFromProps(props: Props, _state: State) { + static getDerivedStateFromProps(props: Props, _state: State): State { const {plugins, pluginStates, pluginMessageQueue, selectedClient} = props; const availablePluginsToExport = getActivePersistentPlugins( pluginStates, diff --git a/src/chrome/ListView.tsx b/src/chrome/ListView.tsx index 4b5a4efc0..141dba3de 100644 --- a/src/chrome/ListView.tsx +++ b/src/chrome/ListView.tsx @@ -37,7 +37,7 @@ type Props = { onSubmit?: () => void; onChange: (elements: Array) => void; onHide: () => any; - elements: Array; + elements: Array<{label: string; id: string}>; title?: string; } & SubType; @@ -80,14 +80,15 @@ const Padder = styled.div<{ })); type RowComponentProps = { - name: string; + id: string; + label: string; selected: boolean; onChange: (name: string, selected: boolean) => void; }; class RowComponent extends Component { render() { - const {name, selected, onChange} = this.props; + const {id, label, selected, onChange} = this.props; return ( { paddingBottom={8} paddingLeft={8}> - {name} + {label} { - onChange(name, selected); + onChange(id, selected); }} /> @@ -151,10 +152,11 @@ export default class ListView extends Component { {this.props.title && {this.props.title}} - {this.props.elements.map(id => { + {this.props.elements.map(({id, label}) => { return ( - {plugin.title || plugin.id} + {getPluginTitle(plugin)} {starred !== undefined && (!starred || isActive) && ( { }, { type: 'button', - text: `Hide all ${plugin != null ? plugin.title : ''}`, + text: `Hide all ${ + plugin != null ? getPluginTitle(plugin) : '' + }`, }, ], closeButtonText: 'Hide', diff --git a/src/plugin.tsx b/src/plugin.tsx index 2b3395ad3..d174d7ea0 100644 --- a/src/plugin.tsx +++ b/src/plugin.tsx @@ -19,7 +19,6 @@ import {serialize, deserialize} from './utils/serialization'; import {Idler} from './utils/Idler'; import {StaticView} from './reducers/connections'; import {State as ReduxState} from './reducers'; -import {PersistedState} from './plugins/layout'; import {DEFAULT_MAX_QUEUE_SIZE} from './reducers/pluginMessageQueue'; type Parameters = any; @@ -275,10 +274,3 @@ export class FlipperPlugin< this.init(); } } - -export function sortPluginsByName( - a: typeof FlipperBasePlugin, - b: typeof FlipperBasePlugin, -): number { - return (a.title || a.id) > (b.title || b.id) ? 1 : -1; -} diff --git a/src/utils/__tests__/pluginUtils.node.js b/src/utils/__tests__/pluginUtils.node.js index a330a6af6..366448398 100644 --- a/src/utils/__tests__/pluginUtils.node.js +++ b/src/utils/__tests__/pluginUtils.node.js @@ -18,33 +18,45 @@ import type {State as PluginMessageQueueState} from '../../reducers/pluginStates import {FlipperBasePlugin} from 'flipper'; import type {ReduxState} from '../../reducers/index.tsx'; -class MockFlipperPluginWithDefaultPersistedState extends FlipperBasePlugin< - *, - *, - {msg: string}, -> { - static defaultPersistedState = {msg: 'MockFlipperPluginWithPersistedState'}; -} - -class MockFlipperPluginWithExportPersistedState extends FlipperBasePlugin< - *, - *, - {msg: string}, -> { - static exportPersistedState = ( - callClient: (string, ?Object) => Promise, - persistedState: ?{msg: string}, - store: ?ReduxState, - ): Promise => { - return Promise.resolve({msg: 'MockFlipperPluginWithExportPersistedState'}); +function createMockFlipperPluginWithDefaultPersistedState(id: string) { + return class MockFlipperPluginWithDefaultPersistedState extends FlipperBasePlugin< + *, + *, + {msg: string}, + > { + static id = id; + static defaultPersistedState = {msg: 'MockFlipperPluginWithPersistedState'}; }; } -class MockFlipperPluginWithNoPersistedState extends FlipperBasePlugin< - *, - *, - *, -> {} +function createMockFlipperPluginWithExportPersistedState(id: string) { + return class MockFlipperPluginWithExportPersistedState extends FlipperBasePlugin< + *, + *, + {msg: string}, + > { + static id = id; + static exportPersistedState = ( + callClient: (string, ?Object) => Promise, + persistedState: ?{msg: string}, + store: ?ReduxState, + ): Promise => { + return Promise.resolve({ + msg: 'MockFlipperPluginWithExportPersistedState', + }); + }; + }; +} + +function createMockFlipperPluginWithNoPersistedState(id: string) { + return class MockFlipperPluginWithNoPersistedState extends FlipperBasePlugin< + *, + *, + *, + > { + static id = id; + }; +} function mockPluginState( gatekeepedPlugins: Array, @@ -53,12 +65,24 @@ function mockPluginState( ): PluginsState { return { devicePlugins: new Map([ - ['DevicePlugin1', MockFlipperPluginWithDefaultPersistedState], - ['DevicePlugin2', MockFlipperPluginWithDefaultPersistedState], + [ + 'DevicePlugin1', + createMockFlipperPluginWithDefaultPersistedState('DevicePlugin1'), + ], + [ + 'DevicePlugin2', + createMockFlipperPluginWithDefaultPersistedState('DevicePlugin2'), + ], ]), clientPlugins: new Map([ - ['ClientPlugin1', MockFlipperPluginWithDefaultPersistedState], - ['ClientPlugin2', MockFlipperPluginWithDefaultPersistedState], + [ + 'ClientPlugin1', + createMockFlipperPluginWithDefaultPersistedState('ClientPlugin1'), + ], + [ + 'ClientPlugin2', + createMockFlipperPluginWithDefaultPersistedState('ClientPlugin2'), + ], ]), gatekeepedPlugins, disabledPlugins, @@ -98,12 +122,24 @@ test('getPersistentPlugins with no plugins getting excluded', () => { test('getPersistentPlugins, where the plugins with exportPersistedState not getting excluded', () => { const state: PluginsState = { devicePlugins: new Map([ - ['DevicePlugin1', MockFlipperPluginWithExportPersistedState], - ['DevicePlugin2', MockFlipperPluginWithExportPersistedState], + [ + 'DevicePlugin1', + createMockFlipperPluginWithExportPersistedState('DevicePlugin1'), + ], + [ + 'DevicePlugin2', + createMockFlipperPluginWithExportPersistedState('DevicePlugin2'), + ], ]), clientPlugins: new Map([ - ['ClientPlugin1', MockFlipperPluginWithExportPersistedState], - ['ClientPlugin2', MockFlipperPluginWithExportPersistedState], + [ + 'ClientPlugin1', + createMockFlipperPluginWithExportPersistedState('ClientPlugin1'), + ], + [ + 'ClientPlugin2', + createMockFlipperPluginWithExportPersistedState('ClientPlugin2'), + ], ]), gatekeepedPlugins: [], disabledPlugins: [], @@ -122,12 +158,24 @@ test('getPersistentPlugins, where the plugins with exportPersistedState not gett test('getPersistentPlugins, where the non persistent plugins getting excluded', () => { const state: PluginsState = { devicePlugins: new Map([ - ['DevicePlugin1', MockFlipperPluginWithNoPersistedState], - ['DevicePlugin2', MockFlipperPluginWithDefaultPersistedState], + [ + 'DevicePlugin1', + createMockFlipperPluginWithNoPersistedState('DevicePlugin1'), + ], + [ + 'DevicePlugin2', + createMockFlipperPluginWithDefaultPersistedState('DevicePlugin2'), + ], ]), clientPlugins: new Map([ - ['ClientPlugin1', MockFlipperPluginWithDefaultPersistedState], - ['ClientPlugin2', MockFlipperPluginWithNoPersistedState], + [ + 'ClientPlugin1', + createMockFlipperPluginWithDefaultPersistedState('ClientPlugin1'), + ], + [ + 'ClientPlugin2', + createMockFlipperPluginWithNoPersistedState('ClientPlugin2'), + ], ]), gatekeepedPlugins: [], disabledPlugins: [], @@ -141,12 +189,24 @@ test('getPersistentPlugins, where the non persistent plugins getting excluded', test('getActivePersistentPlugins, where the non persistent plugins getting excluded', () => { const state: PluginsState = { devicePlugins: new Map([ - ['DevicePlugin1', MockFlipperPluginWithNoPersistedState], - ['DevicePlugin2', MockFlipperPluginWithDefaultPersistedState], + [ + 'DevicePlugin1', + createMockFlipperPluginWithNoPersistedState('DevicePlugin1'), + ], + [ + 'DevicePlugin2', + createMockFlipperPluginWithDefaultPersistedState('DevicePlugin2'), + ], ]), clientPlugins: new Map([ - ['ClientPlugin1', MockFlipperPluginWithDefaultPersistedState], - ['ClientPlugin2', MockFlipperPluginWithNoPersistedState], + [ + 'ClientPlugin1', + createMockFlipperPluginWithDefaultPersistedState('ClientPlugin1'), + ], + [ + 'ClientPlugin2', + createMockFlipperPluginWithNoPersistedState('ClientPlugin2'), + ], ]), gatekeepedPlugins: [], disabledPlugins: [], @@ -161,19 +221,43 @@ test('getActivePersistentPlugins, where the non persistent plugins getting exclu }; const queues: PluginMessageQueueState = {}; const list = getActivePersistentPlugins(plugins, queues, state); - expect(list).toEqual(['ClientPlugin1', 'DevicePlugin2']); + expect(list).toEqual([ + { + id: 'ClientPlugin1', + label: 'ClientPlugin1', + }, + { + id: 'DevicePlugin2', + label: 'DevicePlugin2', + }, + ]); }); test('getActivePersistentPlugins, where the plugins not in pluginState or queue gets excluded', () => { const state: PluginsState = { devicePlugins: new Map([ - ['DevicePlugin1', MockFlipperPluginWithDefaultPersistedState], - ['DevicePlugin2', MockFlipperPluginWithDefaultPersistedState], + [ + 'DevicePlugin1', + createMockFlipperPluginWithDefaultPersistedState('DevicePlugin1'), + ], + [ + 'DevicePlugin2', + createMockFlipperPluginWithDefaultPersistedState('DevicePlugin2'), + ], ]), clientPlugins: new Map([ - ['ClientPlugin1', MockFlipperPluginWithDefaultPersistedState], - ['ClientPlugin2', MockFlipperPluginWithDefaultPersistedState], - ['ClientPlugin3', MockFlipperPluginWithDefaultPersistedState], + [ + 'ClientPlugin1', + createMockFlipperPluginWithDefaultPersistedState('ClientPlugin1'), + ], + [ + 'ClientPlugin2', + createMockFlipperPluginWithDefaultPersistedState('ClientPlugin2'), + ], + [ + 'ClientPlugin3', + createMockFlipperPluginWithDefaultPersistedState('ClientPlugin3'), + ], ]), gatekeepedPlugins: [], disabledPlugins: [], @@ -190,5 +274,18 @@ test('getActivePersistentPlugins, where the plugins not in pluginState or queue ], }; const list = getActivePersistentPlugins(plugins, queues, state); - expect(list).toEqual(['ClientPlugin2', 'ClientPlugin3', 'DevicePlugin1']); + expect(list).toEqual([ + { + id: 'ClientPlugin2', + label: 'ClientPlugin2', + }, + { + id: 'ClientPlugin3', + label: 'ClientPlugin3', + }, + { + id: 'DevicePlugin1', + label: 'DevicePlugin1', + }, + ]); }); diff --git a/src/utils/exportData.tsx b/src/utils/exportData.tsx index 25f25ed0d..ce3904c55 100644 --- a/src/utils/exportData.tsx +++ b/src/utils/exportData.tsx @@ -42,6 +42,7 @@ import {setSelectPluginsToExportActiveSheet} from '../reducers/application'; import {deconstructClientId, deconstructPluginKey} from '../utils/clientUtils'; import {performance} from 'perf_hooks'; import {processMessageQueue} from './messageQueue'; +import {getPluginTitle} from './pluginUtils'; export const IMPORT_FLIPPER_TRACE_EVENT = 'import-flipper-trace'; export const EXPORT_FLIPPER_TRACE_EVENT = 'export-flipper-trace'; @@ -85,7 +86,8 @@ type SerializePluginStatesOptions = { type PluginsToProcess = { pluginKey: string; - plugin: string; + pluginId: string; + pluginName: string; pluginClass: typeof FlipperPlugin | typeof FlipperDevicePlugin; client: Client; }[]; @@ -398,33 +400,39 @@ export async function fetchMetadata( const newPluginState = {...pluginStates}; const errorArray: Array = []; - for (const {plugin, pluginClass, client, pluginKey} of pluginsToProcess) { + for (const { + pluginName, + pluginId, + pluginClass, + client, + pluginKey, + } of pluginsToProcess) { const exportState = pluginClass ? pluginClass.exportPersistedState : null; if (exportState) { const fetchMetaDataMarker = `${EXPORT_FLIPPER_TRACE_EVENT}:fetch-meta-data-per-plugin`; performance.mark(fetchMetaDataMarker); try { statusUpdate && - statusUpdate(`Fetching metadata for plugin ${plugin}...`); + statusUpdate(`Fetching metadata for plugin ${pluginName}...`); const data = await promiseTimeout( 240000, // Fetching MobileConfig data takes ~ 3 mins, thus keeping timeout at 4 mins. exportState( - callClient(client, plugin), + callClient(client, pluginId), newPluginState[pluginKey], state, idler, statusUpdate, ), - `Timed out while collecting data for ${plugin}`, + `Timed out while collecting data for ${pluginName}`, ); getLogger().trackTimeSince(fetchMetaDataMarker, fetchMetaDataMarker, { - plugin, + pluginId, }); newPluginState[pluginKey] = data; } catch (e) { errorArray.push(e); getLogger().trackTimeSince(fetchMetaDataMarker, fetchMetaDataMarker, { - plugin, + pluginId, error: e, }); continue; @@ -441,7 +449,12 @@ async function processQueues( statusUpdate?: (msg: string) => void, idler?: Idler, ) { - for (const {plugin, pluginKey, pluginClass} of pluginsToProcess) { + for (const { + pluginName, + pluginId, + pluginKey, + pluginClass, + } of pluginsToProcess) { if (pluginClass.persistedStateReducer) { const processQueueMarker = `${EXPORT_FLIPPER_TRACE_EVENT}:process-queue-per-plugin`; performance.mark(processQueueMarker); @@ -454,14 +467,14 @@ async function processQueues( statusUpdate?.( `Processing event ${current} / ${total} (${Math.round( (current / total) * 100, - )}%) for plugin ${plugin}`, + )}%) for plugin ${pluginName}`, ); }, idler, ); getLogger().trackTimeSince(processQueueMarker, processQueueMarker, { - plugin, + pluginId, }); } } @@ -506,7 +519,8 @@ export function determinePluginsToProcess( pluginsToProcess.push({ pluginKey: key, client, - plugin, + pluginId: plugin, + pluginName: getPluginTitle(pluginClass), pluginClass, }); } diff --git a/src/utils/pluginUtils.tsx b/src/utils/pluginUtils.tsx index 2bd032b57..9803e94df 100644 --- a/src/utils/pluginUtils.tsx +++ b/src/utils/pluginUtils.tsx @@ -77,38 +77,46 @@ export function getActivePersistentPlugins( pluginsMessageQueue: PluginMessageQueueState, plugins: PluginsState, selectedClient?: Client, -): Array { +): {id: string; label: string}[] { const pluginsMap: Map< string, typeof FlipperDevicePlugin | typeof FlipperPlugin > = pluginsClassMap(plugins); - return getPersistentPlugins(plugins).filter(plugin => { - const pluginClass = pluginsMap.get(plugin); - const keys = [ - ...new Set([ - ...Object.keys(pluginsState), - ...Object.keys(pluginsMessageQueue), - ]), - ] - .filter(k => !selectedClient || k.includes(selectedClient.id)) - .map(key => deconstructPluginKey(key).pluginName); - let result = plugin == 'DeviceLogs'; - const pluginsWithExportPersistedState = - pluginClass && pluginClass.exportPersistedState != undefined; - const pluginsWithReduxData = keys.includes(plugin); - if (!result && selectedClient) { - // If there is a selected client, active persistent plugin is the plugin which is active for selectedClient and also persistent. - result = - selectedClient.plugins.includes(plugin) && - (pluginsWithExportPersistedState || pluginsWithReduxData); - } else if (!result && !selectedClient) { - // If there is no selected client, active persistent plugin is the plugin which is just persistent. - result = - (pluginClass && pluginClass.exportPersistedState != undefined) || - keys.includes(plugin); - } - return result; - }); + return getPersistentPlugins(plugins) + .map(pluginName => pluginsMap.get(pluginName)!) + .sort(sortPluginsByName) + .map(plugin => { + const keys = [ + ...new Set([ + ...Object.keys(pluginsState), + ...Object.keys(pluginsMessageQueue), + ]), + ] + .filter(k => !selectedClient || k.includes(selectedClient.id)) + .map(key => deconstructPluginKey(key).pluginName); + let result = plugin.id == 'DeviceLogs'; + const pluginsWithExportPersistedState = + plugin && plugin.exportPersistedState != undefined; + const pluginsWithReduxData = keys.includes(plugin.id); + if (!result && selectedClient) { + // If there is a selected client, active persistent plugin is the plugin which is active for selectedClient and also persistent. + result = + selectedClient.plugins.includes(plugin.id) && + (pluginsWithExportPersistedState || pluginsWithReduxData); + } else if (!result && !selectedClient) { + // If there is no selected client, active persistent plugin is the plugin which is just persistent. + result = + (plugin && plugin.exportPersistedState != undefined) || + keys.includes(plugin.id); + } + return (result + ? { + id: plugin.id, + label: getPluginTitle(plugin), + } + : undefined)!; + }) + .filter(Boolean); } export function getPersistentPlugins(plugins: PluginsState): Array { @@ -144,3 +152,27 @@ export function getPersistentPlugins(plugins: PluginsState): Array { ); }); } + +export function getPluginTitle(pluginClass: typeof FlipperBasePlugin) { + return pluginClass.title || pluginClass.id; +} + +export function sortPluginsByName( + a: typeof FlipperBasePlugin, + b: typeof FlipperBasePlugin, +): number { + // make sure Device plugins are sorted before normal plugins + if ( + a.prototype instanceof FlipperDevicePlugin && + !(b.prototype instanceof FlipperDevicePlugin) + ) { + return -1; + } + if ( + b.prototype instanceof FlipperDevicePlugin && + !(a.prototype instanceof FlipperDevicePlugin) + ) { + return 1; + } + return getPluginTitle(a) > getPluginTitle(b) ? 1 : -1; +}