@kartverket/backstage-plugin-risk-scorecard 1.0.12 → 1.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,11 +4,14 @@ This is a plugin for Backstage that helps you and your team when working continu
4
4
  The plugin is dependent on a backend service in order to decrypt and communicate with GitHub, and some configuration is
5
5
  necessary for them to communicate.
6
6
 
7
- Add the root url to your running backend service to the app-config:
7
+ Add the following configuration to the proxy-block in your app-config. Modify the `target` to the root url of your running backend service.
8
8
 
9
- ``` yaml
10
- app:
11
- backendUrl: http://localhost:8080
9
+ ```yaml
10
+ proxy:
11
+ endpoints:
12
+ '/risc-proxy':
13
+ target: http://localhost:8080
14
+ allowedHeaders: ['Authorization', 'GCP-Access-Token']
12
15
  ```
13
16
 
14
17
  The backend uses EntraID id-tokens to validate the user, and GCP access tokens to federate access to the GCP KMS.
@@ -18,4 +21,4 @@ for both:
18
21
  1. Add discovery of organization data for Microsoft Org
19
22
  2. Add authentication for both providers
20
23
 
21
- Happy RiSc-ing 🌹
24
+ Happy RiSc-ing 🌹
@@ -1,9 +1,9 @@
1
1
  import React, { useState, useEffect, useCallback, createContext, useContext, Fragment, useRef } from 'react';
2
- import { makeStyles, FormControl, FormLabel, FormHelperText, TextField as TextField$1, Grid, Typography, Box as Box$1, useTheme, Paper, Button, Radio, CircularProgress, Dialog, DialogTitle, DialogActions, Stepper, Step, StepButton, Divider as Divider$1, Drawer, List, ListSubheader, ListItem, ListItemText as ListItemText$1, ClickAwayListener, Tooltip, DialogContent, DialogContentText, IconButton, Tab } from '@material-ui/core';
2
+ import { makeStyles, FormControl, FormLabel, FormHelperText, TextField as TextField$1, Grid, Typography, Box as Box$1, useTheme, Paper, Button, Radio, CircularProgress, Dialog, DialogTitle, DialogActions, Stepper, Step, StepButton, Divider as Divider$1, Drawer, List, ListSubheader, ListItem, ListItemText as ListItemText$1, ClickAwayListener, Tooltip, DialogContent, DialogContentText, IconButton, Tab, LinearProgress } from '@material-ui/core';
3
3
  import { InfoCard, ContentHeader, SupportButton } from '@backstage/core-components';
4
4
  import { useEntity } from '@backstage/plugin-catalog-react';
5
5
  import { useRouteRef, useApi, googleAuthApiRef, identityApiRef, fetchApiRef, configApiRef } from '@backstage/core-plugin-api';
6
- import { s as scenarioRouteRef, r as riScRouteRef, p as pluginRiScTranslationRef } from './index-f0f7d618.esm.js';
6
+ import { s as scenarioRouteRef, r as riScRouteRef, p as pluginRiScTranslationRef } from './index-d8d91dc1.esm.js';
7
7
  import { useNavigate, useLocation, useParams } from 'react-router';
8
8
  import { useEffectOnce } from 'react-use';
9
9
  import { useSearchParams, Routes, Route } from 'react-router-dom';
@@ -69,6 +69,13 @@ var ProcessingStatus = /* @__PURE__ */ ((ProcessingStatus2) => {
69
69
  ProcessingStatus2["ErrorWhenFetchingJSONSchema"] = "ErrorWhenFetchingJSONSchema";
70
70
  return ProcessingStatus2;
71
71
  })(ProcessingStatus || {});
72
+ var ContentStatus = /* @__PURE__ */ ((ContentStatus2) => {
73
+ ContentStatus2["Success"] = "Success";
74
+ ContentStatus2["Failure"] = "Failure";
75
+ ContentStatus2["FileNotFound"] = "FileNotFound";
76
+ ContentStatus2["DecryptionFailed"] = "DecryptionFailed";
77
+ return ContentStatus2;
78
+ })(ContentStatus || {});
72
79
 
73
80
  var $schema = "https://json-schema.org/draft/2020-12/schema";
74
81
  var $id = "https://kartverket.github.io/backstage-plugin-risk-scorecard-backend/risc_schema_en_v3_2.json";
@@ -446,7 +453,7 @@ const requiresNewApproval = (oldRiSc, updatedRiSc) => {
446
453
  return true;
447
454
  }
448
455
  let requiresApproval = false;
449
- oldRiSc.scenarios.map((oldScenario, index) => {
456
+ oldRiSc.scenarios.forEach((oldScenario, index) => {
450
457
  const updatedScenario = updatedRiSc.scenarios[index];
451
458
  if (oldScenario.threatActors !== updatedScenario.threatActors)
452
459
  requiresApproval = true;
@@ -462,10 +469,14 @@ const requiresNewApproval = (oldRiSc, updatedRiSc) => {
462
469
  requiresApproval = true;
463
470
  if (oldScenario.remainingRisk.consequence !== updatedScenario.remainingRisk.consequence)
464
471
  requiresApproval = true;
465
- oldScenario.actions.map((oldAction, i) => {
466
- const updatedAction = updatedScenario.actions[i];
467
- if (oldAction.deadline !== updatedAction.deadline)
472
+ oldScenario.actions.forEach((oldAction, i) => {
473
+ if (updatedScenario.actions[i] !== void 0) {
474
+ const updatedAction = updatedScenario.actions[i];
475
+ if (oldAction.deadline !== updatedAction.deadline)
476
+ requiresApproval = true;
477
+ } else {
468
478
  requiresApproval = true;
479
+ }
469
480
  });
470
481
  });
471
482
  return requiresApproval;
@@ -592,12 +603,12 @@ const useFetch = () => {
592
603
  const googleApi = useApi(googleAuthApiRef);
593
604
  const identityApi = useApi(identityApiRef);
594
605
  const { fetch: fetchApi } = useApi(fetchApiRef);
595
- const baseUri = useApi(configApiRef).getString("riskAssessment.baseUrl");
596
- const riScUri = `${baseUri}/api/risc/${repoInformation.owner}/${repoInformation.name}`;
606
+ const backendUrl = useApi(configApiRef).getString("backend.baseUrl");
607
+ const riScUri = `${backendUrl}/api/proxy/risc-proxy/api/risc/${repoInformation.owner}/${repoInformation.name}`;
597
608
  const uriToFetchAllRiScs = () => `${riScUri}/all`;
598
609
  const uriToFetchRiSc = (id) => `${riScUri}/${id}`;
599
610
  const uriToPublishRiSc = (id) => `${riScUri}/publish/${id}`;
600
- const uriToFetchLatestJSONSchema = () => `${baseUri}/api/risc/schemas/latest`;
611
+ const uriToFetchLatestJSONSchema = () => `${backendUrl}/api/proxy/risc-proxy/api/risc/schemas/latest`;
601
612
  const [response, setResponse] = useResponse();
602
613
  const fetch = (uri, method, onSuccess, onError, body) => {
603
614
  Promise.all([
@@ -739,7 +750,6 @@ const useScenarioDrawer = (riSc, updateRiSc, scenarioIdFromParams) => {
739
750
  return;
740
751
  }
741
752
  if (isNewScenario) {
742
- setSearchParams({ step: "scenario" });
743
753
  return;
744
754
  }
745
755
  const selectedScenario = riSc.content.scenarios.find(
@@ -755,12 +765,17 @@ const useScenarioDrawer = (riSc, updateRiSc, scenarioIdFromParams) => {
755
765
  setOriginalScenario(selectedScenario);
756
766
  setScenarioDrawerState(2 /* View */);
757
767
  }
758
- }, [getRiScPath, isNewScenario, navigate, riSc, scenarioIdFromParams, setSearchParams]);
768
+ }, [riSc, scenarioIdFromParams, getRiScPath, navigate, isNewScenario]);
759
769
  const openScenario = (id) => {
760
770
  if (riSc) {
761
771
  navigate(getScenarioPath({ riScId: riSc.id, scenarioId: id }));
762
772
  }
763
773
  };
774
+ const openNewScenarioWizard = (id, step) => {
775
+ if (riSc) {
776
+ navigate(`${getScenarioPath({ riScId: riSc.id, scenarioId: id })}?step=${step}`);
777
+ }
778
+ };
764
779
  const closeScenario = () => {
765
780
  if (riSc) {
766
781
  setIsNewScenario(false);
@@ -813,7 +828,7 @@ const useScenarioDrawer = (riSc, updateRiSc, scenarioIdFromParams) => {
813
828
  const s = emptyScenario();
814
829
  setScenario(s);
815
830
  setOriginalScenario(s);
816
- openScenario(s.ID);
831
+ openNewScenarioWizard(s.ID, "scenario");
817
832
  }
818
833
  };
819
834
  const setTitle = (title) => {
@@ -923,6 +938,7 @@ const useFetchRiScs = (riScIdFromParams) => {
923
938
  const location = useLocation();
924
939
  const navigate = useNavigate();
925
940
  const getRiScPath = useRouteRef(riScRouteRef);
941
+ const [isRequesting, setIsRequesting] = useState(false);
926
942
  const {
927
943
  fetchRiScs,
928
944
  postRiScs,
@@ -953,8 +969,9 @@ const useFetchRiScs = (riScIdFromParams) => {
953
969
  useEffectOnce(() => {
954
970
  fetchRiScs(
955
971
  (res) => {
956
- const fetchedRiScs = res.map((riScDTO) => {
957
- const content = dtoToRiSc(JSON.parse(riScDTO.riScContent));
972
+ const fetchedRiScs = res.filter((risk) => risk.status === ContentStatus.Success).map((riScDTO) => {
973
+ const json = JSON.parse(riScDTO.riScContent);
974
+ const content = dtoToRiSc(json);
958
975
  return {
959
976
  id: riScDTO.riScId,
960
977
  content,
@@ -963,6 +980,16 @@ const useFetchRiScs = (riScIdFromParams) => {
963
980
  });
964
981
  setRiScs(fetchedRiScs);
965
982
  setIsFetching(false);
983
+ const errorRiScs = res.filter((risk) => risk.status !== ContentStatus.Success).map((risk) => risk.riScId);
984
+ if (errorRiScs.length > 0) {
985
+ const errorMessage = `Failed to fetch risc scorecards with ids: ${errorRiScs.join(
986
+ ", "
987
+ )}`;
988
+ setResponse({
989
+ statusMessage: errorMessage,
990
+ status: ProcessingStatus.ErrorWhenFetchingRiScs
991
+ });
992
+ }
966
993
  if (fetchedRiScs.length === 0) {
967
994
  return;
968
995
  }
@@ -1092,6 +1119,7 @@ const useFetchRiScs = (riScIdFromParams) => {
1092
1119
  isError: false,
1093
1120
  isSuccess: false
1094
1121
  });
1122
+ setIsRequesting(true);
1095
1123
  putRiScs(
1096
1124
  updatedRiSc,
1097
1125
  () => {
@@ -1104,6 +1132,7 @@ const useFetchRiScs = (riScIdFromParams) => {
1104
1132
  setRiScs(
1105
1133
  riScs.map((r) => r.id === selectedRiSc.id ? updatedRiSc : r)
1106
1134
  );
1135
+ setIsRequesting(false);
1107
1136
  },
1108
1137
  () => {
1109
1138
  setUpdateRiScStatus({
@@ -1111,6 +1140,7 @@ const useFetchRiScs = (riScIdFromParams) => {
1111
1140
  isError: true,
1112
1141
  isSuccess: false
1113
1142
  });
1143
+ setIsRequesting(false);
1114
1144
  }
1115
1145
  );
1116
1146
  }
@@ -1137,7 +1167,8 @@ const useFetchRiScs = (riScIdFromParams) => {
1137
1167
  createNewRiSc,
1138
1168
  updateRiSc,
1139
1169
  approveRiSc,
1140
- response
1170
+ response,
1171
+ isRequesting
1141
1172
  };
1142
1173
  };
1143
1174
 
@@ -1242,7 +1273,7 @@ const useInputFieldStyles = makeStyles((theme) => ({
1242
1273
  paper: {
1243
1274
  padding: theme.spacing(2),
1244
1275
  marginBottom: theme.spacing(2),
1245
- backgroundColor: theme.palette.type === "dark" ? "#404040" : "#e0e0e0"
1276
+ backgroundColor: theme.palette.type === "dark" ? "#404040" : "#FFFFFF"
1246
1277
  },
1247
1278
  root: {
1248
1279
  "&.Mui-disabled": {
@@ -1301,10 +1332,23 @@ const Dropdown = ({
1301
1332
  handleChange,
1302
1333
  error,
1303
1334
  required,
1304
- variant = "outlined"
1335
+ variant = "outlined",
1336
+ renderSelectedValue
1305
1337
  }) => {
1306
1338
  const { formLabel, formControl } = useInputFieldStyles();
1307
1339
  const multiple = Array.isArray(selectedValues);
1340
+ function getOptionsValue(option) {
1341
+ return typeof option === "string" ? option : option.value;
1342
+ }
1343
+ function getOptionsRenderValue(option) {
1344
+ return typeof option === "string" ? option : option.renderedValue;
1345
+ }
1346
+ function renderSelectedValueHandler(value) {
1347
+ if (renderSelectedValue) {
1348
+ return renderSelectedValue(value);
1349
+ }
1350
+ return value;
1351
+ }
1308
1352
  const onChange = (event) => handleChange(event.target.value);
1309
1353
  const renderValue = (selected) => multiple ? /* @__PURE__ */ React.createElement(
1310
1354
  Box,
@@ -1316,7 +1360,7 @@ const Dropdown = ({
1316
1360
  paddingBottom: 0
1317
1361
  }
1318
1362
  },
1319
- selected.map((value) => /* @__PURE__ */ React.createElement(Chip, { key: value, label: value }))
1363
+ selected.map((value) => /* @__PURE__ */ React.createElement(Chip, { key: value, label: renderSelectedValueHandler(value) }))
1320
1364
  ) : selected;
1321
1365
  return /* @__PURE__ */ React.createElement(FormControl$1, { className: formControl }, label && /* @__PURE__ */ React.createElement(FormLabel$1, { className: formLabel, required }, label), /* @__PURE__ */ React.createElement(
1322
1366
  Select,
@@ -1336,7 +1380,7 @@ const Dropdown = ({
1336
1380
  }
1337
1381
  } : {}
1338
1382
  },
1339
- options.map((name) => /* @__PURE__ */ React.createElement(MenuItem, { key: name, value: name }, multiple && /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(Checkbox, { checked: selectedValues.indexOf(name) > -1 })), /* @__PURE__ */ React.createElement(ListItemText, { primary: name })))
1383
+ options.map((name) => /* @__PURE__ */ React.createElement(MenuItem, { key: getOptionsValue(name), value: getOptionsValue(name) }, multiple && /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(Checkbox, { checked: selectedValues.indexOf(getOptionsValue(name)) > -1 })), /* @__PURE__ */ React.createElement(ListItemText, { primary: getOptionsRenderValue(name) })))
1340
1384
  ), error && /* @__PURE__ */ React.createElement(FormHelperText, { error: true }, error));
1341
1385
  };
1342
1386
 
@@ -1353,6 +1397,20 @@ const ScenarioStep = () => {
1353
1397
  setThreatActors,
1354
1398
  setVulnerabilities
1355
1399
  } = useContext(ScenarioContext);
1400
+ const translatedThreatActors = threatActorsOptions.map((threatActor) => {
1401
+ return {
1402
+ value: threatActor,
1403
+ /* @ts-ignore Because ts can't typecheck strings agains our keys */
1404
+ renderedValue: t(`threatActors.${threatActor}`)
1405
+ };
1406
+ });
1407
+ const translatedVulnerabilities = vulnerabilitiesOptions.map((vulnerability) => {
1408
+ return {
1409
+ value: vulnerability,
1410
+ /* @ts-ignore Because ts can't typecheck strings agains our keys */
1411
+ renderedValue: t(`vulnerabilities.${vulnerability}`)
1412
+ };
1413
+ });
1356
1414
  return /* @__PURE__ */ React.createElement(Grid, { container: true }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(Typography, { className: h2 }, t("scenarioDrawer.title")), /* @__PURE__ */ React.createElement(Typography, { className: subtitle2 }, t("scenarioDrawer.subtitle"))), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(
1357
1415
  TextField,
1358
1416
  {
@@ -1379,8 +1437,11 @@ const ScenarioStep = () => {
1379
1437
  Dropdown,
1380
1438
  {
1381
1439
  selectedValues: scenario.threatActors,
1382
- options: threatActorsOptions,
1383
- handleChange: setThreatActors
1440
+ options: translatedThreatActors,
1441
+ handleChange: setThreatActors,
1442
+ renderSelectedValue: (value) => {
1443
+ return t(`threatActors.${value}`);
1444
+ }
1384
1445
  }
1385
1446
  )
1386
1447
  ), /* @__PURE__ */ React.createElement(
@@ -1399,8 +1460,11 @@ const ScenarioStep = () => {
1399
1460
  Dropdown,
1400
1461
  {
1401
1462
  selectedValues: scenario.vulnerabilities,
1402
- options: vulnerabilitiesOptions,
1403
- handleChange: setVulnerabilities
1463
+ options: translatedVulnerabilities,
1464
+ handleChange: setVulnerabilities,
1465
+ renderSelectedValue: (value) => {
1466
+ return t(`vulnerabilities.${value}`);
1467
+ }
1404
1468
  }
1405
1469
  )
1406
1470
  ), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(
@@ -1623,6 +1687,7 @@ const ProbabilityTable = ({
1623
1687
  selectedValue,
1624
1688
  handleChange
1625
1689
  }) => {
1690
+ const theme = useTheme();
1626
1691
  const { t } = useTranslationRef(pluginRiScTranslationRef);
1627
1692
  const { table, cell, radio } = useTableStyles$1();
1628
1693
  const handleChangeRow = (row) => () => handleChange(row);
@@ -1631,7 +1696,10 @@ const ProbabilityTable = ({
1631
1696
  {
1632
1697
  checked: selectedValue === row,
1633
1698
  onChange: handleChangeRow(row),
1634
- style: { padding: 0, color: "#9BC9FE" }
1699
+ style: {
1700
+ padding: 0,
1701
+ color: theme.palette.type === "dark" ? "#9BC9FE" : theme.palette.primary.main
1702
+ }
1635
1703
  }
1636
1704
  ), row, ": ", t(`probabilityTable.rows.${row}`)));
1637
1705
  const contentCell = (row) => /* @__PURE__ */ React.createElement("td", { className: cell }, t(`probabilityTable.cells.${row}`));
@@ -1642,6 +1710,7 @@ const ConsequenceTable = ({
1642
1710
  selectedValue,
1643
1711
  handleChange
1644
1712
  }) => {
1713
+ const theme = useTheme();
1645
1714
  const { t } = useTranslationRef(pluginRiScTranslationRef);
1646
1715
  const { table, labelCell, cell, voidCell, radio } = useTableStyles$1();
1647
1716
  const handleChangeRow = (row) => () => handleChange(row);
@@ -1651,7 +1720,10 @@ const ConsequenceTable = ({
1651
1720
  {
1652
1721
  checked: selectedValue === row,
1653
1722
  onChange: handleChangeRow(row),
1654
- style: { padding: 0, color: "#9BC9FE" }
1723
+ style: {
1724
+ padding: 0,
1725
+ color: theme.palette.type === "dark" ? theme.palette.primary.light : theme.palette.primary.main
1726
+ }
1655
1727
  }
1656
1728
  ), row, ": ", t(`consequenceTable.rows.${row}`)));
1657
1729
  };
@@ -1693,11 +1765,21 @@ const RiskStep = ({ riskType, restEqualsInitial }) => {
1693
1765
  useEffect(() => {
1694
1766
  if (restEqualsInitial)
1695
1767
  setRemainingConsequence(getConsequenceLevel(scenario.risk));
1696
- }, [restEqualsInitial, scenario.risk, scenario.risk.consequence, setRemainingConsequence]);
1768
+ }, [
1769
+ restEqualsInitial,
1770
+ scenario.risk,
1771
+ scenario.risk.consequence,
1772
+ setRemainingConsequence
1773
+ ]);
1697
1774
  useEffect(() => {
1698
1775
  if (restEqualsInitial)
1699
1776
  setRemainingProbability(getProbabilityLevel(scenario.risk));
1700
- }, [restEqualsInitial, scenario.risk, scenario.risk.probability, setRemainingProbability]);
1777
+ }, [
1778
+ restEqualsInitial,
1779
+ scenario.risk,
1780
+ scenario.risk.probability,
1781
+ setRemainingProbability
1782
+ ]);
1701
1783
  const setC = riskType === "initial" ? setConsequence : setRemainingConsequence;
1702
1784
  const setP = riskType === "initial" ? setProbability : setRemainingProbability;
1703
1785
  return /* @__PURE__ */ React.createElement(Fragment, null, /* @__PURE__ */ React.createElement(Box, { className: box }, /* @__PURE__ */ React.createElement(Typography, { className: h2 }, t(`scenarioStepper.${resourceKey}.title`)), /* @__PURE__ */ React.createElement(Typography, { className: subtitle2 }, t(`scenarioStepper.${resourceKey}.subtitle`))), /* @__PURE__ */ React.createElement(Box, { className: box }, /* @__PURE__ */ React.createElement(Typography, { className: h3 }, t("dictionary.probability")), /* @__PURE__ */ React.createElement(Typography, { className: subtitle2 }, t(`scenarioStepper.${resourceKey}.probabilitySubtitle`))), /* @__PURE__ */ React.createElement(Box, { className: box }, /* @__PURE__ */ React.createElement(
@@ -1749,6 +1831,15 @@ const useWizardStyle = makeStyles((theme) => ({
1749
1831
  saveAndNextButtons: {
1750
1832
  display: "flex",
1751
1833
  gap: theme.spacing(2)
1834
+ },
1835
+ button: {
1836
+ "&.MuiButton-outlined": {
1837
+ color: theme.palette.type === "dark" ? "#9CC9FF" : theme.palette.link,
1838
+ borderr: "1px solid rgba(25, 118, 210, 0.5)",
1839
+ "&:hover": {
1840
+ borderColor: theme.palette.type === "dark" ? "#9CC9FF" : theme.palette.linkHover
1841
+ }
1842
+ }
1752
1843
  }
1753
1844
  }));
1754
1845
 
@@ -1786,6 +1877,7 @@ const ScenarioWizard = ({
1786
1877
  header,
1787
1878
  stepper,
1788
1879
  steps,
1880
+ button,
1789
1881
  buttonContainer,
1790
1882
  buttonContainerRight,
1791
1883
  saveAndNextButtons
@@ -1801,6 +1893,7 @@ const ScenarioWizard = ({
1801
1893
  } = useContext(ScenarioContext);
1802
1894
  const [showCloseConfirmation, setShowCloseConfirmation] = useState(false);
1803
1895
  const [restEqualsInitial, setRestEqualsInitial] = useState(isNewScenario);
1896
+ const [canCloseIfSuccessfull, setCanCloseIfSuccessfull] = useState(false);
1804
1897
  useEffect(() => {
1805
1898
  if (step === "restRisk")
1806
1899
  setRestEqualsInitial(false);
@@ -1810,15 +1903,16 @@ const ScenarioWizard = ({
1810
1903
  setShowCloseConfirmation(false);
1811
1904
  }, [closeScenario]);
1812
1905
  useEffect(() => {
1813
- if (updateStatus.isSuccess) {
1906
+ if (updateStatus.isSuccess && canCloseIfSuccessfull) {
1814
1907
  close();
1815
1908
  } else if (updateStatus.isError && wizardRef.current) {
1816
1909
  setShowCloseConfirmation(false);
1817
1910
  wizardRef.current.scrollIntoView({ behavior: "smooth", block: "end" });
1818
1911
  }
1819
- }, [close, updateStatus]);
1912
+ }, [canCloseIfSuccessfull, close, updateStatus]);
1820
1913
  const saveAndClose = () => {
1821
1914
  saveScenario();
1915
+ setCanCloseIfSuccessfull(true);
1822
1916
  };
1823
1917
  const handleCloseStepper = () => {
1824
1918
  if (JSON.stringify(scenario) !== JSON.stringify(originalScenario)) {
@@ -1848,7 +1942,15 @@ const ScenarioWizard = ({
1848
1942
  const isLastStep = () => {
1849
1943
  return scenarioWizardSteps.indexOf(step) === scenarioWizardSteps.length - 1;
1850
1944
  };
1851
- return /* @__PURE__ */ React.createElement("div", { ref: wizardRef }, /* @__PURE__ */ React.createElement(Box, { className: root }, /* @__PURE__ */ React.createElement(Box, { className: container }, /* @__PURE__ */ React.createElement(Box, { className: header }, /* @__PURE__ */ React.createElement(Typography, { className: h1 }, t("scenarioDrawer.title")), /* @__PURE__ */ React.createElement(Button$1, { variant: "outlined", onClick: handleCloseStepper }, t("dictionary.cancel"))), /* @__PURE__ */ React.createElement(
1945
+ return /* @__PURE__ */ React.createElement("div", { ref: wizardRef }, /* @__PURE__ */ React.createElement(Box, { className: root }, /* @__PURE__ */ React.createElement(Box, { className: container }, /* @__PURE__ */ React.createElement(Box, { className: header }, /* @__PURE__ */ React.createElement(Typography, { className: h1 }, t("scenarioDrawer.title")), /* @__PURE__ */ React.createElement(
1946
+ Button$1,
1947
+ {
1948
+ variant: "outlined",
1949
+ className: button,
1950
+ onClick: handleCloseStepper
1951
+ },
1952
+ t("dictionary.cancel")
1953
+ )), /* @__PURE__ */ React.createElement(
1852
1954
  Stepper,
1853
1955
  {
1854
1956
  className: stepper,
@@ -1896,6 +1998,7 @@ const ScenarioWizard = ({
1896
1998
  /* @__PURE__ */ React.createElement(Box, { className: saveAndNextButtons }, /* @__PURE__ */ React.createElement(
1897
1999
  Button$1,
1898
2000
  {
2001
+ className: button,
1899
2002
  variant: isLastStep() ? "contained" : "outlined",
1900
2003
  onClick: saveAndClose,
1901
2004
  disabled: updateStatus.isLoading
@@ -1957,7 +2060,6 @@ const useScenarioDrawerStyles = makeStyles((theme) => ({
1957
2060
  backgroundColor: theme.palette.type === "dark" ? "#555555" : "#e0e0e0"
1958
2061
  },
1959
2062
  gridContainer: {
1960
- padding: theme.spacing(2),
1961
2063
  marginBottom: theme.palette.type === "dark" ? theme.spacing(1) : "7px",
1962
2064
  backgroundColor: theme.palette.type === "dark" ? "#55555519" : "#e0e0e019"
1963
2065
  },
@@ -2059,7 +2161,7 @@ const RiskSection = () => {
2059
2161
  ));
2060
2162
  };
2061
2163
 
2062
- const useActionBoxStyles = makeStyles((_) => ({
2164
+ const useActionBoxStyles = makeStyles((theme) => ({
2063
2165
  alignCenter: {
2064
2166
  display: "flex",
2065
2167
  alignItems: "center"
@@ -2067,6 +2169,28 @@ const useActionBoxStyles = makeStyles((_) => ({
2067
2169
  justifyEnd: {
2068
2170
  display: "flex",
2069
2171
  justifyContent: "flex-end"
2172
+ },
2173
+ row: {
2174
+ display: "flex",
2175
+ flexDirection: "row",
2176
+ justifyContent: "space-between",
2177
+ padding: theme.spacing(1)
2178
+ },
2179
+ column: {
2180
+ display: "flex",
2181
+ flexDirection: "column",
2182
+ justifyContent: "center"
2183
+ },
2184
+ paddingTop: {
2185
+ paddingTop: theme.spacing(2)
2186
+ },
2187
+ actionDescription: {
2188
+ marginTop: theme.spacing(-2)
2189
+ },
2190
+ chipDropdown: {
2191
+ display: "flex",
2192
+ flexDirection: "row",
2193
+ justifyContent: "flex-end"
2070
2194
  }
2071
2195
  }));
2072
2196
 
@@ -2087,7 +2211,7 @@ const ChipDropdown = ({
2087
2211
  style: {
2088
2212
  margin: 0,
2089
2213
  padding: 0,
2090
- backgroundColor: "#D9D9D9",
2214
+ backgroundColor: selectedValue === "Completed" ? "#6BC6A4" : "#D9D9D9",
2091
2215
  color: "#000000"
2092
2216
  },
2093
2217
  label: selectedValue,
@@ -2126,7 +2250,14 @@ const ActionBox = ({
2126
2250
  const { t } = useTranslationRef(pluginRiScTranslationRef);
2127
2251
  const { body2, label } = useFontStyles();
2128
2252
  const { gridContainer } = useScenarioDrawerStyles();
2129
- const { alignCenter, justifyEnd } = useActionBoxStyles();
2253
+ const {
2254
+ alignCenter,
2255
+ row,
2256
+ paddingTop,
2257
+ actionDescription,
2258
+ column,
2259
+ chipDropdown
2260
+ } = useActionBoxStyles();
2130
2261
  const [previousAction, setPreviousAction] = useState(action);
2131
2262
  useEffect(() => {
2132
2263
  if (previousAction !== action) {
@@ -2140,19 +2271,28 @@ const ActionBox = ({
2140
2271
  status
2141
2272
  });
2142
2273
  };
2143
- return /* @__PURE__ */ React.createElement(Grid, { container: true, className: gridContainer }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12, className: alignCenter }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 6 }, /* @__PURE__ */ React.createElement(Typography, { className: label }, t("dictionary.measure"), " ", index)), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 6, className: justifyEnd }, /* @__PURE__ */ React.createElement(
2274
+ return /* @__PURE__ */ React.createElement(Grid, { container: true, className: gridContainer }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12, className: alignCenter }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 6, className: paddingTop }, /* @__PURE__ */ React.createElement(Typography, { className: label }, t("dictionary.measure"), " ", index))), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12, className: actionDescription }, /* @__PURE__ */ React.createElement(Typography, { className: body2 }, action.description)), /* @__PURE__ */ React.createElement(Grid, { container: true, xs: 12, className: row }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 4 }, /* @__PURE__ */ React.createElement(Typography, { className: label }, t("dictionary.measureOwner")), /* @__PURE__ */ React.createElement(Typography, { className: body2 }, action.owner)), /* @__PURE__ */ React.createElement(Grid, { container: true, xs: 4, className: column }, /* @__PURE__ */ React.createElement(Typography, { className: label }, t("dictionary.deadline")), /* @__PURE__ */ React.createElement(Typography, { className: body2 }, formatDate(action.deadline))), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 3, className: chipDropdown }, /* @__PURE__ */ React.createElement(
2144
2275
  ChipDropdown,
2145
2276
  {
2146
2277
  options: actionStatusOptions,
2147
2278
  selectedValue: action.status,
2148
2279
  handleChange: setStatus
2149
2280
  }
2150
- ))), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(Typography, { className: body2 }, action.description)), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 4 }, /* @__PURE__ */ React.createElement(Typography, { className: label }, t("dictionary.measureOwner")), /* @__PURE__ */ React.createElement(Typography, { className: body2 }, action.owner)), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 4 }, /* @__PURE__ */ React.createElement(Typography, { className: label }, t("dictionary.deadline")), /* @__PURE__ */ React.createElement(Typography, { className: body2 }, formatDate(action.deadline))));
2281
+ ))));
2282
+ };
2283
+
2284
+ const ExistingActionBox = ({
2285
+ existingAction
2286
+ }) => {
2287
+ const { body2 } = useFontStyles();
2288
+ const { gridContainer } = useScenarioDrawerStyles();
2289
+ const { alignCenter } = useActionBoxStyles();
2290
+ return /* @__PURE__ */ React.createElement(Grid, { container: true, className: gridContainer }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12, className: alignCenter }, /* @__PURE__ */ React.createElement(Typography, { className: body2 }, existingAction)));
2151
2291
  };
2152
2292
 
2153
2293
  const ActionsSection = () => {
2154
2294
  const { t } = useTranslationRef(pluginRiScTranslationRef);
2155
- const { h3, button } = useFontStyles();
2295
+ const { h3, button, label } = useFontStyles();
2156
2296
  const { titleAndButton, section, editIcon } = useScenarioDrawerStyles();
2157
2297
  const { scenario, editScenario, saveScenario, updateAction } = useContext(ScenarioContext);
2158
2298
  return /* @__PURE__ */ React.createElement(Box$2, null, /* @__PURE__ */ React.createElement(Paper, { className: section, style: { padding: "1rem" } }, /* @__PURE__ */ React.createElement(
@@ -2165,7 +2305,7 @@ const ActionsSection = () => {
2165
2305
  marginBottom: "12px"
2166
2306
  }
2167
2307
  },
2168
- /* @__PURE__ */ React.createElement(Typography, { className: h3, gutterBottom: true }, t("scenarioDrawer.measureTab.actionsTitle")),
2308
+ /* @__PURE__ */ React.createElement(Typography, { className: h3 }, t("scenarioDrawer.measureTab.actionsTitle")),
2169
2309
  /* @__PURE__ */ React.createElement(
2170
2310
  Button,
2171
2311
  {
@@ -2176,7 +2316,7 @@ const ActionsSection = () => {
2176
2316
  startIcon: /* @__PURE__ */ React.createElement(EditIcon, { className: editIcon, "aria-label": "Edit" })
2177
2317
  }
2178
2318
  )
2179
- ), scenario.actions.map((action, index) => /* @__PURE__ */ React.createElement(Fragment, { key: action.ID }, /* @__PURE__ */ React.createElement(
2319
+ ), scenario.existingActions && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Typography, { className: label }, t("scenarioDrawer.measureTab.existingMeasure")), /* @__PURE__ */ React.createElement(ExistingActionBox, { existingAction: scenario.existingActions }), scenario.actions.length > 0 && /* @__PURE__ */ React.createElement(Divider$1, { variant: "fullWidth" })), scenario.actions.map((action, index) => /* @__PURE__ */ React.createElement(Fragment, { key: action.ID }, /* @__PURE__ */ React.createElement(
2180
2320
  ActionBox,
2181
2321
  {
2182
2322
  action,
@@ -3119,10 +3259,19 @@ const RiScInfo = ({ riSc, approveRiSc, edit }) => {
3119
3259
  ), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12, sm: 6, md: 4 }, /* @__PURE__ */ React.createElement(RiScStatusComponent, { selectedRiSc: riSc, publishRiScFn: approveRiSc })));
3120
3260
  };
3121
3261
 
3262
+ const useLinearProgressStyle = makeStyles((theme) => ({
3263
+ linearProgress: {
3264
+ position: "sticky",
3265
+ top: 0,
3266
+ margin: theme.spacing(2)
3267
+ }
3268
+ }));
3269
+
3122
3270
  const Plugin = () => {
3123
3271
  var _a, _b;
3124
3272
  const params = useParams();
3125
3273
  const { t } = useTranslationRef(pluginRiScTranslationRef);
3274
+ const { linearProgress } = useLinearProgressStyle();
3126
3275
  const [RiScDialogState, setRiScDialogState] = useState(
3127
3276
  RiScDialogStates.Closed
3128
3277
  );
@@ -3139,7 +3288,8 @@ const Plugin = () => {
3139
3288
  updateRiScStatus,
3140
3289
  resetRiScStatus,
3141
3290
  approveRiSc,
3142
- response
3291
+ response,
3292
+ isRequesting
3143
3293
  } = useFetchRiScs(params.riScId);
3144
3294
  const scenario = useScenarioDrawer(
3145
3295
  selectedRiSc != null ? selectedRiSc : null,
@@ -3152,7 +3302,7 @@ const Plugin = () => {
3152
3302
  if (scenarioWizardStep !== null)
3153
3303
  resetRiScStatus();
3154
3304
  }, [resetRiScStatus, scenarioWizardStep]);
3155
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(ScenarioContext.Provider, { value: scenario }, response && /* @__PURE__ */ React.createElement(Alert, { severity: getAlertSeverity(response.status) }, /* @__PURE__ */ React.createElement(Typography, null, response.statusMessage)), scenarioWizardStep !== null ? /* @__PURE__ */ React.createElement(
3305
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(ScenarioContext.Provider, { value: scenario }, response && /* @__PURE__ */ React.createElement(Alert, { severity: getAlertSeverity(response.status) }, /* @__PURE__ */ React.createElement(Typography, null, response.statusMessage)), isRequesting && /* @__PURE__ */ React.createElement(LinearProgress, { className: linearProgress }), scenarioWizardStep !== null ? /* @__PURE__ */ React.createElement(
3156
3306
  ScenarioWizard,
3157
3307
  {
3158
3308
  step: scenarioWizardStep,
@@ -3214,4 +3364,4 @@ const RiScPlugin = () => {
3214
3364
  };
3215
3365
 
3216
3366
  export { RiScPlugin };
3217
- //# sourceMappingURL=index-55c3e55b.esm.js.map
3367
+ //# sourceMappingURL=index-b5d09407.esm.js.map