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',
+ );
}
});