Update risk matrix

Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
gearnode
2025-04-15 11:16:24 -07:00
parent 7ff083e339
commit a90de1fdde
12 changed files with 771 additions and 585 deletions

View File

@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
## [Unreleased] ## [Unreleased]
### Changed
- More explicit scale, legend and score for risk matrix
## [0.10.1] - 2025-04-14 ## [0.10.1] - 2025-04-14
### Fixed ### Fixed

View File

@@ -5,8 +5,8 @@
"variations": [ "variations": [
{ {
"context": "B2B SaaS SMB", "context": "B2B SaaS SMB",
"impact": 0.75, "impact": 4,
"likelihood": 0.8, "likelihood": 4,
"recommendedTreatment": "Transfer" "recommendedTreatment": "Transfer"
} }
] ]
@@ -17,8 +17,8 @@
"variations": [ "variations": [
{ {
"context": "B2B SaaS SMB", "context": "B2B SaaS SMB",
"impact": 1.0, "impact": 5,
"likelihood": 0.3, "likelihood": 2,
"recommendedTreatment": "Transfer" "recommendedTreatment": "Transfer"
} }
] ]
@@ -29,8 +29,8 @@
"variations": [ "variations": [
{ {
"context": "B2B SaaS SMB", "context": "B2B SaaS SMB",
"impact": 0.5, "impact": 3,
"likelihood": 0.5, "likelihood": 3,
"recommendedTreatment": "Reduce" "recommendedTreatment": "Reduce"
} }
] ]
@@ -41,8 +41,8 @@
"variations": [ "variations": [
{ {
"context": "B2B SaaS SMB", "context": "B2B SaaS SMB",
"impact": 0.5, "impact": 3,
"likelihood": 0.15, "likelihood": 1,
"recommendedTreatment": "Accept" "recommendedTreatment": "Accept"
} }
] ]
@@ -53,8 +53,8 @@
"variations": [ "variations": [
{ {
"context": "B2B SaaS SMB", "context": "B2B SaaS SMB",
"impact": 0.5, "impact": 3,
"likelihood": 0.8, "likelihood": 4,
"recommendedTreatment": "Transfer" "recommendedTreatment": "Transfer"
} }
] ]
@@ -65,8 +65,8 @@
"variations": [ "variations": [
{ {
"context": "B2B SaaS SMB", "context": "B2B SaaS SMB",
"impact": 0.5, "impact": 3,
"likelihood": 0.3, "likelihood": 2,
"recommendedTreatment": "Reduce" "recommendedTreatment": "Reduce"
} }
] ]
@@ -77,8 +77,8 @@
"variations": [ "variations": [
{ {
"context": "B2B SaaS SMB", "context": "B2B SaaS SMB",
"impact": 0.1, "impact": 1,
"likelihood": 0.15, "likelihood": 1,
"recommendedTreatment": "Accept" "recommendedTreatment": "Accept"
} }
] ]
@@ -89,8 +89,8 @@
"variations": [ "variations": [
{ {
"context": "B2B SaaS SMB", "context": "B2B SaaS SMB",
"impact": 0.5, "impact": 3,
"likelihood": 0.5, "likelihood": 3,
"recommendedTreatment": "Reduce" "recommendedTreatment": "Reduce"
} }
] ]
@@ -101,8 +101,8 @@
"variations": [ "variations": [
{ {
"context": "B2B SaaS SMB", "context": "B2B SaaS SMB",
"impact": 0.5, "impact": 3,
"likelihood": 0.8, "likelihood": 4,
"recommendedTreatment": "Transfer" "recommendedTreatment": "Transfer"
} }
] ]
@@ -113,8 +113,8 @@
"variations": [ "variations": [
{ {
"context": "B2B SaaS SMB", "context": "B2B SaaS SMB",
"impact": 0.75, "impact": 4,
"likelihood": 0.5, "likelihood": 3,
"recommendedTreatment": "Transfer" "recommendedTreatment": "Transfer"
} }
] ]
@@ -125,8 +125,8 @@
"variations": [ "variations": [
{ {
"context": "B2B SaaS SMB", "context": "B2B SaaS SMB",
"impact": 0.75, "impact": 4,
"likelihood": 0.3, "likelihood": 2,
"recommendedTreatment": "Reduce" "recommendedTreatment": "Reduce"
} }
] ]
@@ -137,8 +137,8 @@
"variations": [ "variations": [
{ {
"context": "B2B SaaS SMB", "context": "B2B SaaS SMB",
"impact": 0.5, "impact": 3,
"likelihood": 0.15, "likelihood": 1,
"recommendedTreatment": "Accept" "recommendedTreatment": "Accept"
} }
] ]
@@ -149,8 +149,8 @@
"variations": [ "variations": [
{ {
"context": "B2B SaaS SMB", "context": "B2B SaaS SMB",
"impact": 0.25, "impact": 1,
"likelihood": 0.5, "likelihood": 3,
"recommendedTreatment": "Reduce" "recommendedTreatment": "Reduce"
} }
] ]
@@ -161,8 +161,8 @@
"variations": [ "variations": [
{ {
"context": "B2B SaaS SMB", "context": "B2B SaaS SMB",
"impact": 0.5, "impact": 3,
"likelihood": 0.5, "likelihood": 3,
"recommendedTreatment": "Reduce" "recommendedTreatment": "Reduce"
} }
] ]
@@ -173,8 +173,8 @@
"variations": [ "variations": [
{ {
"context": "B2B SaaS SMB", "context": "B2B SaaS SMB",
"impact": 1.0, "impact": 5,
"likelihood": 0.5, "likelihood": 3,
"recommendedTreatment": "Transfer" "recommendedTreatment": "Transfer"
} }
] ]
@@ -185,8 +185,8 @@
"variations": [ "variations": [
{ {
"context": "B2B SaaS SMB", "context": "B2B SaaS SMB",
"impact": 0.5, "impact": 3,
"likelihood": 0.8, "likelihood": 4,
"recommendedTreatment": "Transfer" "recommendedTreatment": "Transfer"
} }
] ]
@@ -197,8 +197,8 @@
"variations": [ "variations": [
{ {
"context": "B2B SaaS SMB", "context": "B2B SaaS SMB",
"impact": 0.25, "impact": 1,
"likelihood": 0.3, "likelihood": 2,
"recommendedTreatment": "Reduce" "recommendedTreatment": "Reduce"
} }
] ]
@@ -209,8 +209,8 @@
"variations": [ "variations": [
{ {
"context": "B2B SaaS SMB", "context": "B2B SaaS SMB",
"impact": 0.5, "impact": 3,
"likelihood": 0.8, "likelihood": 4,
"recommendedTreatment": "Transfer" "recommendedTreatment": "Transfer"
} }
] ]
@@ -221,8 +221,8 @@
"variations": [ "variations": [
{ {
"context": "B2B SaaS SMB", "context": "B2B SaaS SMB",
"impact": 0.5, "impact": 3,
"likelihood": 0.8, "likelihood": 4,
"recommendedTreatment": "Transfer" "recommendedTreatment": "Transfer"
} }
] ]
@@ -233,8 +233,8 @@
"variations": [ "variations": [
{ {
"context": "B2B SaaS SMB", "context": "B2B SaaS SMB",
"impact": 0.25, "impact": 1,
"likelihood": 1.0, "likelihood": 5,
"recommendedTreatment": "Reduce" "recommendedTreatment": "Reduce"
} }
] ]
@@ -245,8 +245,8 @@
"variations": [ "variations": [
{ {
"context": "B2B SaaS SMB", "context": "B2B SaaS SMB",
"impact": 0.5, "impact": 3,
"likelihood": 0.5, "likelihood": 3,
"recommendedTreatment": "Reduce" "recommendedTreatment": "Reduce"
} }
] ]

View File

@@ -104,12 +104,10 @@ function EditRiskViewContent({
const [name, setName] = useState(""); const [name, setName] = useState("");
const [description, setDescription] = useState(""); const [description, setDescription] = useState("");
const [inherentLikelihood, setInherentLikelihood] = const [inherentLikelihood, setInherentLikelihood] = useState<number>(3);
useState<string>("OCCASIONAL"); const [inherentImpact, setInherentImpact] = useState<number>(3);
const [inherentImpact, setInherentImpact] = useState<string>("MODERATE"); const [residualLikelihood, setResidualLikelihood] = useState<number>(3);
const [residualLikelihood, setResidualLikelihood] = const [residualImpact, setResidualImpact] = useState<number>(3);
useState<string>("OCCASIONAL");
const [residualImpact, setResidualImpact] = useState<string>("MODERATE");
const [treatment, setTreatment] = useState<RiskTreatment>("MITIGATED"); const [treatment, setTreatment] = useState<RiskTreatment>("MITIGATED");
const [ownerId, setOwnerId] = useState<string | null>(null); const [ownerId, setOwnerId] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
@@ -117,68 +115,16 @@ function EditRiskViewContent({
const [updateRisk, isInFlight] = const [updateRisk, isInFlight] =
useMutation<EditRiskViewUpdateRiskMutation>(updateRiskMutation); useMutation<EditRiskViewUpdateRiskMutation>(updateRiskMutation);
// Helper function to convert float to likelihood string
const floatToLikelihood = (value: number): string => {
if (value <= 0.2) return "IMPROBABLE";
if (value <= 0.4) return "REMOTE";
if (value <= 0.6) return "OCCASIONAL";
if (value <= 0.8) return "PROBABLE";
return "FREQUENT";
};
// Helper function to convert float to impact string
const floatToImpact = (value: number): string => {
if (value <= 0.2) return "NEGLIGIBLE";
if (value <= 0.4) return "LOW";
if (value <= 0.6) return "MODERATE";
if (value <= 0.8) return "SIGNIFICANT";
return "CATASTROPHIC";
};
// Map string values to float values
const likelihoodToFloat = (value: string): number => {
switch (value) {
case "IMPROBABLE":
return 0.1;
case "REMOTE":
return 0.3;
case "OCCASIONAL":
return 0.5;
case "PROBABLE":
return 0.7;
case "FREQUENT":
return 0.9;
default:
return 0.5;
}
};
const impactToFloat = (value: string): number => {
switch (value) {
case "NEGLIGIBLE":
return 0.1;
case "LOW":
return 0.3;
case "MODERATE":
return 0.5;
case "SIGNIFICANT":
return 0.7;
case "CATASTROPHIC":
return 0.9;
default:
return 0.5;
}
};
// Initialize form with risk data // Initialize form with risk data
useEffect(() => { useEffect(() => {
if (risk) { if (risk) {
setName(risk.name || ""); setName(risk.name || "");
setDescription(risk.description || ""); setDescription(risk.description || "");
setInherentLikelihood(floatToLikelihood(risk.inherentLikelihood || 0.5)); // Set values directly as integers
setInherentImpact(floatToImpact(risk.inherentImpact || 0.5)); setInherentLikelihood(risk.inherentLikelihood || 3);
setResidualLikelihood(floatToLikelihood(risk.residualLikelihood || 0.5)); setInherentImpact(risk.inherentImpact || 3);
setResidualImpact(floatToImpact(risk.residualImpact || 0.5)); setResidualLikelihood(risk.residualLikelihood || 3);
setResidualImpact(risk.residualImpact || 3);
setTreatment(risk.treatment || "MITIGATED"); setTreatment(risk.treatment || "MITIGATED");
setOwnerId(risk.owner?.id || null); setOwnerId(risk.owner?.id || null);
} }
@@ -202,10 +148,10 @@ function EditRiskViewContent({
id: riskId!, id: riskId!,
name, name,
description, description,
inherentLikelihood: likelihoodToFloat(inherentLikelihood), inherentLikelihood,
inherentImpact: impactToFloat(inherentImpact), inherentImpact,
residualLikelihood: likelihoodToFloat(residualLikelihood), residualLikelihood,
residualImpact: impactToFloat(residualImpact), residualImpact,
treatment, treatment,
ownerId: ownerId || undefined, ownerId: ownerId || undefined,
}; };
@@ -302,40 +248,44 @@ function EditRiskViewContent({
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="inherentLikelihood"> <Label htmlFor="inherentLikelihood">
Inherent Likelihood Inherent Likelihood (1-5)
</Label> </Label>
<Select <Select
value={inherentLikelihood} value={inherentLikelihood.toString()}
onValueChange={setInherentLikelihood} onValueChange={(value) =>
setInherentLikelihood(parseInt(value))
}
> >
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
<SelectValue placeholder="Select likelihood" /> <SelectValue placeholder="Select likelihood level" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="IMPROBABLE">Improbable</SelectItem> <SelectItem value="1">1 - Improbable</SelectItem>
<SelectItem value="REMOTE">Remote</SelectItem> <SelectItem value="2">2 - Remote</SelectItem>
<SelectItem value="OCCASIONAL">Occasional</SelectItem> <SelectItem value="3">3 - Occasional</SelectItem>
<SelectItem value="PROBABLE">Probable</SelectItem> <SelectItem value="4">4 - Probable</SelectItem>
<SelectItem value="FREQUENT">Frequent</SelectItem> <SelectItem value="5">5 - Frequent</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="inherentImpact">Inherent Impact</Label> <Label htmlFor="inherentImpact">Inherent Impact (1-5)</Label>
<Select <Select
value={inherentImpact} value={inherentImpact.toString()}
onValueChange={setInherentImpact} onValueChange={(value) =>
setInherentImpact(parseInt(value))
}
> >
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
<SelectValue placeholder="Select impact" /> <SelectValue placeholder="Select impact level" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="NEGLIGIBLE">Negligible</SelectItem> <SelectItem value="1">1 - Negligible</SelectItem>
<SelectItem value="LOW">Low</SelectItem> <SelectItem value="2">2 - Low</SelectItem>
<SelectItem value="MODERATE">Moderate</SelectItem> <SelectItem value="3">3 - Moderate</SelectItem>
<SelectItem value="SIGNIFICANT">Significant</SelectItem> <SelectItem value="4">4 - Significant</SelectItem>
<SelectItem value="CATASTROPHIC">Catastrophic</SelectItem> <SelectItem value="5">5 - Catastrophic</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
@@ -376,40 +326,44 @@ function EditRiskViewContent({
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="residualLikelihood"> <Label htmlFor="residualLikelihood">
Residual Likelihood Residual Likelihood (1-5)
</Label> </Label>
<Select <Select
value={residualLikelihood} value={residualLikelihood.toString()}
onValueChange={setResidualLikelihood} onValueChange={(value) =>
setResidualLikelihood(parseInt(value))
}
> >
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
<SelectValue placeholder="Select likelihood" /> <SelectValue placeholder="Select likelihood level" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="IMPROBABLE">Improbable</SelectItem> <SelectItem value="1">1 - Improbable</SelectItem>
<SelectItem value="REMOTE">Remote</SelectItem> <SelectItem value="2">2 - Remote</SelectItem>
<SelectItem value="OCCASIONAL">Occasional</SelectItem> <SelectItem value="3">3 - Occasional</SelectItem>
<SelectItem value="PROBABLE">Probable</SelectItem> <SelectItem value="4">4 - Probable</SelectItem>
<SelectItem value="FREQUENT">Frequent</SelectItem> <SelectItem value="5">5 - Frequent</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="residualImpact">Residual Impact</Label> <Label htmlFor="residualImpact">Residual Impact (1-5)</Label>
<Select <Select
value={residualImpact} value={residualImpact.toString()}
onValueChange={setResidualImpact} onValueChange={(value) =>
setResidualImpact(parseInt(value))
}
> >
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
<SelectValue placeholder="Select impact" /> <SelectValue placeholder="Select impact level" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="NEGLIGIBLE">Negligible</SelectItem> <SelectItem value="1">1 - Negligible</SelectItem>
<SelectItem value="LOW">Low</SelectItem> <SelectItem value="2">2 - Low</SelectItem>
<SelectItem value="MODERATE">Moderate</SelectItem> <SelectItem value="3">3 - Moderate</SelectItem>
<SelectItem value="SIGNIFICANT">Significant</SelectItem> <SelectItem value="4">4 - Significant</SelectItem>
<SelectItem value="CATASTROPHIC">Catastrophic</SelectItem> <SelectItem value="5">5 - Catastrophic</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>

View File

@@ -109,17 +109,11 @@ const deleteRiskMutation = graphql`
} }
`; `;
// Helper function to convert float values to percentage
// This is kept for potential future use, like tooltips
const floatToPercentage = (value: number): string => {
return `${Math.round(value * 100)}%`;
};
// Helper function to convert risk score to severity label // Helper function to convert risk score to severity label
const riskScoreToSeverity = (score: number): string => { const riskScoreToSeverity = (score: number): string => {
if (score >= 0.75) return "Catastrophic"; if (score >= 20) return "Catastrophic";
if (score >= 0.5) return "Critical"; if (score >= 12) return "Critical";
if (score >= 0.25) return "Marginal"; if (score >= 5) return "Marginal";
return "Negligible"; return "Negligible";
}; };
@@ -207,6 +201,7 @@ const emptyRiskMatrixColors = {
function RiskMatrix({ function RiskMatrix({
risks, risks,
isResidual = false, isResidual = false,
organizationId,
}: { }: {
risks: Array<{ risks: Array<{
id: string; id: string;
@@ -217,24 +212,13 @@ function RiskMatrix({
residualImpact?: number; residualImpact?: number;
}>; }>;
isResidual?: boolean; isResidual?: boolean;
organizationId: string;
}): JSX.Element { }): JSX.Element {
// Define impact ranges for the vertical axis (rows) // Define impact values for the vertical axis (rows) - from highest (5) to lowest (1)
const impactRanges: [number, number][] = [ const impactValues: number[] = [5, 4, 3, 2, 1];
[0.8, 1], // Highest impact - top row
[0.6, 0.8], // High impact
[0.4, 0.6], // Medium impact
[0.2, 0.4], // Low impact
[0, 0.2], // Lowest impact - bottom row
];
// Define likelihood ranges for the horizontal axis (columns) // Define likelihood values for the horizontal axis (columns) - from lowest (1) to highest (5)
const likelihoodRanges: [number, number][] = [ const likelihoodValues: number[] = [1, 2, 3, 4, 5];
[0, 0.2], // Lowest likelihood - leftmost column
[0.2, 0.4], // Low likelihood
[0.4, 0.6], // Medium likelihood
[0.6, 0.8], // High likelihood
[0.8, 1], // Highest likelihood - rightmost column
];
const impactLabels = [ const impactLabels = [
"Catastrophic", "Catastrophic",
@@ -253,10 +237,7 @@ function RiskMatrix({
]; ];
// Function to get cell content with risks that fall in this cell // Function to get cell content with risks that fall in this cell
const getCellContent = ( const getCellContent = (impactValue: number, likelihoodValue: number) => {
impactRange: [number, number],
likelihoodRange: [number, number]
) => {
return risks.filter((risk) => { return risks.filter((risk) => {
const likelihood = isResidual const likelihood = isResidual
? risk.residualLikelihood ?? risk.inherentLikelihood ? risk.residualLikelihood ?? risk.inherentLikelihood
@@ -265,12 +246,7 @@ function RiskMatrix({
? risk.residualImpact ?? risk.inherentImpact ? risk.residualImpact ?? risk.inherentImpact
: risk.inherentImpact; : risk.inherentImpact;
return ( return likelihood === likelihoodValue && impact === impactValue;
likelihood > likelihoodRange[0] &&
likelihood <= likelihoodRange[1] &&
impact > impactRange[0] &&
impact <= impactRange[1]
);
}); });
}; };
@@ -280,54 +256,63 @@ function RiskMatrix({
const getCellColor = (row: number, col: number, isEmpty: boolean): string => { const getCellColor = (row: number, col: number, isEmpty: boolean): string => {
const colorSet = isEmpty ? emptyRiskMatrixColors : riskMatrixColors; const colorSet = isEmpty ? emptyRiskMatrixColors : riskMatrixColors;
// Top row (highest impact - Catastrophic) // Hard-coded color matrix where each element [row][col] represents a specific cell
if (row === 0) { // This follows the 5x5 grid with rows representing impact (5 to 1) and columns representing likelihood (1 to 5)
if (col <= 1) return colorSet.medium; // Yellow for first two cells const colorMatrix = [
return colorSet.high; // Red for the rest // Impact 5 (Catastrophic) - first row
} [
colorSet.medium,
colorSet.medium,
colorSet.high,
colorSet.high,
colorSet.high,
],
// Impact 4 (Significant) - second row
[
colorSet.low,
colorSet.medium,
colorSet.medium,
colorSet.high,
colorSet.high,
],
// Impact 3 (Moderate) - third row
[
colorSet.low,
colorSet.low,
colorSet.medium,
colorSet.medium,
colorSet.high,
],
// Impact 2 (Low) - fourth row
[
colorSet.low,
colorSet.low,
colorSet.low,
colorSet.medium,
colorSet.medium,
],
// Impact 1 (Negligible) - fifth row
[colorSet.low, colorSet.low, colorSet.low, colorSet.low, colorSet.medium],
];
// Second row (Significant impact) return colorMatrix[row][col];
if (row === 1) {
if (col === 0) return colorSet.low; // Green for first cell
if (col <= 2) return colorSet.medium; // Yellow for next two
return colorSet.high; // Red for the rest
}
// Middle row (Moderate impact)
if (row === 2) {
if (col === 0) return colorSet.low; // Green for first cell
if (col <= 3) return colorSet.medium; // Yellow for next three
return colorSet.high; // Red for last cell
}
// Fourth row (Low impact)
if (row === 3) {
if (col <= 1) return colorSet.low; // Green for first two
return colorSet.medium; // Yellow for the rest
}
// Bottom row (lowest impact - Negligible)
if (row === 4) {
if (col <= 3) return colorSet.low; // Green for first four
return colorSet.medium; // Yellow for last cell
}
return "bg-gray-100";
}; };
// Instead of using refs and tippy, we'll use a component with popover // Instead of using refs and tippy, we'll use a component with popover
const RiskCell = ({ const RiskCell = ({
rowIndex, rowIndex,
colIndex, colIndex,
impactRange, impactValue,
likelihoodRange, likelihoodValue,
organizationId,
}: { }: {
rowIndex: number; rowIndex: number;
colIndex: number; colIndex: number;
impactRange: [number, number]; impactValue: number;
likelihoodRange: [number, number]; likelihoodValue: number;
organizationId: string;
}) => { }) => {
const cellRisks = getCellContent(impactRange, likelihoodRange); const cellRisks = getCellContent(impactValue, likelihoodValue);
const isEmpty = cellRisks.length === 0; const isEmpty = cellRisks.length === 0;
const cellColor = getCellColor(rowIndex, colIndex, isEmpty); const cellColor = getCellColor(rowIndex, colIndex, isEmpty);
@@ -354,17 +339,109 @@ function RiskMatrix({
</div> </div>
</td> </td>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-64 p-3" align="center"> <PopoverContent className="w-72 p-4" align="center">
<div className="font-medium mb-2"> <div className="font-semibold text-lg mb-2">
{cellRisks.length} Risk{cellRisks.length > 1 ? "s" : ""}: {cellRisks.length} Risk{cellRisks.length > 1 ? "s" : ""}
</div> </div>
<ul className="list-disc pl-4 space-y-1">
{cellRisks.map((risk) => ( <div className="bg-gray-50 p-3 rounded-md mb-3 space-y-1">
<li key={risk.id} className="text-sm"> <div className="grid grid-cols-2 gap-2">
{risk.name} <div className="text-sm">
</li> <span className="text-muted-foreground">Impact:</span>{" "}
))} <span className="font-medium">{impactValue}</span>
</ul> </div>
<div className="text-sm">
<span className="text-muted-foreground">Likelihood:</span>{" "}
<span className="font-medium">{likelihoodValue}</span>
</div>
</div>
<div className="text-sm pt-1 border-t border-gray-200 mt-1">
<span className="text-muted-foreground">Risk Score:</span>
<span className="font-bold ml-1">
{impactValue * likelihoodValue}
</span>
<span
className="ml-2 px-2 py-0.5 text-xs rounded-full font-medium inline-block"
style={{
backgroundColor:
impactValue * likelihoodValue >= 20
? "#ef4444"
: impactValue * likelihoodValue >= 12
? "#f59e0b"
: impactValue * likelihoodValue >= 5
? "#10b981"
: "#94a3b8",
color:
impactValue * likelihoodValue >= 12 ? "white" : "inherit",
}}
>
{impactValue * likelihoodValue >= 20
? "Catastrophic"
: impactValue * likelihoodValue >= 12
? "Critical"
: impactValue * likelihoodValue >= 5
? "Marginal"
: "Negligible"}
</span>
</div>
</div>
{cellRisks.length > 0 && (
<>
<div className="text-sm font-medium mb-1">Items:</div>
<ul className="space-y-1.5 max-h-48 overflow-y-auto">
{cellRisks.map((risk) => (
<li
key={risk.id}
className="text-sm border-l-2 pl-2"
style={{
borderColor:
(isResidual
? (risk.residualImpact || risk.inherentImpact) *
(risk.residualLikelihood || risk.inherentLikelihood)
: risk.inherentImpact * risk.inherentLikelihood) >= 20
? "#ef4444"
: (isResidual
? (risk.residualImpact || risk.inherentImpact) *
(risk.residualLikelihood ||
risk.inherentLikelihood)
: risk.inherentImpact *
risk.inherentLikelihood) >= 12
? "#f59e0b"
: (isResidual
? (risk.residualImpact || risk.inherentImpact) *
(risk.residualLikelihood ||
risk.inherentLikelihood)
: risk.inherentImpact *
risk.inherentLikelihood) >= 5
? "#10b981"
: "#94a3b8",
}}
>
<Link
to={`/organizations/${organizationId}/risks/${risk.id}`}
className="hover:underline hover:text-primary transition-colors flex items-center w-full py-1 rounded hover:bg-gray-100 px-1"
>
<span className="truncate">{risk.name}</span>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="w-3.5 h-3.5 ml-auto flex-shrink-0 text-muted-foreground"
>
<polyline points="9 18 15 12 9 6" />
</svg>
</Link>
</li>
))}
</ul>
</>
)}
</PopoverContent> </PopoverContent>
</Popover> </Popover>
); );
@@ -387,18 +464,19 @@ function RiskMatrix({
</div> </div>
<table className="w-full border-collapse table-fixed"> <table className="w-full border-collapse table-fixed">
<tbody> <tbody>
{impactRanges.map((impactRange, rowIndex) => ( {impactValues.map((impactValue, rowIndex) => (
<tr key={rowIndex}> <tr key={rowIndex}>
<th className="p-1 text-xs text-center border-0 font-medium w-14 h-14"> <th className="p-1 text-xs text-right border-0 font-medium w-14 h-14">
{impactLabels[rowIndex]} {impactLabels[rowIndex]} ({impactValue})
</th> </th>
{likelihoodRanges.map((likelihoodRange, colIndex) => ( {likelihoodValues.map((likelihoodValue, colIndex) => (
<RiskCell <RiskCell
key={colIndex} key={colIndex}
rowIndex={rowIndex} rowIndex={rowIndex}
colIndex={colIndex} colIndex={colIndex}
impactRange={impactRange} impactValue={impactValue}
likelihoodRange={likelihoodRange} likelihoodValue={likelihoodValue}
organizationId={organizationId}
/> />
))} ))}
</tr> </tr>
@@ -412,7 +490,7 @@ function RiskMatrix({
key={index} key={index}
className="p-1 text-xs text-center border-t font-medium w-14" className="p-1 text-xs text-center border-t font-medium w-14"
> >
{label} {label} ({likelihoodValues[index]})
</th> </th>
))} ))}
</tr> </tr>
@@ -583,7 +661,11 @@ function ListRiskViewContent({
</Label> </Label>
</div> </div>
</div> </div>
<RiskMatrix risks={risks} isResidual={showResidualRisk} /> <RiskMatrix
risks={risks}
isResidual={showResidualRisk}
organizationId={organizationId || ""}
/>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
@@ -648,9 +730,36 @@ function ListRiskViewContent({
to={`/organizations/${organizationId}/risks/${risk.id}`} to={`/organizations/${organizationId}/risks/${risk.id}`}
className="block p-4 h-full w-full" className="block p-4 h-full w-full"
> >
{riskScoreToSeverity( <span
risk.inherentLikelihood * risk.inherentImpact className="px-2 py-0.5 text-xs rounded-full font-medium inline-block"
)} style={{
backgroundColor:
risk.inherentLikelihood *
risk.inherentImpact >=
20
? "#ef4444"
: risk.inherentLikelihood *
risk.inherentImpact >=
12
? "#f59e0b"
: risk.inherentLikelihood *
risk.inherentImpact >=
5
? "#10b981"
: "#94a3b8",
color:
risk.inherentLikelihood *
risk.inherentImpact >=
12
? "white"
: "inherit",
}}
>
{riskScoreToSeverity(
risk.inherentLikelihood * risk.inherentImpact
)}{" "}
({risk.inherentLikelihood * risk.inherentImpact})
</span>
</Link> </Link>
</td> </td>
<td className="p-0 align-middle w-1/6 whitespace-nowrap"> <td className="p-0 align-middle w-1/6 whitespace-nowrap">
@@ -658,11 +767,41 @@ function ListRiskViewContent({
to={`/organizations/${organizationId}/risks/${risk.id}`} to={`/organizations/${organizationId}/risks/${risk.id}`}
className="block p-4 h-full w-full" className="block p-4 h-full w-full"
> >
{risk.residualLikelihood && risk.residualImpact {risk.residualLikelihood && risk.residualImpact ? (
? riskScoreToSeverity( <span
className="px-2 py-0.5 text-xs rounded-full font-medium inline-block"
style={{
backgroundColor:
risk.residualLikelihood *
risk.residualImpact >=
20
? "#ef4444"
: risk.residualLikelihood *
risk.residualImpact >=
12
? "#f59e0b"
: risk.residualLikelihood *
risk.residualImpact >=
5
? "#10b981"
: "#94a3b8",
color:
risk.residualLikelihood *
risk.residualImpact >=
12
? "white"
: "inherit",
}}
>
{riskScoreToSeverity(
risk.residualLikelihood * risk.residualImpact risk.residualLikelihood * risk.residualImpact
)}{" "}
({risk.residualLikelihood * risk.residualImpact}
) )
: "Not set"} </span>
) : (
"Not set"
)}
</Link> </Link>
</td> </td>
<td className="p-4 align-middle w-[120px]"> <td className="p-4 align-middle w-[120px]">

View File

@@ -87,12 +87,10 @@ function NewRiskForm({
const data = usePreloadedQuery<NewRiskViewQuery>(newRiskQuery, queryRef); const data = usePreloadedQuery<NewRiskViewQuery>(newRiskQuery, queryRef);
const [name, setName] = useState(""); const [name, setName] = useState("");
const [description, setDescription] = useState(""); const [description, setDescription] = useState("");
const [inherentLikelihood, setinherentLikelihood] = const [inherentLikelihood, setInherentLikelihood] = useState<number>(3);
useState<string>("OCCASIONAL"); const [inherentImpact, setInherentImpact] = useState<number>(3);
const [inherentImpact, setinherentImpact] = useState<string>("MODERATE"); const [residualLikelihood, setResidualLikelihood] = useState<number>(3);
const [residualLikelihood, setResidualLikelihood] = const [residualImpact, setResidualImpact] = useState<number>(3);
useState<string>("OCCASIONAL");
const [residualImpact, setResidualImpact] = useState<string>("MODERATE");
const [treatment, setTreatment] = useState<string>("MITIGATED"); const [treatment, setTreatment] = useState<string>("MITIGATED");
const [ownerId, setOwnerId] = useState<string | null>(null); const [ownerId, setOwnerId] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
@@ -121,41 +119,6 @@ function NewRiskForm({
loadRiskTemplates(); loadRiskTemplates();
}, [toast]); }, [toast]);
// Map string values to float values
const likelihoodToFloat = (value: string): number => {
switch (value) {
case "IMPROBABLE":
return 0.1;
case "REMOTE":
return 0.3;
case "OCCASIONAL":
return 0.5;
case "PROBABLE":
return 0.7;
case "FREQUENT":
return 0.9;
default:
return 0.5;
}
};
const impactToFloat = (value: string): number => {
switch (value) {
case "NEGLIGIBLE":
return 0.1;
case "LOW":
return 0.3;
case "MODERATE":
return 0.5;
case "SIGNIFICANT":
return 0.7;
case "CATASTROPHIC":
return 0.9;
default:
return 0.5;
}
};
// Handle template selection and prefill form // Handle template selection and prefill form
const handleTemplateChange = (templateId: string) => { const handleTemplateChange = (templateId: string) => {
setSelectedTemplate(templateId); setSelectedTemplate(templateId);
@@ -164,10 +127,10 @@ function NewRiskForm({
// Clear form if "Select a template" is chosen // Clear form if "Select a template" is chosen
setName(""); setName("");
setDescription(""); setDescription("");
setinherentLikelihood("OCCASIONAL"); setInherentLikelihood(3);
setinherentImpact("MODERATE"); setInherentImpact(3);
setResidualLikelihood("OCCASIONAL"); setResidualLikelihood(3);
setResidualImpact("MODERATE"); setResidualImpact(3);
setTreatment("MITIGATED"); setTreatment("MITIGATED");
return; return;
} }
@@ -176,16 +139,19 @@ function NewRiskForm({
if (template) { if (template) {
setName(template.name); setName(template.name);
setDescription(template.description); setDescription(template.description);
// Convert numeric values to string values for the select components
const likelihoodValue = floatTolikelihood( // Convert template values to 1-5 scale
template.variations[0].likelihood const likelihoodValue =
); Math.round(template.variations[0].likelihood * 5) || 3;
const impactValue = floatToImpact(template.variations[0].impact); const impactValue = Math.round(template.variations[0].impact * 5) || 3;
setinherentLikelihood(likelihoodValue);
setinherentImpact(impactValue); // Ensure values are in 1-5 range
setInherentLikelihood(Math.min(Math.max(likelihoodValue, 1), 5));
setInherentImpact(Math.min(Math.max(impactValue, 1), 5));
// Set residual values to be the same as initial values by default // Set residual values to be the same as initial values by default
setResidualLikelihood(likelihoodValue); setResidualLikelihood(Math.min(Math.max(likelihoodValue, 1), 5));
setResidualImpact(impactValue); setResidualImpact(Math.min(Math.max(impactValue, 1), 5));
// Set recommended treatment if available // Set recommended treatment if available
if (template.variations[0].recommendedTreatment) { if (template.variations[0].recommendedTreatment) {
@@ -194,24 +160,6 @@ function NewRiskForm({
} }
}; };
// Helper function to convert float likelihood to string
const floatTolikelihood = (value: number): string => {
if (value <= 0.2) return "IMPROBABLE";
if (value <= 0.4) return "REMOTE";
if (value <= 0.6) return "OCCASIONAL";
if (value <= 0.8) return "PROBABLE";
return "FREQUENT";
};
// Helper function to convert float impact to string
const floatToImpact = (value: number): string => {
if (value <= 0.2) return "NEGLIGIBLE";
if (value <= 0.4) return "LOW";
if (value <= 0.6) return "MODERATE";
if (value <= 0.8) return "SIGNIFICANT";
return "CATASTROPHIC";
};
const handleSubmit = (e: React.FormEvent) => { const handleSubmit = (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
@@ -230,10 +178,10 @@ function NewRiskForm({
organizationId: organizationId!, organizationId: organizationId!,
name, name,
description, description,
inherentLikelihood: likelihoodToFloat(inherentLikelihood), inherentLikelihood,
inherentImpact: impactToFloat(inherentImpact), inherentImpact,
residualLikelihood: likelihoodToFloat(residualLikelihood), residualLikelihood,
residualImpact: impactToFloat(residualImpact), residualImpact,
treatment, treatment,
ownerId: ownerId || undefined, ownerId: ownerId || undefined,
}; };
@@ -355,39 +303,43 @@ function NewRiskForm({
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="inherentLikelihood">Initial Likelihood</Label> <Label htmlFor="inherentLikelihood">
Initial Likelihood (1-5)
</Label>
<Select <Select
value={inherentLikelihood} value={inherentLikelihood.toString()}
onValueChange={setinherentLikelihood} onValueChange={(value) =>
setInherentLikelihood(parseInt(value))
}
> >
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
<SelectValue placeholder="Select likelihood" /> <SelectValue placeholder="Select likelihood level" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="IMPROBABLE">Improbable</SelectItem> <SelectItem value="1">1 - Improbable</SelectItem>
<SelectItem value="REMOTE">Remote</SelectItem> <SelectItem value="2">2 - Remote</SelectItem>
<SelectItem value="OCCASIONAL">Occasional</SelectItem> <SelectItem value="3">3 - Occasional</SelectItem>
<SelectItem value="PROBABLE">Probable</SelectItem> <SelectItem value="4">4 - Probable</SelectItem>
<SelectItem value="FREQUENT">Frequent</SelectItem> <SelectItem value="5">5 - Frequent</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="inherentImpact">Initial Impact</Label> <Label htmlFor="inherentImpact">Initial Impact (1-5)</Label>
<Select <Select
value={inherentImpact} value={inherentImpact.toString()}
onValueChange={setinherentImpact} onValueChange={(value) => setInherentImpact(parseInt(value))}
> >
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
<SelectValue placeholder="Select impact" /> <SelectValue placeholder="Select impact level" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="NEGLIGIBLE">Negligible</SelectItem> <SelectItem value="1">1 - Negligible</SelectItem>
<SelectItem value="LOW">Low</SelectItem> <SelectItem value="2">2 - Low</SelectItem>
<SelectItem value="MODERATE">Moderate</SelectItem> <SelectItem value="3">3 - Moderate</SelectItem>
<SelectItem value="SIGNIFICANT">Significant</SelectItem> <SelectItem value="4">4 - Significant</SelectItem>
<SelectItem value="CATASTROPHIC">Catastrophic</SelectItem> <SelectItem value="5">5 - Catastrophic</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
@@ -422,40 +374,44 @@ function NewRiskForm({
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="residualLikelihood"> <Label htmlFor="residualLikelihood">
Residual Likelihood Residual Likelihood (1-5)
</Label> </Label>
<Select <Select
value={residualLikelihood} value={residualLikelihood.toString()}
onValueChange={setResidualLikelihood} onValueChange={(value) =>
setResidualLikelihood(parseInt(value))
}
> >
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
<SelectValue placeholder="Select likelihood" /> <SelectValue placeholder="Select likelihood level" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="IMPROBABLE">Improbable</SelectItem> <SelectItem value="1">1 - Improbable</SelectItem>
<SelectItem value="REMOTE">Remote</SelectItem> <SelectItem value="2">2 - Remote</SelectItem>
<SelectItem value="OCCASIONAL">Occasional</SelectItem> <SelectItem value="3">3 - Occasional</SelectItem>
<SelectItem value="PROBABLE">Probable</SelectItem> <SelectItem value="4">4 - Probable</SelectItem>
<SelectItem value="FREQUENT">Frequent</SelectItem> <SelectItem value="5">5 - Frequent</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="residualImpact">Residual Impact</Label> <Label htmlFor="residualImpact">Residual Impact (1-5)</Label>
<Select <Select
value={residualImpact} value={residualImpact.toString()}
onValueChange={setResidualImpact} onValueChange={(value) =>
setResidualImpact(parseInt(value))
}
> >
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
<SelectValue placeholder="Select impact" /> <SelectValue placeholder="Select impact level" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="NEGLIGIBLE">Negligible</SelectItem> <SelectItem value="1">1 - Negligible</SelectItem>
<SelectItem value="LOW">Low</SelectItem> <SelectItem value="2">2 - Low</SelectItem>
<SelectItem value="MODERATE">Moderate</SelectItem> <SelectItem value="3">3 - Moderate</SelectItem>
<SelectItem value="SIGNIFICANT">Significant</SelectItem> <SelectItem value="4">4 - Significant</SelectItem>
<SelectItem value="CATASTROPHIC">Catastrophic</SelectItem> <SelectItem value="5">5 - Catastrophic</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>

View File

@@ -210,30 +210,102 @@ const deleteRiskPolicyMappingMutation = graphql`
function getRiskSeverity(likelihood: number, impact: number) { function getRiskSeverity(likelihood: number, impact: number) {
const score = likelihood * impact; const score = likelihood * impact;
if (score >= 0.75) if (score >= 20)
return { level: "Catastrophic", class: "bg-red-100 text-red-800" }; return {
if (score >= 0.5) level: "Catastrophic",
return { level: "Critical", class: "bg-orange-100 text-orange-800" }; score: score,
if (score >= 0.25) style: {
return { level: "Marginal", class: "bg-yellow-100 text-yellow-800" }; backgroundColor: "#ef4444",
return { level: "Negligible", class: "bg-green-100 text-green-800" }; color: "white",
},
};
if (score >= 12)
return {
level: "Critical",
score: score,
style: {
backgroundColor: "#f59e0b",
color: "white",
},
};
if (score >= 5)
return {
level: "Marginal",
score: score,
style: {
backgroundColor: "#10b981",
color: "inherit",
},
};
return {
level: "Negligible",
score: score,
style: {
backgroundColor: "#94a3b8",
color: "inherit",
},
};
} }
// Add helper functions to convert numerical values to labels // Add helper functions to convert numerical values to labels
function getLikelihoodLabel(likelihood: number): string { function getLikelihoodLabel(likelihood: number): {
if (likelihood >= 0.8) return "Frequent"; label: string;
if (likelihood >= 0.6) return "Probable"; style: React.CSSProperties;
if (likelihood >= 0.4) return "Occasional"; } {
if (likelihood >= 0.2) return "Remote"; if (likelihood === 5)
return "Improbable"; return {
label: "Frequent (5)",
style: { backgroundColor: "#ef4444", color: "white" },
};
if (likelihood === 4)
return {
label: "Probable (4)",
style: { backgroundColor: "#f59e0b", color: "white" },
};
if (likelihood === 3)
return {
label: "Occasional (3)",
style: { backgroundColor: "#eab308", color: "black" },
};
if (likelihood === 2)
return {
label: "Remote (2)",
style: { backgroundColor: "#10b981", color: "black" },
};
return {
label: "Improbable (1)",
style: { backgroundColor: "#94a3b8", color: "black" },
};
} }
function getImpactLabel(impact: number): string { function getImpactLabel(impact: number): {
if (impact >= 0.8) return "Catastrophic"; label: string;
if (impact >= 0.6) return "Significant"; style: React.CSSProperties;
if (impact >= 0.4) return "Moderate"; } {
if (impact >= 0.2) return "Low"; if (impact === 5)
return "Negligible"; return {
label: "Catastrophic (5)",
style: { backgroundColor: "#ef4444", color: "white" },
};
if (impact === 4)
return {
label: "Significant (4)",
style: { backgroundColor: "#f59e0b", color: "white" },
};
if (impact === 3)
return {
label: "Moderate (3)",
style: { backgroundColor: "#eab308", color: "black" },
};
if (impact === 2)
return {
label: "Low (2)",
style: { backgroundColor: "#10b981", color: "black" },
};
return {
label: "Negligible (1)",
style: { backgroundColor: "#94a3b8", color: "black" },
};
} }
function ShowRiskViewContent({ function ShowRiskViewContent({
@@ -765,65 +837,43 @@ function ShowRiskViewContent({
> >
<div className="space-y-6"> <div className="space-y-6">
<Card> <Card>
<CardHeader> <CardHeader className="pb-2">
<CardTitle>Risk Overview</CardTitle> <CardTitle>Risk Overview</CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4"> <div className="space-y-8">
<div> {/* Risk Management Overview */}
<h3 className="text-sm font-medium text-secondary"> <div className="flex flex-col md:flex-row gap-5">
Likelihood <div className="flex items-center">
</h3> {risk.treatment === "MITIGATED" && (
<p className="mt-1 text-lg"> <Shield className="h-6 w-6 mr-3 text-blue-500" />
{getLikelihoodLabel(risk.inherentLikelihood!)} )}
</p> {risk.treatment === "TRANSFERRED" && (
</div> <Handshake className="h-6 w-6 mr-3 text-purple-500" />
<div> )}
<h3 className="text-sm font-medium text-secondary">Impact</h3> {risk.treatment === "AVOIDED" && (
<p className="mt-1 text-lg"> <Ban className="h-6 w-6 mr-3 text-red-500" />
{getImpactLabel(risk.inherentImpact!)} )}
</p> {risk.treatment === "ACCEPTED" && (
</div> <ShieldCheck className="h-6 w-6 mr-3 text-green-500" />
<div> )}
<h3 className="text-sm font-medium text-secondary">Severity</h3> <div>
<p className="mt-1"> <p className="text-xs text-slate-500 uppercase">
<Badge className={severity.class}>{severity.level}</Badge> Treatment
</p> </p>
</div> <p className="text-base font-medium capitalize mt-1">
</div>
{/* Treatment and Owner section */}
<div className="mt-6 border-t pt-4">
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
<div>
<h3 className="text-sm font-medium text-secondary">
Treatment
</h3>
<div className="mt-1 text-lg capitalize flex items-center">
{risk.treatment === "MITIGATED" && (
<Shield className="h-5 w-5 mr-2 text-blue-500" />
)}
{risk.treatment === "TRANSFERRED" && (
<Handshake className="h-5 w-5 mr-2 text-purple-500" />
)}
{risk.treatment === "AVOIDED" && (
<Ban className="h-5 w-5 mr-2 text-red-500" />
)}
{risk.treatment === "ACCEPTED" && (
<ShieldCheck className="h-5 w-5 mr-2 text-green-500" />
)}
<span>
{risk.treatment ? risk.treatment.toLowerCase() : "N/A"} {risk.treatment ? risk.treatment.toLowerCase() : "N/A"}
</span> </p>
</div> </div>
</div> </div>
<div>
<h3 className="text-sm font-medium text-secondary"> <div className="flex items-center">
Risk Owner <User className="h-6 w-6 mr-3 text-slate-500" />
</h3> <div>
<div className="mt-1 text-lg flex items-center"> <p className="text-xs text-slate-500 uppercase">
<User className="h-5 w-5 mr-2 text-gray-500" /> Risk Owner
<span> </p>
<p className="text-base font-medium mt-1 truncate">
{risk.owner ? ( {risk.owner ? (
<Link <Link
to={`/organizations/${organizationId}/people/${risk.owner.id}`} to={`/organizations/${organizationId}/people/${risk.owner.id}`}
@@ -834,57 +884,127 @@ function ShowRiskViewContent({
) : ( ) : (
"Unassigned" "Unassigned"
)} )}
</span> </p>
</div> </div>
</div> </div>
<div></div>
</div> </div>
</div>
{/* Residual risk section */} {/* Risk Assessment */}
<div className="mt-6 border-t pt-4"> <div>
<h3 className="text-md font-semibold mb-3">Residual Risk</h3> <h3 className="text-sm font-medium mb-6">Risk Assessment</h3>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
<div> <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<h3 className="text-sm font-medium text-secondary"> {/* Initial Assessment */}
Residual Likelihood <div>
</h3> <h4 className="text-sm font-medium mb-5 flex items-center">
<p className="mt-1 text-lg"> <span className="w-3 h-3 rounded-full bg-orange-500 mr-2"></span>
{getLikelihoodLabel(risk.residualLikelihood!)} Initial Assessment
</p> </h4>
<p className="text-xs text-secondary mt-1"> <div className="grid grid-cols-3 gap-6">
{risk.residualLikelihood !== risk.inherentLikelihood <div>
? `Mitigated` <p className="text-sm text-slate-600 mb-2">
: "No mitigation"} Likelihood
</p> </p>
</div> <div
<div> className="px-5 py-2 rounded-md font-medium text-sm flex justify-center items-center whitespace-nowrap"
<h3 className="text-sm font-medium text-secondary"> style={
Residual Impact getLikelihoodLabel(risk.inherentLikelihood!).style
</h3> }
<p className="mt-1 text-lg"> >
{getImpactLabel(risk.residualImpact!)} {getLikelihoodLabel(risk.inherentLikelihood!).label}
</p> </div>
<p className="text-xs text-secondary mt-1"> </div>
{risk.residualImpact !== risk.inherentImpact <div>
? `Mitigated` <p className="text-sm text-slate-600 mb-2">Impact</p>
: "No mitigation"} <div
</p> className="px-5 py-2 rounded-md font-medium text-sm flex justify-center items-center whitespace-nowrap"
</div> style={getImpactLabel(risk.inherentImpact!).style}
<div> >
<h3 className="text-sm font-medium text-secondary"> {getImpactLabel(risk.inherentImpact!).label}
Residual Severity </div>
</h3> </div>
<p className="mt-1"> <div>
<Badge className={residualSeverity.class}> <p className="text-sm text-slate-600 mb-2">Severity</p>
{residualSeverity.level} <div
</Badge> className="px-5 py-2 rounded-md font-medium text-sm flex justify-center items-center whitespace-nowrap"
</p> style={severity.style}
<p className="text-xs text-secondary mt-1"> >
{mesures.length} mesure {severity.level} (
{mesures.length !== 1 ? "s" : ""} applied {risk.inherentLikelihood! * risk.inherentImpact!})
</p> </div>
</div>
</div>
</div>
{/* Residual Assessment */}
<div>
<h4 className="text-sm font-medium mb-5 flex items-center">
<span className="w-3 h-3 rounded-full bg-green-500 mr-2"></span>
After Treatment
</h4>
<div className="grid grid-cols-3 gap-6">
<div>
<p className="text-sm text-slate-600 mb-2">
Likelihood
</p>
<div
className="px-5 py-2 rounded-md font-medium text-sm flex justify-center items-center whitespace-nowrap"
style={
getLikelihoodLabel(risk.residualLikelihood!).style
}
>
{getLikelihoodLabel(risk.residualLikelihood!).label}
</div>
</div>
<div>
<p className="text-sm text-slate-600 mb-2">Impact</p>
<div
className="px-5 py-2 rounded-md font-medium text-sm flex justify-center items-center whitespace-nowrap"
style={getImpactLabel(risk.residualImpact!).style}
>
{getImpactLabel(risk.residualImpact!).label}
</div>
</div>
<div>
<p className="text-sm text-slate-600 mb-2">Severity</p>
<div
className="px-5 py-2 rounded-md font-medium text-sm flex justify-center items-center whitespace-nowrap"
style={residualSeverity.style}
>
{residualSeverity.level} (
{risk.residualLikelihood! * risk.residualImpact!})
</div>
</div>
</div>
</div>
</div> </div>
{/* Risk Reduction Summary */}
{(risk.inherentLikelihood !== risk.residualLikelihood ||
risk.inherentImpact !== risk.residualImpact) && (
<div className="mt-6 text-center pt-4 border-t">
<p className="text-sm text-slate-600">
Risk severity reduced from{" "}
<span className="font-semibold">
{severity.level} (
{risk.inherentLikelihood! * risk.inherentImpact!})
</span>{" "}
to{" "}
<span className="font-semibold">
{residualSeverity.level} (
{risk.residualLikelihood! * risk.residualImpact!})
</span>
{risk.treatment === "MITIGATED" &&
risk.mesures?.edges &&
risk.mesures.edges.length > 0 &&
` with ${risk.mesures.edges.length} ${
risk.mesures.edges.length === 1
? "measure"
: "measures"
}`}
</p>
</div>
)}
</div> </div>
</div> </div>
</CardContent> </CardContent>
@@ -910,7 +1030,7 @@ function ShowRiskViewContent({
Link Mesure Link Mesure
</Button> </Button>
</div> </div>
{risk.mesures?.edges?.length && risk.mesures?.edges?.length > 0 ? ( {risk.mesures?.edges && risk.mesures.edges.length > 0 ? (
<div className="rounded-md border"> <div className="rounded-md border">
<Table> <Table>
<TableHeader> <TableHeader>

View File

@@ -0,0 +1,44 @@
ALTER TABLE risks ADD COLUMN inherent_likelihood_int INTEGER;
ALTER TABLE risks ADD COLUMN inherent_impact_int INTEGER;
ALTER TABLE risks ADD COLUMN residual_likelihood_int INTEGER;
ALTER TABLE risks ADD COLUMN residual_impact_int INTEGER;
UPDATE risks SET
inherent_likelihood_int = CASE
WHEN inherent_likelihood <= 0.20 THEN 1
WHEN inherent_likelihood <= 0.40 THEN 2
WHEN inherent_likelihood <= 0.60 THEN 3
WHEN inherent_likelihood <= 0.80 THEN 4
ELSE 5
END,
inherent_impact_int = CASE
WHEN inherent_impact <= 0.20 THEN 1
WHEN inherent_impact <= 0.40 THEN 2
WHEN inherent_impact <= 0.60 THEN 3
WHEN inherent_impact <= 0.80 THEN 4
ELSE 5
END,
residual_likelihood_int = CASE
WHEN residual_likelihood <= 0.20 THEN 1
WHEN residual_likelihood <= 0.40 THEN 2
WHEN residual_likelihood <= 0.60 THEN 3
WHEN residual_likelihood <= 0.80 THEN 4
ELSE 5
END,
residual_impact_int = CASE
WHEN residual_impact <= 0.20 THEN 1
WHEN residual_impact <= 0.40 THEN 2
WHEN residual_impact <= 0.60 THEN 3
WHEN residual_impact <= 0.80 THEN 4
ELSE 5
END;
ALTER TABLE risks DROP COLUMN inherent_likelihood;
ALTER TABLE risks DROP COLUMN inherent_impact;
ALTER TABLE risks DROP COLUMN residual_likelihood;
ALTER TABLE risks DROP COLUMN residual_impact;
ALTER TABLE risks RENAME COLUMN inherent_likelihood_int TO inherent_likelihood;
ALTER TABLE risks RENAME COLUMN inherent_impact_int TO inherent_impact;
ALTER TABLE risks RENAME COLUMN residual_likelihood_int TO residual_likelihood;
ALTER TABLE risks RENAME COLUMN residual_impact_int TO residual_impact;

View File

@@ -34,10 +34,10 @@ type (
Description string `db:"description"` Description string `db:"description"`
Treatment RiskTreatment `db:"treatment"` Treatment RiskTreatment `db:"treatment"`
OwnerID *gid.GID `db:"owner_id"` OwnerID *gid.GID `db:"owner_id"`
InherentLikelihood float64 `db:"inherent_likelihood"` InherentLikelihood int `db:"inherent_likelihood"`
InherentImpact float64 `db:"inherent_impact"` InherentImpact int `db:"inherent_impact"`
ResidualLikelihood float64 `db:"residual_likelihood"` ResidualLikelihood int `db:"residual_likelihood"`
ResidualImpact float64 `db:"residual_impact"` ResidualImpact int `db:"residual_impact"`
CreatedAt time.Time `db:"created_at"` CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"` UpdatedAt time.Time `db:"updated_at"`
} }
@@ -54,11 +54,11 @@ func (r *Risk) CursorKey(orderBy RiskOrderField) page.CursorKey {
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
func (r *Risk) InherentSeverity() float64 { func (r *Risk) InherentSeverity() int {
return r.InherentLikelihood * r.InherentImpact return r.InherentLikelihood * r.InherentImpact
} }
func (r *Risk) ResidualSeverity() float64 { func (r *Risk) ResidualSeverity() int {
return r.ResidualLikelihood * r.ResidualImpact return r.ResidualLikelihood * r.ResidualImpact
} }

View File

@@ -36,10 +36,10 @@ type (
Description string Description string
Treatment coredata.RiskTreatment Treatment coredata.RiskTreatment
OwnerID *gid.GID OwnerID *gid.GID
InherentLikelihood float64 InherentLikelihood int
InherentImpact float64 InherentImpact int
ResidualLikelihood *float64 ResidualLikelihood *int
ResidualImpact *float64 ResidualImpact *int
} }
UpdateRiskRequest struct { UpdateRiskRequest struct {
@@ -48,10 +48,10 @@ type (
Description *string Description *string
Treatment *coredata.RiskTreatment Treatment *coredata.RiskTreatment
OwnerID *gid.GID OwnerID *gid.GID
InherentLikelihood *float64 InherentLikelihood *int
InherentImpact *float64 InherentImpact *int
ResidualLikelihood *float64 ResidualLikelihood *int
ResidualImpact *float64 ResidualImpact *int
} }
) )

View File

@@ -628,12 +628,12 @@ type Risk implements Node {
name: String! name: String!
description: String! description: String!
treatment: RiskTreatment! treatment: RiskTreatment!
inherentLikelihood: Float! inherentLikelihood: Int!
inherentImpact: Float! inherentImpact: Int!
inherentSeverity: Float! inherentSeverity: Int!
residualLikelihood: Float! residualLikelihood: Int!
residualImpact: Float! residualImpact: Int!
residualSeverity: Float! residualSeverity: Int!
owner: People @goField(forceResolver: true) owner: People @goField(forceResolver: true)
@@ -1092,10 +1092,10 @@ input CreateRiskInput {
description: String! description: String!
ownerId: ID ownerId: ID
treatment: RiskTreatment! treatment: RiskTreatment!
inherentLikelihood: Float! inherentLikelihood: Int!
inherentImpact: Float! inherentImpact: Int!
residualLikelihood: Float residualLikelihood: Int
residualImpact: Float residualImpact: Int
} }
input UpdateRiskInput { input UpdateRiskInput {
@@ -1104,10 +1104,10 @@ input UpdateRiskInput {
description: String description: String
ownerId: ID ownerId: ID
treatment: RiskTreatment treatment: RiskTreatment
inherentLikelihood: Float inherentLikelihood: Int
inherentImpact: Float inherentImpact: Int
residualLikelihood: Float residualLikelihood: Int
residualImpact: Float residualImpact: Int
} }
input DeleteRiskInput { input DeleteRiskInput {

View File

@@ -3739,12 +3739,12 @@ type Risk implements Node {
name: String! name: String!
description: String! description: String!
treatment: RiskTreatment! treatment: RiskTreatment!
inherentLikelihood: Float! inherentLikelihood: Int!
inherentImpact: Float! inherentImpact: Int!
inherentSeverity: Float! inherentSeverity: Int!
residualLikelihood: Float! residualLikelihood: Int!
residualImpact: Float! residualImpact: Int!
residualSeverity: Float! residualSeverity: Int!
owner: People @goField(forceResolver: true) owner: People @goField(forceResolver: true)
@@ -4203,10 +4203,10 @@ input CreateRiskInput {
description: String! description: String!
ownerId: ID ownerId: ID
treatment: RiskTreatment! treatment: RiskTreatment!
inherentLikelihood: Float! inherentLikelihood: Int!
inherentImpact: Float! inherentImpact: Int!
residualLikelihood: Float residualLikelihood: Int
residualImpact: Float residualImpact: Int
} }
input UpdateRiskInput { input UpdateRiskInput {
@@ -4215,10 +4215,10 @@ input UpdateRiskInput {
description: String description: String
ownerId: ID ownerId: ID
treatment: RiskTreatment treatment: RiskTreatment
inherentLikelihood: Float inherentLikelihood: Int
inherentImpact: Float inherentImpact: Int
residualLikelihood: Float residualLikelihood: Int
residualImpact: Float residualImpact: Int
} }
input DeleteRiskInput { input DeleteRiskInput {
@@ -16907,9 +16907,9 @@ func (ec *executionContext) _Risk_inherentLikelihood(ctx context.Context, field
} }
return graphql.Null return graphql.Null
} }
res := resTmp.(float64) res := resTmp.(int)
fc.Result = res fc.Result = res
return ec.marshalNFloat2float64(ctx, field.Selections, res) return ec.marshalNInt2int(ctx, field.Selections, res)
} }
func (ec *executionContext) fieldContext_Risk_inherentLikelihood(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { func (ec *executionContext) fieldContext_Risk_inherentLikelihood(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
@@ -16919,7 +16919,7 @@ func (ec *executionContext) fieldContext_Risk_inherentLikelihood(_ context.Conte
IsMethod: false, IsMethod: false,
IsResolver: false, IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Float does not have child fields") return nil, errors.New("field of type Int does not have child fields")
}, },
} }
return fc, nil return fc, nil
@@ -16951,9 +16951,9 @@ func (ec *executionContext) _Risk_inherentImpact(ctx context.Context, field grap
} }
return graphql.Null return graphql.Null
} }
res := resTmp.(float64) res := resTmp.(int)
fc.Result = res fc.Result = res
return ec.marshalNFloat2float64(ctx, field.Selections, res) return ec.marshalNInt2int(ctx, field.Selections, res)
} }
func (ec *executionContext) fieldContext_Risk_inherentImpact(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { func (ec *executionContext) fieldContext_Risk_inherentImpact(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
@@ -16963,7 +16963,7 @@ func (ec *executionContext) fieldContext_Risk_inherentImpact(_ context.Context,
IsMethod: false, IsMethod: false,
IsResolver: false, IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Float does not have child fields") return nil, errors.New("field of type Int does not have child fields")
}, },
} }
return fc, nil return fc, nil
@@ -16995,9 +16995,9 @@ func (ec *executionContext) _Risk_inherentSeverity(ctx context.Context, field gr
} }
return graphql.Null return graphql.Null
} }
res := resTmp.(float64) res := resTmp.(int)
fc.Result = res fc.Result = res
return ec.marshalNFloat2float64(ctx, field.Selections, res) return ec.marshalNInt2int(ctx, field.Selections, res)
} }
func (ec *executionContext) fieldContext_Risk_inherentSeverity(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { func (ec *executionContext) fieldContext_Risk_inherentSeverity(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
@@ -17007,7 +17007,7 @@ func (ec *executionContext) fieldContext_Risk_inherentSeverity(_ context.Context
IsMethod: false, IsMethod: false,
IsResolver: false, IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Float does not have child fields") return nil, errors.New("field of type Int does not have child fields")
}, },
} }
return fc, nil return fc, nil
@@ -17039,9 +17039,9 @@ func (ec *executionContext) _Risk_residualLikelihood(ctx context.Context, field
} }
return graphql.Null return graphql.Null
} }
res := resTmp.(float64) res := resTmp.(int)
fc.Result = res fc.Result = res
return ec.marshalNFloat2float64(ctx, field.Selections, res) return ec.marshalNInt2int(ctx, field.Selections, res)
} }
func (ec *executionContext) fieldContext_Risk_residualLikelihood(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { func (ec *executionContext) fieldContext_Risk_residualLikelihood(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
@@ -17051,7 +17051,7 @@ func (ec *executionContext) fieldContext_Risk_residualLikelihood(_ context.Conte
IsMethod: false, IsMethod: false,
IsResolver: false, IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Float does not have child fields") return nil, errors.New("field of type Int does not have child fields")
}, },
} }
return fc, nil return fc, nil
@@ -17083,9 +17083,9 @@ func (ec *executionContext) _Risk_residualImpact(ctx context.Context, field grap
} }
return graphql.Null return graphql.Null
} }
res := resTmp.(float64) res := resTmp.(int)
fc.Result = res fc.Result = res
return ec.marshalNFloat2float64(ctx, field.Selections, res) return ec.marshalNInt2int(ctx, field.Selections, res)
} }
func (ec *executionContext) fieldContext_Risk_residualImpact(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { func (ec *executionContext) fieldContext_Risk_residualImpact(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
@@ -17095,7 +17095,7 @@ func (ec *executionContext) fieldContext_Risk_residualImpact(_ context.Context,
IsMethod: false, IsMethod: false,
IsResolver: false, IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Float does not have child fields") return nil, errors.New("field of type Int does not have child fields")
}, },
} }
return fc, nil return fc, nil
@@ -17127,9 +17127,9 @@ func (ec *executionContext) _Risk_residualSeverity(ctx context.Context, field gr
} }
return graphql.Null return graphql.Null
} }
res := resTmp.(float64) res := resTmp.(int)
fc.Result = res fc.Result = res
return ec.marshalNFloat2float64(ctx, field.Selections, res) return ec.marshalNInt2int(ctx, field.Selections, res)
} }
func (ec *executionContext) fieldContext_Risk_residualSeverity(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { func (ec *executionContext) fieldContext_Risk_residualSeverity(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
@@ -17139,7 +17139,7 @@ func (ec *executionContext) fieldContext_Risk_residualSeverity(_ context.Context
IsMethod: false, IsMethod: false,
IsResolver: false, IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Float does not have child fields") return nil, errors.New("field of type Int does not have child fields")
}, },
} }
return fc, nil return fc, nil
@@ -24054,28 +24054,28 @@ func (ec *executionContext) unmarshalInputCreateRiskInput(ctx context.Context, o
it.Treatment = data it.Treatment = data
case "inherentLikelihood": case "inherentLikelihood":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("inherentLikelihood")) ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("inherentLikelihood"))
data, err := ec.unmarshalNFloat2float64(ctx, v) data, err := ec.unmarshalNInt2int(ctx, v)
if err != nil { if err != nil {
return it, err return it, err
} }
it.InherentLikelihood = data it.InherentLikelihood = data
case "inherentImpact": case "inherentImpact":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("inherentImpact")) ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("inherentImpact"))
data, err := ec.unmarshalNFloat2float64(ctx, v) data, err := ec.unmarshalNInt2int(ctx, v)
if err != nil { if err != nil {
return it, err return it, err
} }
it.InherentImpact = data it.InherentImpact = data
case "residualLikelihood": case "residualLikelihood":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("residualLikelihood")) ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("residualLikelihood"))
data, err := ec.unmarshalOFloat2ᚖfloat64(ctx, v) data, err := ec.unmarshalOInt2ᚖint(ctx, v)
if err != nil { if err != nil {
return it, err return it, err
} }
it.ResidualLikelihood = data it.ResidualLikelihood = data
case "residualImpact": case "residualImpact":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("residualImpact")) ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("residualImpact"))
data, err := ec.unmarshalOFloat2ᚖfloat64(ctx, v) data, err := ec.unmarshalOInt2ᚖint(ctx, v)
if err != nil { if err != nil {
return it, err return it, err
} }
@@ -25605,28 +25605,28 @@ func (ec *executionContext) unmarshalInputUpdateRiskInput(ctx context.Context, o
it.Treatment = data it.Treatment = data
case "inherentLikelihood": case "inherentLikelihood":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("inherentLikelihood")) ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("inherentLikelihood"))
data, err := ec.unmarshalOFloat2ᚖfloat64(ctx, v) data, err := ec.unmarshalOInt2ᚖint(ctx, v)
if err != nil { if err != nil {
return it, err return it, err
} }
it.InherentLikelihood = data it.InherentLikelihood = data
case "inherentImpact": case "inherentImpact":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("inherentImpact")) ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("inherentImpact"))
data, err := ec.unmarshalOFloat2ᚖfloat64(ctx, v) data, err := ec.unmarshalOInt2ᚖint(ctx, v)
if err != nil { if err != nil {
return it, err return it, err
} }
it.InherentImpact = data it.InherentImpact = data
case "residualLikelihood": case "residualLikelihood":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("residualLikelihood")) ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("residualLikelihood"))
data, err := ec.unmarshalOFloat2ᚖfloat64(ctx, v) data, err := ec.unmarshalOInt2ᚖint(ctx, v)
if err != nil { if err != nil {
return it, err return it, err
} }
it.ResidualLikelihood = data it.ResidualLikelihood = data
case "residualImpact": case "residualImpact":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("residualImpact")) ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("residualImpact"))
data, err := ec.unmarshalOFloat2ᚖfloat64(ctx, v) data, err := ec.unmarshalOInt2ᚖint(ctx, v)
if err != nil { if err != nil {
return it, err return it, err
} }
@@ -32508,21 +32508,6 @@ var (
} }
) )
func (ec *executionContext) unmarshalNFloat2float64(ctx context.Context, v any) (float64, error) {
res, err := graphql.UnmarshalFloatContext(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNFloat2float64(ctx context.Context, sel ast.SelectionSet, v float64) graphql.Marshaler {
res := graphql.MarshalFloatContext(v)
if res == graphql.Null {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow")
}
}
return graphql.WrapContextMarshaler(ctx, res)
}
func (ec *executionContext) marshalNFramework2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐFramework(ctx context.Context, sel ast.SelectionSet, v *types.Framework) graphql.Marshaler { func (ec *executionContext) marshalNFramework2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐFramework(ctx context.Context, sel ast.SelectionSet, v *types.Framework) graphql.Marshaler {
if v == nil { if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
@@ -34555,22 +34540,6 @@ func (ec *executionContext) unmarshalOEvidenceOrder2ᚖgithubᚗcomᚋgetprobo
return &res, graphql.ErrorOnPath(ctx, err) return &res, graphql.ErrorOnPath(ctx, err)
} }
func (ec *executionContext) unmarshalOFloat2ᚖfloat64(ctx context.Context, v any) (*float64, error) {
if v == nil {
return nil, nil
}
res, err := graphql.UnmarshalFloatContext(ctx, v)
return &res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalOFloat2ᚖfloat64(ctx context.Context, sel ast.SelectionSet, v *float64) graphql.Marshaler {
if v == nil {
return graphql.Null
}
res := graphql.MarshalFloatContext(*v)
return graphql.WrapContextMarshaler(ctx, res)
}
func (ec *executionContext) unmarshalOFrameworkOrder2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐFrameworkOrderBy(ctx context.Context, v any) (*types.FrameworkOrderBy, error) { func (ec *executionContext) unmarshalOFrameworkOrder2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐFrameworkOrderBy(ctx context.Context, v any) (*types.FrameworkOrderBy, error) {
if v == nil { if v == nil {
return nil, nil return nil, nil

View File

@@ -152,10 +152,10 @@ type CreateRiskInput struct {
Description string `json:"description"` Description string `json:"description"`
OwnerID *gid.GID `json:"ownerId,omitempty"` OwnerID *gid.GID `json:"ownerId,omitempty"`
Treatment coredata.RiskTreatment `json:"treatment"` Treatment coredata.RiskTreatment `json:"treatment"`
InherentLikelihood float64 `json:"inherentLikelihood"` InherentLikelihood int `json:"inherentLikelihood"`
InherentImpact float64 `json:"inherentImpact"` InherentImpact int `json:"inherentImpact"`
ResidualLikelihood *float64 `json:"residualLikelihood,omitempty"` ResidualLikelihood *int `json:"residualLikelihood,omitempty"`
ResidualImpact *float64 `json:"residualImpact,omitempty"` ResidualImpact *int `json:"residualImpact,omitempty"`
} }
type CreateRiskMesureMappingInput struct { type CreateRiskMesureMappingInput struct {
@@ -562,12 +562,12 @@ type Risk struct {
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
Treatment coredata.RiskTreatment `json:"treatment"` Treatment coredata.RiskTreatment `json:"treatment"`
InherentLikelihood float64 `json:"inherentLikelihood"` InherentLikelihood int `json:"inherentLikelihood"`
InherentImpact float64 `json:"inherentImpact"` InherentImpact int `json:"inherentImpact"`
InherentSeverity float64 `json:"inherentSeverity"` InherentSeverity int `json:"inherentSeverity"`
ResidualLikelihood float64 `json:"residualLikelihood"` ResidualLikelihood int `json:"residualLikelihood"`
ResidualImpact float64 `json:"residualImpact"` ResidualImpact int `json:"residualImpact"`
ResidualSeverity float64 `json:"residualSeverity"` ResidualSeverity int `json:"residualSeverity"`
Owner *People `json:"owner,omitempty"` Owner *People `json:"owner,omitempty"`
Mesures *MesureConnection `json:"mesures"` Mesures *MesureConnection `json:"mesures"`
Policies *PolicyConnection `json:"policies"` Policies *PolicyConnection `json:"policies"`
@@ -691,10 +691,10 @@ type UpdateRiskInput struct {
Description *string `json:"description,omitempty"` Description *string `json:"description,omitempty"`
OwnerID *gid.GID `json:"ownerId,omitempty"` OwnerID *gid.GID `json:"ownerId,omitempty"`
Treatment *coredata.RiskTreatment `json:"treatment,omitempty"` Treatment *coredata.RiskTreatment `json:"treatment,omitempty"`
InherentLikelihood *float64 `json:"inherentLikelihood,omitempty"` InherentLikelihood *int `json:"inherentLikelihood,omitempty"`
InherentImpact *float64 `json:"inherentImpact,omitempty"` InherentImpact *int `json:"inherentImpact,omitempty"`
ResidualLikelihood *float64 `json:"residualLikelihood,omitempty"` ResidualLikelihood *int `json:"residualLikelihood,omitempty"`
ResidualImpact *float64 `json:"residualImpact,omitempty"` ResidualImpact *int `json:"residualImpact,omitempty"`
} }
type UpdateRiskPayload struct { type UpdateRiskPayload struct {