diff --git a/src/components/editor/faultTree/menu/faultEvent/FaultEventMenu.styles.tsx b/src/components/editor/faultTree/menu/faultEvent/FaultEventMenu.styles.tsx index c55d5293..6226984d 100644 --- a/src/components/editor/faultTree/menu/faultEvent/FaultEventMenu.styles.tsx +++ b/src/components/editor/faultTree/menu/faultEvent/FaultEventMenu.styles.tsx @@ -15,6 +15,12 @@ const useStyles = makeStyles()((theme: Theme) => ({ marginTop: 8, marginBottom: 8, }, + numberInput: { + "& .MuiInputBase-input": { + color: "black", + padding: "8px 12px", + }, + }, })); export default useStyles; diff --git a/src/components/editor/faultTree/menu/faultEvent/FaultEventMenu.tsx b/src/components/editor/faultTree/menu/faultEvent/FaultEventMenu.tsx index 9efde798..bfc9acae 100644 --- a/src/components/editor/faultTree/menu/faultEvent/FaultEventMenu.tsx +++ b/src/components/editor/faultTree/menu/faultEvent/FaultEventMenu.tsx @@ -13,6 +13,7 @@ import { useTranslation } from "react-i18next"; import { asArray } from "@utils/utils"; import { ReusableFaultEventsProvider } from "@hooks/useReusableFaultEvents"; import { useSelectedSystem } from "@hooks/useSelectedSystem"; +import { Radio, RadioGroup, FormControlLabel, FormControl, TextField } from "@mui/material"; interface Props { shapeToolData?: FaultEvent; @@ -21,6 +22,12 @@ interface Props { rootIri?: string; } +enum RadioButtonType { + Predicted = "Predicted", + Manual = "Manual", + Operational = "Operational", +} + const FaultEventMenu = ({ shapeToolData, onEventUpdated, refreshTree, rootIri }: Props) => { const { t } = useTranslation(); const { classes } = useStyles(); @@ -38,12 +45,48 @@ const FaultEventMenu = ({ shapeToolData, onEventUpdated, refreshTree, rootIri }: const [schematicDesignation, setSchematicDesignation] = useState<string | undefined>(undefined); const [selectedSystem] = useSelectedSystem(); + const [snsPredictedFailureRate, setSnsPredictedFailureRate] = useState<number | undefined>(undefined); + const [snsManuallyDefinedFailureRate, setSnsManuallyDefinedFailureRate] = useState<number | undefined>(undefined); + const [snsOperationalFailureRate, setSnsOperationalFailureRate] = useState<number | undefined>(undefined); + const [selectedRadioButton, setSelectedRadioButton] = useState<string>(RadioButtonType.Predicted); + + const handleManuallyDefinedFailureRateChange = (event) => { + const inputValue = event.target.value; + const regex = /^[0-9]*\.?[0-9]*$/; + if (regex.test(inputValue)) { + setSnsManuallyDefinedFailureRate(inputValue); + } + }; + + const handleSnsBasicSelectedFailureRateChange = (event: React.ChangeEvent<HTMLInputElement>) => { + // TODO: Add handler for update with error + setSelectedRadioButton(event.target.value as RadioButtonType); + if (event.target.value === RadioButtonType.Predicted) { + // Updated when we switch to Pred. rate: + onEventUpdated({ ...shapeToolData, probability: snsPredictedFailureRate }); + } + if (event.target.value === RadioButtonType.Operational) { + // Updated when we switch to Oper. rate: + onEventUpdated({ ...shapeToolData, probability: snsOperationalFailureRate }); + } + }; + + const handleManualFailureRateUpdate = () => { + onEventUpdated({ ...shapeToolData, probability: snsManuallyDefinedFailureRate }); + }; + const handleFailureModeClicked = (failureMode: FailureMode) => { setFailureModeOverview(failureMode); setFailureModeOverviewDialogOpen(true); }; useEffect(() => { + // Clear values, after node was changed + setSnsPredictedFailureRate(undefined); + setSnsOperationalFailureRate(undefined); + setSnsOperationalFailureRate(undefined); + setSelectedRadioButton(RadioButtonType.Predicted); + if (shapeToolData?.supertypes?.criticality) { setCriticality(shapeToolData.supertypes.criticality); } else { @@ -99,6 +142,31 @@ const FaultEventMenu = ({ shapeToolData, onEventUpdated, refreshTree, rootIri }: } else { setSchematicDesignation(undefined); } + + if (shapeToolData?.selectedEstimate) { + // SELECTED ESTIMATE => PREDICTED IS SELECTED + setSnsPredictedFailureRate(shapeToolData?.selectedEstimate.value); + setSelectedRadioButton(RadioButtonType.Predicted); + setSnsManuallyDefinedFailureRate(undefined); + } else { + // NO SELECTED ESTIMATE => MANUAL IS SELECTED + setSelectedRadioButton(RadioButtonType.Manual); + if (shapeToolData?.probability) { + setSnsManuallyDefinedFailureRate(shapeToolData.probability); + } + } + let supertypes: any = shapeToolData?.supertypes?.supertypes; + if (supertypes) { + const objectWithHasFailureRate = supertypes.find((obj) => obj.hasOwnProperty("hasFailureRate")); + + if (objectWithHasFailureRate?.hasFailureRate?.prediction?.value) { + setSnsPredictedFailureRate(objectWithHasFailureRate?.hasFailureRate?.prediction?.value); + } + if (objectWithHasFailureRate?.hasFailureRate?.estimate?.value) { + setSelectedRadioButton(RadioButtonType.Operational); + setSnsOperationalFailureRate(objectWithHasFailureRate?.hasFailureRate?.estimate?.value); + } + } }, [shapeToolData]); const basedFailureRate = shapeToolData?.supertypes?.supertypes?.hasFailureRate?.estimate?.value; @@ -169,7 +237,64 @@ const FaultEventMenu = ({ shapeToolData, onEventUpdated, refreshTree, rootIri }: {/* INTERMEDIATE NODE */} {shapeToolData && shapeToolData.eventType === EventType.INTERMEDIATE && ( - <Box style={{ display: "flex", flexDirection: "row" }}></Box> + <> + {shapeToolData?.probability && ( + <Box className={classes.labelRow}> + <Typography> + <span className={classes.label}>Calculated failure rate</span> + {shapeToolData?.probability.toExponential(2)} + </Typography> + </Box> + )} + </> + )} + + {shapeToolData && shapeToolData.eventType === EventType.BASIC && ( + <> + <Box className={classes.labelRow}> + <FormControl> + <RadioGroup value={selectedRadioButton} onChange={handleSnsBasicSelectedFailureRateChange}> + {snsPredictedFailureRate && ( + <Box display={"flex"} flexDirection={"row"} alignItems="center"> + <FormControlLabel + value={RadioButtonType.Predicted} + control={<Radio />} + label="Predicted failure rate:" + /> + <Typography>{`${snsPredictedFailureRate}`}</Typography> + </Box> + )} + {snsOperationalFailureRate && ( + <Box display={"flex"} flexDirection={"row"} alignItems="center"> + <FormControlLabel + value={RadioButtonType.Operational} + control={<Radio />} + label="Operational failure rate:" + /> + <Typography>{`${snsOperationalFailureRate}`}</Typography> + </Box> + )} + <Box display={"flex"} flexDirection={"row"} alignItems="center"> + <FormControlLabel + value={RadioButtonType.Manual} + control={<Radio />} + label="Manually defined failure rate:" + /> + <TextField + className={classes.numberInput} + InputLabelProps={{ shrink: false }} + variant="outlined" + value={snsManuallyDefinedFailureRate} + onChange={handleManuallyDefinedFailureRateChange} + inputProps={{ inputMode: "decimal" }} + disabled={selectedRadioButton !== RadioButtonType.Manual} + onBlur={handleManualFailureRateUpdate} + /> + </Box> + </RadioGroup> + </FormControl> + </Box> + </> )} {/* EXTERNAL NODE */} diff --git a/src/components/editor/faultTree/shapes/RenderTree.tsx b/src/components/editor/faultTree/shapes/RenderTree.tsx index 52d99eea..1cdb5bad 100644 --- a/src/components/editor/faultTree/shapes/RenderTree.tsx +++ b/src/components/editor/faultTree/shapes/RenderTree.tsx @@ -54,8 +54,15 @@ const renderTree = async (container, node, parentShape = null, pathsToHighlight) if (width > DEFAULT_NODE_SHAPE_SIZE) nodeShape.prop("size", { width: width }); nodeShape.attr(["label", "text"], node.name); + + // For now it seems impossible to detect when operational failure rate was selected. + // "has" function from lodash can only check property existence with direct path. So we can't show "(o)" in front of "probability" + if (has(node, "probability")) { - nodeShape.attr(["probabilityLabel", "text"], node.probability.toExponential(2)); + nodeShape.attr( + ["probabilityLabel", "text"], + `${has(node, "selectedEstimate") ? "(p)" : "(m)"}${node.probability.toExponential(2)}`, + ); } if (has(node, "probabilityRequirement")) { nodeShape.attr(["probabilityRequirementLabel", "text"], node.probabilityRequirement.toExponential(2)); diff --git a/src/models/eventModel.tsx b/src/models/eventModel.tsx index 832daa09..3db44232 100644 --- a/src/models/eventModel.tsx +++ b/src/models/eventModel.tsx @@ -52,6 +52,7 @@ const ctx = { username: VocabularyUtils.PREFIX + "username", estimate: VocabularyUtils.PREFIX + "has-estimate", schematicDesignation: VocabularyUtils.PREFIX + "schematic-designation", + selectedEstimate: VocabularyUtils.PREFIX + "has-selected-estimation", }; export const CONTEXT = Object.assign({}, ctx, ABSTRACT_CONTEXT, FAILURE_MODE_CONTEXT, RECTANGLE_CONTEXT); @@ -81,6 +82,11 @@ export interface FaultEvent extends AbstractModel { references?: { isPartOf?: string; }; + selectedEstimate?: { + iri?: string; + types?: string[]; + value?: number; + }; supertypes?: { criticality?: number; supertypes?: {