Allow disabling iOS development in Settings

Summary:
Currently Android development can be disabled in Settings, but iOS development not. Because of this Doctor always shows warnings to Android-only developers who has no iOS SDK installed. This change makes it possible to disable "iOS development" option in the same way as we already had for Android.

Additionally I changed Doctor warning to show more specific message if only iOS or only Android checks are failed with suggestion to disable the failing platform if it is not required.

Reviewed By: jknoxville

Differential Revision: D19538070

fbshipit-source-id: 234d2c6cf21083f9d6aebd63418aed7f9a78e922
This commit is contained in:
Anton Nikolaev
2020-01-23 13:35:12 -08:00
committed by Facebook Github Bot
parent b625efee3d
commit aab004aa15
7 changed files with 109 additions and 27 deletions

View File

@@ -14,6 +14,7 @@ import {
setActiveSheet, setActiveSheet,
ActiveSheet, ActiveSheet,
ACTIVE_SHEET_DOCTOR, ACTIVE_SHEET_DOCTOR,
ACTIVE_SHEET_SETTINGS,
} from '../reducers/application'; } from '../reducers/application';
import {State as Store} from '../reducers/index'; import {State as Store} from '../reducers/index';
import {ButtonGroup, Button} from 'flipper'; import {ButtonGroup, Button} from 'flipper';
@@ -23,23 +24,24 @@ import runHealthchecks, {
HealthcheckEventsHandler, HealthcheckEventsHandler,
} from '../utils/runHealthchecks'; } from '../utils/runHealthchecks';
import { import {
HealthcheckResult,
updateHealthcheckResult, updateHealthcheckResult,
startHealthchecks, startHealthchecks,
finishHealthchecks, finishHealthchecks,
HealthcheckReport,
HealthcheckResult,
} from '../reducers/healthchecks'; } from '../reducers/healthchecks';
import {reportUsage} from '../utils/metrics'; import {reportUsage} from '../utils/metrics';
type StateFromProps = { type StateFromProps = {
healthcheckResult: HealthcheckResult; healthcheckReport: HealthcheckReport;
} & HealthcheckSettings; } & HealthcheckSettings;
type DispatchFromProps = { type DispatchFromProps = {
setActiveSheet: (payload: ActiveSheet) => void; setActiveSheet: (payload: ActiveSheet) => void;
} & HealthcheckEventsHandler; } & HealthcheckEventsHandler;
type State = {visible: boolean}; type State = {visible: boolean; message: string; showSettingsButton: boolean};
type Props = DispatchFromProps & StateFromProps; type Props = DispatchFromProps & StateFromProps;
class DoctorBar extends Component<Props, State> { class DoctorBar extends Component<Props, State> {
@@ -47,18 +49,41 @@ class DoctorBar extends Component<Props, State> {
super(props); super(props);
this.state = { this.state = {
visible: false, visible: false,
message: '',
showSettingsButton: false,
}; };
} }
componentDidMount() { componentDidMount() {
this.showMessageIfChecksFailed(); this.showMessageIfChecksFailed();
} }
static getDerivedStateFromProps(props: Props, state: State): State | null {
const failedCategories = Object.values(
props.healthcheckReport.categories,
).filter(cat => hasProblems(cat.result));
if (failedCategories.length == 1) {
const failedCat = failedCategories[0];
if (failedCat.key === 'ios' || failedCat.key === 'android') {
return {
...state,
message: `Doctor has discovered problems with your ${failedCat.label} setup. If you are not interested in ${failedCat.label} development you can disable it in Settings.`,
showSettingsButton: true,
};
}
}
if (failedCategories.length) {
return {
...state,
message: 'Doctor has discovered problems with your installation.',
showSettingsButton: false,
};
}
return null;
}
async showMessageIfChecksFailed() { async showMessageIfChecksFailed() {
await runHealthchecks(this.props); await runHealthchecks(this.props);
if ( const result = this.props.healthcheckReport.result;
this.props.healthcheckResult.status === 'FAILED' || if (hasProblems(result)) {
this.props.healthcheckResult.status === 'WARNING' if (result.isAcknowledged) {
) {
if (this.props.healthcheckResult.isAcknowledged) {
reportUsage('doctor:warning:suppressed'); reportUsage('doctor:warning:suppressed');
} else { } else {
this.setVisible(true); this.setVisible(true);
@@ -76,18 +101,28 @@ class DoctorBar extends Component<Props, State> {
<ButtonGroup> <ButtonGroup>
<Button <Button
onClick={() => { onClick={() => {
reportUsage('doctor:report:opened:fromWarningBar');
this.props.setActiveSheet(ACTIVE_SHEET_DOCTOR); this.props.setActiveSheet(ACTIVE_SHEET_DOCTOR);
this.setVisible(false); this.setVisible(false);
}}> }}>
Show Problems Show Problems
</Button> </Button>
{this.state.showSettingsButton && (
<Button
onClick={() => {
reportUsage('settings:opened:fromWarningBar');
this.props.setActiveSheet(ACTIVE_SHEET_SETTINGS);
}}>
Show Settings
</Button>
)}
<Button onClick={() => this.setVisible(false)}> <Button onClick={() => this.setVisible(false)}>
Dismiss Dismiss
</Button> </Button>
</ButtonGroup> </ButtonGroup>
</ButtonSection> </ButtonSection>
<FlexColumn style={{flexGrow: 1}}> <FlexColumn style={{flexGrow: 1}}>
Doctor has discovered problems with your installation {this.state.message}
</FlexColumn> </FlexColumn>
</FlexRow> </FlexRow>
</WarningContainer> </WarningContainer>
@@ -107,13 +142,12 @@ class DoctorBar extends Component<Props, State> {
export default connect<StateFromProps, DispatchFromProps, {}, Store>( export default connect<StateFromProps, DispatchFromProps, {}, Store>(
({ ({
settingsState: {enableAndroid}, settingsState: {enableAndroid, enableIOS},
healthchecks: { healthchecks: {healthcheckReport},
healthcheckReport: {result},
},
}) => ({ }) => ({
enableAndroid, enableAndroid,
healthcheckResult: result, enableIOS,
healthcheckReport,
}), }),
{ {
setActiveSheet, setActiveSheet,
@@ -149,3 +183,7 @@ const ButtonSection = styled(FlexColumn)({
flexShrink: 0, flexShrink: 0,
flexGrow: 0, flexGrow: 0,
}); });
function hasProblems(result: HealthcheckResult) {
return result.status === 'WARNING' || result.status === 'FAILED';
}

View File

@@ -201,7 +201,7 @@ function ResultMessage(props: {result: HealthcheckResult}) {
return ( return (
<p> <p>
Doctor has discovered problems with your installation. Please click to Doctor has discovered problems with your installation. Please click to
each item to get details. an item to get its details.
</p> </p>
); );
} else { } else {
@@ -404,9 +404,13 @@ class DoctorSheet extends Component<Props, State> {
} }
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>( export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
({healthchecks: {healthcheckReport}, settingsState}) => ({ ({
healthchecks: {healthcheckReport},
settingsState: {enableAndroid, enableIOS},
}) => ({
healthcheckReport, healthcheckReport,
enableAndroid: settingsState.enableAndroid, enableAndroid,
enableIOS,
}), }),
{ {
startHealthchecks, startHealthchecks,

View File

@@ -24,6 +24,10 @@ import {FilePathConfigField, ConfigText} from './settings/configFields';
import isEqual from 'lodash.isequal'; import isEqual from 'lodash.isequal';
import restartFlipper from '../utils/restartFlipper'; import restartFlipper from '../utils/restartFlipper';
import LauncherSettingsPanel from '../fb-stubs/LauncherSettingsPanel'; import LauncherSettingsPanel from '../fb-stubs/LauncherSettingsPanel';
import {reportUsage} from '../utils/metrics';
import os from 'os';
const platform = os.platform();
const Container = styled(FlexColumn)({ const Container = styled(FlexColumn)({
padding: 20, padding: 20,
@@ -64,6 +68,10 @@ class SettingsSheet extends Component<Props, State> {
updatedLauncherSettings: {...this.props.launcherSettings}, updatedLauncherSettings: {...this.props.launcherSettings},
}; };
componentDidMount() {
reportUsage('settings:opened');
}
applyChanges = async () => { applyChanges = async () => {
this.props.updateSettings(this.state.updatedSettings); this.props.updateSettings(this.state.updatedSettings);
this.props.updateLauncherSettings(this.state.updatedLauncherSettings); this.props.updateLauncherSettings(this.state.updatedLauncherSettings);
@@ -104,15 +112,26 @@ class SettingsSheet extends Component<Props, State> {
</ToggledSection> </ToggledSection>
<ToggledSection <ToggledSection
label="iOS Developer" label="iOS Developer"
toggled={this.props.isXcodeDetected} toggled={
frozen> this.state.updatedSettings.enableIOS && platform === 'darwin'
}
frozen={platform !== 'darwin'}
onChange={v => {
this.setState({
updatedSettings: {...this.state.updatedSettings, enableIOS: v},
});
}}>
{' '} {' '}
<ConfigText {platform === 'darwin' && (
content={ <ConfigText
'Use xcode-select to enable or switch between xcode versions' content={'Use "xcode-select" to switch between Xcode versions'}
} />
frozen )}
/> {platform !== 'darwin' && (
<ConfigText
content={'iOS development is only supported on MacOS'}
/>
)}
</ToggledSection> </ToggledSection>
<LauncherSettingsPanel <LauncherSettingsPanel
isPrefetchingEnabled={this.state.updatedSettings.enablePrefetching} isPrefetchingEnabled={this.state.updatedSettings.enablePrefetching}

View File

@@ -42,6 +42,7 @@ import isProduction from '../utils/isProduction';
import {clipboard} from 'electron'; import {clipboard} from 'electron';
import React from 'react'; import React from 'react';
import {State} from 'src/reducers'; import {State} from 'src/reducers';
import {reportUsage} from '../utils/metrics';
const AppTitleBar = styled(FlexRow)<{focused?: boolean}>(({focused}) => ({ const AppTitleBar = styled(FlexRow)<{focused?: boolean}>(({focused}) => ({
background: focused background: focused
@@ -175,7 +176,10 @@ class TitleBar extends React.Component<Props, StateFromProps> {
icon="settings" icon="settings"
title="Settings" title="Settings"
compact={true} compact={true}
onClick={() => this.props.setActiveSheet(ACTIVE_SHEET_SETTINGS)} onClick={() => {
this.props.setActiveSheet(ACTIVE_SHEET_SETTINGS);
reportUsage('settings:opened:fromTitleBar');
}}
/> />
{config.bugReportButtonVisible && ( {config.bugReportButtonVisible && (
<Button <Button
@@ -189,7 +193,10 @@ class TitleBar extends React.Component<Props, StateFromProps> {
icon="first-aid" icon="first-aid"
title="Doctor" title="Doctor"
compact={true} compact={true}
onClick={() => this.props.setActiveSheet(ACTIVE_SHEET_DOCTOR)} onClick={() => {
this.props.setActiveSheet(ACTIVE_SHEET_DOCTOR);
reportUsage('doctor:report:opened:fromTitleBar');
}}
/> />
<ButtonGroup> <ButtonGroup>
<Button <Button

View File

@@ -255,6 +255,9 @@ export default (store: Store, logger: Logger) => {
if (process.platform !== 'darwin') { if (process.platform !== 'darwin') {
return; return;
} }
if (!store.getState().settingsState.enableIOS) {
return;
}
isXcodeDetected() isXcodeDetected()
.then(isDetected => { .then(isDetected => {
store.dispatch(setXcodeDetected(isDetected)); store.dispatch(setXcodeDetected(isDetected));

View File

@@ -20,6 +20,7 @@ export enum Tristate {
export type Settings = { export type Settings = {
androidHome: string; androidHome: string;
enableAndroid: boolean; enableAndroid: boolean;
enableIOS: boolean;
/** /**
* If unset, this will assume the value of the GK setting. * If unset, this will assume the value of the GK setting.
* Note that this setting has no effect in the open source version * Note that this setting has no effect in the open source version
@@ -47,6 +48,7 @@ export const DEFAULT_ANDROID_SDK_PATH = getDefaultAndroidSdkPath();
const initialState: Settings = { const initialState: Settings = {
androidHome: getDefaultAndroidSdkPath(), androidHome: getDefaultAndroidSdkPath(),
enableAndroid: true, enableAndroid: true,
enableIOS: os.platform() === 'darwin',
enablePrefetching: Tristate.Unset, enablePrefetching: Tristate.Unset,
jsApps: { jsApps: {
webAppLauncher: { webAppLauncher: {

View File

@@ -26,6 +26,7 @@ export type HealthcheckEventsHandler = {
export type HealthcheckSettings = { export type HealthcheckSettings = {
enableAndroid: boolean; enableAndroid: boolean;
enableIOS: boolean;
}; };
export type HealthcheckOptions = HealthcheckEventsHandler & HealthcheckSettings; export type HealthcheckOptions = HealthcheckEventsHandler & HealthcheckSettings;
@@ -40,6 +41,14 @@ async function launchHealthchecks(options: HealthcheckOptions): Promise<void> {
'Healthcheck is skipped, because "Android Development" option is disabled in the Flipper settings', 'Healthcheck is skipped, because "Android Development" option is disabled in the Flipper settings',
}; };
} }
if (!options.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(); const environmentInfo = await getEnvInfo();
let hasProblems = false; let hasProblems = false;