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:
committed by
Facebook GitHub Bot
parent
821bf2b5b7
commit
6af6652ab2
@@ -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 = {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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-',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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']) {
|
||||||
|
|||||||
11
desktop/flipper-ui-core/src/utils/safeFilename.tsx
Normal file
11
desktop/flipper-ui-core/src/utils/safeFilename.tsx
Normal 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, '-');
|
||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user