diff --git a/desktop/plugins/network/ManageMockResponsePanel.tsx b/desktop/plugins/network/ManageMockResponsePanel.tsx index 86c6b3fa9..10c735d20 100644 --- a/desktop/plugins/network/ManageMockResponsePanel.tsx +++ b/desktop/plugins/network/ManageMockResponsePanel.tsx @@ -38,7 +38,7 @@ const ColumnSizes = {route: 'flex'}; const Columns = {route: {value: 'Route', resizable: false}}; -const AddRouteButton = styled(FlexBox)({ +const Button = styled(FlexBox)({ color: colors.blackAlpha50, alignItems: 'center', padding: 5, @@ -48,16 +48,6 @@ const AddRouteButton = styled(FlexBox)({ textOverflow: 'ellipsis', }); -const CopyHighlightedCallsButton = styled(FlexBox)({ - color: colors.blueDark, - alignItems: 'center', - padding: 5, - flexShrink: 0, - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', -}); - const Container = styled(FlexRow)({ flex: 1, justifyContent: 'space-around', @@ -199,9 +189,9 @@ export function ManageMockResponsePanel(props: Props) { props.routes, ]); return ( - + - { networkRouteManager.addRoute(); }}> @@ -212,8 +202,8 @@ export function ManageMockResponsePanel(props: Props) { color={colors.blackAlpha30} />  Add Route - - +
- + { const newHeaders = { @@ -335,25 +336,27 @@ export function MockResponseDetails({id, route, isDuplicated}: Props) { />  Add Header - - + + + +
diff --git a/desktop/plugins/network/MockResponseDialog.tsx b/desktop/plugins/network/MockResponseDialog.tsx index 9616504b7..3d7aa10ec 100644 --- a/desktop/plugins/network/MockResponseDialog.tsx +++ b/desktop/plugins/network/MockResponseDialog.tsx @@ -7,12 +7,15 @@ * @format */ -import {FlexColumn, Button, styled, Layout} from 'flipper'; +import {FlexColumn, Button, styled, Layout, Spacer} from 'flipper'; import {ManageMockResponsePanel} from './ManageMockResponsePanel'; import {Route, Request, Response} from './types'; import React from 'react'; +import {NetworkRouteContext} from './index'; +import {useContext} from 'react'; + type Props = { routes: {[id: string]: Route}; onHide: () => void; @@ -39,6 +42,7 @@ const Row = styled(FlexColumn)({ }); export function MockResponseDialog(props: Props) { + const networkRouteManager = useContext(NetworkRouteContext); return ( Mock Network Responses @@ -50,7 +54,32 @@ export function MockResponseDialog(props: Props) { responses={props.responses} /> - + + + + + diff --git a/desktop/plugins/network/index.tsx b/desktop/plugins/network/index.tsx index 1f85830e3..e1fea2983 100644 --- a/desktop/plugins/network/index.tsx +++ b/desktop/plugins/network/index.tsx @@ -10,6 +10,7 @@ import {padStart} from 'lodash'; import React, {createContext} from 'react'; import {MenuItemConstructorOptions} from 'electron'; +import {message} from 'antd'; import { ContextMenu, @@ -45,6 +46,9 @@ import {URL} from 'url'; import {MockResponseDialog} from './MockResponseDialog'; import {combineBase64Chunks} from './chunks'; import {PluginClient, createState, usePlugin, useValue} from 'flipper-plugin'; +import {remote, OpenDialogOptions} from 'electron'; +import fs from 'fs'; +import electron from 'electron'; const LOCALSTORAGE_MOCK_ROUTE_LIST_KEY = '__NETWORK_CACHED_MOCK_ROUTE_LIST'; @@ -127,6 +131,9 @@ export interface NetworkRouteManager { requests: {[id: string]: Request}, responses: {[id: string]: Response}, ): void; + importRoutes(): void; + exportRoutes(): void; + clearRoutes(): void; } const nullNetworkRouteManager: NetworkRouteManager = { addRoute() {}, @@ -137,6 +144,9 @@ const nullNetworkRouteManager: NetworkRouteManager = { _requests: {[id: string]: Request}, _responses: {[id: string]: Response}, ) {}, + importRoutes() {}, + exportRoutes() {}, + clearRoutes() {}, }; export const NetworkRouteContext = createContext( nullNetworkRouteManager, @@ -385,6 +395,73 @@ export function plugin(client: PluginClient) { informClientMockChange(routes.get()); }, + importRoutes() { + const options: OpenDialogOptions = { + properties: ['openFile'], + filters: [{extensions: ['json'], name: 'Flipper Route Files'}], + }; + remote.dialog.showOpenDialog(options).then((result) => { + const filePaths = result.filePaths; + if (filePaths.length > 0) { + fs.readFile(filePaths[0], 'utf8', (err, data) => { + if (err) { + message.error('Unable to import file'); + return; + } + const importedRoutes = JSON.parse(data); + importedRoutes?.forEach((importedRoute: Route) => { + if (importedRoute != null) { + const newNextRouteId = nextRouteId.get(); + routes.update((draft) => { + draft[newNextRouteId.toString()] = { + requestUrl: importedRoute.requestUrl, + requestMethod: importedRoute.requestMethod, + responseData: importedRoute.responseData as string, + responseHeaders: importedRoute.responseHeaders, + responseStatus: importedRoute.responseStatus, + }; + }); + nextRouteId.set(newNextRouteId + 1); + } + }); + informClientMockChange(routes.get()); + }); + } + }); + }, + exportRoutes() { + remote.dialog + .showSaveDialog( + // @ts-ignore This appears to work but isn't allowed by the types + null, + { + title: 'Export Routes', + defaultPath: 'NetworkPluginRoutesExport.json', + }, + ) + .then((result: electron.SaveDialogReturnValue) => { + const file = result.filePath; + if (!file) { + return; + } + fs.writeFile( + file, + JSON.stringify(routes.get(), null, 2), + 'utf8', + (err) => { + if (err) { + message.error('Failed to store mock routes: ' + err); + } else { + message.info('Successfully exported mock routes'); + } + }, + ); + }); + }, + clearRoutes() { + routes.set({}); + informClientMockChange(routes.get()); + }, }); }