Summary: When trying to refactor some components, did once again run into circular imports that cause the flipper startup sequence to fail. Added linting rules to make sure this is much less likely to happen in the future, and fixed all resulting errors Reviewed By: nikoant Differential Revision: D24390583 fbshipit-source-id: 9b20cf6a4d3555dc68f0069c2950dd7162b17e67
250 lines
6.3 KiB
TypeScript
250 lines
6.3 KiB
TypeScript
/**
|
|
* 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 {FlexColumn, Button, styled, Text, FlexRow, Spacer} from '../ui';
|
|
import React, {Component} from 'react';
|
|
import {setExportStatusComponent, unsetShare} from '../reducers/application';
|
|
import {reportPlatformFailures} from '../utils/metrics';
|
|
import CancellableExportStatus from './CancellableExportStatus';
|
|
import {performance} from 'perf_hooks';
|
|
import {Logger} from '../fb-interfaces/Logger';
|
|
import {Idler} from '../utils/Idler';
|
|
import {
|
|
exportStoreToFile,
|
|
EXPORT_FLIPPER_TRACE_EVENT,
|
|
displayFetchMetadataErrors,
|
|
} from '../utils/exportData';
|
|
import ShareSheetErrorList from './ShareSheetErrorList';
|
|
import ShareSheetPendingDialog from './ShareSheetPendingDialog';
|
|
import {ReactReduxContext} from 'react-redux';
|
|
import {MiddlewareAPI} from '../reducers/index';
|
|
|
|
const Container = styled(FlexColumn)({
|
|
padding: 20,
|
|
width: 500,
|
|
});
|
|
|
|
const ErrorMessage = styled(Text)({
|
|
display: 'block',
|
|
marginTop: 6,
|
|
wordBreak: 'break-all',
|
|
whiteSpace: 'pre-line',
|
|
lineHeight: 1.35,
|
|
});
|
|
|
|
const Title = styled(Text)({
|
|
marginBottom: 6,
|
|
});
|
|
|
|
const InfoText = styled(Text)({
|
|
lineHeight: 1.35,
|
|
marginBottom: 15,
|
|
});
|
|
|
|
type Props = {
|
|
onHide: () => void;
|
|
file: string;
|
|
logger: Logger;
|
|
};
|
|
|
|
type State = {
|
|
fetchMetaDataErrors: {
|
|
[plugin: string]: Error;
|
|
} | null;
|
|
result:
|
|
| {
|
|
kind: 'success';
|
|
}
|
|
| {
|
|
kind: 'error';
|
|
error: Error;
|
|
}
|
|
| {
|
|
kind: 'pending';
|
|
};
|
|
statusUpdate: string | null;
|
|
runInBackground: boolean;
|
|
};
|
|
|
|
export default class ShareSheetExportFile extends Component<Props, State> {
|
|
static contextType = ReactReduxContext;
|
|
|
|
get store(): MiddlewareAPI {
|
|
return this.context.store;
|
|
}
|
|
|
|
state: State = {
|
|
fetchMetaDataErrors: null,
|
|
result: {kind: 'pending'},
|
|
statusUpdate: null,
|
|
runInBackground: false,
|
|
};
|
|
|
|
idler = new Idler();
|
|
|
|
dispatchAndUpdateToolBarStatus(msg: string) {
|
|
this.store.dispatch(
|
|
setExportStatusComponent(
|
|
<CancellableExportStatus
|
|
msg={msg}
|
|
onCancel={() => {
|
|
this.idler.cancel();
|
|
this.store.dispatch(unsetShare());
|
|
}}
|
|
/>,
|
|
),
|
|
);
|
|
}
|
|
|
|
async componentDidMount() {
|
|
const mark = 'shareSheetExportFile';
|
|
performance.mark(mark);
|
|
try {
|
|
if (!this.props.file) {
|
|
return;
|
|
}
|
|
const {fetchMetaDataErrors} = await reportPlatformFailures(
|
|
exportStoreToFile(
|
|
this.props.file,
|
|
this.store,
|
|
false,
|
|
this.idler,
|
|
(msg: string) => {
|
|
if (this.state.runInBackground) {
|
|
this.dispatchAndUpdateToolBarStatus(msg);
|
|
} else {
|
|
this.setState({statusUpdate: msg});
|
|
}
|
|
},
|
|
),
|
|
`${EXPORT_FLIPPER_TRACE_EVENT}:UI_FILE`,
|
|
);
|
|
if (this.state.runInBackground) {
|
|
new Notification('Shareable Flipper Export created', {
|
|
body: `Saved to ${this.props.file}`,
|
|
requireInteraction: true,
|
|
});
|
|
}
|
|
this.setState({fetchMetaDataErrors, result: {kind: 'success'}});
|
|
this.store.dispatch(unsetShare());
|
|
this.props.logger.trackTimeSince(mark, 'export:file-success');
|
|
} catch (err) {
|
|
const result: {
|
|
kind: 'error';
|
|
error: Error;
|
|
} = {
|
|
kind: 'error',
|
|
error: err,
|
|
};
|
|
if (!this.state.runInBackground) {
|
|
// Show the error in UI.
|
|
this.setState({result});
|
|
}
|
|
this.store.dispatch(unsetShare());
|
|
this.props.logger.trackTimeSince(mark, 'export:file-error', result);
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
renderSuccess() {
|
|
const {title, errorArray} = displayFetchMetadataErrors(
|
|
this.state.fetchMetaDataErrors,
|
|
);
|
|
|
|
return (
|
|
<ReactReduxContext.Consumer>
|
|
{({store}) => (
|
|
<Container>
|
|
<FlexColumn>
|
|
<Title bold>Data Exported Successfully</Title>
|
|
<InfoText>
|
|
When sharing your Flipper data, consider that the captured data
|
|
might contain sensitive information like access tokens used in
|
|
network requests.
|
|
</InfoText>
|
|
<ShareSheetErrorList
|
|
errors={errorArray}
|
|
title={title}
|
|
type={'warning'}
|
|
/>
|
|
</FlexColumn>
|
|
<FlexRow>
|
|
<Spacer />
|
|
<Button compact padded onClick={() => this.cancelAndHide(store)}>
|
|
Close
|
|
</Button>
|
|
</FlexRow>
|
|
</Container>
|
|
)}
|
|
</ReactReduxContext.Consumer>
|
|
);
|
|
}
|
|
|
|
renderError(result: {kind: 'error'; error: Error}) {
|
|
return (
|
|
<ReactReduxContext.Consumer>
|
|
{({store}) => (
|
|
<Container>
|
|
<Title bold>Error</Title>
|
|
<ErrorMessage code>
|
|
{result.error.message || 'File could not be saved.'}
|
|
</ErrorMessage>
|
|
<FlexRow>
|
|
<Spacer />
|
|
<Button compact padded onClick={() => this.cancelAndHide(store)}>
|
|
Close
|
|
</Button>
|
|
</FlexRow>
|
|
</Container>
|
|
)}
|
|
</ReactReduxContext.Consumer>
|
|
);
|
|
}
|
|
|
|
renderPending(statusUpdate: string | null) {
|
|
return (
|
|
<ReactReduxContext.Consumer>
|
|
{({store}) => (
|
|
<ShareSheetPendingDialog
|
|
width={500}
|
|
statusUpdate={statusUpdate}
|
|
statusMessage="Creating Flipper Export..."
|
|
onCancel={() => this.cancelAndHide(store)}
|
|
onRunInBackground={() => {
|
|
this.setState({runInBackground: true});
|
|
if (statusUpdate) {
|
|
this.dispatchAndUpdateToolBarStatus(statusUpdate);
|
|
}
|
|
this.props.onHide();
|
|
}}
|
|
/>
|
|
)}
|
|
</ReactReduxContext.Consumer>
|
|
);
|
|
}
|
|
|
|
cancelAndHide(store: MiddlewareAPI) {
|
|
store.dispatch(unsetShare());
|
|
this.props.onHide();
|
|
this.idler.cancel();
|
|
}
|
|
|
|
render() {
|
|
const {result, statusUpdate} = this.state;
|
|
switch (result.kind) {
|
|
case 'success':
|
|
return this.renderSuccess();
|
|
case 'error':
|
|
return this.renderError(result);
|
|
case 'pending':
|
|
return this.renderPending(statusUpdate);
|
|
}
|
|
}
|
|
}
|