Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#10026: Interactive legend for TOC layers [WMS] #10180

Merged
merged 18 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
124e01b
#10026: Interactive legend for TOC layers
mahmoudadel54 Mar 24, 2024
c291f32
#10026: Interactive legend for TOC layers
mahmoudadel54 Apr 2, 2024
85617f2
#10026: Interactive legend for TOC layers
mahmoudadel54 Apr 8, 2024
5277175
#10026: Interactive legend for TOC layers
mahmoudadel54 Apr 9, 2024
a29974f
#10026: Interactive legend for TOC layers
mahmoudadel54 Apr 9, 2024
161178a
#10026: Interactive legend for TOC layers
mahmoudadel54 Apr 10, 2024
b0a2693
#10026: Interactive legend for TOC layers
mahmoudadel54 Apr 15, 2024
3af0429
#10026: Interactive legend for TOC layers
mahmoudadel54 Apr 15, 2024
c76bd16
#10026: Interactive legend for TOC layers
mahmoudadel54 Apr 15, 2024
d177ccf
#10026: Interactive legend for TOC layers [Resolve review comments]
mahmoudadel54 Apr 18, 2024
6b7e7e3
#10026: Interactive legend for TOC layers [Resolve review comments]
mahmoudadel54 Apr 19, 2024
ff42bc6
#10026: Interactive legend for TOC layers [Resolve review comments]
mahmoudadel54 Apr 19, 2024
7b9bc99
#10026: Interactive legend for TOC layers [Resolve review comments]
mahmoudadel54 Apr 19, 2024
bab1218
Update layerFilter.js
mahmoudadel54 Apr 19, 2024
2c0eaa0
#10026: Interactive legend for TOC layers [WMS]
mahmoudadel54 Apr 19, 2024
65e5deb
#10026: Interactive legend for TOC layers [WMS]
mahmoudadel54 Apr 19, 2024
4aa80c9
#10026: Interactive legend for TOC layers [Resolve review comments]
mahmoudadel54 Apr 22, 2024
d39a0d0
Merge branch 'feature_10026' of https://github.com/mahmoudadel54/MapS…
mahmoudadel54 Apr 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading