Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 124 additions & 112 deletions frontend/src/pages/Vulnerabilities/Vulnerabilities.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import {
Box,
Button,
IconButton,
MenuItem,
Menu,
// To-do: Re-enable this as part of Status dropdown once the feature is approved.
// MenuItem,
// Menu,
Paper,
Stack,
Typography
Expand All @@ -24,7 +25,8 @@ import {
import CustomToolbar from 'components/DataGrid/CustomToolbar';
import CustomNoRowsOverlay from 'components/DataGrid/CustomNoRowsOverlay';
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
// To-do: Re-enable this as part of Status dropdown once the feature is approved.
// import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { getSeverityColor } from 'pages/Risk/utils';
import { differenceInCalendarDays, parseISO } from 'date-fns';
import { truncateString } from 'utils/dataTransformUtils';
Expand Down Expand Up @@ -80,34 +82,37 @@ export const Vulnerabilities: React.FC<{ groupBy?: string }> = ({
children?: React.ReactNode;
groupBy?: string;
}) => {
const { currentOrganization, apiPost, apiPut, user } = useAuthContext();
const { currentOrganization, apiPost, user } = useAuthContext();
// To-do: Re-enable this as part of Status dropdown once the feature is approved.
// const { apiPut } = useAuthContext();
const [vulnerabilities, setVulnerabilities] = useState<Vulnerability[]>([]);
const [totalResults, setTotalResults] = useState(0);
const [loadingError, setLoadingError] = useState(false);
const [isLoading, setIsLoading] = useState(false);

// TO-DO
// Implement regional rollup for vulnerabilities view to allow for proper vunl drilldown from dashboard
const updateVulnerability = useCallback(
async (index: number, body: { [key: string]: string }) => {
try {
const updatedVulns = await apiPut<Vulnerability>(
'/vulnerabilities/' + vulnerabilities[index].id,
{
body: body
}
);
setVulnerabilities((prevState) =>
prevState.map((orgVulns, targetIndex) =>
targetIndex === index ? updatedVulns : orgVulns
)
);
} catch (e) {
console.error(e);
}
},
[setVulnerabilities, apiPut, vulnerabilities]
);
// To-do: Re-enable this as part of Status dropdown once the feature is approved.
// const updateVulnerability = useCallback(
// async (index: number, body: { [key: string]: string }) => {
// try {
// const updatedVulns = await apiPut<Vulnerability>(
// '/vulnerabilities/' + vulnerabilities[index].id,
// {
// body: body
// }
// );
// setVulnerabilities((prevState) =>
// prevState.map((orgVulns, targetIndex) =>
// targetIndex === index ? updatedVulns : orgVulns
// )
// );
// } catch (e) {
// console.error(e);
// }
// },
// [setVulnerabilities, apiPut, vulnerabilities]
// );

const vulnerabilitiesSearch = useCallback(
async ({
Expand Down Expand Up @@ -281,38 +286,43 @@ export const Vulnerabilities: React.FC<{ groupBy?: string }> = ({
operator: filter.operator
}))
});

// To-do: Re-enable this as part of Status dropdown once the feature is approved.
// Row scoped menu state management for vulnerability status updates
interface MenuState {
[key: string]: {
anchorEl: HTMLElement | null;
open: boolean;
};
}

const [menuState, setMenuState] = useState<MenuState>({});

const handleMenuClick = (
event: React.MouseEvent<HTMLButtonElement>,
rowId: any
) => {
setMenuState((prev) => ({
...prev,
[rowId]: {
anchorEl: event.currentTarget,
open: true
}
}));
};

const handleClose = (rowId: any) => {
setMenuState((prev) => ({
...prev,
[rowId]: {
...prev[rowId],
open: false
}
}));
};
// interface MenuState {
// [key: string]: {
// anchorEl: HTMLElement | null;
// open: boolean;
// };
// }

// To-do: Re-enable this as part of Status dropdown once the feature is approved.
// const [menuState, setMenuState] = useState<MenuState>({});

// To-do: Re-enable this as part of Status dropdown once the feature is approved.
// const handleMenuClick = (
// event: React.MouseEvent<HTMLButtonElement>,
// rowId: any
// ) => {
// setMenuState((prev) => ({
// ...prev,
// [rowId]: {
// anchorEl: event.currentTarget,
// open: true
// }
// }));
// };

// To-do: Re-enable this as part of Status dropdown once the feature is approved.
// const handleClose = (rowId: any) => {
// setMenuState((prev) => ({
// ...prev,
// [rowId]: {
// ...prev[rowId],
// open: false
// }
// }));
// };

const resetVulnerabilities = useCallback(() => {
setInitialFilters([]);
Expand Down Expand Up @@ -569,63 +579,65 @@ export const Vulnerabilities: React.FC<{ groupBy?: string }> = ({
field: 'state',
headerName: 'Status',
minWidth: 100,
flex: 1,
renderCell: (cellValues: GridRenderCellParams) => {
const handleUpdate = (id: string, substate: string) => {
const index = vulnerabilities.findIndex((v) => v.id === id);
updateVulnerability(index, {
substate: substate
});
};

return (
<div>
<Button
id={`basic-button-${cellValues.row.id}`}
style={{ textDecorationLine: 'underline' }}
aria-controls={
menuState[cellValues.row.id]?.open
? `basic-menu-${cellValues.row.id}`
: undefined
}
aria-haspopup="true"
aria-expanded={
menuState[cellValues.row.id]?.open ? 'true' : undefined
}
tabIndex={cellValues.tabIndex}
endIcon={<ExpandMoreIcon />}
onClick={(event) => handleMenuClick(event, cellValues.row.id)}
>
{cellValues.row.state}
</Button>
<Menu
id={`basic-menu-${cellValues.row.id}`}
anchorEl={menuState[cellValues.row.id]?.anchorEl}
open={menuState[cellValues.row.id]?.open}
onClose={() => handleClose(cellValues.row.id)}
MenuListProps={{
'aria-labelledby': `basic-button-${cellValues.row.id}`
}}
>
{Object.keys(stateMap).map((substate) => (
<MenuItem
key={`${cellValues.row.id}-${substate}`}
id={`menu-item-${cellValues.row.id}-${substate}`}
onClick={() => {
handleUpdate(cellValues.row.id, substate);
handleClose(cellValues.row.id);
}}
>
{substate === 'unconfirmed' || substate === 'exploitable'
? 'Open'
: 'Closed'}
{` (${substate})`}
</MenuItem>
))}
</Menu>
</div>
);
}
flex: 1
// To-do: Re-enable this Status dropdown once the feature is approved.
// Summary: Per CRASM-1090, the status dropdown was removed in favor of static text.
// renderCell: (cellValues: GridRenderCellParams) => {
// const handleUpdate = (id: string, substate: string) => {
// const index = vulnerabilities.findIndex((v) => v.id === id);
// updateVulnerability(index, {
// substate: substate
// });
// };

// return (
// <div>
// <Button
// id={`basic-button-${cellValues.row.id}`}
// style={{ textDecorationLine: 'underline' }}
// aria-controls={
// menuState[cellValues.row.id]?.open
// ? `basic-menu-${cellValues.row.id}`
// : undefined
// }
// aria-haspopup="true"
// aria-expanded={
// menuState[cellValues.row.id]?.open ? 'true' : undefined
// }
// tabIndex={cellValues.tabIndex}
// endIcon={<ExpandMoreIcon />}
// onClick={(event) => handleMenuClick(event, cellValues.row.id)}
// >
// {cellValues.row.state}
// </Button>
// <Menu
// id={`basic-menu-${cellValues.row.id}`}
// anchorEl={menuState[cellValues.row.id]?.anchorEl}
// open={menuState[cellValues.row.id]?.open}
// onClose={() => handleClose(cellValues.row.id)}
// MenuListProps={{
// 'aria-labelledby': `basic-button-${cellValues.row.id}`
// }}
// >
// {Object.keys(stateMap).map((substate) => (
// <MenuItem
// key={`${cellValues.row.id}-${substate}`}
// id={`menu-item-${cellValues.row.id}-${substate}`}
// onClick={() => {
// handleUpdate(cellValues.row.id, substate);
// handleClose(cellValues.row.id);
// }}
// >
// {substate === 'unconfirmed' || substate === 'exploitable'
// ? 'Open'
// : 'Closed'}
// {` (${substate})`}
// </MenuItem>
// ))}
// </Menu>
// </div>
// );
// }
},
{
field: 'viewDetails',
Expand Down
Loading