Fix bug with plugin re-installation after uninstallation
Summary: Fixed a bug when plugin installation status wouldn't be saved between sessions when plugin uninstalled and then re-installed again. Before the fix, after Flipper restart, such plugin was uninstalled again because its package name was not removed from "uninstalledPlugins" state. This was because plugin id was used by mistake instead of name in few places. To try avoiding this issue in future I've also renamed "uninstalledPlugins" to "uninstalledPluginNames" to make it more clear than package name should be used there rather than ID. As this field is persisted, I also added migration which moves data to the renamed field. Reviewed By: passy Differential Revision: D28314447 fbshipit-source-id: fbe3edc258b78fe7fbb0d966f93aabcdf3b66d4b
This commit is contained in:
committed by
Facebook GitHub Bot
parent
22974130c8
commit
00851c6b5d
@@ -67,6 +67,6 @@ Object {
|
|||||||
"loadedPlugins": Map {},
|
"loadedPlugins": Map {},
|
||||||
"marketplacePlugins": Array [],
|
"marketplacePlugins": Array [],
|
||||||
"selectedPlugins": Array [],
|
"selectedPlugins": Array [],
|
||||||
"uninstalledPlugins": Set {},
|
"uninstalledPluginNames": Set {},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -153,7 +153,9 @@ test('uninstall plugin', async () => {
|
|||||||
mockFlipper.getState().plugins.loadedPlugins.has('plugin1'),
|
mockFlipper.getState().plugins.loadedPlugins.has('plugin1'),
|
||||||
).toBeFalsy();
|
).toBeFalsy();
|
||||||
expect(
|
expect(
|
||||||
mockFlipper.getState().plugins.uninstalledPlugins.has('flipper-plugin1'),
|
mockFlipper
|
||||||
|
.getState()
|
||||||
|
.plugins.uninstalledPluginNames.has('flipper-plugin1'),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
expect(mockClient.sandyPluginStates.has('plugin1')).toBeFalsy();
|
expect(mockClient.sandyPluginStates.has('plugin1')).toBeFalsy();
|
||||||
});
|
});
|
||||||
@@ -179,7 +181,7 @@ test('uninstall bundled plugin', async () => {
|
|||||||
expect(
|
expect(
|
||||||
mockFlipper
|
mockFlipper
|
||||||
.getState()
|
.getState()
|
||||||
.plugins.uninstalledPlugins.has('flipper-bundled-plugin'),
|
.plugins.uninstalledPluginNames.has('flipper-bundled-plugin'),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
expect(mockClient.sandyPluginStates.has('bundled-plugin')).toBeFalsy();
|
expect(mockClient.sandyPluginStates.has('bundled-plugin')).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ import {
|
|||||||
const maxInstalledPluginVersionsToKeep = 2;
|
const maxInstalledPluginVersionsToKeep = 2;
|
||||||
|
|
||||||
function refreshInstalledPlugins(store: Store) {
|
function refreshInstalledPlugins(store: Store) {
|
||||||
removePlugins(store.getState().plugins.uninstalledPlugins.values())
|
removePlugins(store.getState().plugins.uninstalledPluginNames.values())
|
||||||
.then(() =>
|
.then(() =>
|
||||||
cleanupOldInstalledPluginVersions(maxInstalledPluginVersionsToKeep),
|
cleanupOldInstalledPluginVersions(maxInstalledPluginVersionsToKeep),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -85,14 +85,15 @@ export default async (store: Store, logger: Logger) => {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const uninstalledPlugins = store.getState().plugins.uninstalledPlugins;
|
const uninstalledPluginNames =
|
||||||
|
store.getState().plugins.uninstalledPluginNames;
|
||||||
|
|
||||||
const bundledPlugins = getBundledPlugins();
|
const bundledPlugins = getBundledPlugins();
|
||||||
|
|
||||||
const loadedPlugins = filterNewestVersionOfEachPlugin(
|
const loadedPlugins = filterNewestVersionOfEachPlugin(
|
||||||
bundledPlugins,
|
bundledPlugins,
|
||||||
await getDynamicPlugins(),
|
await getDynamicPlugins(),
|
||||||
).filter((p) => !uninstalledPlugins.has(p.name));
|
).filter((p) => !uninstalledPluginNames.has(p.name));
|
||||||
|
|
||||||
const initialPlugins: PluginDefinition[] = loadedPlugins
|
const initialPlugins: PluginDefinition[] = loadedPlugins
|
||||||
.map(reportVersion)
|
.map(reportVersion)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ test('add clientPlugin', () => {
|
|||||||
disabledPlugins: [],
|
disabledPlugins: [],
|
||||||
selectedPlugins: [],
|
selectedPlugins: [],
|
||||||
marketplacePlugins: [],
|
marketplacePlugins: [],
|
||||||
uninstalledPlugins: new Set(),
|
uninstalledPluginNames: new Set(),
|
||||||
installedPlugins: new Map(),
|
installedPlugins: new Map(),
|
||||||
initialised: false,
|
initialised: false,
|
||||||
},
|
},
|
||||||
@@ -61,7 +61,7 @@ test('add devicePlugin', () => {
|
|||||||
disabledPlugins: [],
|
disabledPlugins: [],
|
||||||
selectedPlugins: [],
|
selectedPlugins: [],
|
||||||
marketplacePlugins: [],
|
marketplacePlugins: [],
|
||||||
uninstalledPlugins: new Set(),
|
uninstalledPluginNames: new Set(),
|
||||||
installedPlugins: new Map(),
|
installedPlugins: new Map(),
|
||||||
initialised: false,
|
initialised: false,
|
||||||
},
|
},
|
||||||
@@ -82,7 +82,7 @@ test('do not add plugin twice', () => {
|
|||||||
disabledPlugins: [],
|
disabledPlugins: [],
|
||||||
selectedPlugins: [],
|
selectedPlugins: [],
|
||||||
marketplacePlugins: [],
|
marketplacePlugins: [],
|
||||||
uninstalledPlugins: new Set(),
|
uninstalledPluginNames: new Set(),
|
||||||
installedPlugins: new Map(),
|
installedPlugins: new Map(),
|
||||||
initialised: false,
|
initialised: false,
|
||||||
},
|
},
|
||||||
@@ -120,7 +120,7 @@ test('add gatekeeped plugin', () => {
|
|||||||
selectedPlugins: [],
|
selectedPlugins: [],
|
||||||
marketplacePlugins: [],
|
marketplacePlugins: [],
|
||||||
installedPlugins: new Map(),
|
installedPlugins: new Map(),
|
||||||
uninstalledPlugins: new Set(),
|
uninstalledPluginNames: new Set(),
|
||||||
initialised: false,
|
initialised: false,
|
||||||
},
|
},
|
||||||
addGatekeepedPlugins(gatekeepedPlugins),
|
addGatekeepedPlugins(gatekeepedPlugins),
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ import notifications, {
|
|||||||
import plugins, {
|
import plugins, {
|
||||||
State as PluginsState,
|
State as PluginsState,
|
||||||
Action as PluginsAction,
|
Action as PluginsAction,
|
||||||
|
persistMigrations as pluginsPersistMigrations,
|
||||||
|
persistVersion as pluginsPersistVersion,
|
||||||
} from './plugins';
|
} from './plugins';
|
||||||
import supportForm, {
|
import supportForm, {
|
||||||
State as SupportFormState,
|
State as SupportFormState,
|
||||||
@@ -169,8 +171,10 @@ export default combineReducers<State, Actions>({
|
|||||||
{
|
{
|
||||||
key: 'plugins',
|
key: 'plugins',
|
||||||
storage,
|
storage,
|
||||||
whitelist: ['marketplacePlugins', 'uninstalledPlugins'],
|
whitelist: ['marketplacePlugins', 'uninstalledPluginNames'],
|
||||||
transforms: [setTransformer({whitelist: ['uninstalledPlugins']})],
|
transforms: [setTransformer({whitelist: ['uninstalledPluginNames']})],
|
||||||
|
version: pluginsPersistVersion,
|
||||||
|
migrate: createMigrate(pluginsPersistMigrations),
|
||||||
},
|
},
|
||||||
plugins,
|
plugins,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ export interface MarketplacePluginDetails extends DownloadablePluginDetails {
|
|||||||
availableVersions?: DownloadablePluginDetails[];
|
availableVersions?: DownloadablePluginDetails[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State = {
|
export type State = StateV1;
|
||||||
|
|
||||||
|
type StateV1 = {
|
||||||
devicePlugins: DevicePluginMap;
|
devicePlugins: DevicePluginMap;
|
||||||
clientPlugins: ClientPluginMap;
|
clientPlugins: ClientPluginMap;
|
||||||
loadedPlugins: Map<string, ActivatablePluginDetails>;
|
loadedPlugins: Map<string, ActivatablePluginDetails>;
|
||||||
@@ -37,11 +39,27 @@ export type State = {
|
|||||||
failedPlugins: Array<[ActivatablePluginDetails, string]>;
|
failedPlugins: Array<[ActivatablePluginDetails, string]>;
|
||||||
selectedPlugins: Array<string>;
|
selectedPlugins: Array<string>;
|
||||||
marketplacePlugins: Array<MarketplacePluginDetails>;
|
marketplacePlugins: Array<MarketplacePluginDetails>;
|
||||||
uninstalledPlugins: Set<string>;
|
uninstalledPluginNames: Set<string>;
|
||||||
installedPlugins: Map<string, InstalledPluginDetails>;
|
installedPlugins: Map<string, InstalledPluginDetails>;
|
||||||
initialised: boolean;
|
initialised: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type StateV0 = Omit<StateV1, 'uninstalledPluginNames'> & {
|
||||||
|
uninstalledPlugins: Set<string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const persistVersion = 1;
|
||||||
|
export const persistMigrations = {
|
||||||
|
1: (state: any) => {
|
||||||
|
const stateV0 = state as StateV0;
|
||||||
|
const stateV1: StateV1 = {
|
||||||
|
...stateV0,
|
||||||
|
uninstalledPluginNames: new Set(stateV0.uninstalledPlugins),
|
||||||
|
};
|
||||||
|
return stateV1 as any;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export type RegisterPluginAction = {
|
export type RegisterPluginAction = {
|
||||||
type: 'REGISTER_PLUGINS';
|
type: 'REGISTER_PLUGINS';
|
||||||
payload: PluginDefinition[];
|
payload: PluginDefinition[];
|
||||||
@@ -107,7 +125,7 @@ const INITIAL_STATE: State = {
|
|||||||
failedPlugins: [],
|
failedPlugins: [],
|
||||||
selectedPlugins: [],
|
selectedPlugins: [],
|
||||||
marketplacePlugins: [],
|
marketplacePlugins: [],
|
||||||
uninstalledPlugins: new Set(),
|
uninstalledPluginNames: new Set(),
|
||||||
installedPlugins: new Map(),
|
installedPlugins: new Map(),
|
||||||
initialised: false,
|
initialised: false,
|
||||||
};
|
};
|
||||||
@@ -169,7 +187,7 @@ export default function reducer(
|
|||||||
return produce(state, (draft) => {
|
return produce(state, (draft) => {
|
||||||
draft.installedPlugins.clear();
|
draft.installedPlugins.clear();
|
||||||
action.payload.forEach((p) => {
|
action.payload.forEach((p) => {
|
||||||
if (!draft.uninstalledPlugins.has(p.id)) {
|
if (!draft.uninstalledPluginNames.has(p.name)) {
|
||||||
draft.installedPlugins.set(p.id, p);
|
draft.installedPlugins.set(p.id, p);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -188,7 +206,7 @@ export default function reducer(
|
|||||||
draft.clientPlugins.delete(plugin.id);
|
draft.clientPlugins.delete(plugin.id);
|
||||||
draft.devicePlugins.delete(plugin.id);
|
draft.devicePlugins.delete(plugin.id);
|
||||||
draft.loadedPlugins.delete(plugin.id);
|
draft.loadedPlugins.delete(plugin.id);
|
||||||
draft.uninstalledPlugins.add(plugin.name);
|
draft.uninstalledPluginNames.add(plugin.name);
|
||||||
});
|
});
|
||||||
} else if (action.type === 'PLUGIN_LOADED') {
|
} else if (action.type === 'PLUGIN_LOADED') {
|
||||||
const plugin = action.payload;
|
const plugin = action.payload;
|
||||||
@@ -198,7 +216,7 @@ export default function reducer(
|
|||||||
} else {
|
} else {
|
||||||
draft.clientPlugins.set(plugin.id, plugin);
|
draft.clientPlugins.set(plugin.id, plugin);
|
||||||
}
|
}
|
||||||
draft.uninstalledPlugins.delete(plugin.id);
|
draft.uninstalledPluginNames.delete(plugin.details.name);
|
||||||
draft.loadedPlugins.set(plugin.id, plugin.details);
|
draft.loadedPlugins.set(plugin.id, plugin.details);
|
||||||
});
|
});
|
||||||
} else if (action.type === 'PLUGINS_INITIALISED') {
|
} else if (action.type === 'PLUGINS_INITIALISED') {
|
||||||
|
|||||||
@@ -767,7 +767,7 @@ test('test determinePluginsToProcess for mutilple clients having plugins present
|
|||||||
selectedPlugins: ['TestPlugin'],
|
selectedPlugins: ['TestPlugin'],
|
||||||
marketplacePlugins: [],
|
marketplacePlugins: [],
|
||||||
installedPlugins: new Map(),
|
installedPlugins: new Map(),
|
||||||
uninstalledPlugins: new Set(),
|
uninstalledPluginNames: new Set(),
|
||||||
initialised: true,
|
initialised: true,
|
||||||
};
|
};
|
||||||
const op = determinePluginsToProcess(
|
const op = determinePluginsToProcess(
|
||||||
@@ -842,7 +842,7 @@ test('test determinePluginsToProcess for no selected plugin present in any clien
|
|||||||
selectedPlugins: ['RandomPlugin'],
|
selectedPlugins: ['RandomPlugin'],
|
||||||
marketplacePlugins: [],
|
marketplacePlugins: [],
|
||||||
installedPlugins: new Map(),
|
installedPlugins: new Map(),
|
||||||
uninstalledPlugins: new Set(),
|
uninstalledPluginNames: new Set(),
|
||||||
initialised: true,
|
initialised: true,
|
||||||
};
|
};
|
||||||
const op = determinePluginsToProcess([client1, client2], device1, plugins);
|
const op = determinePluginsToProcess([client1, client2], device1, plugins);
|
||||||
@@ -894,7 +894,7 @@ test('test determinePluginsToProcess for multiple clients on same device', async
|
|||||||
selectedPlugins: ['TestPlugin'],
|
selectedPlugins: ['TestPlugin'],
|
||||||
marketplacePlugins: [],
|
marketplacePlugins: [],
|
||||||
installedPlugins: new Map(),
|
installedPlugins: new Map(),
|
||||||
uninstalledPlugins: new Set(),
|
uninstalledPluginNames: new Set(),
|
||||||
initialised: true,
|
initialised: true,
|
||||||
};
|
};
|
||||||
const op = determinePluginsToProcess([client1, client2], device1, plugins);
|
const op = determinePluginsToProcess([client1, client2], device1, plugins);
|
||||||
@@ -984,7 +984,7 @@ test('test determinePluginsToProcess for multiple clients on different device',
|
|||||||
selectedPlugins: ['TestPlugin'],
|
selectedPlugins: ['TestPlugin'],
|
||||||
marketplacePlugins: [],
|
marketplacePlugins: [],
|
||||||
installedPlugins: new Map(),
|
installedPlugins: new Map(),
|
||||||
uninstalledPlugins: new Set(),
|
uninstalledPluginNames: new Set(),
|
||||||
initialised: true,
|
initialised: true,
|
||||||
};
|
};
|
||||||
const op = determinePluginsToProcess(
|
const op = determinePluginsToProcess(
|
||||||
@@ -1070,7 +1070,7 @@ test('test determinePluginsToProcess to ignore archived clients', async () => {
|
|||||||
selectedPlugins: ['TestPlugin'],
|
selectedPlugins: ['TestPlugin'],
|
||||||
marketplacePlugins: [],
|
marketplacePlugins: [],
|
||||||
installedPlugins: new Map(),
|
installedPlugins: new Map(),
|
||||||
uninstalledPlugins: new Set(),
|
uninstalledPluginNames: new Set(),
|
||||||
initialised: true,
|
initialised: true,
|
||||||
};
|
};
|
||||||
const op = determinePluginsToProcess(
|
const op = determinePluginsToProcess(
|
||||||
|
|||||||
Reference in New Issue
Block a user