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:
Anton Nikolaev
2019-11-21 02:51:35 -08:00
committed by Facebook Github Bot
parent c1de6f4276
commit ddb135ac39
16 changed files with 823 additions and 37 deletions

307
src/chrome/DoctorSheet.tsx Normal file
View 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);