diff --git a/app/src/components/elements/CollapsePaper.js b/app/src/components/elements/CollapsePaper.js new file mode 100644 index 00000000..71c1035e --- /dev/null +++ b/app/src/components/elements/CollapsePaper.js @@ -0,0 +1,60 @@ +import { + KeyboardArrowDownRounded, + KeyboardArrowUpRounded, +} from "@mui/icons-material"; +import { Badge, Box, Collapse, IconButton, Paper } from "@mui/material"; +import { useState } from "react"; + +export function CollapsePaper({ + title, + count, + children, + defaultExpanded = false, + sx, +}) { + const [expanded, setExpanded] = useState(defaultExpanded); + + return ( + + { + if (e.target === e.currentTarget) { + setExpanded(!expanded); + } + }} + > + setExpanded(!expanded)} + sx={{ + alignItems: "center", + gap: "10px", + "& span": { + position: "relative", + transform: "scale(1)", + backgroundColor: "dimgray", + }, + }} + > + {title} + + setExpanded(!expanded)} + > + {expanded ? : } + + + + {children} + + + ); +} diff --git a/app/src/components/elements/ColorSlider.js b/app/src/components/elements/ColorSlider.js index 36cf3ec6..eaf2c9c0 100644 --- a/app/src/components/elements/ColorSlider.js +++ b/app/src/components/elements/ColorSlider.js @@ -25,7 +25,12 @@ const baseMarks = [ }, ]; -export function ColorSlider({ marks, defaultValue, onChange }) { +export function ColorSlider({ + marks, + defaultValue, + onChange, + hideDescription, +}) { const joinedMarks = marks.map((m, i) => ({ ...(baseMarks.length > i ? baseMarks[i] : {}), ...m, @@ -43,7 +48,8 @@ export function ColorSlider({ marks, defaultValue, onChange }) { marks={joinedMarks} min={0} max={4} - valueLabelDisplay="off" + // valueLabelFormat={(v, i) => joinedMarks[i].label} + // valueLabelDisplay="on" onChange={(e) => { setSelectedMark( joinedMarks.find((m) => m.value === e.target.value) @@ -53,7 +59,7 @@ export function ColorSlider({ marks, defaultValue, onChange }) { sx={{ color: selectedMark?.color || "primary" }} /> - {selectedMark?.description && ( + {!hideDescription && selectedMark?.description && ( theme.palette.review.text, + }} + variant="outlined" + label={user.name} + icon={ + <> + {user?.slackUrl && ( + + + + )} + {user?.mail && ( + + + + )} + + } + /> + ); +} diff --git a/app/src/components/elements/modal/ModalManager.js b/app/src/components/elements/modal/ModalManager.js index 1a2d1bae..38db7db6 100644 --- a/app/src/components/elements/modal/ModalManager.js +++ b/app/src/components/elements/modal/ModalManager.js @@ -10,7 +10,6 @@ import { ChangeReviewer } from "../../model/modals/ChangeReviewer"; import { Tutorial } from "../../model/tutorial/Tutorial"; import { CancelReview } from "../../reviews/modals/CancelReview"; import { DeclineReview } from "../../reviews/modals/DeclineReview"; -import { ViewActionItems } from "../../model/modals/ViewActionItems"; export const MODALS = { ChangeReviewer, @@ -23,7 +22,6 @@ export const MODALS = { DeleteSelected, DeclineReview, CancelReview, - ViewActionItems, }; export function ModalManager() { diff --git a/app/src/components/model/hooks/useActionItems.js b/app/src/components/model/hooks/useActionItems.js new file mode 100644 index 00000000..a8314bb7 --- /dev/null +++ b/app/src/components/model/hooks/useActionItems.js @@ -0,0 +1,22 @@ +import { useListThreatsQuery } from "../../../api/gram/threats"; +import { useModelID } from "./useModelID"; + +export function useActionItems() { + const modelId = useModelID(); + const { data: threats } = useListThreatsQuery({ modelId }); + + const actionItems = threats?.threats + ? Object.keys(threats?.threats) + .map((componentId) => ({ + componentId, + threats: threats?.threats[componentId].filter( + (th) => th.isActionItem + ), + })) + .filter(({ threats }) => threats && threats.length > 0) + : []; + + console.log(actionItems); + + return actionItems; +} diff --git a/app/src/components/model/modals/ApproveReview.js b/app/src/components/model/modals/ApproveReview.js index 20f99b14..2da5becc 100644 --- a/app/src/components/model/modals/ApproveReview.js +++ b/app/src/components/model/modals/ApproveReview.js @@ -20,89 +20,11 @@ import { useGetReviewQuery, } from "../../../api/gram/review"; import { modalActions } from "../../../redux/modalSlice"; -import { ColorSlider } from "../../elements/ColorSlider"; import { LoadingPage } from "../../elements/loading/loading-page/LoadingPage"; import { PERMISSIONS } from "../constants"; -import { ActionItemList } from "./ActionItemList"; - -function LikelihoodSlider({ onChange }) { - const marks = [ - { - label: "Rare", - description: `➢ This will probably never happen/recur -➢ Every 25 years`, - }, - { - label: "Unlikely", - description: `➢ This is not likely to happen/recur but could -➢ Every 10 years`, - }, - { - label: "Occasional", - description: `➢ This is unexpected to happen/recur but is certainly possible to occur -➢ Every 5 years`, - }, - { - label: "Likely", - description: `➢ This will probably happen/recur but is not a persisting issue. -➢ Every 3 years`, - }, - { - label: "Almost certain", - description: `➢ This will undoubtedly happen/recur -➢ Every year`, - }, - ]; - - return ( - <> - onChange(marks[e.target.value])} - /> - - ); -} - -function ImpactSlider({ onChange }) { - const marks = [ - { - label: "Very low", - description: `➢ Users can not interact with the service <1h -➢ No regulatory sanctions/fines`, - }, - { - label: "Low", - description: `➢ Users can not interact with the service <1-4h -➢ Incident reviewed by authorities but dismissed`, - }, - { - label: "Medium", - description: `➢ Users can not interact with the service <4-10h -➢ Incident reviewed by authorities and regulatory warning`, - }, - { - label: "High", - description: `➢ Users can not interact with the service <10-16h -➢ Incident reviewed by authorities and sanctions/fines imposed`, - }, - { - label: "Very high", - description: `➢ Users can not interact with the service >16h -➢ Incident reviewed by authorities and sanctions/fines threaten operations / Loss of licence`, - }, - ]; - - return ( - onChange(marks[e.target.value])} - /> - ); -} +import { ActionItemList } from "../panels/left/ActionItemList"; +import { ImpactSlider } from "./ImpactSlider"; +import { LikelihoodSlider } from "./LikelihoodSlider"; export function ApproveReview({ modelId }) { const dispatch = useDispatch(); @@ -145,40 +67,8 @@ export function ApproveReview({ modelId }) { {(isUninitialized || isLoading) && ( <> - Risk Evaluation - {/* - Every system threat model is connected to a risk ticket. When you - approve this threat model, it will be automatically created for - you (if the model is connected to a system). - */} - {/*
*/} - - Based on the threat model, set the risk value as your estimate of - the overall risk of all threats/controls found in the threat - model. - - -
- - Impact - - setExtras({ ...extras, impact: value.label }) - } - /> - -
- - Likelihood - - setExtras({ ...extras, likelihood: value.label }) - } - /> - -
Action Items - +
Summary diff --git a/app/src/components/model/modals/ImpactSlider.js b/app/src/components/model/modals/ImpactSlider.js new file mode 100644 index 00000000..736a4fe4 --- /dev/null +++ b/app/src/components/model/modals/ImpactSlider.js @@ -0,0 +1,41 @@ +import { ColorSlider } from "../../elements/ColorSlider"; + +export function ImpactSlider({ onChange, ...props }) { + const marks = [ + { + label: "Very low", + description: `➢ Users can not interact with the service <1h +➢ No regulatory sanctions/fines`, + }, + { + label: "Low", + description: `➢ Users can not interact with the service <1-4h +➢ Incident reviewed by authorities but dismissed`, + }, + { + label: "Medium", + description: `➢ Users can not interact with the service <4-10h +➢ Incident reviewed by authorities and regulatory warning`, + }, + { + label: "High", + description: `➢ Users can not interact with the service <10-16h +➢ Incident reviewed by authorities and sanctions/fines imposed`, + }, + { + label: "Very high", + description: `➢ Users can not interact with the service >16h +➢ Incident reviewed by authorities and sanctions/fines threaten operations / Loss of licence`, + }, + ]; + + return ( + onChange(marks[e.target.value])} + {...props} + /> + ); +} diff --git a/app/src/components/model/modals/LikelihoodSlider.js b/app/src/components/model/modals/LikelihoodSlider.js new file mode 100644 index 00000000..6d823f40 --- /dev/null +++ b/app/src/components/model/modals/LikelihoodSlider.js @@ -0,0 +1,40 @@ +import { ColorSlider } from "../../elements/ColorSlider"; + +export function LikelihoodSlider({ onChange, ...props }) { + const marks = [ + { + label: "Rare", + description: `➢ This will probably never happen/recur +➢ Every 25 years`, + }, + { + label: "Unlikely", + description: `➢ This is not likely to happen/recur but could +➢ Every 10 years`, + }, + { + label: "Occasional", + description: `➢ This is unexpected to happen/recur but is certainly possible to occur +➢ Every 5 years`, + }, + { + label: "Likely", + description: `➢ This will probably happen/recur but is not a persisting issue. +➢ Every 3 years`, + }, + { + label: "Almost certain", + description: `➢ This will undoubtedly happen/recur +➢ Every year`, + }, + ]; + + return ( + onChange(marks[e.target.value])} + {...props} + /> + ); +} diff --git a/app/src/components/model/modals/ViewActionItems.js b/app/src/components/model/modals/ViewActionItems.js deleted file mode 100644 index eb592bec..00000000 --- a/app/src/components/model/modals/ViewActionItems.js +++ /dev/null @@ -1,39 +0,0 @@ -import { AssignmentTurnedIn as AssignmentTurnedInIcon } from "@mui/icons-material"; -import { - Box, - Button, - Dialog, - DialogActions, - DialogContent, - DialogTitle, - Typography, -} from "@mui/material"; -import { useDispatch } from "react-redux"; -import { modalActions } from "../../../redux/modalSlice"; -import { ActionItemList } from "./ActionItemList"; - -export function ViewActionItems({ modelId }) { - const dispatch = useDispatch(); - - return ( - - - - - Action Items - - - - - - - - - - ); -} diff --git a/app/src/components/model/modals/ActionItemList.js b/app/src/components/model/panels/left/ActionItemList.js similarity index 50% rename from app/src/components/model/modals/ActionItemList.js rename to app/src/components/model/panels/left/ActionItemList.js index b2794f83..64999e83 100644 --- a/app/src/components/model/modals/ActionItemList.js +++ b/app/src/components/model/panels/left/ActionItemList.js @@ -1,37 +1,50 @@ import AssignmentTurnedInIcon from "@mui/icons-material/AssignmentTurnedIn"; -import { Box, DialogContentText, Typography } from "@mui/material"; -import { useListThreatsQuery } from "../../../api/gram/threats"; -import { useComponent } from "../hooks/useComponent"; -import { Threat } from "../panels/right/Threat"; -import { useModelID } from "../hooks/useModelID"; +import { + Badge, + Box, + Collapse, + DialogContentText, + IconButton, + Paper, + Stack, + Typography, +} from "@mui/material"; +import { useActionItems } from "../../hooks/useActionItems"; +import { useComponent } from "../../hooks/useComponent"; +import { Threat } from "../right/Threat"; +import { useState } from "react"; +import { + KeyboardArrowDownRounded, + KeyboardArrowUpRounded, +} from "@mui/icons-material"; +import { CollapsePaper } from "../../../elements/CollapsePaper"; -function ComponentActionItem({ componentId, threats }) { +function ComponentActionItem({ + componentId, + threats, + defaultExpanded = false, +}) { const component = useComponent(componentId); return ( - {component.name} - {threats.map((th) => ( - - ))} + + + {threats.map((th, i) => ( + + ))} + + ); } -export function ActionItemList() { - const modelId = useModelID(); - const { data: threats } = useListThreatsQuery({ modelId }); - - const actionItems = threats?.threats - ? Object.keys(threats?.threats) - .map((componentId) => ({ - componentId, - threats: threats?.threats[componentId].filter( - (th) => th.isActionItem - ), - })) - .filter(({ threats }) => threats && threats.length > 0) - : []; +export function ActionItemList({ automaticallyExpanded = false }) { + const actionItems = useActionItems(); return ( <> @@ -42,7 +55,11 @@ export function ActionItemList() {
{actionItems.map(({ componentId, threats }) => ( - + ))} )} diff --git a/app/src/components/model/panels/left/ActionItemTab.js b/app/src/components/model/panels/left/ActionItemTab.js new file mode 100644 index 00000000..f291cd10 --- /dev/null +++ b/app/src/components/model/panels/left/ActionItemTab.js @@ -0,0 +1,15 @@ +import { Box } from "@mui/material"; +import { ActionItemList } from "./ActionItemList"; + +export function ActionItemTab() { + return ( + + + + ); +} diff --git a/app/src/components/model/panels/left/Footer.js b/app/src/components/model/panels/left/Footer.js index 94d036c5..f988c664 100644 --- a/app/src/components/model/panels/left/Footer.js +++ b/app/src/components/model/panels/left/Footer.js @@ -2,6 +2,7 @@ import { DeleteRounded as DeleteRoundedIcon, HelpRounded as HelpRoundedIcon, } from "@mui/icons-material"; +// import IosShareIcon from "@mui/icons-material/IosShare"; import { Box, IconButton, Paper, Tooltip, tooltipClasses } from "@mui/material"; import { useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; @@ -12,7 +13,7 @@ import { MODALS } from "../../../elements/modal/ModalManager"; import { PERMISSIONS } from "../../constants"; import { useModelID } from "../../hooks/useModelID"; -export function Footer() { +export function LeftFooter() { const dispatch = useDispatch(); const emptyDiagram = useSelector( @@ -51,6 +52,11 @@ export function Footer() { + {/* + + + + */} + + + {component && ( )} -