Commit 3358f10d authored by Viacheslav Pavlov's avatar Viacheslav Pavlov Committed by Matija Obreza

BooleanFilter upgrade

- Added Nullable filtering for BooleanFilter
- FiltersBlock now class component
- cleanFilters now returns any not string
- Fixed CWM for FilterBlock

- Added translations
- Added correct suggestions for "NULL"/"NOTNULL"
- Changed nullable to notNull property

- Improved clearing of Nullable fields on form submit

- Code cleanup
parent 9e3596e9
......@@ -68,6 +68,11 @@
"textSearch": "Text search",
"keyword": "Keyword search",
"noFilters": "No available filters",
"NULL": "Not provided",
"NOTNULL": "Either",
"dontCare": "Don't use filter",
"dataNotProvided": "Data not provided",
"dataExists": "Data exists",
"suggestedFilters": "Suggested filters"
},
"fileUploader": {
......
......@@ -56,6 +56,7 @@ class BrowsePage extends BrowsePageTemplate<Accession> {
const overviewEl = suggestions[key];
const terms = new Map();
overviewEl.terms.forEach((term) => terms.set(term.term, term.count));
terms.set('missing', overviewEl.terms.missing);
suggestionTerms.set(key, terms);
});
}
......
......@@ -14,7 +14,7 @@ import Accession from 'model/accession/Accession';
import DateFilter from 'ui/common/filter/DateFilter';
import BooleanFilter from 'ui/common/filter/BooleanFilter';
const AccessionFilters = ({handleSubmit, initialValues, initialize, terms, crops, t, ...other}) => {
const AccessionFilters = ({handleSubmit, initialize, terms, crops, t, ...other}) => {
// console.log('AccessionFilters', initialValues);
return (
<FiltersBlock title={ t('accessions.public.f.filtersTitle') } handleSubmit={ handleSubmit } initialize={ initialize } { ...other }>
......@@ -22,6 +22,7 @@ const AccessionFilters = ({handleSubmit, initialValues, initialize, terms, crops
<BooleanFilter
name="historic"
terms={ terms && terms.get('historic') }
notNull
/>
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title={ t('common:f.textSearch') }>
......@@ -93,12 +94,13 @@ const AccessionFilters = ({handleSubmit, initialValues, initialize, terms, crops
name="sgsv"
label={ t('accessions.public.f.sgsv') }
terms={ terms && terms.get('sgsv') }
notNull
/>
<BooleanFilter
name="images"
label={ t('accessions.public.f.images') }
terms={ terms && terms.get('images') }
initialValues
notNull
/>
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title={ t('accessions.public.f.climate') }>
......
......@@ -26,7 +26,7 @@ const DescriptorFilters = ({ handleSubmit, initialize, t, ...other }) => (
<StringArrFilter name="category" options={ Descriptor.CATEGORIES } byKey/>
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title={ t('descriptors.public.f.tidbits') }>
<BooleanFilter name="used" label={ t('descriptors.public.f.isUsed') }/>
<BooleanFilter name="used" label={ t('descriptors.public.f.isUsed') } notNull/>
<BooleanFilter name="key" label={ t('descriptors.public.f.key') }/>
<StringFilter name="columnName" searchType="contains" label={ t('descriptors.public.f.columnName') } placeholder={ t('descriptors.public.f.columnName') }/>
<StringArrFilter name="uom" label={ t('descriptors.public.f.uom') } placeholder={ t('descriptors.public.f.language') }/>
......
......@@ -22,7 +22,7 @@ const DashboardFilters = ({ handleSubmit, initialize, t, ...other }) => (
>
<CollapsibleComponentSearch title={ t('descriptors.dashboard.f.status') }>
<StatusFilter/>
<BooleanFilter name="used" label={ t('descriptors.dashboard.f.descriptorInUse') }/>
<BooleanFilter name="used" label={ t('descriptors.dashboard.f.descriptorInUse') } notNull/>
</CollapsibleComponentSearch>
<TextFilter
name="_text" label={ t('descriptors.dashboard.f.keywordSearch') }
......
......@@ -16,7 +16,7 @@ const AccessionFilters = ({handleSubmit, initialValues, initialize, t, ...other}
return (
<FiltersBlock title={ t('institutes.public.f.title') } handleSubmit={ handleSubmit } initialize={ initialize } { ...other }>
<CollapsibleComponentSearch title={ t('institutes.common.accessionsInGenesys') }>
<BooleanFilter name="accessions"/>
<BooleanFilter name="accessions" noNull/>
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title={ t('common:f.textSearch') }>
<StringArrFilter name="code" label={ t('institutes.common.instituteCode') } placeholder="NGA039"/>
......
......@@ -4,22 +4,76 @@ import { Field } from 'redux-form';
import Number from 'ui/common/Number';
import RadioSelection from 'ui/common/forms/RadioSelection';
import TextField from '@material-ui/core/TextField';
interface IBooleanFilter extends React.ClassAttributes<any> {
name: string;
label?: string;
terms: Map<string, any>;
initialValues: any;
notNull: boolean;
t: any;
}
class NullableFilterInternal extends React.Component<any> {
public componentWillReceiveProps(nextProps): void {
const {input} = this.props;
const {nullChecked: newNullChecked, boolName} = nextProps;
const checked = input.value.indexOf(boolName) !== -1;
if (checked !== newNullChecked) {
if (newNullChecked) {
input.onChange([...input.value, boolName]);
} else {
const index = input.value.indexOf(boolName);
if (index !== -1) {
const newValue = input.value.filter((val) => val !== boolName);
input.onChange(newValue);
}
}
}
}
public render(): React.ReactNode {
return (<TextField style={ {display: 'none'} }/>);
}
}
class BooleanFilter extends React.Component<IBooleanFilter, any> {
private getLabel = (val) => val === 'true' ? this.props.t(`common:label.yes`) : val === 'false' ? this.props.t(`common:label.no`) : this.props.t(`common:label.either`);
public state = {
nullChecked: undefined,
notNullChecked: undefined,
};
private getLabel = (val) => val === 'true' ? this.props.t(`common:label.yes`) : val === 'false' ? this.props.t(`common:label.no`) : val === '' ? this.props.t(`common:f.dontCare`) : this.props.t(`common:f.${val}`);
private getTermsValue = (val) => {
switch (val) {
case 'true':
return this.props.terms.get('1');
case 'false':
return this.props.terms.get('0');
case 'NULL':
return this.props.terms.get('missing') || '0';
case 'NOTNULL':
return (this.props.terms.get('1') + this.props.terms.get('0') - (this.props.terms.get('missing') || 0)) || '0';
default:
return (this.props.terms.get('1') + this.props.terms.get('0')) || '0';
}
}
private handleChange = (input) => (event, value) => {
this.setState({nullChecked: value === 'NULL', notNullChecked: value === 'NOTNULL'});
private getTermsValue = (val) => val === 'true' ? this.props.terms.get('1') : val === 'false' ? this.props.terms.get('0') : (this.props.terms.get('1') + this.props.terms.get('0')) || '0';
input.onChange(value);
}
public render() {
const {terms, name, label, t} = this.props;
const {terms, name, notNull = false, label , t} = this.props;
const options = !notNull ? [true, false, 'NULL', 'NOTNULL', ''] : [true, false, ''];
return (
<div>
<Field
......@@ -27,9 +81,10 @@ class BooleanFilter extends React.Component<IBooleanFilter, any> {
singleColumn
component={ RadioSelection }
formLabel={ t(label) }
parse={ (value) => value === '' ? null : value === 'true' }
options={ [true, false, ''] }
parse={ (value) => value === 'true' ? true : value === 'false' ? false : value }
options={ options }
classes={ {label: 'full-width'} }
handleChange={ this.handleChange }
renderOptionLabel={ (stat) =>
<span className="full-width" style={ {display: 'flex', justifyContent: 'space-between'} }>
{ `${this.getLabel(`${stat}`)}` }
......@@ -41,6 +96,22 @@ class BooleanFilter extends React.Component<IBooleanFilter, any> {
</span>
}
/>
{ !notNull &&
<span>
<Field
name="NULL"
boolName={ name }
nullChecked={ this.state.nullChecked }
component={ NullableFilterInternal }
/>
< Field
name="NOTNULL"
boolName={ name }
nullChecked={ this.state.notNullChecked }
component={ NullableFilterInternal }
/>
</span>
}
</div>
);
}
......
import * as React from 'react';
import * as _ from 'lodash';
import {translate} from 'react-i18next';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
......@@ -32,46 +33,105 @@ const styles = (theme) => ({
//
// Renders a standard filters block
const FiltersBlock = ({ title, children, handleSubmit, onSubmit, initialize, classes, showSnackbar, values, t }) => {
const scrollToTop = () => {
class FiltersBlock extends React.Component<any> {
public state = {
initialized: false,
};
public componentWillMount(): void {
const {values, submitting} = this.props;
const {initialized} = this.state;
if (!initialized && !submitting && values && (values.NULL || values.NOTNULL)) {
const newValues = values;
if (values.NULL) {
values.NULL.map((key) => newValues[key] = 'NULL');
}
if (values.NOTNULL) {
values.NOTNULL.map((key) => newValues[key] = 'NOTNULL');
}
this.setState({initialized: true});
}
}
public componentWillReceiveProps(nextProps): void {
const {values, submitting} = nextProps;
const {initialized} = this.state;
if (!initialized && !submitting && values && (values.NULL || values.NOTNULL)) {
const newValues = values;
if (values.NULL) {
values.NULL.map((key) => newValues[key] = 'NULL');
}
if (values.NOTNULL) {
values.NOTNULL.map((key) => newValues[key] = 'NOTNULL');
}
this.setState({initialized: true});
}
}
private scrollToTop = () => {
if (window) {
window.scrollTo(0, 0);
}
};
}
const onReset = (e) => {
private onReset = (e) => {
const {values, initialize, handleSubmit, showSnackbar} = this.props;
showSnackbar('Resetting filters...');
log('Clearing form');
const initialValues = {};
Object.keys(values).forEach((key) => {
initialValues[key] = null;
initialValues[key] = null;
});
initialize(initialValues);
setTimeout(handleSubmit, 100);
scrollToTop();
};
this.scrollToTop();
this.setState({initialized: false});
}
const processSubmit = handleSubmit((data) => {
const clean = cleanFilters(data);
private processSubmit = this.props.handleSubmit((data) => {
const {onSubmit} = this.props;
const dataToSubmit = {...data};
if (dataToSubmit.NULL) {
dataToSubmit.NULL.forEach((key) => dataToSubmit[key] = null);
}
if (dataToSubmit.NOTNULL) {
dataToSubmit.NOTNULL.forEach((key) => dataToSubmit[key] = null);
}
const clean = cleanFilters(dataToSubmit);
log('Submitting filters', clean);
scrollToTop();
this.scrollToTop();
this.setState({initialized: false});
return onSubmit(clean);
});
return (
<div className={ classes.filtersBlock }>
<ExpandFiltersComponent title={ title || 'Filters' }>
<form onSubmit={ processSubmit }>
<div className={ `pt-20 pb-20 pl-20 ${classes.stickyButtonContainer}` }>
<Button variant="contained" onClick={ processSubmit } type="submit" className={ `${classes.btnGreen} float-left` }>{ t('common:action.applyFilters') }</Button>
<Button onClick={ onReset } type="button" className={ `${classes.btnReset} float-right` }>{ t('common:action.reset') }</Button>
</div>
{ children }
</form>
</ExpandFiltersComponent>
</div>
);
};
public render() {
const {classes, children, t, title} = this.props;
return (
<div className={ classes.filtersBlock }>
<ExpandFiltersComponent title={ title || 'Filters' }>
<form onSubmit={ this.processSubmit }>
<div className={ `pt-20 pb-20 pl-20 ${ classes.stickyButtonContainer }` }>
<Button variant="contained" onClick={ this.processSubmit } type="submit"
className={ `${ classes.btnGreen } float-left` }>{ t('common:action.applyFilters') }</Button>
<Button onClick={ this.onReset } type="button"
className={ `${ classes.btnReset } float-right` }>{ t('common:action.reset') }</Button>
</div>
{ children }
</form>
</ExpandFiltersComponent>
</div>
);
}
}
const mapStateToProps = (state, ownProps) => ({
values: getFormValues(ownProps.form)(state),
......
......@@ -121,8 +121,18 @@ function handleFilterObj(filterObj, path) {
function translatePath(t, prefix: string, prettyPath: string) {
const isNot = prettyPath.startsWith('NOT.');
const isNull = prettyPath.startsWith('NULL');
const isNotNull = prettyPath.startsWith('NOTNULL');
const isLe = prettyPath.endsWith('.le');
const isGe = prettyPath.endsWith('.ge');
if (isNull) {
return `${t('common:f.dataNotProvided')}:`;
}
if (isNotNull) {
return `${t('common:f.dataExists')}:`;
}
// console.log(`translating not=${isNot} le=${isLe} ge=${isGe} ${prefix}.${prettyPath}`);
if (isNot) {
prettyPath = prettyPath.replace(/^NOT\./, '');
......@@ -192,7 +202,7 @@ function getLabelName(path, value, lookups, prefix, t, labels) {
}
const translatedPrettyPath = translatePath(t, prefix, prettyPath);
// console.log(`${prettyPath} = ${translatedPrettyPath}`);
console.log(`${prettyPath} = ${translatedPrettyPath}`);
if (labels && !_.isEmpty(labels) && prettyPath.includes('uuid')) {
name = labels.get(name);
......@@ -208,6 +218,8 @@ function getLabelName(path, value, lookups, prefix, t, labels) {
class PrettyFilters extends React.Component<IPrettyFiltersProps, any> {
private banWords = ['NULL', 'NOTNULL'];
constructor(props: IPrettyFiltersProps, context: any) {
super(props, context);
this.state = {
......@@ -233,7 +245,7 @@ class PrettyFilters extends React.Component<IPrettyFiltersProps, any> {
const { classes, lookups, prefix, t, amount, displayName } = this.props;
const labels = new Map(this.props.labels);
const { chipData } = this.state;
const dataArr = Object.getOwnPropertyNames(chipData);
const dataArr = Object.getOwnPropertyNames(chipData).filter((key) => this.banWords.indexOf(chipData[key]) === -1);
const withAmount = amount !== undefined && amount !== null;
return (dataArr.length > 0 || withAmount) ? (
......
......@@ -331,7 +331,7 @@ class InternalStringArrField extends React.Component<IStringArrFilterInternal &
<FormGroup>
{ filteredOptions.map((key) => (
<FormControlLabel
key={ `${ key }` }
key={ valueField ? `${ key[valueField] }` : `${ key }` }
style={
(document && document.dir === 'rtl') ?
{ paddingRight: `${tree ? (tree.get(key) * 20) : 0}px`, marginLeft: 0 } :
......
......@@ -26,9 +26,9 @@ const UserFilters = ({handleSubmit, initialValues, initialize, t, ...other}) =>
<DateFilter name="lastLogin" label="user.admin.f.lastLogin"/>
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title="user.common.statusLabel">
<BooleanFilter name="locked" label="user.admin.f.locked" />
<BooleanFilter name="active" label="user.admin.f.enabled" />
<BooleanFilter name="expired" label="user.admin.f.expired" />
<BooleanFilter name="locked" label="user.admin.f.locked" notNull/>
<BooleanFilter name="active" label="user.admin.f.enabled" notNull/>
<BooleanFilter name="expired" label="user.admin.f.expired" notNull/>
</CollapsibleComponentSearch>
</FiltersBlock>
);
......
......@@ -75,7 +75,7 @@ export function arraysEqual(a: any[], b: any[]): boolean {
/**
* Remove empty arrays and null filters
*/
export function cleanFilters(filter, keysToSkip?): string {
export function cleanFilters(filter, keysToSkip?): any {
const result: any = {};
for (const k of Object.keys(filter).sort()) {
if (keysToSkip && keysToSkip.indexOf(k) !== -1) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment