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
This commit is contained in:
committed by
Facebook GitHub Bot
parent
2a4fe77404
commit
2480ed30c5
@@ -16,6 +16,7 @@
|
|||||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"eslint-plugin-react": "^7.27.1",
|
"eslint-plugin-react": "^7.27.1",
|
||||||
|
"flipper-common": "0.0.0",
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"prettier": "^2.4.1",
|
"prettier": "^2.4.1",
|
||||||
"ts-jest": "^26.5.6",
|
"ts-jest": "^26.5.6",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import {getEnvInfo} from './environmentInfo';
|
|||||||
category.healthchecks.map(async ({key, label, run}) => ({
|
category.healthchecks.map(async ({key, label, run}) => ({
|
||||||
key,
|
key,
|
||||||
label,
|
label,
|
||||||
result: await run(environmentInfo),
|
result: await run!(environmentInfo),
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,28 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {run} from 'envinfo';
|
import {run} from 'envinfo';
|
||||||
|
import {FlipperDoctor} from 'flipper-common';
|
||||||
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;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
async function retrieveAndParseEnvInfo(): Promise<any> {
|
async function retrieveAndParseEnvInfo(): Promise<any> {
|
||||||
return JSON.parse(
|
return JSON.parse(
|
||||||
@@ -43,6 +22,6 @@ async function retrieveAndParseEnvInfo(): Promise<any> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getEnvInfo(): Promise<EnvironmentInfo> {
|
export async function getEnvInfo(): Promise<FlipperDoctor.EnvironmentInfo> {
|
||||||
return await retrieveAndParseEnvInfo();
|
return await retrieveAndParseEnvInfo();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,65 +9,15 @@
|
|||||||
|
|
||||||
import {exec} from 'child_process';
|
import {exec} from 'child_process';
|
||||||
import {promisify} from 'util';
|
import {promisify} from 'util';
|
||||||
import {EnvironmentInfo, getEnvInfo} from './environmentInfo';
|
import {getEnvInfo} from './environmentInfo';
|
||||||
export {EnvironmentInfo, getEnvInfo} from './environmentInfo';
|
export {getEnvInfo} from './environmentInfo';
|
||||||
|
|
||||||
import * as watchman from 'fb-watchman';
|
import * as watchman from 'fb-watchman';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import {FlipperDoctor} from 'flipper-common';
|
||||||
|
|
||||||
export type HealthcheckCategory = {
|
export function getHealthchecks(): FlipperDoctor.Healthchecks {
|
||||||
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<HealthcheckRunResult>;
|
|
||||||
};
|
|
||||||
|
|
||||||
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 {
|
|
||||||
return {
|
return {
|
||||||
common: {
|
common: {
|
||||||
label: 'Common',
|
label: 'Common',
|
||||||
@@ -77,7 +27,7 @@ export function getHealthchecks(): Healthchecks {
|
|||||||
{
|
{
|
||||||
key: 'common.openssl',
|
key: 'common.openssl',
|
||||||
label: 'OpenSSL Installed',
|
label: 'OpenSSL Installed',
|
||||||
run: async (_: EnvironmentInfo) => {
|
run: async (_: FlipperDoctor.EnvironmentInfo) => {
|
||||||
const result = await tryExecuteCommand('openssl version');
|
const result = await tryExecuteCommand('openssl version');
|
||||||
const hasProblem = result.hasProblem;
|
const hasProblem = result.hasProblem;
|
||||||
const message = hasProblem
|
const message = hasProblem
|
||||||
@@ -92,7 +42,7 @@ export function getHealthchecks(): Healthchecks {
|
|||||||
{
|
{
|
||||||
key: 'common.watchman',
|
key: 'common.watchman',
|
||||||
label: 'Watchman Installed',
|
label: 'Watchman Installed',
|
||||||
run: async (_: EnvironmentInfo) => {
|
run: async (_: FlipperDoctor.EnvironmentInfo) => {
|
||||||
const isAvailable = await isWatchmanAvailable();
|
const isAvailable = await isWatchmanAvailable();
|
||||||
return {
|
return {
|
||||||
hasProblem: !isAvailable,
|
hasProblem: !isAvailable,
|
||||||
@@ -113,11 +63,11 @@ export function getHealthchecks(): Healthchecks {
|
|||||||
key: 'android.sdk',
|
key: 'android.sdk',
|
||||||
label: 'SDK Installed',
|
label: 'SDK Installed',
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
run: async (_: EnvironmentInfo) => {
|
run: async (_: FlipperDoctor.EnvironmentInfo) => {
|
||||||
const androidHome = process.env.ANDROID_HOME;
|
const androidHome = process.env.ANDROID_HOME;
|
||||||
const androidSdkRoot = process.env.ANDROID_SDK_ROOT;
|
const androidSdkRoot = process.env.ANDROID_SDK_ROOT;
|
||||||
|
|
||||||
let androidHomeResult: HealthcheckRunResult;
|
let androidHomeResult: FlipperDoctor.HealthcheckRunResult;
|
||||||
if (!androidHome) {
|
if (!androidHome) {
|
||||||
androidHomeResult = {
|
androidHomeResult = {
|
||||||
hasProblem: true,
|
hasProblem: true,
|
||||||
@@ -145,7 +95,7 @@ export function getHealthchecks(): Healthchecks {
|
|||||||
return androidHomeResult;
|
return androidHomeResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
let androidSdkRootResult: HealthcheckRunResult;
|
let androidSdkRootResult: FlipperDoctor.HealthcheckRunResult;
|
||||||
if (!androidSdkRoot) {
|
if (!androidSdkRoot) {
|
||||||
androidSdkRootResult = {
|
androidSdkRootResult = {
|
||||||
hasProblem: true,
|
hasProblem: true,
|
||||||
@@ -188,7 +138,7 @@ export function getHealthchecks(): Healthchecks {
|
|||||||
key: 'ios.sdk',
|
key: 'ios.sdk',
|
||||||
label: 'SDK Installed',
|
label: 'SDK Installed',
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
run: async (e: EnvironmentInfo) => {
|
run: async (e: FlipperDoctor.EnvironmentInfo) => {
|
||||||
const hasProblem =
|
const hasProblem =
|
||||||
!e.SDKs['iOS SDK'] ||
|
!e.SDKs['iOS SDK'] ||
|
||||||
!e.SDKs['iOS SDK'].Platforms ||
|
!e.SDKs['iOS SDK'].Platforms ||
|
||||||
@@ -208,7 +158,7 @@ export function getHealthchecks(): Healthchecks {
|
|||||||
key: 'ios.xcode',
|
key: 'ios.xcode',
|
||||||
label: 'XCode Installed',
|
label: 'XCode Installed',
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
run: async (e: EnvironmentInfo) => {
|
run: async (e: FlipperDoctor.EnvironmentInfo) => {
|
||||||
const hasProblem = e.IDEs == null || e.IDEs.Xcode == null;
|
const hasProblem = e.IDEs == null || e.IDEs.Xcode == null;
|
||||||
const message = hasProblem
|
const message = hasProblem
|
||||||
? 'Xcode (https://developer.apple.com/xcode/) is not installed.'
|
? 'Xcode (https://developer.apple.com/xcode/) is not installed.'
|
||||||
@@ -223,7 +173,7 @@ export function getHealthchecks(): Healthchecks {
|
|||||||
key: 'ios.xcode-select',
|
key: 'ios.xcode-select',
|
||||||
label: 'xcode-select set',
|
label: 'xcode-select set',
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
run: async (_: EnvironmentInfo) => {
|
run: async (_: FlipperDoctor.EnvironmentInfo) => {
|
||||||
const result = await tryExecuteCommand('xcode-select -p');
|
const result = await tryExecuteCommand('xcode-select -p');
|
||||||
const hasProblem = result.hasProblem;
|
const hasProblem = result.hasProblem;
|
||||||
const message = hasProblem
|
const message = hasProblem
|
||||||
@@ -239,7 +189,7 @@ export function getHealthchecks(): Healthchecks {
|
|||||||
key: 'ios.xctrace',
|
key: 'ios.xctrace',
|
||||||
label: 'xctrace exists',
|
label: 'xctrace exists',
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
run: async (_: EnvironmentInfo) => {
|
run: async (_: FlipperDoctor.EnvironmentInfo) => {
|
||||||
const result = await tryExecuteCommand(
|
const result = await tryExecuteCommand(
|
||||||
'xcrun xctrace version',
|
'xcrun xctrace version',
|
||||||
);
|
);
|
||||||
@@ -258,7 +208,7 @@ export function getHealthchecks(): Healthchecks {
|
|||||||
label: 'IDB installed',
|
label: 'IDB installed',
|
||||||
isRequired: false,
|
isRequired: false,
|
||||||
run: async (
|
run: async (
|
||||||
_: EnvironmentInfo,
|
_: FlipperDoctor.EnvironmentInfo,
|
||||||
settings?: {enablePhysicalIOS: boolean; idbPath: string},
|
settings?: {enablePhysicalIOS: boolean; idbPath: string},
|
||||||
) => {
|
) => {
|
||||||
if (!settings) {
|
if (!settings) {
|
||||||
@@ -299,50 +249,48 @@ export function getHealthchecks(): Healthchecks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function runHealthchecks(): Promise<
|
export async function runHealthchecks(): Promise<
|
||||||
Array<CategoryResult | SkippedHealthcheckCategory>
|
Array<FlipperDoctor.CategoryResult | FlipperDoctor.SkippedHealthcheckCategory>
|
||||||
> {
|
> {
|
||||||
const environmentInfo = await getEnvInfo();
|
const environmentInfo = await getEnvInfo();
|
||||||
const healthchecks: Healthchecks = getHealthchecks();
|
const healthchecks: FlipperDoctor.Healthchecks = getHealthchecks();
|
||||||
const results: Array<CategoryResult | SkippedHealthcheckCategory> =
|
const results: Array<
|
||||||
await Promise.all(
|
FlipperDoctor.CategoryResult | FlipperDoctor.SkippedHealthcheckCategory
|
||||||
Object.entries(healthchecks).map(async ([key, category]) => {
|
> = await Promise.all(
|
||||||
if (category.isSkipped) {
|
Object.entries(healthchecks).map(async ([key, category]) => {
|
||||||
return category;
|
if (category.isSkipped) {
|
||||||
}
|
return category;
|
||||||
const categoryResult: CategoryResult = [
|
}
|
||||||
key,
|
const categoryResult: FlipperDoctor.CategoryResult = [
|
||||||
{
|
key,
|
||||||
label: category.label,
|
{
|
||||||
results: await Promise.all(
|
label: category.label,
|
||||||
category.healthchecks.map(
|
results: await Promise.all(
|
||||||
async ({key, label, run, isRequired}) => ({
|
category.healthchecks.map(
|
||||||
key,
|
async ({key, label, run, isRequired}) => ({
|
||||||
label,
|
key,
|
||||||
isRequired: isRequired ?? true,
|
label,
|
||||||
result: await run(environmentInfo).catch((e) => {
|
isRequired: isRequired ?? true,
|
||||||
console.warn(
|
result: await run!(environmentInfo).catch((e) => {
|
||||||
`Health check ${key}/${label} failed with:`,
|
console.warn(`Health check ${key}/${label} failed with:`, e);
|
||||||
e,
|
// TODO Improve result type to be: OK | Problem(message, fix...)
|
||||||
);
|
return {
|
||||||
// TODO Improve result type to be: OK | Problem(message, fix...)
|
hasProblem: true,
|
||||||
return {
|
};
|
||||||
hasProblem: true,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
),
|
}),
|
||||||
),
|
),
|
||||||
},
|
),
|
||||||
];
|
},
|
||||||
return categoryResult;
|
];
|
||||||
}),
|
return categoryResult;
|
||||||
);
|
}),
|
||||||
|
);
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function tryExecuteCommand(
|
async function tryExecuteCommand(
|
||||||
command: string,
|
command: string,
|
||||||
): Promise<HealthcheckRunResult> {
|
): Promise<FlipperDoctor.HealthcheckRunResult> {
|
||||||
try {
|
try {
|
||||||
const output = await promisify(exec)(command);
|
const output = await promisify(exec)(command);
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -3,5 +3,10 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "lib",
|
"outDir": "lib",
|
||||||
"rootDir": "src"
|
"rootDir": "src"
|
||||||
}
|
},
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "../flipper-common"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
126
desktop/flipper-common/src/doctor.tsx
Normal file
126
desktop/flipper-common/src/doctor.tsx
Normal file
@@ -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<HealthcheckRunResult>;
|
||||||
|
};
|
||||||
|
|
||||||
|
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<T> = {[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<HealthcheckReportItem>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HealthcheckReport = {
|
||||||
|
result: HealthcheckResult;
|
||||||
|
categories: Dictionary<HealthcheckReportCategory>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HealthcheckSettings = {
|
||||||
|
settings: {
|
||||||
|
enableAndroid: boolean;
|
||||||
|
enableIOS: boolean;
|
||||||
|
enablePhysicalIOS: boolean;
|
||||||
|
idbPath: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -47,3 +47,4 @@ export * from './GK';
|
|||||||
export * from './clientUtils';
|
export * from './clientUtils';
|
||||||
export * from './settings';
|
export * from './settings';
|
||||||
export * from './PluginDetails';
|
export * from './PluginDetails';
|
||||||
|
export * from './doctor';
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {FlipperDoctor} from './doctor';
|
||||||
import {
|
import {
|
||||||
BundledPluginDetails,
|
BundledPluginDetails,
|
||||||
DeviceSpec,
|
DeviceSpec,
|
||||||
@@ -180,6 +181,14 @@ export type FlipperServerCommands = {
|
|||||||
path: string,
|
path: string,
|
||||||
) => Promise<InstalledPluginDetails>;
|
) => Promise<InstalledPluginDetails>;
|
||||||
'plugins-remove-plugins': (names: string[]) => Promise<void>;
|
'plugins-remove-plugins': (names: string[]) => Promise<void>;
|
||||||
|
'doctor-get-healthchecks': (
|
||||||
|
settings: FlipperDoctor.HealthcheckSettings,
|
||||||
|
) => Promise<FlipperDoctor.Healthchecks>;
|
||||||
|
'doctor-run-healthcheck': (
|
||||||
|
settings: FlipperDoctor.HealthcheckSettings,
|
||||||
|
category: keyof FlipperDoctor.Healthchecks,
|
||||||
|
name: string,
|
||||||
|
) => Promise<FlipperDoctor.HealthcheckResult>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
"async-mutex": "^0.3.2",
|
"async-mutex": "^0.3.2",
|
||||||
"flipper-plugin-lib": "0.0.0",
|
"flipper-plugin-lib": "0.0.0",
|
||||||
"flipper-common": "0.0.0",
|
"flipper-common": "0.0.0",
|
||||||
|
"flipper-doctor": "0.0.0",
|
||||||
"fs-extra": "^10.0.0",
|
"fs-extra": "^10.0.0",
|
||||||
"invariant": "^2.2.4",
|
"invariant": "^2.2.4",
|
||||||
"js-base64": "^3.7.2",
|
"js-base64": "^3.7.2",
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import {saveSettings} from './utils/settings';
|
|||||||
import {saveLauncherSettings} from './utils/launcherSettings';
|
import {saveLauncherSettings} from './utils/launcherSettings';
|
||||||
import {KeytarManager} from './utils/keytar';
|
import {KeytarManager} from './utils/keytar';
|
||||||
import {PluginManager} from './plugins/PluginManager';
|
import {PluginManager} from './plugins/PluginManager';
|
||||||
|
import {runHealthcheck, getHealthChecks} from './utils/runHealthchecks';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FlipperServer takes care of all incoming device & client connections.
|
* FlipperServer takes care of all incoming device & client connections.
|
||||||
@@ -269,6 +270,8 @@ export class FlipperServerImpl implements FlipperServer {
|
|||||||
'plugins-install-from-npm': (name) =>
|
'plugins-install-from-npm': (name) =>
|
||||||
this.pluginManager.installPluginFromNpm(name),
|
this.pluginManager.installPluginFromNpm(name),
|
||||||
'plugin-source': (path) => this.pluginManager.loadSource(path),
|
'plugin-source': (path) => this.pluginManager.loadSource(path),
|
||||||
|
'doctor-get-healthchecks': getHealthChecks,
|
||||||
|
'doctor-run-healthcheck': runHealthcheck,
|
||||||
};
|
};
|
||||||
|
|
||||||
registerDevice(device: ServerDevice) {
|
registerDevice(device: ServerDevice) {
|
||||||
|
|||||||
76
desktop/flipper-server-core/src/utils/runHealthchecks.tsx
Normal file
76
desktop/flipper-server-core/src/utils/runHealthchecks.tsx
Normal file
@@ -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<FlipperDoctor.HealthcheckResult> {
|
||||||
|
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};
|
||||||
|
}
|
||||||
@@ -5,6 +5,9 @@
|
|||||||
"rootDir": "src"
|
"rootDir": "src"
|
||||||
},
|
},
|
||||||
"references": [
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "../doctor"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "../flipper-common"
|
"path": "../flipper-common"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -31,7 +31,6 @@
|
|||||||
"expand-tilde": "^2.0.2",
|
"expand-tilde": "^2.0.2",
|
||||||
"flipper-client-sdk": "^0.0.3",
|
"flipper-client-sdk": "^0.0.3",
|
||||||
"flipper-common": "0.0.0",
|
"flipper-common": "0.0.0",
|
||||||
"flipper-doctor": "0.0.0",
|
|
||||||
"flipper-plugin": "0.0.0",
|
"flipper-plugin": "0.0.0",
|
||||||
"flipper-ui-core": "0.0.0",
|
"flipper-ui-core": "0.0.0",
|
||||||
"fs-extra": "^10.0.0",
|
"fs-extra": "^10.0.0",
|
||||||
|
|||||||
@@ -24,9 +24,6 @@ import React, {Component} from 'react';
|
|||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import {State as Store} from '../reducers';
|
import {State as Store} from '../reducers';
|
||||||
import {
|
import {
|
||||||
HealthcheckResult,
|
|
||||||
HealthcheckReportCategory,
|
|
||||||
HealthcheckReport,
|
|
||||||
startHealthchecks,
|
startHealthchecks,
|
||||||
finishHealthchecks,
|
finishHealthchecks,
|
||||||
updateHealthcheckResult,
|
updateHealthcheckResult,
|
||||||
@@ -38,10 +35,10 @@ import runHealthchecks, {
|
|||||||
HealthcheckEventsHandler,
|
HealthcheckEventsHandler,
|
||||||
} from '../utils/runHealthchecks';
|
} from '../utils/runHealthchecks';
|
||||||
import {getFlipperLib} from 'flipper-plugin';
|
import {getFlipperLib} from 'flipper-plugin';
|
||||||
import {reportUsage} from 'flipper-common';
|
import {reportUsage, FlipperDoctor} from 'flipper-common';
|
||||||
|
|
||||||
type StateFromProps = {
|
type StateFromProps = {
|
||||||
healthcheckReport: HealthcheckReport;
|
healthcheckReport: FlipperDoctor.HealthcheckReport;
|
||||||
} & HealthcheckSettings;
|
} & HealthcheckSettings;
|
||||||
|
|
||||||
type DispatchFromProps = {
|
type DispatchFromProps = {
|
||||||
@@ -123,7 +120,9 @@ function CenteredCheckbox(props: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function HealthcheckIcon(props: {checkResult: HealthcheckResult}) {
|
function HealthcheckIcon(props: {
|
||||||
|
checkResult: FlipperDoctor.HealthcheckResult;
|
||||||
|
}) {
|
||||||
const {checkResult: check} = props;
|
const {checkResult: check} = props;
|
||||||
switch (props.checkResult.status) {
|
switch (props.checkResult.status) {
|
||||||
case 'IN_PROGRESS':
|
case 'IN_PROGRESS':
|
||||||
@@ -170,7 +169,7 @@ function HealthcheckIcon(props: {checkResult: HealthcheckResult}) {
|
|||||||
|
|
||||||
function HealthcheckDisplay(props: {
|
function HealthcheckDisplay(props: {
|
||||||
label: string;
|
label: string;
|
||||||
result: HealthcheckResult;
|
result: FlipperDoctor.HealthcheckResult;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}) {
|
}) {
|
||||||
@@ -194,7 +193,7 @@ function SideMessageDisplay(props: {children: React.ReactNode}) {
|
|||||||
return <SideContainerText selectable>{props.children}</SideContainerText>;
|
return <SideContainerText selectable>{props.children}</SideContainerText>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ResultMessage(props: {result: HealthcheckResult}) {
|
function ResultMessage(props: {result: FlipperDoctor.HealthcheckResult}) {
|
||||||
if (status === 'IN_PROGRESS') {
|
if (status === 'IN_PROGRESS') {
|
||||||
return <p>Doctor is running healthchecks...</p>;
|
return <p>Doctor is running healthchecks...</p>;
|
||||||
} else if (hasProblems(props.result)) {
|
} 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;
|
const {status} = result;
|
||||||
return status === 'FAILED' || status === 'WARNING';
|
return status === 'FAILED' || status === 'WARNING';
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasNewProblems(result: HealthcheckResult) {
|
function hasNewProblems(result: FlipperDoctor.HealthcheckResult) {
|
||||||
return hasProblems(result) && !result.isAcknowledged;
|
return hasProblems(result) && !result.isAcknowledged;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,7 +320,7 @@ class DoctorSheet extends Component<Props, State> {
|
|||||||
<FlexRow>
|
<FlexRow>
|
||||||
<HealthcheckListContainer>
|
<HealthcheckListContainer>
|
||||||
{Object.values(this.props.healthcheckReport.categories).map(
|
{Object.values(this.props.healthcheckReport.categories).map(
|
||||||
(category: HealthcheckReportCategory) => {
|
(category: FlipperDoctor.HealthcheckReportCategory) => {
|
||||||
return (
|
return (
|
||||||
<CategoryContainer key={category.key}>
|
<CategoryContainer key={category.key}>
|
||||||
<HealthcheckDisplay
|
<HealthcheckDisplay
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ import {
|
|||||||
updateHealthcheckResult,
|
updateHealthcheckResult,
|
||||||
acknowledgeProblems,
|
acknowledgeProblems,
|
||||||
} from '../healthchecks';
|
} from '../healthchecks';
|
||||||
import {Healthchecks, EnvironmentInfo} from 'flipper-doctor';
|
import type {FlipperDoctor} from 'flipper-common';
|
||||||
|
|
||||||
const HEALTHCHECKS: Healthchecks = {
|
const HEALTHCHECKS: FlipperDoctor.Healthchecks = {
|
||||||
ios: {
|
ios: {
|
||||||
label: 'iOS',
|
label: 'iOS',
|
||||||
isSkipped: false,
|
isSkipped: false,
|
||||||
@@ -25,7 +25,7 @@ const HEALTHCHECKS: Healthchecks = {
|
|||||||
{
|
{
|
||||||
key: 'ios.sdk',
|
key: 'ios.sdk',
|
||||||
label: 'SDK Installed',
|
label: 'SDK Installed',
|
||||||
run: async (_env: EnvironmentInfo) => {
|
run: async (_env: FlipperDoctor.EnvironmentInfo) => {
|
||||||
return {hasProblem: false, message: ''};
|
return {hasProblem: false, message: ''};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -39,7 +39,7 @@ const HEALTHCHECKS: Healthchecks = {
|
|||||||
{
|
{
|
||||||
key: 'android.sdk',
|
key: 'android.sdk',
|
||||||
label: 'SDK Installed',
|
label: 'SDK Installed',
|
||||||
run: async (_env: EnvironmentInfo) => {
|
run: async (_env: FlipperDoctor.EnvironmentInfo) => {
|
||||||
return {hasProblem: true, message: 'Error'};
|
return {hasProblem: true, message: 'Error'};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -53,7 +53,7 @@ const HEALTHCHECKS: Healthchecks = {
|
|||||||
{
|
{
|
||||||
key: 'common.openssl',
|
key: 'common.openssl',
|
||||||
label: 'OpenSSL Istalled',
|
label: 'OpenSSL Istalled',
|
||||||
run: async (_env: EnvironmentInfo) => {
|
run: async (_env: FlipperDoctor.EnvironmentInfo) => {
|
||||||
return {hasProblem: false, message: ''};
|
return {hasProblem: false, message: ''};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,17 +9,17 @@
|
|||||||
|
|
||||||
import {Actions} from './';
|
import {Actions} from './';
|
||||||
import {produce} from 'immer';
|
import {produce} from 'immer';
|
||||||
import {Healthchecks} from 'flipper-doctor';
|
import type {FlipperDoctor} from 'flipper-common';
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
healthcheckReport: HealthcheckReport;
|
healthcheckReport: FlipperDoctor.HealthcheckReport;
|
||||||
acknowledgedProblems: string[];
|
acknowledgedProblems: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Action =
|
export type Action =
|
||||||
| {
|
| {
|
||||||
type: 'START_HEALTHCHECKS';
|
type: 'START_HEALTHCHECKS';
|
||||||
payload: Healthchecks;
|
payload: FlipperDoctor.Healthchecks;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'FINISH_HEALTHCHECKS';
|
type: 'FINISH_HEALTHCHECKS';
|
||||||
@@ -29,7 +29,7 @@ export type Action =
|
|||||||
payload: {
|
payload: {
|
||||||
categoryKey: string;
|
categoryKey: string;
|
||||||
itemKey: string;
|
itemKey: string;
|
||||||
result: HealthcheckResult;
|
result: FlipperDoctor.HealthcheckResult;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
@@ -47,39 +47,6 @@ const INITIAL_STATE: State = {
|
|||||||
acknowledgedProblems: [],
|
acknowledgedProblems: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
type Dictionary<T> = {[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<HealthcheckReportItem>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type HealthcheckReport = {
|
|
||||||
result: HealthcheckResult;
|
|
||||||
categories: Dictionary<HealthcheckReportCategory>;
|
|
||||||
};
|
|
||||||
|
|
||||||
function recomputeHealthcheckStatus(draft: State): void {
|
function recomputeHealthcheckStatus(draft: State): void {
|
||||||
draft.healthcheckReport.result = computeAggregatedResult(
|
draft.healthcheckReport.result = computeAggregatedResult(
|
||||||
Object.values(draft.healthcheckReport.categories).map((c) => c.result),
|
Object.values(draft.healthcheckReport.categories).map((c) => c.result),
|
||||||
@@ -87,8 +54,8 @@ function recomputeHealthcheckStatus(draft: State): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function computeAggregatedResult(
|
function computeAggregatedResult(
|
||||||
results: HealthcheckResult[],
|
results: FlipperDoctor.HealthcheckResult[],
|
||||||
): HealthcheckResult {
|
): FlipperDoctor.HealthcheckResult {
|
||||||
return results.some((r) => r.status === 'IN_PROGRESS')
|
return results.some((r) => r.status === 'IN_PROGRESS')
|
||||||
? {status: 'IN_PROGRESS'}
|
? {status: 'IN_PROGRESS'}
|
||||||
: results.every((r) => r.status === 'SUCCESS')
|
: results.every((r) => r.status === 'SUCCESS')
|
||||||
@@ -114,7 +81,7 @@ const updateCheckResult = produce(
|
|||||||
}: {
|
}: {
|
||||||
categoryKey: string;
|
categoryKey: string;
|
||||||
itemKey: string;
|
itemKey: string;
|
||||||
result: HealthcheckResult;
|
result: FlipperDoctor.HealthcheckResult;
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
const category = draft.healthcheckReport.categories[categoryKey];
|
const category = draft.healthcheckReport.categories[categoryKey];
|
||||||
@@ -124,55 +91,57 @@ const updateCheckResult = produce(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
function createDict<T>(pairs: [string, T][]): Dictionary<T> {
|
function createDict<T>(pairs: [string, T][]): FlipperDoctor.Dictionary<T> {
|
||||||
const obj: Dictionary<T> = {};
|
const obj: FlipperDoctor.Dictionary<T> = {};
|
||||||
for (const pair of pairs) {
|
for (const pair of pairs) {
|
||||||
obj[pair[0]] = pair[1];
|
obj[pair[0]] = pair[1];
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
const start = produce((draft: State, healthchecks: Healthchecks) => {
|
const start = produce(
|
||||||
draft.healthcheckReport = {
|
(draft: State, healthchecks: FlipperDoctor.Healthchecks) => {
|
||||||
result: {status: 'IN_PROGRESS'},
|
draft.healthcheckReport = {
|
||||||
categories: createDict<HealthcheckReportCategory>(
|
result: {status: 'IN_PROGRESS'},
|
||||||
Object.entries(healthchecks).map(([categoryKey, category]) => {
|
categories: createDict<FlipperDoctor.HealthcheckReportCategory>(
|
||||||
if (category.isSkipped) {
|
Object.entries(healthchecks).map(([categoryKey, category]) => {
|
||||||
|
if (category.isSkipped) {
|
||||||
|
return [
|
||||||
|
categoryKey,
|
||||||
|
{
|
||||||
|
key: categoryKey,
|
||||||
|
result: {
|
||||||
|
status: 'SKIPPED',
|
||||||
|
message: category.skipReason,
|
||||||
|
},
|
||||||
|
label: category.label,
|
||||||
|
checks: createDict<FlipperDoctor.HealthcheckReportItem>([]),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
return [
|
return [
|
||||||
categoryKey,
|
categoryKey,
|
||||||
{
|
{
|
||||||
key: categoryKey,
|
key: categoryKey,
|
||||||
result: {
|
result: {status: 'IN_PROGRESS'},
|
||||||
status: 'SKIPPED',
|
|
||||||
message: category.skipReason,
|
|
||||||
},
|
|
||||||
label: category.label,
|
label: category.label,
|
||||||
checks: createDict<HealthcheckReportItem>([]),
|
checks: createDict<FlipperDoctor.HealthcheckReportItem>(
|
||||||
|
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<HealthcheckReportItem>(
|
|
||||||
category.healthchecks.map((check) => [
|
|
||||||
check.key,
|
|
||||||
{
|
|
||||||
key: check.key,
|
|
||||||
result: {status: 'IN_PROGRESS'},
|
|
||||||
label: check.label,
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const finish = produce((draft: State) => {
|
const finish = produce((draft: State) => {
|
||||||
Object.values(draft.healthcheckReport.categories)
|
Object.values(draft.healthcheckReport.categories)
|
||||||
@@ -244,7 +213,7 @@ export default function reducer(
|
|||||||
export const updateHealthcheckResult = (
|
export const updateHealthcheckResult = (
|
||||||
categoryKey: string,
|
categoryKey: string,
|
||||||
itemKey: string,
|
itemKey: string,
|
||||||
result: HealthcheckResult,
|
result: FlipperDoctor.HealthcheckResult,
|
||||||
): Action => ({
|
): Action => ({
|
||||||
type: 'UPDATE_HEALTHCHECK_RESULT',
|
type: 'UPDATE_HEALTHCHECK_RESULT',
|
||||||
payload: {
|
payload: {
|
||||||
@@ -254,7 +223,9 @@ export const updateHealthcheckResult = (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const startHealthchecks = (healthchecks: Healthchecks): Action => ({
|
export const startHealthchecks = (
|
||||||
|
healthchecks: FlipperDoctor.Healthchecks,
|
||||||
|
): Action => ({
|
||||||
type: 'START_HEALTHCHECKS',
|
type: 'START_HEALTHCHECKS',
|
||||||
payload: healthchecks,
|
payload: healthchecks,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,12 +18,6 @@ import {
|
|||||||
LoadingOutlined,
|
LoadingOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import {Layout} from '../ui';
|
import {Layout} from '../ui';
|
||||||
import {
|
|
||||||
HealthcheckReport,
|
|
||||||
HealthcheckReportItem,
|
|
||||||
HealthcheckStatus,
|
|
||||||
HealthcheckResult,
|
|
||||||
} from '../reducers/healthchecks';
|
|
||||||
import {theme} from 'flipper-plugin';
|
import {theme} from 'flipper-plugin';
|
||||||
import {
|
import {
|
||||||
startHealthchecks,
|
startHealthchecks,
|
||||||
@@ -33,13 +27,14 @@ import {
|
|||||||
resetAcknowledgedProblems,
|
resetAcknowledgedProblems,
|
||||||
} from '../reducers/healthchecks';
|
} from '../reducers/healthchecks';
|
||||||
import runHealthchecks from '../utils/runHealthchecks';
|
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';
|
import {reportUsage} from 'flipper-common';
|
||||||
|
|
||||||
const {Title, Paragraph, Text} = Typography;
|
const {Title, Paragraph, Text} = Typography;
|
||||||
|
|
||||||
const statusTypeAndMessage: {
|
const statusTypeAndMessage: {
|
||||||
[key in HealthcheckStatus]: {
|
[key in FlipperDoctor.HealthcheckStatus]: {
|
||||||
type: 'success' | 'info' | 'warning' | 'error';
|
type: 'success' | 'info' | 'warning' | 'error';
|
||||||
message: string;
|
message: string;
|
||||||
};
|
};
|
||||||
@@ -67,15 +62,15 @@ const statusTypeAndMessage: {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function checkHasProblem(result: HealthcheckResult) {
|
function checkHasProblem(result: FlipperDoctor.HealthcheckResult) {
|
||||||
return result.status === 'FAILED' || result.status === 'WARNING';
|
return result.status === 'FAILED' || result.status === 'WARNING';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkHasNewProblem(result: HealthcheckResult) {
|
export function checkHasNewProblem(result: FlipperDoctor.HealthcheckResult) {
|
||||||
return checkHasProblem(result) && !result.isAcknowledged;
|
return checkHasProblem(result) && !result.isAcknowledged;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ResultTopDialog(props: {status: HealthcheckStatus}) {
|
function ResultTopDialog(props: {status: FlipperDoctor.HealthcheckStatus}) {
|
||||||
const messages = statusTypeAndMessage[props.status];
|
const messages = statusTypeAndMessage[props.status];
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
@@ -92,7 +87,7 @@ function ResultTopDialog(props: {status: HealthcheckStatus}) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CheckIcon(props: {status: HealthcheckStatus}) {
|
function CheckIcon(props: {status: FlipperDoctor.HealthcheckStatus}) {
|
||||||
switch (props.status) {
|
switch (props.status) {
|
||||||
case 'SUCCESS':
|
case 'SUCCESS':
|
||||||
return (
|
return (
|
||||||
@@ -119,7 +114,9 @@ function CheckIcon(props: {status: HealthcheckStatus}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function CollapsableCategory(props: {checks: Array<HealthcheckReportItem>}) {
|
function CollapsableCategory(props: {
|
||||||
|
checks: Array<FlipperDoctor.HealthcheckReportItem>;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<Collapse ghost>
|
<Collapse ghost>
|
||||||
{props.checks.map((check) => (
|
{props.checks.map((check) => (
|
||||||
@@ -134,7 +131,7 @@ function CollapsableCategory(props: {checks: Array<HealthcheckReportItem>}) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function HealthCheckList(props: {report: HealthcheckReport}) {
|
function HealthCheckList(props: {report: FlipperDoctor.HealthcheckReport}) {
|
||||||
useEffect(() => reportUsage('doctor:report:opened'), []);
|
useEffect(() => reportUsage('doctor:report:opened'), []);
|
||||||
return (
|
return (
|
||||||
<Layout.Container gap>
|
<Layout.Container gap>
|
||||||
@@ -241,7 +238,7 @@ export default function SetupDoctorScreen(props: {
|
|||||||
updateHealthcheckResult: (
|
updateHealthcheckResult: (
|
||||||
categoryKey: string,
|
categoryKey: string,
|
||||||
itemKey: string,
|
itemKey: string,
|
||||||
result: HealthcheckResult,
|
result: FlipperDoctor.HealthcheckResult,
|
||||||
) => dispatch(updateHealthcheckResult(categoryKey, itemKey, result)),
|
) => dispatch(updateHealthcheckResult(categoryKey, itemKey, result)),
|
||||||
finishHealthchecks: () => dispatch(finishHealthchecks()),
|
finishHealthchecks: () => dispatch(finishHealthchecks()),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,9 +7,12 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {HealthcheckResult} from '../reducers/healthchecks';
|
import {
|
||||||
import {getHealthchecks, getEnvInfo, Healthchecks} from 'flipper-doctor';
|
logPlatformSuccessRate,
|
||||||
import {logPlatformSuccessRate, reportPlatformFailures} from 'flipper-common';
|
reportPlatformFailures,
|
||||||
|
FlipperDoctor,
|
||||||
|
} from 'flipper-common';
|
||||||
|
import {getRenderHostInstance} from '../RenderHost';
|
||||||
|
|
||||||
let healthcheckIsRunning: boolean;
|
let healthcheckIsRunning: boolean;
|
||||||
let runningHealthcheck: Promise<void>;
|
let runningHealthcheck: Promise<void>;
|
||||||
@@ -18,9 +21,9 @@ export type HealthcheckEventsHandler = {
|
|||||||
updateHealthcheckResult: (
|
updateHealthcheckResult: (
|
||||||
categoryKey: string,
|
categoryKey: string,
|
||||||
itemKey: string,
|
itemKey: string,
|
||||||
result: HealthcheckResult,
|
result: FlipperDoctor.HealthcheckResult,
|
||||||
) => void;
|
) => void;
|
||||||
startHealthchecks: (healthchecks: Healthchecks) => void;
|
startHealthchecks: (healthchecks: FlipperDoctor.Healthchecks) => void;
|
||||||
finishHealthchecks: () => void;
|
finishHealthchecks: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -36,34 +39,25 @@ export type HealthcheckSettings = {
|
|||||||
export type HealthcheckOptions = HealthcheckEventsHandler & HealthcheckSettings;
|
export type HealthcheckOptions = HealthcheckEventsHandler & HealthcheckSettings;
|
||||||
|
|
||||||
async function launchHealthchecks(options: HealthcheckOptions): Promise<void> {
|
async function launchHealthchecks(options: HealthcheckOptions): Promise<void> {
|
||||||
const healthchecks = getHealthchecks();
|
const {flipperServer} = getRenderHostInstance();
|
||||||
if (!options.settings.enableAndroid) {
|
const healthchecks = await flipperServer.exec('doctor-get-healthchecks', {
|
||||||
healthchecks.android = {
|
settings: options.settings,
|
||||||
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',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
options.startHealthchecks(healthchecks);
|
options.startHealthchecks(healthchecks);
|
||||||
const environmentInfo = await getEnvInfo();
|
|
||||||
let hasProblems = false;
|
let hasProblems = false;
|
||||||
for (const [categoryKey, category] of Object.entries(healthchecks)) {
|
for (const [categoryKey, category] of Object.entries(healthchecks)) {
|
||||||
if (category.isSkipped) {
|
if (category.isSkipped) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (const h of category.healthchecks) {
|
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"
|
const metricName = `doctor:${h.key.replace('.', ':')}.healthcheck`; // e.g. "doctor:ios:xcode-select.healthcheck"
|
||||||
if (checkResult.hasProblem) {
|
if (checkResult.status !== 'SUCCESS') {
|
||||||
hasProblems = true;
|
hasProblems = true;
|
||||||
logPlatformSuccessRate(metricName, {
|
logPlatformSuccessRate(metricName, {
|
||||||
kind: 'failure',
|
kind: 'failure',
|
||||||
@@ -75,19 +69,7 @@ async function launchHealthchecks(options: HealthcheckOptions): Promise<void> {
|
|||||||
kind: 'success',
|
kind: 'success',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const result: HealthcheckResult =
|
options.updateHealthcheckResult(categoryKey, h.key, checkResult);
|
||||||
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.finishHealthchecks();
|
options.finishHealthchecks();
|
||||||
|
|||||||
@@ -7,9 +7,6 @@
|
|||||||
"emitDeclarationOnly": true
|
"emitDeclarationOnly": true
|
||||||
},
|
},
|
||||||
"references": [
|
"references": [
|
||||||
{
|
|
||||||
"path": "../doctor"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"path": "../flipper-common"
|
"path": "../flipper-common"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user