Files
flipper/desktop/plugins/public/network/request-mocking/MockResponseDetails.tsx
bizzguy 6c534f5749 Fix selection on mock list (#2574)
Summary:
Fix selection problem on ManageMockResponsePanel.  Currently, when the user adds new routes, the detail section continues to show the detail for the first route.  Select different routes in the list does not change the detail panel.  The screen is currently not doing anything on selection.

Also fixed a problem with updating on mock header names and values.  Updates were not persisting if user did not exist input fields before leaving header input panel.

## Changelog

Network Plugin - Fix selection problem on ManageMockResponsePanel

Pull Request resolved: https://github.com/facebook/flipper/pull/2574

Test Plan:
Add multiple mock routes
Select various routes
Verify that detail panel shows the selected route

![2021-07-06_21-43-56](https://user-images.githubusercontent.com/337874/124692775-cc169680-dea3-11eb-8a70-14457c5f6304.jpg)

Reviewed By: passy

Differential Revision: D29582286

Pulled By: mweststrate

fbshipit-source-id: 6690e2262a033cdfa5afa6e6fdfacc9694244590
2021-07-07 05:04:26 -07:00

220 lines
5.9 KiB
TypeScript

/**
* Copyright (c) Facebook, Inc. and its 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 React, {useContext, useState} from 'react';
import {
NetworkRouteContext,
NetworkRouteManager,
Route,
} from './NetworkRouteManager';
import {RequestId} from '../types';
import {Button, Input, Select} from 'antd';
import {Layout, produce, Tabs, Tab, theme} from 'flipper-plugin';
import {CloseCircleOutlined, WarningOutlined} from '@ant-design/icons';
type Props = {
id: RequestId;
route: Route;
isDuplicated: boolean;
};
function HeaderInput(props: {
initialValue: string;
onUpdate: (newValue: string) => void;
style?: React.CSSProperties;
}) {
const [value, setValue] = useState(props.initialValue);
return (
<Input
type="text"
placeholder="Name"
value={value}
onChange={(event) => {
setValue(event.target.value);
props.onUpdate(event.target.value);
}}
style={props.style}
/>
);
}
function ResponseHeaders({
routeId,
route,
networkRouteManager,
}: {
routeId: string;
route: Route;
networkRouteManager: NetworkRouteManager;
}) {
return (
<Layout.Container gap style={{paddingRight: theme.space.small}}>
{Object.entries(route.responseHeaders).map(([id, header]) => (
<Layout.Horizontal center gap key={id}>
<HeaderInput
initialValue={header.key}
onUpdate={(newValue: string) => {
const newHeaders = produce(
route.responseHeaders,
(draftHeaders) => {
draftHeaders[id].key = newValue;
},
);
networkRouteManager.modifyRoute(routeId, {
responseHeaders: newHeaders,
});
}}
style={{width: 300}}
/>
<HeaderInput
initialValue={header.value}
onUpdate={(newValue: string) => {
const newHeaders = produce(
route.responseHeaders,
(draftHeaders) => {
draftHeaders[id].value = newValue;
},
);
networkRouteManager.modifyRoute(routeId, {
responseHeaders: newHeaders,
});
}}
/>
<Layout.Container
onClick={() => {
const newHeaders = produce(
route.responseHeaders,
(draftHeaders) => {
delete draftHeaders[id];
},
);
networkRouteManager.modifyRoute(routeId, {
responseHeaders: newHeaders,
});
}}>
<CloseCircleOutlined />
</Layout.Container>
</Layout.Horizontal>
))}
</Layout.Container>
);
}
const httpMethods = [
'GET',
'POST',
'PATCH',
'HEAD',
'PUT',
'DELETE',
'TRACE',
'OPTIONS',
'CONNECT',
].map((v) => ({value: v, label: v}));
export function MockResponseDetails({id, route, isDuplicated}: Props) {
const networkRouteManager = useContext(NetworkRouteContext);
const [nextHeaderId, setNextHeaderId] = useState(0);
const {requestUrl, requestMethod, responseData, responseStatus} = route;
let formattedResponse = '';
try {
formattedResponse = JSON.stringify(JSON.parse(responseData), null, 2);
} catch (e) {
formattedResponse = responseData;
}
return (
<Layout.Container gap>
<Layout.Horizontal gap>
<Select
value={requestMethod}
options={httpMethods}
onChange={(text) =>
networkRouteManager.modifyRoute(id, {requestMethod: text})
}
/>
<Input
type="text"
placeholder="URL"
value={requestUrl}
onChange={(event) =>
networkRouteManager.modifyRoute(id, {
requestUrl: event.target.value,
})
}
style={{flex: 1}}
/>
<Input
type="text"
placeholder="STATUS"
value={responseStatus}
onChange={(event) =>
networkRouteManager.modifyRoute(id, {
responseStatus: event.target.value,
})
}
style={{width: 100}}
/>
</Layout.Horizontal>
{isDuplicated && (
<Layout.Horizontal gap>
<WarningOutlined />
Route is duplicated (Same URL and Method)
</Layout.Horizontal>
)}
<Layout.Container height={500}>
<Tabs grow>
<Tab tab={'Data'}>
<Input.TextArea
wrap="soft"
autoComplete="off"
spellCheck={false}
value={formattedResponse}
onChange={(event) =>
networkRouteManager.modifyRoute(id, {
responseData: event.target.value,
})
}
style={{flex: 1}}
/>
</Tab>
<Tab tab={'Headers'}>
<Layout.Top gap>
<Layout.Horizontal>
<Button
onClick={() => {
const newHeaders = {
...route.responseHeaders,
[nextHeaderId.toString()]: {key: '', value: ''},
};
setNextHeaderId(nextHeaderId + 1);
networkRouteManager.modifyRoute(id, {
responseHeaders: newHeaders,
});
}}>
Add Header
</Button>
</Layout.Horizontal>
<Layout.ScrollContainer>
<ResponseHeaders
routeId={id}
route={route}
networkRouteManager={networkRouteManager}
/>
</Layout.ScrollContainer>
</Layout.Top>
</Tab>
</Tabs>
</Layout.Container>
</Layout.Container>
);
}