Basic Doctor UI
Summary: - Basic Doctor UI showing issues with installation - Run healthchecks in background on startup and show warning message if something is wrong Reviewed By: jknoxville Differential Revision: D18502599 fbshipit-source-id: 194939a080ba7412ed3293d95c533bfad7031d3b
This commit is contained in:
committed by
Facebook Github Bot
parent
c1de6f4276
commit
ddb135ac39
@@ -7,10 +7,29 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {runHealthchecks} from './index';
|
import {getHealthchecks} from './index';
|
||||||
|
import {getEnvInfo} from './environmentInfo';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const results = await runHealthchecks();
|
const environmentInfo = await getEnvInfo();
|
||||||
|
console.log(JSON.stringify(environmentInfo));
|
||||||
|
const healthchecks = getHealthchecks();
|
||||||
|
const results = await Promise.all(
|
||||||
|
Object.entries(healthchecks).map(async ([key, category]) => [
|
||||||
|
key,
|
||||||
|
category
|
||||||
|
? {
|
||||||
|
label: category.label,
|
||||||
|
results: await Promise.all(
|
||||||
|
category.healthchecks.map(async ({label, run}) => ({
|
||||||
|
label,
|
||||||
|
result: await run(environmentInfo),
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: {},
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
console.log(JSON.stringify(results, null, 2));
|
console.log(JSON.stringify(results, null, 2));
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -7,10 +7,10 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {getEnvInfo, EnvironmentInfo} from './environmentInfo';
|
|
||||||
export {getEnvInfo} from './environmentInfo';
|
|
||||||
import {exec} from 'child_process';
|
import {exec} from 'child_process';
|
||||||
import {promisify} from 'util';
|
import {promisify} from 'util';
|
||||||
|
import {EnvironmentInfo, getEnvInfo} from './environmentInfo';
|
||||||
|
export {getEnvInfo} from './environmentInfo';
|
||||||
|
|
||||||
type HealthcheckCategory = {
|
type HealthcheckCategory = {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -31,6 +31,7 @@ type Healthcheck = {
|
|||||||
env: EnvironmentInfo,
|
env: EnvironmentInfo,
|
||||||
) => Promise<{
|
) => Promise<{
|
||||||
hasProblem: boolean;
|
hasProblem: boolean;
|
||||||
|
helpUrl?: string;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -54,7 +55,6 @@ export function getHealthchecks(): Healthchecks {
|
|||||||
healthchecks: [
|
healthchecks: [
|
||||||
{
|
{
|
||||||
label: 'OpenSSL Installed',
|
label: 'OpenSSL Installed',
|
||||||
isRequired: true,
|
|
||||||
run: async (_: EnvironmentInfo) => {
|
run: async (_: EnvironmentInfo) => {
|
||||||
const isAvailable = await commandSucceeds('openssl version');
|
const isAvailable = await commandSucceeds('openssl version');
|
||||||
return {
|
return {
|
||||||
@@ -75,6 +75,12 @@ export function getHealthchecks(): Healthchecks {
|
|||||||
hasProblem: e.SDKs['Android SDK'] === 'Not Found',
|
hasProblem: e.SDKs['Android SDK'] === 'Not Found',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'ANDROID_HOME set',
|
||||||
|
run: async (e: EnvironmentInfo) => ({
|
||||||
|
hasProblem: !!process.env.ANDROID_HOME,
|
||||||
|
}),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
...(process.platform === 'darwin'
|
...(process.platform === 'darwin'
|
||||||
@@ -127,33 +133,35 @@ export function getHealthchecks(): Healthchecks {
|
|||||||
export async function runHealthchecks(): Promise<Array<CategoryResult>> {
|
export async function runHealthchecks(): Promise<Array<CategoryResult>> {
|
||||||
const environmentInfo = await getEnvInfo();
|
const environmentInfo = await getEnvInfo();
|
||||||
const healthchecks: Healthchecks = getHealthchecks();
|
const healthchecks: Healthchecks = getHealthchecks();
|
||||||
const results: Array<CategoryResult> = (await Promise.all(
|
const results: Array<CategoryResult> = (
|
||||||
Object.entries(healthchecks).map(async ([key, category]) => {
|
await Promise.all(
|
||||||
if (!category) {
|
Object.entries(healthchecks).map(async ([key, category]) => {
|
||||||
return null;
|
if (!category) {
|
||||||
}
|
return null;
|
||||||
const categoryResult: CategoryResult = [
|
}
|
||||||
key,
|
const categoryResult: CategoryResult = [
|
||||||
{
|
key,
|
||||||
label: category.label,
|
{
|
||||||
results: await Promise.all(
|
label: category.label,
|
||||||
category.healthchecks.map(async ({label, run, isRequired}) => ({
|
results: await Promise.all(
|
||||||
label,
|
category.healthchecks.map(async ({label, run, isRequired}) => ({
|
||||||
isRequired: isRequired ?? true,
|
label,
|
||||||
result: await run(environmentInfo).catch(e => {
|
isRequired: isRequired ?? true,
|
||||||
console.error(e);
|
result: await run(environmentInfo).catch(e => {
|
||||||
// TODO Improve result type to be: OK | Problem(message, fix...)
|
console.error(e);
|
||||||
return {
|
// TODO Improve result type to be: OK | Problem(message, fix...)
|
||||||
hasProblem: true,
|
return {
|
||||||
};
|
hasProblem: true,
|
||||||
}),
|
};
|
||||||
})),
|
}),
|
||||||
),
|
})),
|
||||||
},
|
),
|
||||||
];
|
},
|
||||||
return categoryResult;
|
];
|
||||||
}),
|
return categoryResult;
|
||||||
)).filter(notNull);
|
}),
|
||||||
|
)
|
||||||
|
).filter(notNull);
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -133,6 +133,7 @@
|
|||||||
"emotion": "^9.2.6",
|
"emotion": "^9.2.6",
|
||||||
"expand-tilde": "^2.0.2",
|
"expand-tilde": "^2.0.2",
|
||||||
"express": "^4.15.2",
|
"express": "^4.15.2",
|
||||||
|
"flipper-doctor": "^0.2.0",
|
||||||
"fs-extra": "^8.0.1",
|
"fs-extra": "^8.0.1",
|
||||||
"immutable": "^4.0.0-rc.12",
|
"immutable": "^4.0.0-rc.12",
|
||||||
"invariant": "^2.2.2",
|
"invariant": "^2.2.2",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import TitleBar from './chrome/TitleBar';
|
|||||||
import MainSidebar from './chrome/MainSidebar';
|
import MainSidebar from './chrome/MainSidebar';
|
||||||
import BugReporterDialog from './chrome/BugReporterDialog';
|
import BugReporterDialog from './chrome/BugReporterDialog';
|
||||||
import ErrorBar from './chrome/ErrorBar';
|
import ErrorBar from './chrome/ErrorBar';
|
||||||
|
import DoctorBar from './chrome/DoctorBar';
|
||||||
import ShareSheetExportUrl from './chrome/ShareSheetExportUrl';
|
import ShareSheetExportUrl from './chrome/ShareSheetExportUrl';
|
||||||
import SignInSheet from './chrome/SignInSheet';
|
import SignInSheet from './chrome/SignInSheet';
|
||||||
import ExportDataPluginSheet from './chrome/ExportDataPluginSheet';
|
import ExportDataPluginSheet from './chrome/ExportDataPluginSheet';
|
||||||
@@ -29,6 +30,7 @@ import {
|
|||||||
ACTIVE_SHEET_SHARE_DATA,
|
ACTIVE_SHEET_SHARE_DATA,
|
||||||
ACTIVE_SHEET_SIGN_IN,
|
ACTIVE_SHEET_SIGN_IN,
|
||||||
ACTIVE_SHEET_SETTINGS,
|
ACTIVE_SHEET_SETTINGS,
|
||||||
|
ACTIVE_SHEET_DOCTOR,
|
||||||
ACTIVE_SHEET_SHARE_DATA_IN_FILE,
|
ACTIVE_SHEET_SHARE_DATA_IN_FILE,
|
||||||
ACTIVE_SHEET_SELECT_PLUGINS_TO_EXPORT,
|
ACTIVE_SHEET_SELECT_PLUGINS_TO_EXPORT,
|
||||||
ACTIVE_SHEET_PLUGIN_SHEET,
|
ACTIVE_SHEET_PLUGIN_SHEET,
|
||||||
@@ -40,6 +42,7 @@ import {StaticView, FlipperError} from './reducers/connections';
|
|||||||
import PluginManager from './chrome/PluginManager';
|
import PluginManager from './chrome/PluginManager';
|
||||||
import StatusBar from './chrome/StatusBar';
|
import StatusBar from './chrome/StatusBar';
|
||||||
import SettingsSheet from './chrome/SettingsSheet';
|
import SettingsSheet from './chrome/SettingsSheet';
|
||||||
|
import DoctorSheet from './chrome/DoctorSheet';
|
||||||
const version = remote.app.getVersion();
|
const version = remote.app.getVersion();
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
@@ -89,6 +92,8 @@ export class App extends React.Component<Props> {
|
|||||||
return <SignInSheet onHide={onHide} />;
|
return <SignInSheet onHide={onHide} />;
|
||||||
case ACTIVE_SHEET_SETTINGS:
|
case ACTIVE_SHEET_SETTINGS:
|
||||||
return <SettingsSheet onHide={onHide} />;
|
return <SettingsSheet onHide={onHide} />;
|
||||||
|
case ACTIVE_SHEET_DOCTOR:
|
||||||
|
return <DoctorSheet onHide={onHide} />;
|
||||||
case ACTIVE_SHEET_SELECT_PLUGINS_TO_EXPORT:
|
case ACTIVE_SHEET_SELECT_PLUGINS_TO_EXPORT:
|
||||||
return <ExportDataPluginSheet onHide={onHide} />;
|
return <ExportDataPluginSheet onHide={onHide} />;
|
||||||
case ACTIVE_SHEET_SHARE_DATA:
|
case ACTIVE_SHEET_SHARE_DATA:
|
||||||
@@ -126,6 +131,7 @@ export class App extends React.Component<Props> {
|
|||||||
return (
|
return (
|
||||||
<FlexColumn grow={true}>
|
<FlexColumn grow={true}>
|
||||||
<TitleBar version={version} />
|
<TitleBar version={version} />
|
||||||
|
<DoctorBar />
|
||||||
<ErrorBar />
|
<ErrorBar />
|
||||||
<Sheet>{this.getSheet}</Sheet>
|
<Sheet>{this.getSheet}</Sheet>
|
||||||
<FlexRow grow={true}>
|
<FlexRow grow={true}>
|
||||||
|
|||||||
142
src/chrome/DoctorBar.tsx
Normal file
142
src/chrome/DoctorBar.tsx
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
/**
|
||||||
|
* 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 {styled, colors} from 'flipper';
|
||||||
|
import React, {Component} from 'react';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
import {
|
||||||
|
setActiveSheet,
|
||||||
|
ActiveSheet,
|
||||||
|
ACTIVE_SHEET_DOCTOR,
|
||||||
|
} from '../reducers/application';
|
||||||
|
import {State as Store} from '../reducers/index';
|
||||||
|
import {ButtonGroup, Button} from 'flipper';
|
||||||
|
import {FlexColumn, FlexRow} from 'flipper';
|
||||||
|
import runHealthchecks from '../utils/runHealthchecks';
|
||||||
|
import {
|
||||||
|
initHealthcheckReport,
|
||||||
|
updateHealthcheckReportItem,
|
||||||
|
startHealthchecks,
|
||||||
|
finishHealthchecks,
|
||||||
|
HealthcheckReport,
|
||||||
|
HealthcheckReportItem,
|
||||||
|
} from '../reducers/healthchecks';
|
||||||
|
|
||||||
|
type StateFromProps = {};
|
||||||
|
|
||||||
|
type DispatchFromProps = {
|
||||||
|
setActiveSheet: (payload: ActiveSheet) => void;
|
||||||
|
initHealthcheckReport: (report: HealthcheckReport) => void;
|
||||||
|
updateHealthcheckReportItem: (
|
||||||
|
categoryIdx: number,
|
||||||
|
itemIdx: number,
|
||||||
|
item: HealthcheckReportItem,
|
||||||
|
) => void;
|
||||||
|
startHealthchecks: () => void;
|
||||||
|
finishHealthchecks: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
visible: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = DispatchFromProps & StateFromProps;
|
||||||
|
class DoctorBar extends Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
visible: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
this.showMessageIfChecksFailed();
|
||||||
|
}
|
||||||
|
async showMessageIfChecksFailed() {
|
||||||
|
const result = await runHealthchecks({
|
||||||
|
initHealthcheckReport: this.props.initHealthcheckReport,
|
||||||
|
updateHealthcheckReportItem: this.props.updateHealthcheckReportItem,
|
||||||
|
startHealthchecks: this.props.startHealthchecks,
|
||||||
|
finishHealthchecks: this.props.finishHealthchecks,
|
||||||
|
});
|
||||||
|
if (!result) {
|
||||||
|
this.setVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
this.state.visible && (
|
||||||
|
<Container>
|
||||||
|
<WarningContainer>
|
||||||
|
<FlexRow style={{flexDirection: 'row-reverse'}}>
|
||||||
|
<ButtonSection>
|
||||||
|
<ButtonGroup>
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
this.props.setActiveSheet(ACTIVE_SHEET_DOCTOR)
|
||||||
|
}>
|
||||||
|
Show Problems
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => this.setVisible(false)}>
|
||||||
|
Dismiss
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</ButtonSection>
|
||||||
|
<FlexColumn style={{flexGrow: 1}}>
|
||||||
|
Doctor has discovered problems with your installation
|
||||||
|
</FlexColumn>
|
||||||
|
</FlexRow>
|
||||||
|
</WarningContainer>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setVisible(visible: boolean) {
|
||||||
|
this.setState(prevState => {
|
||||||
|
return {
|
||||||
|
...prevState,
|
||||||
|
visible,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect<StateFromProps, DispatchFromProps, {}, Store>(null, {
|
||||||
|
setActiveSheet,
|
||||||
|
initHealthcheckReport,
|
||||||
|
updateHealthcheckReportItem,
|
||||||
|
startHealthchecks,
|
||||||
|
finishHealthchecks,
|
||||||
|
})(DoctorBar);
|
||||||
|
|
||||||
|
const Container = styled('div')({
|
||||||
|
boxShadow: '2px 2px 2px #ccc',
|
||||||
|
userSelect: 'text',
|
||||||
|
});
|
||||||
|
|
||||||
|
const WarningContainer = styled('div')({
|
||||||
|
backgroundColor: colors.orange,
|
||||||
|
color: '#fff',
|
||||||
|
maxHeight: '600px',
|
||||||
|
overflowY: 'auto',
|
||||||
|
overflowX: 'hidden',
|
||||||
|
transition: 'max-height 0.3s ease',
|
||||||
|
'&.collapsed': {
|
||||||
|
maxHeight: '0px',
|
||||||
|
},
|
||||||
|
padding: '4px 12px',
|
||||||
|
borderBottom: '1px solid ' + colors.orangeDark3,
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
lineHeight: '28px',
|
||||||
|
});
|
||||||
|
|
||||||
|
const ButtonSection = styled(FlexColumn)({
|
||||||
|
marginLeft: '8px',
|
||||||
|
flexShrink: 0,
|
||||||
|
flexGrow: 0,
|
||||||
|
});
|
||||||
307
src/chrome/DoctorSheet.tsx
Normal file
307
src/chrome/DoctorSheet.tsx
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
FlexColumn,
|
||||||
|
styled,
|
||||||
|
Text,
|
||||||
|
FlexRow,
|
||||||
|
Glyph,
|
||||||
|
LoadingIndicator,
|
||||||
|
colors,
|
||||||
|
Spacer,
|
||||||
|
Button,
|
||||||
|
FlexBox,
|
||||||
|
} from 'flipper';
|
||||||
|
import React, {Component} from 'react';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
import {State as Store} from '../reducers';
|
||||||
|
import {
|
||||||
|
HealthcheckResult,
|
||||||
|
HealthcheckReportCategory,
|
||||||
|
HealthcheckReportItem,
|
||||||
|
HealthcheckReport,
|
||||||
|
initHealthcheckReport,
|
||||||
|
updateHealthcheckReportItem,
|
||||||
|
startHealthchecks,
|
||||||
|
finishHealthchecks,
|
||||||
|
} from '../reducers/healthchecks';
|
||||||
|
import runHealthchecks from '../utils/runHealthchecks';
|
||||||
|
import {shell} from 'electron';
|
||||||
|
|
||||||
|
type StateFromProps = {
|
||||||
|
report: HealthcheckReport;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DispatchFromProps = {
|
||||||
|
initHealthcheckReport: (report: HealthcheckReport) => void;
|
||||||
|
updateHealthcheckReportItem: (
|
||||||
|
categoryIdx: number,
|
||||||
|
itemIdx: number,
|
||||||
|
item: HealthcheckReportItem,
|
||||||
|
) => void;
|
||||||
|
startHealthchecks: () => void;
|
||||||
|
finishHealthchecks: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Container = styled(FlexColumn)({
|
||||||
|
padding: 20,
|
||||||
|
width: 600,
|
||||||
|
});
|
||||||
|
|
||||||
|
const HealthcheckDisplayContainer = styled(FlexRow)({
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
const HealthcheckListContainer = styled(FlexColumn)({
|
||||||
|
marginBottom: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
const Title = styled(Text)({
|
||||||
|
marginBottom: 18,
|
||||||
|
marginRight: 10,
|
||||||
|
fontWeight: 100,
|
||||||
|
fontSize: '40px',
|
||||||
|
});
|
||||||
|
|
||||||
|
const CategoryContainer = styled(FlexColumn)({
|
||||||
|
marginBottom: 5,
|
||||||
|
marginLeft: 20,
|
||||||
|
marginRight: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
const SideContainer = styled(FlexBox)({
|
||||||
|
marginBottom: 20,
|
||||||
|
padding: 20,
|
||||||
|
backgroundColor: colors.highlightBackground,
|
||||||
|
border: '1px solid #b3b3b3',
|
||||||
|
width: 320,
|
||||||
|
});
|
||||||
|
|
||||||
|
const SideContainerText = styled(Text)({
|
||||||
|
display: 'block',
|
||||||
|
'word-wrap': 'break-word',
|
||||||
|
});
|
||||||
|
|
||||||
|
const HealthcheckLabel = styled(Text)({
|
||||||
|
paddingLeft: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
onHide: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function HealthcheckIcon(props: {check: HealthcheckResult}) {
|
||||||
|
switch (props.check.status) {
|
||||||
|
case 'IN_PROGRESS':
|
||||||
|
return <LoadingIndicator size={16} title={props.check.message} />;
|
||||||
|
case 'SUCCESS':
|
||||||
|
return (
|
||||||
|
<Glyph
|
||||||
|
size={16}
|
||||||
|
name={'checkmark'}
|
||||||
|
color={colors.green}
|
||||||
|
title={props.check.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case 'WARNING':
|
||||||
|
return (
|
||||||
|
<Glyph
|
||||||
|
size={16}
|
||||||
|
name={'caution'}
|
||||||
|
color={colors.yellow}
|
||||||
|
title={props.check.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<Glyph
|
||||||
|
size={16}
|
||||||
|
name={'cross'}
|
||||||
|
color={colors.red}
|
||||||
|
title={props.check.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function HealthcheckDisplay(props: {
|
||||||
|
category: HealthcheckReportCategory;
|
||||||
|
check: HealthcheckReportItem;
|
||||||
|
onClick?: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<FlexColumn shrink>
|
||||||
|
<HealthcheckDisplayContainer shrink title={props.check.message}>
|
||||||
|
<HealthcheckIcon check={props.check} />
|
||||||
|
<HealthcheckLabel
|
||||||
|
underline={!!props.onClick}
|
||||||
|
cursor={props.onClick && 'pointer'}
|
||||||
|
onClick={props.onClick}>
|
||||||
|
{props.check.label}
|
||||||
|
</HealthcheckLabel>
|
||||||
|
</HealthcheckDisplayContainer>
|
||||||
|
</FlexColumn>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SideMessageDisplay(props: {
|
||||||
|
isHealthcheckInProgress: boolean;
|
||||||
|
hasProblems: boolean;
|
||||||
|
}) {
|
||||||
|
if (props.isHealthcheckInProgress) {
|
||||||
|
return (
|
||||||
|
<SideContainerText selectable>
|
||||||
|
Doctor is running healthchecks...
|
||||||
|
</SideContainerText>
|
||||||
|
);
|
||||||
|
} else if (props.hasProblems) {
|
||||||
|
return (
|
||||||
|
<SideContainerText selectable>
|
||||||
|
Doctor has discovered problems with your installation.
|
||||||
|
</SideContainerText>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<SideContainerText selectable>
|
||||||
|
All good! Doctor has not discovered any issues with your installation.
|
||||||
|
</SideContainerText>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type State = {};
|
||||||
|
|
||||||
|
type Props = OwnProps & StateFromProps & DispatchFromProps;
|
||||||
|
class DoctorSheet extends Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
openHelpUrl(check: HealthcheckReportItem): void {
|
||||||
|
check.helpUrl && shell.openExternal(check.helpUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
async runHealthchecks() {
|
||||||
|
this.setState(prevState => {
|
||||||
|
return {
|
||||||
|
...prevState,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
await runHealthchecks({
|
||||||
|
initHealthcheckReport: this.props.initHealthcheckReport,
|
||||||
|
updateHealthcheckReportItem: this.props.updateHealthcheckReportItem,
|
||||||
|
startHealthchecks: this.props.startHealthchecks,
|
||||||
|
finishHealthchecks: this.props.finishHealthchecks,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hasProblems() {
|
||||||
|
return this.props.report.categories.some(cat =>
|
||||||
|
cat.checks.some(chk => chk.status != 'SUCCESS'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getHealthcheckCategoryReportItem(
|
||||||
|
state: HealthcheckReportCategory,
|
||||||
|
): HealthcheckReportItem {
|
||||||
|
return {
|
||||||
|
label: state.label,
|
||||||
|
...(state.checks.some(c => c.status === 'IN_PROGRESS')
|
||||||
|
? {status: 'IN_PROGRESS'}
|
||||||
|
: state.checks.every(c => c.status === 'SUCCESS')
|
||||||
|
? {status: 'SUCCESS'}
|
||||||
|
: state.checks.some(c => c.status === 'FAILED')
|
||||||
|
? {
|
||||||
|
status: 'FAILED',
|
||||||
|
message: 'Doctor discovered problems with the current installation',
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
status: 'WARNING',
|
||||||
|
message:
|
||||||
|
'Doctor discovered non-blocking problems with the current installation',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Title>Doctor</Title>
|
||||||
|
<FlexRow>
|
||||||
|
<HealthcheckListContainer>
|
||||||
|
{Object.values(this.props.report.categories).map(
|
||||||
|
(category, categoryIdx) => {
|
||||||
|
return (
|
||||||
|
<CategoryContainer key={categoryIdx}>
|
||||||
|
<HealthcheckDisplay
|
||||||
|
check={this.getHealthcheckCategoryReportItem(category)}
|
||||||
|
category={category}
|
||||||
|
/>
|
||||||
|
<CategoryContainer>
|
||||||
|
{category.checks.map((check, checkIdx) => (
|
||||||
|
<HealthcheckDisplay
|
||||||
|
key={checkIdx}
|
||||||
|
category={category}
|
||||||
|
check={check}
|
||||||
|
onClick={
|
||||||
|
check.helpUrl
|
||||||
|
? () => this.openHelpUrl(check)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</CategoryContainer>
|
||||||
|
</CategoryContainer>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</HealthcheckListContainer>
|
||||||
|
<Spacer />
|
||||||
|
<SideContainer shrink>
|
||||||
|
<SideMessageDisplay
|
||||||
|
isHealthcheckInProgress={
|
||||||
|
this.props.report.isHealthcheckInProgress
|
||||||
|
}
|
||||||
|
hasProblems={this.hasProblems()}
|
||||||
|
/>
|
||||||
|
</SideContainer>
|
||||||
|
</FlexRow>
|
||||||
|
<FlexRow>
|
||||||
|
<Spacer />
|
||||||
|
<Button compact padded onClick={this.props.onHide}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={this.props.report.isHealthcheckInProgress}
|
||||||
|
type="primary"
|
||||||
|
compact
|
||||||
|
padded
|
||||||
|
onClick={() => this.runHealthchecks()}>
|
||||||
|
Re-run
|
||||||
|
</Button>
|
||||||
|
</FlexRow>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
||||||
|
({healthchecks: {healthcheckReport}}) => ({
|
||||||
|
report: healthcheckReport,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
initHealthcheckReport,
|
||||||
|
updateHealthcheckReportItem,
|
||||||
|
startHealthchecks,
|
||||||
|
finishHealthchecks,
|
||||||
|
},
|
||||||
|
)(DoctorSheet);
|
||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
toggleRightSidebarVisible,
|
toggleRightSidebarVisible,
|
||||||
ACTIVE_SHEET_BUG_REPORTER,
|
ACTIVE_SHEET_BUG_REPORTER,
|
||||||
ACTIVE_SHEET_SETTINGS,
|
ACTIVE_SHEET_SETTINGS,
|
||||||
|
ACTIVE_SHEET_DOCTOR,
|
||||||
} from '../reducers/application';
|
} from '../reducers/application';
|
||||||
import {
|
import {
|
||||||
colors,
|
colors,
|
||||||
@@ -169,6 +170,13 @@ class TitleBar extends React.Component<Props, StateFromProps> {
|
|||||||
version={this.props.version}
|
version={this.props.version}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
icon="settings"
|
||||||
|
title="Settings"
|
||||||
|
compact={true}
|
||||||
|
onClick={() => this.props.setActiveSheet(ACTIVE_SHEET_SETTINGS)}
|
||||||
|
/>
|
||||||
{config.bugReportButtonVisible && (
|
{config.bugReportButtonVisible && (
|
||||||
<Button
|
<Button
|
||||||
compact={true}
|
compact={true}
|
||||||
@@ -178,10 +186,10 @@ class TitleBar extends React.Component<Props, StateFromProps> {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
icon="settings"
|
icon="first-aid"
|
||||||
title="Settings"
|
title="Doctor"
|
||||||
compact={true}
|
compact={true}
|
||||||
onClick={() => this.props.setActiveSheet(ACTIVE_SHEET_SETTINGS)}
|
onClick={() => this.props.setActiveSheet(ACTIVE_SHEET_DOCTOR)}
|
||||||
/>
|
/>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ exports[`ShareSheetPendingDialog is rendered with status update 1`] = `
|
|||||||
size={30}
|
size={30}
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
className="css-18qh9b2"
|
className="css-91luyc"
|
||||||
color="#6f6f6f"
|
color="#6f6f6f"
|
||||||
>
|
>
|
||||||
Update
|
Update
|
||||||
@@ -57,7 +57,7 @@ exports[`ShareSheetPendingDialog is rendered without status update 1`] = `
|
|||||||
size={30}
|
size={30}
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
className="css-18qh9b2"
|
className="css-91luyc"
|
||||||
color="#6f6f6f"
|
color="#6f6f6f"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export const ACTIVE_SHEET_SELECT_PLUGINS_TO_EXPORT: 'SELECT_PLUGINS_TO_EXPORT' =
|
|||||||
export const ACTIVE_SHEET_SHARE_DATA: 'SHARE_DATA' = 'SHARE_DATA';
|
export const ACTIVE_SHEET_SHARE_DATA: 'SHARE_DATA' = 'SHARE_DATA';
|
||||||
export const ACTIVE_SHEET_SIGN_IN: 'SIGN_IN' = 'SIGN_IN';
|
export const ACTIVE_SHEET_SIGN_IN: 'SIGN_IN' = 'SIGN_IN';
|
||||||
export const ACTIVE_SHEET_SETTINGS: 'SETTINGS' = 'SETTINGS';
|
export const ACTIVE_SHEET_SETTINGS: 'SETTINGS' = 'SETTINGS';
|
||||||
|
export const ACTIVE_SHEET_DOCTOR: 'DOCTOR' = 'DOCTOR';
|
||||||
export const ACTIVE_SHEET_SHARE_DATA_IN_FILE: 'SHARE_DATA_IN_FILE' =
|
export const ACTIVE_SHEET_SHARE_DATA_IN_FILE: 'SHARE_DATA_IN_FILE' =
|
||||||
'SHARE_DATA_IN_FILE';
|
'SHARE_DATA_IN_FILE';
|
||||||
export const SET_EXPORT_STATUS_MESSAGE: 'SET_EXPORT_STATUS_MESSAGE' =
|
export const SET_EXPORT_STATUS_MESSAGE: 'SET_EXPORT_STATUS_MESSAGE' =
|
||||||
@@ -33,6 +34,7 @@ export type ActiveSheet =
|
|||||||
| typeof ACTIVE_SHEET_SHARE_DATA
|
| typeof ACTIVE_SHEET_SHARE_DATA
|
||||||
| typeof ACTIVE_SHEET_SIGN_IN
|
| typeof ACTIVE_SHEET_SIGN_IN
|
||||||
| typeof ACTIVE_SHEET_SETTINGS
|
| typeof ACTIVE_SHEET_SETTINGS
|
||||||
|
| typeof ACTIVE_SHEET_DOCTOR
|
||||||
| typeof ACTIVE_SHEET_SHARE_DATA_IN_FILE
|
| typeof ACTIVE_SHEET_SHARE_DATA_IN_FILE
|
||||||
| typeof ACTIVE_SHEET_SELECT_PLUGINS_TO_EXPORT
|
| typeof ACTIVE_SHEET_SELECT_PLUGINS_TO_EXPORT
|
||||||
| null;
|
| null;
|
||||||
|
|||||||
154
src/reducers/healthchecks.tsx
Normal file
154
src/reducers/healthchecks.tsx
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
/**
|
||||||
|
* 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 {Actions} from './';
|
||||||
|
|
||||||
|
export type State = {
|
||||||
|
healthcheckReport: HealthcheckReport;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Action =
|
||||||
|
| {
|
||||||
|
type: 'INIT_HEALTHCHECK_REPORT';
|
||||||
|
payload: HealthcheckReport;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'UPDATE_HEALTHCHECK_REPORT_ITEM';
|
||||||
|
payload: {
|
||||||
|
categoryIdx: number;
|
||||||
|
itemIdx: number;
|
||||||
|
item: HealthcheckReportItem;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'START_HEALTHCHECKS';
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'FINISH_HEALTHCHECKS';
|
||||||
|
};
|
||||||
|
|
||||||
|
const INITIAL_STATE: State = {
|
||||||
|
healthcheckReport: {
|
||||||
|
isHealthcheckInProgress: false,
|
||||||
|
categories: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HealthcheckStatus =
|
||||||
|
| 'IN_PROGRESS'
|
||||||
|
| 'SUCCESS'
|
||||||
|
| 'FAILED'
|
||||||
|
| 'WARNING';
|
||||||
|
|
||||||
|
export type HealthcheckResult = {
|
||||||
|
status: HealthcheckStatus;
|
||||||
|
message?: string;
|
||||||
|
helpUrl?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HealthcheckReportItem = {
|
||||||
|
label: string;
|
||||||
|
} & HealthcheckResult;
|
||||||
|
|
||||||
|
export type HealthcheckReportCategory = {
|
||||||
|
label: string;
|
||||||
|
status: HealthcheckStatus;
|
||||||
|
checks: Array<HealthcheckReportItem>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HealthcheckReport = {
|
||||||
|
isHealthcheckInProgress: boolean;
|
||||||
|
categories: Array<HealthcheckReportCategory>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function reducer(
|
||||||
|
state: State | undefined = INITIAL_STATE,
|
||||||
|
action: Actions,
|
||||||
|
): State {
|
||||||
|
if (action.type === 'INIT_HEALTHCHECK_REPORT') {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
healthcheckReport: action.payload,
|
||||||
|
};
|
||||||
|
} else if (action.type === 'START_HEALTHCHECKS') {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
healthcheckReport: {
|
||||||
|
...state.healthcheckReport,
|
||||||
|
isHealthcheckInProgress: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (action.type === 'FINISH_HEALTHCHECKS') {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
healthcheckReport: {
|
||||||
|
...state.healthcheckReport,
|
||||||
|
isHealthcheckInProgress: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (action.type === 'UPDATE_HEALTHCHECK_REPORT_ITEM') {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
healthcheckReport: {
|
||||||
|
...state.healthcheckReport,
|
||||||
|
categories: [
|
||||||
|
...state.healthcheckReport.categories.slice(
|
||||||
|
0,
|
||||||
|
action.payload.categoryIdx,
|
||||||
|
),
|
||||||
|
{
|
||||||
|
...state.healthcheckReport.categories[action.payload.categoryIdx],
|
||||||
|
checks: [
|
||||||
|
...state.healthcheckReport.categories[
|
||||||
|
action.payload.categoryIdx
|
||||||
|
].checks.slice(0, action.payload.itemIdx),
|
||||||
|
{
|
||||||
|
...action.payload.item,
|
||||||
|
},
|
||||||
|
...state.healthcheckReport.categories[
|
||||||
|
action.payload.categoryIdx
|
||||||
|
].checks.slice(action.payload.itemIdx + 1),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
...state.healthcheckReport.categories.slice(
|
||||||
|
action.payload.categoryIdx + 1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initHealthcheckReport = (report: HealthcheckReport): Action => ({
|
||||||
|
type: 'INIT_HEALTHCHECK_REPORT',
|
||||||
|
payload: report,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const updateHealthcheckReportItem = (
|
||||||
|
categoryIdx: number,
|
||||||
|
itemIdx: number,
|
||||||
|
item: HealthcheckReportItem,
|
||||||
|
): Action => ({
|
||||||
|
type: 'UPDATE_HEALTHCHECK_REPORT_ITEM',
|
||||||
|
payload: {
|
||||||
|
categoryIdx,
|
||||||
|
itemIdx,
|
||||||
|
item,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const startHealthchecks = (): Action => ({
|
||||||
|
type: 'START_HEALTHCHECKS',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const finishHealthchecks = (): Action => ({
|
||||||
|
type: 'FINISH_HEALTHCHECKS',
|
||||||
|
});
|
||||||
@@ -40,6 +40,10 @@ import pluginManager, {
|
|||||||
State as PluginManagerState,
|
State as PluginManagerState,
|
||||||
Action as PluginManagerAction,
|
Action as PluginManagerAction,
|
||||||
} from './pluginManager';
|
} from './pluginManager';
|
||||||
|
import healthchecks, {
|
||||||
|
Action as HealthcheckAction,
|
||||||
|
State as HealthcheckState,
|
||||||
|
} from './healthchecks';
|
||||||
import user, {State as UserState, Action as UserAction} from './user';
|
import user, {State as UserState, Action as UserAction} from './user';
|
||||||
import JsonFileStorage from '../utils/jsonFileReduxPersistStorage';
|
import JsonFileStorage from '../utils/jsonFileReduxPersistStorage';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
@@ -61,6 +65,7 @@ export type Actions =
|
|||||||
| SettingsAction
|
| SettingsAction
|
||||||
| SupportFormAction
|
| SupportFormAction
|
||||||
| PluginManagerAction
|
| PluginManagerAction
|
||||||
|
| HealthcheckAction
|
||||||
| {type: 'INIT'};
|
| {type: 'INIT'};
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
@@ -73,6 +78,7 @@ export type State = {
|
|||||||
settingsState: SettingsState & PersistPartial;
|
settingsState: SettingsState & PersistPartial;
|
||||||
supportForm: SupportFormState;
|
supportForm: SupportFormState;
|
||||||
pluginManager: PluginManagerState;
|
pluginManager: PluginManagerState;
|
||||||
|
healthchecks: HealthcheckState;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Store = ReduxStore<State, Actions>;
|
export type Store = ReduxStore<State, Actions>;
|
||||||
@@ -124,4 +130,5 @@ export default combineReducers<State, Actions>({
|
|||||||
{key: 'settings', storage: settingsStorage},
|
{key: 'settings', storage: settingsStorage},
|
||||||
settings,
|
settings,
|
||||||
),
|
),
|
||||||
|
healthchecks,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ export default class Glyph extends React.PureComponent<{
|
|||||||
className?: string;
|
className?: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
|
title?: string;
|
||||||
}> {
|
}> {
|
||||||
render() {
|
render() {
|
||||||
const {name, size = 16, variant, color, className, style} = this.props;
|
const {name, size = 16, variant, color, className, style} = this.props;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
FontFamilyProperty,
|
FontFamilyProperty,
|
||||||
WhiteSpaceProperty,
|
WhiteSpaceProperty,
|
||||||
WordWrapProperty,
|
WordWrapProperty,
|
||||||
|
CursorProperty,
|
||||||
} from 'csstype';
|
} from 'csstype';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,6 +26,7 @@ const Text = styled('span')(
|
|||||||
color?: ColorProperty;
|
color?: ColorProperty;
|
||||||
bold?: boolean;
|
bold?: boolean;
|
||||||
italic?: boolean;
|
italic?: boolean;
|
||||||
|
underline?: boolean;
|
||||||
align?: TextAlignProperty;
|
align?: TextAlignProperty;
|
||||||
size?: FontSizeProperty<number>;
|
size?: FontSizeProperty<number>;
|
||||||
code?: boolean;
|
code?: boolean;
|
||||||
@@ -32,13 +34,16 @@ const Text = styled('span')(
|
|||||||
selectable?: boolean;
|
selectable?: boolean;
|
||||||
wordWrap?: WordWrapProperty;
|
wordWrap?: WordWrapProperty;
|
||||||
whiteSpace?: WhiteSpaceProperty;
|
whiteSpace?: WhiteSpaceProperty;
|
||||||
|
cursor?: CursorProperty;
|
||||||
}) => ({
|
}) => ({
|
||||||
color: props.color ? props.color : 'inherit',
|
color: props.color ? props.color : 'inherit',
|
||||||
|
cursor: props.cursor ? props.cursor : 'auto',
|
||||||
display: 'inline',
|
display: 'inline',
|
||||||
fontWeight: props.bold ? 'bold' : 'inherit',
|
fontWeight: props.bold ? 'bold' : 'inherit',
|
||||||
fontStyle: props.italic ? 'italic' : 'normal',
|
fontStyle: props.italic ? 'italic' : 'normal',
|
||||||
textAlign: props.align || 'left',
|
textAlign: props.align || 'left',
|
||||||
fontSize: props.size == null && props.code ? 12 : props.size,
|
fontSize: props.size == null && props.code ? 12 : props.size,
|
||||||
|
textDecoration: props.underline ? 'underline' : 'initial',
|
||||||
fontFamily: props.code
|
fontFamily: props.code
|
||||||
? 'SF Mono, Monaco, Andale Mono, monospace'
|
? 'SF Mono, Monaco, Andale Mono, monospace'
|
||||||
: props.family,
|
: props.family,
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ const ICONS = {
|
|||||||
bug: [12],
|
bug: [12],
|
||||||
camcorder: [12],
|
camcorder: [12],
|
||||||
camera: [12],
|
camera: [12],
|
||||||
|
caution: [16],
|
||||||
|
cross: [16],
|
||||||
|
checkmark: [16],
|
||||||
desktop: [12],
|
desktop: [12],
|
||||||
directions: [12],
|
directions: [12],
|
||||||
internet: [12],
|
internet: [12],
|
||||||
|
|||||||
111
src/utils/runHealthchecks.tsx
Normal file
111
src/utils/runHealthchecks.tsx
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
HealthcheckResult,
|
||||||
|
HealthcheckReport,
|
||||||
|
HealthcheckReportItem,
|
||||||
|
HealthcheckReportCategory,
|
||||||
|
} from '../reducers/healthchecks';
|
||||||
|
import {getHealthchecks, getEnvInfo} from 'flipper-doctor';
|
||||||
|
|
||||||
|
let healthcheckIsRunning: boolean;
|
||||||
|
let runningHealthcheck: Promise<boolean>;
|
||||||
|
|
||||||
|
export type HealthcheckEventsHandler = {
|
||||||
|
initHealthcheckReport: (report: HealthcheckReport) => void;
|
||||||
|
updateHealthcheckReportItem: (
|
||||||
|
categoryIdx: number,
|
||||||
|
itemIdx: number,
|
||||||
|
item: HealthcheckReportItem,
|
||||||
|
) => void;
|
||||||
|
startHealthchecks: () => void;
|
||||||
|
finishHealthchecks: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function launchHealthchecks(
|
||||||
|
dispatch: HealthcheckEventsHandler,
|
||||||
|
): Promise<boolean> {
|
||||||
|
let hasProblems: boolean = true;
|
||||||
|
dispatch.startHealthchecks();
|
||||||
|
try {
|
||||||
|
const initialState: HealthcheckResult = {
|
||||||
|
status: 'IN_PROGRESS',
|
||||||
|
message: 'The healthcheck is in progress',
|
||||||
|
};
|
||||||
|
const hcState: HealthcheckReport = {
|
||||||
|
isHealthcheckInProgress: true,
|
||||||
|
categories: Object.values(getHealthchecks())
|
||||||
|
.map(category => {
|
||||||
|
if (!category) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...initialState,
|
||||||
|
label: category.label,
|
||||||
|
checks: category.healthchecks.map(x => ({
|
||||||
|
...initialState,
|
||||||
|
label: x.label,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(x => !!x)
|
||||||
|
.map(x => x as HealthcheckReportCategory),
|
||||||
|
};
|
||||||
|
dispatch.initHealthcheckReport(hcState);
|
||||||
|
const environmentInfo = await getEnvInfo();
|
||||||
|
const categories = Object.values(getHealthchecks());
|
||||||
|
for (let cIdx = 0; cIdx < categories.length; cIdx++) {
|
||||||
|
const c = categories[cIdx];
|
||||||
|
if (!c) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (let hIdx = 0; hIdx < c.healthchecks.length; hIdx++) {
|
||||||
|
const h = c.healthchecks[hIdx];
|
||||||
|
const result = await h.run(environmentInfo);
|
||||||
|
if (result.hasProblem) {
|
||||||
|
hasProblems = false;
|
||||||
|
}
|
||||||
|
dispatch.updateHealthcheckReportItem(cIdx, hIdx, {
|
||||||
|
...h,
|
||||||
|
...(result.hasProblem && h.isRequired
|
||||||
|
? {
|
||||||
|
status: 'FAILED',
|
||||||
|
message: 'The healthcheck failed',
|
||||||
|
helpUrl: result.helpUrl,
|
||||||
|
}
|
||||||
|
: result.hasProblem && !h.isRequired
|
||||||
|
? {
|
||||||
|
status: 'WARNING',
|
||||||
|
message: 'Doctor discovered a problem during the healthcech',
|
||||||
|
helpUrl: result.helpUrl,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
status: 'SUCCESS',
|
||||||
|
message: 'The healthcheck completed succesfully',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
} finally {
|
||||||
|
dispatch.finishHealthchecks();
|
||||||
|
}
|
||||||
|
return hasProblems;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function runHealthchecks(
|
||||||
|
dispatch: HealthcheckEventsHandler,
|
||||||
|
): Promise<boolean> {
|
||||||
|
if (healthcheckIsRunning) {
|
||||||
|
return runningHealthcheck;
|
||||||
|
}
|
||||||
|
runningHealthcheck = launchHealthchecks(dispatch);
|
||||||
|
return runningHealthcheck;
|
||||||
|
}
|
||||||
12
yarn.lock
12
yarn.lock
@@ -3444,6 +3444,11 @@ envify@^4.0.0:
|
|||||||
esprima "^4.0.0"
|
esprima "^4.0.0"
|
||||||
through "~2.3.4"
|
through "~2.3.4"
|
||||||
|
|
||||||
|
envinfo@^7.4.0:
|
||||||
|
version "7.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.4.0.tgz#bef4ece9e717423aaf0c3584651430b735ad6630"
|
||||||
|
integrity sha512-FdDfnWnCVjxTTpWE3d6Jgh5JDIA3Cw7LCgpM/pI7kK1ORkjaqI2r6NqQ+ln2j0dfpgxY00AWieSvtkiZQKIItA==
|
||||||
|
|
||||||
err-code@^1.0.0:
|
err-code@^1.0.0:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960"
|
resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960"
|
||||||
@@ -4115,6 +4120,13 @@ flatted@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08"
|
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08"
|
||||||
integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==
|
integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==
|
||||||
|
|
||||||
|
flipper-doctor@^0.2.0:
|
||||||
|
version "0.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/flipper-doctor/-/flipper-doctor-0.2.0.tgz#b0b1b51f3e6b99cf82eefe68098b74f75455e9d4"
|
||||||
|
integrity sha512-OQ+L4bm6OEcVGmmeQVwWJcPx6y6letE9BV+vkT2pS49jk2q1q++aJ+Z+aH7P0f/VfXpGYc3JaNUhMFie0A6LVQ==
|
||||||
|
dependencies:
|
||||||
|
envinfo "^7.4.0"
|
||||||
|
|
||||||
flow-bin@0.112.0:
|
flow-bin@0.112.0:
|
||||||
version "0.112.0"
|
version "0.112.0"
|
||||||
resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.112.0.tgz#6a21c31937c4a2f23a750056a364c598a95ea216"
|
resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.112.0.tgz#6a21c31937c4a2f23a750056a364c598a95ea216"
|
||||||
|
|||||||
Reference in New Issue
Block a user