Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(filters): save worklist query filters to session storage so that they persist between navigation to the viewer and back #3749

Merged
merged 6 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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');
jbocce marked this conversation as resolved.
Show resolved Hide resolved
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