Basic error handling
Summary: Adds some very basic error handling. It's not great because once you hit an error, you're basically stuck and there's no retry yet, but it's still strictly an improvement over being stuck with the looping spinner, so I think it's worth shipping it in this state. Reviewed By: jknoxville Differential Revision: D17763467 fbshipit-source-id: b5b7996554dd1dce682fba87f96e3806432a475a
This commit is contained in:
committed by
Facebook Github Bot
parent
d310027d88
commit
e074898d46
@@ -21,6 +21,7 @@ import {
|
|||||||
Link,
|
Link,
|
||||||
Text,
|
Text,
|
||||||
LoadingIndicator,
|
LoadingIndicator,
|
||||||
|
Tooltip,
|
||||||
} from 'flipper';
|
} from 'flipper';
|
||||||
import React, {useCallback, useState, useMemo, useEffect} from 'react';
|
import React, {useCallback, useState, useMemo, useEffect} from 'react';
|
||||||
import {remote} from 'electron';
|
import {remote} from 'electron';
|
||||||
@@ -139,72 +140,106 @@ const Spinner = styled(LoadingIndicator)({
|
|||||||
marginTop: 6,
|
marginTop: 6,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const AlignedGlyph = styled(Glyph)({
|
||||||
|
marginTop: 6,
|
||||||
|
});
|
||||||
|
|
||||||
function InstallButton(props: {
|
function InstallButton(props: {
|
||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
onInstall: () => void;
|
onInstall: () => void;
|
||||||
installed: boolean;
|
installed: boolean;
|
||||||
}) {
|
}) {
|
||||||
type InstallAction = 'Install' | 'Waiting' | 'Remove';
|
type InstallAction =
|
||||||
|
| {kind: 'Install'}
|
||||||
|
| {kind: 'Waiting'}
|
||||||
|
| {kind: 'Remove'}
|
||||||
|
| {kind: 'Error'; error: string};
|
||||||
|
|
||||||
const onInstall = useCallback(async () => {
|
const catchError = (fn: () => Promise<void>) => async () => {
|
||||||
reportUsage(`${TAG}:install`, undefined, props.name);
|
try {
|
||||||
setAction('Waiting');
|
await fn();
|
||||||
await fs.ensureDir(PLUGIN_DIR);
|
} catch (err) {
|
||||||
// create empty watchman config (required by metro's file watcher)
|
setAction({kind: 'Error', error: err.toString()});
|
||||||
await fs.writeFile(path.join(PLUGIN_DIR, '.watchmanconfig'), '{}');
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// install the plugin and all it's dependencies into node_modules
|
const onInstall = useCallback(
|
||||||
PluginManager.options.pluginsPath = path.join(
|
catchError(async () => {
|
||||||
PLUGIN_DIR,
|
reportUsage(`${TAG}:install`, undefined, props.name);
|
||||||
props.name,
|
setAction({kind: 'Waiting'});
|
||||||
'node_modules',
|
await fs.ensureDir(PLUGIN_DIR);
|
||||||
);
|
// create empty watchman config (required by metro's file watcher)
|
||||||
await PluginManager.install(props.name);
|
await fs.writeFile(path.join(PLUGIN_DIR, '.watchmanconfig'), '{}');
|
||||||
|
|
||||||
// move the plugin itself out of the node_modules folder
|
// install the plugin and all it's dependencies into node_modules
|
||||||
const pluginDir = path.join(
|
PluginManager.options.pluginsPath = path.join(
|
||||||
PLUGIN_DIR,
|
PLUGIN_DIR,
|
||||||
props.name,
|
props.name,
|
||||||
'node_modules',
|
'node_modules',
|
||||||
props.name,
|
);
|
||||||
);
|
await PluginManager.install(props.name);
|
||||||
const pluginFiles = await fs.readdir(pluginDir);
|
|
||||||
await Promise.all(
|
|
||||||
pluginFiles.map(f =>
|
|
||||||
fs.move(path.join(pluginDir, f), path.join(pluginDir, '..', '..', f)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
props.onInstall();
|
// move the plugin itself out of the node_modules folder
|
||||||
setAction('Remove');
|
const pluginDir = path.join(
|
||||||
}, [props.name, props.version]);
|
PLUGIN_DIR,
|
||||||
|
props.name,
|
||||||
|
'node_modules',
|
||||||
|
props.name,
|
||||||
|
);
|
||||||
|
const pluginFiles = await fs.readdir(pluginDir);
|
||||||
|
await Promise.all(
|
||||||
|
pluginFiles.map(f =>
|
||||||
|
fs.move(path.join(pluginDir, f), path.join(pluginDir, '..', '..', f)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
const onRemove = useCallback(async () => {
|
props.onInstall();
|
||||||
reportUsage(`${TAG}:remove`, undefined, props.name);
|
setAction({kind: 'Remove'});
|
||||||
setAction('Waiting');
|
}),
|
||||||
await fs.remove(path.join(PLUGIN_DIR, props.name));
|
[props.name, props.version],
|
||||||
props.onInstall();
|
|
||||||
setAction('Install');
|
|
||||||
}, [props.name]);
|
|
||||||
|
|
||||||
const [action, setAction] = useState<InstallAction>(
|
|
||||||
props.installed ? 'Remove' : 'Install',
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (action === 'Waiting') {
|
const onRemove = useCallback(
|
||||||
|
catchError(async () => {
|
||||||
|
reportUsage(`${TAG}:remove`, undefined, props.name);
|
||||||
|
setAction({kind: 'Waiting'});
|
||||||
|
await fs.remove(path.join(PLUGIN_DIR, props.name));
|
||||||
|
props.onInstall();
|
||||||
|
setAction({kind: 'Install'});
|
||||||
|
}),
|
||||||
|
[props.name],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [action, setAction] = useState<InstallAction>(
|
||||||
|
props.installed ? {kind: 'Remove'} : {kind: 'Install'},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (action.kind === 'Waiting') {
|
||||||
return <Spinner size={16} />;
|
return <Spinner size={16} />;
|
||||||
}
|
}
|
||||||
|
if (action.kind === 'Error') {
|
||||||
|
const glyph = (
|
||||||
|
<AlignedGlyph color={colors.orange} size={16} name="caution-triangle" />
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
options={{position: 'toRight'}}
|
||||||
|
title={`Something went wrong: ${action.error}`}
|
||||||
|
children={glyph}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<TableButton
|
<TableButton
|
||||||
compact
|
compact
|
||||||
type={action === 'Install' ? 'primary' : undefined}
|
type={action.kind === 'Install' ? 'primary' : undefined}
|
||||||
onClick={
|
onClick={
|
||||||
action === 'Install'
|
action.kind === 'Install'
|
||||||
? () => reportPlatformFailures(onInstall(), `${TAG}:install`)
|
? () => reportPlatformFailures(onInstall(), `${TAG}:install`)
|
||||||
: () => reportPlatformFailures(onRemove(), `${TAG}:remove`)
|
: () => reportPlatformFailures(onRemove(), `${TAG}:remove`)
|
||||||
}>
|
}>
|
||||||
{action}
|
{action.kind}
|
||||||
</TableButton>
|
</TableButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -251,7 +286,11 @@ function useNPMSearch(
|
|||||||
<EllipsisText>{h.description}</EllipsisText>
|
<EllipsisText>{h.description}</EllipsisText>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<Link href={`https://yarnpkg.com/en/package/${h.name}`}>
|
<Link href={`https://yarnpkg.com/en/package/${h.name}`}>
|
||||||
<Glyph color={colors.light20} name="info-circle" size={16} />
|
<AlignedGlyph
|
||||||
|
color={colors.light20}
|
||||||
|
name="info-circle"
|
||||||
|
size={16}
|
||||||
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</FlexRow>
|
</FlexRow>
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user