Show status updates while exporting

Summary: Adds a feature to display status update, while exporting Flipper trace. This will make user aware of the steps which are being executed to export flipper trace. This diff also logs the status update, to better support the issue posted by the user regarding the Flipper trace.

Reviewed By: jknoxville

Differential Revision: D16203810

fbshipit-source-id: 8f0bdc3836fec6dd79f2ca9827822d13f6cfd8bd
This commit is contained in:
Pritesh Nandgaonkar
2019-07-15 03:58:35 -07:00
committed by Facebook Github Bot
parent 7f2709e1a5
commit ba95e73a8d
3 changed files with 148 additions and 68 deletions

View File

@@ -80,6 +80,7 @@ type State = {
| {
flipperUrl: string,
},
statusUpdate: ?string,
};
export default class ShareSheet extends Component<Props, State> {
@@ -90,6 +91,7 @@ export default class ShareSheet extends Component<Props, State> {
state = {
errorArray: [],
result: null,
statusUpdate: null,
};
idler = new Idler();
@@ -99,7 +101,9 @@ export default class ShareSheet extends Component<Props, State> {
performance.mark(mark);
try {
const {serializedString, errorArray} = await reportPlatformFailures(
exportStore(this.context.store, this.idler),
exportStore(this.context.store, this.idler, (msg: string) => {
this.setState({statusUpdate: msg});
}),
`${EXPORT_FLIPPER_TRACE_EVENT}:UI_LINK`,
);
@@ -129,66 +133,80 @@ export default class ShareSheet extends Component<Props, State> {
}
}
renderTheProgessState(onHide: () => void, statusUpdate: ?string) {
return (
<Container>
<FlexColumn>
<Center>
<LoadingIndicator size={30} />
{statusUpdate && statusUpdate.length > 0 ? (
<Uploading bold color={colors.macOSTitleBarIcon}>
{statusUpdate}
</Uploading>
) : (
<Uploading bold color={colors.macOSTitleBarIcon}>
Uploading Flipper trace...
</Uploading>
)}
</Center>
<FlexRow>
<Spacer />
<Button compact padded onClick={onHide}>
Close
</Button>
</FlexRow>
</FlexColumn>
</Container>
);
}
render() {
const onHide = () => {
this.props.onHide();
this.idler.cancel();
};
const {result, statusUpdate, errorArray} = this.state;
if (!result || !result.flipperUrl) {
return this.renderTheProgessState(onHide, statusUpdate);
}
return (
<Container>
{this.state.result ? (
<>
<FlexColumn>
{this.state.result.flipperUrl ? (
<>
<Title bold>Data Upload Successful</Title>
<InfoText>
Flipper's data was successfully uploaded. This URL can be
used to share with other Flipper users. Opening it will
import the data from your trace.
</InfoText>
<Copy value={this.state.result.flipperUrl} />
<InfoText>
When sharing your Flipper link, consider that the captured
data might contain sensitve information like access tokens
used in network requests.
</InfoText>
<ShareSheetErrorList errors={this.state.errorArray} />
)}
</>
) : (
<>
<Title bold>{this.state.result.error_class || 'Error'}</Title>
<ErrorMessage code>
{this.state.result.error ||
'The data could not be uploaded'}
</ErrorMessage>
</>
)}
</FlexColumn>
<FlexRow>
<Spacer />
<Button compact padded onClick={onHide}>
Close
</Button>
</FlexRow>
</>
) : (
<>
<FlexColumn>
<Center>
<LoadingIndicator size={30} />
<Uploading bold color={colors.macOSTitleBarIcon}>
Uploading Flipper trace...
</Uploading>
</Center>
<FlexRow>
<Spacer />
<Button compact padded onClick={onHide}>
Cancel
</Button>
</FlexRow>
{result.flipperUrl ? (
<>
<Title bold>Data Upload Successful</Title>
<InfoText>
Flipper's data was successfully uploaded. This URL can be used
to share with other Flipper users. Opening it will import the
data from your trace.
</InfoText>
<Copy value={result.flipperUrl} />
<InfoText>
When sharing your Flipper link, consider that the captured
data might contain sensitve information like access tokens
used in network requests.
</InfoText>
<ShareSheetErrorList errors={errorArray} />
)}
</>
) : (
<>
<Title bold>{result.error_class || 'Error'}</Title>
<ErrorMessage code>
{result.error || 'The data could not be uploaded'}
</ErrorMessage>
</>
)}
</FlexColumn>
)}
<FlexRow>
<Spacer />
<Button compact padded onClick={onHide}>
Close
</Button>
</FlexRow>
</>
)
</Container>
);
}

View File

@@ -71,6 +71,7 @@ type State = {
success: boolean,
error: ?Error,
},
statusUpdate: ?string,
};
export default class ShareSheetExportFile extends Component<Props, State> {
@@ -81,6 +82,7 @@ export default class ShareSheetExportFile extends Component<Props, State> {
state = {
errorArray: [],
result: null,
statusUpdate: null,
};
idler = new Idler();
@@ -95,7 +97,14 @@ export default class ShareSheetExportFile extends Component<Props, State> {
return;
}
const {errorArray} = await reportPlatformFailures(
exportStoreToFile(this.props.file, this.context.store, this.idler),
exportStoreToFile(
this.props.file,
this.context.store,
this.idler,
(msg: string) => {
this.setState({statusUpdate: msg});
},
),
`${EXPORT_FLIPPER_TRACE_EVENT}:UI_FILE`,
);
this.setState({errorArray, result: {success: true, error: null}});
@@ -115,7 +124,7 @@ export default class ShareSheetExportFile extends Component<Props, State> {
if (!this.props.file) {
return this.renderNoFileError(onHide);
}
const {result} = this.state;
const {result, statusUpdate} = this.state;
if (result) {
const {success, error} = result;
if (success) {
@@ -161,9 +170,15 @@ export default class ShareSheetExportFile extends Component<Props, State> {
<Container>
<Center>
<LoadingIndicator size={30} />
<Uploading bold color={colors.macOSTitleBarIcon}>
Exporting Flipper trace...
</Uploading>
{statusUpdate && statusUpdate.length > 0 ? (
<Uploading bold color={colors.macOSTitleBarIcon}>
{statusUpdate}
</Uploading>
) : (
<Uploading bold color={colors.macOSTitleBarIcon}>
Exporting Flipper trace...
</Uploading>
)}
</Center>
<FlexRow>
<Spacer />

View File

@@ -43,8 +43,14 @@ export type ExportType = {|
export function processClients(
clients: Array<ClientExport>,
serial: string,
statusUpdate?: (msg: string) => void,
): Array<ClientExport> {
return clients.filter(client => client.query.device_id === serial);
statusUpdate &&
statusUpdate(`Filtering Clients for the device id ${serial}...`);
const filteredClients = clients.filter(
client => client.query.device_id === serial,
);
return filteredClients;
}
export function pluginsClassMap(
@@ -68,8 +74,11 @@ export function processPluginStates(
serial: string,
allPluginStates: PluginStatesState,
devicePlugins: Map<string, Class<FlipperDevicePlugin<>>>,
statusUpdate?: (msg: string) => void,
): PluginStatesState {
let pluginStates = {};
statusUpdate &&
statusUpdate('Filtering the plugin states for the filtered Clients...');
for (const key in allPluginStates) {
const keyArray = key.split('#');
const pluginName = keyArray.pop();
@@ -93,7 +102,10 @@ export function processNotificationStates(
serial: string,
allActiveNotifications: Array<PluginNotification>,
devicePlugins: Map<string, Class<FlipperDevicePlugin<>>>,
statusUpdate?: (msg: string) => void,
): Array<PluginNotification> {
statusUpdate &&
statusUpdate('Filtering the notifications for the filtered Clients...');
const activeNotifications = allActiveNotifications.filter(notif => {
const filteredClients = clients.filter(client =>
notif.client ? client.id.includes(notif.client) : false,
@@ -112,6 +124,7 @@ const addSaltToDeviceSerial = async (
clients: Array<ClientExport>,
pluginStates: PluginStatesState,
pluginNotification: Array<PluginNotification>,
statusUpdate?: (msg: string) => void,
): Promise<ExportType> => {
const {serial} = device;
const newSerial = salt + '-' + serial;
@@ -122,6 +135,8 @@ const addSaltToDeviceSerial = async (
device.os,
device.getLogs(),
);
statusUpdate &&
statusUpdate('Adding salt to the selected device id in the client data...');
const updatedClients = clients.map((client: ClientExport) => {
return {
...client,
@@ -130,6 +145,10 @@ const addSaltToDeviceSerial = async (
};
});
statusUpdate &&
statusUpdate(
'Adding salt to the selected device id in the plugin states...',
);
const updatedPluginStates: PluginStatesState = {};
for (let key in pluginStates) {
if (!key.includes(serial)) {
@@ -142,6 +161,10 @@ const addSaltToDeviceSerial = async (
updatedPluginStates[key] = pluginData;
}
statusUpdate &&
statusUpdate(
'Adding salt to the selected device id in the notification data...',
);
const updatedPluginNotifications = pluginNotification.map(notif => {
if (!notif.client || !notif.client.includes(serial)) {
throw new Error(
@@ -172,21 +195,24 @@ export const processStore = async (
clients: Array<ClientExport>,
devicePlugins: Map<string, Class<FlipperDevicePlugin<>>>,
salt: string,
statusUpdate?: (msg: string) => void,
): Promise<?ExportType> => {
if (device) {
const {serial} = device;
const processedClients = processClients(clients, serial);
const processedClients = processClients(clients, serial, statusUpdate);
const processedPluginStates = processPluginStates(
processedClients,
serial,
pluginStates,
devicePlugins,
statusUpdate,
);
const processedActiveNotifications = processNotificationStates(
processedClients,
serial,
activeNotifications,
devicePlugins,
statusUpdate,
);
// Adding salt to the device id, so that the device_id in the device list is unique.
const exportFlipperData = await addSaltToDeviceSerial(
@@ -195,6 +221,7 @@ export const processStore = async (
processedClients,
processedPluginStates,
processedActiveNotifications,
statusUpdate,
);
return exportFlipperData;
}
@@ -205,6 +232,7 @@ export async function fetchMetadata(
pluginStates: PluginStatesState,
pluginsMap: Map<string, Class<FlipperDevicePlugin<> | FlipperPlugin<>>>,
store: MiddlewareAPI,
statusUpdate?: (msg: string) => void,
): Promise<{pluginStates: PluginStatesState, errorArray: Array<Error>}> {
const newPluginState = {...pluginStates};
const errorArray: Array<Error> = [];
@@ -226,6 +254,8 @@ export async function fetchMetadata(
if (exportState) {
const key = pluginKey(client.id, plugin);
try {
statusUpdate &&
statusUpdate(`Fetching metadata for plugin ${plugin}...`);
const data = await promiseTimeout(
120000, // Timeout in 2 mins
exportState(callClient(client, plugin), newPluginState[key], store),
@@ -244,6 +274,7 @@ export async function fetchMetadata(
export async function getStoreExport(
store: MiddlewareAPI,
statusUpdate?: (msg: string) => void,
): Promise<{exportData: ?ExportType, errorArray: Array<Error>}> {
const state = store.getState();
const {clients} = state.connections;
@@ -266,7 +297,13 @@ export async function getStoreExport(
plugins.devicePlugins.forEach((val, key) => {
pluginsMap.set(key, val);
});
const metadata = await fetchMetadata(pluginStates, pluginsMap, store);
statusUpdate && statusUpdate('Preparing to fetch metadata from client...');
const metadata = await fetchMetadata(
pluginStates,
pluginsMap,
store,
statusUpdate,
);
const {errorArray} = metadata;
const newPluginState = metadata.pluginStates;
@@ -279,6 +316,7 @@ export async function getStoreExport(
clients.map(client => client.toJSON()),
devicePlugins,
uuid.v4(),
statusUpdate,
);
return {exportData, errorArray};
}
@@ -286,16 +324,22 @@ export async function getStoreExport(
export function exportStore(
store: MiddlewareAPI,
idler?: Idler,
statusUpdate?: (msg: string) => void,
): Promise<{serializedString: string, errorArray: Array<Error>}> {
getLogger().track('usage', EXPORT_FLIPPER_TRACE_EVENT);
return new Promise(async (resolve, reject) => {
try {
const {exportData, errorArray} = await getStoreExport(store);
statusUpdate && statusUpdate('Preparing to export Flipper data...');
const {exportData, errorArray} = await getStoreExport(
store,
statusUpdate,
);
if (!exportData) {
console.error('Make sure a device is connected');
reject(new Error('No device is selected'));
}
try {
statusUpdate && statusUpdate('Serializing Flipper data...');
const serializedString = await serialize(exportData, idler);
if (serializedString.length <= 0) {
reject(new Error('Serialize function returned empty string'));
@@ -314,14 +358,17 @@ export const exportStoreToFile = (
exportFilePath: string,
store: Store,
idler?: Idler,
statusUpdate?: (msg: string) => void,
): Promise<{errorArray: Array<Error>}> => {
return exportStore(store, idler).then(({serializedString, errorArray}) => {
return promisify(fs.writeFile)(exportFilePath, serializedString).then(
() => {
return {errorArray};
},
);
});
return exportStore(store, idler, statusUpdate).then(
({serializedString, errorArray}) => {
return promisify(fs.writeFile)(exportFilePath, serializedString).then(
() => {
return {errorArray};
},
);
},
);
};
export function importDataToStore(data: string, store: Store) {