diff --git a/cypress/commons/actions/generic/Scenarios.js b/cypress/commons/actions/generic/Scenarios.js
index 3d5226f0f..07eb2fdea 100644
--- a/cypress/commons/actions/generic/Scenarios.js
+++ b/cypress/commons/actions/generic/Scenarios.js
@@ -105,6 +105,14 @@ function switchToScenarioView() {
getScenarioViewTab().click();
}
+function getErrorBanner() {
+ return cy.get(GENERIC_SELECTORS.genericComponents.error.errorBanner);
+}
+
+function getDismissErrorButton() {
+ return cy.get(GENERIC_SELECTORS.genericComponents.error.dismissErrorButton);
+}
+
// Select the scenario with the provided name and id
function selectScenario(scenarioName, scenarioId) {
const reqName = `requestSelectScenario_${scenarioName}`.replaceAll(' ', '');
@@ -246,4 +254,6 @@ export const Scenarios = {
validateScenario,
rejectScenario,
resetScenarioValidationStatus,
+ getErrorBanner,
+ getDismissErrorButton,
};
diff --git a/cypress/commons/constants/generic/IdConstants.js b/cypress/commons/constants/generic/IdConstants.js
index 6df1ec1e5..44bbbfb5d 100644
--- a/cypress/commons/constants/generic/IdConstants.js
+++ b/cypress/commons/constants/generic/IdConstants.js
@@ -106,5 +106,9 @@ export const GENERIC_SELECTORS = {
basicNumberInput: {
input: 'input',
},
+ error: {
+ errorBanner: '[data-cy=error-banner]',
+ dismissErrorButton: '[data-cy=dismiss-error-button]',
+ },
},
};
diff --git a/cypress/integration/brewery/ErrorScenarioRun.spec.js b/cypress/integration/brewery/ErrorScenarioRun.spec.js
new file mode 100644
index 000000000..f92976930
--- /dev/null
+++ b/cypress/integration/brewery/ErrorScenarioRun.spec.js
@@ -0,0 +1,60 @@
+// Copyright (c) Cosmo Tech.
+// Licensed under the MIT license.
+
+import 'cypress-file-upload';
+import utils from '../../commons/TestUtils';
+
+import { DATASET, RUN_TEMPLATE } from '../../commons/constants/brewery/TestConstants';
+import { Downloads, Login, Scenarios, ScenarioManager, ScenarioParameters } from '../../commons/actions';
+import { URL_REGEX } from '../../commons/constants/generic/TestConstants';
+
+Cypress.Keyboard.defaults({
+ keystrokeDelay: 0,
+});
+
+const SCENARIO_DATASET = DATASET.BREWERY_ADT;
+const SCENARIO_RUN_TEMPLATE = RUN_TEMPLATE.BASIC_TYPES;
+
+function forgeScenarioName() {
+ const prefix = 'Scenario - ';
+ return `${prefix}${utils.randomStr(7)}`;
+}
+
+describe('Displaying error banner on run scenario fail', () => {
+ before(() => {
+ Login.login();
+ });
+
+ beforeEach(() => {
+ Login.relogin();
+ });
+
+ const scenarioNamesToDelete = [];
+ after(() => {
+ Downloads.clearDownloadsFolder();
+ // Delete all tests scenarios
+ ScenarioManager.switchToScenarioManager();
+ for (const scenarioName of scenarioNamesToDelete) {
+ ScenarioManager.deleteScenario(scenarioName);
+ }
+ });
+ it('can display error banner and dismiss it', () => {
+ const scenarioName = forgeScenarioName();
+ scenarioNamesToDelete.push(scenarioName);
+ Scenarios.createScenario(scenarioName, true, SCENARIO_DATASET, SCENARIO_RUN_TEMPLATE);
+ ScenarioParameters.getLaunchButton().click();
+ ScenarioParameters.checkDontAskAgain();
+ ScenarioParameters.getLaunchConfirmButton().click();
+ cy.intercept('POST', URL_REGEX.SCENARIO_PAGE_RUN_WITH_ID, {
+ statusCode: 400,
+ body: {
+ title: 'Bad Request',
+ status: 400,
+ detail: 'Scenario #scenarioId not found in workspace #W-rXeBwRa0PM in organization #O-gZYpnd27G7',
+ },
+ });
+ Scenarios.getErrorBanner().should('be.visible');
+ Scenarios.getDismissErrorButton().click();
+ Scenarios.getErrorBanner().should('not.exist');
+ });
+});
diff --git a/src/components/ScenarioParameters/FileManagementUtils.js b/src/components/ScenarioParameters/FileManagementUtils.js
index afda8af3c..09b65da71 100644
--- a/src/components/ScenarioParameters/FileManagementUtils.js
+++ b/src/components/ScenarioParameters/FileManagementUtils.js
@@ -8,6 +8,8 @@ import WorkspaceService from '../../services/workspace/WorkspaceService';
import { AppInsights } from '../../services/AppInsights';
import { DATASET_ID_VARTYPE } from '../../services/config/ApiConstants';
import { DatasetsUtils, ScenarioParametersUtils } from '../../utils';
+import applicationStore from '../../state/Store.config';
+import { catchNonCriticalErrors } from '../../utils/ApiUtils';
const appInsights = AppInsights.getInstance();
@@ -217,11 +219,8 @@ const prepareToDeleteFile = (setClientFileDescriptorStatus) => {
};
const downloadFile = async (datasetId, setClientFileDescriptorStatus) => {
- const { error, data } = await DatasetService.findDatasetById(ORGANIZATION_ID, datasetId);
- if (error) {
- console.error(error);
- throw new Error(`Error finding dataset ${datasetId}`);
- } else {
+ try {
+ const { data } = await DatasetService.findDatasetById(ORGANIZATION_ID, datasetId);
const storageFilePath = DatasetsUtils.getStorageFilePathFromDataset(data);
if (storageFilePath !== undefined) {
setClientFileDescriptorStatus(UPLOAD_FILE_STATUS_KEY.DOWNLOADING);
@@ -229,6 +228,8 @@ const downloadFile = async (datasetId, setClientFileDescriptorStatus) => {
setClientFileDescriptorStatus(UPLOAD_FILE_STATUS_KEY.READY_TO_DOWNLOAD);
}
appInsights.trackDownload();
+ } catch (error) {
+ applicationStore.dispatch(catchNonCriticalErrors(error, 'Impossible to download dataset'));
}
};
@@ -241,7 +242,6 @@ const downloadFileData = async (datasets, datasetId, setClientFileDescriptorStat
if (!storageFilePath) {
return;
}
-
setClientFileDescriptorStatuses(UPLOAD_FILE_STATUS_KEY.DOWNLOADING, TABLE_DATA_STATUS.DOWNLOADING);
const data = await WorkspaceService.downloadWorkspaceFileData(ORGANIZATION_ID, WORKSPACE_ID, storageFilePath);
setClientFileDescriptorStatuses(UPLOAD_FILE_STATUS_KEY.READY_TO_DOWNLOAD, TABLE_DATA_STATUS.PARSING);
diff --git a/src/layouts/TabLayout/TabLayout.js b/src/layouts/TabLayout/TabLayout.js
index 8ee54dc86..104b6f6e4 100644
--- a/src/layouts/TabLayout/TabLayout.js
+++ b/src/layouts/TabLayout/TabLayout.js
@@ -6,7 +6,7 @@ import { AppBar, Tabs, Tab, Box, makeStyles } from '@material-ui/core';
import { Switch, Route, Link, Redirect, useLocation } from 'react-router-dom';
import PropTypes from 'prop-types';
import { Auth } from '@cosmotech/core';
-import { PrivateRoute, UserInfo, HelpMenu } from '@cosmotech/ui';
+import { PrivateRoute, UserInfo, HelpMenu, ErrorBanner } from '@cosmotech/ui';
import { useTranslation } from 'react-i18next';
import { LANGUAGES, SUPPORT_URL, DOCUMENTATION_URL } from '../../config/AppConfiguration';
import { About } from '../../services/config/Menu';
@@ -71,7 +71,7 @@ const useStyles = makeStyles((theme) => ({
const TabLayout = (props) => {
const classes = useStyles();
- const { tabs, authenticated, authorized, signInPath, unauthorizedPath } = props;
+ const { tabs, authenticated, authorized, signInPath, unauthorizedPath, error, clearMinorErrors } = props;
const { t, i18n } = useTranslation();
const location = useLocation();
@@ -86,7 +86,6 @@ const TabLayout = (props) => {
aboutTitle: t('genericcomponent.helpmenu.about'),
close: t('genericcomponent.dialog.about.button.close'),
};
-
return (
<>
@@ -131,6 +130,7 @@ const TabLayout = (props) => {
+ {error && }
{tabs.map((tab) => (
({
userId: state.auth.userId,
userName: state.auth.userName,
userProfilePic: state.auth.profilePic || '',
authStatus: state.auth.status,
+ error: state.application.error,
});
-export default connect(mapStateToProps)(TabLayout);
+const mapDispatchToProps = {
+ clearMinorErrors: dispatchClearMinorErrors,
+};
+export default connect(mapStateToProps, mapDispatchToProps)(TabLayout);
diff --git a/src/services/scenarioRun/ScenarioRunService.js b/src/services/scenarioRun/ScenarioRunService.js
index a3d758324..7699bb13e 100644
--- a/src/services/scenarioRun/ScenarioRunService.js
+++ b/src/services/scenarioRun/ScenarioRunService.js
@@ -5,36 +5,30 @@ import { FileBlobUtils } from '@cosmotech/core';
import { LOG_TYPES } from './ScenarioRunConstants.js';
import { ORGANIZATION_ID } from '../../config/AppInstance';
import { Api } from '../../services/config/Api';
+import applicationStore from '../../state/Store.config';
+import { catchNonCriticalErrors } from '../../utils/ApiUtils';
async function downloadCumulatedLogsFile(lastRun) {
try {
const fileName = lastRun.scenarioRunId + '_cumulated_logs.txt';
- const { data, status } = await Api.ScenarioRuns.getScenarioRunCumulatedLogs(
- ORGANIZATION_ID,
- lastRun.scenarioRunId,
- { responseType: 'blob' }
- );
- if (status !== 200) {
- throw new Error(`Error when fetching ${fileName}`);
- }
+ const { data } = await Api.ScenarioRuns.getScenarioRunCumulatedLogs(ORGANIZATION_ID, lastRun.scenarioRunId, {
+ responseType: 'blob',
+ });
FileBlobUtils.downloadFileFromData(data, fileName);
- } catch (e) {
- console.error(e);
+ } catch (error) {
+ applicationStore.dispatch(catchNonCriticalErrors(error, 'Impossible to download logs'));
}
}
async function downloadLogsSimpleFile(lastRun) {
try {
const fileName = lastRun.scenarioRunId + '_simple_logs.json';
- const { data, status } = await Api.ScenarioRuns.getScenarioRunLogs(ORGANIZATION_ID, lastRun.scenarioRunId, {
+ const { data } = await Api.ScenarioRuns.getScenarioRunLogs(ORGANIZATION_ID, lastRun.scenarioRunId, {
responseType: 'blob',
});
- if (status !== 200) {
- throw new Error(`Error when fetching ${fileName}`);
- }
FileBlobUtils.downloadFileFromData(data, fileName);
- } catch (e) {
- console.error(e);
+ } catch (error) {
+ applicationStore.dispatch(catchNonCriticalErrors(error, 'Impossible to download logs'));
}
}
diff --git a/src/state/commons/ApplicationConstants.js b/src/state/commons/ApplicationConstants.js
index d79cd262b..73bccce58 100644
--- a/src/state/commons/ApplicationConstants.js
+++ b/src/state/commons/ApplicationConstants.js
@@ -5,4 +5,6 @@
export const APPLICATION_ACTIONS_KEY = {
SET_APPLICATION_STATUS: 'SET_APPLICATION_STATUS',
GET_ALL_INITIAL_DATA: 'GET_ALL_INITIAL_DATA',
+ GET_NON_CRITICAL_ERRORS: 'GET_NON_CRITICAL_ERRORS',
+ CLEAR_ALL_ERRORS: 'CLEAR_ALL_ERRORS',
};
diff --git a/src/state/dispatchers/app/ApplicationDispatcher.js b/src/state/dispatchers/app/ApplicationDispatcher.js
index b4b9d19dd..a6ad0c9b7 100644
--- a/src/state/dispatchers/app/ApplicationDispatcher.js
+++ b/src/state/dispatchers/app/ApplicationDispatcher.js
@@ -4,6 +4,7 @@
import { APPLICATION_ACTIONS_KEY } from '../../commons/ApplicationConstants';
import { STATUSES } from '../../commons/Constants';
import { WORKSPACE_ID } from '../../../config/AppInstance';
+import { catchNonCriticalErrors } from '../../../utils/ApiUtils';
export const dispatchSetApplicationStatus = (payLoad) => ({
type: APPLICATION_ACTIONS_KEY.SET_APPLICATION_STATUS,
@@ -15,3 +16,12 @@ export const dispatchGetAllInitialData = () => ({
status: STATUSES.LOADING,
workspaceId: WORKSPACE_ID,
});
+
+export const dispatchClearMinorErrors = () => ({
+ type: APPLICATION_ACTIONS_KEY.CLEAR_ALL_ERRORS,
+ error: null,
+});
+
+export const dispatchCatchNonCriticalErrors = (error, commentOnAppBehaviour) => {
+ return catchNonCriticalErrors(error, commentOnAppBehaviour);
+};
diff --git a/src/state/reducers/app/ApplicationReducer.js b/src/state/reducers/app/ApplicationReducer.js
index e6d7a5218..1f64ab566 100644
--- a/src/state/reducers/app/ApplicationReducer.js
+++ b/src/state/reducers/app/ApplicationReducer.js
@@ -11,14 +11,21 @@ export const applicationInitialState = {
};
export const applicationReducer = createReducer(applicationInitialState, (builder) => {
- builder.addCase(APPLICATION_ACTIONS_KEY.SET_APPLICATION_STATUS, (state, action) => {
- state.status = action.status;
- if (state.status === STATUSES.ERROR) {
- if (action.error) {
- state.error = action.error;
- } else {
- state.error = { title: 'Unknown error', status: null, detail: 'Something went wrong' };
+ builder
+ .addCase(APPLICATION_ACTIONS_KEY.GET_NON_CRITICAL_ERRORS, (state, action) => {
+ state.error = action.error;
+ })
+ .addCase(APPLICATION_ACTIONS_KEY.CLEAR_ALL_ERRORS, (state) => {
+ state.error = null;
+ })
+ .addCase(APPLICATION_ACTIONS_KEY.SET_APPLICATION_STATUS, (state, action) => {
+ state.status = action.status;
+ if (state.status === STATUSES.ERROR) {
+ if (action.error) {
+ state.error = action.error;
+ } else {
+ state.error = { title: 'Unknown error', status: null, detail: 'Something went wrong' };
+ }
}
- }
- });
+ });
});
diff --git a/src/state/sagas/scenario/CreateScenario/CreateScenarioData.js b/src/state/sagas/scenario/CreateScenario/CreateScenarioData.js
index f5c01fb3d..b102be12f 100644
--- a/src/state/sagas/scenario/CreateScenario/CreateScenarioData.js
+++ b/src/state/sagas/scenario/CreateScenario/CreateScenarioData.js
@@ -7,7 +7,7 @@ import { STATUSES } from '../../../commons/Constants';
import { ORGANIZATION_ID } from '../../../../config/AppInstance';
import { getAllScenariosData } from '../FindAllScenarios/FindAllScenariosData';
import { Api } from '../../../../services/config/Api';
-import { formatParametersFromApi } from '../../../../utils/ApiUtils';
+import { formatParametersFromApi, catchNonCriticalErrors } from '../../../../utils/ApiUtils';
import { AppInsights } from '../../../../services/AppInsights';
const appInsights = AppInsights.getInstance();
@@ -28,8 +28,9 @@ export function* createScenario(action) {
status: STATUSES.SUCCESS,
scenario: data,
});
- } catch (e) {
+ } catch (error) {
// TODO handle error management
+ yield put(catchNonCriticalErrors(error, 'Scenario not created'));
yield put({
type: SCENARIO_ACTIONS_KEY.SET_CURRENT_SCENARIO,
status: STATUSES.ERROR,
diff --git a/src/state/sagas/scenario/DeleteScenario/DeleteScenario.js b/src/state/sagas/scenario/DeleteScenario/DeleteScenario.js
index 31b682d6b..6164c7515 100644
--- a/src/state/sagas/scenario/DeleteScenario/DeleteScenario.js
+++ b/src/state/sagas/scenario/DeleteScenario/DeleteScenario.js
@@ -1,19 +1,20 @@
// Copyright (c) Cosmo Tech.
// Licensed under the MIT license.
-import { takeEvery, call } from 'redux-saga/effects';
+import { takeEvery, call, put } from 'redux-saga/effects';
import { SCENARIO_ACTIONS_KEY } from '../../../commons/ScenarioConstants';
import { ORGANIZATION_ID } from '../../../../config/AppInstance';
import { getAllScenariosData } from '../FindAllScenarios/FindAllScenariosData';
import { Api } from '../../../../services/config/Api';
+import { catchNonCriticalErrors } from '../../../../utils/ApiUtils';
export function* deleteScenario(action) {
try {
const workspaceId = action.workspaceId;
yield call(Api.Scenarios.deleteScenario, ORGANIZATION_ID, workspaceId, action.scenarioId);
yield call(getAllScenariosData, workspaceId);
- } catch (e) {
- console.error(e);
+ } catch (error) {
+ yield put(catchNonCriticalErrors(error, 'Scenario not deleted'));
}
}
diff --git a/src/state/sagas/scenario/LaunchScenario/LaunchScenario.js b/src/state/sagas/scenario/LaunchScenario/LaunchScenario.js
index 40d6725a2..51676e9a2 100644
--- a/src/state/sagas/scenario/LaunchScenario/LaunchScenario.js
+++ b/src/state/sagas/scenario/LaunchScenario/LaunchScenario.js
@@ -8,6 +8,7 @@ import { SCENARIO_RUN_STATE } from '../../../../services/config/ApiConstants';
import { ORGANIZATION_ID } from '../../../../config/AppInstance';
import { Api } from '../../../../services/config/Api';
import { AppInsights } from '../../../../services/AppInsights';
+import { catchNonCriticalErrors } from '../../../../utils/ApiUtils';
const appInsights = AppInsights.getInstance();
@@ -25,14 +26,14 @@ export function* launchScenario(action) {
status: STATUSES.SAVING,
scenario: { state: SCENARIO_RUN_STATE.RUNNING },
});
+
+ // Launch scenario if parameters update succeeded
+ yield call(Api.ScenarioRuns.runScenario, ORGANIZATION_ID, workspaceId, scenarioId);
yield put({
type: SCENARIO_ACTIONS_KEY.UPDATE_SCENARIO,
data: { scenarioState: SCENARIO_RUN_STATE.RUNNING, scenarioId: scenarioId, lastRun: null },
});
- // Launch scenario if parameters update succeeded
- yield call(Api.ScenarioRuns.runScenario, ORGANIZATION_ID, workspaceId, scenarioId);
-
// Start backend polling to update the scenario status
yield put({
type: SCENARIO_ACTIONS_KEY.START_SCENARIO_STATUS_POLLING,
@@ -40,8 +41,13 @@ export function* launchScenario(action) {
scenarioId: scenarioId,
startTime: runStartTime,
});
- } catch (e) {
- console.error(e);
+ } catch (error) {
+ yield put(catchNonCriticalErrors(error, 'Problem during scenario run'));
+ yield put({
+ type: SCENARIO_ACTIONS_KEY.SET_CURRENT_SCENARIO,
+ status: STATUSES.ERROR,
+ scenario: { state: 'Failed' },
+ });
}
}
diff --git a/src/state/sagas/scenario/PollScenarioState/PollScenarioState.js b/src/state/sagas/scenario/PollScenarioState/PollScenarioState.js
index fb53413be..91c363056 100644
--- a/src/state/sagas/scenario/PollScenarioState/PollScenarioState.js
+++ b/src/state/sagas/scenario/PollScenarioState/PollScenarioState.js
@@ -7,6 +7,8 @@ import { ORGANIZATION_ID } from '../../../../config/AppInstance';
import { Api } from '../../../../services/config/Api';
import { SCENARIO_STATUS_POLLING_DELAY } from '../../../../config/AppConfiguration';
import { AppInsights } from '../../../../services/AppInsights';
+import { catchNonCriticalErrors } from '../../../../utils/ApiUtils';
+import { STATUSES } from '../../../commons/Constants';
const appInsights = AppInsights.getInstance();
@@ -50,8 +52,13 @@ export function* pollScenarioState(action) {
}
// Wait before retrying
yield delay(SCENARIO_STATUS_POLLING_DELAY);
- } catch (err) {
- console.error(err);
+ } catch (error) {
+ yield put(catchNonCriticalErrors(error, 'Problem during scenario run'));
+ yield put({
+ type: SCENARIO_ACTIONS_KEY.SET_CURRENT_SCENARIO,
+ status: STATUSES.ERROR,
+ scenario: { state: 'Failed' },
+ });
// Stop the polling for this scenario
yield put(forgeStopPollingAction(action.scenarioId));
}
diff --git a/src/state/sagas/scenario/UpdateAndLaunchScenario/UpdateAndLaunchScenario.js b/src/state/sagas/scenario/UpdateAndLaunchScenario/UpdateAndLaunchScenario.js
index c0e121ccb..be6027f84 100644
--- a/src/state/sagas/scenario/UpdateAndLaunchScenario/UpdateAndLaunchScenario.js
+++ b/src/state/sagas/scenario/UpdateAndLaunchScenario/UpdateAndLaunchScenario.js
@@ -4,7 +4,7 @@
import { takeEvery, call, put } from 'redux-saga/effects';
import { SCENARIO_ACTIONS_KEY } from '../../../commons/ScenarioConstants';
import { STATUSES } from '../../../commons/Constants';
-import { formatParametersForApi, formatParametersFromApi } from '../../../../utils/ApiUtils';
+import { catchNonCriticalErrors, formatParametersForApi, formatParametersFromApi } from '../../../../utils/ApiUtils';
import { SCENARIO_RUN_STATE } from '../../../../services/config/ApiConstants';
import { ORGANIZATION_ID } from '../../../../config/AppInstance';
import { Api } from '../../../../services/config/Api';
@@ -17,11 +17,11 @@ export function* updateAndLaunchScenario(action) {
const workspaceId = action.workspaceId;
const scenarioId = action.scenarioId;
const scenarioParameters = action.scenarioParameters;
+ const runStartTime = new Date().getTime();
try {
appInsights.trackScenarioLaunch();
// Update scenario parameters
- const runStartTime = new Date().getTime();
yield put({
type: SCENARIO_ACTIONS_KEY.SET_CURRENT_SCENARIO,
status: STATUSES.SAVING,
@@ -30,10 +30,6 @@ export function* updateAndLaunchScenario(action) {
parametersValues: scenarioParameters,
},
});
- yield put({
- type: SCENARIO_ACTIONS_KEY.UPDATE_SCENARIO,
- data: { scenarioState: SCENARIO_RUN_STATE.RUNNING, scenarioId: scenarioId, lastRun: null },
- });
const { data: updateData } = yield call(
Api.Scenarios.updateScenario,
ORGANIZATION_ID,
@@ -41,17 +37,30 @@ export function* updateAndLaunchScenario(action) {
scenarioId,
formatParametersForApi(scenarioParameters)
);
-
updateData.parametersValues = formatParametersFromApi(updateData.parametersValues);
-
yield put({
type: SCENARIO_ACTIONS_KEY.SET_CURRENT_SCENARIO,
status: STATUSES.IDLE,
scenario: { state: SCENARIO_RUN_STATE.RUNNING, parametersValues: updateData.parametersValues },
});
+ } catch (error) {
+ yield put(catchNonCriticalErrors(error, "Problem during scenario update : your new parameters aren't saved"));
+ yield put({
+ type: SCENARIO_ACTIONS_KEY.SET_CURRENT_SCENARIO,
+ status: STATUSES.ERROR,
+ scenario: { state: 'Failed' },
+ });
+ return;
+ }
+ try {
// Launch scenario if parameters update succeeded
yield call(Api.ScenarioRuns.runScenario, ORGANIZATION_ID, workspaceId, scenarioId);
+ yield put({
+ type: SCENARIO_ACTIONS_KEY.UPDATE_SCENARIO,
+ data: { scenarioState: SCENARIO_RUN_STATE.RUNNING, scenarioId: scenarioId, lastRun: null },
+ });
+
// Start backend polling to update the scenario status
yield put({
type: SCENARIO_ACTIONS_KEY.START_SCENARIO_STATUS_POLLING,
@@ -59,12 +68,12 @@ export function* updateAndLaunchScenario(action) {
scenarioId: scenarioId,
startTime: runStartTime,
});
- } catch (e) {
- console.error(e);
+ } catch (error) {
+ yield put(catchNonCriticalErrors(error, 'Problem during scenario run'));
yield put({
type: SCENARIO_ACTIONS_KEY.SET_CURRENT_SCENARIO,
status: STATUSES.ERROR,
- scenario: null,
+ scenario: { state: 'Failed' },
});
}
}
diff --git a/src/utils/ApiUtils.js b/src/utils/ApiUtils.js
index 761236ee0..4e51936bb 100644
--- a/src/utils/ApiUtils.js
+++ b/src/utils/ApiUtils.js
@@ -6,6 +6,7 @@ import { VAR_TYPES_TO_STRING_FUNCTIONS } from './scenarioParameters/ConversionTo
import { VAR_TYPES_FROM_STRING_FUNCTIONS } from './scenarioParameters/ConversionFromString.js';
import { ConfigUtils } from './ConfigUtils';
import { SCENARIO_PARAMETERS_CONFIG } from '../config/ScenarioParameters';
+import { APPLICATION_ACTIONS_KEY } from '../state/commons/ApplicationConstants';
const clone = rfdc();
@@ -37,3 +38,19 @@ export function formatParametersFromApi(parameters) {
const newParams = _formatParameters(parameters, VAR_TYPES_FROM_STRING_FUNCTIONS);
return newParams;
}
+
+// Catch non-critical errors to display in error banner
+export function catchNonCriticalErrors(error, commentOnAppBehaviour) {
+ return {
+ type: APPLICATION_ACTIONS_KEY.GET_NON_CRITICAL_ERRORS,
+ error: {
+ title:
+ error.response?.message || error.response?.data?.title || navigator.onLine
+ ? 'Unknown error'
+ : 'Network problem, please check your internet connexion',
+ detail: error.response?.data?.detail || '',
+ status: error.response?.data?.status || '',
+ comment: commentOnAppBehaviour,
+ },
+ };
+}
diff --git a/src/views/Scenario/Scenario.js b/src/views/Scenario/Scenario.js
index 8410e8e9f..b187b268c 100644
--- a/src/views/Scenario/Scenario.js
+++ b/src/views/Scenario/Scenario.js
@@ -39,6 +39,7 @@ const Scenario = (props) => {
const {
setScenarioValidationStatus,
+ setCurrentScenario,
currentScenario,
scenarioList,
findScenarioById,
@@ -51,6 +52,7 @@ const Scenario = (props) => {
updateAndLaunchScenario,
launchScenario,
reports,
+ catchNonCriticalErrors,
} = props;
const workspaceId = workspace.data.id;
@@ -68,6 +70,11 @@ const Scenario = (props) => {
localStorage.setItem('scenarioParametersAccordionExpanded', accordionSummaryExpanded);
}, [accordionSummaryExpanded]);
+ useEffect(() => {
+ if (currentScenario.data === null && sortedScenarioList.length > 0) {
+ setCurrentScenario(sortedScenarioList[0]);
+ }
+ });
const expandParametersAndCreateScenario = (workspaceId, scenarioData) => {
createScenario(workspaceId, scenarioData);
setAccordionSummaryExpanded(true);
@@ -103,19 +110,38 @@ const Scenario = (props) => {
}
const resetScenarioValidationStatus = async () => {
- setScenarioValidationStatus(currentScenario.data.id, SCENARIO_VALIDATION_STATUS.LOADING);
- await ScenarioService.resetValidationStatus(workspaceId, currentScenario.data.id);
- findScenarioById(workspaceId, currentScenario.data.id);
+ const currentStatus = currentScenario.data.validationStatus;
+ try {
+ setScenarioValidationStatus(currentScenario.data.id, SCENARIO_VALIDATION_STATUS.LOADING);
+ await ScenarioService.resetValidationStatus(workspaceId, currentScenario.data.id);
+ findScenarioById(workspaceId, currentScenario.data.id);
+ } catch (error) {
+ catchNonCriticalErrors(error, 'Impossible to reset validation');
+ setScenarioValidationStatus(
+ currentScenario.data.id,
+ currentStatus === 'Validated' ? SCENARIO_VALIDATION_STATUS.VALIDATED : SCENARIO_VALIDATION_STATUS.REJECTED
+ );
+ }
};
const validateScenario = async () => {
- setScenarioValidationStatus(currentScenario.data.id, SCENARIO_VALIDATION_STATUS.LOADING);
- await ScenarioService.setScenarioValidationStatusToValidated(workspaceId, currentScenario.data.id);
- findScenarioById(workspaceId, currentScenario.data.id);
+ try {
+ setScenarioValidationStatus(currentScenario.data.id, SCENARIO_VALIDATION_STATUS.LOADING);
+ await ScenarioService.setScenarioValidationStatusToValidated(workspaceId, currentScenario.data.id);
+ findScenarioById(workspaceId, currentScenario.data.id);
+ } catch (error) {
+ catchNonCriticalErrors(error, 'Impossible to validate the scenario');
+ setScenarioValidationStatus(currentScenario.data.id, SCENARIO_VALIDATION_STATUS.DRAFT);
+ }
};
const rejectScenario = async () => {
- setScenarioValidationStatus(currentScenario.data.id, SCENARIO_VALIDATION_STATUS.LOADING);
- await ScenarioService.setScenarioValidationStatusToRejected(workspaceId, currentScenario.data.id);
- findScenarioById(workspaceId, currentScenario.data.id);
+ try {
+ setScenarioValidationStatus(currentScenario.data.id, SCENARIO_VALIDATION_STATUS.LOADING);
+ await ScenarioService.setScenarioValidationStatusToRejected(workspaceId, currentScenario.data.id);
+ findScenarioById(workspaceId, currentScenario.data.id);
+ } catch (error) {
+ catchNonCriticalErrors(error, 'Impossible to reject the scenario');
+ setScenarioValidationStatus(currentScenario.data.id, SCENARIO_VALIDATION_STATUS.DRAFT);
+ }
};
const scenarioValidationStatusLabels = {
@@ -306,6 +332,7 @@ const Scenario = (props) => {
Scenario.propTypes = {
setScenarioValidationStatus: PropTypes.func.isRequired,
+ setCurrentScenario: PropTypes.func.isRequired,
scenarioList: PropTypes.object.isRequired,
datasetList: PropTypes.object.isRequired,
currentScenario: PropTypes.object.isRequired,
@@ -318,6 +345,7 @@ Scenario.propTypes = {
updateAndLaunchScenario: PropTypes.func.isRequired,
launchScenario: PropTypes.func.isRequired,
reports: PropTypes.object.isRequired,
+ catchNonCriticalErrors: PropTypes.func,
};
export default Scenario;
diff --git a/src/views/Scenario/index.js b/src/views/Scenario/index.js
index df5482ebf..ab52ab18f 100644
--- a/src/views/Scenario/index.js
+++ b/src/views/Scenario/index.js
@@ -8,8 +8,10 @@ import {
dispatchUpdateAndLaunchScenario,
dispatchLaunchScenario,
dispatchSetScenarioValidationStatus,
+ dispatchSetCurrentScenario,
} from '../../state/dispatchers/scenario/ScenarioDispatcher';
import { dispatchAddDatasetToStore } from '../../state/dispatchers/dataset/DatasetDispatcher';
+import { dispatchCatchNonCriticalErrors } from '../../state/dispatchers/app/ApplicationDispatcher';
const mapStateToProps = (state) => ({
scenarioList: state.scenario.list,
@@ -28,6 +30,8 @@ const mapDispatchToProps = {
createScenario: dispatchCreateScenario,
updateAndLaunchScenario: dispatchUpdateAndLaunchScenario,
launchScenario: dispatchLaunchScenario,
+ setCurrentScenario: dispatchSetCurrentScenario,
+ catchNonCriticalErrors: dispatchCatchNonCriticalErrors,
};
export default connect(mapStateToProps, mapDispatchToProps)(Scenario);