Skip to content

Commit

Permalink
Fix #10026: Interactive legend for TOC layers [WMS] (#10180)
Browse files Browse the repository at this point in the history
  • Loading branch information
mahmoudadel54 committed Apr 22, 2024
1 parent 7396436 commit b0144c0
Show file tree
Hide file tree
Showing 24 changed files with 1,139 additions and 51 deletions.
16 changes: 16 additions & 0 deletions web/client/api/WMS.js
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,22 @@ export const getSupportedFormat = (url, includeGFIFormats = false) => {
.catch(() => includeGFIFormats ? { imageFormats: [], infoFormats: [] } : []);
};

let layerLegendJsonData = {};
export const getJsonWMSLegend = (url) => {
const request = layerLegendJsonData[url]
? () => Promise.resolve(layerLegendJsonData[url])
: () => 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;
return response?.data?.Legend || [];
});
return request().then((data) => data).catch(err => {
throw err;
});
};

const Api = {
flatLayers,
parseUrl,
Expand Down
67 changes: 67 additions & 0 deletions web/client/api/__tests__/WMS-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,70 @@ describe('Test correctness of the WMS APIs (mock axios)', () => {
API.describeLayer(url, layers, { query });
});
});

describe('Test get json wms graphic legend (mock axios)', () => {
beforeEach(done => {
mockAxios = new MockAdapter(axios);
setTimeout(done);
});

afterEach(done => {
mockAxios.restore();
setTimeout(done);
});

it('get json wms graphic legend', (done) => {
let url = "http://localhost:8080/geoserver/wms?service=WMS&request=GetLegendGraphic&format=application/json&layers=workspace:layer&style=pophade&version=1.3.0&SLD_VERSION=1.1.0";
mockAxios.onGet().reply(() => {
return [ 200, {
"Legend": [{
"layerName": "layer",
"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"
}}]
}]
}]
}];
});

API.getJsonWMSLegend(url).then(result => {
try {
expect(result.length).toEqual(1);
expect(result[0]).toBeTruthy();
expect(result[0].layerName).toBeTruthy();
expect(result[0].layerName).toEqual('layer');
expect(result[0].rules).toBeTruthy();
expect(result[0].rules.length).toEqual(2);
done();
} catch (ex) {
done(ex);
}
});
});
});
57 changes: 45 additions & 12 deletions web/client/components/TOC/fragments/settings/Display.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ import InfoPopover from '../../../widgets/widget/InfoPopover';
import Legend from '../../../../plugins/TOC/components/Legend';
import VisibilityLimitsForm from './VisibilityLimitsForm';
import { ServerTypes } from '../../../../utils/LayersUtils';
import {updateLayerLegendFilter} from '../../../../utils/FilterUtils';
import Select from 'react-select';
import { getSupportedFormat } from '../../../../api/WMS';
import WMSCacheOptions from './WMSCacheOptions';
import ThreeDTilesSettings from './ThreeDTilesSettings';
import ModelTransformation from './ModelTransformation';
import StyleBasedWMSJsonLegend from '../../../../plugins/TOC/components/StyleBasedWMSJsonLegend';
export default class extends React.Component {
static propTypes = {
opacityText: PropTypes.node,
Expand Down Expand Up @@ -261,7 +263,26 @@ export default class extends React.Component {
<Col xs={12} className={"legend-label"}>
<label key="legend-options-title" className="control-label"><Message msgId="layerProperties.legendOptions.title" /></label>
</Col>
<Col xs={12} sm={6} className="first-selectize">
{ this.props.element?.serverType !== ServerTypes.NO_VENDOR &&
<Col xs={12} className="first-selectize">
<Checkbox
data-qa="display-interactive-legend-option"
value="enableInteractiveLegend"
key="enableInteractiveLegend"
onChange={(e) => {
if (!e.target.checked) {
const newLayerFilter = updateLayerLegendFilter(this.props.element.layerFilter);
this.props.onChange("layerFilter", newLayerFilter );
}
this.props.onChange("enableInteractiveLegend", e.target.checked);
}}
checked={this.props.element.enableInteractiveLegend} >
<Message msgId="layerProperties.enableInteractiveLegendInfo.label"/>
&nbsp;<InfoPopover text={<Message msgId="layerProperties.enableInteractiveLegendInfo.info" />} />
</Checkbox>
</Col>
}
{!this.props.element?.enableInteractiveLegend && <><Col xs={12} sm={6} className="first-selectize">
<FormGroup validationState={this.getValidationState("legendWidth")}>
<ControlLabel><Message msgId="layerProperties.legendOptions.legendWidth" /></ControlLabel>
<IntlNumberFormControl
Expand Down Expand Up @@ -290,20 +311,32 @@ export default class extends React.Component {
onBlur={this.onBlur}
/>
</FormGroup>
</Col>
</Col></>}
<Col xs={12} className="legend-preview">
<ControlLabel><Message msgId="layerProperties.legendOptions.legendPreview" /></ControlLabel>
<div style={this.setOverFlow() && this.state.containerStyle || {}} ref={this.containerRef} >
<Legend
style={this.setOverFlow() && {} || undefined}
layer={this.props.element}
legendHeight={
this.useLegendOptions() && this.state.legendOptions.legendHeight || undefined}
legendWidth={
this.useLegendOptions() && this.state.legendOptions.legendWidth || undefined}
language={
this.props.isLocalizedLayerStylesEnabled ? this.props.currentLocaleLanguage : undefined}
/>
{ this.props.element?.enableInteractiveLegend ?
<StyleBasedWMSJsonLegend
owner="legendPreview"
style={this.setOverFlow() && {} || undefined}
layer={this.props.element}
legendHeight={
this.useLegendOptions() && this.state.legendOptions.legendHeight || undefined}
legendWidth={
this.useLegendOptions() && this.state.legendOptions.legendWidth || undefined}
language={
this.props.isLocalizedLayerStylesEnabled ? this.props.currentLocaleLanguage : undefined}
/> :
<Legend
style={this.setOverFlow() && {} || undefined}
layer={this.props.element}
legendHeight={
this.useLegendOptions() && this.state.legendOptions.legendHeight || undefined}
legendWidth={
this.useLegendOptions() && this.state.legendOptions.legendWidth || undefined}
language={
this.props.isLocalizedLayerStylesEnabled ? this.props.currentLocaleLanguage : undefined}
/>}
</div>
</Col>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ describe('test Layer Properties Display module component', () => {
// wrap in a stateful component, stateless components render return null
// see: https://facebook.github.io/react/docs/top-level-api.html#reactdom.render
const comp = ReactDOM.render(<Display element={l} settings={settings} />, document.getElementById("container"));
expect(comp).toExist();
expect(comp).toBeTruthy();
const inputs = ReactTestUtils.scryRenderedDOMComponentsWithTag( comp, "input" );
expect(inputs).toExist();
expect(inputs).toBeTruthy();
expect(inputs.length).toBe(5);
ReactTestUtils.Simulate.focus(inputs[0]);
expect(inputs[0].value).toBe('100');
Expand All @@ -71,10 +71,10 @@ describe('test Layer Properties Display module component', () => {
// wrap in a stateful component, stateless components render return null
// see: https://facebook.github.io/react/docs/top-level-api.html#reactdom.render
const comp = ReactDOM.render(<Display element={l} settings={settings} onChange={handlers.onChange}/>, document.getElementById("container"));
expect(comp).toExist();
expect(comp).toBeTruthy();
const inputs = ReactTestUtils.scryRenderedDOMComponentsWithTag( comp, "input" );
expect(inputs).toExist();
expect(inputs.length).toBe(13);
expect(inputs).toBeTruthy();
expect(inputs.length).toBe(14);
ReactTestUtils.Simulate.focus(inputs[2]);
expect(inputs[2].value).toBe('70');
inputs[8].click();
Expand Down Expand Up @@ -109,7 +109,7 @@ describe('test Layer Properties Display module component', () => {
};

const comp = ReactDOM.render(<Display element={l} settings={settings} onChange={handlers.onChange}/>, document.getElementById("container"));
expect(comp).toExist();
expect(comp).toBeTruthy();
const formatRefresh = ReactTestUtils.scryRenderedDOMComponentsWithClass( comp, "format-refresh" );
ReactTestUtils.Simulate.click(formatRefresh[0]);
});
Expand Down Expand Up @@ -165,7 +165,7 @@ describe('test Layer Properties Display module component', () => {
};
ReactDOM.render(<Display isLocalizedLayerStylesEnabled element={l} settings={settings}/>, document.getElementById("container"));
const isLocalizedLayerStylesOption = document.querySelector('[data-qa="display-lacalized-layer-styles-option"]');
expect(isLocalizedLayerStylesOption).toExist();
expect(isLocalizedLayerStylesOption).toBeTruthy();
});

it('tests Display component for wms with force proxy option displayed', () => {
Expand Down Expand Up @@ -243,11 +243,11 @@ describe('test Layer Properties Display module component', () => {
onChange() {}
};
const comp = ReactDOM.render(<Display element={l} settings={settings} onChange={handlers.onChange}/>, document.getElementById("container"));
expect(comp).toExist();
expect(comp).toBeTruthy();
const labels = ReactTestUtils.scryRenderedDOMComponentsWithClass( comp, "control-label" );
const inputs = ReactTestUtils.scryRenderedDOMComponentsWithTag( comp, "input" );
const legendWidth = inputs[11];
const legendHeight = inputs[12];
const legendWidth = inputs[12];
const legendHeight = inputs[13];
// Default legend values
expect(legendWidth.value).toBe('12');
expect(legendHeight.value).toBe('12');
Expand All @@ -269,7 +269,8 @@ describe('test Layer Properties Display module component', () => {
legendOptions: {
legendWidth: 15,
legendHeight: 15
}
},
enableInteractiveLegend: false
};
const settings = {
options: {
Expand All @@ -281,14 +282,15 @@ describe('test Layer Properties Display module component', () => {
};
let spy = expect.spyOn(handlers, "onChange");
const comp = ReactDOM.render(<Display element={l} settings={settings} onChange={handlers.onChange}/>, document.getElementById("container"));
expect(comp).toExist();
expect(comp).toBeTruthy();
const inputs = ReactTestUtils.scryRenderedDOMComponentsWithTag( comp, "input" );
const legendPreview = ReactTestUtils.scryRenderedDOMComponentsWithClass( comp, "legend-preview" );
expect(legendPreview).toExist();
expect(inputs).toExist();
expect(inputs.length).toBe(13);
let legendWidth = inputs[11];
let legendHeight = inputs[12];
expect(legendPreview).toBeTruthy();
expect(inputs).toBeTruthy();
expect(inputs.length).toBe(14);
let interactiveLegendConfig = inputs[11];
let legendWidth = inputs[12];
let legendHeight = inputs[13];
const img = ReactTestUtils.scryRenderedDOMComponentsWithTag(comp, 'img');

// Check value in img src
Expand Down Expand Up @@ -329,6 +331,14 @@ describe('test Layer Properties Display module component', () => {
expect(params.get("width")).toBe('12');
expect(params.get("height")).toBe('12');

// change enableInteractiveLegend to enable interactive legend
interactiveLegendConfig.checked = true;
ReactTestUtils.Simulate.change(interactiveLegendConfig);
expect(spy).toHaveBeenCalled();
expect(spy.calls[4].arguments[0]).toEqual("enableInteractiveLegend");
expect(spy.calls[4].arguments[1]).toEqual(true);


});

it("tests Layer Properties Legend component with values from layers", () => {
Expand All @@ -350,11 +360,11 @@ describe('test Layer Properties Display module component', () => {
}
};
const comp = ReactDOM.render(<Display element={l} settings={settings}/>, document.getElementById("container"));
expect(comp).toExist();
expect(comp).toBeTruthy();
const inputs = ReactTestUtils.scryRenderedDOMComponentsWithTag( comp, "input" );
expect(inputs).toExist();
expect(inputs.length).toBe(13);
expect(inputs[11].value).toBe("20");
expect(inputs[12].value).toBe("40");
expect(inputs).toBeTruthy();
expect(inputs.length).toBe(14);
expect(inputs[12].value).toBe("20");
expect(inputs[13].value).toBe("40");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,20 @@ export default ({
<Select
value={service.layerOptions?.serverType}
options={serverTypeOptions}
onChange={event => onChangeServiceProperty("layerOptions", { ...service.layerOptions, serverType: event?.value })} />
onChange={event => {
if (event?.value === ServerTypes.NO_VENDOR) onChangeServiceProperty("layerOptions", { ...service.layerOptions, enableInteractiveLegend: undefined});
onChangeServiceProperty("layerOptions", { ...service.layerOptions, serverType: event?.value });
}} />
</InputGroup>
</FormGroup>
{![ServerTypes.NO_VENDOR].includes(service.layerOptions?.serverType) && ['wms', 'csw'].includes(service.type) && <FormGroup controlId="enableInteractiveLegend" key="enableInteractiveLegend">
<Checkbox data-qa="display-interactive-legend-option"
onChange={(e) => onChangeServiceProperty("layerOptions", { ...service.layerOptions, enableInteractiveLegend: e.target.checked})}
checked={!isNil(service.layerOptions?.enableInteractiveLegend) ? service.layerOptions?.enableInteractiveLegend : false}>
<Message msgId="layerProperties.enableInteractiveLegendInfo.label" />
&nbsp;<InfoPopover text={<Message msgId="layerProperties.enableInteractiveLegendInfo.info" />} />
</Checkbox>
</FormGroup>}
<hr style={{margin: "8px 0"}}/>
<FormGroup style={advancedRasterSettingsStyles} className="form-group-flex">
<ControlLabel className="strong"><Message msgId="layerProperties.format.title" /></ControlLabel>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ describe('Test Raster advanced settings', () => {
const advancedSettingPanel = document.getElementsByClassName("mapstore-switch-panel");
expect(advancedSettingPanel).toBeTruthy();
const fields = document.querySelectorAll(".form-group");
expect(fields.length).toBe(14);
});
it('test wms advanced options with no vendor serverType', () => {
ReactDOM.render(<RasterAdvancedSettings service={{type: "wms", autoload: false, layerOptions: {serverType: 'no-vendor'}}} isLocalizedLayerStylesEnabled/>, document.getElementById("container"));
const advancedSettingPanel = document.getElementsByClassName("mapstore-switch-panel");
expect(advancedSettingPanel).toBeTruthy();
const fields = document.querySelectorAll(".form-group");
expect(fields.length).toBe(13);
});
it('test csw advanced options', () => {
Expand All @@ -42,6 +49,17 @@ describe('Test Raster advanced settings', () => {
const fields = document.querySelectorAll(".form-group");
const cswFilters = document.getElementsByClassName("catalog-csw-filters");
const sortBy = document.getElementsByClassName("sort-by");
expect(fields.length).toBe(13);
expect(cswFilters).toBeTruthy();
expect(sortBy).toBeTruthy();
});
it('test csw advanced options with no vendor serverType', () => {
ReactDOM.render(<RasterAdvancedSettings service={{type: "csw", autoload: false, layerOptions: {serverType: 'no-vendor'}}}/>, document.getElementById("container"));
const advancedSettingPanel = document.getElementsByClassName("mapstore-switch-panel");
expect(advancedSettingPanel).toBeTruthy();
const fields = document.querySelectorAll(".form-group");
const cswFilters = document.getElementsByClassName("catalog-csw-filters");
const sortBy = document.getElementsByClassName("sort-by");
expect(fields.length).toBe(12);
expect(cswFilters).toBeTruthy();
expect(sortBy).toBeTruthy();
Expand Down
Loading

0 comments on commit b0144c0

Please sign in to comment.