Add a modal with status updates for the universal export

Summary:
Design doc: https://docs.google.com/document/d/1HLCFl46RfqG0o1mSt8SWrwf_HMfOCRg_oENioc1rkvQ/edit#

Exporting all files form a device and export Flipper's own state could take a long time. We need to keep our users updated on the status.

Reviewed By: passy

Differential Revision: D40551661

fbshipit-source-id: d5c94fb99d4bc8b4495ce463915b77c475548f01
This commit is contained in:
Andrey Goncharov
2022-10-25 05:31:48 -07:00
committed by Facebook GitHub Bot
parent 96aa0ac02b
commit 80f947212b
2 changed files with 125 additions and 14 deletions

View File

@@ -8,7 +8,16 @@
*/
import React, {cloneElement, useState, useCallback, useMemo} from 'react';
import {Button, Divider, Badge, Tooltip, Avatar, Popover, Menu} from 'antd';
import {
Button,
Divider,
Badge,
Tooltip,
Avatar,
Popover,
Menu,
Modal,
} from 'antd';
import {
MobileFilled,
AppstoreOutlined,
@@ -64,12 +73,14 @@ import {
startFileExport,
startLinkExport,
startFlipperLogsExport,
ExportEverythingEverywhereAllAtOnceStatus,
} from '../utils/exportData';
import {openDeeplinkDialog} from '../deeplink';
import {css} from '@emotion/css';
import {getRenderHostInstance} from 'flipper-frontend-core';
import openSupportRequestForm from '../fb-stubs/openSupportRequestForm';
import {StyleGuide} from './StyleGuide';
import {useEffect} from 'react';
const LeftRailButtonElem = styled(Button)<{kind?: 'small'}>(({kind}) => ({
width: kind === 'small' ? 32 : 36,
@@ -435,14 +446,88 @@ function DebugLogsButton({
function ExportEverythingEverywhereAllAtOnceButton() {
const store = useStore();
const [status, setStatus] = useState<
ExportEverythingEverywhereAllAtOnceStatus | undefined
>();
const [statusMessage, setStatusMessage] = useState<JSX.Element | undefined>();
const exportEverythingEverywhereAllAtOnceTracked = useTrackedCallback(
'Debug data export',
() => exportEverythingEverywhereAllAtOnce(store),
[store],
() => exportEverythingEverywhereAllAtOnce(store, setStatus),
[store, setStatus],
);
useEffect(() => {
switch (status) {
case 'logs': {
setStatusMessage(<p>Exporting Flipper logs...</p>);
return;
}
case 'files': {
let sheepCount = 0;
const setFileExportMessage = () => {
setStatusMessage(
<>
<p>Exporting Flipper debug files from all devices...</p>
<p>It could take a long time!</p>
<p>Let's count sheep while we wait: {sheepCount++}.</p>
<p>
Scream for help if the sheep count reaches 42, but not earlier.
</p>
</>,
);
};
setFileExportMessage();
const interval = setInterval(setFileExportMessage, 3000);
return () => clearInterval(interval);
}
case 'state': {
let dinosaursCount = 0;
const setStateExportMessage = () => {
setStatusMessage(
<>
<p>Exporting Flipper state...</p>
<p>It also could take a long time!</p>
<p>This time we could count dinosaurs: {dinosaursCount++}.</p>
<p>You already know what to do when the counter reaches 42.</p>
</>,
);
};
setStateExportMessage();
const interval = setInterval(setStateExportMessage, 1000);
return () => clearInterval(interval);
}
case 'archive': {
setStatusMessage(<p>Creating an archive...</p>);
return;
}
case 'done': {
setStatusMessage(<p>Done!</p>);
return;
}
case 'cancelled': {
setStatusMessage(<p>Cancelled! Why? 😱🤯👏</p>);
return;
}
}
}, [status]);
return (
<>
<Modal
visible={!!status}
centered
onCancel={() => {
setStatus(undefined);
}}
title="Exporting everything everywhere all at once"
footer={null}>
{statusMessage}
</Modal>
<LeftRailButton
icon={<BugOutlined />}
title="Export Flipper debug data"
@@ -451,6 +536,7 @@ function ExportEverythingEverywhereAllAtOnceButton() {
}}
small
/>
</>
);
}

View File

@@ -616,16 +616,27 @@ export async function startFlipperLogsExport() {
}
async function startDeviceFlipperFolderExport() {
return await getRenderHostInstance().flipperServer.exec('fetch-debug-data');
return await getRenderHostInstance().flipperServer.exec(
{timeout: 3 * 60 * 1000},
'fetch-debug-data',
);
}
export type ExportEverythingEverywhereAllAtOnceStatus =
| 'logs'
| 'files'
| 'state'
| 'archive'
| 'done'
| 'cancelled';
export async function exportEverythingEverywhereAllAtOnce(
store: MiddlewareAPI,
onStatusUpdate?: (status: ExportEverythingEverywhereAllAtOnceStatus) => void,
) {
// TODO: Show a progress dialog
const zip = new JSZip();
// Step 1: Export Flipper logs
onStatusUpdate?.('logs');
const serializedLogs = exportLogs
.map((item) => JSON.stringify(item))
.join('\n');
@@ -633,6 +644,7 @@ export async function exportEverythingEverywhereAllAtOnce(
zip.file('flipper_logs.txt', serializedLogs);
// Step 2: Export device logs
onStatusUpdate?.('files');
const flipperFolderContent = await startDeviceFlipperFolderExport();
const deviceFlipperFolder = zip.folder('device_flipper_folder')!;
@@ -661,6 +673,7 @@ export async function exportEverythingEverywhereAllAtOnce(
});
// Step 3: Export Flipper State
onStatusUpdate?.('state');
const exportablePlugins = getExportablePlugins(store.getState());
// TODO: no need to put this in the store,
// need to be cleaned up later in combination with SupportForm
@@ -669,9 +682,21 @@ export async function exportEverythingEverywhereAllAtOnce(
zip.file('flipper_export', serializedString);
onStatusUpdate?.('archive');
const archiveData = await zip.generateAsync({type: 'uint8array'});
await getRenderHostInstance().exportFileBinary?.(archiveData);
const exportedFilePath = await getRenderHostInstance().exportFileBinary?.(
archiveData,
{
defaultPath: 'flipper_EEAaO_export',
},
);
if (exportedFilePath) {
onStatusUpdate?.('done');
} else {
onStatusUpdate?.('cancelled');
}
}
export async function startFileExport(dispatch: Store['dispatch']) {