Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FI-2433: Collapse request details #448

Merged
merged 9 commits into from
Feb 23, 2024
48 changes: 33 additions & 15 deletions client/src/components/RequestDetailModal/CodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,50 @@
import { Container, Paper } from '@mui/material';
import React, { FC } from 'react';
import useStyles from './styles';
import { Box, Card, CardContent, CardHeader, Collapse, Divider } from '@mui/material';
import { RequestHeader } from '~/models/testSuiteModels';
import CopyButton from '~/components/_common/CopyButton';

import { formatBodyIfJSON } from './helpers';
import useStyles from './styles';
import CollapseButton from '../_common/CollapseButton';

export interface CodeBlockProps {
body?: string | null;
collapsedState?: boolean;
headers?: RequestHeader[] | null | undefined;
title?: string;
}

const CodeBlock: FC<CodeBlockProps> = ({ body, headers }) => {
const CodeBlock: FC<CodeBlockProps> = ({ body, collapsedState = false, headers, title }) => {
const { classes } = useStyles();
const [collapsed, setCollapsed] = React.useState(collapsedState);

if (body && body.length > 0) {
return (
<Container
component={Paper}
variant="outlined"
className={classes.codeblock}
data-testid="code-block"
>
<pre data-testid="pre">
<code data-testid="code" className={classes.code}>
{formatBodyIfJSON(body, headers)}
</code>
</pre>
</Container>
<Card variant="outlined" className={classes.codeblock} data-testid="code-block">
<CardHeader
subheader={title || 'Code'}
action={
<Box display="flex">
<CopyButton copyText={formatBodyIfJSON(body, headers)} size="small" />
<CollapseButton
setCollapsed={setCollapsed}
startState={collapsedState}
size="small"
/>
</Box>
}
/>
<Collapse in={!collapsed}>
<Divider />
<CardContent sx={{ pt: 0 }}>
<pre data-testid="pre">
<code data-testid="code" className={classes.code}>
{formatBodyIfJSON(body, headers)}
</code>
</pre>
</CardContent>
</Collapse>
</Card>
);
} else {
return null;
Expand Down
38 changes: 15 additions & 23 deletions client/src/components/RequestDetailModal/RequestDetailModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import {
DialogContent,
DialogTitle,
Divider,
IconButton,
Typography,
} from '@mui/material';
import { Request } from '~/models/testSuiteModels';
import { ContentCopy, Input } from '@mui/icons-material';
import { Input } from '@mui/icons-material';
import CustomTooltip from '~/components/_common/CustomTooltip';
import CodeBlock from './CodeBlock';
import HeaderTable from './HeaderTable';
import useStyles from './styles';
import CopyButton from '../_common/CopyButton';

export interface RequestDetailModalProps {
request?: Request;
Expand All @@ -30,19 +30,9 @@ const RequestDetailModal: FC<RequestDetailModalProps> = ({
modalVisible,
usedRequest,
}) => {
const [copySuccess, setCopySuccess] = React.useState(false);
const { classes } = useStyles();
const timestamp = request?.timestamp ? new Date(request?.timestamp) : null;

const copyTextClick = async (text: string) => {
await navigator.clipboard.writeText(text).then(() => {
setCopySuccess(true);
setTimeout(() => {
setCopySuccess(false);
}, 2000); // 2 second delay
});
};

const usedRequestIcon = (
<CustomTooltip title="This request was performed in another test and the result is used by this test">
<Input className={classes.inputIcon} />
Expand All @@ -64,15 +54,7 @@ const RequestDetailModal: FC<RequestDetailModalProps> = ({
<Box pr={1} className={classes.modalTitleURL}>
{request?.url}
</Box>
{request?.url && (
<CustomTooltip title={copySuccess ? 'Text copied!' : 'Copy text'}>
<Box pr={1}>
<IconButton color="secondary" onClick={() => void copyTextClick(request.url)}>
<ContentCopy fontSize="inherit" />
</IconButton>
</Box>
</CustomTooltip>
)}
{request?.url && <CopyButton copyText={request.url} />}
<Box display="flex" flexShrink={0}>
&#8594; {request?.status}
</Box>
Expand Down Expand Up @@ -105,14 +87,24 @@ const RequestDetailModal: FC<RequestDetailModalProps> = ({
</Typography>
{timestamp && <Typography variant="overline">{timestamp.toLocaleString()}</Typography>}
<HeaderTable headers={request.request_headers || []} />
<CodeBlock body={request.request_body} headers={request.request_headers} />
<CodeBlock
body={request.request_body}
collapsedState={true}
headers={request.request_headers}
title="Full Request"
tstrass marked this conversation as resolved.
Show resolved Hide resolved
/>
</Box>
<Box pb={3}>
<Typography variant="h5" component="h3" pb={2}>
Response
</Typography>
<HeaderTable headers={request.response_headers || []} />
<CodeBlock body={request.response_body} headers={request.response_headers} />
<CodeBlock
body={request.response_body}
collapsedState={true}
headers={request.response_headers}
title="Full Response"
/>
</Box>
</DialogContent>
<DialogActions>
Expand Down
3 changes: 1 addition & 2 deletions client/src/components/RequestDetailModal/styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ export default makeStyles()((_theme: Theme) => ({
fontWeight: 600,
},
codeblock: {
width: '100%',
overflow: 'auto',
fontSize: 'small',
marginTop: '10px',
marginTop: '16px',
},
code: {
textWrap: 'wrap',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React, { FC } from 'react';
import {
Box,
Button,
IconButton,
Typography,
TableRow,
TableCell,
Expand All @@ -11,13 +10,14 @@ import {
TableBody,
TableHead,
} from '@mui/material';
import { ContentCopy, Input, SaveAlt } from '@mui/icons-material';
import { Input, SaveAlt } from '@mui/icons-material';
import { Request } from '~/models/testSuiteModels';
import { getRequestDetails } from '~/api/RequestsApi';
import RequestDetailModal from '~/components/RequestDetailModal/RequestDetailModal';
import CustomTooltip from '~/components/_common/CustomTooltip';
import { useSnackbar } from 'notistack';
import useStyles from './styles';
import CopyButton from '~/components/_common/CopyButton';

interface RequestListProps {
resultId: string;
Expand All @@ -30,7 +30,6 @@ const RequestList: FC<RequestListProps> = ({ requests, resultId, updateRequest,
const { classes } = useStyles();
const { enqueueSnackbar } = useSnackbar();
const [showDetails, setShowDetails] = React.useState(false);
const [copySuccess, setCopySuccess] = React.useState({});
const [detailedRequest, setDetailedRequest] = React.useState<Request>();
const headerTitles = ['Type', 'URL', 'Status'];

Expand All @@ -55,16 +54,6 @@ const RequestList: FC<RequestListProps> = ({ requests, resultId, updateRequest,
}
};

const copyTextClick = async (text: string) => {
await navigator.clipboard.writeText(text).then(() => {
setCopySuccess({ ...copySuccess, [text]: true });
setTimeout(() => {
// Reset map instead of setting false to avoid async bug
setCopySuccess({});
}, 2000); // 2 second delay
});
};

const renderReferenceIcon = (request: Request) => {
if (request.result_id !== resultId) {
return (
Expand Down Expand Up @@ -109,7 +98,7 @@ const RequestList: FC<RequestListProps> = ({ requests, resultId, updateRequest,
role="none"
aria-hidden="true"
aria-label="header-request-direction"
></TableCell>
/>
{view === 'run' && (
<TableCell
key={'Details'}
Expand Down Expand Up @@ -155,29 +144,7 @@ const RequestList: FC<RequestListProps> = ({ requests, resultId, updateRequest,
{request.url}
</Typography>
</CustomTooltip>
<CustomTooltip
title={
copySuccess[request.url as keyof typeof copySuccess] ? 'Text copied!' : 'Copy text'
}
sx={
view === 'report'
? {
display: 'none',
'@media print': {
display: 'none',
},
}
: {}
}
>
<IconButton
size="small"
color="secondary"
onClick={() => void copyTextClick(request.url)}
>
<ContentCopy fontSize="inherit" />
</IconButton>
</CustomTooltip>
<CopyButton copyText={request.url} size="small" view={view} />
</Box>
</TableCell>
<TableCell>
Expand Down
54 changes: 54 additions & 0 deletions client/src/components/_common/CollapseButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React, { FC, useEffect } from 'react';
import { IconButton, IconButtonPropsSizeOverrides } from '@mui/material';
import { OverridableStringUnion } from '@mui/types';
import { ExpandLess, ExpandMore } from '@mui/icons-material';
import CustomTooltip from '~/components/_common/CustomTooltip';

export interface CollapseButtonProps {
setCollapsed: (collapsed: boolean) => void;
size?: OverridableStringUnion<'small' | 'large' | 'medium', IconButtonPropsSizeOverrides>;
startState?: boolean;
view?: string;
}

const CollapseButton: FC<CollapseButtonProps> = ({
setCollapsed: setParentCollapsed,
size,
startState = false,
view,
}) => {
const [collapsed, setCollapsed] = React.useState(startState);

useEffect(() => {
setParentCollapsed(collapsed);
}, [collapsed]);

return (
<>
tstrass marked this conversation as resolved.
Show resolved Hide resolved
<CustomTooltip
title={collapsed ? 'Expand panel' : 'Collapse panel'}
sx={
view === 'report'
? {
display: 'none',
'@media print': {
display: 'none',
},
}
: {}
}
tstrass marked this conversation as resolved.
Show resolved Hide resolved
>
<IconButton
size={size}
color="secondary"
aria-label={collapsed ? 'expand button' : 'collapse button'}
onClick={() => setCollapsed(!collapsed)}
>
{collapsed ? <ExpandMore fontSize="inherit" /> : <ExpandLess fontSize="inherit" />}
</IconButton>
</CustomTooltip>
</>
);
};

export default CollapseButton;
53 changes: 53 additions & 0 deletions client/src/components/_common/CopyButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { FC } from 'react';
import { Box, IconButton, IconButtonPropsSizeOverrides } from '@mui/material';
import { OverridableStringUnion } from '@mui/types';
import { ContentCopy } from '@mui/icons-material';
import CustomTooltip from '~/components/_common/CustomTooltip';

export interface CopyButtonProps {
copyText: string;
size?: OverridableStringUnion<'small' | 'large' | 'medium', IconButtonPropsSizeOverrides>;
view?: string;
}

const CopyButton: FC<CopyButtonProps> = ({ copyText, size, view }) => {
const [copySuccess, setCopySuccess] = React.useState({}); // Use map for lists of copiable items

const copyTextClick = async (text: string) => {
await navigator.clipboard.writeText(text).then(() => {
setCopySuccess({ ...copySuccess, [text]: true });
setTimeout(() => {
// Reset map instead of setting false to avoid async bug
setCopySuccess({});
}, 2000); // 2 second delay
});
};
return (
<CustomTooltip
title={copySuccess[copyText as keyof typeof copySuccess] ? 'Text copied!' : 'Copy text'}
sx={
view === 'report'
? {
display: 'none',
'@media print': {
display: 'none',
},
}
: {}
}
>
<Box pr={1}>
<IconButton
size={size}
color="secondary"
aria-label="copy button"
onClick={() => void copyTextClick(copyText)}
>
<ContentCopy fontSize="inherit" />
</IconButton>
</Box>
</CustomTooltip>
);
};

export default CopyButton;
Loading