diff --git a/web/client/components/data/query/QueryToolbar.jsx b/web/client/components/data/query/QueryToolbar.jsx
index 6d441f521b..50d5b790bf 100644
--- a/web/client/components/data/query/QueryToolbar.jsx
+++ b/web/client/components/data/query/QueryToolbar.jsx
@@ -124,9 +124,8 @@ class QueryToolbar extends React.Component {
let fieldsExceptions = this.props.filterFields.filter((field) => field.exception).length > 0;
// option allowEmptyFilter available only for the base toolbar, not advanced TODO: externalize this behaviour
const allowEmpty = (this.props.allowEmptyFilter && !this.props.advancedToolbar);
-
// 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;
+ let hasValidAttributeFields = this.props.filterFields.filter((field) => checkOperatorValidity(field.value, field.operator)).length > 0;
const isCurrentFilterEmpty = isFilterEmpty(this.props);
const isAppliedFilterEmpty = isFilterEmpty(this.props.appliedFilter);
diff --git a/web/client/utils/FilterUtils.js b/web/client/utils/FilterUtils.js
index a1c22dfc8a..25626abe8f 100644
--- a/web/client/utils/FilterUtils.js
+++ b/web/client/utils/FilterUtils.js
@@ -29,7 +29,7 @@ export const cqlToOgc = (cqlFilter, fOpts) => {
return toFilter(read(cqlFilter));
};
-import { get, isNil, isUndefined, isArray, find, findIndex, isString, flatten } from 'lodash';
+import { get, isNil, isArray, find, findIndex, isString, flatten } from 'lodash';
let FilterUtils;
const wrapValueWithWildcard = (value, condition) => {
@@ -43,7 +43,7 @@ export const wrapIfNoWildcards = (value) => {
export const escapeCQLStrings = str => str && str.replace ? str.replace(/\'/g, "''") : str;
export const checkOperatorValidity = (value, operator) => {
- return (!isNil(value) && operator !== "isNull" || !isUndefined(value) && operator === "isNull");
+ return ( operator === "isNull" || !isNil(value) );
};
/**
* Test if crossLayer filter is valid.
@@ -960,9 +960,7 @@ export const cqlStringField = function(attribute, operator, value) {
const wrappedAttr = wrapAttributeWithDoubleQuotes(attribute);
if (!isNil(value)) {
const processedValue = processCqlWildcards(value, operator);
- if (operator === "isNull") {
- fieldFilter = "isNull(" + wrappedAttr + ")=true";
- } else if (["<>", "="].includes(operator)) {
+ if (["<>", "="].includes(operator)) {
fieldFilter = wrappedAttr + operator + processedValue;
} else if (operator === "ilike") {
fieldFilter = "strToLowerCase(" + wrappedAttr + ") LIKE " + processedValue;
@@ -1029,30 +1027,34 @@ export const processCQLFilterFields = function(group, objFilter) {
if (fields) {
fields.forEach((field) => {
let fieldFilter;
-
- switch (field.type) {
- case "date":
- case "time":
- case "date-time":
- fieldFilter = FilterUtils.cqlDateField(field.attribute, field.operator, field.value);
- break;
- case "number":
- fieldFilter = FilterUtils.cqlNumberField(field.attribute, field.operator, field.value);
- break;
- case "string":
- fieldFilter = FilterUtils.cqlStringField(field.attribute, field.operator, field.value);
- break;
- case "boolean":
- fieldFilter = FilterUtils.cqlBooleanField(field.attribute, field.operator, field.value);
- break;
- case "list":
- fieldFilter = FilterUtils.cqlListField(field.attribute, field.operator, field.value);
- break;
- case "array":
- fieldFilter = FilterUtils.cqlArrayField(field.attribute, field.operator, field.value);
- break;
- default:
- break;
+ if (field.operator === "isNull") {
+ const wrappedAttr = wrapAttributeWithDoubleQuotes(field.attribute);
+ fieldFilter = "isNull(" + wrappedAttr + ")=true";
+ } else {
+ switch (field.type) {
+ case "date":
+ case "time":
+ case "date-time":
+ fieldFilter = FilterUtils.cqlDateField(field.attribute, field.operator, field.value);
+ break;
+ case "number":
+ fieldFilter = FilterUtils.cqlNumberField(field.attribute, field.operator, field.value);
+ break;
+ case "string":
+ fieldFilter = FilterUtils.cqlStringField(field.attribute, field.operator, field.value);
+ break;
+ case "boolean":
+ fieldFilter = FilterUtils.cqlBooleanField(field.attribute, field.operator, field.value);
+ break;
+ case "list":
+ fieldFilter = FilterUtils.cqlListField(field.attribute, field.operator, field.value);
+ break;
+ case "array":
+ fieldFilter = FilterUtils.cqlArrayField(field.attribute, field.operator, field.value);
+ break;
+ default:
+ break;
+ }
}
if (fieldFilter) {
filter.push(group.negateAll ? 'NOT (' + fieldFilter + ')' : fieldFilter);
@@ -1139,7 +1141,7 @@ export const getWFSFilterData = (filterObj, options) => {
};
export const isLikeOrIlike = (operator) => operator === "ilike" || operator === "like";
export const isFilterEmpty = ({ filterFields = [], spatialField = {}, crossLayerFilter = {}, filters = [] } = {}) =>
- !(filterFields.filter((field) => field.value || field.value === 0).length > 0)
+ !(filterFields.filter((field) => field.value || field.value === 0 || field.operator === "isNull").length > 0)
&& !spatialField.geometry
&& !(crossLayerFilter && crossLayerFilter.attribute && crossLayerFilter.operation)
&& !(filters && filters.length > 0);
diff --git a/web/client/utils/__tests__/FilterUtils-test.js b/web/client/utils/__tests__/FilterUtils-test.js
index d17ef83f74..9f9dd1df27 100644
--- a/web/client/utils/__tests__/FilterUtils-test.js
+++ b/web/client/utils/__tests__/FilterUtils-test.js
@@ -27,7 +27,8 @@ import {
wrapIfNoWildcards,
mergeFiltersToOGC,
convertFiltersToOGC,
- convertFiltersToCQL
+ convertFiltersToCQL,
+ isFilterEmpty
} from '../FilterUtils';
@@ -577,49 +578,57 @@ describe('FilterUtils', () => {
expect(filter).toEqual(expected);
});
it('Test checkOperatorValidity', () => {
- let filterObj = {
- filterFields: [{
- attribute: "attributeNull",
- groupId: 1,
- exception: null,
- operator: "=",
- rowId: "1",
- type: "string",
- value: null
- }, {
- attribute: "attributeUndefined",
- groupId: 1,
- exception: null,
- operator: "=",
- rowId: "2",
- type: "string",
- value: undefined
- }, {
- attribute: "attributeNull2",
- groupId: 1,
- exception: null,
- operator: "isNull",
- rowId: "3",
- type: "string",
- value: undefined
- }, {
- attribute: "attributeUndefined2",
- groupId: 1,
- exception: null,
- operator: "isNull",
- rowId: "4",
- type: "string",
- value: null // valid value for isnull operator
- }]
- };
-
- filterObj.filterFields.forEach((f, i) => {
- let valid = checkOperatorValidity(f.value, f.operator);
- if (i <= 2) {
- expect(valid).toEqual(false);
- } else {
- expect(valid).toEqual(true);
- }
+ const validFilterFields = [{
+ attribute: "operatorIsEqaual",
+ groupId: 1,
+ exception: null,
+ operator: "=",
+ rowId: "1",
+ type: "string",
+ value: "value"
+ }, {
+ attribute: "attributeNull2",
+ groupId: 1,
+ exception: null,
+ operator: "isNull",
+ rowId: "3",
+ type: "string",
+ value: undefined
+ }, {
+ attribute: "attributeUndefined2",
+ groupId: 1,
+ exception: null,
+ operator: "isNull",
+ rowId: "4",
+ type: "string",
+ value: null // valid value for isnull operator
+ }];
+ validFilterFields.forEach((f, i) => {
+ expect(checkOperatorValidity(f.value, f.operator)).toBe(true, `Failed on ${i}` );
+ });
+ const invalidFilterFields = [{
+ attribute: "attributeNull",
+ groupId: 1,
+ exception: null,
+ operator: "=",
+ rowId: "1",
+ type: "string",
+ value: null
+ }, {
+ attribute: "attributeUndefined",
+ groupId: 1,
+ exception: null,
+ operator: "=",
+ rowId: "2",
+ type: "string",
+ value: undefined
+ }, {
+ attribute: "attributeUndefined",
+ groupId: 1,
+ exception: null
+ }];
+ invalidFilterFields.forEach((f, i) => {
+ expect(checkOperatorValidity(f.value, f.operator)).toBe(false, `Failed on ${i}` );
});
});
it('getGetFeatureBase gets viewParams', () => {
@@ -811,7 +820,24 @@ describe('FilterUtils', () => {
value: "isNull"
}]
};
- let expected = 'attributeNullattributeValid';
+ let expected = ''
+ + ''
+ + ''
+ + 'attributeNull'
+ + 'attributeUndefined'
+ + 'attributeValid'
+ + ''
+ + ''
+ + '';
let filter = toOGCFilter("ft_name_test", filterObj);
expect(filter).toEqual(expected);
});
@@ -1196,8 +1222,6 @@ describe('FilterUtils', () => {
expect(cqlStringField("attribute_1", "=", "PRE'")).toBe("\"attribute_1\"='PRE'''");
// test <>
expect(cqlStringField("attribute_1", "<>", "Alabama")).toBe("\"attribute_1\"<>'Alabama'");
- // test isNull
- expect(cqlStringField("attribute_1", "isNull", "")).toBe("isNull(\"attribute_1\")=true");
// test ilike
expect(cqlStringField("attribute_1", "ilike", "A")).toBe("strToLowerCase(\"attribute_1\") LIKE '%a%'");
// test LIKE
@@ -1419,6 +1443,51 @@ describe('FilterUtils', () => {
};
expect(toCQLFilter(filterObject)).toBe("(\"STATE_NAME\"='Alabama' OR (\"STATE_NAME\"='Arizona' OR \"STATE_NAME\"='Arkansas'))");
});
+ it('isNull operator in CQL filter', () => {
+ const filterObj = {
+ "searchUrl": null,
+ "featureTypeConfigUrl": null,
+ "showGeneratedFilter": false,
+ "attributePanelExpanded": true,
+ "spatialPanelExpanded": true,
+ "crossLayerExpanded": true,
+ "showDetailsPanel": false,
+ "groupLevels": 5,
+ "useMapProjection": false,
+ "toolbarEnabled": true,
+ "groupFields": [
+ {
+ "id": 1,
+ "logic": "NOR",
+ "index": 0
+ }
+ ],
+ "maxFeaturesWPS": 5,
+ "filterFields": [
+ {
+ "rowId": 1680880641587,
+ "groupId": 1,
+ "attribute": "STATE_NAME",
+ "operator": "isNull",
+ "value": null,
+ "type": "string",
+ "fieldOptions": {
+ "valuesCount": 0,
+ "currentPage": 1
+ },
+ "exception": null
+ }
+ ],
+ "spatialField": {
+ "method": null,
+ "operation": "INTERSECTS",
+ "geometry": null,
+ "attribute": "the_geom"
+ }
+ };
+ expect(toCQLFilter(filterObj)).toBe('(NOT (isNull("STATE_NAME")=true))');
+
+ });
it('getCrossLayerCqlFilter', () => {
const filter = getCrossLayerCqlFilter({
collectGeometries: {
@@ -2042,6 +2111,26 @@ describe('FilterUtils', () => {
};
filter = processCQLFilterFields(group, objFilter);
expect(filter).toEqual("");
+ // test one operator
+ expect(processCQLFilterFields(group, {
+ filterFields: [{
+ groupId: 1,
+ attribute: "test",
+ type: "string",
+ operator: "=",
+ value: "test"
+ }]
+ })).toEqual(`"test"='test'`);
+ // test is null
+ expect(processCQLFilterFields(group, {
+ filterFields: [{
+ groupId: 1,
+ attribute: "test",
+ type: "string",
+ operator: "isNull"
+ }]
+ })).toEqual(`isNull("test")=true`);
+
});
it('wrapIfNoWildcards', () => {
@@ -2175,4 +2264,43 @@ describe('FilterUtils', () => {
});
});
});
+ it('isFilterEmpty', () => {
+ expect(isFilterEmpty({
+ filterFields: [],
+ spatialField: {},
+ crossLayerFilter: {},
+ filters: []
+ })).toBe(true);
+ expect(isFilterEmpty({
+ filterFields: [{value: 1}],
+ spatialField: {},
+ crossLayerFilter: {},
+ filters: []
+ })).toBe(false);
+ expect(isFilterEmpty({
+ filterFields: [{value: 1}],
+ spatialField: {geometry: {type: 'Point', coordinates: [1, 2]}},
+ crossLayerFilter: {},
+ filters: []
+ })).toBe(false);
+ expect(isFilterEmpty({
+ filterFields: [],
+ spatialField: {},
+ crossLayerFilter: {attribute: 'attr', operation: 'op'},
+ filters: []
+ })).toBe(false);
+ expect(isFilterEmpty({
+ filterFields: [],
+ spatialField: {},
+ crossLayerFilter: {},
+ filters: [{format: 'logic', logic: 'AND', filters: []}]
+ })).toBe(false);
+ expect(isFilterEmpty({
+ filterFields: [{operator: "isNull"}],
+ spatialField: {},
+ crossLayerFilter: {},
+ filters: []
+ })).toBe(false);
+
+ });
});