Export data for the universal export as a single zip archive

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

It is better UX not to prompt a user multiple times to save various pieces of the debug export

Reviewed By: passy

Differential Revision: D40550084

fbshipit-source-id: 51ea3acee7daf5074682219020e1e1eed2182b7d
This commit is contained in:
Andrey Goncharov
2022-10-25 05:31:48 -07:00
committed by Facebook GitHub Bot
parent 821bf2b5b7
commit 6af6652ab2
6 changed files with 92 additions and 12 deletions

View File

@@ -162,10 +162,18 @@ export interface FSStatsLike {
birthtimeMs: number; birthtimeMs: number;
} }
export interface DeviceDebugFile {
path: string;
data: string;
}
export interface DeviceDebugCommand {
command: string;
result: string;
}
export interface DeviceDebugData { export interface DeviceDebugData {
serial: string; serial: string;
appId: string; appId: string;
data: ({path: string; data: string} | {command: string; result: string})[]; data: (DeviceDebugFile | DeviceDebugCommand)[];
} }
export type FlipperServerCommands = { export type FlipperServerCommands = {

View File

@@ -28,6 +28,7 @@
"hotkeys-js": "^3.9.3", "hotkeys-js": "^3.9.3",
"immer": "^9.0.12", "immer": "^9.0.12",
"js-base64": "^3.7.2", "js-base64": "^3.7.2",
"jszip": "^3.10.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lodash.memoize": "^4.1.2", "lodash.memoize": "^4.1.2",
"p-map": "^4.0.0", "p-map": "^4.0.0",

View File

@@ -0,0 +1,18 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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 {safeFilename} from '../safeFilename';
describe('safeFilename', () => {
test('replaces special chars in a string to make it a safe file name', async () => {
expect(safeFilename('/data/data/0/files/sonar/spec!al file name%')).toBe(
'-data-data-0-files-sonar-spec-al-file-name-',
);
});
});

View File

@@ -8,7 +8,7 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {getLogger} from 'flipper-common'; import {getLogger, DeviceDebugFile, DeviceDebugCommand} from 'flipper-common';
import {Store, MiddlewareAPI} from '../reducers'; import {Store, MiddlewareAPI} from '../reducers';
import {DeviceExport} from 'flipper-frontend-core'; import {DeviceExport} from 'flipper-frontend-core';
import {selectedPlugins, State as PluginsState} from '../reducers/plugins'; import {selectedPlugins, State as PluginsState} from '../reducers/plugins';
@@ -33,6 +33,8 @@ import ExportDataPluginSheet from '../chrome/ExportDataPluginSheet';
import {getRenderHostInstance} from 'flipper-frontend-core'; import {getRenderHostInstance} from 'flipper-frontend-core';
import {uploadFlipperMedia} from '../fb-stubs/user'; import {uploadFlipperMedia} from '../fb-stubs/user';
import {exportLogs} from '../chrome/ConsoleLogs'; import {exportLogs} from '../chrome/ConsoleLogs';
import JSZip from 'jszip';
import {safeFilename} from './safeFilename';
export const IMPORT_FLIPPER_TRACE_EVENT = 'import-flipper-trace'; export const IMPORT_FLIPPER_TRACE_EVENT = 'import-flipper-trace';
export const EXPORT_FLIPPER_TRACE_EVENT = 'export-flipper-trace'; export const EXPORT_FLIPPER_TRACE_EVENT = 'export-flipper-trace';
@@ -612,25 +614,50 @@ export async function startFlipperLogsExport() {
await getRenderHostInstance().exportFile?.(serializedLogs); await getRenderHostInstance().exportFile?.(serializedLogs);
} }
export async function startClientLogsExport() { async function startDeviceFlipperFolderExport() {
const _clientLogs = await getRenderHostInstance().flipperServer.exec( return await getRenderHostInstance().flipperServer.exec('fetch-debug-data');
'fetch-debug-data',
);
// TODO: Save all log files
} }
export async function exportEverythingEverywhereAllAtOnce( export async function exportEverythingEverywhereAllAtOnce(
store: MiddlewareAPI, store: MiddlewareAPI,
) { ) {
// TODO: Show a progress dialog // TODO: Show a progress dialog
// TODO: Pack all files in a single archive const zip = new JSZip();
// Step 1: Export Flipper logs // Step 1: Export Flipper logs
await startFlipperLogsExport(); const serializedLogs = exportLogs
.map((item) => JSON.stringify(item))
.join('\n');
zip.file('flipper_logs.txt', serializedLogs);
// Step 2: Export device logs // Step 2: Export device logs
await startClientLogsExport(); const flipperFolderContent = await startDeviceFlipperFolderExport();
const deviceFlipperFolder = zip.folder('device_flipper_folder')!;
flipperFolderContent.forEach((deviceDebugItem) => {
const deviceAppFolder = deviceFlipperFolder.folder(
safeFilename(`${deviceDebugItem.serial}__${deviceDebugItem.appId}`),
)!;
deviceDebugItem.data.forEach((appDebugItem) => {
const appDebugItemIsFile = (
item: DeviceDebugFile | DeviceDebugCommand,
): item is DeviceDebugFile => !!(appDebugItem as DeviceDebugFile).path;
if (appDebugItemIsFile(appDebugItem)) {
deviceAppFolder.file(
safeFilename(appDebugItem.path),
appDebugItem.data,
);
} else {
deviceAppFolder.file(
safeFilename(appDebugItem.command),
appDebugItem.result,
);
}
});
});
// Step 3: Export Flipper State // Step 3: Export Flipper State
// TODO: Export all plugins automatically // TODO: Export all plugins automatically
@@ -642,7 +669,12 @@ export async function exportEverythingEverywhereAllAtOnce(
// need to be cleaned up later in combination with SupportForm // need to be cleaned up later in combination with SupportForm
store.dispatch(selectedPlugins(plugins)); store.dispatch(selectedPlugins(plugins));
const {serializedString} = await exportStore(store); const {serializedString} = await exportStore(store);
await getRenderHostInstance().exportFile?.(serializedString);
zip.file('flipper_export', serializedString);
const archiveData = await zip.generateAsync({type: 'uint8array'});
await getRenderHostInstance().exportFileBinary?.(archiveData);
} }
export async function startFileExport(dispatch: Store['dispatch']) { export async function startFileExport(dispatch: Store['dispatch']) {

View File

@@ -0,0 +1,11 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
export const safeFilename = (rawFilename: string) =>
rawFilename.replace(/(\W+)/gi, '-');

View File

@@ -9965,6 +9965,16 @@ jszip@^3.1.0:
readable-stream "~2.3.6" readable-stream "~2.3.6"
set-immediate-shim "~1.0.1" set-immediate-shim "~1.0.1"
jszip@^3.10.1:
version "3.10.1"
resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2"
integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==
dependencies:
lie "~3.3.0"
pako "~1.0.2"
readable-stream "~2.3.6"
setimmediate "^1.0.5"
keyv@^3.0.0: keyv@^3.0.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9"