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 1 commit
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
93 changes: 72 additions & 21 deletions web/client/api/searchText.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,91 @@ 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 = {
const axios = require('axios');
const urlUtil = require('url');
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 );
},
bzVie: (searchText, {pathname, lang}) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a custom service. Please remove it from pull request

let params = assign({}, {query: searchText, lang});
let url = urlUtil.format({
pathname,
query: params
});
return axios.post(url).then( (res) => {
if (res && res.data && res.data.success) {
return res.data.vie.map((item) => {
return {
"type": "Feature",
"properties": {
"code": item.codice,
"desc": item.descrizione
}
};
});
}
return [];
});
},
bzCivico: (searchText, {pathname, item}) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this too

let params = assign({}, {query: searchText, idVia: item.properties.code});
let url = urlUtil.format({
pathname,
query: params
});
return axios.post(url).then( (res) => {
if (res && res.data && res.data.success) {
return res.data.vie.map((nestedItem) => {
return {
"type": "Feature",
"properties": {
"code": nestedItem.codice,
"desc": nestedItem.descrizione
}
};
});
}
return [];
});
}
};

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
133 changes: 105 additions & 28 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,31 +112,6 @@ describe('search Epics', () => {
});

it('searchItemSelected epic with nested services', () => {
let 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",
then: [nestedService]
}
};
let action = selectSearchItem(item, {
size: {
width: 200,
Expand All @@ -129,11 +135,82 @@ describe('search Epics', () => {
}
});
expect(actions[4].items).toEqual({
placeholder: "SEARCH NESTED",
placeholder: SEARCH_NESTED,
placeholderMsgId: TEST_NESTED_PLACEHOLDER,
text: TEXT
});
expect(actions[5].type).toBe(TEXT_SEARCH_TEXT_CHANGE);
expect(actions[5].searchText).toBe("Dinagat Islands");
expect(actions[5].searchText).toBe(TEXT);
});

it('searchItemSelected with geomService', () => {
const itemWithoutGeom = {
"type": "Feature",
"bbox": [125, 10, 126, 11],
"properties": {
"name": TEXT
},
"__SERVICE__": {
searchTextTemplate: "${properties.name}",
displayName: "${properties.name}",
type: "wfs",
options: {
staticFilter: "${properties.name}"
},
"geomService": {
type: 'wfs',
options: {
url: 'base/web/client/test-resources/wfs/Wyoming.json',
typeName: 'topp:states',
queriableAttributes: [STATE_NAME]
}
},
nestedPlaceholder: SEARCH_NESTED,
nestedPlaceholderMsgId: TEST_NESTED_PLACEHOLDER
}
};

let action = selectSearchItem(itemWithoutGeom, {
size: {
width: 200,
height: 200
},
services: [{
type: 'wfs',
options: {
url: 'base/web/client/test-resources/wfs/Wyoming.json',
typeName: 'topp:states',
queriableAttributes: [STATE_NAME]
}
}],
projection: "EPSG:4326"
});

store.dispatch( action );
setTimeout(() => {
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[5].type).toBe(TEXT_SEARCH_TEXT_CHANGE);

expect(actions[4].services[0]).toEqual({
...nestedService,
options: {
item
}
});
expect(actions[4].services[0].geometry).toExist();
expect(actions[4].items).toEqual({
placeholder: SEARCH_NESTED,
placeholderMsgId: TEST_NESTED_PLACEHOLDER,
text: TEXT
});
expect(actions[5].searchText).toBe(TEXT);
expect(actions[5].type).toBe(TEXT_SEARCH_TEXT_CHANGE);

}, 400);
});
});
Loading