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(