Add update indicators to PluginInstaller

Summary:
Display is functional, the update itself isn't just yet.
Want to keep this easier to review. Instead of GK, I just
have a top-level toggle for now, because that will go
away with one of the next diffs anyway.

Reviewed By: jknoxville

Differential Revision: D18479290

fbshipit-source-id: b49394d4ab681c9d1dc5db0e4bee54f9255494b9
This commit is contained in:
Pascal Hartig
2019-11-14 08:51:33 -08:00
committed by Facebook Github Bot
parent 7a148ef7a6
commit 144338e74a
2 changed files with 135 additions and 12 deletions

View File

@@ -42,12 +42,15 @@ import {
readInstalledPlugins, readInstalledPlugins,
providePluginManager, providePluginManager,
provideSearchIndex, provideSearchIndex,
findPluginUpdates,
UpdateResult,
} from '../utils/pluginManager'; } from '../utils/pluginManager';
import {State as AppState} from '../reducers'; import {State as AppState} from '../reducers';
import {connect} from 'react-redux'; import {connect} from 'react-redux';
import {Dispatch, Action} from 'redux'; import {Dispatch, Action} from 'redux';
const TAG = 'PluginInstaller'; const TAG = 'PluginInstaller';
const ENABLE_PLUGIN_UPDATES = false;
const EllipsisText = styled(Text)({ const EllipsisText = styled(Text)({
overflow: 'hidden', overflow: 'hidden',
@@ -114,6 +117,26 @@ const defaultProps: OwnProps = {
autoHeight: false, autoHeight: false,
}; };
type UpdatablePlugin = {
updateStatus: UpdateResult;
};
type UpdatablePluginDefinition = PluginDefinition & UpdatablePlugin;
// exported for testing
export function annotatePluginsWithUpdates(
installedPlugins: Map<string, PluginDefinition>,
updates: Map<string, UpdateResult>,
): Map<string, UpdatablePluginDefinition> {
const annotated: Array<[string, UpdatablePluginDefinition]> = Array.from(
installedPlugins.entries(),
).map(([key, value]) => {
const updateStatus = updates.get(key) || {kind: 'up-to-date'};
return [key, {...value, updateStatus: updateStatus}];
});
return new Map(annotated);
}
const PluginInstaller = function props(props: Props) { const PluginInstaller = function props(props: Props) {
const [restartRequired, setRestartRequired] = useState(false); const [restartRequired, setRestartRequired] = useState(false);
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
@@ -174,16 +197,25 @@ const AlignedGlyph = styled(Glyph)({
marginTop: 6, marginTop: 6,
}); });
function liftUpdatable(val: PluginDefinition): UpdatablePluginDefinition {
return {
...val,
updateStatus: {kind: 'up-to-date'},
};
}
function InstallButton(props: { function InstallButton(props: {
name: string; name: string;
version: string; version: string;
onInstall: () => void; onInstall: () => void;
installed: boolean; installed: boolean;
updateStatus: UpdateResult;
}) { }) {
type InstallAction = type InstallAction =
| {kind: 'Install'; error?: string} | {kind: 'Install'; error?: string}
| {kind: 'Waiting'} | {kind: 'Waiting'}
| {kind: 'Remove'; error?: string}; | {kind: 'Remove'; error?: string}
| {kind: 'Update'; error?: string};
const catchError = ( const catchError = (
actionKind: 'Install' | 'Remove', actionKind: 'Install' | 'Remove',
@@ -245,7 +277,11 @@ function InstallButton(props: {
); );
const [action, setAction] = useState<InstallAction>( const [action, setAction] = useState<InstallAction>(
props.installed ? {kind: 'Remove'} : {kind: 'Install'}, props.updateStatus.kind === 'update-available'
? {kind: 'Update'}
: props.installed
? {kind: 'Remove'}
: {kind: 'Install'},
); );
if (action.kind === 'Waiting') { if (action.kind === 'Waiting') {
@@ -256,12 +292,20 @@ function InstallButton(props: {
const button = ( const button = (
<TableButton <TableButton
compact compact
type={action.kind === 'Install' ? 'primary' : undefined} type={action.kind !== 'Remove' ? 'primary' : undefined}
onClick={ onClick={() => {
action.kind === 'Install' switch (action.kind) {
? () => reportPlatformFailures(performInstall(), `${TAG}:install`) case 'Install':
: () => reportPlatformFailures(performRemove(), `${TAG}:remove`) reportPlatformFailures(performInstall(), `${TAG}:install`);
}> break;
case 'Remove':
reportPlatformFailures(performRemove(), `${TAG}:remove`);
break;
case 'Update':
alert('Not implemented yet.');
break;
}
}}>
{action.kind} {action.kind}
</TableButton> </TableButton>
); );
@@ -305,7 +349,7 @@ function useNPMSearch(
}, []); }, []);
const createRow = useCallback( const createRow = useCallback(
(h: PluginDefinition) => ({ (h: UpdatablePluginDefinition) => ({
key: h.name, key: h.name,
columns: { columns: {
name: {value: <EllipsisText>{h.name}</EllipsisText>}, name: {value: <EllipsisText>{h.name}</EllipsisText>},
@@ -331,6 +375,7 @@ function useNPMSearch(
version={h.version} version={h.version}
onInstall={onInstall} onInstall={onInstall}
installed={installedPlugins.has(h.name)} installed={installedPlugins.has(h.name)}
updateStatus={h.updateStatus}
/> />
), ),
align: 'center' as 'center', align: 'center' as 'center',
@@ -340,7 +385,13 @@ function useNPMSearch(
[installedPlugins], [installedPlugins],
); );
const [searchResults, setSearchResults] = useState<PluginDefinition[]>([]); const [searchResults, setSearchResults] = useState<
UpdatablePluginDefinition[]
>([]);
const [
updateAnnotatedInstalledPlugins,
setUpdateAnnotatedInstalledPlugins,
] = useState<Map<string, UpdatablePluginDefinition>>(new Map());
useEffect(() => { useEffect(() => {
(async () => { (async () => {
@@ -353,12 +404,27 @@ function useNPMSearch(
`${TAG}:queryIndex`, `${TAG}:queryIndex`,
); );
setSearchResults(hits.filter(hit => !installedPlugins.has(hit.name))); setSearchResults(
hits.filter(hit => !installedPlugins.has(hit.name)).map(liftUpdatable),
);
setQuery(query); setQuery(query);
})(); })();
}, [query, installedPlugins]); }, [query, installedPlugins]);
const results = Array.from(installedPlugins.values()).concat(searchResults); useEffect(() => {
(async () => {
const updates = ENABLE_PLUGIN_UPDATES
? new Map(await findPluginUpdates(installedPlugins))
: new Map();
setUpdateAnnotatedInstalledPlugins(
annotatePluginsWithUpdates(installedPlugins, updates),
);
})();
}, [installedPlugins]);
const results = Array.from(updateAnnotatedInstalledPlugins.values()).concat(
searchResults,
);
return List(results.map(createRow)); return List(results.map(createRow));
} }

View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {annotatePluginsWithUpdates} from '../PluginInstaller';
import {UpdateResult} from '../../utils/pluginManager';
test('annotatePluginsWithUpdates', async () => {
const installedPlugins = new Map([
[
'example',
{
name: 'example',
version: '0.1.0',
description: 'Gaze into the death crystal',
},
],
[
'ricksybusiness',
{
name: 'ricksybusiness',
version: '1.0.0',
description: 'Rick Die Rickpeat',
},
],
]);
const updates = new Map<string, UpdateResult>([
['example', {kind: 'update-available', version: '1.1.0'}],
]);
const res = annotatePluginsWithUpdates(installedPlugins, updates);
expect(res).toMatchInlineSnapshot(`
Map {
"example" => Object {
"description": "Gaze into the death crystal",
"name": "example",
"updateStatus": Object {
"kind": "update-available",
"version": "1.1.0",
},
"version": "0.1.0",
},
"ricksybusiness" => Object {
"description": "Rick Die Rickpeat",
"name": "ricksybusiness",
"updateStatus": Object {
"kind": "up-to-date",
},
"version": "1.0.0",
},
}
`);
});