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

#10264: Layer visibility limits may prevent the Info panel of search results from opening #10302

Merged
merged 2 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
110 changes: 69 additions & 41 deletions web/client/epics/__tests__/search-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ import {
zoomAndAddPointEpic,
searchOnStartEpic,
textSearchShowGFIEpic,
delayedSearchEpic
delayedSearchEpic,
getFeatureInfoOfSelectedItem
} from '../search';
const rootEpic = combineEpics(searchEpic, searchItemSelected, zoomAndAddPointEpic, searchOnStartEpic, textSearchShowGFIEpic);
const epicMiddleware = createEpicMiddleware(rootEpic);
Expand Down Expand Up @@ -181,7 +182,7 @@ describe('search Epics', () => {
});

it('produces the selectSearchItem epic and GFI for all layers', () => {
let action = selectSearchItem({
let selectSearchItemAction = selectSearchItem({
"type": "Feature",
"bbox": [125, 10, 126, 11],
"geometry": {
Expand All @@ -205,21 +206,29 @@ describe('search Epics', () => {
projection: "EPSG:4326"
});

store.dispatch( action );
store.dispatch( selectSearchItemAction );

let actions = store.getActions();
expect(actions.length).toBe(6);
expect(actions.length).toBe(4);
expect(actions[1].type).toBe(TEXT_SEARCH_RESULTS_PURGE);
expect(actions[2].type).toBe(FEATURE_INFO_CLICK);
expect(actions[2].filterNameList).toEqual([]);
expect(actions[3].type).toBe(SHOW_MAPINFO_MARKER);
expect(actions[4].type).toBe(ZOOM_TO_EXTENT);
expect(actions[5].type).toBe(TEXT_SEARCH_ADD_MARKER);
expect(actions[2].type).toBe(ZOOM_TO_EXTENT);
expect(actions[3].type).toBe(TEXT_SEARCH_ADD_MARKER);
let addMarkerAction = actions[3];
const NUM_ACTIONS = 2;
// epic 'getFeatureInfoOfSelectedItem' for getting feature info data
testEpic(addTimeoutEpic(getFeatureInfoOfSelectedItem, 50), NUM_ACTIONS, addMarkerAction, (getInfoActions) => {
expect(getInfoActions.length).toBe(2);
expect(getInfoActions[0].type).toBe(TEST_TIMEOUT);
expect(getInfoActions[1].type).toBe(FEATURE_INFO_CLICK);
expect(getInfoActions[1].filterNameList).toEqual([]);
expect(getInfoActions[1].layer).toEqual("gs:layername");
});

});


it('produces the selectSearchItem epic and GFI for single layer', (done) => {
let action = selectSearchItem({
let selectSearchItemAction = selectSearchItem({
"id": "Feature_1",
"type": "Feature",
"bbox": [125, 10, 126, 11],
Expand All @@ -244,17 +253,24 @@ describe('search Epics', () => {
projection: "EPSG:4326"
});

const NUM_ACTIONS = 6;
testEpic(addTimeoutEpic(searchItemSelected, 0), NUM_ACTIONS, action, (actions) => {
expect(actions[0].type).toBe(TEXT_SEARCH_RESULTS_PURGE);
expect(actions[1].type).toBe(FEATURE_INFO_CLICK);
expect(actions[1].itemId).toEqual("Feature_1");
expect(actions[1].filterNameList).toEqual(["gs:layername"]);
expect(actions[1].overrideParams).toEqual({"gs:layername": {info_format: "text/html", featureid: "Feature_1", CQL_FILTER: undefined}}); // forces CQL FILTER to undefined (server do not support featureid + CQL_FILTER)
expect(actions[2].type).toBe(SHOW_MAPINFO_MARKER);
expect(actions[3].type).toBe(ZOOM_TO_EXTENT);
expect(actions[4].type).toBe(TEXT_SEARCH_ADD_MARKER);
expect(actions[5].type).toBe(TEST_TIMEOUT);
store.dispatch( selectSearchItemAction );

let actions = store.getActions();
expect(actions.length).toBe(4);
expect(actions[1].type).toBe(TEXT_SEARCH_RESULTS_PURGE);
expect(actions[2].type).toBe(ZOOM_TO_EXTENT);
expect(actions[3].type).toBe(TEXT_SEARCH_ADD_MARKER);
let addMarkerAction = actions[3];
const NUM_ACTIONS = 2;
// epic 'getFeatureInfoOfSelectedItem' for getting feature info data
testEpic(addTimeoutEpic(getFeatureInfoOfSelectedItem, 50), NUM_ACTIONS, addMarkerAction, (getInfoActions) => {
expect(getInfoActions.length).toBe(2);
expect(getInfoActions[0].type).toBe(TEST_TIMEOUT);
expect(getInfoActions[1].type).toBe(FEATURE_INFO_CLICK);
expect(getInfoActions[1].itemId).toEqual("Feature_1");
expect(getInfoActions[1].filterNameList).toEqual(["gs:layername"]);
expect(getInfoActions[1].overrideParams).toEqual({"gs:layername": {info_format: "text/html", featureid: "Feature_1", CQL_FILTER: undefined}}); // forces CQL FILTER to undefined (server do not support featureid + CQL_FILTER)
expect(getInfoActions[1].layer).toEqual("gs:layername");
done();
}, {layers: {flat: [{name: "gs:layername", url: "base/web/client/test-resources/wms/GetFeature.json", visibility: true, featureInfo: {format: "HTML"}, queryable: true, type: "wms"}]}});
});
Expand Down Expand Up @@ -324,7 +340,7 @@ describe('search Epics', () => {
});

it('searchItemSelected epic with a service with openFeatureInfoButtonEnabled=false', (done) => {
let action = selectSearchItem({
let selectSearchItemAction = selectSearchItem({
"id": "Feature_1",
"type": "Feature",
"bbox": [125, 10, 126, 11],
Expand All @@ -349,20 +365,30 @@ describe('search Epics', () => {
},
projection: "EPSG:4326"
});
const NUM_ACTIONS = 6;
testEpic(addTimeoutEpic(searchItemSelected, 100), NUM_ACTIONS, action, (actions) => {
let expectedActions = [ZOOM_TO_EXTENT, TEXT_SEARCH_ADD_MARKER, SHOW_MAPINFO_MARKER, FEATURE_INFO_CLICK, TEST_TIMEOUT];
let actionsType = actions.map(a => a.type);
store.dispatch( selectSearchItemAction );

expectedActions.forEach((a) => {
expect(actionsType.indexOf(a)).toNotBe(-1);
});
let actions = store.getActions();
expect(actions.length).toBe(4);
expect(actions[1].type).toBe(TEXT_SEARCH_RESULTS_PURGE);
expect(actions[2].type).toBe(ZOOM_TO_EXTENT);
expect(actions[3].type).toBe(TEXT_SEARCH_ADD_MARKER);
let addMarkerAction = actions[3];
const NUM_ACTIONS = 2;
// epic 'getFeatureInfoOfSelectedItem' for getting feature info data
testEpic(addTimeoutEpic(getFeatureInfoOfSelectedItem, 50), NUM_ACTIONS, addMarkerAction, (getInfoActions) => {
expect(getInfoActions.length).toBe(2);
expect(getInfoActions[0].type).toBe(TEST_TIMEOUT);
expect(getInfoActions[1].type).toBe(FEATURE_INFO_CLICK);
expect(getInfoActions[1].itemId).toEqual("Feature_1");
expect(getInfoActions[1].filterNameList).toEqual(["gs:layername"]);
expect(getInfoActions[1].overrideParams).toEqual({"gs:layername": {info_format: "text/html", featureid: "Feature_1", CQL_FILTER: undefined}}); // forces CQL FILTER to undefined (server do not support featureid + CQL_FILTER)
expect(getInfoActions[1].layer).toEqual("gs:layername");
done();
}, {layers: {flat: [{name: "gs:layername", url: "base/web/client/test-resources/wms/GetFeature.json", visibility: true, featureInfo: {format: "HTML"}, queryable: true, type: "wms"}]}});
});

it('searchItemSelected epic with a service with openFeatureInfoButtonEnabled=true', (done) => {
let action = selectSearchItem({
let selectSearchItemAction = selectSearchItem({
"id": "Feature_1",
"type": "Feature",
"bbox": [125, 10, 126, 11],
Expand All @@ -387,18 +413,20 @@ describe('search Epics', () => {
},
projection: "EPSG:4326"
});
const NUM_ACTIONS = 4;
testEpic(addTimeoutEpic(searchItemSelected, 100), NUM_ACTIONS, action, (actions) => {
let expectedActions = [ZOOM_TO_EXTENT, TEXT_SEARCH_ADD_MARKER, SHOW_MAPINFO_MARKER];
let actionsType = actions.map(a => a.type);
store.dispatch( selectSearchItemAction );

expectedActions.forEach((a) => {
expect(actionsType.indexOf(a)).toNotBe(-1);
});

let featureInfoClickAction = actions.filter(m => m.type === FEATURE_INFO_CLICK);
expect(featureInfoClickAction).toExist();
expect(featureInfoClickAction.length).toBe(0);
let actions = store.getActions();
expect(actions.length).toBe(4);
expect(actions[1].type).toBe(TEXT_SEARCH_RESULTS_PURGE);
expect(actions[2].type).toBe(ZOOM_TO_EXTENT);
expect(actions[3].type).toBe(TEXT_SEARCH_ADD_MARKER);
let addMarkerAction = actions[3];
const NUM_ACTIONS = 2;
// epic 'getFeatureInfoOfSelectedItem' for getting feature info data
testEpic(addTimeoutEpic(getFeatureInfoOfSelectedItem, 50), NUM_ACTIONS, addMarkerAction, (getInfoActions) => {
expect(getInfoActions.length).toBe(2);
expect(getInfoActions[0].type).toBe(TEST_TIMEOUT);
expect(getInfoActions[1].type).toBe(SHOW_MAPINFO_MARKER);
done();
}, {layers: {flat: [{name: "gs:layername", url: "base/web/client/test-resources/wms/GetFeature.json", visibility: true, featureInfo: {format: "HTML"}, queryable: true, type: "wms"}]}});
});
Expand Down
93 changes: 52 additions & 41 deletions web/client/epics/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
searchTextLoading,
selectNestedService,
serverError,
TEXT_SEARCH_ADD_MARKER,
TEXT_SEARCH_ITEM_SELECTED,
TEXT_SEARCH_RESET,
TEXT_SEARCH_RESULTS_PURGE,
Expand Down Expand Up @@ -122,7 +123,7 @@ export const searchEpic = action$ =>
* @return {Observable}
*/

export const searchItemSelected = (action$, store) =>
export const searchItemSelected = (action$) =>
action$.ofType(TEXT_SEARCH_ITEM_SELECTED)
.switchMap(action => {
// itemSelectionStream --> emits actions for zoom and marker add
Expand All @@ -145,46 +146,6 @@ export const searchItemSelected = (action$, store) =>
zoomToExtent([bbox[0], bbox[1], bbox[2], bbox[3]], "EPSG:4326", item.__SERVICE__ && item.__SERVICE__.options && item.__SERVICE__.options.maxZoomLevel || 21),
addMarker(item)
];
if (item.__SERVICE__ && !isNil(item.__SERVICE__.launchInfoPanel) && item.__SERVICE__.options && item.__SERVICE__.options.typeName) {
let coord = pointOnSurface(item).geometry.coordinates;
const latlng = { lng: coord[0], lat: coord[1] };
const typeName = item.__SERVICE__.options.typeName;
if (coord) {
const state = store.getState();
const layerObj = typeName && getLayerFromName(state, typeName);
let itemId = null;
let filterNameList = [];
let overrideParams = {};
let forceVisibility = false;
if (item.__SERVICE__.launchInfoPanel === "single_layer") {
/* take info from the item selected and restrict feature info to this layer
* and filtering with `featureid` which might be ignored by other servers,
* but can be used by GeoServer to select the specific feature instead to showing all the results
* when info_format is other than application/json */
forceVisibility = item.__SERVICE__.forceSearchLayerVisibility;
filterNameList = [typeName];
itemId = item.id;
overrideParams = {
[item.__SERVICE__.options.typeName]: {
info_format: getInfoFormat(layerObj, state),
...(itemId
? {
featureid: itemId,
CQL_FILTER: undefined
}
: {}
)
}
};
}
return [
...(forceVisibility && layerObj ? [changeLayerProperties(layerObj.id, {visibility: true})] : []),
...(!item.__SERVICE__.openFeatureInfoButtonEnabled ? [featureInfoClick({ latlng }, typeName, filterNameList, overrideParams, itemId)] : []),
showMapinfoMarker(),
...actions
];
}
}
return actions;
});

Expand Down Expand Up @@ -213,6 +174,56 @@ export const searchItemSelected = (action$, store) =>
return Rx.Observable.of(resultsPurge()).concat(itemSelectionStream, nestedServicesStream, searchTextStream);
});

/**
* Handles performing a GFI on the selected search results after zooming in, and adds a marker
* @param {external:Observable} action$ manages [`FEATURE_INFO_CLICK` and `SHOW_MAPINFO_MARKER`] for showing identify feature info
* @memberof epics.search
* @return {external:Observable}
*/
export const getFeatureInfoOfSelectedItem = (action$, store) =>
action$.ofType(TEXT_SEARCH_ADD_MARKER).switchMap((action) => {
const item = action.markerPosition;
if (item.__SERVICE__ && !isNil(item.__SERVICE__.launchInfoPanel) && item.__SERVICE__.options && item.__SERVICE__.options.typeName) {
let coord = pointOnSurface(item).geometry.coordinates;
const latlng = { lng: coord[0], lat: coord[1] };
const typeName = item.__SERVICE__.options.typeName;
if (coord) {
const state = store.getState();
const layerObj = typeName && getLayerFromName(state, typeName);
let itemId = null;
let filterNameList = [];
let overrideParams = {};
let forceVisibility = false;
if (item.__SERVICE__.launchInfoPanel === "single_layer") {
/* take info from the item selected and restrict feature info to this layer
* and filtering with `featureid` which might be ignored by other servers,
* but can be used by GeoServer to select the specific feature instead to showing all the results
* when info_format is other than application/json */
forceVisibility = item.__SERVICE__.forceSearchLayerVisibility;
filterNameList = [typeName];
itemId = item.id;
overrideParams = {
[item.__SERVICE__.options.typeName]: {
info_format: getInfoFormat(layerObj, state),
...(itemId
? {
featureid: itemId,
CQL_FILTER: undefined
}
: {}
)
}
};
}
return [
...(forceVisibility && layerObj ? [changeLayerProperties(layerObj.id, {visibility: true})] : []),
...(!item.__SERVICE__.openFeatureInfoButtonEnabled ? [featureInfoClick({ latlng }, typeName, filterNameList, overrideParams, itemId)] : []),
showMapinfoMarker()
];
}
}
return [Rx.Observable.empty()];
}).delay(50);
/**
* Handles show GFI button click action.
*/
Expand Down
5 changes: 3 additions & 2 deletions web/client/plugins/Search.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ import {
searchOnStartEpic,
textSearchShowGFIEpic,
zoomAndAddPointEpic,
delayedSearchEpic
delayedSearchEpic,
getFeatureInfoOfSelectedItem
} from '../epics/search';
import mapInfoReducers from '../reducers/mapInfo';
import searchReducers from '../reducers/search';
Expand Down Expand Up @@ -420,7 +421,7 @@ export default {
priority: 1
}
}),
epics: {searchEpic, searchOnStartEpic, searchItemSelected, zoomAndAddPointEpic, textSearchShowGFIEpic, delayedSearchEpic},
epics: {searchEpic, searchOnStartEpic, searchItemSelected, zoomAndAddPointEpic, textSearchShowGFIEpic, delayedSearchEpic, getFeatureInfoOfSelectedItem},
reducers: {
search: searchReducers,
mapInfo: mapInfoReducers,
Expand Down
Loading