Skip to content

Commit

Permalink
geosolutions-it#10236: Fix - Legend filter persisting issue when edit…
Browse files Browse the repository at this point in the history
…ing style
  • Loading branch information
dsuren1 committed Dec 16, 2024
1 parent e3a7348 commit e0cbee5
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 59 deletions.
20 changes: 15 additions & 5 deletions web/client/api/WMS.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import urlUtil from 'url';
import { isArray, castArray, get } from 'lodash';
import xml2js from 'xml2js';
import axios from '../libs/ajax';
import { getConfigProp } from '../utils/ConfigUtils';
import ConfigUtils, { getConfigProp } from '../utils/ConfigUtils';
import { getWMSBoundingBox } from '../utils/CoordinatesUtils';
import { isValidGetMapFormat, isValidGetFeatureInfoFormat } from '../utils/WMSUtils';
const capabilitiesCache = {};
Expand Down Expand Up @@ -323,15 +323,25 @@ export const getSupportedFormat = (url, includeGFIFormats = false) => {

let layerLegendJsonData = {};
export const getJsonWMSLegend = (url) => {
const request = layerLegendJsonData[url]
? () => Promise.resolve(layerLegendJsonData[url])
: () => axios.get(url).then((response) => {
let request;

// enables caching of the JSON legend for a specified duration,
// while providing the possibility of re-fetching the legend data in case of external modifications
const cached = layerLegendJsonData[url];
if (cached && new Date().getTime() < cached.timestamp + (ConfigUtils.getConfigProp('cacheExpire') || 60) * 1000) {
request = () => Promise.resolve(cached.data);
} else {
request = () => axios.get(url).then((response) => {
if (typeof response?.data === 'string' && response.data.includes("Exception")) {
throw new Error("Faild to get json legend");
}
layerLegendJsonData[url] = response?.data?.Legend;
layerLegendJsonData[url] = {
timestamp: new Date().getTime(),
data: response?.data?.Legend
};
return response?.data?.Legend || [];
});
}
return request().then((data) => data).catch(err => {
throw err;
});
Expand Down
45 changes: 27 additions & 18 deletions web/client/plugins/TOC/components/StyleBasedWMSJsonLegend.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ class StyleBasedWMSJsonLegend extends React.Component {
const prevLayerStyle = prevProps?.layer?.style;
const currentLayerStyle = this.props?.layer?.style;

const prevFilter = getLayerFilterByLegendFormat(prevProps?.layer, LEGEND_FORMAT.JSON);
const currFilter = getLayerFilterByLegendFormat(this.props?.layer, LEGEND_FORMAT.JSON);
const [prevFilter, currFilter] = [prevProps?.layer, this.props?.layer]
.map(_layer => getLayerFilterByLegendFormat(_layer, LEGEND_FORMAT.JSON));

// get the new json legend and rerender in case of change in style or layer filter
if (currentLayerStyle !== prevLayerStyle
if (!isEqual(prevLayerStyle, currentLayerStyle)
|| !isEqual(prevFilter, currFilter)
|| !isEqual(prevProps.mapBbox, this.props.mapBbox)
) {
Expand All @@ -87,6 +87,7 @@ class StyleBasedWMSJsonLegend extends React.Component {
const newLayerFilter = updateLayerLegendFilter(this.props?.layer?.layerFilter);
this.props.onChange({ layerFilter: newLayerFilter });
}

getLegendData() {
let jsonLegendUrl = this.getUrl(this.props);
if (!jsonLegendUrl) {
Expand All @@ -109,6 +110,7 @@ class StyleBasedWMSJsonLegend extends React.Component {
}
return null;
};

getUrl = (props, urlIdx) => {
if (props.layer && props.layer.type === "wms" && props.layer.url) {
const layer = props.layer;
Expand Down Expand Up @@ -147,9 +149,10 @@ class StyleBasedWMSJsonLegend extends React.Component {
}
return '';
}

renderRules = (rules) => {
const isLegendFilterIncluded = get(this.props, 'layer.layerFilter.filters', []).find(f => f.id === INTERACTIVE_LEGEND_ID);
const legendFilters = isLegendFilterIncluded ? isLegendFilterIncluded?.filters : [];
const interactiveLegendFilters = get(this.props, 'layer.layerFilter.filters', []).find(f => f.id === INTERACTIVE_LEGEND_ID);
const legendFilters = get(interactiveLegendFilters, 'filters', []);
const isPreviousFilterValid = this.checkPreviousFiltersAreValid(rules, legendFilters);
return (
<>
Expand All @@ -163,22 +166,26 @@ class StyleBasedWMSJsonLegend extends React.Component {
<Message msgId={"layerProperties.interactiveLegend.resetLegendFilter"} />
</ButtonWithTooltip>
</Alert> : null}
{(rules || []).map((rule) => {
const activeFilter = legendFilters?.some(f => f.id === rule.filter);
const isFilterDisabled = this.props?.layer?.layerFilter?.disabled;
return (
<div
className={`wms-json-legend-rule ${isFilterDisabled || this.props.owner === 'legendPreview' || !rule?.filter ? "" : "filter-enabled "} ${activeFilter ? 'active' : ''}`}
key={rule.filter}
onClick={() => this.filterWMSLayerHandler(rule.filter)}>
<WMSJsonLegendIcon rule={rule} />
<span>{rule.name || rule.title || ''}</span>
</div>
);
})}
{isEmpty(rules)
? <Message msgId={"layerProperties.interactiveLegend.noLegendData"} />
: rules.map((rule, idx) => {
const activeFilter = legendFilters?.some(f => f.id === rule.filter);
const isFilterDisabled = this.props?.layer?.layerFilter?.disabled;
return (
<div
className={`wms-json-legend-rule ${isFilterDisabled || this.props.owner === 'legendPreview' || !rule?.filter ? "" : "filter-enabled "} ${activeFilter ? 'active' : ''}`}
key={`${rule.filter}-${idx}`}
onClick={() => this.filterWMSLayerHandler(rule.filter)}>
<WMSJsonLegendIcon rule={rule} />
<span>{rule.name || rule.title || ''}</span>
</div>
);
})
}
</>
);
};

render() {
if (!this.state.error && this.props.layer && this.props.layer.type === "wms" && this.props.layer.url) {
return <>
Expand All @@ -202,12 +209,14 @@ class StyleBasedWMSJsonLegend extends React.Component {
</OverlayTrigger>
</div>);
}

filterWMSLayerHandler = (filter) => {
const isFilterDisabled = this.props?.layer?.layerFilter?.disabled;
if (!filter || isFilterDisabled) return;
const newLayerFilter = updateLayerLegendFilter(this.props?.layer?.layerFilter, filter);
this.props.onChange({ layerFilter: newLayerFilter });
};

checkPreviousFiltersAreValid = (rules, prevLegendFilters) => {
const rulesFilters = rules.map(rule => rule.filter);
return prevLegendFilters?.every(f => rulesFilters.includes(f.id));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,39 @@ import axios from '../../../../libs/ajax';
import StyleBasedWMSJsonLegend from '../StyleBasedWMSJsonLegend';
import expect from 'expect';
import TestUtils from 'react-dom/test-utils';
import { INTERACTIVE_LEGEND_ID } from '../../../../utils/LegendUtils';

let mockAxios;
const rules = [
{
"name": ">= 159.05 and < 5062.5",
"filter": "[field >= '159.05' AND field < '5062.5']",
"symbolizers": [{"Polygon": {
"uom": "in/72",
"stroke": "#ffffff",
"stroke-width": "1.0",
"stroke-opacity": "0.35",
"stroke-linecap": "butt",
"stroke-linejoin": "miter",
"fill": "#8DD3C7",
"fill-opacity": "0.75"
}}]
},
{
"name": ">= 5062.5 and < 20300.35",
"filter": "[field >= '5062.5' AND field < '20300.35']",
"symbolizers": [{"Polygon": {
"uom": "in/72",
"stroke": "#ffffff",
"stroke-width": "1.0",
"stroke-opacity": "0.35",
"stroke-linecap": "butt",
"stroke-linejoin": "miter",
"fill": "#ABD9C5",
"fill-opacity": "0.75"
}}]
}
];

describe('test StyleBasedWMSJsonLegend module component', () => {
beforeEach((done) => {
Expand Down Expand Up @@ -45,35 +76,7 @@ describe('test StyleBasedWMSJsonLegend module component', () => {
"Legend": [{
"layerName": "layer00",
"title": "Layer",
"rules": [
{
"name": ">= 159.05 and < 5062.5",
"filter": "[field >= '159.05' AND field < '5062.5']",
"symbolizers": [{"Polygon": {
"uom": "in/72",
"stroke": "#ffffff",
"stroke-width": "1.0",
"stroke-opacity": "0.35",
"stroke-linecap": "butt",
"stroke-linejoin": "miter",
"fill": "#8DD3C7",
"fill-opacity": "0.75"
}}]
},
{
"name": ">= 5062.5 and < 20300.35",
"filter": "[field >= '5062.5' AND field < '20300.35']",
"symbolizers": [{"Polygon": {
"uom": "in/72",
"stroke": "#ffffff",
"stroke-width": "1.0",
"stroke-opacity": "0.35",
"stroke-linecap": "butt",
"stroke-linejoin": "miter",
"fill": "#ABD9C5",
"fill-opacity": "0.75"
}}]
}]
rules
}]
}];
});
Expand All @@ -89,4 +92,74 @@ describe('test StyleBasedWMSJsonLegend module component', () => {
expect(legendRuleElem).toBeTruthy();
expect(legendRuleElem.length).toEqual(2);
});
it('tests legend with empty rules', async() => {
const l = {
name: 'layer00',
title: 'Layer',
visibility: true,
storeIndex: 9,
type: 'wms',
url: 'http://localhost:8080/geoserver1/wms'
};
mockAxios.onGet(/geoserver1/).reply(() => {
return [200, {
"Legend": [{
"layerName": "layer01",
"title": "Layer1",
"rules": []
}]
}];
});
const comp = ReactDOM.render(<StyleBasedWMSJsonLegend legendHeight={50} legendWidth={50} layer={l} />, document.getElementById("container"));
await TestUtils.act(async() => comp);

const domNode = ReactDOM.findDOMNode(comp);
expect(domNode).toBeTruthy();

const legendElem = document.querySelector('.wms-legend');
expect(legendElem).toBeTruthy();
expect(legendElem.innerText).toBe('layerProperties.interactiveLegend.noLegendData');
const legendRuleElem = domNode.querySelectorAll('.wms-json-legend-rule');
expect(legendRuleElem.length).toBe(0);
});
it('tests legend with incompatible filter rules', async() => {
const l = {
name: 'layer00',
title: 'Layer',
visibility: true,
storeIndex: 9,
type: 'wms',
url: 'http://localhost:8080/geoserver2/wms',
layerFilter: {
filters: [{
id: INTERACTIVE_LEGEND_ID,
filters: [{
id: 'filter1'
}]
}]
}
};
mockAxios.onGet(/geoserver2/).reply(() => {
return [200, {
"Legend": [{
"layerName": "layer01",
"title": "Layer1",
rules
}]
}];
});
const comp = ReactDOM.render(<StyleBasedWMSJsonLegend legendHeight={50} legendWidth={50} layer={l} />, document.getElementById("container"));
await TestUtils.act(async() => comp);

const domNode = ReactDOM.findDOMNode(comp);
expect(domNode).toBeTruthy();

const legendElem = document.querySelector('.wms-legend');
expect(legendElem).toBeTruthy();
const legendRuleElem = domNode.querySelector('.wms-legend .alert-warning');
expect(legendRuleElem).toBeTruthy();
expect(legendRuleElem.innerText).toContain('layerProperties.interactiveLegend.incompatibleFilterWarning');
const resetLegendFilter = domNode.querySelector('.wms-legend .alert-warning button');
expect(resetLegendFilter).toBeTruthy();
});
});
3 changes: 2 additions & 1 deletion web/client/translations/data.de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,8 @@
"disableFeaturesEditing": "Deaktivieren Sie die Bearbeitung der Attributtabelle",
"interactiveLegend": {
"incompatibleFilterWarning": "Angewendete Legendenfilter sind mit dem aktiven Ebenenfilter nicht kompatibel. Klicken Sie auf Zurücksetzen, um die Legendenfilter zu entfernen",
"resetLegendFilter": "Zurücksetzen"
"resetLegendFilter": "Zurücksetzen",
"noLegendData": "Keine Legenden Elemente zum Anzeigen"
}
},
"localizedInput": {
Expand Down
3 changes: 2 additions & 1 deletion web/client/translations/data.en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,8 @@
"disableFeaturesEditing": "Disable editing on Attribute table",
"interactiveLegend": {
"incompatibleFilterWarning": "Applied legend filters are incompatible with the active layer filter. Click on reset to remove legend filters",
"resetLegendFilter": "Reset"
"resetLegendFilter": "Reset",
"noLegendData": "No legend items to show"
}
},
"localizedInput": {
Expand Down
3 changes: 2 additions & 1 deletion web/client/translations/data.es-ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,8 @@
"disableFeaturesEditing": "Deshabilitar la edición en la tabla de atributos",
"interactiveLegend": {
"incompatibleFilterWarning": "Los filtros de leyenda aplicados son incompatibles con el filtro de capa activo. Haga clic en restablecer para eliminar los filtros de leyenda",
"resetLegendFilter": "Restablecer"
"resetLegendFilter": "Restablecer",
"noLegendData": "No hay elementos de leyenda para mostrar"
}
},
"localizedInput": {
Expand Down
3 changes: 2 additions & 1 deletion web/client/translations/data.fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,8 @@
"disableFeaturesEditing": "Désactiver la modification sur la table attributaire",
"interactiveLegend": {
"incompatibleFilterWarning": "Les filtres de légende appliqués sont incompatibles avec le filtre de couche actif. Cliquez sur réinitialiser pour supprimer les filtres de légende",
"resetLegendFilter": "Réinitialiser"
"resetLegendFilter": "Réinitialiser",
"noLegendData": "Aucun élément de légende à afficher"
}
},
"localizedInput": {
Expand Down
4 changes: 2 additions & 2 deletions web/client/translations/data.it-IT.json
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,8 @@
"disableFeaturesEditing": "Disabilita la modifica sulla tabella degli attributi",
"interactiveLegend": {
"incompatibleFilterWarning": "I filtri della legenda applicati sono incompatibili con il filtro del livello attivo. Clicca su reset per rimuovere i filtri della legenda",
"resetLegendFilter": "Reset"
}
"resetLegendFilter": "Reset",
"noLegendData": "Nessun elemento della legenda da mostrare" }
},
"localizedInput": {
"localize": "Traduci questo testo...",
Expand Down
2 changes: 1 addition & 1 deletion web/client/utils/LegendUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const LEGEND_FORMAT = {
JSON: "application/json"
};

export const getLayerFilterByLegendFormat = (layer, format) => {
export const getLayerFilterByLegendFormat = (layer, format = LEGEND_FORMAT.JSON) => {
const layerFilter = layer?.layerFilter;
if (layer && layer.type === "wms" && layer.url) {
if (format === LEGEND_FORMAT.JSON && !isEmpty(layerFilter)) {
Expand Down

0 comments on commit e0cbee5

Please sign in to comment.