Refactor risk management

Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
gearnode
2025-04-12 00:18:31 -07:00
parent c5e7f030a8
commit 16efab774a
48 changed files with 5725 additions and 779 deletions

View File

@@ -0,0 +1,29 @@
"use client";
import * as React from "react";
import * as SwitchPrimitives from "@radix-ui/react-switch";
import { cn } from "@/lib/utils";
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-[24px] w-[44px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
)}
/>
</SwitchPrimitives.Root>
));
Switch.displayName = SwitchPrimitives.Root.displayName;
export { Switch };

View File

@@ -18,6 +18,7 @@ import { OrganizationBreadcrumbBreadcrumbVendorOverviewQuery } from "./__generat
import { OrganizationBreadcrumbOrganizationQuery } from "./__generated__/OrganizationBreadcrumbOrganizationQuery.graphql";
import { OrganizationBreadcrumbBreadcrumbMitigationViewQuery } from "./__generated__/OrganizationBreadcrumbBreadcrumbMitigationViewQuery.graphql";
import { OrganizationBreadcrumbBreadcrumbControlQuery } from "./__generated__/OrganizationBreadcrumbBreadcrumbControlQuery.graphql";
import { OrganizationBreadcrumbBreadcrumbRiskShowQuery } from "./__generated__/OrganizationBreadcrumbBreadcrumbRiskShowQuery.graphql";
import ErrorBoundary from "@/components/ErrorBoundary";
const New = () => {
@@ -418,6 +419,38 @@ function BreadcrumbRiskList() {
);
}
function BreadcrumbRiskShow() {
const { organizationId, riskId } = useParams();
const data = useLazyLoadQuery<OrganizationBreadcrumbBreadcrumbRiskShowQuery>(
graphql`
query OrganizationBreadcrumbBreadcrumbRiskShowQuery($riskId: ID!) {
risk: node(id: $riskId) {
id
... on Risk {
name
}
}
}
`,
{ riskId: riskId! },
{ fetchPolicy: "store-or-network" }
);
return (
<>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbNavLink
to={`/organizations/${organizationId}/risks/${riskId}`}
>
{data.risk?.name}
</BreadcrumbNavLink>
</BreadcrumbItem>
<Outlet />
</>
);
}
export function BreadCrumb() {
return (
<Routes>
@@ -445,6 +478,16 @@ export function BreadCrumb() {
</Route>
<Route path="risks" element={<BreadcrumbRiskList />}>
<Route path="new" element={<New />} />
<Route
path=":riskId"
element={
<Suspense>
<BreadcrumbRiskShow />
</Suspense>
}
>
<Route path="edit" element={<Edit />} />
</Route>
</Route>
<Route path="frameworks" element={<BreadcrumbFrameworkList />}>
<Route

View File

@@ -22,6 +22,7 @@ import { EditPolicyPage } from "./policies/EditPolicyPage";
import { NewPolicyPage } from "./policies/NewPolicyPage";
import { PolicyListPage } from "./policies/PolicyListPage";
import { PolicyPage } from "./policies/PolicyPage";
import { EditRiskPage } from "./risks/EditRiskPage";
import { NewRiskPage } from "./risks/NewRiskPage";
import { RiskListPage } from "./risks/RiskListPage";
import ShowRiskView from "./risks/ShowRiskView";
@@ -62,6 +63,7 @@ export function OrganizationsRoutes() {
<Route path="risks" element={<RiskListPage />} />
<Route path="risks/new" element={<NewRiskPage />} />
<Route path="risks/:riskId" element={<ShowRiskView />} />
<Route path="risks/:riskId/edit" element={<EditRiskPage />} />
<Route path="settings" element={<SettingsPage />} />
<Route path="*" element={<NotFoundPage />} />
</Route>

View File

@@ -0,0 +1,127 @@
/**
* @generated SignedSource<<6cc6ac4101b94ac06f94f04368fbaa6d>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type OrganizationBreadcrumbBreadcrumbRiskShowQuery$variables = {
riskId: string;
};
export type OrganizationBreadcrumbBreadcrumbRiskShowQuery$data = {
readonly risk: {
readonly id: string;
readonly name?: string;
};
};
export type OrganizationBreadcrumbBreadcrumbRiskShowQuery = {
response: OrganizationBreadcrumbBreadcrumbRiskShowQuery$data;
variables: OrganizationBreadcrumbBreadcrumbRiskShowQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "riskId"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "riskId"
}
],
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v3 = {
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
}
],
"type": "Risk",
"abstractKey": null
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "OrganizationBreadcrumbBreadcrumbRiskShowQuery",
"selections": [
{
"alias": "risk",
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v3/*: any*/)
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "OrganizationBreadcrumbBreadcrumbRiskShowQuery",
"selections": [
{
"alias": "risk",
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
(v2/*: any*/),
(v3/*: any*/)
],
"storageKey": null
}
]
},
"params": {
"cacheID": "eaafff390efc7cb4b97300fa65d98c99",
"id": null,
"metadata": {},
"name": "OrganizationBreadcrumbBreadcrumbRiskShowQuery",
"operationKind": "query",
"text": "query OrganizationBreadcrumbBreadcrumbRiskShowQuery(\n $riskId: ID!\n) {\n risk: node(id: $riskId) {\n __typename\n id\n ... on Risk {\n name\n }\n }\n}\n"
}
};
})();
(node as any).hash = "3f83f754047443e38a51bd8d2351f81c";
export default node;

View File

@@ -16,6 +16,8 @@ import {
CheckCircle2,
Clock,
X,
HelpCircle,
ExternalLink,
} from "lucide-react";
import { PageTemplate } from "@/components/PageTemplate";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
@@ -24,6 +26,17 @@ import { MitigationListViewSkeleton } from "./MitigationListPage";
import { Button } from "@/components/ui/button";
import { useToast } from "@/hooks/use-toast";
import { MitigationListViewImportMitigationMutation as MitigationListViewImportMitigationMutationType } from "./__generated__/MitigationListViewImportMitigationMutation.graphql";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
const mitigationListViewQuery = graphql`
query MitigationListViewQuery($organizationId: ID!, $first: Int) {
@@ -543,12 +556,113 @@ function MitigationListContent({
</div>
</td>
<td className="px-4 py-3 align-middle">
<Link
to={`/organizations/${organizationId}/mitigations/${mitigation.id}`}
className="font-medium block"
>
{mitigation.name}
</Link>
<div className="flex items-center gap-3">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Link
to={`/organizations/${organizationId}/mitigations/${mitigation.id}`}
className="font-medium group flex items-center relative"
>
<span className="mr-1">
{mitigation.name}
</span>
<span className="absolute bottom-0 left-0 w-full h-0.5 bg-primary-bg scale-x-0 group-hover:scale-x-100 transition-transform"></span>
</Link>
</TooltipTrigger>
<TooltipContent side="bottom">
<p className="text-xs">
Click to view details
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
{/* Enhanced popover for description info */}
<Popover>
<PopoverTrigger asChild>
<Button
variant="ghost"
className="p-0 h-6 w-6 rounded-full hover:bg-primary-bg"
aria-label="Learn more about this mitigation"
>
<HelpCircle className="h-5 w-5 text-primary hover:text-primary" />
</Button>
</PopoverTrigger>
<PopoverContent
className="w-80 p-4 shadow-md"
align="start"
sideOffset={5}
>
<div className="space-y-3">
<div className="flex justify-between items-start">
<h4 className="font-bold text-md">
{mitigation.name}
</h4>
<Badge
variant="outline"
className="text-xs"
>
{mitigation.importance}
</Badge>
</div>
{mitigation.description && (
<div className="text-sm">
<p className="font-semibold mb-1">
Why is this important:
</p>
<p>
{mitigation.description.startsWith(
"##"
)
? mitigation.description
.split("\n")
.find((line) =>
line.startsWith(
"## Why"
)
)
?.replace("## Why?", "")
?.replace("## Why", "")
?.trim() ||
mitigation.description.split(
"\n"
)[1] ||
""
: mitigation.description.substring(
0,
180
) +
(mitigation.description
.length > 180
? "..."
: "")}
</p>
</div>
)}
<div className="pt-2 flex justify-end">
<Button
variant="outline"
size="sm"
className="text-xs gap-1"
asChild
>
<Link
to={`/organizations/${organizationId}/mitigations/${mitigation.id}`}
>
<span>
Implementation guide
</span>
<ExternalLink className="h-3 w-3" />
</Link>
</Button>
</div>
</div>
</PopoverContent>
</Popover>
</div>
</td>
</tr>
))}

View File

@@ -449,8 +449,8 @@ const mitigationRisksQuery = graphql`
id
name
description
probability
impact
inherentLikelihood
inherentImpact
createdAt
updatedAt
}
@@ -491,8 +491,9 @@ type RiskNode = {
id: string;
name: string;
description: string;
probability: number;
likelihood: number;
impact: number;
severity: number;
createdAt: string;
updatedAt: string;
};
@@ -1846,12 +1847,7 @@ function MitigationViewContent({
}, [controlSearchQuery, getControls]);
// Helper function to get risk severity color
const getRiskSeverityColor = (
probability: number,
impact: number
): string => {
const severity = probability * impact;
const getRiskSeverityColor = (severity: number): string => {
if (severity >= 0.75) return "bg-red-100 text-red-800";
if (severity >= 0.5) return "bg-orange-100 text-orange-800";
if (severity >= 0.25) return "bg-yellow-100 text-yellow-800";
@@ -1859,17 +1855,15 @@ function MitigationViewContent({
};
// Helper function to get risk severity text
const getRiskSeverityText = (probability: number, impact: number): string => {
const severity = probability * impact;
const getRiskSeverityText = (severity: number): string => {
if (severity >= 0.75) return "Critical";
if (severity >= 0.5) return "High";
if (severity >= 0.25) return "Medium";
return "Low";
};
// Format probability as text
const formatProbability = (value: number): string => {
// Format likelihood as text
const formatlikelihood = (value: number): string => {
if (value <= 0.1) return "Very Low";
if (value <= 0.3) return "Low";
if (value <= 0.5) return "Medium";
@@ -2825,7 +2819,7 @@ function MitigationViewContent({
Risk Name
</th>
<th className="text-left font-medium text-sm py-2 px-4">
Probability
likelihood
</th>
<th className="text-left font-medium text-sm py-2 px-4">
Impact
@@ -2852,7 +2846,7 @@ function MitigationViewContent({
</td>
<td className="py-3 px-4">
<div className="bg-blue-100 text-blue-800 px-2 py-0.5 rounded-full text-xs inline-block">
{formatProbability(node.probability)}
{formatlikelihood(node.likelihood)}
</div>
</td>
<td className="py-3 px-4">
@@ -2863,14 +2857,10 @@ function MitigationViewContent({
<td className="py-3 px-4">
<div
className={`${getRiskSeverityColor(
node.probability,
node.impact
node.severity
)} px-2 py-0.5 rounded-full text-xs inline-block`}
>
{getRiskSeverityText(
node.probability,
node.impact
)}
{getRiskSeverityText(node.severity)}
</div>
</td>
</tr>

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<7ecc796e34bf88aa378e81249110be90>>
* @generated SignedSource<<b7154e691d26fef3b8f64ec3a39304fd>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -21,9 +21,9 @@ export type MitigationViewRisksQuery$data = {
readonly createdAt: string;
readonly description: string;
readonly id: string;
readonly impact: number;
readonly inherentImpact: number;
readonly inherentLikelihood: number;
readonly name: string;
readonly probability: number;
readonly updatedAt: string;
};
}>;
@@ -100,14 +100,14 @@ v4 = [
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "probability",
"name": "inherentLikelihood",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "impact",
"name": "inherentImpact",
"storageKey": null
},
{
@@ -259,7 +259,7 @@ return {
]
},
"params": {
"cacheID": "61c985f00c12fb3cdab3e345a8c65957",
"cacheID": "6e8982bef70e90ee745d6985c82c597e",
"id": null,
"metadata": {
"connection": [
@@ -276,11 +276,11 @@ return {
},
"name": "MitigationViewRisksQuery",
"operationKind": "query",
"text": "query MitigationViewRisksQuery(\n $mitigationId: ID!\n) {\n mitigation: node(id: $mitigationId) {\n __typename\n id\n ... on Mitigation {\n risks(first: 100) {\n edges {\n node {\n id\n name\n description\n probability\n impact\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
"text": "query MitigationViewRisksQuery(\n $mitigationId: ID!\n) {\n mitigation: node(id: $mitigationId) {\n __typename\n id\n ... on Mitigation {\n risks(first: 100) {\n edges {\n node {\n id\n name\n description\n inherentLikelihood\n inherentImpact\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "e05802ec519933edce3d0f3487742e3d";
(node as any).hash = "0fe825c558627da60c1025efa825f91e";
export default node;

View File

@@ -0,0 +1,61 @@
import { Suspense } from "react";
import { useLocation } from "react-router";
import { ErrorBoundaryWithLocation } from "../ErrorBoundary";
import { Skeleton } from "@/components/ui/skeleton";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { PageTemplate } from "@/components/PageTemplate";
import { EditRiskView } from "./EditRiskView";
// We'll import this once we create it
// import { EditRiskView } from "./EditRiskView";
export function EditRiskViewSkeleton() {
return (
<PageTemplate title="Edit Risk">
<Card className="max-w-2xl">
<CardHeader>
<Skeleton className="h-8 w-64" />
<Skeleton className="h-4 w-96 mt-2" />
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-2">
<Skeleton className="h-4 w-20" />
<Skeleton className="h-10 w-full" />
</div>
<div className="space-y-2">
<Skeleton className="h-4 w-24" />
<Skeleton className="h-24 w-full" />
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Skeleton className="h-4 w-20" />
<Skeleton className="h-10 w-full" />
</div>
<div className="space-y-2">
<Skeleton className="h-4 w-20" />
<Skeleton className="h-10 w-full" />
</div>
</div>
<div className="flex justify-end gap-3">
<Skeleton className="h-10 w-24" />
<Skeleton className="h-10 w-24" />
</div>
</CardContent>
</Card>
</PageTemplate>
);
}
export function EditRiskPage() {
const location = useLocation();
return (
<Suspense key={location.pathname} fallback={<EditRiskViewSkeleton />}>
<ErrorBoundaryWithLocation>
<EditRiskView />
</ErrorBoundaryWithLocation>
</Suspense>
);
}
export default EditRiskPage;

View File

@@ -0,0 +1,404 @@
"use client";
import { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router";
import {
graphql,
useMutation,
usePreloadedQuery,
useQueryLoader,
PreloadedQuery,
} from "react-relay";
import {
Card,
CardContent,
CardHeader,
CardTitle,
CardDescription,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { useToast } from "@/hooks/use-toast";
import { PageTemplate } from "@/components/PageTemplate";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { Suspense } from "react";
import { EditRiskViewSkeleton } from "./EditRiskPage";
import type { EditRiskViewQuery } from "./__generated__/EditRiskViewQuery.graphql";
import type { EditRiskViewUpdateRiskMutation } from "./__generated__/EditRiskViewUpdateRiskMutation.graphql";
// Query to get risk details
const editRiskViewQuery = graphql`
query EditRiskViewQuery($riskId: ID!) {
risk: node(id: $riskId) {
... on Risk {
id
name
description
inherentLikelihood
inherentImpact
residualLikelihood
residualImpact
}
}
}
`;
// Mutation to update risk
const updateRiskMutation = graphql`
mutation EditRiskViewUpdateRiskMutation($input: UpdateRiskInput!) {
updateRisk(input: $input) {
risk {
id
name
description
inherentLikelihood
inherentImpact
residualLikelihood
residualImpact
updatedAt
}
}
}
`;
// Component to handle the actual editing once data is loaded
function EditRiskViewContent({
queryRef,
}: {
queryRef: PreloadedQuery<EditRiskViewQuery>;
}) {
const navigate = useNavigate();
const { organizationId, riskId } = useParams<{
organizationId: string;
riskId: string;
}>();
const { toast } = useToast();
const data = usePreloadedQuery(editRiskViewQuery, queryRef);
const risk = data.risk;
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [inherentLikelihood, setInherentLikelihood] =
useState<string>("MEDIUM");
const [inherentImpact, setInherentImpact] = useState<string>("MEDIUM");
const [residualLikelihood, setResidualLikelihood] =
useState<string>("MEDIUM");
const [residualImpact, setResidualImpact] = useState<string>("MEDIUM");
const [isSubmitting, setIsSubmitting] = useState(false);
const [updateRisk, isInFlight] =
useMutation<EditRiskViewUpdateRiskMutation>(updateRiskMutation);
// Helper function to convert float to likelihood string
const floatToLikelihood = (value: number): string => {
if (value <= 0.2) return "VERY_LOW";
if (value <= 0.4) return "LOW";
if (value <= 0.6) return "MEDIUM";
if (value <= 0.8) return "HIGH";
return "VERY_HIGH";
};
// Helper function to convert float to impact string
const floatToImpact = (value: number): string => {
if (value <= 0.2) return "VERY_LOW";
if (value <= 0.4) return "LOW";
if (value <= 0.6) return "MEDIUM";
if (value <= 0.8) return "HIGH";
return "VERY_HIGH";
};
// Map string values to float values
const likelihoodToFloat = (value: string): number => {
switch (value) {
case "VERY_LOW":
return 0.1;
case "LOW":
return 0.3;
case "MEDIUM":
return 0.5;
case "HIGH":
return 0.7;
case "VERY_HIGH":
return 0.9;
default:
return 0.5;
}
};
const impactToFloat = (value: string): number => {
switch (value) {
case "VERY_LOW":
return 0.1;
case "LOW":
return 0.3;
case "MEDIUM":
return 0.5;
case "HIGH":
return 0.7;
case "VERY_HIGH":
return 0.9;
default:
return 0.5;
}
};
// Initialize form with risk data
useEffect(() => {
if (risk) {
setName(risk.name || "");
setDescription(risk.description || "");
setInherentLikelihood(floatToLikelihood(risk.inherentLikelihood || 0.5));
setInherentImpact(floatToImpact(risk.inherentImpact || 0.5));
setResidualLikelihood(floatToLikelihood(risk.residualLikelihood || 0.5));
setResidualImpact(floatToImpact(risk.residualImpact || 0.5));
}
}, [risk]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!name.trim()) {
toast({
title: "Error",
description: "Please enter a name for the risk.",
variant: "destructive",
});
return;
}
setIsSubmitting(true);
const input = {
id: riskId!,
name,
description,
inherentLikelihood: likelihoodToFloat(inherentLikelihood),
inherentImpact: impactToFloat(inherentImpact),
residualLikelihood: likelihoodToFloat(residualLikelihood),
residualImpact: impactToFloat(residualImpact),
};
updateRisk({
variables: {
input,
},
onCompleted: (response, errors) => {
setIsSubmitting(false);
if (errors) {
console.error("Error updating risk:", errors);
toast({
title: "Error",
description: "Failed to update risk. Please try again.",
variant: "destructive",
});
return;
}
toast({
title: "Success",
description: "Risk updated successfully!",
});
navigate(`/organizations/${organizationId}/risks/${riskId}`);
},
onError: (error) => {
setIsSubmitting(false);
console.error("Error updating risk:", error);
toast({
title: "Error",
description: "Failed to update risk. Please try again.",
variant: "destructive",
});
},
});
};
return (
<PageTemplate
title="Edit Risk"
description="Update the details of this risk"
>
<Card className="max-w-2xl">
<CardHeader>
<CardTitle>Risk Details</CardTitle>
<CardDescription>Update the details of this risk</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="space-y-2">
<Label htmlFor="name">Name</Label>
<Input
id="name"
placeholder="Risk name"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="description">Description</Label>
<Textarea
id="description"
placeholder="Describe the risk in detail"
value={description}
onChange={(e) => setDescription(e.target.value)}
rows={5}
/>
</div>
<Separator />
<div>
<h3 className="text-lg font-medium mb-2">
Initial Risk Assessment
</h3>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="inherentLikelihood">
Inherent Likelihood
</Label>
<Select
value={inherentLikelihood}
onValueChange={setInherentLikelihood}
>
<SelectTrigger id="inherentLikelihood">
<SelectValue placeholder="Select likelihood" />
</SelectTrigger>
<SelectContent className="max-h-[300px] overflow-y-auto">
<SelectItem value="VERY_LOW">Very Low</SelectItem>
<SelectItem value="LOW">Low</SelectItem>
<SelectItem value="MEDIUM">Medium</SelectItem>
<SelectItem value="HIGH">High</SelectItem>
<SelectItem value="VERY_HIGH">Very High</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="inherentImpact">Inherent Impact</Label>
<Select
value={inherentImpact}
onValueChange={setInherentImpact}
>
<SelectTrigger id="inherentImpact">
<SelectValue placeholder="Select impact" />
</SelectTrigger>
<SelectContent className="max-h-[300px] overflow-y-auto">
<SelectItem value="VERY_LOW">Very Low</SelectItem>
<SelectItem value="LOW">Low</SelectItem>
<SelectItem value="MEDIUM">Medium</SelectItem>
<SelectItem value="HIGH">High</SelectItem>
<SelectItem value="VERY_HIGH">Very High</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
<Separator />
<div>
<h3 className="text-lg font-medium mb-2">Residual Risk</h3>
<p className="text-sm text-tertiary mb-4">
Estimate the risk after treatment measures have been applied
</p>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="residualLikelihood">
Residual Likelihood
</Label>
<Select
value={residualLikelihood}
onValueChange={setResidualLikelihood}
>
<SelectTrigger id="residualLikelihood">
<SelectValue placeholder="Select residual likelihood" />
</SelectTrigger>
<SelectContent className="max-h-[300px] overflow-y-auto">
<SelectItem value="VERY_LOW">Very Low</SelectItem>
<SelectItem value="LOW">Low</SelectItem>
<SelectItem value="MEDIUM">Medium</SelectItem>
<SelectItem value="HIGH">High</SelectItem>
<SelectItem value="VERY_HIGH">Very High</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="residualImpact">Residual Impact</Label>
<Select
value={residualImpact}
onValueChange={setResidualImpact}
>
<SelectTrigger id="residualImpact">
<SelectValue placeholder="Select residual impact" />
</SelectTrigger>
<SelectContent className="max-h-[300px] overflow-y-auto">
<SelectItem value="VERY_LOW">Very Low</SelectItem>
<SelectItem value="LOW">Low</SelectItem>
<SelectItem value="MEDIUM">Medium</SelectItem>
<SelectItem value="HIGH">High</SelectItem>
<SelectItem value="VERY_HIGH">Very High</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
<div className="flex justify-end gap-3">
<Button
type="button"
variant="outline"
onClick={() =>
navigate(`/organizations/${organizationId}/risks/${riskId}`)
}
>
Cancel
</Button>
<Button type="submit" disabled={isInFlight || isSubmitting}>
{isInFlight || isSubmitting ? "Updating..." : "Update Risk"}
</Button>
</div>
</form>
</CardContent>
</Card>
</PageTemplate>
);
}
// Main component that loads the query
export function EditRiskView() {
const { riskId } = useParams<{ riskId: string }>();
const [queryRef, loadQuery] =
useQueryLoader<EditRiskViewQuery>(editRiskViewQuery);
useEffect(() => {
if (riskId) {
loadQuery({ riskId });
}
}, [loadQuery, riskId]);
if (!queryRef) {
return <EditRiskViewSkeleton />;
}
return (
<Suspense fallback={<EditRiskViewSkeleton />}>
<EditRiskViewContent queryRef={queryRef} />
</Suspense>
);
}
export default EditRiskView;

View File

@@ -1,4 +1,4 @@
import { useState } from "react";
import { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router";
import { ConnectionHandler, graphql, useMutation } from "react-relay";
import {
@@ -21,74 +21,18 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
// Risk template library - static definitions of common risks
const riskTemplates = [
{
id: "data-breach",
name: "Data Breach",
description:
"Unauthorized access to sensitive data resulting in data disclosure, theft, or corruption.",
probability: "HIGH",
impact: "HIGH",
},
{
id: "service-outage",
name: "Service Outage",
description:
"System downtime or degradation affecting availability of services to customers.",
probability: "MEDIUM",
impact: "HIGH",
},
{
id: "compliance-violation",
name: "Compliance Violation",
description:
"Failure to meet regulatory requirements resulting in penalties or legal action.",
probability: "MEDIUM",
impact: "VERY_HIGH",
},
{
id: "insider-threat",
name: "Insider Threat",
description:
"Malicious actions by employees or contractors with privileged access to systems or data.",
probability: "LOW",
impact: "HIGH",
},
{
id: "third-party-risk",
name: "Third-Party Risk",
description:
"Vulnerabilities introduced through vendors, suppliers, or partners with access to systems or data.",
probability: "MEDIUM",
impact: "MEDIUM",
},
{
id: "ransomware",
name: "Ransomware Attack",
description:
"Malware that encrypts data and demands payment for decryption keys.",
probability: "MEDIUM",
impact: "VERY_HIGH",
},
{
id: "ddos",
name: "DDoS Attack",
description:
"Distributed denial of service attack overwhelming systems and preventing legitimate access.",
probability: "MEDIUM",
impact: "HIGH",
},
{
id: "credential-compromise",
name: "Credential Compromise",
description:
"Unauthorized access to accounts due to weak, stolen, or improperly secured credentials.",
probability: "HIGH",
impact: "HIGH",
},
];
interface RiskTemplate {
name: string;
description: string;
variations: {
context: string;
impact: number;
likelihood: number;
recommendedTreatment: string;
}[];
}
const createRiskMutation = graphql`
mutation NewRiskViewCreateRiskMutation(
@@ -101,8 +45,10 @@ const createRiskMutation = graphql`
id
name
description
probability
impact
inherentLikelihood
inherentImpact
residualLikelihood
residualImpact
createdAt
updatedAt
}
@@ -116,16 +62,40 @@ export default function NewRiskView() {
const { organizationId } = useParams<{ organizationId: string }>();
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [probability, setProbability] = useState<string>("MEDIUM");
const [impact, setImpact] = useState<string>("MEDIUM");
const [inherentLikelihood, setinherentLikelihood] =
useState<string>("MEDIUM");
const [inherentImpact, setinherentImpact] = useState<string>("MEDIUM");
const [residualLikelihood, setResidualLikelihood] =
useState<string>("MEDIUM");
const [residualImpact, setResidualImpact] = useState<string>("MEDIUM");
const [isSubmitting, setIsSubmitting] = useState(false);
const [selectedTemplate, setSelectedTemplate] = useState<string>("");
const [riskTemplates, setRiskTemplates] = useState<RiskTemplate[]>([]);
const { toast } = useToast();
const [commitMutation, isInFlight] = useMutation(createRiskMutation);
const [createRisk, isInFlight] = useMutation(createRiskMutation);
useEffect(() => {
const loadRiskTemplates = async () => {
try {
const response = await fetch("/data/risks/risks.json");
const data = await response.json();
setRiskTemplates(data);
} catch (error) {
console.error("Error loading risk templates:", error);
toast({
title: "Error",
description: "Failed to load risk templates. Please try again.",
variant: "destructive",
});
}
};
loadRiskTemplates();
}, [toast]);
// Map string values to float values
const probabilityToFloat = (value: string): number => {
const likelihoodToFloat = (value: string): number => {
switch (value) {
case "VERY_LOW":
return 0.1;
@@ -167,20 +137,48 @@ export default function NewRiskView() {
// Clear form if "Select a template" is chosen
setName("");
setDescription("");
setProbability("MEDIUM");
setImpact("MEDIUM");
setinherentLikelihood("MEDIUM");
setinherentImpact("MEDIUM");
setResidualLikelihood("MEDIUM");
setResidualImpact("MEDIUM");
return;
}
const template = riskTemplates.find((t) => t.id === templateId);
const template = riskTemplates[parseInt(templateId)];
if (template) {
setName(template.name);
setDescription(template.description);
setProbability(template.probability);
setImpact(template.impact);
// Convert numeric values to string values for the select components
const likelihoodValue = floatTolikelihood(
template.variations[0].likelihood
);
const impactValue = floatToImpact(template.variations[0].impact);
setinherentLikelihood(likelihoodValue);
setinherentImpact(impactValue);
// Set residual values to be the same as initial values by default
setResidualLikelihood(likelihoodValue);
setResidualImpact(impactValue);
}
};
// Helper function to convert float likelihood to string
const floatTolikelihood = (value: number): string => {
if (value <= 0.2) return "VERY_LOW";
if (value <= 0.4) return "LOW";
if (value <= 0.6) return "MEDIUM";
if (value <= 0.8) return "HIGH";
return "VERY_HIGH";
};
// Helper function to convert float impact to string
const floatToImpact = (value: number): string => {
if (value <= 0.2) return "VERY_LOW";
if (value <= 0.4) return "LOW";
if (value <= 0.6) return "MEDIUM";
if (value <= 0.8) return "HIGH";
return "VERY_HIGH";
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
@@ -199,11 +197,13 @@ export default function NewRiskView() {
organizationId: organizationId!,
name,
description,
probability: probabilityToFloat(probability),
impact: impactToFloat(impact),
inherentLikelihood: likelihoodToFloat(inherentLikelihood),
inherentImpact: impactToFloat(inherentImpact),
residualLikelihood: likelihoodToFloat(residualLikelihood),
residualImpact: impactToFloat(residualImpact),
};
commitMutation({
createRisk({
variables: {
input,
connections: [
@@ -268,10 +268,10 @@ export default function NewRiskView() {
<SelectTrigger id="template">
<SelectValue placeholder="Select a risk template" />
</SelectTrigger>
<SelectContent>
<SelectContent className="max-h-[300px] overflow-y-auto">
<SelectItem value="none">Select a template</SelectItem>
{riskTemplates.map((template) => (
<SelectItem key={template.id} value={template.id}>
{riskTemplates.map((template, index) => (
<SelectItem key={index} value={index.toString()}>
{template.name}
</SelectItem>
))}
@@ -306,12 +306,15 @@ export default function NewRiskView() {
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="probability">Probability</Label>
<Select value={probability} onValueChange={setProbability}>
<SelectTrigger id="probability">
<SelectValue placeholder="Select probability" />
<Label htmlFor="inherentLikelihood">Initial Likelihood</Label>
<Select
value={inherentLikelihood}
onValueChange={setinherentLikelihood}
>
<SelectTrigger id="inherentLikelihood">
<SelectValue placeholder="Select inherentLikelihood" />
</SelectTrigger>
<SelectContent>
<SelectContent className="max-h-[300px] overflow-y-auto">
<SelectItem value="VERY_LOW">Very Low</SelectItem>
<SelectItem value="LOW">Low</SelectItem>
<SelectItem value="MEDIUM">Medium</SelectItem>
@@ -322,12 +325,15 @@ export default function NewRiskView() {
</div>
<div className="space-y-2">
<Label htmlFor="impact">Impact</Label>
<Select value={impact} onValueChange={setImpact}>
<SelectTrigger id="impact">
<SelectValue placeholder="Select impact" />
<Label htmlFor="inherentImpact">Initial Impact</Label>
<Select
value={inherentImpact}
onValueChange={setinherentImpact}
>
<SelectTrigger id="inherentImpact">
<SelectValue placeholder="Select inherentImpact" />
</SelectTrigger>
<SelectContent>
<SelectContent className="max-h-[300px] overflow-y-auto">
<SelectItem value="VERY_LOW">Very Low</SelectItem>
<SelectItem value="LOW">Low</SelectItem>
<SelectItem value="MEDIUM">Medium</SelectItem>
@@ -338,6 +344,57 @@ export default function NewRiskView() {
</div>
</div>
<Separator className="my-4" />
<div>
<h3 className="text-lg font-medium mb-2">Residual Risk</h3>
<p className="text-sm text-tertiary mb-4">
Estimate the risk after treatment measures have been applied
</p>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="residualLikelihood">
Residual Likelihood
</Label>
<Select
value={residualLikelihood}
onValueChange={setResidualLikelihood}
>
<SelectTrigger id="residualLikelihood">
<SelectValue placeholder="Select residual likelihood" />
</SelectTrigger>
<SelectContent className="max-h-[300px] overflow-y-auto">
<SelectItem value="VERY_LOW">Very Low</SelectItem>
<SelectItem value="LOW">Low</SelectItem>
<SelectItem value="MEDIUM">Medium</SelectItem>
<SelectItem value="HIGH">High</SelectItem>
<SelectItem value="VERY_HIGH">Very High</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="residualImpact">Residual Impact</Label>
<Select
value={residualImpact}
onValueChange={setResidualImpact}
>
<SelectTrigger id="residualImpact">
<SelectValue placeholder="Select residual impact" />
</SelectTrigger>
<SelectContent className="max-h-[300px] overflow-y-auto">
<SelectItem value="VERY_LOW">Very Low</SelectItem>
<SelectItem value="LOW">Low</SelectItem>
<SelectItem value="MEDIUM">Medium</SelectItem>
<SelectItem value="HIGH">High</SelectItem>
<SelectItem value="VERY_HIGH">Very High</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
<div className="flex justify-end gap-3">
<Button
type="button"

View File

@@ -22,9 +22,9 @@ import { RiskViewSkeleton } from "./RiskListPage";
import { RiskListViewPaginationQuery } from "./__generated__/RiskListViewPaginationQuery.graphql";
import { RiskListView_risks$key } from "./__generated__/RiskListView_risks.graphql";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Link } from "react-router";
import { Plus, Trash2 } from "lucide-react";
import { Plus, Trash2, Edit } from "lucide-react";
import { useToast } from "@/hooks/use-toast";
import {
Dialog,
@@ -35,6 +35,13 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import { RiskListViewDeleteMutation } from "./__generated__/RiskListViewDeleteMutation.graphql";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
const defaultPageSize = 25;
@@ -71,8 +78,10 @@ const riskListFragment = graphql`
node {
id
name
probability
impact
inherentLikelihood
inherentImpact
residualLikelihood
residualImpact
description
createdAt
updatedAt
@@ -99,21 +108,9 @@ const deleteRiskMutation = graphql`
}
`;
// Helper functions to convert float values to text
const floatToProbabilityText = (value: number): string => {
if (value <= 0.1) return "Very Low";
if (value <= 0.3) return "Low";
if (value <= 0.5) return "Medium";
if (value <= 0.7) return "High";
return "Very High";
};
const floatToImpactText = (value: number): string => {
if (value <= 0.1) return "Very Low";
if (value <= 0.3) return "Low";
if (value <= 0.5) return "Medium";
if (value <= 0.7) return "High";
return "Very High";
// Helper function to convert float values to percentage
const floatToPercentage = (value: number): string => {
return `${Math.round(value * 100)}%`;
};
function LoadAboveButton({
@@ -170,6 +167,228 @@ function LoadBelowButton({
);
}
// Define colors for risk matrix cells
const riskMatrixColors = {
lowest: "bg-green-500 text-white",
low: "bg-lime-300 text-black",
medium: "bg-yellow-300 text-black",
high: "bg-amber-400 text-white",
highest: "bg-red-500 text-white",
};
// Empty cell variants (lighter colors)
const emptyRiskMatrixColors = {
lowest: "bg-green-50 text-black",
low: "bg-lime-50 text-black",
medium: "bg-yellow-50 text-black",
high: "bg-amber-50 text-black",
highest: "bg-red-50 text-black",
};
// Risk Matrix Component
function RiskMatrix({
risks,
isResidual = false,
}: {
risks: Array<{
id: string;
name: string;
inherentLikelihood: number;
inherentImpact: number;
residualLikelihood?: number;
residualImpact?: number;
}>;
isResidual?: boolean;
}): JSX.Element {
// Define likelihood and impact ranges for the 5x5 matrix
const likelihoodRanges: [number, number][] = [
[0.8, 1], // Highest likelihood
[0.6, 0.8], // High likelihood
[0.4, 0.6], // Medium likelihood
[0.2, 0.4], // Low likelihood
[0, 0.2], // Lowest likelihood
];
const impactRanges: [number, number][] = [
[0, 0.2], // Lowest impact
[0.2, 0.4], // Low impact
[0.4, 0.6], // Medium impact
[0.6, 0.8], // High impact
[0.8, 1], // Highest impact
];
const likelihoodLabels = ["Very High", "High", "Medium", "Low", "Very Low"];
const impactLabels = ["Very Low", "Low", "Medium", "High", "Very High"];
// Function to get cell content with risks that fall in this cell
const getCellContent = (
likelihoodRange: [number, number],
impactRange: [number, number]
) => {
return risks.filter((risk) => {
const likelihood = isResidual
? risk.residualLikelihood ?? risk.inherentLikelihood
: risk.inherentLikelihood;
const impact = isResidual
? risk.residualImpact ?? risk.inherentImpact
: risk.inherentImpact;
return (
likelihood > likelihoodRange[0] &&
likelihood <= likelihoodRange[1] &&
impact > impactRange[0] &&
impact <= impactRange[1]
);
});
};
// Helper to determine cell color based on position in matrix
// New matrix has rows indexed from top to bottom (0 = highest likelihood, 4 = lowest likelihood)
// and columns indexed from left to right (0 = lowest impact, 4 = highest impact)
const getCellColor = (row: number, col: number, isEmpty: boolean): string => {
const colorSet = isEmpty ? emptyRiskMatrixColors : riskMatrixColors;
// Top row (highest likelihood)
if (row === 0) {
if (col === 0) return colorSet.lowest;
if (col === 1) return colorSet.high;
return colorSet.highest;
}
// Second row
if (row === 1) {
if (col === 0) return colorSet.lowest;
if (col === 1) return colorSet.medium;
if (col === 2) return colorSet.high;
return colorSet.highest;
}
// Middle row
if (row === 2) {
if (col === 0) return colorSet.lowest;
if (col === 1) return colorSet.low;
if (col === 2) return colorSet.medium;
if (col === 3) return colorSet.high;
return colorSet.highest;
}
// Fourth row
if (row === 3) {
if (col === 0) return colorSet.lowest;
if (col === 1) return colorSet.low;
if (col === 2 || col === 3) return colorSet.medium;
return colorSet.high;
}
// Bottom row (lowest likelihood)
if (row === 4) {
if (col <= 1) return colorSet.lowest;
if (col <= 3) return colorSet.low;
return colorSet.medium;
}
return "bg-gray-100";
};
// Instead of using refs and tippy, we'll use a component with popover
const RiskCell = ({
rowIndex,
colIndex,
likelihoodRange,
impactRange,
}: {
rowIndex: number;
colIndex: number;
likelihoodRange: [number, number];
impactRange: [number, number];
}) => {
const cellRisks = getCellContent(likelihoodRange, impactRange);
const isEmpty = cellRisks.length === 0;
const cellColor = getCellColor(rowIndex, colIndex, isEmpty);
if (isEmpty) {
return (
<td
className={`border aspect-square w-14 h-14 text-center ${cellColor}`}
data-risks={0}
>
<div className="text-sm font-bold flex items-center justify-center h-full"></div>
</td>
);
}
return (
<Popover>
<PopoverTrigger asChild>
<td
className={`border aspect-square w-14 h-14 text-center ${cellColor} cursor-pointer hover:opacity-90`}
data-risks={cellRisks.length}
>
<div className="text-sm font-bold flex items-center justify-center h-full">
{cellRisks.length}
</div>
</td>
</PopoverTrigger>
<PopoverContent className="w-64 p-3" align="center">
<div className="font-medium mb-2">
{cellRisks.length} Risk{cellRisks.length > 1 ? "s" : ""}:
</div>
<ul className="list-disc pl-4 space-y-1">
{cellRisks.map((risk) => (
<li key={risk.id} className="text-sm">
{risk.name}
</li>
))}
</ul>
</PopoverContent>
</Popover>
);
};
return (
<div className="overflow-x-auto">
<div className="flex flex-col">
<div className="text-xs font-medium text-center mb-1">CONSEQUENCE</div>
<table className="w-full border-collapse table-fixed">
<thead>
<tr>
<th className="p-1 text-center border w-14"></th>
{impactLabels.map((label, index) => (
<th
key={index}
className="p-1 text-xs text-center border font-medium w-14"
>
{label}
</th>
))}
</tr>
</thead>
<tbody>
{likelihoodRanges.map((likelihoodRange, rowIndex) => (
<tr key={rowIndex}>
<th className="p-1 text-xs text-center border font-medium w-14 h-14">
{likelihoodLabels[rowIndex]}
</th>
{impactRanges.map((impactRange, colIndex) => (
<RiskCell
key={colIndex}
rowIndex={rowIndex}
colIndex={colIndex}
likelihoodRange={likelihoodRange}
impactRange={impactRange}
/>
))}
</tr>
))}
</tbody>
</table>
<div className="text-xs font-medium ml-2 mt-1">LIKELIHOOD</div>
</div>
</div>
);
}
function RiskListViewContent({
queryRef,
}: {
@@ -188,6 +407,9 @@ function RiskListViewContent({
} | null>(null);
const [isDeleting, setIsDeleting] = useState(false);
// State for toggling between initial and residual risk matrix
const [showResidualRisk, setShowResidualRisk] = useState(false);
// Setup delete mutation
const [commitDeleteMutation] =
useMutation<RiskListViewDeleteMutation>(deleteRiskMutation);
@@ -282,6 +504,46 @@ function RiskListViewContent({
}}
/>
{/* Combined Risk Matrix with Toggle */}
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle>
{showResidualRisk
? "Residual Risk Matrix"
: "Initial Risk Matrix"}
</CardTitle>
<div className="flex items-center space-x-4 border-2 border-gray-300 rounded-lg p-3 bg-gray-50 shadow-md">
<Label
htmlFor="risk-toggle"
className={`text-sm font-semibold cursor-pointer ${
!showResidualRisk ? "text-primary" : "text-muted-foreground"
}`}
>
Initial
</Label>
<div className="relative">
<Switch
id="risk-toggle"
checked={showResidualRisk}
onCheckedChange={setShowResidualRisk}
className="border-2 border-gray-400 data-[state=checked]:border-primary"
/>
</div>
<Label
htmlFor="risk-toggle"
className={`text-sm font-semibold cursor-pointer ${
showResidualRisk ? "text-primary" : "text-muted-foreground"
}`}
>
Residual
</Label>
</div>
</CardHeader>
<CardContent>
<RiskMatrix risks={risks} isResidual={showResidualRisk} />
</CardContent>
</Card>
<Card>
<CardContent className="p-0">
<div className="w-full overflow-auto">
@@ -292,12 +554,12 @@ function RiskListViewContent({
Name
</th>
<th className="h-12 px-4 text-left align-middle font-medium text-tertiary w-1/4">
Probability
Inherent Severity
</th>
<th className="h-12 px-4 text-left align-middle font-medium text-tertiary w-1/4">
Impact
Residual Severity
</th>
<th className="h-12 px-4 text-left align-middle font-medium text-tertiary w-[80px]">
<th className="h-12 px-4 text-left align-middle font-medium text-tertiary w-[120px]">
Action
</th>
</tr>
@@ -331,7 +593,9 @@ function RiskListViewContent({
to={`/organizations/${organizationId}/risks/${risk.id}`}
className="block p-4 h-full w-full"
>
{floatToProbabilityText(risk.probability)}
{floatToPercentage(
risk.inherentLikelihood * risk.inherentImpact
)}
</Link>
</td>
<td className="p-0 align-middle w-1/4 whitespace-nowrap">
@@ -339,19 +603,40 @@ function RiskListViewContent({
to={`/organizations/${organizationId}/risks/${risk.id}`}
className="block p-4 h-full w-full"
>
{floatToImpactText(risk.impact)}
{risk.residualLikelihood && risk.residualImpact
? floatToPercentage(
risk.residualLikelihood * risk.residualImpact
)
: "Not set"}
</Link>
</td>
<td className="p-4 align-middle w-[80px]">
<Button
variant="ghost"
size="icon"
onClick={() => {
setRiskToDelete({ id: risk.id, name: risk.name });
}}
>
<Trash2 className="h-4 w-4 text-danger" />
</Button>
<td className="p-4 align-middle w-[120px]">
<div className="flex">
<Button
variant="ghost"
size="icon"
asChild
className="mr-1"
>
<Link
to={`/organizations/${organizationId}/risks/${risk.id}/edit`}
>
<Edit className="h-4 w-4 text-tertiary" />
</Link>
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => {
setRiskToDelete({
id: risk.id,
name: risk.name,
});
}}
>
<Trash2 className="h-4 w-4 text-danger" />
</Button>
</div>
</td>
</tr>
))

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,184 @@
/**
* @generated SignedSource<<c0da8faf6e1669e8cf890804a805b2d1>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type EditRiskViewQuery$variables = {
riskId: string;
};
export type EditRiskViewQuery$data = {
readonly risk: {
readonly description?: string;
readonly id?: string;
readonly inherentImpact?: number;
readonly inherentLikelihood?: number;
readonly name?: string;
readonly residualImpact?: number;
readonly residualLikelihood?: number;
};
};
export type EditRiskViewQuery = {
response: EditRiskViewQuery$data;
variables: EditRiskViewQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "riskId"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "riskId"
}
],
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "inherentLikelihood",
"storageKey": null
},
v6 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "inherentImpact",
"storageKey": null
},
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualLikelihood",
"storageKey": null
},
v8 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualImpact",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "EditRiskViewQuery",
"selections": [
{
"alias": "risk",
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"kind": "InlineFragment",
"selections": [
(v2/*: any*/),
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
(v6/*: any*/),
(v7/*: any*/),
(v8/*: any*/)
],
"type": "Risk",
"abstractKey": null
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "EditRiskViewQuery",
"selections": [
{
"alias": "risk",
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
(v2/*: any*/),
{
"kind": "InlineFragment",
"selections": [
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
(v6/*: any*/),
(v7/*: any*/),
(v8/*: any*/)
],
"type": "Risk",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "0cf5e4b47335aeb1fe04520d137942d1",
"id": null,
"metadata": {},
"name": "EditRiskViewQuery",
"operationKind": "query",
"text": "query EditRiskViewQuery(\n $riskId: ID!\n) {\n risk: node(id: $riskId) {\n __typename\n ... on Risk {\n id\n name\n description\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n }\n id\n }\n}\n"
}
};
})();
(node as any).hash = "d057f56759426d63136cef80269dc8a3";
export default node;

View File

@@ -0,0 +1,167 @@
/**
* @generated SignedSource<<c9530c22837ce495259b6d3ac404da87>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type UpdateRiskInput = {
description?: string | null | undefined;
id: string;
inherentImpact?: number | null | undefined;
inherentLikelihood?: number | null | undefined;
name?: string | null | undefined;
residualImpact?: number | null | undefined;
residualLikelihood?: number | null | undefined;
};
export type EditRiskViewUpdateRiskMutation$variables = {
input: UpdateRiskInput;
};
export type EditRiskViewUpdateRiskMutation$data = {
readonly updateRisk: {
readonly risk: {
readonly description: string;
readonly id: string;
readonly inherentImpact: number;
readonly inherentLikelihood: number;
readonly name: string;
readonly residualImpact: number;
readonly residualLikelihood: number;
readonly updatedAt: string;
};
};
};
export type EditRiskViewUpdateRiskMutation = {
response: EditRiskViewUpdateRiskMutation$data;
variables: EditRiskViewUpdateRiskMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "UpdateRiskPayload",
"kind": "LinkedField",
"name": "updateRisk",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Risk",
"kind": "LinkedField",
"name": "risk",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "inherentLikelihood",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "inherentImpact",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualLikelihood",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualImpact",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "EditRiskViewUpdateRiskMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "EditRiskViewUpdateRiskMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "1fb9a7f1a9a6dc2fd8b1c3235336324b",
"id": null,
"metadata": {},
"name": "EditRiskViewUpdateRiskMutation",
"operationKind": "mutation",
"text": "mutation EditRiskViewUpdateRiskMutation(\n $input: UpdateRiskInput!\n) {\n updateRisk(input: $input) {\n risk {\n id\n name\n description\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n updatedAt\n }\n }\n}\n"
}
};
})();
(node as any).hash = "75f21269b0c81f77ce4e3547010059ee";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<6488a6430dca9def82544e0ac0ca0db8>>
* @generated SignedSource<<63841b5a52846eba02cc6d442cfd4133>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -11,10 +11,12 @@
import { ConcreteRequest } from 'relay-runtime';
export type CreateRiskInput = {
description: string;
impact: number;
inherentImpact: number;
inherentLikelihood: number;
name: string;
organizationId: string;
probability: number;
residualImpact?: number | null | undefined;
residualLikelihood?: number | null | undefined;
};
export type NewRiskViewCreateRiskMutation$variables = {
connections: ReadonlyArray<string>;
@@ -27,9 +29,11 @@ export type NewRiskViewCreateRiskMutation$data = {
readonly createdAt: string;
readonly description: string;
readonly id: string;
readonly impact: number;
readonly inherentImpact: number;
readonly inherentLikelihood: number;
readonly name: string;
readonly probability: number;
readonly residualImpact: number;
readonly residualLikelihood: number;
readonly updatedAt: string;
};
};
@@ -99,14 +103,28 @@ v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "probability",
"name": "inherentLikelihood",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "impact",
"name": "inherentImpact",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualLikelihood",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualImpact",
"storageKey": null
},
{
@@ -195,16 +213,16 @@ return {
]
},
"params": {
"cacheID": "345bb376e4168b3b964b8a7ffa4ccbb9",
"cacheID": "86d84d72c424807216edf39eb8f0c34f",
"id": null,
"metadata": {},
"name": "NewRiskViewCreateRiskMutation",
"operationKind": "mutation",
"text": "mutation NewRiskViewCreateRiskMutation(\n $input: CreateRiskInput!\n) {\n createRisk(input: $input) {\n riskEdge {\n node {\n id\n name\n description\n probability\n impact\n createdAt\n updatedAt\n }\n }\n }\n}\n"
"text": "mutation NewRiskViewCreateRiskMutation(\n $input: CreateRiskInput!\n) {\n createRisk(input: $input) {\n riskEdge {\n node {\n id\n name\n description\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n createdAt\n updatedAt\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "3cf30aab7c80cc9308343c6ec1577c5a";
(node as any).hash = "7ad6c27c192e419839b8105eb2ca9ae7";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<e9f2db8dc7c26cc332a75299317684b7>>
* @generated SignedSource<<ada1e6614842f2accf7bbe92a4e845a4>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -190,14 +190,28 @@ return {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "probability",
"name": "inherentLikelihood",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "impact",
"name": "inherentImpact",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualLikelihood",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualImpact",
"storageKey": null
},
{
@@ -308,16 +322,16 @@ return {
]
},
"params": {
"cacheID": "33b15ec024ff6f9963d539be971aef7b",
"cacheID": "afcf1c15c2ed7722673da85d53acfef3",
"id": null,
"metadata": {},
"name": "RiskListViewPaginationQuery",
"operationKind": "query",
"text": "query RiskListViewPaginationQuery(\n $after: CursorKey\n $before: CursorKey\n $first: Int\n $last: Int\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...RiskListView_risks_pbnwq\n id\n }\n}\n\nfragment RiskListView_risks_pbnwq on Organization {\n risks(first: $first, after: $after, last: $last, before: $before) {\n edges {\n node {\n id\n name\n probability\n impact\n description\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n id\n}\n"
"text": "query RiskListViewPaginationQuery(\n $after: CursorKey\n $before: CursorKey\n $first: Int\n $last: Int\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...RiskListView_risks_pbnwq\n id\n }\n}\n\nfragment RiskListView_risks_pbnwq on Organization {\n risks(first: $first, after: $after, last: $last, before: $before) {\n edges {\n node {\n id\n name\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n description\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n id\n}\n"
}
};
})();
(node as any).hash = "59f58f0b1c17242dea5e762b6337bf98";
(node as any).hash = "7da0a3d5e4fb09ce7f6b7ddf233702ab";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<f8cd1d0374c4a6de2b577c7fcb35c984>>
* @generated SignedSource<<5301e57ef0b2621712f4c8ca693afdeb>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -192,14 +192,28 @@ return {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "probability",
"name": "inherentLikelihood",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "impact",
"name": "inherentImpact",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualLikelihood",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualImpact",
"storageKey": null
},
{
@@ -310,12 +324,12 @@ return {
]
},
"params": {
"cacheID": "78d7871004210dec73a83511b147856f",
"cacheID": "56ddd15a8a41c48c01bf0be667569473",
"id": null,
"metadata": {},
"name": "RiskListViewQuery",
"operationKind": "query",
"text": "query RiskListViewQuery(\n $organizationId: ID!\n $first: Int\n $after: CursorKey\n $last: Int\n $before: CursorKey\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ...RiskListView_risks_pbnwq\n }\n}\n\nfragment RiskListView_risks_pbnwq on Organization {\n risks(first: $first, after: $after, last: $last, before: $before) {\n edges {\n node {\n id\n name\n probability\n impact\n description\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n id\n}\n"
"text": "query RiskListViewQuery(\n $organizationId: ID!\n $first: Int\n $after: CursorKey\n $last: Int\n $before: CursorKey\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ...RiskListView_risks_pbnwq\n }\n}\n\nfragment RiskListView_risks_pbnwq on Organization {\n risks(first: $first, after: $after, last: $last, before: $before) {\n edges {\n node {\n id\n name\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n description\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n id\n}\n"
}
};
})();

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<1d60fbe24f12a69953d5ade8fb94ec1e>>
* @generated SignedSource<<c2e3450de2b84e18c9ade3ac1bed3b69>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -19,9 +19,11 @@ export type RiskListView_risks$data = {
readonly createdAt: string;
readonly description: string;
readonly id: string;
readonly impact: number;
readonly inherentImpact: number;
readonly inherentLikelihood: number;
readonly name: string;
readonly probability: number;
readonly residualImpact: number;
readonly residualLikelihood: number;
readonly updatedAt: string;
};
}>;
@@ -143,14 +145,28 @@ return {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "probability",
"name": "inherentLikelihood",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "impact",
"name": "inherentImpact",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualLikelihood",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualImpact",
"storageKey": null
},
{
@@ -255,6 +271,6 @@ return {
};
})();
(node as any).hash = "59f58f0b1c17242dea5e762b6337bf98";
(node as any).hash = "7da0a3d5e4fb09ce7f6b7ddf233702ab";
export default node;

View File

@@ -0,0 +1,93 @@
/**
* @generated SignedSource<<73623cc6511c56dd418345cd64d500d5>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type CreateRiskMitigationMappingInput = {
mitigationId: string;
riskId: string;
};
export type ShowRiskViewCreateRiskMitigationMappingMutation$variables = {
input: CreateRiskMitigationMappingInput;
};
export type ShowRiskViewCreateRiskMitigationMappingMutation$data = {
readonly createRiskMitigationMapping: {
readonly success: boolean;
};
};
export type ShowRiskViewCreateRiskMitigationMappingMutation = {
response: ShowRiskViewCreateRiskMitigationMappingMutation$data;
variables: ShowRiskViewCreateRiskMitigationMappingMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "CreateRiskMitigationMappingPayload",
"kind": "LinkedField",
"name": "createRiskMitigationMapping",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "success",
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "ShowRiskViewCreateRiskMitigationMappingMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "ShowRiskViewCreateRiskMitigationMappingMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "5a8744dfb9cf5f977c2702d848aab7ae",
"id": null,
"metadata": {},
"name": "ShowRiskViewCreateRiskMitigationMappingMutation",
"operationKind": "mutation",
"text": "mutation ShowRiskViewCreateRiskMitigationMappingMutation(\n $input: CreateRiskMitigationMappingInput!\n) {\n createRiskMitigationMapping(input: $input) {\n success\n }\n}\n"
}
};
})();
(node as any).hash = "bdb664dab4a9e655c0923cfdd4ab0fe9";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<16f4621b72343a04c88df9d0b137c9fd>>
* @generated SignedSource<<0ecdbc802635f485dd265dac34b04386>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -9,23 +9,21 @@
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type CreateRiskMappingInput = {
impact: number;
mitigationId: string;
probability: number;
export type CreateRiskPolicyMappingInput = {
policyId: string;
riskId: string;
};
export type ShowRiskViewCreateRiskMappingMutation$variables = {
input: CreateRiskMappingInput;
export type ShowRiskViewCreateRiskPolicyMappingMutation$variables = {
input: CreateRiskPolicyMappingInput;
};
export type ShowRiskViewCreateRiskMappingMutation$data = {
readonly createRiskMapping: {
export type ShowRiskViewCreateRiskPolicyMappingMutation$data = {
readonly createRiskPolicyMapping: {
readonly success: boolean;
};
};
export type ShowRiskViewCreateRiskMappingMutation = {
response: ShowRiskViewCreateRiskMappingMutation$data;
variables: ShowRiskViewCreateRiskMappingMutation$variables;
export type ShowRiskViewCreateRiskPolicyMappingMutation = {
response: ShowRiskViewCreateRiskPolicyMappingMutation$data;
variables: ShowRiskViewCreateRiskPolicyMappingMutation$variables;
};
const node: ConcreteRequest = (function(){
@@ -46,9 +44,9 @@ v1 = [
"variableName": "input"
}
],
"concreteType": "CreateRiskMappingPayload",
"concreteType": "CreateRiskPolicyMappingPayload",
"kind": "LinkedField",
"name": "createRiskMapping",
"name": "createRiskPolicyMapping",
"plural": false,
"selections": [
{
@@ -67,7 +65,7 @@ return {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "ShowRiskViewCreateRiskMappingMutation",
"name": "ShowRiskViewCreateRiskPolicyMappingMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
@@ -76,20 +74,20 @@ return {
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "ShowRiskViewCreateRiskMappingMutation",
"name": "ShowRiskViewCreateRiskPolicyMappingMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "32dfd9049a8c6c1d404528bac1316346",
"cacheID": "7aac9b6766d79dcc4785f72b6f6805a8",
"id": null,
"metadata": {},
"name": "ShowRiskViewCreateRiskMappingMutation",
"name": "ShowRiskViewCreateRiskPolicyMappingMutation",
"operationKind": "mutation",
"text": "mutation ShowRiskViewCreateRiskMappingMutation(\n $input: CreateRiskMappingInput!\n) {\n createRiskMapping(input: $input) {\n success\n }\n}\n"
"text": "mutation ShowRiskViewCreateRiskPolicyMappingMutation(\n $input: CreateRiskPolicyMappingInput!\n) {\n createRiskPolicyMapping(input: $input) {\n success\n }\n}\n"
}
};
})();
(node as any).hash = "02f82b14cc6cf7c2e39dad4613e9d191";
(node as any).hash = "5a10339ed779d863662406775e38ea7b";
export default node;

View File

@@ -0,0 +1,93 @@
/**
* @generated SignedSource<<406a706e512ed01c1924632306a4f81c>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type DeleteRiskMitigationMappingInput = {
mitigationId: string;
riskId: string;
};
export type ShowRiskViewDeleteRiskMitigationMappingMutation$variables = {
input: DeleteRiskMitigationMappingInput;
};
export type ShowRiskViewDeleteRiskMitigationMappingMutation$data = {
readonly deleteRiskMitigationMapping: {
readonly success: boolean;
};
};
export type ShowRiskViewDeleteRiskMitigationMappingMutation = {
response: ShowRiskViewDeleteRiskMitigationMappingMutation$data;
variables: ShowRiskViewDeleteRiskMitigationMappingMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "DeleteRiskMitigationMappingPayload",
"kind": "LinkedField",
"name": "deleteRiskMitigationMapping",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "success",
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "ShowRiskViewDeleteRiskMitigationMappingMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "ShowRiskViewDeleteRiskMitigationMappingMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "9a4b56b457c7fb53dc2583da93a3aaa4",
"id": null,
"metadata": {},
"name": "ShowRiskViewDeleteRiskMitigationMappingMutation",
"operationKind": "mutation",
"text": "mutation ShowRiskViewDeleteRiskMitigationMappingMutation(\n $input: DeleteRiskMitigationMappingInput!\n) {\n deleteRiskMitigationMapping(input: $input) {\n success\n }\n}\n"
}
};
})();
(node as any).hash = "d9fece39ecffbd551662d6491088ce4c";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<45a4aca30e16d598cd188f3cbc041a0d>>
* @generated SignedSource<<d11690667058b0e7d271b815f510c44c>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -9,21 +9,21 @@
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type DeleteRiskMappingInput = {
mitigationId: string;
export type DeleteRiskPolicyMappingInput = {
policyId: string;
riskId: string;
};
export type ShowRiskViewDeleteRiskMappingMutation$variables = {
input: DeleteRiskMappingInput;
export type ShowRiskViewDeleteRiskPolicyMappingMutation$variables = {
input: DeleteRiskPolicyMappingInput;
};
export type ShowRiskViewDeleteRiskMappingMutation$data = {
readonly deleteRiskMapping: {
export type ShowRiskViewDeleteRiskPolicyMappingMutation$data = {
readonly deleteRiskPolicyMapping: {
readonly success: boolean;
};
};
export type ShowRiskViewDeleteRiskMappingMutation = {
response: ShowRiskViewDeleteRiskMappingMutation$data;
variables: ShowRiskViewDeleteRiskMappingMutation$variables;
export type ShowRiskViewDeleteRiskPolicyMappingMutation = {
response: ShowRiskViewDeleteRiskPolicyMappingMutation$data;
variables: ShowRiskViewDeleteRiskPolicyMappingMutation$variables;
};
const node: ConcreteRequest = (function(){
@@ -44,9 +44,9 @@ v1 = [
"variableName": "input"
}
],
"concreteType": "DeleteRiskMappingPayload",
"concreteType": "DeleteRiskPolicyMappingPayload",
"kind": "LinkedField",
"name": "deleteRiskMapping",
"name": "deleteRiskPolicyMapping",
"plural": false,
"selections": [
{
@@ -65,7 +65,7 @@ return {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "ShowRiskViewDeleteRiskMappingMutation",
"name": "ShowRiskViewDeleteRiskPolicyMappingMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
@@ -74,20 +74,20 @@ return {
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "ShowRiskViewDeleteRiskMappingMutation",
"name": "ShowRiskViewDeleteRiskPolicyMappingMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "847faec465a30f457c57c5c1524a707d",
"cacheID": "a3e208ccc9b5c1248450edaec7ef8417",
"id": null,
"metadata": {},
"name": "ShowRiskViewDeleteRiskMappingMutation",
"name": "ShowRiskViewDeleteRiskPolicyMappingMutation",
"operationKind": "mutation",
"text": "mutation ShowRiskViewDeleteRiskMappingMutation(\n $input: DeleteRiskMappingInput!\n) {\n deleteRiskMapping(input: $input) {\n success\n }\n}\n"
"text": "mutation ShowRiskViewDeleteRiskPolicyMappingMutation(\n $input: DeleteRiskPolicyMappingInput!\n) {\n deleteRiskPolicyMapping(input: $input) {\n success\n }\n}\n"
}
};
})();
(node as any).hash = "41aa62a4185b1e6061bd7e06abbec85d";
(node as any).hash = "5e6b5677587f3c8bad3cec9d751952e1";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<771e7ea5d1af4e722dc5790db8abd623>>
* @generated SignedSource<<da6cda7380aec978f37e3adc35942e03>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -9,7 +9,6 @@
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type MitigationImportance = "ADVANCED" | "MANDATORY" | "PREFERRED";
export type MitigationState = "IMPLEMENTED" | "IN_PROGRESS" | "NOT_APPLICABLE" | "NOT_STARTED";
export type ShowRiskViewOrganizationMitigationsQuery$variables = {
organizationId: string;
@@ -23,7 +22,6 @@ export type ShowRiskViewOrganizationMitigationsQuery$data = {
readonly category: string;
readonly description: string;
readonly id: string;
readonly importance: MitigationImportance;
readonly name: string;
readonly state: MitigationState;
};
@@ -104,13 +102,6 @@ v4 = [
"name": "category",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "importance",
"storageKey": null
},
{
"alias": null,
"args": null,
@@ -253,7 +244,7 @@ return {
]
},
"params": {
"cacheID": "ac4f3fc31facab3cb2b9455751a4a528",
"cacheID": "a73345064022bd2f11ef2f971cd898b5",
"id": null,
"metadata": {
"connection": [
@@ -270,11 +261,11 @@ return {
},
"name": "ShowRiskViewOrganizationMitigationsQuery",
"operationKind": "query",
"text": "query ShowRiskViewOrganizationMitigationsQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n mitigations(first: 100) {\n edges {\n node {\n id\n name\n description\n category\n importance\n state\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
"text": "query ShowRiskViewOrganizationMitigationsQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n mitigations(first: 100) {\n edges {\n node {\n id\n name\n description\n category\n state\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "608bcf7b0e53529673b25ac7762627e2";
(node as any).hash = "76ce05cd9f5a367595eb31b5df24a679";
export default node;

View File

@@ -0,0 +1,255 @@
/**
* @generated SignedSource<<86be887d4ec59050fd69900fd80863a2>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type PolicyStatus = "ACTIVE" | "DRAFT";
export type ShowRiskViewOrganizationPoliciesQuery$variables = {
organizationId: string;
};
export type ShowRiskViewOrganizationPoliciesQuery$data = {
readonly organization: {
readonly id: string;
readonly policies?: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly name: string;
readonly status: PolicyStatus;
};
}>;
};
};
};
export type ShowRiskViewOrganizationPoliciesQuery = {
response: ShowRiskViewOrganizationPoliciesQuery$data;
variables: ShowRiskViewOrganizationPoliciesQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "organizationId"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "organizationId"
}
],
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v4 = [
{
"alias": null,
"args": null,
"concreteType": "PolicyEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Policy",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
(v3/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
"storageKey": null
}
],
v5 = [
{
"kind": "Literal",
"name": "first",
"value": 100
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "ShowRiskViewOrganizationPoliciesQuery",
"selections": [
{
"alias": "organization",
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": "policies",
"args": null,
"concreteType": "PolicyConnection",
"kind": "LinkedField",
"name": "__Organization__policies_connection",
"plural": false,
"selections": (v4/*: any*/),
"storageKey": null
}
],
"type": "Organization",
"abstractKey": null
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "ShowRiskViewOrganizationPoliciesQuery",
"selections": [
{
"alias": "organization",
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
(v2/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": (v5/*: any*/),
"concreteType": "PolicyConnection",
"kind": "LinkedField",
"name": "policies",
"plural": false,
"selections": (v4/*: any*/),
"storageKey": "policies(first:100)"
},
{
"alias": null,
"args": (v5/*: any*/),
"filters": null,
"handle": "connection",
"key": "Organization__policies",
"kind": "LinkedHandle",
"name": "policies"
}
],
"type": "Organization",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "a229f1f805e79f8de378305e021d7fea",
"id": null,
"metadata": {
"connection": [
{
"count": null,
"cursor": null,
"direction": "forward",
"path": [
"organization",
"policies"
]
}
]
},
"name": "ShowRiskViewOrganizationPoliciesQuery",
"operationKind": "query",
"text": "query ShowRiskViewOrganizationPoliciesQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n policies(first: 100) {\n edges {\n node {\n id\n name\n status\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "26cbf19cd716aee7413931357b9c5354";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<61ecdce52780dbfd62783deecb27645d>>
* @generated SignedSource<<a6b1be8aa499bb8709a4d4e04edb01b7>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -9,17 +9,29 @@
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type MitigationImportance = "ADVANCED" | "MANDATORY" | "PREFERRED";
export type MitigationState = "IMPLEMENTED" | "IN_PROGRESS" | "NOT_APPLICABLE" | "NOT_STARTED";
export type PolicyStatus = "ACTIVE" | "DRAFT";
export type ShowRiskViewQuery$variables = {
riskId: string;
};
export type ShowRiskViewQuery$data = {
readonly node: {
readonly controls?: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly createdAt: string;
readonly description: string;
readonly id: string;
readonly name: string;
readonly referenceId: string;
};
}>;
};
readonly createdAt?: string;
readonly description?: string;
readonly id: string;
readonly impact?: number;
readonly inherentImpact?: number;
readonly inherentLikelihood?: number;
readonly mitigations?: {
readonly edges: ReadonlyArray<{
readonly node: {
@@ -27,14 +39,24 @@ export type ShowRiskViewQuery$data = {
readonly createdAt: string;
readonly description: string;
readonly id: string;
readonly importance: MitigationImportance;
readonly name: string;
readonly state: MitigationState;
};
}>;
};
readonly name?: string;
readonly probability?: number;
readonly policies?: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly createdAt: string;
readonly id: string;
readonly name: string;
readonly status: PolicyStatus;
};
}>;
};
readonly residualImpact?: number;
readonly residualLikelihood?: number;
readonly updatedAt?: string;
};
};
@@ -83,38 +105,84 @@ v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "probability",
"name": "inherentLikelihood",
"storageKey": null
},
v6 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "impact",
"name": "inherentImpact",
"storageKey": null
},
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"name": "residualLikelihood",
"storageKey": null
},
v8 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"name": "residualImpact",
"storageKey": null
},
v9 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
v10 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
v11 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v10 = [
v12 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
},
v13 = {
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
"storageKey": null
},
v14 = [
{
"alias": null,
"args": null,
@@ -141,14 +209,7 @@ v10 = [
"name": "category",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "importance",
"storageKey": null
},
(v7/*: any*/),
(v9/*: any*/),
{
"alias": null,
"args": null,
@@ -156,47 +217,92 @@ v10 = [
"name": "state",
"storageKey": null
},
(v9/*: any*/)
(v11/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
(v12/*: any*/)
],
"storageKey": null
},
(v13/*: any*/)
],
v15 = [
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"concreteType": "PolicyEdge",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"concreteType": "Policy",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
(v9/*: any*/),
(v11/*: any*/)
],
"storageKey": null
},
(v12/*: any*/)
],
"storageKey": null
},
(v13/*: any*/)
],
v16 = [
{
"alias": null,
"args": null,
"concreteType": "ControlEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"concreteType": "Control",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "referenceId",
"storageKey": null
},
(v3/*: any*/),
(v4/*: any*/),
(v9/*: any*/),
(v11/*: any*/)
],
"storageKey": null
}
},
(v12/*: any*/)
],
"storageKey": null
}
},
(v13/*: any*/)
],
v11 = [
v17 = [
{
"kind": "Literal",
"name": "first",
@@ -228,6 +334,8 @@ return {
(v6/*: any*/),
(v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/),
(v10/*: any*/),
{
"alias": "mitigations",
"args": null,
@@ -235,7 +343,27 @@ return {
"kind": "LinkedField",
"name": "__Risk__mitigations_connection",
"plural": false,
"selections": (v10/*: any*/),
"selections": (v14/*: any*/),
"storageKey": null
},
{
"alias": "policies",
"args": null,
"concreteType": "PolicyConnection",
"kind": "LinkedField",
"name": "__Risk__policies_connection",
"plural": false,
"selections": (v15/*: any*/),
"storageKey": null
},
{
"alias": "controls",
"args": null,
"concreteType": "ControlConnection",
"kind": "LinkedField",
"name": "__Risk__controls_connection",
"plural": false,
"selections": (v16/*: any*/),
"storageKey": null
}
],
@@ -263,7 +391,7 @@ return {
"name": "node",
"plural": false,
"selections": [
(v9/*: any*/),
(v11/*: any*/),
(v2/*: any*/),
{
"kind": "InlineFragment",
@@ -274,24 +402,64 @@ return {
(v6/*: any*/),
(v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/),
(v10/*: any*/),
{
"alias": null,
"args": (v11/*: any*/),
"args": (v17/*: any*/),
"concreteType": "MitigationConnection",
"kind": "LinkedField",
"name": "mitigations",
"plural": false,
"selections": (v10/*: any*/),
"selections": (v14/*: any*/),
"storageKey": "mitigations(first:100)"
},
{
"alias": null,
"args": (v11/*: any*/),
"args": (v17/*: any*/),
"filters": null,
"handle": "connection",
"key": "Risk__mitigations",
"kind": "LinkedHandle",
"name": "mitigations"
},
{
"alias": null,
"args": (v17/*: any*/),
"concreteType": "PolicyConnection",
"kind": "LinkedField",
"name": "policies",
"plural": false,
"selections": (v15/*: any*/),
"storageKey": "policies(first:100)"
},
{
"alias": null,
"args": (v17/*: any*/),
"filters": null,
"handle": "connection",
"key": "Risk__policies",
"kind": "LinkedHandle",
"name": "policies"
},
{
"alias": null,
"args": (v17/*: any*/),
"concreteType": "ControlConnection",
"kind": "LinkedField",
"name": "controls",
"plural": false,
"selections": (v16/*: any*/),
"storageKey": "controls(first:100)"
},
{
"alias": null,
"args": (v17/*: any*/),
"filters": null,
"handle": "connection",
"key": "Risk__controls",
"kind": "LinkedHandle",
"name": "controls"
}
],
"type": "Risk",
@@ -303,7 +471,7 @@ return {
]
},
"params": {
"cacheID": "b30cf22fe56e7cb776d6d4a5af81e358",
"cacheID": "4463fac13f91564f513bfdabcc3e0063",
"id": null,
"metadata": {
"connection": [
@@ -315,16 +483,34 @@ return {
"node",
"mitigations"
]
},
{
"count": null,
"cursor": null,
"direction": "forward",
"path": [
"node",
"policies"
]
},
{
"count": null,
"cursor": null,
"direction": "forward",
"path": [
"node",
"controls"
]
}
]
},
"name": "ShowRiskViewQuery",
"operationKind": "query",
"text": "query ShowRiskViewQuery(\n $riskId: ID!\n) {\n node(id: $riskId) {\n __typename\n id\n ... on Risk {\n name\n description\n probability\n impact\n createdAt\n updatedAt\n mitigations(first: 100) {\n edges {\n node {\n id\n name\n description\n category\n importance\n createdAt\n state\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
"text": "query ShowRiskViewQuery(\n $riskId: ID!\n) {\n node(id: $riskId) {\n __typename\n id\n ... on Risk {\n name\n description\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n createdAt\n updatedAt\n mitigations(first: 100) {\n edges {\n node {\n id\n name\n description\n category\n createdAt\n state\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n policies(first: 100) {\n edges {\n node {\n id\n name\n status\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n controls(first: 100) {\n edges {\n node {\n id\n referenceId\n name\n description\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "77f36d2a4405051ede742be63d3e1e38";
(node as any).hash = "68db9a4877abc338711844de6da38cb7";
export default node;