Skip to content

Commit

Permalink
feat(filters): save worklist query filters to session storage so that…
Browse files Browse the repository at this point in the history
… they persist between navigation to the viewer and back (#3749)

Co-authored-by: ladeirarodolfo <39910206+ladeirarodolfo@users.noreply.github.com>
  • Loading branch information
jbocce and ladeirarodolfo authored Oct 30, 2023
1 parent db39585 commit 2a15ef0
Show file tree
Hide file tree
Showing 13 changed files with 322 additions and 9 deletions.
65 changes: 65 additions & 0 deletions platform/app/cypress/integration/study-list/OHIFStudyList.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
describe('OHIF Study List', function () {
context('Desktop resolution', function () {
beforeEach(function () {
cy.window().then(win => win.sessionStorage.clear());
cy.openStudyList();

cy.viewport(1750, 720);
Expand All @@ -14,6 +15,10 @@ describe('OHIF Study List', function () {
cy.get('@StudyDescription').clear();
});

afterEach(function () {
cy.window().then(win => win.sessionStorage.clear());
});

it('Displays several studies initially', function () {
cy.waitStudyList();
cy.get('@searchResult2').should($list => {
Expand All @@ -33,6 +38,21 @@ describe('OHIF Study List', function () {
});
});

it('maintains Patient Name filter upon return from viewer', function () {
cy.get('@PatientName').type('Juno');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('[data-cy="studyRow-1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1"]').click();
cy.get(
'[data-cy="mode-basic-test-1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1"]'
).click();
cy.get('[data-cy="return-to-work-list"]').click();
cy.get('@searchResult2').should($list => {
expect($list.length).to.be.eq(1);
expect($list).to.contain('Juno');
});
});

it('searches MRN with exact string', function () {
cy.get('@MRN').type('0000003');
//Wait result list to be displayed
Expand All @@ -43,6 +63,21 @@ describe('OHIF Study List', function () {
});
});

it('maintains MRN filter upon return from viewer', function () {
cy.get('@MRN').type('0000003');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('[data-cy="studyRow-1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1"]').click();
cy.get(
'[data-cy="mode-basic-test-1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1"]'
).click();
cy.get('[data-cy="return-to-work-list"]').click();
cy.get('@searchResult2').should($list => {
expect($list.length).to.be.eq(1);
expect($list).to.contain('0000003');
});
});

it('searches Accession with exact string', function () {
cy.get('@AccessionNumber').type('321');
//Wait result list to be displayed
Expand All @@ -53,6 +88,21 @@ describe('OHIF Study List', function () {
});
});

it('maintains Accession filter upon return from viewer', function () {
cy.get('@AccessionNumber').type('0000155811');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('[data-cy="studyRow-1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1"]').click();
cy.get(
'[data-cy="mode-basic-test-1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1"]'
).click();
cy.get('[data-cy="return-to-work-list"]').click();
cy.get('@searchResult2').should($list => {
expect($list.length).to.be.eq(1);
expect($list).to.contain('0000155811');
});
});

it('searches Description with exact string', function () {
cy.get('@StudyDescription').type('PETCT');
//Wait result list to be displayed
Expand All @@ -63,6 +113,21 @@ describe('OHIF Study List', function () {
});
});

it('maintains Description filter upon return from viewer', function () {
cy.get('@StudyDescription').type('PETCT');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('[data-cy="studyRow-1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1"]').click();
cy.get(
'[data-cy="mode-basic-test-1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1"]'
).click();
cy.get('[data-cy="return-to-work-list"]').click();
cy.get('@searchResult2').should($list => {
expect($list.length).to.be.eq(1);
expect($list).to.contain('PETCT');
});
});

/* Todo: fix react select
it('searches Modality with camel case', function() {
cy.get('@modalities').type('Ct');
Expand Down
14 changes: 13 additions & 1 deletion platform/app/src/routes/WorkList/WorkList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
AboutModal,
UserPreferences,
LoadingIndicatorProgress,
useSessionStorage,
} from '@ohif/ui';

import i18n from '@ohif/i18n';
Expand Down Expand Up @@ -60,9 +61,17 @@ function WorkList({
const navigate = useNavigate();
const STUDIES_LIMIT = 101;
const queryFilterValues = _getQueryFilterValues(searchParams);
const [sessionQueryFilterValues, updateSessionQueryFilterValues] = useSessionStorage({
key: 'queryFilterValues',
defaultValue: queryFilterValues,
// ToDo: useSessionStorage currently uses an unload listener to clear the filters from session storage
// so on systems that do not support unload events a user will NOT be able to alter any existing filter
// in the URL, load the page and have it apply.
clearOnUnload: true,
});
const [filterValues, _setFilterValues] = useState({
...defaultFilterValues,
...queryFilterValues,
...sessionQueryFilterValues,
});

const debouncedFilterValues = useDebounce(filterValues, 200);
Expand Down Expand Up @@ -119,6 +128,7 @@ function WorkList({
val.pageNumber = 1;
}
_setFilterValues(val);
updateSessionQueryFilterValues(val);
setExpandedRows([]);
};

Expand Down Expand Up @@ -251,6 +261,7 @@ function WorkList({
moment(time, ['HH', 'HHmm', 'HHmmss', 'HHmmss.SSS']).format('hh:mm A');

return {
dataCY: `studyRow-${studyInstanceUid}`,
row: [
{
key: 'patientName',
Expand Down Expand Up @@ -377,6 +388,7 @@ function WorkList({
disabled={!isValidMode}
endIcon={<Icon name="launch-arrow" />} // launch-arrow | launch-info
onClick={() => {}}
data-cy={`mode-${mode.routeName}-${studyInstanceUid}`}
>
{t(`Modes:${mode.displayName}`)}
</LegacyButton>
Expand Down
13 changes: 13 additions & 0 deletions platform/ui/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const base = require('../../jest.config.base.js');
const pkg = require('./package');

module.exports = {
...base,
name: pkg.name,
displayName: pkg.name,
// rootDir: "../.."
// testMatch: [
// //`<rootDir>/platform/${pack.name}/**/*.spec.js`
// "<rootDir>/platform/app/**/*.test.js"
// ]
};
4 changes: 3 additions & 1 deletion platform/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@
"react-window": "^1.8.9",
"react-with-direction": "^1.3.1",
"swiper": "^8.4.2",
"webpack": "^5.81.0"
"webpack": "^5.81.0",
"@testing-library/react-hooks": "^3.2.1",
"react-test-renderer": "^16.12.0"
},
"devDependencies": {
"@babel/core": "^7.21.4",
Expand Down
2 changes: 1 addition & 1 deletion platform/ui/src/components/DateRange/DateRange.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ DateRange.propTypes = {
/** YYYYMMDD (19921022) */
startDate: PropTypes.string,
/** YYYYMMDD (19921022) */
endDate: PropTypes.object,
endDate: PropTypes.string,
/** Callback that received { startDate: string(YYYYMMDD), endDate: string(YYYYMMDD)} */
onChange: PropTypes.func.isRequired,
};
Expand Down
1 change: 1 addition & 0 deletions platform/ui/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ function Header({
isReturnEnabled && 'cursor-pointer'
)}
onClick={onClickReturn}
data-cy="return-to-work-list"
>
{isReturnEnabled && (
<Icon
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const StudyListFilter = ({
</div>
)}
</div>
<div className="flex flex-row">
<div className="flex h-[34px] flex-row items-center">
{/* TODO revisit the completely rounded style of button used for clearing the study list filter - for now use LegacyButton*/}
{isFiltering && (
<LegacyButton
Expand All @@ -76,7 +76,7 @@ const StudyListFilter = ({
</Typography>
<Typography
variant="h6"
className="text-primary-light self-end pb-1"
className="text-primary-light"
>
{t('Studies')}
</Typography>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import Icon from '../Icon';

const StudyListTableRow = props => {
const { tableData } = props;
const { row, expandedContent, onClickRow, isExpanded } = tableData;
const { row, expandedContent, onClickRow, isExpanded, dataCY } = tableData;
return (
<>
<tr className="select-none">
<tr
className="select-none"
data-cy={dataCY}
>
<td
className={classnames('border-0 p-0', {
'border-secondary-light bg-primary-dark border-b': isExpanded,
Expand Down Expand Up @@ -104,6 +107,7 @@ StudyListTableRow.propTypes = {
expandedContent: PropTypes.node.isRequired,
onClickRow: PropTypes.func.isRequired,
isExpanded: PropTypes.bool.isRequired,
dataCY: PropTypes.string,
}),
};

Expand Down
3 changes: 2 additions & 1 deletion platform/ui/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import useResizeObserver from './useResizeObserver';
import useSessionStorage from './useSessionStorage';

export { useResizeObserver };
export { useResizeObserver, useSessionStorage };
102 changes: 102 additions & 0 deletions platform/ui/src/hooks/useSessionStorage.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { renderHook, act } from '@testing-library/react-hooks';
import useSessionStorage from './useSessionStorage';

const SESSION_STORAGE_KEY = 'test';

describe('Hook Session Storage', () => {
beforeEach(() => {
window.sessionStorage.removeItem(SESSION_STORAGE_KEY);
});

it('hook should return state and setState', () => {
const data = { test: 1 };
const { result } = renderHook(() =>
useSessionStorage({ key: SESSION_STORAGE_KEY, defaultValue: data })
);
const [hookState, setHookState] = result.current;
expect(hookState).toStrictEqual(data);
expect(typeof setHookState).toBe('function');
});

it('hook should store data on sessionStorage', () => {
const data = { test: 2 };
renderHook(() => useSessionStorage({ key: SESSION_STORAGE_KEY, defaultValue: data }));

const dataStr = JSON.stringify(data);
const dataSessionStorage = window.sessionStorage.getItem(SESSION_STORAGE_KEY);
expect(dataSessionStorage).toEqual(dataStr);
});

it('hook should return stored data from sessionStorage', () => {
const data = { test: 3 };
const dataToCompare = { test: 4 };

window.sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(dataToCompare));

const { result } = renderHook(() =>
useSessionStorage({ key: SESSION_STORAGE_KEY, defaultValue: data })
);
const [hookState, setHookState] = result.current;

expect(hookState).toStrictEqual(dataToCompare);
});

it('hook should provide a setState method which updates its state', () => {
const data = { test: 5 };
const dataToCompare = { test: 6 };
const { result } = renderHook(() =>
useSessionStorage({ key: SESSION_STORAGE_KEY, defaultValue: data })
);
const [hookState, setHookState] = result.current;

act(() => {
setHookState(dataToCompare);
});

const dataToCompareStr = JSON.stringify(dataToCompare);
const dataSessionStorage = window.sessionStorage.getItem(SESSION_STORAGE_KEY);

const [hookStateToCompare] = result.current;
expect(dataSessionStorage).toEqual(dataToCompareStr);
expect(hookStateToCompare).toStrictEqual(dataToCompare);
});

it('hook state must be preserved in case rerender', () => {
const data = { test: 7 };
const { result, rerender } = renderHook(() =>
useSessionStorage({ key: SESSION_STORAGE_KEY, defaultValue: data })
);

rerender();

const [hookState, setHookState] = result.current;

const dataToCompareStr = JSON.stringify(data);
const dataSessionStorage = window.sessionStorage.getItem(SESSION_STORAGE_KEY);

expect(dataSessionStorage).toEqual(dataToCompareStr);
expect(hookState).toStrictEqual(data);
});

it('hook state must be preserved in case multiple operations and rerender', () => {
const data = { test: 8 };
const dataToCompare = { test: 9 };
const { result, rerender } = renderHook(() =>
useSessionStorage({ key: SESSION_STORAGE_KEY, defaultValue: data })
);
const [hookState, setHookState] = result.current;

act(() => {
setHookState(dataToCompare);
});

rerender();

const dataToCompareStr = JSON.stringify(dataToCompare);
const dataSessionStorage = window.sessionStorage.getItem(SESSION_STORAGE_KEY);

const [hookStateToCompare] = result.current;
expect(dataSessionStorage).toEqual(dataToCompareStr);
expect(hookStateToCompare).toStrictEqual(dataToCompare);
});
});
Loading

0 comments on commit 2a15ef0

Please sign in to comment.