/**
* Copyright (c) Meta Platforms, Inc. and 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 React, {useEffect, useCallback, useMemo, useState} from 'react';
import {useDispatch, useStore} from '../utils/useStore';
import {Typography, Collapse, Button, Modal, Checkbox, Alert} from 'antd';
import {
CheckCircleFilled,
CloseCircleFilled,
WarningFilled,
QuestionCircleFilled,
LoadingOutlined,
} from '@ant-design/icons';
import {Layout, styled} from '../ui';
import {theme} from 'flipper-plugin';
import {
startHealthchecks,
updateHealthcheckResult,
finishHealthchecks,
acknowledgeProblems,
resetAcknowledgedProblems,
} from '../reducers/healthchecks';
import runHealthchecks from '../utils/runHealthchecks';
import type {FlipperDoctor} from 'flipper-common';
type Healthchecks = FlipperDoctor.Healthchecks;
import {reportUsage} from 'flipper-common';
const {Title, Paragraph, Text} = Typography;
const statusTypeAndMessage: {
[key in FlipperDoctor.HealthcheckStatus]: {
type: 'success' | 'info' | 'warning' | 'error';
message: string;
};
} = {
IN_PROGRESS: {type: 'info', message: 'Doctor is running healthchecks...'},
FAILED: {
type: 'error',
message:
'Problems have been discovered with your installation. Please expand items for details.',
},
WARNING: {
type: 'warning',
message: 'Doctor has discovered warnings. Please expand items for details.',
},
SUCCESS: {
type: 'success',
message:
'All good! Doctor has not discovered any issues with your installation.',
},
// This is deduced from default case (for completeness)
SKIPPED: {
type: 'success',
message:
'All good! Doctor has not discovered any issues with your installation.',
},
};
function checkHasProblem(result: FlipperDoctor.HealthcheckResult) {
return result.status === 'FAILED' || result.status === 'WARNING';
}
export function checkHasNewProblem(result: FlipperDoctor.HealthcheckResult) {
return checkHasProblem(result) && !result.isAcknowledged;
}
function ResultTopDialog(props: {status: FlipperDoctor.HealthcheckStatus}) {
const messages = statusTypeAndMessage[props.status];
return (
);
}
function CheckIcon(props: {status: FlipperDoctor.HealthcheckStatus}) {
switch (props.status) {
case 'SUCCESS':
return (
);
case 'FAILED':
return (
);
case 'WARNING':
return (
);
case 'SKIPPED':
return (
);
case 'IN_PROGRESS':
return (
);
}
}
function CollapsableCategory(props: {
checks: Array;
}) {
return (
{props.checks.map((check) => (
}>
{check.result.message}
))}
);
}
function HealthCheckList(props: {report: FlipperDoctor.HealthcheckReport}) {
useEffect(() => reportUsage('doctor:report:opened'), []);
return (
{Object.values(props.report.categories).map((category) => (
{category.label}
))}
);
}
const FooterContainer = styled(Layout.Horizontal)({
justifyContent: 'space-between',
alignItems: 'center',
});
function SetupDoctorFooter(props: {
onClose: () => void;
onRerunDoctor: () => Promise;
showAcknowledgeCheckbox: boolean;
acknowledgeCheck: boolean;
onAcknowledgeCheck: (checked: boolean) => void;
disableRerun: boolean;
}) {
return (
{props.showAcknowledgeCheckbox ? (
props.onAcknowledgeCheck(e.target.checked)}>
Do not show warning about these problems at startup
) : (
)}
);
}
export default function SetupDoctorScreen(props: {
visible: boolean;
onClose: () => void;
}) {
const healthcheckReport = useStore(
(state) => state.healthchecks.healthcheckReport,
);
const settings = useStore((state) => state.settingsState);
const dispatch = useDispatch();
const [acknowlodgeProblem, setAcknowlodgeProblem] = useState(
checkHasNewProblem(healthcheckReport.result),
);
const hasProblem = useMemo(
() => checkHasProblem(healthcheckReport.result),
[healthcheckReport],
);
const onCloseModal = useCallback(() => {
const hasNewProblem = checkHasNewProblem(healthcheckReport.result);
if (acknowlodgeProblem) {
if (hasNewProblem) {
reportUsage('doctor:report:closed:newProblems:acknowledged');
}
reportUsage('doctor:report:closed:acknowleged');
dispatch(acknowledgeProblems());
} else {
if (hasNewProblem) {
reportUsage('doctor:report:closed:newProblems:notAcknowledged');
}
reportUsage('doctor:report:closed:notAcknowledged');
dispatch(resetAcknowledgedProblems());
}
props.onClose();
}, [healthcheckReport.result, acknowlodgeProblem, props, dispatch]);
const runDoctor = useCallback(async () => {
await runHealthchecks({
settings,
startHealthchecks: (healthchecks: Healthchecks) =>
dispatch(startHealthchecks(healthchecks)),
updateHealthcheckResult: (
categoryKey: string,
itemKey: string,
result: FlipperDoctor.HealthcheckResult,
) => dispatch(updateHealthcheckResult(categoryKey, itemKey, result)),
finishHealthchecks: () => dispatch(finishHealthchecks()),
});
}, [settings, dispatch]);
// This will act like componentDidMount
useEffect(() => {
runDoctor();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return (
setAcknowlodgeProblem(checked)}
disableRerun={healthcheckReport.result.status === 'IN_PROGRESS'}
/>
}
onCancel={onCloseModal}>
);
}