Create and upload a universal export when using the support request link

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

Reviewed By: passy

Differential Revision: D40586468

fbshipit-source-id: 4d6a8706c7d1cad1951bda701c51f0998c985628
This commit is contained in:
Andrey Goncharov
2022-10-25 05:31:48 -07:00
committed by Facebook GitHub Bot
parent 970c03d942
commit 778a56d7ac
3 changed files with 152 additions and 46 deletions

View File

@@ -12,11 +12,12 @@ import {State} from '../reducers';
export type SupportRequestDetails = { export type SupportRequestDetails = {
title?: string; title?: string;
whatAlreadyTried?: string; whatAlreadyTried?: string;
everythingEverywhereAllAtOnceExportDownloadURL?: string;
}; };
export default function openSupportRequestForm( export default function openSupportRequestForm(
_state: State, _state: State,
_details?: SupportRequestDetails, _details?: SupportRequestDetails,
): void { ): Promise<void> {
throw new Error('Not implemented!'); throw new Error('Not implemented!');
} }

View File

@@ -255,8 +255,6 @@ function ExtrasMenu() {
const {showWelcomeAtStartup} = settings; const {showWelcomeAtStartup} = settings;
const [welcomeVisible, setWelcomeVisible] = useState(showWelcomeAtStartup); const [welcomeVisible, setWelcomeVisible] = useState(showWelcomeAtStartup);
const fullState = useStore((state) => state);
return ( return (
<> <>
<NUX <NUX
@@ -305,21 +303,7 @@ function ExtrasMenu() {
</Menu.Item> </Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>
<Menu.Divider /> <Menu.Divider />
{config.isFBBuild ? ( {config.isFBBuild ? <OpenSupportRequestMenuItem /> : null}
<>
<Menu.Item
key="feedback"
onClick={() => {
getLogger().track('usage', 'support-form-source', {
source: 'sidebar',
group: undefined,
});
openSupportRequestForm(fullState);
}}>
Feedback
</Menu.Item>
</>
) : null}
<Menu.Item key="settings" onClick={() => setShowSettings(true)}> <Menu.Item key="settings" onClick={() => setShowSettings(true)}>
Settings Settings
</Menu.Item> </Menu.Item>
@@ -433,21 +417,51 @@ function DebugLogsButton({
); );
} }
function ExportEverythingEverywhereAllAtOnceButton() { function OpenSupportRequestMenuItem() {
const store = useStore(); const store = useStore();
const [status, setStatus] = useState< const [status, setStatus] = useState<
ExportEverythingEverywhereAllAtOnceStatus | undefined ExportEverythingEverywhereAllAtOnceStatus | undefined
>(); >();
return (
<>
<ExportEverythingEverywhereAllAtOnceStatusModal
status={status}
setStatus={setStatus}
/>
<Menu.Item
key="feedback"
onClick={async () => {
getLogger().track('usage', 'support-form-source', {
source: 'sidebar',
group: undefined,
});
await exportEverythingEverywhereAllAtOnce(
store,
(...args) => setStatus(args),
true,
);
}}>
Feedback
</Menu.Item>
</>
);
}
function ExportEverythingEverywhereAllAtOnceStatusModal({
status,
setStatus,
}: {
status: ExportEverythingEverywhereAllAtOnceStatus | undefined;
setStatus: (
newStatus: ExportEverythingEverywhereAllAtOnceStatus | undefined,
) => void;
}) {
const [statusMessage, setStatusMessage] = useState<JSX.Element | undefined>(); const [statusMessage, setStatusMessage] = useState<JSX.Element | undefined>();
const exportEverythingEverywhereAllAtOnceTracked = useTrackedCallback(
'Debug data export',
() => exportEverythingEverywhereAllAtOnce(store, setStatus),
[store, setStatus],
);
useEffect(() => { useEffect(() => {
switch (status) { switch (status?.[0]) {
case 'logs': { case 'logs': {
setStatusMessage(<p>Exporting Flipper logs...</p>); setStatusMessage(<p>Exporting Flipper logs...</p>);
return; return;
@@ -492,6 +506,23 @@ function ExportEverythingEverywhereAllAtOnceButton() {
setStatusMessage(<p>Creating an archive...</p>); setStatusMessage(<p>Creating an archive...</p>);
return; return;
} }
case 'upload': {
setStatusMessage(<p>Uploading the archive...</p>);
return;
}
case 'support': {
setStatusMessage(<p>Creating a support request...</p>);
return;
}
case 'error': {
setStatusMessage(
<>
<p>Oops! Something went wrong.</p>
<p>{status[1]}</p>
</>,
);
return;
}
case 'done': { case 'done': {
setStatusMessage(<p>Done!</p>); setStatusMessage(<p>Done!</p>);
return; return;
@@ -503,18 +534,39 @@ function ExportEverythingEverywhereAllAtOnceButton() {
} }
}, [status]); }, [status]);
return (
<Modal
visible={!!status}
centered
onCancel={() => {
setStatus(undefined);
}}
title="Exporting everything everywhere all at once"
footer={null}>
{statusMessage}
</Modal>
);
}
function ExportEverythingEverywhereAllAtOnceButton() {
const store = useStore();
const [status, setStatus] = useState<
ExportEverythingEverywhereAllAtOnceStatus | undefined
>();
const exportEverythingEverywhereAllAtOnceTracked = useTrackedCallback(
'Debug data export',
() =>
exportEverythingEverywhereAllAtOnce(store, (...args) => setStatus(args)),
[store, setStatus],
);
return ( return (
<> <>
<Modal <ExportEverythingEverywhereAllAtOnceStatusModal
visible={!!status} status={status}
centered setStatus={setStatus}
onCancel={() => { />
setStatus(undefined);
}}
title="Exporting everything everywhere all at once"
footer={null}>
{statusMessage}
</Modal>
<NUX title="Press this button if you have issues with Flipper. It will collect Flipper debug data that you can send to the Flipper team to get help."> <NUX title="Press this button if you have issues with Flipper. It will collect Flipper debug data that you can send to the Flipper team to get help.">
<LeftRailButton <LeftRailButton
icon={<BugOutlined />} icon={<BugOutlined />}

View File

@@ -13,6 +13,7 @@ import {
DeviceDebugFile, DeviceDebugFile,
DeviceDebugCommand, DeviceDebugCommand,
timeout, timeout,
getStringFromErrorLike,
} from 'flipper-common'; } 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';
@@ -41,6 +42,8 @@ import {exportLogs} from '../chrome/ConsoleLogs';
import JSZip from 'jszip'; import JSZip from 'jszip';
import {safeFilename} from './safeFilename'; import {safeFilename} from './safeFilename';
import {getExportablePlugins} from '../selectors/connections'; import {getExportablePlugins} from '../selectors/connections';
import {notification} from 'antd';
import openSupportRequestForm from '../fb-stubs/openSupportRequestForm';
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';
@@ -621,15 +624,19 @@ async function startDeviceFlipperFolderExport() {
} }
export type ExportEverythingEverywhereAllAtOnceStatus = export type ExportEverythingEverywhereAllAtOnceStatus =
| 'logs' | ['logs']
| 'files' | ['files']
| 'state' | ['state']
| 'archive' | ['archive']
| 'done' | ['upload']
| 'cancelled'; | ['support']
| ['done']
| ['error', string]
| ['cancelled'];
export async function exportEverythingEverywhereAllAtOnce( export async function exportEverythingEverywhereAllAtOnce(
store: MiddlewareAPI, store: MiddlewareAPI,
onStatusUpdate?: (status: ExportEverythingEverywhereAllAtOnceStatus) => void, onStatusUpdate?: (...args: ExportEverythingEverywhereAllAtOnceStatus) => void,
openSupportRequest?: boolean,
) { ) {
const zip = new JSZip(); const zip = new JSZip();
@@ -704,10 +711,56 @@ export async function exportEverythingEverywhereAllAtOnce(
}, },
); );
if (exportedFilePath) { if (openSupportRequest) {
onStatusUpdate?.('done'); if (exportedFilePath) {
onStatusUpdate?.('upload');
let everythingEverywhereAllAtOnceExportDownloadURL: string | undefined;
try {
everythingEverywhereAllAtOnceExportDownloadURL =
await getRenderHostInstance().flipperServer.exec(
'intern-cloud-upload',
exportedFilePath,
);
} catch (e) {
console.error(
'exportEverythingEverywhereAllAtOnce -> failed to upload export to intern',
exportedFilePath,
);
notification.warn({
message: 'Failed to upload debug data',
description: `Flipper failed to upload debug export (${exportedFilePath}) automatically. Please, attach it to the support request manually in the comments after it is created.`,
duration: null,
});
}
onStatusUpdate?.('support');
try {
await openSupportRequestForm(store.getState(), {
everythingEverywhereAllAtOnceExportDownloadURL,
});
onStatusUpdate?.('done');
} catch (e) {
console.error(
'exportEverythingEverywhereAllAtOnce -> failed to create a support request',
e,
);
onStatusUpdate?.('error', getStringFromErrorLike(e));
}
} else {
notification.warn({
message: 'Export cancelled',
description: `Exporting Flipper debug data was cancelled. Flipper team will not be able to help you without this data. Please, restart the export.`,
duration: null,
});
onStatusUpdate?.('cancelled');
}
} else { } else {
onStatusUpdate?.('cancelled'); if (exportedFilePath) {
onStatusUpdate?.('done');
} else {
onStatusUpdate?.('cancelled');
}
} }
} }