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
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,
|
||||
ACTIVE_SHEET_BUG_REPORTER,
|
||||
ACTIVE_SHEET_SETTINGS,
|
||||
ACTIVE_SHEET_DOCTOR,
|
||||
} from '../reducers/application';
|
||||
import {
|
||||
colors,
|
||||
@@ -169,6 +170,13 @@ class TitleBar extends React.Component<Props, StateFromProps> {
|
||||
version={this.props.version}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Button
|
||||
icon="settings"
|
||||
title="Settings"
|
||||
compact={true}
|
||||
onClick={() => this.props.setActiveSheet(ACTIVE_SHEET_SETTINGS)}
|
||||
/>
|
||||
{config.bugReportButtonVisible && (
|
||||
<Button
|
||||
compact={true}
|
||||
@@ -178,10 +186,10 @@ class TitleBar extends React.Component<Props, StateFromProps> {
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
icon="settings"
|
||||
title="Settings"
|
||||
icon="first-aid"
|
||||
title="Doctor"
|
||||
compact={true}
|
||||
onClick={() => this.props.setActiveSheet(ACTIVE_SHEET_SETTINGS)}
|
||||
onClick={() => this.props.setActiveSheet(ACTIVE_SHEET_DOCTOR)}
|
||||
/>
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
|
||||
@@ -12,7 +12,7 @@ exports[`ShareSheetPendingDialog is rendered with status update 1`] = `
|
||||
size={30}
|
||||
/>
|
||||
<span
|
||||
className="css-18qh9b2"
|
||||
className="css-91luyc"
|
||||
color="#6f6f6f"
|
||||
>
|
||||
Update
|
||||
@@ -57,7 +57,7 @@ exports[`ShareSheetPendingDialog is rendered without status update 1`] = `
|
||||
size={30}
|
||||
/>
|
||||
<span
|
||||
className="css-18qh9b2"
|
||||
className="css-91luyc"
|
||||
color="#6f6f6f"
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user