diff --git a/desktop/flipper-ui-core/src/sandy-chrome/LeftRail.tsx b/desktop/flipper-ui-core/src/sandy-chrome/LeftRail.tsx index 1b2af759f..a4d542ab3 100644 --- a/desktop/flipper-ui-core/src/sandy-chrome/LeftRail.tsx +++ b/desktop/flipper-ui-core/src/sandy-chrome/LeftRail.tsx @@ -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,22 +446,97 @@ function DebugLogsButton({ function ExportEverythingEverywhereAllAtOnceButton() { const store = useStore(); + const [status, setStatus] = useState< + ExportEverythingEverywhereAllAtOnceStatus | undefined + >(); + const [statusMessage, setStatusMessage] = useState(); const exportEverythingEverywhereAllAtOnceTracked = useTrackedCallback( 'Debug data export', - () => exportEverythingEverywhereAllAtOnce(store), - [store], + () => exportEverythingEverywhereAllAtOnce(store, setStatus), + [store, setStatus], ); + useEffect(() => { + switch (status) { + case 'logs': { + setStatusMessage(

Exporting Flipper logs...

); + return; + } + case 'files': { + let sheepCount = 0; + const setFileExportMessage = () => { + setStatusMessage( + <> +

Exporting Flipper debug files from all devices...

+

It could take a long time!

+

Let's count sheep while we wait: {sheepCount++}.

+

+ Scream for help if the sheep count reaches 42, but not earlier. +

+ , + ); + }; + + setFileExportMessage(); + + const interval = setInterval(setFileExportMessage, 3000); + return () => clearInterval(interval); + } + case 'state': { + let dinosaursCount = 0; + const setStateExportMessage = () => { + setStatusMessage( + <> +

Exporting Flipper state...

+

It also could take a long time!

+

This time we could count dinosaurs: {dinosaursCount++}.

+

You already know what to do when the counter reaches 42.

+ , + ); + }; + + setStateExportMessage(); + + const interval = setInterval(setStateExportMessage, 1000); + return () => clearInterval(interval); + } + case 'archive': { + setStatusMessage(

Creating an archive...

); + return; + } + case 'done': { + setStatusMessage(

Done!

); + return; + } + case 'cancelled': { + setStatusMessage(

Cancelled! Why? 😱🤯👏

); + return; + } + } + }, [status]); + return ( - } - title="Export Flipper debug data" - onClick={() => { - exportEverythingEverywhereAllAtOnceTracked(); - }} - small - /> + <> + { + setStatus(undefined); + }} + title="Exporting everything everywhere all at once" + footer={null}> + {statusMessage} + + } + title="Export Flipper debug data" + onClick={() => { + exportEverythingEverywhereAllAtOnceTracked(); + }} + small + /> + ); } diff --git a/desktop/flipper-ui-core/src/utils/exportData.tsx b/desktop/flipper-ui-core/src/utils/exportData.tsx index b67c7bd7f..628b90153 100644 --- a/desktop/flipper-ui-core/src/utils/exportData.tsx +++ b/desktop/flipper-ui-core/src/utils/exportData.tsx @@ -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']) {