Files
flipper/desktop/plugins/public/network/request-mocking/ManageMockResponsePanel.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

223 lines
6.0 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,
useMemo,
useEffect,
useCallback,
} from 'react';
import {MockResponseDetails} from './MockResponseDetails';
import {NetworkRouteContext, Route} from './NetworkRouteManager';
import {RequestId} from '../types';
import {Checkbox, Modal, Tooltip, Button, Typography} from 'antd';
import {
NUX,
Layout,
DataList,
Toolbar,
createState,
useValue,
} from 'flipper-plugin';
import {CloseCircleOutlined, WarningOutlined} from '@ant-design/icons';
const {Text} = Typography;
type Props = {
routes: {[id: string]: Route};
};
type RouteItem = {
id: string;
title: string;
route: Route;
isDuplicate: boolean;
};
// return ids that have the same pair of requestUrl and method; this will return only the duplicate
function _duplicateIds(routes: {[id: string]: Route}): Array<RequestId> {
const idSet: {[id: string]: {[method: string]: boolean}} = {};
return Object.entries(routes).reduce((acc: Array<RequestId>, [id, route]) => {
if (idSet.hasOwnProperty(route.requestUrl)) {
if (idSet[route.requestUrl].hasOwnProperty(route.requestMethod)) {
return acc.concat(id);
}
idSet[route.requestUrl] = {
...idSet[route.requestUrl],
[route.requestMethod]: true,
};
return acc;
} else {
idSet[route.requestUrl] = {[route.requestMethod]: true};
return acc;
}
}, []);
}
export function ManageMockResponsePanel(props: Props) {
const networkRouteManager = useContext(NetworkRouteContext);
const [selectedIdAtom] = useState(() => createState<RequestId | undefined>());
const selectedId = useValue(selectedIdAtom);
useEffect(() => {
selectedIdAtom.update((selectedId) => {
const keys = Object.keys(props.routes);
let returnValue: string | undefined = undefined;
// selectId is undefined when there are no rows or it is the first time rows are shown
if (selectedId === undefined) {
if (keys.length === 0) {
// there are no rows
returnValue = undefined;
} else {
// first time rows are shown
returnValue = keys[0];
}
} else {
if (keys.includes(selectedId)) {
returnValue = selectedId;
} else {
// selectedId row value not in routes so default to first line
returnValue = keys[0];
}
}
return returnValue;
});
}, [props.routes, selectedIdAtom]);
const duplicatedIds = useMemo(
() => _duplicateIds(props.routes),
[props.routes],
);
const items: RouteItem[] = Object.entries(props.routes).map(
([id, route]) => ({
id,
route,
title: route.requestUrl,
isDuplicate: duplicatedIds.includes(id),
}),
);
const handleDelete = useCallback(
(id: string) => {
Modal.confirm({
title: 'Are you sure you want to delete this item?',
icon: '',
onOk() {
networkRouteManager.removeRoute(id);
selectedIdAtom.set(undefined);
},
onCancel() {},
});
},
[networkRouteManager, selectedIdAtom],
);
const handleToggle = useCallback(
(id: string) => {
networkRouteManager.enableRoute(id);
},
[networkRouteManager],
);
const handleRender = useCallback(
(item: RouteItem) => (
<RouteEntry item={item} onDelete={handleDelete} onToggle={handleToggle} />
),
[handleDelete, handleToggle],
);
const handleSelect = useCallback(
(id: string) => {
if (id) {
selectedIdAtom.set(id);
}
},
[selectedIdAtom],
);
return (
<Layout.Left resizable style={{minHeight: 400}}>
<Layout.Top>
<Toolbar>
<Button
onClick={() => {
const newId = networkRouteManager.addRoute();
selectedIdAtom.set(newId);
}}>
Add Route
</Button>
<NUX
title="It is now possible to select calls from the network call list and convert them into mock routes."
placement="bottom">
<Button
onClick={() => {
networkRouteManager.copySelectedCalls();
}}>
Copy Selected Calls
</Button>
</NUX>
<Button onClick={networkRouteManager.importRoutes}>Import</Button>
<Button onClick={networkRouteManager.exportRoutes}>Export</Button>
<Button onClick={networkRouteManager.clearRoutes}>Clear</Button>
</Toolbar>
<DataList
items={items}
selection={selectedId}
onRenderItem={handleRender}
onSelect={handleSelect}
scrollable
/>
</Layout.Top>
<Layout.Container gap pad>
{selectedId && props.routes.hasOwnProperty(selectedId) && (
<MockResponseDetails
id={selectedId}
route={props.routes[selectedId]}
isDuplicated={duplicatedIds.includes(selectedId)}
/>
)}
</Layout.Container>
</Layout.Left>
);
}
const RouteEntry = ({
item,
onToggle,
onDelete,
}: {
item: RouteItem;
onToggle(id: string): void;
onDelete(id: string): void;
}) => {
const tip = item.route.enabled
? 'Un-check to disable mock route'
: 'Check to enable mock route';
return (
<Layout.Horizontal gap center>
<Tooltip title={tip} mouseEnterDelay={1.1}>
<Checkbox
onClick={() => onToggle(item.id)}
checked={item.route.enabled}></Checkbox>
</Tooltip>
{item.route.requestUrl.length === 0 ? (
<Text ellipsis>untitled</Text>
) : (
<Text ellipsis>{item.route.requestUrl}</Text>
)}
<Tooltip title="Click to delete mock route" mouseEnterDelay={1.1}>
<Layout.Horizontal onClick={() => onDelete(item.id)}>
<CloseCircleOutlined />
</Layout.Horizontal>
</Tooltip>
{item.isDuplicate && <WarningOutlined />}
</Layout.Horizontal>
);
};