diff --git a/desktop/app/src/sandy-chrome/appinspect/PluginList.tsx b/desktop/app/src/sandy-chrome/appinspect/PluginList.tsx index e8b3c1f42..4cec4b76d 100644 --- a/desktop/app/src/sandy-chrome/appinspect/PluginList.tsx +++ b/desktop/app/src/sandy-chrome/appinspect/PluginList.tsx @@ -24,6 +24,7 @@ import {getFavoritePlugins} from '../../chrome/mainsidebar/sidebarUtils'; import {PluginDetails} from 'flipper-plugin-lib'; import {useMemoize} from '../../utils/useMemoize'; import MetroDevice from '../../devices/MetroDevice'; +import {DownloadablePluginDetails} from 'plugin-lib/lib'; const {SubMenu} = Menu; const {Text} = Typography; @@ -47,6 +48,7 @@ export const PluginList = memo(function PluginList({ enabledPlugins, disabledPlugins, unavailablePlugins, + uninstalledPlugins, } = useMemoize(computePluginLists, [ activeDevice, metroDevice, @@ -192,6 +194,20 @@ export const PluginList = memo(function PluginList({ ))} )} + + {uninstalledPlugins.map((plugin) => ( + + ))} + {!isArchived && ( { - if (!client.plugins.includes(plugin.id)) { - unavailablePlugins.push([ - plugin.details, - `Plugin '${getPluginTitle( - plugin.details, - )}' is not loaded by the client application`, - ]); - } else if (favoritePlugins.includes(plugin)) { - enabledPlugins.push(plugin); - } else { - disabledPlugins.push(plugin); - } - }); - } - // process problematic plugins plugins.disabledPlugins.forEach((plugin) => { unavailablePlugins.push([plugin, 'Plugin is disabled by configuration']); @@ -437,11 +424,61 @@ export function computePluginLists( ]); }); + // process all client plugins + if (device && client) { + const clientPlugins = Array.from(plugins.clientPlugins.values()).sort( + sortPluginsByName, + ); + const favoritePlugins = getFavoritePlugins( + device, + client, + clientPlugins, + client && userStarredPlugins[client.query.app], + true, + ); + clientPlugins.forEach((plugin) => { + if (!client.supportsPlugin(plugin.id)) { + unavailablePlugins.push([ + plugin.details, + `Plugin '${getPluginTitle( + plugin.details, + )}' is installed in Flipper, but not supported by the client application`, + ]); + } else if (favoritePlugins.includes(plugin)) { + enabledPlugins.push(plugin); + } else { + disabledPlugins.push(plugin); + } + }); + const installedPluginIds = new Set([ + ...clientPlugins.map((p) => p.id), + ...unavailablePlugins.map(([p]) => p.id), + ]); + const uninstalledMarketplacePlugins = plugins.marketplacePlugins.filter( + (p) => !installedPluginIds.has(p.id), + ); + uninstalledMarketplacePlugins.forEach((plugin) => { + if (client.supportsPlugin(plugin.id)) { + uninstalledPlugins.push(plugin); + } else { + unavailablePlugins.push([ + plugin, + `Plugin '${getPluginTitle( + plugin, + )}' is not installed in Flipper and not supported by the client application`, + ]); + } + }); + } + devicePlugins.sort(sortPluginsByName); metroPlugins.sort(sortPluginsByName); unavailablePlugins.sort(([a], [b]) => { return getPluginTitle(a) > getPluginTitle(b) ? 1 : -1; }); + uninstalledPlugins.sort((a, b) => { + return getPluginTitle(a) > getPluginTitle(b) ? 1 : -1; + }); return { devicePlugins, @@ -449,6 +486,7 @@ export function computePluginLists( enabledPlugins, disabledPlugins, unavailablePlugins, + uninstalledPlugins, }; } diff --git a/desktop/app/src/sandy-chrome/appinspect/__tests__/PluginList.spec.tsx b/desktop/app/src/sandy-chrome/appinspect/__tests__/PluginList.spec.tsx index bdcb8ebff..a7484d12d 100644 --- a/desktop/app/src/sandy-chrome/appinspect/__tests__/PluginList.spec.tsx +++ b/desktop/app/src/sandy-chrome/appinspect/__tests__/PluginList.spec.tsx @@ -20,10 +20,15 @@ import {_SandyPluginDefinition} from 'flipper-plugin'; import {createMockPluginDetails} from 'flipper-plugin/src/test-utils/test-utils'; import {selectPlugin, starPlugin} from '../../../reducers/connections'; import {registerMetroDevice} from '../../../dispatcher/metroDevice'; -import {addGatekeepedPlugins, registerPlugins} from '../../../reducers/plugins'; +import { + addGatekeepedPlugins, + registerMarketplacePlugins, + registerPlugins, +} from '../../../reducers/plugins'; // eslint-disable-next-line import * as LogsPluginModule from '../../../../../plugins/logs/index'; +import {createMockDownloadablePluginDetails} from '../../../utils/testUtils'; const logsPlugin = new _SandyPluginDefinition( createMockPluginDetails({id: 'DeviceLogs'}), @@ -163,6 +168,7 @@ describe('basic findBestDevice with metro present', () => { state.connections.userStarredPlugins, ), ).toEqual({ + uninstalledPlugins: [], devicePlugins: [logsPlugin], metroPlugins: [logsPlugin], enabledPlugins: [], @@ -196,7 +202,6 @@ describe('basic findBestDevice with metro present', () => { }, }, ); - const unsupportedPlugin = new _SandyPluginDefinition( createMockPluginDetails({ id: 'unsupportedPlugin', @@ -232,6 +237,16 @@ describe('basic findBestDevice with metro present', () => { noopPlugin, ); + const supportedUninstalledPlugin = createMockDownloadablePluginDetails({ + id: 'supportedUninstalledPlugin', + title: 'Supported Uninstalled Plugin', + }); + + const unsupportedUninstalledPlugin = createMockDownloadablePluginDetails({ + id: 'unsupportedUninstalledPlugin', + title: 'Unsupported Uninstalled Plugin', + }); + flipper.store.dispatch( registerPlugins([ unsupportedDevicePlugin, @@ -241,20 +256,29 @@ describe('basic findBestDevice with metro present', () => { ]), ); flipper.store.dispatch(addGatekeepedPlugins([gateKeepedPlugin])); + flipper.store.dispatch( + registerMarketplacePlugins([ + supportedUninstalledPlugin, + unsupportedUninstalledPlugin, + ]), + ); // ok, this is a little hackish - flipper.client.plugins = ['plugin1', 'plugin2']; + flipper.client.plugins = [ + 'plugin1', + 'plugin2', + 'supportedUninstalledPlugin', + ]; let state = flipper.store.getState(); - expect( - computePluginLists( - testDevice, - metro, - flipper.client, - state.plugins, - state.connections.userStarredPlugins, - ), - ).toEqual({ + const pluginLists = computePluginLists( + testDevice, + metro, + flipper.client, + state.plugins, + state.connections.userStarredPlugins, + ); + expect(pluginLists).toEqual({ devicePlugins: [logsPlugin], metroPlugins: [logsPlugin], enabledPlugins: [], @@ -270,9 +294,14 @@ describe('basic findBestDevice with metro present', () => { ], [ unsupportedPlugin.details, - "Plugin 'Unsupported Plugin' is not loaded by the client application", + "Plugin 'Unsupported Plugin' is installed in Flipper, but not supported by the client application", + ], + [ + unsupportedUninstalledPlugin, + "Plugin 'Unsupported Uninstalled Plugin' is not installed in Flipper and not supported by the client application", ], ], + uninstalledPlugins: [supportedUninstalledPlugin], }); flipper.store.dispatch(