diff --git a/cypress/commons/actions/generic/Dashboards.js b/cypress/commons/actions/generic/Dashboards.js new file mode 100644 index 000000000..914e122c5 --- /dev/null +++ b/cypress/commons/actions/generic/Dashboards.js @@ -0,0 +1,7 @@ +// Copyright (c) Cosmo Tech. +// Licensed under the MIT license. +import { GENERIC_SELECTORS } from '../../constants/generic/IdConstants'; + +export const getDashboardsTab = (timeout = 5) => { + return cy.get(GENERIC_SELECTORS.dashboards.tabName, { timeout: timeout * 1000 }); +}; diff --git a/cypress/commons/constants/generic/IdConstants.js b/cypress/commons/constants/generic/IdConstants.js index e044b30a0..8a667c911 100644 --- a/cypress/commons/constants/generic/IdConstants.js +++ b/cypress/commons/constants/generic/IdConstants.js @@ -54,6 +54,9 @@ export const GENERIC_SELECTORS = { cytovizElementAttributeName: '[data-cy="cytoviz-element-attribute-name"]', cytovizElementAttributeValue: '[data-cy="cytoviz-element-attribute-value"]', }, + dashboards: { + tabName: '[data-cy="tabs.dashboards.key"]', + }, scenario: { view: '[data-cy=scenario-view]', tabName: '[data-cy="tabs.scenario.key"]', diff --git a/cypress/commons/utils/apiUtils.js b/cypress/commons/utils/apiUtils.js index 30bdcfeed..0243b8a98 100644 --- a/cypress/commons/utils/apiUtils.js +++ b/cypress/commons/utils/apiUtils.js @@ -742,8 +742,10 @@ const interceptWorkspaceSelectorQueries = () => { ]; }; -const interceptSelectWorkspaceQueries = () => { - return [interceptPowerBIAzureFunction(), interceptGetSolution(), interceptGetScenarios()]; +const interceptSelectWorkspaceQueries = (isPowerBiEnabled = true) => { + const workspaceQueries = [interceptGetSolution(), interceptGetScenarios()]; + if (isPowerBiEnabled) workspaceQueries.push(interceptPowerBIAzureFunction()); + return workspaceQueries; }; export const apiUtils = { diff --git a/cypress/commons/utils/routeUtils.js b/cypress/commons/utils/routeUtils.js index 811de8cdb..d7af8c1ed 100644 --- a/cypress/commons/utils/routeUtils.js +++ b/cypress/commons/utils/routeUtils.js @@ -16,9 +16,9 @@ const _navigateTo = (url) => { }); }; -const getBrowseQueries = (workspaceId, scenarioId) => { +const getBrowseQueries = (workspaceId, scenarioId, isPowerBiEnabled) => { const queries = api.interceptWorkspaceSelectorQueries(); - if (workspaceId) queries.push(...api.interceptSelectWorkspaceQueries()); + if (workspaceId) queries.push(...api.interceptSelectWorkspaceQueries(isPowerBiEnabled)); if (scenarioId) queries.push(api.interceptGetScenario(scenarioId)); return queries; }; @@ -55,7 +55,7 @@ const browse = (options) => { : options.url?.match(WEBAPP_URL_REGEX.SCENARIO_ID_PATTERN)?.[1]; // Intercept scenario only when explicitly specified in options (part 2 of WorkingInTwoWorkspaces) - const queries = getBrowseQueries(workspaceId, options.scenarioId); + const queries = getBrowseQueries(workspaceId, options.scenarioId, options.isPowerBiEnabled); _navigateTo(options.url); if (options.onBrowseCallback) options.onBrowseCallback(); waitBrowseQueries(queries); diff --git a/cypress/e2e/brewery/ResultsDashboards-DisplayDisabled.cy.js b/cypress/e2e/brewery/ResultsDashboards-DisplayDisabled.cy.js new file mode 100644 index 000000000..1a96e3e83 --- /dev/null +++ b/cypress/e2e/brewery/ResultsDashboards-DisplayDisabled.cy.js @@ -0,0 +1,45 @@ +// Copyright (c) Cosmo Tech. +// Licensed under the MIT license. +import { Login, ScenarioParameters, Scenarios } from '../../commons/actions'; +import { getDashboardsTab } from '../../commons/actions/generic/Dashboards'; +import { stub } from '../../commons/services/stubbing'; +import { WORKSPACE_WITHOUT_DASHBOARDS } from '../../fixtures/stubbing/ScenarioViewDashboard/workspace'; +import { DEFAULT_SCENARIOS_LIST } from '../../fixtures/stubbing/default'; + +const { id: scenarioId } = DEFAULT_SCENARIOS_LIST[0]; +const runOptions = { + runDuration: 1000, + dataIngestionDuration: 1000, + finalStatus: 'Successful', + expectedPollsCount: 2, +}; + +describe('results display is disabled', () => { + before(() => { + stub.start(); + stub.setWorkspaces([WORKSPACE_WITHOUT_DASHBOARDS]); + }); + + beforeEach(() => { + Login.login({ + isPowerBiEnabled: false, + }); + }); + + after(() => { + stub.stop(); + }); + + it('can launch a scenario and show Display of results is disabled dashboard', () => { + getDashboardsTab().should('not.exist'); + ScenarioParameters.launch({ scenarioId, runOptions, saveAndLaunch: true }); + ScenarioParameters.waitForScenarioRunEnd(); + Scenarios.getDashboardAccordionLogsDownloadButton().should('be.visible'); + Scenarios.getDashboardAccordion().click(); + Scenarios.getDashboardPlaceholder().should('be.visible'); + Scenarios.getDashboardPlaceholder().should( + 'have.text', + 'Scenario run was successful but the display of results is disabled' + ); + }); +}); diff --git a/cypress/e2e/brewery/Router-RedirectionFromDisabledView.cy.js b/cypress/e2e/brewery/Router-RedirectionFromDisabledView.cy.js new file mode 100644 index 000000000..687e83233 --- /dev/null +++ b/cypress/e2e/brewery/Router-RedirectionFromDisabledView.cy.js @@ -0,0 +1,59 @@ +// Copyright (c) Cosmo Tech. +// Licensed under the MIT license. +import { DatasetManager, InstanceVisualization, Login } from '../../commons/actions'; +import { getDashboardsTab } from '../../commons/actions/generic/Dashboards'; +import { stub } from '../../commons/services/stubbing'; +import { routeUtils as route } from '../../commons/utils'; +import { + WORKSPACE_LIST_WITHOUT_DASHBOARDS, + WORKSPACE_WITHOUT_DASHBOARDS, +} from '../../fixtures/stubbing/ScenarioViewDashboard/workspace'; +import { DEFAULT_SCENARIOS_LIST } from '../../fixtures/stubbing/default'; + +const scenarioId = DEFAULT_SCENARIOS_LIST[0].id; + +describe('redirection from disabled view', () => { + before(() => { + stub.start(); + stub.setWorkspaces(WORKSPACE_LIST_WITHOUT_DASHBOARDS); + }); + + beforeEach(() => { + Login.login({ + url: `/${WORKSPACE_WITHOUT_DASHBOARDS.id}`, + workspaceId: WORKSPACE_WITHOUT_DASHBOARDS.id, + isPowerBiEnabled: false, + }); + }); + + after(() => { + stub.stop(); + }); + it('can redirect from disabled Instance view to scenario view', () => { + route.browse({ + url: `/${WORKSPACE_WITHOUT_DASHBOARDS.id}/instance`, + workspaceId: WORKSPACE_WITHOUT_DASHBOARDS.id, + isPowerBiEnabled: false, + expectedURL: `${WORKSPACE_WITHOUT_DASHBOARDS.id}/scenario/${scenarioId}`, + }); + InstanceVisualization.getInstanceVisualizationViewTab().should('not.exist'); + }); + it('can redirect from disabled Dataset manager view to scenario view', () => { + route.browse({ + url: `/${WORKSPACE_WITHOUT_DASHBOARDS.id}/datasetmanager`, + workspaceId: WORKSPACE_WITHOUT_DASHBOARDS.id, + isPowerBiEnabled: false, + expectedURL: `${WORKSPACE_WITHOUT_DASHBOARDS.id}/scenario/${scenarioId}`, + }); + DatasetManager.getDatasetManagerTab().should('not.exist'); + }); + it('can redirect from dashboards to Scenario view when results display is disabled', () => { + route.browse({ + url: `${WORKSPACE_WITHOUT_DASHBOARDS.id}/dashboards`, + workspaceId: WORKSPACE_WITHOUT_DASHBOARDS.id, + expectedURL: `${WORKSPACE_WITHOUT_DASHBOARDS.id}/scenario/${scenarioId}`, + isPowerBiEnabled: false, + }); + getDashboardsTab().should('not.exist'); + }); +}); diff --git a/cypress/e2e/brewery/ScenarioViewDashboard.cy.js b/cypress/e2e/brewery/ScenarioViewDashboard.cy.js index 67f07df98..37c0bdb3f 100644 --- a/cypress/e2e/brewery/ScenarioViewDashboard.cy.js +++ b/cypress/e2e/brewery/ScenarioViewDashboard.cy.js @@ -2,6 +2,7 @@ // Licensed under the MIT license. import { Login, Scenarios, ScenarioParameters } from '../../commons/actions'; import { BreweryParameters } from '../../commons/actions/brewery'; +import { getDashboardsTab } from '../../commons/actions/generic/Dashboards'; import { stub } from '../../commons/services/stubbing'; import { DEFAULT_SCENARIOS_LIST } from '../../fixtures/stubbing/default'; @@ -28,6 +29,9 @@ describe('Scenario view PowerBI report', () => { }; it('can correctly show "out of sync" dashboard warning & "logs download" button', () => { + // check Dashboard view tab to ensure dashboard config exists + getDashboardsTab().should('be.visible'); + ScenarioParameters.expandParametersAccordion(); // First phase: warning is never visible until we first launch the scenario diff --git a/cypress/fixtures/stubbing/ScenarioViewDashboard/workspace.js b/cypress/fixtures/stubbing/ScenarioViewDashboard/workspace.js new file mode 100644 index 000000000..948372097 --- /dev/null +++ b/cypress/fixtures/stubbing/ScenarioViewDashboard/workspace.js @@ -0,0 +1,16 @@ +// Copyright (c) Cosmo Tech. +// Licensed under the MIT license. +import { WORKSPACE_EXAMPLE } from '../default'; + +export const WORKSPACE_WITHOUT_DASHBOARDS = { + ...WORKSPACE_EXAMPLE, + id: 'w-stbwrknodsh', + key: 'breweryNoDashboards', + webApp: { + url: null, + iframes: null, + options: null, + }, +}; + +export const WORKSPACE_LIST_WITHOUT_DASHBOARDS = [WORKSPACE_EXAMPLE, WORKSPACE_WITHOUT_DASHBOARDS]; diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index f1e909a79..8f1e01737 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -298,6 +298,7 @@ "unknownStatus": { "label": "This scenario has an unknown state, if the problem persists, please, contact your administrator" }, + "resultsDisplayDisabled": "Scenario run was successful but the display of results is disabled", "error": { "unknown": { "details": "Something went wrong when fetching PowerBI reports info", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 476bff2fb..5a533a3dd 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -298,6 +298,7 @@ "unknownStatus": { "label": "Le scénario a un statut inconnu, si le problème persiste, merci de contacter votre administrateur" }, + "resultsDisplayDisabled": "La simulation s'est terminée avec succès mais l'affichage des résultats est désactivé", "error": { "unknown": { "details": "Une erreur est survenue lors de la récupération des informations des rapports PowerBI", diff --git a/src/AppLayout.js b/src/AppLayout.js index 38a6f97fd..0ace5f1d1 100644 --- a/src/AppLayout.js +++ b/src/AppLayout.js @@ -44,11 +44,13 @@ export const getTabsForCurrentWorkspace = (currentWorkspaceData) => { export const filterTabsForCurrentWorkspace = (tabs, currentWorkspaceData) => { const hideInstanceView = !ConfigUtils.isInstanceViewConfigValid(currentWorkspaceData?.webApp?.options?.instanceView); const hideDatasetManager = !ConfigUtils.isDatasetManagerEnabledInWorkspace(currentWorkspaceData); + const hideDashboardsView = !ConfigUtils.isResultsDisplayEnabledInWorkspace(currentWorkspaceData); return tabs.filter((tab) => { if ( (hideInstanceView && tab.key === 'tabs.instance.key') || - (hideDatasetManager && tab.key === 'tabs.datasetmanager.key') + (hideDatasetManager && tab.key === 'tabs.datasetmanager.key') || + (hideDashboardsView && tab.key === 'tabs.dashboards.key') ) return false; return true; diff --git a/src/components/CurrentScenarioPowerBIReport/labels.js b/src/components/CurrentScenarioPowerBIReport/labels.js index 1384bd726..0e1fd8c7c 100644 --- a/src/components/CurrentScenarioPowerBIReport/labels.js +++ b/src/components/CurrentScenarioPowerBIReport/labels.js @@ -36,6 +36,10 @@ export const getReportLabels = (t) => ({ 'This scenario has an unknown state, if the problem persists, please, contact your administrator' ), }, + resultsDisplayDisabled: t( + 'commoncomponents.iframe.scenario.resultsDisplayDisabled', + 'Scenario run was successful but the display of results is disabled' + ), downloadButton: t('commoncomponents.iframe.scenario.results.button.downloadLogs', 'Download logs'), refreshTooltip: t('commoncomponents.iframe.scenario.results.button.refresh', 'Refresh'), errors: { diff --git a/src/hooks/RouterHooks.js b/src/hooks/RouterHooks.js index 0e98fa118..d6d2baf9e 100644 --- a/src/hooks/RouterHooks.js +++ b/src/hooks/RouterHooks.js @@ -8,44 +8,45 @@ import { useWorkspaceData } from '../state/hooks/WorkspaceHooks'; import { ConfigUtils } from '../utils'; import { useSortedScenarioList } from './ScenarioListHooks'; -export const useRedirectFromInstanceToScenarioView = () => { +export const useRedirectFromDisabledView = (view) => { const isUnmounted = useRef(false); const currentWorkspaceData = useWorkspaceData(); const navigate = useNavigate(); + const routerParameters = useParams(); useEffect(() => { if (isUnmounted.current) { return; } - const isInstanceViewEnabled = ConfigUtils.isInstanceViewConfigValid( - currentWorkspaceData?.webApp?.options?.instanceView - ); - if (isInstanceViewEnabled) return; - navigate('/workspaces'); - return () => { - isUnmounted.current = true; - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentWorkspaceData?.webApp?.options?.instanceView]); -}; - -export const useRedirectFromDatasetManagerToScenarioView = () => { - const isUnmounted = useRef(false); - const currentWorkspaceData = useWorkspaceData(); - const navigate = useNavigate(); - - useEffect(() => { - if (isUnmounted.current) { - return; + let isViewEnabled; + switch (view) { + case 'instance': + isViewEnabled = ConfigUtils.isInstanceViewConfigValid(currentWorkspaceData?.webApp?.options?.instanceView); + break; + case 'datasetManager': + isViewEnabled = ConfigUtils.isDatasetManagerEnabledInWorkspace(currentWorkspaceData); + break; + case 'dashboards': + isViewEnabled = ConfigUtils.isResultsDisplayEnabledInWorkspace(currentWorkspaceData); + break; + default: + isViewEnabled = true; } - const isDatasetManagerViewEnabled = ConfigUtils.isDatasetManagerEnabledInWorkspace(currentWorkspaceData); - if (isDatasetManagerViewEnabled) return; - navigate('/workspaces'); + if (isViewEnabled) return; + const workspaceId = routerParameters.workspaceId; + navigate(workspaceId ? `/${workspaceId}` : '/workspaces'); return () => { isUnmounted.current = true; }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentWorkspaceData?.webApp?.options?.datasetmanager, navigate]); + }, [ + currentWorkspaceData?.webApp?.options?.charts, + currentWorkspaceData?.webApp?.options?.instanceView, + currentWorkspaceData?.webApp?.options?.datasetmanager, + navigate, + view, + routerParameters.workspaceId, + currentWorkspaceData, + ]); }; export const useRouterScenarioId = () => { diff --git a/src/layouts/TabLayout/TabLayout.js b/src/layouts/TabLayout/TabLayout.js index 57437d402..7bffc6949 100644 --- a/src/layouts/TabLayout/TabLayout.js +++ b/src/layouts/TabLayout/TabLayout.js @@ -66,7 +66,11 @@ export const TabLayout = (props) => { const shouldRedirectFromDatasetManager = currentTabPathname.startsWith(`/${routerParameters.workspaceId}/datasetmanager`) && !ConfigUtils.isDatasetManagerEnabledInWorkspace(currentWorkspace?.data); - const shouldRedirect = shouldRedirectFromInstanceView || shouldRedirectFromDatasetManager; + const shouldRedirectFromDashboardsView = + currentTabPathname.startsWith(`/${routerParameters.workspaceId}/dashboards`) && + !ConfigUtils.isResultsDisplayEnabledInWorkspace(currentWorkspace?.data); + const shouldRedirect = + shouldRedirectFromInstanceView || shouldRedirectFromDatasetManager || shouldRedirectFromDashboardsView; const tabValue = shouldRedirect ? `/${routerParameters.workspaceId}/scenario` : currentTabPathname; const viewTabs = ( diff --git a/src/state/commons/Constants.js b/src/state/commons/Constants.js index 5c3130b78..f1bdff07c 100644 --- a/src/state/commons/Constants.js +++ b/src/state/commons/Constants.js @@ -9,4 +9,5 @@ export const STATUSES = { ERROR: 'ERROR', SUCCESS: 'SUCCESS', CREATED: 'CREATED', + DISABLED: 'DISABLED', }; diff --git a/src/state/sagas/powerbi/GetPowerBIEmbedInfo/GetPowerBIEmbedInfoData.js b/src/state/sagas/powerbi/GetPowerBIEmbedInfo/GetPowerBIEmbedInfoData.js index c3d91c4dc..899d70441 100644 --- a/src/state/sagas/powerbi/GetPowerBIEmbedInfo/GetPowerBIEmbedInfoData.js +++ b/src/state/sagas/powerbi/GetPowerBIEmbedInfo/GetPowerBIEmbedInfoData.js @@ -3,7 +3,6 @@ import { delay, put, select, takeLatest } from 'redux-saga/effects'; import { POWER_BI_INFO_POLLING_DELAY } from '../../../../services/config/FunctionalConstants'; import { PowerBIService } from '../../../../services/powerbi/PowerBIService'; -import { forgePowerBIError } from '../../../../services/powerbi/errors'; import { PowerBIUtils } from '../../../../utils'; import { STATUSES } from '../../../commons/Constants'; import { POWER_BI_ACTIONS_KEY } from '../../../commons/PowerBIConstants'; @@ -27,16 +26,15 @@ export function* getPowerBIEmbedInfoSaga() { const powerBIChartsConfig = yield select(getPowerBIChartsConfig); if (powerBIChartsConfig == null) { - console.error( - 'PowerBI charts configuration could not be found. Please configure the dashboards to be displayed in your ' + - 'workspace, in [workspace].webApp.options.charts' + console.warn( + 'PowerBI charts configuration could not be found and results display has been disabled. ' + + 'If you want to activate it, please configure the dashboards to be displayed in your workspace, ' + + 'in [workspace].webApp.options.charts' ); - yield put({ type: POWER_BI_ACTIONS_KEY.SET_EMBED_INFO, data: noAccess, - error: forgePowerBIError('', 'Configuration error', 'Cannot find dashboards configuration in workspace data'), - status: STATUSES.ERROR, + status: STATUSES.DISABLED, }); return; } diff --git a/src/utils/ConfigUtils.js b/src/utils/ConfigUtils.js index cd62a6ba7..7ccd86ea4 100644 --- a/src/utils/ConfigUtils.js +++ b/src/utils/ConfigUtils.js @@ -166,6 +166,11 @@ const isDatasetManagerEnabledInWorkspace = (workspace) => { return true; }; +const isResultsDisplayEnabledInWorkspace = (workspace) => { + const reportsConfig = workspace?.webApp?.options?.charts; + return reportsConfig != null; +}; + const checkUnknownKeysInConfig = (schema, data) => { try { schema.parse(data); @@ -243,5 +248,6 @@ export const ConfigUtils = { patchSolution, isInstanceViewConfigValid, isDatasetManagerEnabledInWorkspace, + isResultsDisplayEnabledInWorkspace, checkUnknownKeysInConfig, }; diff --git a/src/views/Dashboards/Dashboards.js b/src/views/Dashboards/Dashboards.js index ddf60b7bf..8c58ae467 100644 --- a/src/views/Dashboards/Dashboards.js +++ b/src/views/Dashboards/Dashboards.js @@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'; import PropTypes from 'prop-types'; import { Card, CardContent, Grid, Tab, Tabs } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; +import { useRedirectFromDisabledView } from '../../hooks/RouterHooks'; import { useDashboardsViewReportsConfig } from '../../state/hooks/PowerBIHooks'; import { DashboardsPowerBiReport } from './components'; @@ -70,6 +71,7 @@ const Dashboards = () => { }; const dashboardsViewReportsConfig = useDashboardsViewReportsConfig(); + useRedirectFromDisabledView('dashboards'); const dashboardTitle = dashboardsViewReportsConfig?.[value]?.title?.[i18n.language] ?? DEFAULT_MISSING_TITLE; return ( diff --git a/src/views/DatasetManager/DatasetManager.js b/src/views/DatasetManager/DatasetManager.js index 36dd0d5c9..de049c872 100644 --- a/src/views/DatasetManager/DatasetManager.js +++ b/src/views/DatasetManager/DatasetManager.js @@ -6,11 +6,10 @@ import { useDatasetManager } from './DatasetManagerHook'; import { DatasetList, DatasetMetadata, DatasetOverview, NoDatasetsPlaceholder } from './components'; const DatasetManager = () => { - const { datasets, useRedirectFromDatasetManagerToScenarioView, useResetSelectedDatasetIfNecessary } = - useDatasetManager(); + const { datasets, useRedirectFromDisabledView, useResetSelectedDatasetIfNecessary } = useDatasetManager(); useResetSelectedDatasetIfNecessary(); - useRedirectFromDatasetManagerToScenarioView(); + useRedirectFromDisabledView('datasetManager'); return datasets?.length > 0 ? (