Skip to content
Open
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added

- **CUMULUS-3624**
- Added Inactivity Modal component
- Added unit test for component

## [v12.2.0] - 2024-09-04

This version of the dashboard requires Cumulus API >= v18.4.0
Expand Down
1 change: 0 additions & 1 deletion app/src/js/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ export const searchCollections = (infix) => ({ type: types.SEARCH_COLLECTIONS, i
export const clearCollectionsSearch = () => ({ type: types.CLEAR_COLLECTIONS_SEARCH });
export const filterCollections = (param) => ({ type: types.FILTER_COLLECTIONS, param });
export const clearCollectionsFilter = (paramKey) => ({ type: types.CLEAR_COLLECTIONS_FILTER, paramKey });

export const getCumulusInstanceMetadata = () => ({
[CALL_API]: {
type: types.ADD_INSTANCE_META,
Expand Down
2 changes: 2 additions & 0 deletions app/src/js/components/Header/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { window } from '../../utils/browser';
import { strings } from '../locale';
import linkToKibana from '../../utils/kibana';
import { getPersistentQueryParams } from '../../utils/url-helper';
import InactivityModal from '../Modal/InactivityModal';

const paths = [
[strings.collections, '/collections/all'],
Expand Down Expand Up @@ -121,6 +122,7 @@ const Header = ({
<li>&nbsp;</li>
)}
</nav>
<div className='inactivity'><InactivityModal /></div>
</div>
</div>
);
Expand Down
111 changes: 111 additions & 0 deletions app/src/js/components/Modal/InactivityModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import React, { useEffect, useState, useCallback } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import get from 'lodash/get';
import DefaultModal from './modal';
import { logout } from '../../actions';

const InactivityModal = ({
title = 'Session Timeout Warning',
children = `We have noticed that you have been inactive for a while.
We will close this session in 5 minutes. If you want to stay signed in, select ‘Continue Session’.`,
dispatch
}) => {
const [lastKeyPress, setLastKeyPress] = useState(Date.now());
const [hasModal, setHasModal] = useState(false);

const inactivityTimeInterval = 1800000; // 1800000 ms = 30 min
const timeToCheckInterval = 60000; // Check every minute, 60000 ms = 1 min
// const timeToLogout = 300000; // force logout after interval, 300000 ms = 5 min

function handleConfirm() {
setLastKeyPress(Date.now());
closeModal();
}

const handleLogout = useCallback(() => {
dispatch(logout()).then(() => {
if (get(window, 'location.reload')) {
window.location.reload();
}
});
}, [dispatch]);

function closeModal() { // the X botton
setLastKeyPress(Date.now());
if (hasModal) {
setHasModal(false); // hide modal if user resumes activity
}
}

// Effect to setup event listeners for keyboard activity
useEffect(() => {
// Function to update the lastKeyPress time
const handleKeypress = () => {
setLastKeyPress(Date.now());
if (hasModal) {
setHasModal(false); // hide modal if user resumes activity
}
};
window.addEventListener('keydown', handleKeypress);

return () => {
window.removeEventListener('keydown', handleKeypress);
};
}, [hasModal]);

// Effect to handle showing the modal after <inactivityTimeInterval> of inactivity
useEffect(() => {
const checkInactivity = setInterval(() => {
if (Date.now() - lastKeyPress > inactivityTimeInterval && !hasModal) {
setHasModal(true);
}
}, timeToCheckInterval);

return () => clearInterval(checkInactivity);
}, [hasModal, lastKeyPress]);

return (
<div>
<DefaultModal
title = {title}
className='IAModal'
onCancel={handleLogout}
onCloseModal={closeModal}
onConfirm={handleConfirm}
showModal={hasModal}
hasConfirmButton={true}
hasCancelButton={true}
cancelButtonText='Close Session'
confirmButtonText='Continue Session'
children = {children}
/>
</div>
);
};

const mapStateToProps = (state) => ({
hasModal: state.hasModal,
lastKeyPress: state.lastKeyPress,
});

InactivityModal.propTypes = {
hasModal: PropTypes.bool,
lastKeyPress: PropTypes.string,
title: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
children: PropTypes.string,
className: PropTypes.string,
cancelButtonText: PropTypes.string,
confirmButtonText: PropTypes.string,
showModal: PropTypes.bool,
onCloseModal: PropTypes.func,
onConfirm: PropTypes.func,
onCancel: PropTypes.func,
hasCancelButton: PropTypes.bool,
hasConfirmButton: PropTypes.bool,
dispatch: PropTypes.func,
};

export { InactivityModal };

export default connect(mapStateToProps)(InactivityModal);
4 changes: 2 additions & 2 deletions app/src/js/components/home.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ const Home = ({
<h1 className='heading--xlarge'>{strings.dashboard}</h1>
</div>
</div>

<div className='page__content page__content--nosidebar'>
{pageSection(
<>Select date and time to refine your results. <em>Time is UTC.</em></>,
Expand Down Expand Up @@ -180,7 +179,8 @@ Home.propTypes = {
rules: PropTypes.object,
stats: PropTypes.object,
dispatch: PropTypes.func,
location: PropTypes.object
location: PropTypes.object,
inactivityModal: PropTypes.object
};

export { Home };
Expand Down
13 changes: 13 additions & 0 deletions app/src/localdashboard_cumulus_std.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash
# SIT shared dashboard
export APIROOT=https://d84ilrgn1b7e1.cloudfront.net/
export AUTH_METHOD=launchpad
export AWS_REGION=us-east-1
export DAAC_NAME=cumulus-std
export KIBANAROOT=https://metrics.sit.earthdata.nasa.gov/s/metrics-cumulus
export HIDE_PDR=false
export STAGE=development
export ENABLE_RECOVERY=true

npm run build
npm run serve
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions test/components/inactivity-modal/inactivity-modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use strict';

import test from 'ava';
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
import React from 'react';
import { shallow, configure } from 'enzyme';
import { InactivityModal } from '../../../app/src/js/components/Modal/InactivityModal';

configure({ adapter: new Adapter() });

test('Inactivity Modal should render when hasModal is true', function (t) {
const hasModal = true ;
const hasCancelButton = 'button--cancel';
const hasConfirmButton = 'button--submit';

// Create a shallow render of the component
const modal = shallow(
<InactivityModal
hasModal={hasModal}
hasCancelButton={hasCancelButton}
hasConfirmButton={hasConfirmButton}
/>
);
t.true(modal.find('DefaultModal').exists(),'Modal should be present when hasModal is true');
t.is(modal.find('DefaultModal').prop('hasCancelButton'), true);
t.is(modal.find('DefaultModal').prop('hasConfirmButton'), true);
});