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

Fixed #1634 added support to custom search services and minor fixes #1664

Merged
merged 6 commits into from
Mar 31, 2017
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
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