From 20c08cf3ce631cd2551efe7df4c3707b27fd0c95 Mon Sep 17 00:00:00 2001 From: mahmoud adel <58145645+mahmoudadel54@users.noreply.github.com> Date: Mon, 10 Feb 2025 17:47:31 +0200 Subject: [PATCH] #10736: fix issue of not hiding the enabled/disabled filter icon in case there is no filters after reseting the interactive legend (#10797) --- .../epics/__tests__/styleeditor-test.js | 395 ++++++++++++------ .../plugins/TOC/components/FilterNodeTool.jsx | 3 +- .../__tests__/DefaultLayer-test.jsx | 39 +- web/client/utils/FilterUtils.js | 28 +- .../utils/__tests__/FilterUtils-test.js | 15 +- 5 files changed, 329 insertions(+), 151 deletions(-) diff --git a/web/client/epics/__tests__/styleeditor-test.js b/web/client/epics/__tests__/styleeditor-test.js index 11eea235cc..64e83ba501 100644 --- a/web/client/epics/__tests__/styleeditor-test.js +++ b/web/client/epics/__tests__/styleeditor-test.js @@ -57,6 +57,7 @@ import { testEpic } from './epicTestUtils'; import MockAdapter from 'axios-mock-adapter'; import axios from '../../libs/ajax'; import { INTERACTIVE_LEGEND_ID } from '../../utils/LegendUtils'; +import { setConfigProp } from '../../utils/ConfigUtils'; let mockAxios; @@ -466,141 +467,283 @@ describe('Test styleeditor epics', () => { state); }); - it('test createStyleEpic', (done) => { - const state = { - layers: { - flat: [ - { - id: 'layerId', - name: 'layerName', - url: 'base/web/client/test-resources/geoserver/', - describeFeatureType: {}, - style: 'test_style', - layerFilter: { - filters: [{id: INTERACTIVE_LEGEND_ID, "test": "test"}] - }, - enableInteractiveLegend: true + describe("tests for createStyleEpic", () => { + beforeEach(done => { + setConfigProp('miscSettings', { experimentalInteractiveLegend: true }); + setTimeout(done); + }); + afterEach(done => { + setConfigProp('miscSettings', { }); + setTimeout(done); + }); + it('test createStyleEpic', (done) => { + const state = { + layers: { + flat: [ + { + id: 'layerId', + name: 'layerName', + url: 'base/web/client/test-resources/geoserver/', + describeFeatureType: {}, + style: 'test_style', + layerFilter: { + filters: [{"format": "cql", "body": "test"}] + } + } + ], + selected: [ + 'layerId' + ] + }, + styleeditor: { + service: { + baseUrl: 'base/web/client/test-resources/geoserver/' } - ], - selected: [ - 'layerId' - ] - }, - styleeditor: { - service: { - baseUrl: 'base/web/client/test-resources/geoserver/' } - } - }; - const NUMBER_OF_ACTIONS = 5; - const results = (actions) => { - expect(actions.length).toBe(NUMBER_OF_ACTIONS); - try { - actions.map((action) => { - switch (action.type) { - case LOADING_STYLE: - expect(action.status).toBe(''); - break; - case UPDATE_OPTIONS_BY_OWNER: - expect(action.owner).toBe('styleeditor'); - expect(action.options).toEqual([{}]); - break; - case UPDATE_SETTINGS_PARAMS: - const styleName = action.newParams.style.split('___'); - expect(styleName[0]).toBe('style_title'); - expect(action.newParams.layerFilter).toBeTruthy(); - expect(action.update).toBe(true); - break; - case UPDATE_STATUS: - expect(action.status).toBe(''); - break; - case LOADED_STYLE: - expect(action).toExist(); - break; - default: - expect(action).toBe(false); - } - }); - } catch (e) { - done(e); - } - done(); - }; + }; + const NUMBER_OF_ACTIONS = 5; + const results = (actions) => { + expect(actions.length).toBe(NUMBER_OF_ACTIONS); + try { + actions.map((action) => { + switch (action.type) { + case LOADING_STYLE: + expect(action.status).toBe(''); + break; + case UPDATE_OPTIONS_BY_OWNER: + expect(action.owner).toBe('styleeditor'); + expect(action.options).toEqual([{}]); + break; + case UPDATE_SETTINGS_PARAMS: + const styleName = action.newParams.style.split('___'); + expect(styleName[0]).toBe('style_title'); + expect(action.update).toBe(true); + break; + case UPDATE_STATUS: + expect(action.status).toBe(''); + break; + case LOADED_STYLE: + expect(action).toExist(); + break; + default: + expect(action).toBe(false); + } + }); + } catch (e) { + done(e); + } + done(); + }; - testEpic( - createStyleEpic, - NUMBER_OF_ACTIONS, - createStyle({title: 'style TitLe'}), - results, - state); - }); + testEpic( + createStyleEpic, + NUMBER_OF_ACTIONS, + createStyle({title: 'style TitLe'}), + results, + state); + }); + it('test createStyleEpic if only interactive legend filter was applied', (done) => { + const state = { + layers: { + flat: [ + { + id: 'layerId', + name: 'layerName', + url: 'base/web/client/test-resources/geoserver/', + describeFeatureType: {}, + style: 'test_style', + layerFilter: { + filters: [{id: INTERACTIVE_LEGEND_ID, "test": "test"}] + }, + enableInteractiveLegend: true + } + ], + selected: [ + 'layerId' + ] + }, + styleeditor: { + service: { + baseUrl: 'base/web/client/test-resources/geoserver/' + } + } + }; + const NUMBER_OF_ACTIONS = 5; + const results = (actions) => { + expect(actions.length).toBe(NUMBER_OF_ACTIONS); + try { + actions.map((action) => { + switch (action.type) { + case LOADING_STYLE: + expect(action.status).toBe(''); + break; + case UPDATE_OPTIONS_BY_OWNER: + expect(action.owner).toBe('styleeditor'); + expect(action.options).toEqual([{}]); + break; + case UPDATE_SETTINGS_PARAMS: + const styleName = action.newParams.style.split('___'); + expect(styleName[0]).toBe('style_title'); + expect(action.newParams.layerFilter).toBeFalsy(); + expect(action.update).toBe(true); + break; + case UPDATE_STATUS: + expect(action.status).toBe(''); + break; + case LOADED_STYLE: + expect(action).toExist(); + break; + default: + expect(action).toBe(false); + } + }); + } catch (e) { + done(e); + } + done(); + }; - it('test createStyleEpic with workspace', (done) => { - const workspace = 'test'; - const state = { - layers: { - flat: [ - { - id: 'layerId', - name: `${workspace}:layerName`, - url: 'base/web/client/test-resources/geoserver/', - describeFeatureType: {}, - style: 'test_style' + testEpic( + createStyleEpic, + NUMBER_OF_ACTIONS, + createStyle({title: 'style TitLe'}), + results, + state); + }); + it('test createStyleEpic if a interactive legend filter [with enabled experimentalInteractiveLegend = true + enableInteractiveLegend = true] was applied plus another filter', (done) => { + const state = { + layers: { + flat: [ + { + id: 'layerId', + name: 'layerName', + url: 'base/web/client/test-resources/geoserver/', + describeFeatureType: {}, + style: 'test_style', + layerFilter: { + filters: [{id: INTERACTIVE_LEGEND_ID, "test": "test"}, {"format": "cql", "body": "test"}] + }, + enableInteractiveLegend: true + } + ], + selected: [ + 'layerId' + ] + }, + styleeditor: { + service: { + baseUrl: 'base/web/client/test-resources/geoserver/' } - ], - selected: [ - 'layerId' - ] - }, - styleeditor: { - service: { - baseUrl: 'base/web/client/test-resources/geoserver/' } - } - }; - const NUMBER_OF_ACTIONS = 4; - const results = (actions) => { - expect(actions.length).toBe(NUMBER_OF_ACTIONS); - try { - actions.map((action) => { - switch (action.type) { - case LOADING_STYLE: - expect(action.status).toBe(''); - break; - case UPDATE_OPTIONS_BY_OWNER: - expect(action.owner).toBe('styleeditor'); - expect(action.options).toEqual([{}]); - break; - case UPDATE_SETTINGS_PARAMS: - const styleName = action.newParams.style.split('___'); - expect(styleName[0]).toBe(`${workspace}:style_title`); - expect(action.newParams.layerFilter).toBeFalsy(); - expect(action.update).toBe(true); - break; - case UPDATE_STATUS: - expect(action.status).toBe(''); - break; - case LOADED_STYLE: - expect(action).toExist(); - break; - default: - expect(action).toBe(false); + }; + const NUMBER_OF_ACTIONS = 5; + const results = (actions) => { + expect(actions.length).toBe(NUMBER_OF_ACTIONS); + try { + actions.map((action) => { + switch (action.type) { + case LOADING_STYLE: + expect(action.status).toBe(''); + break; + case UPDATE_OPTIONS_BY_OWNER: + expect(action.owner).toBe('styleeditor'); + expect(action.options).toEqual([{}]); + break; + case UPDATE_SETTINGS_PARAMS: + const styleName = action.newParams.style.split('___'); + expect(styleName[0]).toBe('style_title'); + expect(action.newParams.layerFilter).toBeTruthy(); + expect(action.update).toBe(true); + break; + case UPDATE_STATUS: + expect(action.status).toBe(''); + break; + case LOADED_STYLE: + expect(action).toExist(); + break; + default: + expect(action).toBe(false); + } + }); + } catch (e) { + done(e); + } + done(); + }; + + testEpic( + createStyleEpic, + NUMBER_OF_ACTIONS, + createStyle({title: 'style TitLe'}), + results, + state); + }); + it('test createStyleEpic with workspace', (done) => { + const workspace = 'test'; + const state = { + layers: { + flat: [ + { + id: 'layerId', + name: `${workspace}:layerName`, + url: 'base/web/client/test-resources/geoserver/', + describeFeatureType: {}, + style: 'test_style' + } + ], + selected: [ + 'layerId' + ] + }, + styleeditor: { + service: { + baseUrl: 'base/web/client/test-resources/geoserver/' } - }); - } catch (e) { - done(e); - } - done(); - }; + } + }; + const NUMBER_OF_ACTIONS = 4; + const results = (actions) => { + expect(actions.length).toBe(NUMBER_OF_ACTIONS); + try { + actions.map((action) => { + switch (action.type) { + case LOADING_STYLE: + expect(action.status).toBe(''); + break; + case UPDATE_OPTIONS_BY_OWNER: + expect(action.owner).toBe('styleeditor'); + expect(action.options).toEqual([{}]); + break; + case UPDATE_SETTINGS_PARAMS: + const styleName = action.newParams.style.split('___'); + expect(styleName[0]).toBe(`${workspace}:style_title`); + expect(action.newParams.layerFilter).toBeFalsy(); + expect(action.update).toBe(true); + break; + case UPDATE_STATUS: + expect(action.status).toBe(''); + break; + case LOADED_STYLE: + expect(action).toExist(); + break; + default: + expect(action).toBe(false); + } + }); + } catch (e) { + done(e); + } + done(); + }; - testEpic( - createStyleEpic, - NUMBER_OF_ACTIONS, - createStyle({title: 'style TitLe'}), - results, - state); + testEpic( + createStyleEpic, + NUMBER_OF_ACTIONS, + createStyle({title: 'style TitLe'}), + results, + state); + }); }); - it('test updateStyleCodeEpic', (done) => { const state = { layers: { diff --git a/web/client/plugins/TOC/components/FilterNodeTool.jsx b/web/client/plugins/TOC/components/FilterNodeTool.jsx index 4fd44b9401..e75381666d 100644 --- a/web/client/plugins/TOC/components/FilterNodeTool.jsx +++ b/web/client/plugins/TOC/components/FilterNodeTool.jsx @@ -6,6 +6,7 @@ * LICENSE file in the root directory of this source tree. */ import React from 'react'; +import { isFilterEmpty } from '../../../utils/FilterUtils'; const FilterNodeTool = ({ node, @@ -14,7 +15,7 @@ const FilterNodeTool = ({ }) => { const ItemComponent = itemComponent; const { layerFilter } = node || {}; - if (!layerFilter || !ItemComponent) { + if (isFilterEmpty(layerFilter) || !ItemComponent) { return null; } const { disabled } = layerFilter || {}; diff --git a/web/client/plugins/TOC/components/__tests__/DefaultLayer-test.jsx b/web/client/plugins/TOC/components/__tests__/DefaultLayer-test.jsx index 7aab1562cc..0322a4d1f9 100644 --- a/web/client/plugins/TOC/components/__tests__/DefaultLayer-test.jsx +++ b/web/client/plugins/TOC/components/__tests__/DefaultLayer-test.jsx @@ -413,20 +413,55 @@ describe('test DefaultLayer module component', () => { expect(errorTooltip).toBeFalsy(); }); - it('should display the layer filter button', () => { + it('should display the layer filter button if there is filters in layerFilter like filterFields/spatialFilter/legendFilter', () => { const layer = { id: 'layer00', name: 'layer00', title: 'Layer', visibility: false, opacity: 0.5, - layerFilter: {} + layerFilter: { + "filterFields": [ + { + "rowId": 1, + "groupId": 1, + "attribute": "FIELD_01", + "operator": "=", + "value": "value01", + "type": "string", + "fieldOptions": { + "valuesCount": 0, + "currentPage": 1 + }, + "exception": null, + "loading": false, + "openAutocompleteMenu": false, + "options": { + "FIELD_01": [] + } + } + ] + } }; ReactDOM.render(, document.getElementById("container")); const filter = document.querySelector('.glyphicon-filter'); expect(filter).toBeTruthy(); }); + it('should hide the layer filter button if layerFilter is empty from any kind filters', () => { + const layer = { + id: 'layer00', + name: 'layer00', + title: 'Layer', + visibility: false, + opacity: 0.5, + layerFilter: {} + }; + + ReactDOM.render(, document.getElementById("container")); + const filter = document.querySelector('.glyphicon-filter'); + expect(filter).toBeFalsy(); + }); it('should not display the layer filter button when hideFilter is true', () => { const layer = { diff --git a/web/client/utils/FilterUtils.js b/web/client/utils/FilterUtils.js index e08dbd5ea4..70d11bb484 100644 --- a/web/client/utils/FilterUtils.js +++ b/web/client/utils/FilterUtils.js @@ -31,6 +31,7 @@ export const cqlToOgc = (cqlFilter, fOpts) => { import { get, isNil, isArray, find, findIndex, isString, flatten } from 'lodash'; import { INTERACTIVE_LEGEND_ID } from './LegendUtils'; +import { getMiscSetting } from './ConfigUtils'; let FilterUtils; const wrapValueWithWildcard = (value, condition) => { @@ -1332,7 +1333,7 @@ export const updateLayerLegendFilter = (layerFilterObj, legendFilter) => { ...filterObj, filters: filterObj?.filters?.filter(f => f.id !== INTERACTIVE_LEGEND_ID) }; } - let newFilter = filterObj ? filterObj : undefined; + let newFilter = !isFilterEmpty(filterObj) ? filterObj : undefined; return newFilter; } let interactiveLegendFilters = isLegendFilterExist ? isLegendFilterExist.filters || [] : []; @@ -1355,7 +1356,7 @@ export const updateLayerLegendFilter = (layerFilterObj, legendFilter) => { } let newFilter = { ...(filterObj || {}), filters: [ - ...(filterObj?.filters?.filter(f => f.id !== INTERACTIVE_LEGEND_ID) || []), ...[ + ...(filterObj?.filters?.filter(f => f.id !== INTERACTIVE_LEGEND_ID) || []), ...(interactiveLegendFilters?.length ? [ { "id": INTERACTIVE_LEGEND_ID, "format": "logic", @@ -1363,10 +1364,10 @@ export const updateLayerLegendFilter = (layerFilterObj, legendFilter) => { "logic": "OR", "filters": [...interactiveLegendFilters] } - ] + ] : []) ] }; - return newFilter; + return !isFilterEmpty(newFilter) ? newFilter : undefined; }; /** @@ -1407,7 +1408,7 @@ export const updateLayerWFSVectorLegendFilter = (layerFilterObj, legendGeostyler ...filterObj, filters: filterObj?.filters?.filter(f => f.id !== INTERACTIVE_LEGEND_ID) }; } - let newFilter = filterObj ? filterObj : undefined; + let newFilter = !isFilterEmpty(filterObj) ? filterObj : undefined; return newFilter; } let interactiveLegendFilters = isLegendFilterExist ? isLegendFilterExist.filters || [] : []; @@ -1425,7 +1426,7 @@ export const updateLayerWFSVectorLegendFilter = (layerFilterObj, legendGeostyler } let newFilter = { ...(filterObj || {}), filters: [ - ...(filterObj?.filters?.filter(f => f.id !== INTERACTIVE_LEGEND_ID) || []), ...[ + ...(filterObj?.filters?.filter(f => f.id !== INTERACTIVE_LEGEND_ID) || []), ...(interactiveLegendFilters?.length ? [ { "id": INTERACTIVE_LEGEND_ID, "format": "logic", @@ -1433,31 +1434,32 @@ export const updateLayerWFSVectorLegendFilter = (layerFilterObj, legendGeostyler "logic": "OR", "filters": [...interactiveLegendFilters] } - ] + ] : []) ] }; - return newFilter; + return !isFilterEmpty(newFilter) ? newFilter : undefined; }; export function resetLayerLegendFilter(layer, reason, value) { + const experimentalInteractiveLegend = getMiscSetting('experimentalInteractiveLegend', false); const isResetForStyle = reason === 'style'; // here the reason for reset is change 'style' or change the enable/disable interactive legend config 'disableEnableInteractiveLegend' let needReset = false; if (isResetForStyle) { needReset = isResetForStyle && value !== layer.style; } // check if the layer has interactive legend or not, if not cancel the epic - const isLayerWithJSONLegend = layer?.enableInteractiveLegend; - let filterObj = layer.layerFilter ? layer.layerFilter : undefined; - if (!needReset || !isLayerWithJSONLegend || !filterObj) return false; + const isLayerHasInterActiveLegend = experimentalInteractiveLegend && layer?.enableInteractiveLegend; + let filterObj = !isFilterEmpty(layer.layerFilter) ? layer.layerFilter : undefined; + if (!needReset || !isLayerHasInterActiveLegend || !filterObj) return false; // reset thte filter if legendCQLFilter is empty const isLegendFilterExist = filterObj?.filters?.find(f => f.id === INTERACTIVE_LEGEND_ID); if (isLegendFilterExist) { filterObj = { ...filterObj, filters: filterObj?.filters?.filter(f => f.id !== INTERACTIVE_LEGEND_ID) }; - return filterObj; + return !isFilterEmpty(filterObj) ? filterObj : undefined; } - return filterObj; + return !isFilterEmpty(filterObj) ? filterObj : undefined; } FilterUtils = { diff --git a/web/client/utils/__tests__/FilterUtils-test.js b/web/client/utils/__tests__/FilterUtils-test.js index 019bb0d779..170bd87f9d 100644 --- a/web/client/utils/__tests__/FilterUtils-test.js +++ b/web/client/utils/__tests__/FilterUtils-test.js @@ -2436,9 +2436,8 @@ describe('FilterUtils', () => { ] }; const updatedFilterObj = updateLayerLegendFilter(layerFilterObj); - expect(updatedFilterObj).toBeTruthy(); - expect(updatedFilterObj.filters.length).toEqual(0); - expect(updatedFilterObj.filters.find(i => i.id === INTERACTIVE_LEGEND_ID)).toBeFalsy(); + // check if there is no any filters --> updatedFilterObj will be undefined + expect(updatedFilterObj).toBeFalsy(); }); it('test resetLayerLegendFilter in case change wms style', () => { const layerFilterObj = { @@ -2491,9 +2490,8 @@ describe('FilterUtils', () => { style: "style_01" }; const updatedFilterObj = resetLayerLegendFilter(layer, 'style', 'style_02'); - expect(updatedFilterObj).toBeTruthy(); - expect(updatedFilterObj.filters.length).toEqual(0); - expect(updatedFilterObj.filters.find(i => i.id === INTERACTIVE_LEGEND_ID)).toBeFalsy(); + // check if there is no any filters --> updatedFilterObj will be undefined + expect(updatedFilterObj).toBeFalsy(); }); // for WFS it('test updateLayerWFSVectorLegendFilter for wfs, simple filter', () => { @@ -2596,8 +2594,7 @@ describe('FilterUtils', () => { ] }; const updatedFilterObj = updateLayerWFSVectorLegendFilter(layerFilterObj); - expect(updatedFilterObj).toBeTruthy(); - expect(updatedFilterObj.filters.length).toEqual(0); - expect(updatedFilterObj.filters.find(i => i.id === INTERACTIVE_LEGEND_ID)).toBeFalsy(); + // check if there is no any filters --> updatedFilterObj will be undefined + expect(updatedFilterObj).toBeFalsy(); }); });