From 07e11ba5eb5838b1dce60506f0e50816a957086e Mon Sep 17 00:00:00 2001 From: Jenny <32821331+jenny-s51@users.noreply.github.com> Date: Fri, 22 Nov 2024 11:04:51 -0500 Subject: [PATCH] [RHOAIENG-1154]: Apply modal to alert user error check for 403 status code add cypress test update test title format format format remove it.only fix relative import --- .../cypress/cypress/pages/loginDialog.ts | 9 +++++ .../cypress/tests/mocked/application.cy.ts | 19 ++++++++++ frontend/src/app/App.tsx | 35 ++++++++++++++++++- frontend/src/app/useApplicationSettings.tsx | 7 ++-- .../src/services/dashboardConfigService.ts | 12 ++++++- 5 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 frontend/src/__tests__/cypress/cypress/pages/loginDialog.ts diff --git a/frontend/src/__tests__/cypress/cypress/pages/loginDialog.ts b/frontend/src/__tests__/cypress/cypress/pages/loginDialog.ts new file mode 100644 index 0000000000..390291d2a1 --- /dev/null +++ b/frontend/src/__tests__/cypress/cypress/pages/loginDialog.ts @@ -0,0 +1,9 @@ +import Chainable = Cypress.Chainable; + +export class LoginDialog { + findText(): Chainable> { + return cy.findByTestId('timeout-text'); + } +} + +export const loginDialog = new LoginDialog(); diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/application.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/application.cy.ts index 5b0747ee19..1dad49de9a 100644 --- a/frontend/src/__tests__/cypress/cypress/tests/mocked/application.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/application.cy.ts @@ -7,6 +7,7 @@ import { import { mockDashboardConfig } from '~/__mocks__'; import { aboutDialog } from '~/__tests__/cypress/cypress/pages/aboutDialog'; import { mockConsoleLinks } from '~/__mocks__/mockConsoleLinks'; +import { loginDialog } from '~/__tests__/cypress/cypress/pages/loginDialog'; describe('Application', () => { it('should disallow access to the dashboard', () => { @@ -113,4 +114,22 @@ describe('Application', () => { aboutDialog.findText().should('contain.text', 'OpenShift'); aboutDialog.findProductName().should('contain.text', 'OpenShift AI'); }); + it('should show the login modal when receiving a 403 status code', () => { + // Mock the intercept to return a 403 status code + + cy.interceptOdh('GET /api/config', { + statusCode: 403, + }).as('getData403'); + + // Visit the page where the request is triggered + cy.visit('/'); + + // Wait for the intercept to be triggered + cy.wait('@getData403'); + + // Verify that the login modal is displayed + loginDialog + .findText() + .should('contain.text', 'Your session timed out. To continue working, log in'); + }); }); diff --git a/frontend/src/app/App.tsx b/frontend/src/app/App.tsx index 8c4d452ee5..f651486dd2 100644 --- a/frontend/src/app/App.tsx +++ b/frontend/src/app/App.tsx @@ -6,6 +6,11 @@ import { Alert, Bullseye, Button, + Modal, + ModalBody, + ModalFooter, + ModalHeader, + ModalVariant, Page, PageSection, Spinner, @@ -67,9 +72,37 @@ const App: React.FC = () => { [buildStatuses, dashboardConfig, storageClasses], ); + const isUnauthorized = React.useMemo(() => { + if (fetchConfigError?.request?.status === 403) { + return true; + } + return false; + }, [fetchConfigError]); + // We lack the critical data to startup the app if (userError || fetchConfigError) { - // There was an error fetching critical data + // Check for unauthorized state + if (isUnauthorized) { + return ( + + + + Your session timed out. To continue working, log in. + + + + + + ); + } + + // Default error handling for other cases return ( diff --git a/frontend/src/app/useApplicationSettings.tsx b/frontend/src/app/useApplicationSettings.tsx index 7797268d01..8e0cb736ff 100644 --- a/frontend/src/app/useApplicationSettings.tsx +++ b/frontend/src/app/useApplicationSettings.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { AxiosError } from 'axios'; import { DashboardConfigKind } from '~/k8sTypes'; import { POLL_INTERVAL } from '~/utilities/const'; import { useDeepCompareMemoize } from '~/utilities/useDeepCompareMemoize'; @@ -8,10 +9,10 @@ import useTimeBasedRefresh from './useTimeBasedRefresh'; export const useApplicationSettings = (): { dashboardConfig: DashboardConfigKind | null; loaded: boolean; - loadError: Error | undefined; + loadError: AxiosError | undefined; } => { const [loaded, setLoaded] = React.useState(false); - const [loadError, setLoadError] = React.useState(); + const [loadError, setLoadError] = React.useState(); const [dashboardConfig, setDashboardConfig] = React.useState(null); const setRefreshMarker = useTimeBasedRefresh(); @@ -29,7 +30,7 @@ export const useApplicationSettings = (): { setLoadError(undefined); }) .catch((e) => { - if (e?.message?.includes('Error getting Oauth Info for user')) { + if (e?.response?.data?.message?.includes('Error getting Oauth Info for user')) { // NOTE: this endpoint only requests oauth because of the security layer, this is not an ironclad use-case // Something went wrong on the server with the Oauth, let us just log them out and refresh for them /* eslint-disable-next-line no-console */ diff --git a/frontend/src/services/dashboardConfigService.ts b/frontend/src/services/dashboardConfigService.ts index 498c0a4d78..f86d07e747 100644 --- a/frontend/src/services/dashboardConfigService.ts +++ b/frontend/src/services/dashboardConfigService.ts @@ -1,3 +1,4 @@ +import { AxiosError } from 'axios'; import axios from '~/utilities/axios'; import { DashboardConfigKind } from '~/k8sTypes'; @@ -7,6 +8,15 @@ export const fetchDashboardConfig = (): Promise => { .get(url) .then((response) => response.data) .catch((e) => { - throw new Error(e.response.data.message); + const message = e.response.data?.message; + + // Throw the AxiosError with status code + throw new AxiosError( + message, // Error message from the server + message, // The error message also serves as the "code" argument for AxiosError + undefined, // Optional: request config that was used + e.response, // Optional: the full response object + e, + ); }); };