From 05914d698b23c123362a899e6df60f1511e1acb0 Mon Sep 17 00:00:00 2001 From: Caitlin <78328496+CaitHawk@users.noreply.github.com> Date: Thu, 19 Dec 2024 09:35:14 -0800 Subject: [PATCH] Session timeout alert moved to LoginModal (#33607) * adds sessionTimeoutAlert component * updates tests * updates index tests * removes href from window call --- src/applications/auth/containers/AuthApp.jsx | 4 +- .../authentication/components/LoginHeader.jsx | 19 +++--- .../components/SessionTimeoutAlert.jsx | 27 +++++++++ .../components/LoginHeader.unit.spec.js | 18 +++++- .../SessionTimeoutAlert.unit.spec.js | 58 +++++++++++++++++++ src/platform/utilities/api/index.js | 2 +- .../utilities/tests/api/index.unit.spec.js | 6 +- 7 files changed, 117 insertions(+), 17 deletions(-) create mode 100644 src/platform/user/authentication/components/SessionTimeoutAlert.jsx create mode 100644 src/platform/user/tests/authentication/components/SessionTimeoutAlert.unit.spec.js diff --git a/src/applications/auth/containers/AuthApp.jsx b/src/applications/auth/containers/AuthApp.jsx index f60b6ad9681f..52de55cd4047 100644 --- a/src/applications/auth/containers/AuthApp.jsx +++ b/src/applications/auth/containers/AuthApp.jsx @@ -27,9 +27,7 @@ import { handleTokenRequest, } from '../helpers'; -const REDIRECT_IGNORE_PATTERN = new RegExp( - ['/auth/login/callback', '/session-expired'].join('|'), -); +const REDIRECT_IGNORE_PATTERN = new RegExp(['/auth/login/callback'].join('|')); export default function AuthApp({ location }) { useDatadogRum(); diff --git a/src/platform/user/authentication/components/LoginHeader.jsx b/src/platform/user/authentication/components/LoginHeader.jsx index 6c827bac4fd4..753c892abc79 100644 --- a/src/platform/user/authentication/components/LoginHeader.jsx +++ b/src/platform/user/authentication/components/LoginHeader.jsx @@ -2,22 +2,21 @@ import React from 'react'; import PropTypes from 'prop-types'; import LogoutAlert from './LogoutAlert'; import DowntimeBanners from './DowntimeBanner'; +import SessionTimeoutAlert from './SessionTimeoutAlert'; export default function LoginHeader({ loggedOut }) { - // Used to get around Cypress E2E lookup of va-modal's sign-in modal h1 - // const HeadingTag = - // (window?.cypress || - // (typeof Cypress !== 'undefined' && Cypress.env('CI'))) && - // !isUnifiedSignIn - // ? `h2` - // : `h1`; - + const queryParams = new URLSearchParams(window.location.search); + const isSessionExpired = queryParams.get('status') === 'session_expired'; + const displayLogoutAlert = loggedOut && !isSessionExpired; return ( <>
- {loggedOut && } + {displayLogoutAlert && } +
-

Sign in or create an account

+

+ Sign in or create an account +

diff --git a/src/platform/user/authentication/components/SessionTimeoutAlert.jsx b/src/platform/user/authentication/components/SessionTimeoutAlert.jsx new file mode 100644 index 000000000000..f6c83e324ecb --- /dev/null +++ b/src/platform/user/authentication/components/SessionTimeoutAlert.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { VaAlert } from '@department-of-veterans-affairs/component-library/dist/react-bindings'; +import { renderMaintenanceWindow, renderDowntimeBanner } from '../downtime'; + +export default function SessionTimeoutAlert() { + const { statuses = [], maintenanceWindows = [] } = useSelector( + state => state.externalServiceStatuses, + ); + const noDowntime = + renderDowntimeBanner(statuses) === null && + renderMaintenanceWindow(maintenanceWindows) === null; + const queryParams = new URLSearchParams(window.location.search); + const isSessionExpired = queryParams.get('status') === 'session_expired'; + const displaySessionTimeout = isSessionExpired && noDowntime; + return ( + <> + {displaySessionTimeout ? ( + +

+ Your session timed out. Sign in again to continue. +

+
+ ) : null} + + ); +} diff --git a/src/platform/user/tests/authentication/components/LoginHeader.unit.spec.js b/src/platform/user/tests/authentication/components/LoginHeader.unit.spec.js index 1aaabbae364f..1c77616905a7 100644 --- a/src/platform/user/tests/authentication/components/LoginHeader.unit.spec.js +++ b/src/platform/user/tests/authentication/components/LoginHeader.unit.spec.js @@ -19,7 +19,7 @@ describe('LoginHeader', () => { expect(screen.queryByText(/Sign in or create an account/)).to.not.be.null; }); - it('should display the LogoutAlert component when user is loggedIn', () => { + it('should display the LogoutAlert component when user is logged out', () => { const screen = renderInReduxProvider(, { initialState: generateState({}), }); @@ -27,4 +27,20 @@ describe('LoginHeader', () => { expect(screen.queryByText(/You have successfully signed out/i)).to.not.be .null; }); + + it('should display the SessionTimeoutAlert component when the session is expired', () => { + const originalLocation = window.location; + delete window.location; + window.location = { + ...originalLocation, + search: '?status=session_expired', + }; + const screen = renderInReduxProvider(, { + initialState: generateState({}), + }); + expect( + screen.queryByText(/Your session timed out. Sign in again to continue./i), + ).to.not.be.null; + window.location = originalLocation; + }); }); diff --git a/src/platform/user/tests/authentication/components/SessionTimeoutAlert.unit.spec.js b/src/platform/user/tests/authentication/components/SessionTimeoutAlert.unit.spec.js new file mode 100644 index 000000000000..fcfa61d417a0 --- /dev/null +++ b/src/platform/user/tests/authentication/components/SessionTimeoutAlert.unit.spec.js @@ -0,0 +1,58 @@ +import React from 'react'; +import { renderInReduxProvider } from 'platform/testing/unit/react-testing-library-helpers'; +import { expect } from 'chai'; +import SessionTimeoutAlert from 'platform/user/authentication/components/SessionTimeoutAlert'; + +const generateState = ({ statuses = [], maintenanceWindows = [] }) => ({ + externalServiceStatuses: { + statuses, + maintenanceWindows, + }, +}); + +describe('SessionTimeoutAlert', () => { + it('does not render when no query parameters are provided', () => { + const screen = renderInReduxProvider(, { + initialState: generateState({}), + }); + + expect( + screen.queryByText(/Your session timed out. Sign in again to continue./i), + ).to.be.null; + }); + + it('renders when session has expired and there is no downtime', () => { + const originalLocation = global.window.location; + global.window.location = { + ...originalLocation, + search: '?status=session_expired', + }; + + const screen = renderInReduxProvider(, { + initialState: generateState({}), + }); + + expect( + screen.queryByText(/Your session timed out. Sign in again to continue./i), + ).to.not.be.null; + global.window.location = originalLocation; + }); + + it('does not render when session has expired but there is downtime', () => { + const originalLocation = global.window.location; + global.window.location = { + ...originalLocation, + search: '?status=session_expired', + }; + + const screen = renderInReduxProvider(, { + initialState: generateState({ + statuses: [{ serviceId: 'idme', status: 'down' }], + }), + }); + expect( + screen.queryByText(/Your session timed out. Sign in again to continue./i), + ).to.be.null; + global.window.location = originalLocation; + }); +}); diff --git a/src/platform/utilities/api/index.js b/src/platform/utilities/api/index.js index 58a429a7bda1..ce5c8a21a166 100644 --- a/src/platform/utilities/api/index.js +++ b/src/platform/utilities/api/index.js @@ -167,7 +167,7 @@ export function apiRequest( if (shouldRedirectToSessionExpired) { sessionStorage.removeItem('shouldRedirectExpiredSession'); - window.location = '/session-expired'; + window.location = '/?next=loginModal&status=session_expired'; } } diff --git a/src/platform/utilities/tests/api/index.unit.spec.js b/src/platform/utilities/tests/api/index.unit.spec.js index 4194e4506906..833ab0e76920 100644 --- a/src/platform/utilities/tests/api/index.unit.spec.js +++ b/src/platform/utilities/tests/api/index.unit.spec.js @@ -44,7 +44,7 @@ describe('test wrapper', () => { expect(mockEnv.isProduction.called).to.be.true; }); - it('should redirect to /session-expired if in production and session expired (401)', async () => { + it('should redirect to LoginModal if in production and session expired (401)', async () => { server.use( rest.get('*', (req, res, ctx) => res( @@ -74,7 +74,9 @@ describe('test wrapper', () => { ); } catch (error) { expect(mockEnv.isProduction.called).to.be.true; - expect(window.location).to.eql('/session-expired'); + expect(window.location).to.eql( + '/?next=loginModal&status=session_expired', + ); } });