Skip to content

Commit

Permalink
#10264: Layer visibility limits may prevent the Info panel of search …
Browse files Browse the repository at this point in the history
…results from opening (#10302)

* #10264: Layer visibility limits may prevent the Info panel of search results from opening
Description:
- isolate getFeature [identify] action from epic 'searchItemSelected' to be applied after zoom and AddMarker events by creating a new epic called 'getFeatureInfoOfSelectedItem'
- write unit test for that

* Update web/client/epics/search.js

---------

Co-authored-by: Suren <dsuren1@gmail.com>
  • Loading branch information
mahmoudadel54 and dsuren1 committed May 10, 2024
1 parent e599785 commit 2acf717
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 84 deletions.
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 @@ -422,7 +423,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

0 comments on commit 2acf717

Please sign in to comment.