(Server) Add MockResponseDetail and Package Used Inside

Summary:
- Add MockResponseDetail component to show and edit mocked route
- Add `immer` package which will be used for internal state handler

Change from PR
- Change to functional component

Note:
- This is a part of this PR: https://github.com/facebook/flipper/pull/488

Reviewed By: mweststrate

Differential Revision: D20440149

fbshipit-source-id: d7c35600b9a22cb62c2bdae7e19abe5c767e3670
This commit is contained in:
Chaiwat Ekkaewnumchai
2020-03-17 10:05:27 -07:00
committed by Facebook GitHub Bot
parent 59c821db8d
commit 95376a17b9
2 changed files with 340 additions and 4 deletions

View File

@@ -0,0 +1,334 @@
/**
* 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 {
FlexRow,
FlexColumn,
FlexBox,
Input,
Text,
Tabs,
Tab,
Glyph,
ManagedTable,
Select,
styled,
colors,
produce,
} from 'flipper';
import React, {useContext, useState} from 'react';
import {NetworkRouteContext, NetworkRouteManager} from './index';
import {RequestId, Route} from './types';
type Props = {
id: RequestId;
route: Route;
isDuplicated: boolean;
};
const StyledSelectContainer = styled(FlexRow)({
paddingLeft: 6,
paddingTop: 2,
paddingBottom: 24,
height: '100%',
flexGrow: 1,
});
const StyledSelect = styled(Select)({
height: '100%',
maxWidth: 200,
});
const StyledText = styled(Text)({
marginLeft: 6,
marginTop: 8,
});
const textAreaStyle: React.CSSProperties = {
width: '100%',
marginTop: 8,
height: 300,
fontSize: 15,
color: '#333',
padding: 10,
resize: 'none',
fontFamily:
'source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace',
display: 'inline-block',
lineHeight: 1.5,
border: '1px solid #dcdee2',
borderRadius: 4,
backgroundColor: '#fff',
cursor: 'text',
WebkitTapHighlightColor: 'transparent',
whiteSpace: 'pre-wrap',
overflowWrap: 'break-word',
};
const StyledInput = styled(Input)({
width: '100%',
height: 20,
marginLeft: 8,
flexGrow: 5,
});
const HeaderInput = styled(Input)({
width: '100%',
height: 20,
marginTop: 6,
marginBottom: 6,
});
const HeaderGlyph = styled(Glyph)({
marginTop: 6,
marginBottom: 6,
});
const Container = styled(FlexColumn)({
flexWrap: 'nowrap',
alignItems: 'flex-start',
alignContent: 'flex-start',
flexGrow: 1,
overflow: 'hidden',
});
const Warning = styled(FlexRow)({
marginTop: 8,
});
const AddHeaderButton = styled(FlexBox)({
color: colors.blackAlpha50,
marginTop: 8,
alignItems: 'center',
padding: 10,
flexShrink: 0,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
});
const HeadersColumnSizes = {
name: '40%',
value: '40%',
close: '10%',
warning: 'flex',
};
const HeadersColumns = {
name: {
value: 'Name',
resizable: false,
},
value: {
value: 'Value',
resizable: false,
},
close: {
value: '',
resizable: false,
},
warning: {
value: '',
resizable: false,
},
};
function _buildMockResponseHeaderRows(
routeId: string,
route: Route,
selectedHeaderId: string | null,
networkRouteManager: NetworkRouteManager,
) {
const selectedHighlight = {backgroundColor: colors.highlight};
return Object.entries(route.responseHeaders).map(([id, header]) => {
const selected = selectedHeaderId === id;
return {
columns: {
name: {
value: (
<HeaderInput
type="text"
placeholder="Name"
value={header.key}
style={selected ? selectedHighlight : {}}
onChange={event => {
if (!selected) {
return;
}
const newHeaders = produce(
route.responseHeaders,
draftHeaders => {
draftHeaders[id].key = event.target.value;
},
);
networkRouteManager.modifyRoute(routeId, {
responseHeaders: newHeaders,
});
}}
/>
),
},
value: {
value: (
<HeaderInput
type="text"
placeholder="Value"
value={header.value}
style={selected ? selectedHighlight : {}}
onChange={event => {
if (!selected) {
return;
}
const newHeaders = produce(
route.responseHeaders,
draftHeaders => {
draftHeaders[id].value = event.target.value;
},
);
networkRouteManager.modifyRoute(routeId, {
responseHeaders: newHeaders,
});
}}
/>
),
},
close: {
value: (
<FlexBox
onClick={() => {
const newHeaders = produce(
route.responseHeaders,
draftHeaders => {
delete draftHeaders[id];
},
);
networkRouteManager.modifyRoute(routeId, {
responseHeaders: newHeaders,
});
}}>
<HeaderGlyph name="cross-circle" color={colors.red} />
</FlexBox>
),
},
},
key: id,
};
});
}
export function MockResponseDetails({id, route, isDuplicated}: Props) {
const networkRouteManager = useContext(NetworkRouteContext);
const [activeTab, setActiveTab] = useState<string>('data');
const [selectedHeaderIds, setSelectedHeaderIds] = useState<Array<RequestId>>(
[],
);
const [nextHeaderId, setNextHeaderId] = useState(0);
const {requestUrl, requestMethod, responseData} = route;
return (
<Container>
<FlexRow style={{width: '100%'}}>
<StyledSelectContainer>
<StyledSelect
grow={true}
selected={requestMethod}
options={{GET: 'GET', POST: 'POST'}}
onChange={(text: string) =>
networkRouteManager.modifyRoute(id, {requestMethod: text})
}
/>
</StyledSelectContainer>
<StyledInput
type="text"
placeholder="URL"
value={requestUrl}
onChange={event =>
networkRouteManager.modifyRoute(id, {
requestUrl: event.target.value,
})
}
/>
</FlexRow>
{isDuplicated && (
<Warning>
<Glyph name="caution-triangle" color={colors.yellow} />
<Text style={{marginLeft: 5}}>
Route is duplicated (Same URL and Method)
</Text>
</Warning>
)}
<StyledText />
<Tabs
active={activeTab}
onActive={newActiveTab => {
if (newActiveTab != null) {
setActiveTab(newActiveTab);
}
}}>
<Tab key={'data'} label={'Data'}>
<textarea
style={textAreaStyle}
wrap="soft"
autoComplete="off"
spellCheck={false}
value={responseData}
onChange={event =>
networkRouteManager.modifyRoute(id, {
responseData: event.target.value,
})
}
/>
</Tab>
<Tab key={'headers'} label={'Headers'}>
<FlexColumn>
<ManagedTable
hideHeader={true}
multiline={true}
columnSizes={HeadersColumnSizes}
columns={HeadersColumns}
rows={_buildMockResponseHeaderRows(
id,
route,
selectedHeaderIds.length === 1 ? selectedHeaderIds[0] : null,
networkRouteManager,
)}
stickyBottom={true}
autoHeight={true}
floating={false}
// height={300}
zebra={false}
onRowHighlighted={setSelectedHeaderIds}
highlightedRows={new Set(selectedHeaderIds)}
/>
</FlexColumn>
<AddHeaderButton
onClick={() => {
const newHeaders = {
...route.responseHeaders,
[nextHeaderId.toString()]: {key: '', value: ''},
};
setNextHeaderId(nextHeaderId + 1);
networkRouteManager.modifyRoute(id, {
responseHeaders: newHeaders,
});
}}>
<Glyph
name="plus-circle"
size={16}
variant="outline"
color={colors.blackAlpha30}
/>
&nbsp;Add Header
</AddHeaderButton>
</Tab>
</Tabs>
</Container>
);
}