From 2480ed30c56c7afe2b76c77d6d89f739ce8ab8cd Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Wed, 8 Dec 2021 04:25:28 -0800 Subject: [PATCH] Move flipper-doctor check running to flipper-server-core Summary: Per title. Two new server API's: get-healthchecks, and run-healtcheck. Types have all been moved to flipper-common, so that they can be used by doctor, server-core and ui-core packages. Since it were quite some, moved them into a FlipperDoctor namespace. Reviewed By: nikoant Differential Revision: D32720510 fbshipit-source-id: 37aa35cde6ebd58479cf0dffec5b7b2da6d22198 --- desktop/doctor/package.json | 1 + desktop/doctor/src/cli.ts | 2 +- desktop/doctor/src/environmentInfo.ts | 25 +-- desktop/doctor/src/index.ts | 148 ++++++------------ desktop/doctor/tsconfig.json | 7 +- desktop/flipper-common/src/doctor.tsx | 126 +++++++++++++++ desktop/flipper-common/src/index.tsx | 1 + desktop/flipper-common/src/server-types.tsx | 9 ++ desktop/flipper-server-core/package.json | 1 + .../src/FlipperServerImpl.tsx | 3 + .../src/utils/runHealthchecks.tsx | 76 +++++++++ desktop/flipper-server-core/tsconfig.json | 3 + desktop/flipper-ui-core/package.json | 1 - .../src/chrome/DoctorSheet.tsx | 21 ++- .../reducers/__tests__/healthchecks.node.tsx | 10 +- .../src/reducers/healthchecks.tsx | 127 ++++++--------- .../src/sandy-chrome/SetupDoctorScreen.tsx | 27 ++-- .../src/utils/runHealthchecks.tsx | 58 +++---- desktop/flipper-ui-core/tsconfig.json | 3 - 19 files changed, 373 insertions(+), 276 deletions(-) create mode 100644 desktop/flipper-common/src/doctor.tsx create mode 100644 desktop/flipper-server-core/src/utils/runHealthchecks.tsx diff --git a/desktop/doctor/package.json b/desktop/doctor/package.json index 9605195f1..8c9d57c15 100644 --- a/desktop/doctor/package.json +++ b/desktop/doctor/package.json @@ -16,6 +16,7 @@ "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-react": "^7.27.1", + "flipper-common": "0.0.0", "jest": "^26.6.3", "prettier": "^2.4.1", "ts-jest": "^26.5.6", diff --git a/desktop/doctor/src/cli.ts b/desktop/doctor/src/cli.ts index 5f89d8fd6..bd9d494dc 100644 --- a/desktop/doctor/src/cli.ts +++ b/desktop/doctor/src/cli.ts @@ -25,7 +25,7 @@ import {getEnvInfo} from './environmentInfo'; category.healthchecks.map(async ({key, label, run}) => ({ key, label, - result: await run(environmentInfo), + result: await run!(environmentInfo), })), ), }, diff --git a/desktop/doctor/src/environmentInfo.ts b/desktop/doctor/src/environmentInfo.ts index bdbcaef5e..550d124b3 100644 --- a/desktop/doctor/src/environmentInfo.ts +++ b/desktop/doctor/src/environmentInfo.ts @@ -8,28 +8,7 @@ */ import {run} from 'envinfo'; - -export type EnvironmentInfo = { - SDKs: { - 'iOS SDK': { - Platforms: string[]; - }; - 'Android SDK': - | { - 'API Levels': string[] | 'Not Found'; - 'Build Tools': string[] | 'Not Found'; - 'System Images': string[] | 'Not Found'; - 'Android NDK': string | 'Not Found'; - } - | 'Not Found'; - }; - IDEs: { - Xcode: { - version: string; - path: string; - }; - }; -}; +import {FlipperDoctor} from 'flipper-common'; async function retrieveAndParseEnvInfo(): Promise { return JSON.parse( @@ -43,6 +22,6 @@ async function retrieveAndParseEnvInfo(): Promise { ); } -export async function getEnvInfo(): Promise { +export async function getEnvInfo(): Promise { return await retrieveAndParseEnvInfo(); } diff --git a/desktop/doctor/src/index.ts b/desktop/doctor/src/index.ts index 12939080a..3ab86151b 100644 --- a/desktop/doctor/src/index.ts +++ b/desktop/doctor/src/index.ts @@ -9,65 +9,15 @@ import {exec} from 'child_process'; import {promisify} from 'util'; -import {EnvironmentInfo, getEnvInfo} from './environmentInfo'; -export {EnvironmentInfo, getEnvInfo} from './environmentInfo'; +import {getEnvInfo} from './environmentInfo'; +export {getEnvInfo} from './environmentInfo'; + import * as watchman from 'fb-watchman'; import * as fs from 'fs'; import * as path from 'path'; +import {FlipperDoctor} from 'flipper-common'; -export type HealthcheckCategory = { - label: string; - isSkipped: false; - isRequired: boolean; - healthchecks: Healthcheck[]; -}; - -export type SkippedHealthcheckCategory = { - label: string; - isSkipped: true; - skipReason: string; -}; - -export type Healthchecks = { - common: HealthcheckCategory | SkippedHealthcheckCategory; - android: HealthcheckCategory | SkippedHealthcheckCategory; - ios: HealthcheckCategory | SkippedHealthcheckCategory; -}; - -export type Settings = { - idbPath: string; - enablePhysicalIOS: boolean; -}; - -export type Healthcheck = { - key: string; - label: string; - isRequired?: boolean; - run: ( - env: EnvironmentInfo, - settings?: Settings, - ) => Promise; -}; - -export type HealthcheckRunResult = { - hasProblem: boolean; - message: string; -}; - -export type CategoryResult = [ - string, - { - label: string; - results: Array<{ - key: string; - label: string; - isRequired: boolean; - result: {hasProblem: boolean}; - }>; - }, -]; - -export function getHealthchecks(): Healthchecks { +export function getHealthchecks(): FlipperDoctor.Healthchecks { return { common: { label: 'Common', @@ -77,7 +27,7 @@ export function getHealthchecks(): Healthchecks { { key: 'common.openssl', label: 'OpenSSL Installed', - run: async (_: EnvironmentInfo) => { + run: async (_: FlipperDoctor.EnvironmentInfo) => { const result = await tryExecuteCommand('openssl version'); const hasProblem = result.hasProblem; const message = hasProblem @@ -92,7 +42,7 @@ export function getHealthchecks(): Healthchecks { { key: 'common.watchman', label: 'Watchman Installed', - run: async (_: EnvironmentInfo) => { + run: async (_: FlipperDoctor.EnvironmentInfo) => { const isAvailable = await isWatchmanAvailable(); return { hasProblem: !isAvailable, @@ -113,11 +63,11 @@ export function getHealthchecks(): Healthchecks { key: 'android.sdk', label: 'SDK Installed', isRequired: true, - run: async (_: EnvironmentInfo) => { + run: async (_: FlipperDoctor.EnvironmentInfo) => { const androidHome = process.env.ANDROID_HOME; const androidSdkRoot = process.env.ANDROID_SDK_ROOT; - let androidHomeResult: HealthcheckRunResult; + let androidHomeResult: FlipperDoctor.HealthcheckRunResult; if (!androidHome) { androidHomeResult = { hasProblem: true, @@ -145,7 +95,7 @@ export function getHealthchecks(): Healthchecks { return androidHomeResult; } - let androidSdkRootResult: HealthcheckRunResult; + let androidSdkRootResult: FlipperDoctor.HealthcheckRunResult; if (!androidSdkRoot) { androidSdkRootResult = { hasProblem: true, @@ -188,7 +138,7 @@ export function getHealthchecks(): Healthchecks { key: 'ios.sdk', label: 'SDK Installed', isRequired: true, - run: async (e: EnvironmentInfo) => { + run: async (e: FlipperDoctor.EnvironmentInfo) => { const hasProblem = !e.SDKs['iOS SDK'] || !e.SDKs['iOS SDK'].Platforms || @@ -208,7 +158,7 @@ export function getHealthchecks(): Healthchecks { key: 'ios.xcode', label: 'XCode Installed', isRequired: true, - run: async (e: EnvironmentInfo) => { + run: async (e: FlipperDoctor.EnvironmentInfo) => { const hasProblem = e.IDEs == null || e.IDEs.Xcode == null; const message = hasProblem ? 'Xcode (https://developer.apple.com/xcode/) is not installed.' @@ -223,7 +173,7 @@ export function getHealthchecks(): Healthchecks { key: 'ios.xcode-select', label: 'xcode-select set', isRequired: true, - run: async (_: EnvironmentInfo) => { + run: async (_: FlipperDoctor.EnvironmentInfo) => { const result = await tryExecuteCommand('xcode-select -p'); const hasProblem = result.hasProblem; const message = hasProblem @@ -239,7 +189,7 @@ export function getHealthchecks(): Healthchecks { key: 'ios.xctrace', label: 'xctrace exists', isRequired: true, - run: async (_: EnvironmentInfo) => { + run: async (_: FlipperDoctor.EnvironmentInfo) => { const result = await tryExecuteCommand( 'xcrun xctrace version', ); @@ -258,7 +208,7 @@ export function getHealthchecks(): Healthchecks { label: 'IDB installed', isRequired: false, run: async ( - _: EnvironmentInfo, + _: FlipperDoctor.EnvironmentInfo, settings?: {enablePhysicalIOS: boolean; idbPath: string}, ) => { if (!settings) { @@ -299,50 +249,48 @@ export function getHealthchecks(): Healthchecks { } export async function runHealthchecks(): Promise< - Array + Array > { const environmentInfo = await getEnvInfo(); - const healthchecks: Healthchecks = getHealthchecks(); - const results: Array = - await Promise.all( - Object.entries(healthchecks).map(async ([key, category]) => { - if (category.isSkipped) { - return category; - } - const categoryResult: CategoryResult = [ - key, - { - label: category.label, - results: await Promise.all( - category.healthchecks.map( - async ({key, label, run, isRequired}) => ({ - key, - label, - isRequired: isRequired ?? true, - result: await run(environmentInfo).catch((e) => { - console.warn( - `Health check ${key}/${label} failed with:`, - e, - ); - // TODO Improve result type to be: OK | Problem(message, fix...) - return { - hasProblem: true, - }; - }), + const healthchecks: FlipperDoctor.Healthchecks = getHealthchecks(); + const results: Array< + FlipperDoctor.CategoryResult | FlipperDoctor.SkippedHealthcheckCategory + > = await Promise.all( + Object.entries(healthchecks).map(async ([key, category]) => { + if (category.isSkipped) { + return category; + } + const categoryResult: FlipperDoctor.CategoryResult = [ + key, + { + label: category.label, + results: await Promise.all( + category.healthchecks.map( + async ({key, label, run, isRequired}) => ({ + key, + label, + isRequired: isRequired ?? true, + result: await run!(environmentInfo).catch((e) => { + console.warn(`Health check ${key}/${label} failed with:`, e); + // TODO Improve result type to be: OK | Problem(message, fix...) + return { + hasProblem: true, + }; }), - ), + }), ), - }, - ]; - return categoryResult; - }), - ); + ), + }, + ]; + return categoryResult; + }), + ); return results; } async function tryExecuteCommand( command: string, -): Promise { +): Promise { try { const output = await promisify(exec)(command); return { diff --git a/desktop/doctor/tsconfig.json b/desktop/doctor/tsconfig.json index 22dce4bac..4bb952b9f 100644 --- a/desktop/doctor/tsconfig.json +++ b/desktop/doctor/tsconfig.json @@ -3,5 +3,10 @@ "compilerOptions": { "outDir": "lib", "rootDir": "src" - } + }, + "references": [ + { + "path": "../flipper-common" + } + ] } diff --git a/desktop/flipper-common/src/doctor.tsx b/desktop/flipper-common/src/doctor.tsx new file mode 100644 index 000000000..5737308a9 --- /dev/null +++ b/desktop/flipper-common/src/doctor.tsx @@ -0,0 +1,126 @@ +/** + * 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 + */ + +export namespace FlipperDoctor { + export type EnvironmentInfo = { + SDKs: { + 'iOS SDK': { + Platforms: string[]; + }; + 'Android SDK': + | { + 'API Levels': string[] | 'Not Found'; + 'Build Tools': string[] | 'Not Found'; + 'System Images': string[] | 'Not Found'; + 'Android NDK': string | 'Not Found'; + } + | 'Not Found'; + }; + IDEs: { + Xcode: { + version: string; + path: string; + }; + }; + }; + + export type HealthcheckCategory = { + label: string; + isSkipped: false; + isRequired: boolean; + healthchecks: Healthcheck[]; + }; + + export type SkippedHealthcheckCategory = { + label: string; + isSkipped: true; + skipReason: string; + }; + + export type Healthchecks = { + common: HealthcheckCategory | SkippedHealthcheckCategory; + android: HealthcheckCategory | SkippedHealthcheckCategory; + ios: HealthcheckCategory | SkippedHealthcheckCategory; + }; + + export type Settings = { + idbPath: string; + enablePhysicalIOS: boolean; + }; + + export type Healthcheck = { + key: string; + label: string; + isRequired?: boolean; + run?: ( + env: EnvironmentInfo, + settings?: Settings, + ) => Promise; + }; + + export type HealthcheckRunResult = { + hasProblem: boolean; + message: string; + }; + + export type CategoryResult = [ + string, + { + label: string; + results: Array<{ + key: string; + label: string; + isRequired: boolean; + result: {hasProblem: boolean}; + }>; + }, + ]; + + export type Dictionary = {[key: string]: T}; + + export type HealthcheckStatus = + | 'IN_PROGRESS' + | 'SUCCESS' + | 'FAILED' + | 'SKIPPED' + | 'WARNING'; + + export type HealthcheckResult = { + status: HealthcheckStatus; + isAcknowledged?: boolean; + message?: string; + }; + + export type HealthcheckReportItem = { + key: string; + label: string; + result: HealthcheckResult; + }; + + export type HealthcheckReportCategory = { + key: string; + label: string; + result: HealthcheckResult; + checks: Dictionary; + }; + + export type HealthcheckReport = { + result: HealthcheckResult; + categories: Dictionary; + }; + + export type HealthcheckSettings = { + settings: { + enableAndroid: boolean; + enableIOS: boolean; + enablePhysicalIOS: boolean; + idbPath: string; + }; + }; +} diff --git a/desktop/flipper-common/src/index.tsx b/desktop/flipper-common/src/index.tsx index 6d13ee85f..b1da3dd81 100644 --- a/desktop/flipper-common/src/index.tsx +++ b/desktop/flipper-common/src/index.tsx @@ -47,3 +47,4 @@ export * from './GK'; export * from './clientUtils'; export * from './settings'; export * from './PluginDetails'; +export * from './doctor'; diff --git a/desktop/flipper-common/src/server-types.tsx b/desktop/flipper-common/src/server-types.tsx index 11a16be1d..e28467b97 100644 --- a/desktop/flipper-common/src/server-types.tsx +++ b/desktop/flipper-common/src/server-types.tsx @@ -7,6 +7,7 @@ * @format */ +import {FlipperDoctor} from './doctor'; import { BundledPluginDetails, DeviceSpec, @@ -180,6 +181,14 @@ export type FlipperServerCommands = { path: string, ) => Promise; 'plugins-remove-plugins': (names: string[]) => Promise; + 'doctor-get-healthchecks': ( + settings: FlipperDoctor.HealthcheckSettings, + ) => Promise; + 'doctor-run-healthcheck': ( + settings: FlipperDoctor.HealthcheckSettings, + category: keyof FlipperDoctor.Healthchecks, + name: string, + ) => Promise; }; /** diff --git a/desktop/flipper-server-core/package.json b/desktop/flipper-server-core/package.json index d37e723a9..cb38914c6 100644 --- a/desktop/flipper-server-core/package.json +++ b/desktop/flipper-server-core/package.json @@ -19,6 +19,7 @@ "async-mutex": "^0.3.2", "flipper-plugin-lib": "0.0.0", "flipper-common": "0.0.0", + "flipper-doctor": "0.0.0", "fs-extra": "^10.0.0", "invariant": "^2.2.4", "js-base64": "^3.7.2", diff --git a/desktop/flipper-server-core/src/FlipperServerImpl.tsx b/desktop/flipper-server-core/src/FlipperServerImpl.tsx index 59439bb4e..8ee9b61a0 100644 --- a/desktop/flipper-server-core/src/FlipperServerImpl.tsx +++ b/desktop/flipper-server-core/src/FlipperServerImpl.tsx @@ -35,6 +35,7 @@ import {saveSettings} from './utils/settings'; import {saveLauncherSettings} from './utils/launcherSettings'; import {KeytarManager} from './utils/keytar'; import {PluginManager} from './plugins/PluginManager'; +import {runHealthcheck, getHealthChecks} from './utils/runHealthchecks'; /** * FlipperServer takes care of all incoming device & client connections. @@ -269,6 +270,8 @@ export class FlipperServerImpl implements FlipperServer { 'plugins-install-from-npm': (name) => this.pluginManager.installPluginFromNpm(name), 'plugin-source': (path) => this.pluginManager.loadSource(path), + 'doctor-get-healthchecks': getHealthChecks, + 'doctor-run-healthcheck': runHealthcheck, }; registerDevice(device: ServerDevice) { diff --git a/desktop/flipper-server-core/src/utils/runHealthchecks.tsx b/desktop/flipper-server-core/src/utils/runHealthchecks.tsx new file mode 100644 index 000000000..945fe9c69 --- /dev/null +++ b/desktop/flipper-server-core/src/utils/runHealthchecks.tsx @@ -0,0 +1,76 @@ +/** + * 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 {getHealthchecks, getEnvInfo} from 'flipper-doctor'; +import {FlipperDoctor} from 'flipper-common'; +import produce from 'immer'; + +export async function getHealthChecks( + options: FlipperDoctor.HealthcheckSettings, +) { + return produce(getHealthchecks(), (healthchecks) => { + if (!options.settings.enableAndroid) { + healthchecks.android = { + label: healthchecks.android.label, + isSkipped: true, + skipReason: + 'Healthcheck is skipped, because "Android Development" option is disabled in the Flipper settings', + }; + } + if (!options.settings.enableIOS) { + healthchecks.ios = { + label: healthchecks.ios.label, + isSkipped: true, + skipReason: + 'Healthcheck is skipped, because "iOS Development" option is disabled in the Flipper settings', + }; + } + Object.keys(healthchecks).forEach((cat) => { + const category = healthchecks[cat as keyof typeof healthchecks]; + if ('healthchecks' in category) { + category.healthchecks.forEach((h) => { + delete h.run; + }); + } + }); + }); +} + +export async function runHealthcheck( + options: FlipperDoctor.HealthcheckSettings, + categoryName: keyof FlipperDoctor.Healthchecks, + ruleName: string, +): Promise { + const healthchecks = getHealthchecks(); + const category = healthchecks[categoryName]; + if (!category) { + throw new Error('Unknown category: ' + categoryName); + } + if (!('healthchecks' in category)) { + throw new Error('Skipped category: ' + categoryName); + } + const check = category.healthchecks.find((h) => h.key === ruleName); + if (!check) { + throw new Error('Unknown healthcheck: ' + ruleName); + } + + const environmentInfo = await getEnvInfo(); + const checkResult = await check.run!(environmentInfo, options.settings); + return checkResult.hasProblem && check.isRequired + ? { + status: 'FAILED', + message: checkResult.message, + } + : checkResult.hasProblem && !check.isRequired + ? { + status: 'WARNING', + message: checkResult.message, + } + : {status: 'SUCCESS', message: checkResult.message}; +} diff --git a/desktop/flipper-server-core/tsconfig.json b/desktop/flipper-server-core/tsconfig.json index 8468e4285..015b69582 100644 --- a/desktop/flipper-server-core/tsconfig.json +++ b/desktop/flipper-server-core/tsconfig.json @@ -5,6 +5,9 @@ "rootDir": "src" }, "references": [ + { + "path": "../doctor" + }, { "path": "../flipper-common" }, diff --git a/desktop/flipper-ui-core/package.json b/desktop/flipper-ui-core/package.json index 974b40eac..de9416d5b 100644 --- a/desktop/flipper-ui-core/package.json +++ b/desktop/flipper-ui-core/package.json @@ -31,7 +31,6 @@ "expand-tilde": "^2.0.2", "flipper-client-sdk": "^0.0.3", "flipper-common": "0.0.0", - "flipper-doctor": "0.0.0", "flipper-plugin": "0.0.0", "flipper-ui-core": "0.0.0", "fs-extra": "^10.0.0", diff --git a/desktop/flipper-ui-core/src/chrome/DoctorSheet.tsx b/desktop/flipper-ui-core/src/chrome/DoctorSheet.tsx index cad0a4c36..5535c126b 100644 --- a/desktop/flipper-ui-core/src/chrome/DoctorSheet.tsx +++ b/desktop/flipper-ui-core/src/chrome/DoctorSheet.tsx @@ -24,9 +24,6 @@ import React, {Component} from 'react'; import {connect} from 'react-redux'; import {State as Store} from '../reducers'; import { - HealthcheckResult, - HealthcheckReportCategory, - HealthcheckReport, startHealthchecks, finishHealthchecks, updateHealthcheckResult, @@ -38,10 +35,10 @@ import runHealthchecks, { HealthcheckEventsHandler, } from '../utils/runHealthchecks'; import {getFlipperLib} from 'flipper-plugin'; -import {reportUsage} from 'flipper-common'; +import {reportUsage, FlipperDoctor} from 'flipper-common'; type StateFromProps = { - healthcheckReport: HealthcheckReport; + healthcheckReport: FlipperDoctor.HealthcheckReport; } & HealthcheckSettings; type DispatchFromProps = { @@ -123,7 +120,9 @@ function CenteredCheckbox(props: { ); } -function HealthcheckIcon(props: {checkResult: HealthcheckResult}) { +function HealthcheckIcon(props: { + checkResult: FlipperDoctor.HealthcheckResult; +}) { const {checkResult: check} = props; switch (props.checkResult.status) { case 'IN_PROGRESS': @@ -170,7 +169,7 @@ function HealthcheckIcon(props: {checkResult: HealthcheckResult}) { function HealthcheckDisplay(props: { label: string; - result: HealthcheckResult; + result: FlipperDoctor.HealthcheckResult; selected?: boolean; onClick?: () => void; }) { @@ -194,7 +193,7 @@ function SideMessageDisplay(props: {children: React.ReactNode}) { return {props.children}; } -function ResultMessage(props: {result: HealthcheckResult}) { +function ResultMessage(props: {result: FlipperDoctor.HealthcheckResult}) { if (status === 'IN_PROGRESS') { return

Doctor is running healthchecks...

; } else if (hasProblems(props.result)) { @@ -213,12 +212,12 @@ function ResultMessage(props: {result: HealthcheckResult}) { } } -function hasProblems(result: HealthcheckResult) { +function hasProblems(result: FlipperDoctor.HealthcheckResult) { const {status} = result; return status === 'FAILED' || status === 'WARNING'; } -function hasNewProblems(result: HealthcheckResult) { +function hasNewProblems(result: FlipperDoctor.HealthcheckResult) { return hasProblems(result) && !result.isAcknowledged; } @@ -321,7 +320,7 @@ class DoctorSheet extends Component { {Object.values(this.props.healthcheckReport.categories).map( - (category: HealthcheckReportCategory) => { + (category: FlipperDoctor.HealthcheckReportCategory) => { return ( { + run: async (_env: FlipperDoctor.EnvironmentInfo) => { return {hasProblem: false, message: ''}; }, }, @@ -39,7 +39,7 @@ const HEALTHCHECKS: Healthchecks = { { key: 'android.sdk', label: 'SDK Installed', - run: async (_env: EnvironmentInfo) => { + run: async (_env: FlipperDoctor.EnvironmentInfo) => { return {hasProblem: true, message: 'Error'}; }, }, @@ -53,7 +53,7 @@ const HEALTHCHECKS: Healthchecks = { { key: 'common.openssl', label: 'OpenSSL Istalled', - run: async (_env: EnvironmentInfo) => { + run: async (_env: FlipperDoctor.EnvironmentInfo) => { return {hasProblem: false, message: ''}; }, }, diff --git a/desktop/flipper-ui-core/src/reducers/healthchecks.tsx b/desktop/flipper-ui-core/src/reducers/healthchecks.tsx index 8947d56a9..3d1445afa 100644 --- a/desktop/flipper-ui-core/src/reducers/healthchecks.tsx +++ b/desktop/flipper-ui-core/src/reducers/healthchecks.tsx @@ -9,17 +9,17 @@ import {Actions} from './'; import {produce} from 'immer'; -import {Healthchecks} from 'flipper-doctor'; +import type {FlipperDoctor} from 'flipper-common'; export type State = { - healthcheckReport: HealthcheckReport; + healthcheckReport: FlipperDoctor.HealthcheckReport; acknowledgedProblems: string[]; }; export type Action = | { type: 'START_HEALTHCHECKS'; - payload: Healthchecks; + payload: FlipperDoctor.Healthchecks; } | { type: 'FINISH_HEALTHCHECKS'; @@ -29,7 +29,7 @@ export type Action = payload: { categoryKey: string; itemKey: string; - result: HealthcheckResult; + result: FlipperDoctor.HealthcheckResult; }; } | { @@ -47,39 +47,6 @@ const INITIAL_STATE: State = { acknowledgedProblems: [], }; -type Dictionary = {[key: string]: T}; - -export type HealthcheckStatus = - | 'IN_PROGRESS' - | 'SUCCESS' - | 'FAILED' - | 'SKIPPED' - | 'WARNING'; - -export type HealthcheckResult = { - status: HealthcheckStatus; - isAcknowledged?: boolean; - message?: string; -}; - -export type HealthcheckReportItem = { - key: string; - label: string; - result: HealthcheckResult; -}; - -export type HealthcheckReportCategory = { - key: string; - label: string; - result: HealthcheckResult; - checks: Dictionary; -}; - -export type HealthcheckReport = { - result: HealthcheckResult; - categories: Dictionary; -}; - function recomputeHealthcheckStatus(draft: State): void { draft.healthcheckReport.result = computeAggregatedResult( Object.values(draft.healthcheckReport.categories).map((c) => c.result), @@ -87,8 +54,8 @@ function recomputeHealthcheckStatus(draft: State): void { } function computeAggregatedResult( - results: HealthcheckResult[], -): HealthcheckResult { + results: FlipperDoctor.HealthcheckResult[], +): FlipperDoctor.HealthcheckResult { return results.some((r) => r.status === 'IN_PROGRESS') ? {status: 'IN_PROGRESS'} : results.every((r) => r.status === 'SUCCESS') @@ -114,7 +81,7 @@ const updateCheckResult = produce( }: { categoryKey: string; itemKey: string; - result: HealthcheckResult; + result: FlipperDoctor.HealthcheckResult; }, ) => { const category = draft.healthcheckReport.categories[categoryKey]; @@ -124,55 +91,57 @@ const updateCheckResult = produce( }, ); -function createDict(pairs: [string, T][]): Dictionary { - const obj: Dictionary = {}; +function createDict(pairs: [string, T][]): FlipperDoctor.Dictionary { + const obj: FlipperDoctor.Dictionary = {}; for (const pair of pairs) { obj[pair[0]] = pair[1]; } return obj; } -const start = produce((draft: State, healthchecks: Healthchecks) => { - draft.healthcheckReport = { - result: {status: 'IN_PROGRESS'}, - categories: createDict( - Object.entries(healthchecks).map(([categoryKey, category]) => { - if (category.isSkipped) { +const start = produce( + (draft: State, healthchecks: FlipperDoctor.Healthchecks) => { + draft.healthcheckReport = { + result: {status: 'IN_PROGRESS'}, + categories: createDict( + Object.entries(healthchecks).map(([categoryKey, category]) => { + if (category.isSkipped) { + return [ + categoryKey, + { + key: categoryKey, + result: { + status: 'SKIPPED', + message: category.skipReason, + }, + label: category.label, + checks: createDict([]), + }, + ]; + } return [ categoryKey, { key: categoryKey, - result: { - status: 'SKIPPED', - message: category.skipReason, - }, + result: {status: 'IN_PROGRESS'}, label: category.label, - checks: createDict([]), + checks: createDict( + category.healthchecks.map((check) => [ + check.key, + { + key: check.key, + result: {status: 'IN_PROGRESS'}, + label: check.label, + }, + ]), + ), }, ]; - } - return [ - categoryKey, - { - key: categoryKey, - result: {status: 'IN_PROGRESS'}, - label: category.label, - checks: createDict( - category.healthchecks.map((check) => [ - check.key, - { - key: check.key, - result: {status: 'IN_PROGRESS'}, - label: check.label, - }, - ]), - ), - }, - ]; - }), - ), - }; -}); + }), + ), + }; + }, +); const finish = produce((draft: State) => { Object.values(draft.healthcheckReport.categories) @@ -244,7 +213,7 @@ export default function reducer( export const updateHealthcheckResult = ( categoryKey: string, itemKey: string, - result: HealthcheckResult, + result: FlipperDoctor.HealthcheckResult, ): Action => ({ type: 'UPDATE_HEALTHCHECK_RESULT', payload: { @@ -254,7 +223,9 @@ export const updateHealthcheckResult = ( }, }); -export const startHealthchecks = (healthchecks: Healthchecks): Action => ({ +export const startHealthchecks = ( + healthchecks: FlipperDoctor.Healthchecks, +): Action => ({ type: 'START_HEALTHCHECKS', payload: healthchecks, }); diff --git a/desktop/flipper-ui-core/src/sandy-chrome/SetupDoctorScreen.tsx b/desktop/flipper-ui-core/src/sandy-chrome/SetupDoctorScreen.tsx index 5a985ef58..85f66d647 100644 --- a/desktop/flipper-ui-core/src/sandy-chrome/SetupDoctorScreen.tsx +++ b/desktop/flipper-ui-core/src/sandy-chrome/SetupDoctorScreen.tsx @@ -18,12 +18,6 @@ import { LoadingOutlined, } from '@ant-design/icons'; import {Layout} from '../ui'; -import { - HealthcheckReport, - HealthcheckReportItem, - HealthcheckStatus, - HealthcheckResult, -} from '../reducers/healthchecks'; import {theme} from 'flipper-plugin'; import { startHealthchecks, @@ -33,13 +27,14 @@ import { resetAcknowledgedProblems, } from '../reducers/healthchecks'; import runHealthchecks from '../utils/runHealthchecks'; -import {Healthchecks} from 'flipper-doctor'; +import type {FlipperDoctor} from 'flipper-common'; +type Healthchecks = FlipperDoctor.Healthchecks; import {reportUsage} from 'flipper-common'; const {Title, Paragraph, Text} = Typography; const statusTypeAndMessage: { - [key in HealthcheckStatus]: { + [key in FlipperDoctor.HealthcheckStatus]: { type: 'success' | 'info' | 'warning' | 'error'; message: string; }; @@ -67,15 +62,15 @@ const statusTypeAndMessage: { }, }; -function checkHasProblem(result: HealthcheckResult) { +function checkHasProblem(result: FlipperDoctor.HealthcheckResult) { return result.status === 'FAILED' || result.status === 'WARNING'; } -export function checkHasNewProblem(result: HealthcheckResult) { +export function checkHasNewProblem(result: FlipperDoctor.HealthcheckResult) { return checkHasProblem(result) && !result.isAcknowledged; } -function ResultTopDialog(props: {status: HealthcheckStatus}) { +function ResultTopDialog(props: {status: FlipperDoctor.HealthcheckStatus}) { const messages = statusTypeAndMessage[props.status]; return ( }) { +function CollapsableCategory(props: { + checks: Array; +}) { return ( {props.checks.map((check) => ( @@ -134,7 +131,7 @@ function CollapsableCategory(props: {checks: Array}) { ); } -function HealthCheckList(props: {report: HealthcheckReport}) { +function HealthCheckList(props: {report: FlipperDoctor.HealthcheckReport}) { useEffect(() => reportUsage('doctor:report:opened'), []); return ( @@ -241,7 +238,7 @@ export default function SetupDoctorScreen(props: { updateHealthcheckResult: ( categoryKey: string, itemKey: string, - result: HealthcheckResult, + result: FlipperDoctor.HealthcheckResult, ) => dispatch(updateHealthcheckResult(categoryKey, itemKey, result)), finishHealthchecks: () => dispatch(finishHealthchecks()), }); diff --git a/desktop/flipper-ui-core/src/utils/runHealthchecks.tsx b/desktop/flipper-ui-core/src/utils/runHealthchecks.tsx index d568c343f..34b3f9420 100644 --- a/desktop/flipper-ui-core/src/utils/runHealthchecks.tsx +++ b/desktop/flipper-ui-core/src/utils/runHealthchecks.tsx @@ -7,9 +7,12 @@ * @format */ -import {HealthcheckResult} from '../reducers/healthchecks'; -import {getHealthchecks, getEnvInfo, Healthchecks} from 'flipper-doctor'; -import {logPlatformSuccessRate, reportPlatformFailures} from 'flipper-common'; +import { + logPlatformSuccessRate, + reportPlatformFailures, + FlipperDoctor, +} from 'flipper-common'; +import {getRenderHostInstance} from '../RenderHost'; let healthcheckIsRunning: boolean; let runningHealthcheck: Promise; @@ -18,9 +21,9 @@ export type HealthcheckEventsHandler = { updateHealthcheckResult: ( categoryKey: string, itemKey: string, - result: HealthcheckResult, + result: FlipperDoctor.HealthcheckResult, ) => void; - startHealthchecks: (healthchecks: Healthchecks) => void; + startHealthchecks: (healthchecks: FlipperDoctor.Healthchecks) => void; finishHealthchecks: () => void; }; @@ -36,34 +39,25 @@ export type HealthcheckSettings = { export type HealthcheckOptions = HealthcheckEventsHandler & HealthcheckSettings; async function launchHealthchecks(options: HealthcheckOptions): Promise { - const healthchecks = getHealthchecks(); - if (!options.settings.enableAndroid) { - healthchecks.android = { - label: healthchecks.android.label, - isSkipped: true, - skipReason: - 'Healthcheck is skipped, because "Android Development" option is disabled in the Flipper settings', - }; - } - if (!options.settings.enableIOS) { - healthchecks.ios = { - label: healthchecks.ios.label, - isSkipped: true, - skipReason: - 'Healthcheck is skipped, because "iOS Development" option is disabled in the Flipper settings', - }; - } + const {flipperServer} = getRenderHostInstance(); + const healthchecks = await flipperServer.exec('doctor-get-healthchecks', { + settings: options.settings, + }); options.startHealthchecks(healthchecks); - const environmentInfo = await getEnvInfo(); let hasProblems = false; for (const [categoryKey, category] of Object.entries(healthchecks)) { if (category.isSkipped) { continue; } for (const h of category.healthchecks) { - const checkResult = await h.run(environmentInfo, options.settings); + const checkResult = await flipperServer.exec( + 'doctor-run-healthcheck', + {settings: options.settings}, + categoryKey as keyof FlipperDoctor.Healthchecks, + h.key, + ); const metricName = `doctor:${h.key.replace('.', ':')}.healthcheck`; // e.g. "doctor:ios:xcode-select.healthcheck" - if (checkResult.hasProblem) { + if (checkResult.status !== 'SUCCESS') { hasProblems = true; logPlatformSuccessRate(metricName, { kind: 'failure', @@ -75,19 +69,7 @@ async function launchHealthchecks(options: HealthcheckOptions): Promise { kind: 'success', }); } - const result: HealthcheckResult = - checkResult.hasProblem && h.isRequired - ? { - status: 'FAILED', - message: checkResult.message, - } - : checkResult.hasProblem && !h.isRequired - ? { - status: 'WARNING', - message: checkResult.message, - } - : {status: 'SUCCESS', message: checkResult.message}; - options.updateHealthcheckResult(categoryKey, h.key, result); + options.updateHealthcheckResult(categoryKey, h.key, checkResult); } } options.finishHealthchecks(); diff --git a/desktop/flipper-ui-core/tsconfig.json b/desktop/flipper-ui-core/tsconfig.json index 00bb977a9..94364d8e1 100644 --- a/desktop/flipper-ui-core/tsconfig.json +++ b/desktop/flipper-ui-core/tsconfig.json @@ -7,9 +7,6 @@ "emitDeclarationOnly": true }, "references": [ - { - "path": "../doctor" - }, { "path": "../flipper-common" },