diff --git a/cypress/component/DataSearch/dataset_search_filters.spec.js b/cypress/component/DataSearch/dataset_search_filters.spec.js
index a8bb21c17..d61ceac93 100644
--- a/cypress/component/DataSearch/dataset_search_filters.spec.js
+++ b/cypress/component/DataSearch/dataset_search_filters.spec.js
@@ -4,18 +4,20 @@ import { mount } from 'cypress/react';
import React from 'react';
import DatasetFilterList from '../../../src/components/data_search/DatasetFilterList';
-const duosUser = {
- isSigningOfficial: false,
-};
-
describe('Data Library Filters', () => {
- beforeEach(() => {
- cy.initApplicationConfig();
- });
+ // Intercept configuration calls
+ beforeEach(() => {
+ cy.initApplicationConfig();
+ });
- it('Renders the data library filters', () => {
- const props = { datasets: [], filters: [], filterHandler: () => {}, isFiltered: () => {}};
- mount();
- cy.get('div').should('contain', 'Filters');
- });
+ it('Renders the data library filters', () => {
+ const props = { datasets: [], filterHandler: () => {}, isFiltered: () => {}};
+ mount();
+ cy.get('div').should('contain', 'Filters');
+ cy.get('div').should('contain', 'Access Type');
+ cy.get('div').should('contain', 'Data Use');
+ cy.get('div').should('contain', 'Data Access Committee');
+ cy.get('div').should('contain', 'Data Type');
+ cy.get('div').should('contain', 'Participant Count');
+ });
});
diff --git a/cypress/component/DataSearch/dataset_search_table.spec.js b/cypress/component/DataSearch/dataset_search_table.spec.js
index 85c1e92c6..c10b27638 100644
--- a/cypress/component/DataSearch/dataset_search_table.spec.js
+++ b/cypress/component/DataSearch/dataset_search_table.spec.js
@@ -9,7 +9,9 @@ const datasets = [
datasetId: 123456,
datasetIdentifier: `DUOS-123456`,
datasetName: 'Some Dataset 1',
+ participantCount: 100,
study: {
+ studyName: 'Some Study 1',
studyId: 1,
dataCustodianEmail: ['Some Data Custodian Email 1'],
}
@@ -23,9 +25,11 @@ const props = {
describe('Dataset Search Table tests', () => {
- describe('Data library with three datasets', () => {
+ describe('Data library with one dataset footer tests', () => {
beforeEach(() => {
+ cy.initApplicationConfig();
cy.stub(TerraDataRepo, 'listSnapshotsByDatasetIds').returns({});
+ cy.clock();
mount();
});
@@ -38,6 +42,66 @@ describe('Dataset Search Table tests', () => {
cy.get('#header-checkbox').click();
cy.contains('1 dataset selected from 1 study');
});
+ });
+
+
+ describe('Data library filter by participant count tests', () => {
+
+ beforeEach(() => {
+ cy.initApplicationConfig();
+ cy.stub(TerraDataRepo, 'listSnapshotsByDatasetIds').returns({});
+ cy.clock();
+ });
+
+ function handler(request, searchText) {
+ if (JSON.stringify(request.body).includes(searchText)) {
+ request.reply(['filtered']);
+ } else {
+ request.reply([]);
+ }
+ }
+
+
+ it('When a participant count filter is applied the query is updated', () => {
+ cy.intercept(
+ {method: 'POST', url: '**/search/index'}, (req) => {
+ return handler(req, '{"range":{"participantCount":{"gte":null,"lte":50}}}');
+ }).as('searchIndex');
+ mount();
+ // first clear the default value (100), without clearing first, type('50') would result in input of 10050
+ const range = cy.get('#participantCountMax-range-input');
+ range.clear();
+ range.type('50');
+ cy.tick(150);
+ // this api call should have had a request that contained the searchText
+ let count = 0;
+ cy.wait('@searchIndex').then((response) => {
+ expect(response.response.body[0]).to.equal('filtered');
+ count++;
+ });
+ cy.get('@searchIndex').then(() => {
+ expect(count).to.equal(1);
+ });
+
+ });
+
+ it('When an invalid participant count filter is applied the query represents the default value', () => {
+ cy.intercept({method: 'POST', url: '**/search/index'}, (req) => {
+ // when non-numeric input is entered, the default value (in this case, 100) is used
+ return handler(req, '{"range":{"participantCount":{"gte":100,"lte":null}}}');
+ }).as('searchIndex');
+ mount();
+ cy.get('#participantCountMin-range-input').type('test');
+ cy.tick(150);
+ let count = 0;
+ cy.wait('@searchIndex').then((response) => {
+ expect(response.response.body[0]).to.equal('filtered');
+ count++;
+ });
+ cy.get('@searchIndex').then(() => {
+ expect(count).to.equal(1);
+ });
+ });
});
});
diff --git a/src/components/data_search/DatasetFilterList.jsx b/src/components/data_search/DatasetFilterList.jsx
index 4b6825579..c8ff3d562 100644
--- a/src/components/data_search/DatasetFilterList.jsx
+++ b/src/components/data_search/DatasetFilterList.jsx
@@ -6,10 +6,10 @@ import ListItemButton from '@mui/material/ListItemButton';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import Divider from '@mui/material/Divider';
-import { Button, Typography } from '@mui/material';
+import { Button, TextField, Typography } from '@mui/material';
import { Checkbox } from '@mui/material';
-import {flatten, uniq, compact, orderBy} from 'lodash';
-import {getAccessManagementSummary} from '../../types/model';
+import { flatten, uniq, compact, orderBy } from 'lodash';
+import { getAccessManagementSummary } from '../../types/model';
export const FilterItemHeader = (props) => {
const { title, headerStyle = { fontFamily: 'Montserrat', fontWeight: '600', marginTop: '1em' } } = props;
@@ -21,20 +21,20 @@ export const FilterItemHeader = (props) => {
};
export const FilterItemList = (props) => {
- const { category, datasets, filter, filterHandler, isFiltered, filterNameFn, filterDisplayFn } = props;
+ const { category, filter, filterHandler, isFiltered, filterNameFn, filterDisplayFn } = props;
return (
{
- filter.map((filter) => {
- const filterName = filterNameFn(filter);
+ filter.map((filterOption) => {
+ const filterName = filterNameFn(filterOption);
return (
-
- filterHandler(event, datasets, category, filter)}>
+
+ filterHandler(category, filterOption)}>
-
+
- {filterDisplayFn ? filterDisplayFn(filter) : filterName}
+ {filterDisplayFn ? filterDisplayFn(filterOption) : filterName}
@@ -45,14 +45,37 @@ export const FilterItemList = (props) => {
);
};
+export const FilterItemRange = (props) => {
+ const { min, max, minCategory, maxCategory, filterHandler } = props;
+ const getValue = (val, defaultVal) => isNaN(Number(val)) ? defaultVal : Number(val);
+ return (
+
+ filterHandler(minCategory, getValue(event.target.value, min))}/>
+ -
+ filterHandler(maxCategory, getValue(event.target.value, max))}
+ />
+
+ );
+};
+
export const DatasetFilterList = (props) => {
- const { datasets, filters, filterHandler, isFiltered, onClear } = props;
+ const { datasets, filterHandler, isFiltered, onClear } = props;
const accessManagementFilters = uniq(compact(datasets.map((dataset) => dataset.accessManagement)));
const dataUseFilters = uniq(compact(flatten(datasets.map((dataset) => dataset.dataUse?.primary))).map((dataUse) => dataUse.code));
const dataTypeFilters = uniq(flatten(datasets.map((dataset) => dataset.study.dataTypes)));
const dacFilters = orderBy(uniq(compact(datasets.map((dataset) => dataset.dac?.dacName))), (dac) => dac.toLowerCase(), 'asc');
-
+ const defaultValues = datasets.reduce((acc, dataset) => {
+ return {
+ max: Math.max(acc.max, dataset.participantCount ? dataset.participantCount : 0),
+ min: Math.min(acc.min, dataset.participantCount ? dataset.participantCount : Infinity) };
+ }, {max: 0, min: Infinity});
return (
@@ -101,15 +124,24 @@ export const DatasetFilterList = (props) => {
isFiltered={isFiltered}
filterNameFn={(filter) => filter}
/>
-
+
filter}
/>
+
+
);
};
diff --git a/src/components/data_search/DatasetSearchTable.jsx b/src/components/data_search/DatasetSearchTable.jsx
index 989e8dfc6..d6677887c 100644
--- a/src/components/data_search/DatasetSearchTable.jsx
+++ b/src/components/data_search/DatasetSearchTable.jsx
@@ -3,8 +3,8 @@ import Tabs from '@mui/material/Tabs';
import useOnMount from '@mui/utils/useOnMount';
import * as React from 'react';
import { Box, Button } from '@mui/material';
-import { useEffect, useRef, useState } from 'react';
-import { isEmpty } from 'lodash';
+import {useEffect, useRef, useState} from 'react';
+import { isArray, isEmpty } from 'lodash';
import { TerraDataRepo } from '../../libs/ajax/TerraDataRepo';
import { DatasetSearchTableDisplay } from './DatasetSearchTableDisplay';
import { datasetSearchTableTabs } from './DatasetSearchTableConstants';
@@ -37,7 +37,9 @@ const defaultFilters = {
accessManagement: [],
dataUse: [],
dataType: [],
- dac: []
+ dac: [],
+ participantCountMin: null,
+ participantCountMax: null,
};
export const DatasetSearchTable = (props) => {
@@ -49,8 +51,12 @@ export const DatasetSearchTable = (props) => {
const [selectedTable, setSelectedTable] = useState(datasetSearchTableTabs.study);
const [searchTerm, setSearchTerm] = useState('');
- const isFiltered = (filter, category) => (filters[category]).indexOf(filter) > -1;
- const numSelectedFilters = (filters) => Object.values(filters).reduce((sum, array) => sum + array.length, 0);
+ const isFilteredArray = (filter, category) => (filters[category]).indexOf(filter) > -1;
+
+ const anyFiltersSelected = (filters) =>
+ Object.values(filters).some(filter => {
+ return isArray(filter) ? filter.length > 0 : filter !== null;
+ });
const getExportableDatasets = async (datasets) => {
// Note the dataset identifier is in each sub-table row.
@@ -108,7 +114,7 @@ export const DatasetSearchTable = (props) => {
}
let filterQuery = {};
- if (numSelectedFilters(filters) > 0) {
+ if (anyFiltersSelected(filters)) {
const filterTerms = [];
filterTerms.push({
@@ -155,6 +161,15 @@ export const DatasetSearchTable = (props) => {
}
});
+ filterTerms.push({
+ 'range': {
+ 'participantCount': {
+ 'gte': filters.participantCountMin,
+ 'lte': filters.participantCountMax,
+ }
+ }
+ });
+
if (filterTerms.length > 0) {
filterQuery = [
{
@@ -179,13 +194,19 @@ export const DatasetSearchTable = (props) => {
};
};
- const filterHandler = (event, data, category, filter) => {
- var newFilters = _.clone(filters);
- if (!isFiltered(filter, category) && filter !== '') {
- newFilters[category] = filters[category].concat(filter);
+ const filterHandler = (category, filter) => {
+ let newFilter;
+ if (isArray(filters[category])) {
+ if (!isFilteredArray(filter, category) && filter !== '') {
+ newFilter = filters[category].concat(filter);
+ } else {
+ newFilter = filters[category].filter((f) => f !== filter);
+ }
} else {
- newFilters[category] = filters[category].filter((f) => f !== filter);
+ newFilter = filter;
}
+ const newFilters = _.clone(filters);
+ newFilters[category] = newFilter;
setFilters(newFilters);
};
@@ -280,7 +301,7 @@ export const DatasetSearchTable = (props) => {
- setFilters(defaultFilters)}/>
+ setFilters(defaultFilters)}/>
{(() => {