/** * 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}> ); }