Skip to content

Commit

Permalink
Fix #1737 Query panel search fails if an id is not defined (#1792)
Browse files Browse the repository at this point in the history
* Added query epic to handle WFS error

* Fixed structure of query epic

* Added documentation to wfsquery epic

* Fixed indent and documentationof wfsquery
  • Loading branch information
allyoucanmap authored and offtherailz committed May 11, 2017
1 parent fab2eca commit b917598
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 50 deletions.
1 change: 1 addition & 0 deletions docma-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
"web/client/epics/globeswitcher.js",
"web/client/epics/maptype.js",
"web/client/epics/search.js",
"web/client/epics/wfsquery.js",

"web/client/utils/index.jsdoc",
"web/client/utils/CoordinatesUtils.js",
Expand Down
38 changes: 12 additions & 26 deletions web/client/actions/wfsquery.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ const QUERY_CREATE = 'QUERY_CREATE';
const QUERY_RESULT = 'QUERY_RESULT';
const QUERY_ERROR = 'QUERY_ERROR';
const RESET_QUERY = 'RESET_QUERY';
const QUERY = 'QUERY';

const axios = require('../libs/ajax');
const {toggleControl, setControlProperty} = require('./controls');
const FilterUtils = require('../utils/FilterUtils');
const {reset} = require('./queryform');

function featureTypeSelected(url, typeName) {
Expand Down Expand Up @@ -103,30 +103,12 @@ function createQuery(searchUrl, filterObj) {
};
}

function query(searchUrl, filterObj) {
createQuery(searchUrl, filterObj);
let data;
if (typeof filterObj === 'string') {
data = filterObj;
} else {
data = filterObj.filterType === "OGC" ?
FilterUtils.toOGCFilter(filterObj.featureTypeName, filterObj, filterObj.ogcVersion, filterObj.sortOptions, filterObj.hits) :
FilterUtils.toCQLFilter(filterObj);
}
return (dispatch, getState) => {
let state = getState();
if (state.controls && state.controls.queryPanel && state.controls.drawer && state.controls.drawer.enabled && state.query && state.query.open) {
dispatch(setControlProperty('drawer', 'enabled', false));
dispatch(setControlProperty('drawer', 'disabled', true));
}
return axios.post(searchUrl + '?service=WFS&&outputFormat=json', data, {
timeout: 60000,
headers: {'Accept': 'application/json', 'Content-Type': 'application/json'}
}).then((response) => {
dispatch(querySearchResponse(response.data, searchUrl, filterObj));
}).catch((e) => {
dispatch(queryError(e));
});
function query(searchUrl, filterObj, retry) {
return {
type: QUERY,
searchUrl,
filterObj,
retry
};
}

Expand Down Expand Up @@ -175,14 +157,18 @@ module.exports = {
QUERY_RESULT,
QUERY_ERROR,
RESET_QUERY,
QUERY,
featureTypeSelected,
featureTypeLoaded,
featureTypeError,
featureError,
loadFeature,
createQuery,
query,
featureClose,
resetQuery,
toggleQueryPanel,
closeResponse
closeResponse,
queryError,
querySearchResponse
};
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ const DockedFeatureGrid = React.createClass({
};
this.featureLoaded = params;
this.sortModel = params && params.sortModel;
this.props.onQuery(this.props.searchUrl, filterObj, this.props.params);
this.props.onQuery(this.props.searchUrl, filterObj);

}
},
Expand Down
154 changes: 133 additions & 21 deletions web/client/epics/wfsquery.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
const Rx = require('rxjs');
const axios = require('../libs/ajax');
const {changeSpatialAttribute} = require('../actions/queryform');
const {FEATURE_TYPE_SELECTED, featureTypeLoaded, featureTypeError} = require('../actions/wfsquery');
const {FEATURE_TYPE_SELECTED, QUERY, featureTypeLoaded, featureTypeError, createQuery, querySearchResponse, queryError, featureClose} = require('../actions/wfsquery');
const FilterUtils = require('../utils/FilterUtils');
const assign = require('object-assign');
const {isString} = require('lodash');
const {TOGGLE_CONTROL, setControlProperty} = require('../actions/controls');

const types = {
// string
Expand Down Expand Up @@ -69,6 +73,7 @@ const types = {
};

const fieldConfig = {};

const extractInfo = (data) => {
return {
geometry: data.featureTypes[0].properties
Expand Down Expand Up @@ -102,27 +107,134 @@ const extractInfo = (data) => {
};
};

const featureTypeSelectedEpic = action$ =>
action$.ofType(FEATURE_TYPE_SELECTED).switchMap(action => {
return Rx.Observable.defer( () =>
axios.get(action.url + '?service=WFS&version=1.1.0&request=DescribeFeatureType&typeName=' + action.typeName + '&outputFormat=application/json'))
.map((response) => {
if (typeof response.data === 'object' && response.data.featureTypes && response.data.featureTypes[0]) {
const info = extractInfo(response.data);
const geometry = info.geometry[0] && info.geometry[0].attribute ? info.geometry[0].attribute : 'the_geom';
return Rx.Observable.from([featureTypeLoaded(action.typeName, info), changeSpatialAttribute(geometry)]);
}
try {
JSON.parse(response.data);
} catch(e) {
return Rx.Observable.from([featureTypeError(action.typeName, 'Error from WFS: ' + e.message)]);
}
return Rx.Observable.from([featureTypeError(action.typeName, 'Error: feature types are empty')]);
const getWFSFilterData = (filterObj) => {
let data;
if (typeof filterObj === 'string') {
data = filterObj;
} else {
data = filterObj.filterType === "OGC" ?
FilterUtils.toOGCFilter(filterObj.featureTypeName, filterObj, filterObj.ogcVersion, filterObj.sortOptions, filterObj.hits) :
FilterUtils.toCQLFilter(filterObj);
}
return data;
};

const getWFSFeature = (searchUrl, filterObj) => {
const data = getWFSFilterData(filterObj);
return Rx.Observable.defer( () =>
axios.post(searchUrl + '?service=WFS&outputFormat=json', data, {
timeout: 60000,
headers: {'Accept': 'application/json', 'Content-Type': 'application/json'}
}));
};

const getWFSResponseException = (response, code) => {
const {unmarshaller} = require('../utils/ogc/WFS');
const json = isString(response.data) ? unmarshaller.unmarshalString(response.data) : null;
return json && json.value && json.value.exception && json.value.exception[0] && json.value.exception[0].TYPE_NAME === 'OWS_1_0_0.ExceptionType' && json.value.exception[0].exceptionCode === code;
};

const getFirstAttribute = (state)=> {
return state.query && state.query.featureTypes && state.query.featureTypes[state.query.typeName] && state.query.featureTypes[state.query.typeName].attributes && state.query.featureTypes[state.query.typeName].attributes[0] && state.query.featureTypes[state.query.typeName].attributes[0].attribute || null;
};

const getDefaultSortOptions = (attribute) => {
return attribute ? { sortBy: attribute, sortOrder: 'A'} : {};
};

const retryWithForcedSortOptions = (action, store) => {
const sortOptions = getDefaultSortOptions(getFirstAttribute(store.getState()));
return getWFSFeature(action.searchUrl, assign(action.filterObj, {
sortOptions
}))
.map((newResponse) => {
const newError = getWFSResponseException(newResponse, 'NoApplicableCode');
return !newError ? querySearchResponse(newResponse.data, action.searchUrl, action.filterObj) : queryError('No sortable request');
})
.mergeAll()
.catch(e => Rx.Observable.of(featureTypeError(action.typeName, e.message)));
});
.catch((e) => {
return Rx.Observable.of(queryError(e));
});
};

/**
* Gets the WFS feature type attributes and geometry when the feature has been selected
* @memberof epics.wfsquery
* @param {external:Observable} action$ manages `FEATURE_TYPE_SELECTED`
* @return {external:Observable}
*/

const featureTypeSelectedEpic = action$ =>
action$.ofType(FEATURE_TYPE_SELECTED)
.switchMap(action => {
return Rx.Observable.defer( () => axios.get(action.url + '?service=WFS&version=1.1.0&request=DescribeFeatureType&typeName=' + action.typeName + '&outputFormat=application/json'))
.map((response) => {
if (typeof response.data === 'object' && response.data.featureTypes && response.data.featureTypes[0]) {
const info = extractInfo(response.data);
const geometry = info.geometry[0] && info.geometry[0].attribute ? info.geometry[0].attribute : 'the_geom';
return Rx.Observable.from([featureTypeLoaded(action.typeName, info), changeSpatialAttribute(geometry)]);
}
try {
JSON.parse(response.data);
} catch(e) {
return Rx.Observable.from([featureTypeError(action.typeName, 'Error from WFS: ' + e.message)]);
}
return Rx.Observable.from([featureTypeError(action.typeName, 'Error: feature types are empty')]);
})
.mergeAll()
.catch(e => Rx.Observable.of(featureTypeError(action.typeName, e.message)));
});

/**
* Sends a WFS query, returns a response or handles request error
* in particular the NoApplicableCode WFS error with a forced sort option on the first attribute
* @memberof epics.wfsquery
* @param {external:Observable} action$ manages `QUERY`
* @return {external:Observable}
*/

const wfsQueryEpic = (action$, store) =>
action$.ofType(QUERY)
.switchMap(action => {

return Rx.Observable.merge(
Rx.Observable.of(createQuery(action.searchUrl, action.filterObj)),
Rx.Observable.of(setControlProperty('drawer', 'enabled', false)),
getWFSFeature(action.searchUrl, action.filterObj)
.switchMap((response) => {
// try to guess if it was a missing id error and try to search again with forced sortOptions
const error = getWFSResponseException(response, 'NoApplicableCode');
if (error) {
return retryWithForcedSortOptions(action, store);
}
return Rx.Observable.of(querySearchResponse(response.data, action.searchUrl, action.filterObj));
})
.catch((e) => {
return Rx.Observable.of(queryError(e));
})
);
});

/**
* Closes the feature grid when the drawer menu button has been toggled
* @memberof epics.wfsquery
* @param {external:Observable} action$ manages `TOGGLE_CONTROL`
* @return {external:Observable}
*/

const closeFeatureEpic = action$ =>
action$.ofType(TOGGLE_CONTROL)
.switchMap(action => {
return action.control && action.control === 'drawer' ? Rx.Observable.of(featureClose()) : Rx.Observable.empty();
});

/**
* Epics for WFS query requests
* @name epics.wfsquery
* @type {Object}
*/

module.exports = {
featureTypeSelectedEpic
featureTypeSelectedEpic,
wfsQueryEpic,
closeFeatureEpic
};
4 changes: 2 additions & 2 deletions web/client/plugins/QueryPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const LayersUtils = require('../utils/LayersUtils');
// include application component
const QueryBuilder = require('../components/data/query/QueryBuilder');

const {featureTypeSelectedEpic} = require('../epics/wfsquery');
const {featureTypeSelectedEpic, wfsQueryEpic, closeFeatureEpic} = require('../epics/wfsquery');

const {bindActionCreators} = require('redux');
const {
Expand Down Expand Up @@ -237,5 +237,5 @@ module.exports = {
queryform: require('../reducers/queryform'),
query: require('../reducers/query')
},
epics: {featureTypeSelectedEpic}
epics: {featureTypeSelectedEpic, wfsQueryEpic, closeFeatureEpic}
};

0 comments on commit b917598

Please sign in to comment.