Skip to content

Commit

Permalink
DR-3164: Handle null values in Free Text Tabular data filtering (#1523)
Browse files Browse the repository at this point in the history
* Handle null values in freetext filtering

* Run lint

* Convert FreetextFilter to Typescript

format

format filter

* Create Component test for FreetextFilter

Update test

format test

Update FreetextFilter.test.tsx
  • Loading branch information
snf2ye authored Oct 20, 2023
1 parent bcbaaad commit 2ba3270
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 160 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ gcloud auth login --no-activate

To run end-to-end tests: `npx cypress run` or `npx cypress open` (interactive mode)

To run unit tests: `npx cypress run-ct` or `npx cypress open-ct` (interactive mode)
To run unit tests: `npx cypress run --component` or `npx cypress open --component` (interactive mode)

## skaffold

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ type CategoryWrapperProps = {
dataset: any;
dispatch: Dispatch<Action>;
filterMap: any;
handleChange: () => void;
handleChange: (value: any) => void;
handleFilters: () => void;
tableName: string;
toggleExclude: () => void;
toggleExclude: (boxIsChecked: boolean) => void;
};

function CategoryWrapper({
Expand Down Expand Up @@ -90,9 +90,7 @@ function CategoryWrapper({
handleFilters={handleFilters}
filterMap={filterMap}
values={originalValues}
table={tableName}
toggleExclude={toggleExclude}
tableName={tableName}
/>
);
}
Expand Down
149 changes: 0 additions & 149 deletions src/components/dataset/data/sidebar/filter/FreetextFilter.jsx

This file was deleted.

69 changes: 69 additions & 0 deletions src/components/dataset/data/sidebar/filter/FreetextFilter.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Provider } from 'react-redux';
import { mount } from 'cypress/react';
import { Router } from 'react-router-dom';
import { ThemeProvider } from '@mui/styles';
import { routerMiddleware } from 'connected-react-router';
import createMockStore, { MockStoreEnhanced } from 'redux-mock-store';
import React from 'react';
import globalTheme from '../../../../../modules/theme';
import history from '../../../../../modules/hist';
import FreetextFilter from './FreetextFilter';

function createState(filterMap: any, values: any): any {
return {
datasets: {
dataset: {
description: 'A dataset description',
phsId: '12345',
},
},
column: {
name: 'column1',
label: 'column1',
},
filterMap,
values,
};
}

let store: MockStoreEnhanced<unknown, unknown>;

function mountComponent(state: any) {
const mockStore = createMockStore([routerMiddleware(history)]);
store = mockStore(state);
mount(
<Provider store={store}>
<Router history={history}>
<ThemeProvider theme={globalTheme}>
<FreetextFilter
classes={{ listItem: 'listItemClass', chip: 'chipClass' }}
column={state.column}
filterMap={state.filterMap}
handleChange={() => null}
handleFilters={() => null}
toggleExclude={() => null}
values={state.values}
/>
</ThemeProvider>
</Router>
</Provider>,
);
}

describe('Free text list that includes null value', () => {
beforeEach(() => {
const filterMap = {
value: [null, '1:100891126:A:G'],
type: 'value',
exclude: false,
};
const values = [{ value: null }, { value: '1:100891126:A:G' }, { value: '2:100891126:A:G' }];
const initialState = createState(filterMap, values);
mountComponent(initialState);
});
it('Load Free text dropdown', () => {
cy.get('[id="autocomplete-column1"]').should('exist').click();
cy.get('#autocomplete-column1-option-0').should('contain.text', '(empty)');
cy.get('#autocomplete-column1-option-1').should('contain.text', '1:100891126:A:G');
});
});
145 changes: 145 additions & 0 deletions src/components/dataset/data/sidebar/filter/FreetextFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import React, { useState } from 'react';
import _ from 'lodash';
import { connect } from 'react-redux';
import TextField from '@mui/material/TextField';
import Chip from '@mui/material/Chip';
import Autocomplete from '@mui/material/Autocomplete';
import { WithStyles, withStyles } from '@mui/styles';
import { FormControlLabel, Checkbox, Typography, CustomTheme } from '@mui/material';
import { TdrState } from 'reducers';

import { TableColumnType } from 'src/reducers/query';

const styles = (theme: CustomTheme) => ({
listItem: {
margin: `${theme.spacing(0.5)} 0px`,
},
chip: {
backgroundColor: theme.palette.common.white,
borderRadius: theme.shape.borderRadius,
},
});

interface FreetextFilterProps extends WithStyles<typeof styles> {
classes: any;
column: TableColumnType;
filterMap: any;
handleChange: (value: any) => void;
handleFilters: () => void;
toggleExclude: (boxIsChecked: boolean) => void;
values: any;
}

const FreetextFilter = withStyles(styles)(
({
classes,
column,
filterMap,
handleChange,
handleFilters,
toggleExclude,
values,
}: FreetextFilterProps) => {
const [inputValue, setInputValue] = useState('');

const onInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { value } = event.target;
setInputValue(value);
};

const onChange = (_event: any, value: any) => {
setInputValue('');
if (handleChange) {
handleChange(value);
}
};

const handleReturn = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter' && handleFilters) {
handleFilters();
}
};

const onPaste = (event: React.ClipboardEvent<HTMLInputElement>) => {
event.preventDefault();
const text = event.clipboardData.getData('text');
const selections = text.split(/[ ,\r\n]+/);
const nonEmpty = selections.filter((s: string) => s !== '');
const updatedValueArray = _.get(filterMap, 'value', []).concat(nonEmpty);
if (handleChange) {
handleChange(updatedValueArray);
}
};

const deleteChip = (option: any) => {
const selected = _.filter(filterMap.value, (v) => v !== option);
handleChange(selected);
};

const value = _.get(filterMap, 'value', []);
const valueList = values?.map((val: any) => val.value) ?? [];

return (
<div>
<Autocomplete
multiple
id={`autocomplete-${column.name}`}
options={valueList}
getOptionLabel={(option) => option ?? '(empty)'}
// this means the user's choice does not have to match the provided options
freeSolo={true}
style={{ width: '100%' }}
renderInput={(params) => (
<TextField
{...params}
fullWidth
variant="outlined"
margin="dense"
onChange={onInputChange}
/>
)}
// tags are rendered manually in list under autocomplete box
renderTags={() => null}
inputValue={inputValue}
onKeyPress={handleReturn}
onPaste={onPaste}
onChange={onChange}
value={value}
forcePopupIcon
disableClearable
/>
{value.map((option: any, index: any) => (
<div key={index} className={classes.listItem}>
<Chip
label={option ?? '(empty)'}
onDelete={() => deleteChip(option)}
variant="outlined"
className={classes.chip}
/>
</div>
))}
{!_.isEmpty(value) && (
<FormControlLabel
control={
<Checkbox
size="small"
checked={filterMap.exclude}
onChange={(event) => toggleExclude(event.target.checked)}
/>
}
label={<Typography variant="body2">Exclude all</Typography>}
data-cy={`exclude-${column.name}`}
/>
)}
</div>
);
},
);

function mapStateToProps(state: TdrState) {
return {
dataset: state.datasets.dataset,
};
}

export default connect(mapStateToProps)(FreetextFilter);
Loading

0 comments on commit 2ba3270

Please sign in to comment.