Skip to content

Commit

Permalink
Fixed #1634 added support to custom search services and minor fixes (#…
Browse files Browse the repository at this point in the history
…1664)

* fixed #1634 added support to custom search services and minor fixes

* clean up code

* fixed indentation

* fixed test

* fixed test

* improved a test
  • Loading branch information
MV88 authored Mar 31, 2017
1 parent 405b8f0 commit 966c6a5
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 118 deletions.
1 change: 0 additions & 1 deletion docma-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@
"label": "Download",
"href": "index.html",
"items": [
{ "label": "<code>mvn clean install</code>" },
{
"label": "MapStore 2 Releases",
"href": "https://github.com/geosolutions-it/MapStore2/releases",
Expand Down
2 changes: 1 addition & 1 deletion docs/developer-guide/map-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ module.exports = {
}],
"toolsOptions": {
"test": {
"label": "ciao"
"label": "Hello"
}
...
}
Expand Down
42 changes: 42 additions & 0 deletions web/client/api/__tests__/searchText-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Copyright 2017, GeoSolutions Sas.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

const expect = require('expect');
const {API} = require('../searchText');
const axios = require('axios');

describe('Test correctness of the searchText APIs', () => {

const myFun = (param) => {
// do stuff
return param;
};
function fun() {
return axios.get('base/web/client/test-resources/featureCollectionZone.js');
}

it('setter and getter services', (done) => {
let servName = "myService";
API.Utils.setService(servName, myFun);
try {
expect(API.Services).toExist();
expect(API.Services[servName]).toExist();
expect(API.Utils.getService(servName)).toExist();
done();
} catch(ex) {
done(ex);
}
});

let serviceType = 'myCustomService';
it('setService', (done) => {
API.Utils.setService(serviceType, fun);
expect(API.Utils.getService(serviceType)).toBe(fun);
done();
});
});
49 changes: 28 additions & 21 deletions web/client/api/searchText.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,47 @@ const WFS = require('./WFS');
const assign = require('object-assign');
const GeoCodeUtils = require('../utils/GeoCodeUtils');
const {generateTemplateString} = require('../utils/TemplateUtils');
/*
const toNominatim = (fc) =>
fc.features && fc.features.map( (f) => ({
boundingbox: f.properties.bbox,
lat: 1,
lon: 1,
display_name: `${f.properties.STATE_NAME} (${f.properties.STATE_ABBR})`

}));
*/

module.exports = {
let Services = {
nominatim: (searchText, options = {}) =>
require('./Nominatim')
.geocode(searchText, options)
.then( res => GeoCodeUtils.nominatimToGeoJson(res.data)),
wfs: (searchText, {url, typeName, queriableAttributes, outputFormat="application/json", predicate ="ILIKE", staticFilter="", blacklist = [], item, ...params }) => {
wfs: (searchText, {url, typeName, queriableAttributes = [], outputFormat="application/json", predicate ="ILIKE", staticFilter="", blacklist = [], item, ...params }) => {
// split into words and remove blacklisted words
const staticFilterParsed = generateTemplateString(staticFilter || "")(item);
let searchWords = searchText.split(" ").filter(w => w).filter( w => blacklist.indexOf(w.toLowerCase()) < 0 );

// if the searchtext is empty use the full searchText
// if the array searchWords is empty, then use the full searchText
if (searchWords.length === 0 ) {
searchWords = [searchText];
searchWords = !!searchText ? [searchText] : [];
}
let filter;
if (searchWords.length > 0 ) {
filter = "(".concat( searchWords.map( (w) => queriableAttributes.map( attr => `${attr} ${predicate} '%${w.replace("'", "''")}%'`).join(" OR ")).join(') AND (')).concat(")");
}

filter = filter ? filter.concat(staticFilterParsed) : staticFilterParsed || null;

return WFS
.getFeatureSimple(url, assign({
maxFeatures: 10,
startIndex: 0,
typeName,
outputFormat,
// create a filter like : `(ATTR ilike '%word1%') AND (ATTR ilike '%word2%')`
cql_filter: "(".concat( searchWords.map( (w) => queriableAttributes.map( attr => `${attr} ${predicate} '%${w.replace("'", "''")}%'`).join(" OR ")).join(') AND (')).concat(")") .concat(staticFilterParsed)
}, params))
maxFeatures: 10,
typeName,
outputFormat,
// create a filter like : `(ATTR ilike '%word1%') AND (ATTR ilike '%word2%')`
cql_filter: filter
}, params))
.then( response => response.features );
}
};

const Utils = {
setService: (type, fun) => {
Services[type] = fun;
},
getService: (type) => {
return !!Services[type] ? Services[type] : null;
}
};

module.exports = {API: {Services, Utils}};
6 changes: 3 additions & 3 deletions web/client/components/mapcontrols/search/SearchBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ require('./searchbar.css');
* @prop {number} blurResetDelay time to wait before to trigger onPurgeResults after blur event, if `hideOnBlur` is true
* @prop {searchText} the text to display in the component
* @prop {object[]} selectedItems the items selected. Must have `text` property to display
* @prop {boolean} autoFocusOnSelect if true, the comonent gets focus when items are added, or deleted but some item is still selected. Useful for continue writing after selecting an item (with nested services for instance)
* @prop {boolean} autoFocusOnSelect if true, the component gets focus when items are added, or deleted but some item is still selected. Useful for continue writing after selecting an item (with nested services for instance)
* @prop {boolean} loading if true, shows the loading tool
* @prop {object} error if not null, an error icon will be display
* @prop {object} style css style to apply to the component
Expand Down Expand Up @@ -154,9 +154,9 @@ let SearchBar = React.createClass({
},
render() {
// const innerGlyphicon = <Button onClick={this.search}></Button>;
let placeholder;
let placeholder = "search.placeholder";
if (!this.props.placeholder && this.context.messages) {
let placeholderLocMessage = LocaleUtils.getMessageById(this.context.messages, this.props.placeholderMsgId);
let placeholderLocMessage = LocaleUtils.getMessageById(this.context.messages, this.props.placeholderMsgId || placeholder);
if (placeholderLocMessage) {
placeholder = placeholderLocMessage;
}
Expand Down
122 changes: 90 additions & 32 deletions web/client/epics/__tests__/search-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,37 @@ const rootEpic = combineEpics(searchEpic, searchItemSelected);
const epicMiddleware = createEpicMiddleware(rootEpic);
const mockStore = configureMockStore([epicMiddleware]);

const SEARCH_NESTED = 'SEARCH NESTED';
const TEST_NESTED_PLACEHOLDER = 'TEST_NESTED_PLACEHOLDER';
const STATE_NAME = 'STATE_NAME';

const nestedService = {
nestedPlaceholder: TEST_NESTED_PLACEHOLDER
};
const TEXT = "Dinagat Islands";
const item = {
"type": "Feature",
"bbox": [125, 10, 126, 11],
"geometry": {
"type": "Point",
"coordinates": [125.6, 10.1]
},
"properties": {
"name": TEXT
},
"__SERVICE__": {
searchTextTemplate: "${properties.name}",
displayName: "${properties.name}",
type: "wfs",
options: {
staticFilter: "${properties.name}"
},
nestedPlaceholder: SEARCH_NESTED,
nestedPlaceholderMsgId: TEST_NESTED_PLACEHOLDER,
then: [nestedService]
}
};

describe('search Epics', () => {
let store;
beforeEach(() => {
Expand All @@ -36,7 +67,7 @@ describe('search Epics', () => {
options: {
url: 'base/web/client/test-resources/wfs/Wyoming.json',
typeName: 'topp:states',
queriableAttributes: ['STATE_NAME']
queriableAttributes: [STATE_NAME]
}
}]
};
Expand Down Expand Up @@ -81,17 +112,44 @@ describe('search Epics', () => {
});

it('searchItemSelected epic with nested services', () => {
let nestedService = {
nestedPlaceholder: "TEST_NESTED_PLACEHOLDER"
};
const TEXT = "Dinagat Islands";
const item = {
let action = selectSearchItem(item, {
size: {
width: 200,
height: 200
},
projection: "EPSG:4326"
});

store.dispatch( action );

let actions = store.getActions();
expect(actions.length).toBe(6);
let expectedActions = [CHANGE_MAP_VIEW, TEXT_SEARCH_ADD_MARKER, TEXT_SEARCH_RESULTS_PURGE, TEXT_SEARCH_NESTED_SERVICES_SELECTED, TEXT_SEARCH_TEXT_CHANGE ];
let actionsType = actions.map(a => a.type);

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

let testSearchNestedServicesSelectedAction = actions.filter(m => m.type === TEXT_SEARCH_NESTED_SERVICES_SELECTED)[0];
expect(testSearchNestedServicesSelectedAction.services[0]).toEqual({
...nestedService,
options: {
item
}
});
expect(testSearchNestedServicesSelectedAction.items).toEqual({
placeholder: SEARCH_NESTED,
placeholderMsgId: TEST_NESTED_PLACEHOLDER,
text: TEXT
});
expect(actions.filter(m => m.type === TEXT_SEARCH_TEXT_CHANGE)[0].searchText).toBe(TEXT);
});

it('testing the geometry service', (done) => {
// use the done function for asynchronus calls
const itemWithoutGeom = {
"type": "Feature",
"bbox": [125, 10, 126, 11],
"geometry": {
"type": "Point",
"coordinates": [125.6, 10.1]
},
"properties": {
"name": TEXT
},
Expand All @@ -102,11 +160,19 @@ describe('search Epics', () => {
options: {
staticFilter: "${properties.name}"
},
nestedPlaceholder: "SEARCH NESTED",
then: [nestedService]
"geomService": {
type: 'wfs',
options: {
url: 'base/web/client/test-resources/wfs/Wyoming.json',
typeName: 'topp:states',
queriableAttributes: [STATE_NAME]
}
}
}
};
let action = selectSearchItem(item, {

// needed for the changeMapView action
let action = selectSearchItem(itemWithoutGeom, {
size: {
width: 200,
height: 200
Expand All @@ -115,25 +181,17 @@ describe('search Epics', () => {
});

store.dispatch( action );
// a set timeout is needed in order to dispatch the actions
setTimeout(() => {
let actions = store.getActions();
expect(actions.length).toBe(5);
let addMarkerAction = actions.filter(m => m.type === TEXT_SEARCH_ADD_MARKER)[0];

let actions = store.getActions();
expect(actions.length).toBe(6);
expect(actions[1].type).toBe(CHANGE_MAP_VIEW);
expect(actions[2].type).toBe(TEXT_SEARCH_ADD_MARKER);
expect(actions[3].type).toBe(TEXT_SEARCH_RESULTS_PURGE);
expect(actions[4].type).toBe(TEXT_SEARCH_NESTED_SERVICES_SELECTED);
expect(actions[4].services[0]).toEqual({
...nestedService,
options: {
item
}
});
expect(actions[4].items).toEqual({
placeholder: "SEARCH NESTED",
text: TEXT
});
expect(actions[5].type).toBe(TEXT_SEARCH_TEXT_CHANGE);
expect(actions[5].searchText).toBe("Dinagat Islands");
expect(addMarkerAction).toExist();
expect(addMarkerAction.markerPosition.geometry).toExist();

done();
// setting 0 as delay arises script error
}, 100);
});
});
Loading

0 comments on commit 966c6a5

Please sign in to comment.