Skip to content

Commit

Permalink
Fixes #3955 filter layer minor issues (#3956)
Browse files Browse the repository at this point in the history
  • Loading branch information
offtherailz authored Jul 12, 2019
1 parent a564023 commit ef8f973
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 74 deletions.
8 changes: 4 additions & 4 deletions web/client/components/data/query/QueryPanelHeader.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const React = require('react');

const Message = require('../../I18N/Message');
const {Button, Glyphicon} = require('react-bootstrap');
const buttonTooltip = require('../../misc/enhancers/buttonTooltip');
const AlertIcon = buttonTooltip((props) => (<div className="square-button pull-right no-border" style={{display: 'flex'}} {...props}><Glyphicon glyph="exclamation-mark" className="text-primary"/></div>));
const popoverTooltip = require('../../misc/enhancers/popover');
const AlertIcon = popoverTooltip((props) => (<div className="square-button pull-right no-border" style={{display: 'flex'}} {...props}><Glyphicon glyph="exclamation-mark" className="text-danger"/></div>));

module.exports = ({loadingError, onToggleQuery = () => {}} = {}) => (<div className="mapstore-block-width">
<Button
Expand All @@ -12,6 +12,6 @@ module.exports = ({loadingError, onToggleQuery = () => {}} = {}) => (<div classN
onClick={() => onToggleQuery()}>
<Glyphicon glyph="arrow-left"/>
</Button>
{loadingError && (<AlertIcon tooltipId="queryform.loadingError" tooltipPosition="bottom"/>) || (
{loadingError && (<AlertIcon popover={{text: (<Message msgId="queryform.loadingError"/>)}}/>) || (
<div className="square-button pull-right no-border" style={{display: 'flex'}}><Glyphicon glyph="filter" className="text-primary"/></div>)}
</div>);
62 changes: 39 additions & 23 deletions web/client/components/data/query/QueryToolbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const React = require('react');
const {Button} = require('react-bootstrap');
const {isEqual} = require('lodash');
const Modal = require('../../misc/Modal');
const {checkOperatorValidity, setupCrossLayerFilterDefaults, isCrossLayerFilterValid} = require('../../../utils/FilterUtils');
const { checkOperatorValidity, setupCrossLayerFilterDefaults, isCrossLayerFilterValid, isFilterEmpty} = require('../../../utils/FilterUtils');
const Toolbar = require('../../misc/toolbar/Toolbar');

class QueryToolbar extends React.Component {
Expand Down Expand Up @@ -84,7 +84,6 @@ class QueryToolbar extends React.Component {
};
constructor(props) {
super(props);
this.state = {showModal: false};
}
getCurrentFilter = () => {
return {
Expand All @@ -111,35 +110,52 @@ class QueryToolbar extends React.Component {
};
}
render() {
// TODO: we should separate advancedToolbar and standardToolbar into 2 different component or enhancers
let fieldsExceptions = this.props.filterFields.filter((field) => field.exception).length > 0;
// let fieldsWithoutValues = this.props.filterFields.filter((field) => !field.value).length > 0;
let fieldsWithValues = this.props.filterFields.filter((field) => field.value || field.value === 0).length > 0 || (this.props.allowEmptyFilter && !this.props.advancedToolbar);
// option allowEmptyFilter available only for the base toolbar, not advanced TODO: externalize this behaviour
const allowEmpty = (this.props.allowEmptyFilter && !this.props.advancedToolbar);

let queryDisabled =
// fieldsWithoutValues ||
fieldsExceptions ||
!this.props.toolbarEnabled ||
!fieldsWithValues && !this.props.spatialField.geometry && !isCrossLayerFilterValid(this.props.crossLayerFilter);
// this flag checks if there is any valid attribute fields (with value)
let hasValidAttributeFields = this.props.filterFields.filter((field) => field.value || field.value === 0).length > 0;

const isCurrentFilterEmpty = isFilterEmpty(this.props);
const isAppliedFilterEmpty = isFilterEmpty(this.props.appliedFilter);
const isCurrentFilterChanged = this.isCurrentFilterChanged();
// TODO: use isFilterValid
const isCurrentFilterValid = hasValidAttributeFields
|| this.props.spatialField.geometry
|| isCrossLayerFilterValid(this.props.crossLayerFilter);
const isAppliedFilterChanged = !isEqual(this.props.appliedFilter, this.props.storedFilter);
// submit for empty filter is allowed when
// - it is forced to be allowed by outside
// - there is already an applied filter
const canSubmitEmptyFilter = (allowEmpty || isCurrentFilterEmpty && this.props.appliedFilter && !isAppliedFilterEmpty );
let queryDisabled = fieldsExceptions
|| !this.props.toolbarEnabled // disabled generally
|| isCurrentFilterEmpty && !canSubmitEmptyFilter // disabled because is empty on startup
|| !isCurrentFilterEmpty && (!isCurrentFilterValid || !isCurrentFilterChanged); // disabled because of invalid or not changed filter
const showTooltip = this.props.emptyFilterWarning && isCurrentFilterEmpty && isCurrentFilterChanged;

const isFilterChanged = !isEqual(this.props.appliedFilter, this.props.storedFilter);
const showTooltip = this.props.emptyFilterWarning
&& this.props.filterFields.filter((field) => field.value).length === 0
&& !this.props.spatialField.geometry
&& !(this.props.crossLayerFilter && this.props.crossLayerFilter.attribute && this.props.crossLayerFilter.operation);
const queryBtnMsgId = this.props.advancedToolbar ? "queryform.apply" : this.props.queryBtnMsgId;
let buttons = [ {
tooltipId: showTooltip ? "queryform.emptyfilter" : queryBtnMsgId,
disabled: queryDisabled || (this.props.advancedToolbar && !this.appliedFilterChanged()),
disabled: queryDisabled,
noTooltipWhenDisabled: true,
glyph: this.props.advancedToolbar && "ok" || this.props.queryBtnGlyph,
className: showTooltip ? "square-button-md showWarning" : "square-button-md",
id: "query-toolbar-query",
onClick: this.search
}];
if (this.props.advancedToolbar) {
const disableSave = !isFilterChanged || !this.props.toolbarEnabled || this.props.loadingError || this.appliedFilterChanged();
const disableRestore = !isFilterChanged || !this.props.storedFilter || !this.props.toolbarEnabled;
const disableReset = !this.props.appliedFilter || !this.props.toolbarEnabled;
const disableSave = !isAppliedFilterChanged
|| this.props.loadingError
|| isCurrentFilterChanged;
const disableRestore = !isAppliedFilterChanged
|| !this.props.storedFilter
|| !this.props.toolbarEnabled;
const disableReset = !this.props.appliedFilter
|| this.props.appliedFilter && isAppliedFilterEmpty
|| !this.props.toolbarEnabled;
buttons = buttons.concat([
{
tooltipId: "queryform.save",
Expand Down Expand Up @@ -192,7 +208,7 @@ class QueryToolbar extends React.Component {
</div>
);
}
appliedFilterChanged = () => {
isCurrentFilterChanged = () => {


const currentFilter = this.getCurrentFilter();
Expand All @@ -211,6 +227,7 @@ class QueryToolbar extends React.Component {
},
crossLayerFilter: appliedFilter.crossLayerExpanded && appliedFilter.crossLayerFilter && appliedFilter.crossLayerFilter.operation ? setupCrossLayerFilterDefaults(appliedFilter.crossLayerFilter) : null
};

return !isEqual(current, applied);
}
search = () => {
Expand All @@ -220,9 +237,6 @@ class QueryToolbar extends React.Component {
this.props.actions.storeAppliedFilter();
}
};
showModal = () => {
this.setState(({showModal: true}));
}
reset = () => {

this.props.actions.onChangeDrawingStatus('clean', '', "queryform", []);
Expand All @@ -244,7 +258,9 @@ class QueryToolbar extends React.Component {
hits: this.props.hits
};
this.props.actions.onQuery(this.props.searchUrl, filterObj, this.props.params);
this.setState(({showModal: false}));
if (this.props.advancedToolbar) {
this.props.actions.storeAppliedFilter();
}
}
};

Expand Down
81 changes: 81 additions & 0 deletions web/client/components/data/query/__tests__/QueryToolbar-test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,87 @@ describe('QueryToolbar component', () => {
expect(container.querySelector('#query-toolbar-query')).toExist();
ReactDOM.render(<QueryToolbar emptyFilterWarning allowEmptyFilter spatialField={{geometry: {}}} crossLayerFilter={{attribute: "ATTR", operation: undefined}}/>, document.getElementById("container"));
expect(container.querySelector('#query-toolbar-query.showWarning')).toNotExist();
});

describe('advancedToolbar', () => {
const VALID_FILTERS = [
// spatial only
{
groupFields: [],
spatialPanelExpanded: true,
spatialField: {
attribute: "the_geom",
geometry: {
"some": "geometry"
}
}
},
// spatial different
{
groupFields: [],
spatialPanelExpanded: true,
spatialField: {
attribute: "the_geom",
geometry: {
"some": "OTHER_geometry"
}
}
}];
const checkButtonsEnabledCondition = (container) => (apply, save, discard, reset) => {
return expect(!container.querySelector("button#query-toolbar-query").disabled).toBe(!!apply, `expected apply to be ${apply ? 'enabled' : 'disabled'}`)
&& expect(!container.querySelector("button#query-toolbar-save").disabled).toBe(!!save, `expected save to be ${save ? 'enabled' : 'disabled'}`)
&& expect(!container.querySelector("button#query-toolbar-discard").disabled).toBe(!!discard, `expected discard to be ${discard ? 'enabled' : 'disabled'}`)
&& expect(!container.querySelector("button#reset").disabled).toBe(!!reset, `expected reset to be ${reset ? 'enabled' : 'disabled'}`);
};
it('defaults', () => {
ReactDOM.render(<QueryToolbar />, document.getElementById("container"));
const container = document.getElementById('container');
expect(container.querySelectorAll('button').length).toBe(2);
ReactDOM.render(<QueryToolbar advancedToolbar />, document.getElementById("container"));
const buttons = [...container.querySelectorAll('button')];
expect(buttons.length).toBe(4);
expect(buttons.filter(({ disabled }) => disabled).length).toBe(4);
});
it('apply button is enabled when valid', () => {
const container = document.getElementById('container');
ReactDOM.render(<QueryToolbar advancedToolbar {...VALID_FILTERS[0]} />, document.getElementById("container"));
checkButtonsEnabledCondition(container)(true, false, false, false);
});
it('save and reset enabled when the current filter is applied', () => {
const container = document.getElementById('container');
ReactDOM.render(<QueryToolbar advancedToolbar {...VALID_FILTERS[0]} appliedFilter={VALID_FILTERS[0]} />, document.getElementById("container"));
const buttons = [...container.querySelectorAll('button')];
expect(buttons.length).toBe(4);
checkButtonsEnabledCondition(container)(false, true, false, true);
});
it('apply and reset enabled when the current filter is different from applied', () => {
const container = document.getElementById('container');
ReactDOM.render(<QueryToolbar advancedToolbar {...VALID_FILTERS[1]} appliedFilter={VALID_FILTERS[0]} />, document.getElementById("container"));
const buttons = [...container.querySelectorAll('button')];
expect(buttons.length).toBe(4);
checkButtonsEnabledCondition(container)(true, false, false, true);
});
it('restore enabled when applied a filter different from saved one, not yet saved', () => {
const container = document.getElementById('container');
ReactDOM.render(<QueryToolbar advancedToolbar
{...VALID_FILTERS[0]}
appliedFilter={VALID_FILTERS[0]}
storedFilter={VALID_FILTERS[1]}
/>, document.getElementById("container"));
const buttons = [...container.querySelectorAll('button')];
expect(buttons.length).toBe(4);
checkButtonsEnabledCondition(container)(false, true, true, true);
});
it('saved filter with no changes has only reset enabled', () => {
const container = document.getElementById('container');
ReactDOM.render(<QueryToolbar advancedToolbar
{...VALID_FILTERS[0]}
appliedFilter={VALID_FILTERS[0]}
storedFilter={VALID_FILTERS[0]}
/>, document.getElementById("container"));
const buttons = [...container.querySelectorAll('button')];
expect(buttons.length).toBe(4);
checkButtonsEnabledCondition(container)(false, false, false, true);
});
});
});
9 changes: 4 additions & 5 deletions web/client/plugins/QueryPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -339,14 +339,13 @@ class QueryPanel extends React.Component {
buttons={[
{
bsStyle: 'primary',
text: <Message msgId="queryform.discardbtn"/>,
onClick: this.restoreAndClose
text: <Message msgId="yes"/>,
onClick: this.storeAndClose
},
{
bsStyle: 'primary',
disabled: this.props.loadingError,
text: <Message msgId="save"/>,
onClick: this.storeAndClose
text: <Message msgId="no" />,
onClick: this.restoreAndClose
}
]}>
<div className="ms-alert">
Expand Down
14 changes: 6 additions & 8 deletions web/client/translations/data.de-DE
Original file line number Diff line number Diff line change
Expand Up @@ -765,10 +765,10 @@
"queryform": {
"title": "Erweiterte Suche",
"query": "Suche",
"apply": "Filter anwenden",
"apply": "Anwenden",
"reset": "Zurücksetzen",
"save": "Aktuellen Filter speichern",
"discard": "Filterwechsel verwerfen",
"save": "Sparen",
"discard": "Rückgängig machen",
"query_request_exception": "Abfrage Fehler",
"config": {
"load_config_exception": "Fehler beim Laden der Konfiguration"
Expand Down Expand Up @@ -857,13 +857,11 @@
}
},
"changedFilter": "Filter geändert",
"discardbtn": "Verwerfen",
"changedFilterAlert": "Aktuellen Filter speichern oder verwerfen?",
"changedFilterAlert": "Es gibt ungespeicherte Änderungen. Möchtest du sie retten?",
"resetFilter": "Filter entfernen",
"resetFilterAlert": "Es ist nicht möglich, eine Filterrücksetzaktion rückgängig zu machen. Bestätigen Sie?",
"confirmReset": "Bestätigen",
"loadingError": "Das WMS-Rendering ist aufgrund der Filterkonfiguration fehlgeschlagen. Versuchen Sie, es zu entfernen oder zu beheben",
"changedFilterWithErrorAlert": "Der Filter verursacht einen WMS-Renderfehler. Versuchen Sie, ihn zu beheben oder zu verwerfen"
"loadingError": "Der aktuelle Filter verursachte ein Rendering-Problem für den Layer auf der Karte. Bitte überprüfen Sie die Layerkonfiguration auf dem Server oder korrigieren Sie den Filter",
"changedFilterWithErrorAlert": "Der Filter verursacht einen Layer-Rendering-Fehler. Möchten Sie ihn trotzdem speichern?"
},
"annotations": {
"errorLoadingSymbols": "Beim Laden der Symbolliste ist ein Fehler aufgetreten. Bitte wenden Sie sich an den Administrator, um die Konfigurationsoptionen zu überprüfen",
Expand Down
14 changes: 6 additions & 8 deletions web/client/translations/data.en-US
Original file line number Diff line number Diff line change
Expand Up @@ -765,10 +765,10 @@
"queryform": {
"title": "Advanced Search",
"query": "Search",
"apply": "Apply filter",
"apply": "Apply",
"reset": "Reset",
"save": "Save current filter",
"discard": "Discard filter changes",
"save": "Save",
"discard": "Undo",
"query_request_exception": "Request Error",
"config": {
"load_config_exception": "Error Loading Configuration"
Expand Down Expand Up @@ -857,13 +857,11 @@
}
},
"changedFilter": "Filter Changed",
"discardbtn": "Discard",
"changedFilterAlert": "Save or discard actual filter?",
"changedFilterAlert": "There are unsaved changes. Do you want to save them?",
"resetFilter": "Remove filter",
"resetFilterAlert": "It is not possible to undo a filter reset action. Do you confirm?",
"confirmReset": "Confirm",
"loadingError": "WMS rendering failed due to filter configuration, try to remove or fix it.",
"changedFilterWithErrorAlert": "Filter is causing WMS render failure, try to fix or discard it"
"loadingError": "The current filter caused a rendering problem for the layer on map. Please check the layer configuration on the server or fix the filter",
"changedFilterWithErrorAlert": "Filter is causing layer rendering error, do you want to save it anyway ?"

},
"annotations": {
Expand Down
16 changes: 7 additions & 9 deletions web/client/translations/data.es-ES
Original file line number Diff line number Diff line change
Expand Up @@ -765,10 +765,10 @@
"queryform": {
"title": "Búsqueda avanzada",
"query": "Búsqueda",
"apply": "Aplicar filtro",
"reset": "Eliminar filtros",
"save": "Guardar filtro actual",
"discard": "Desechar cambios de filtro",
"apply": "Aplicar",
"reset": "Eliminar",
"save": "Guardar",
"discard": "Desechar",
"query_request_exception": "Error en la búsqueda",
"config": {
"load_config_exception": "Error cargando configuración"
Expand Down Expand Up @@ -857,13 +857,11 @@
}
},
"changedFilter": "Filtro cambiado",
"discardbtn": "Descarte",
"changedFilterAlert": "¿Guardar o desechar el filtro de cuentas?",
"changedFilterAlert": "Hay cambios no guardados. ¿Quieres salvarlos?",
"resetFilter": "Quitar filtro",
"resetFilterAlert": "No es posible deshacer una acción de restablecimiento del filtro. ¿Confirmas?",
"confirmReset": "Confirmar",
"loadingError": "La representación de WMS falló debido a la configuración del filtro, intente eliminarlo o arreglarlo.",
"changedFilterWithErrorAlert": "El filtro está causando un error de procesamiento WMS, intente arreglarlo o descartarlo"
"loadingError": "El filtro actual causó un problema de representación para la capa en el mapa. Compruebe la configuración de capa en el servidor o arregle el filtro",
"changedFilterWithErrorAlert": "El filtro está causando un error de representación de la capa, ¿quieres guardarlo de todos modos?"
},
"annotations": {
"errorLoadingSymbols": "Hubo un problema al cargar la lista de símbolos. Por favor, contacte al administrador para verificar las opciones de configuración.",
Expand Down
Loading

0 comments on commit ef8f973

Please sign in to comment.