diff --git a/web/client/epics/__tests__/featuregrid-test.js b/web/client/epics/__tests__/featuregrid-test.js index fad283631f..72c7193a99 100644 --- a/web/client/epics/__tests__/featuregrid-test.js +++ b/web/client/epics/__tests__/featuregrid-test.js @@ -60,7 +60,10 @@ import { launchUpdateFilterFunc, LAUNCH_UPDATE_FILTER_FUNC, setLayer, - setViewportFilter, SET_VIEWPORT_FILTER + setViewportFilter, SET_VIEWPORT_FILTER, + SAVING, + saveChanges, + SAVE_SUCCESS } from '../../actions/featuregrid'; import { SET_HIGHLIGHT_FEATURES_PATH } from '../../actions/highlight'; @@ -141,12 +144,16 @@ import { toggleSnappingOffOnFeatureGridViewMode, closeFeatureGridOnDrawingToolOpen, setViewportFilterEpic, - deactivateViewportFilterEpic, resetViewportFilter + deactivateViewportFilterEpic, resetViewportFilter, + savePendingFeatureGridChanges } from '../featuregrid'; import { onLocationChanged } from 'connected-react-router'; import { TEST_TIMEOUT, testEpic, addTimeoutEpic } from './epicTestUtils'; import { getDefaultFeatureProjection } from '../../utils/FeatureGridUtils'; import { isEmpty, isNil } from 'lodash'; +import axios from "../../libs/ajax"; +import MockAdapter from "axios-mock-adapter"; + const filterObj = { featureTypeName: 'TEST', groupFields: [ @@ -1822,6 +1829,95 @@ describe('featuregrid Epics', () => { })); }); describe('updateSelectedOnSaveOrCloseFeatureGrid', () => { + let mockAxios; + beforeEach(() => { + mockAxios = new MockAdapter(axios); + }); + afterEach(() => { + mockAxios.restore(); + }); + it("test savePendingFeatureGridChanges", (done) => { + const stateFeaturegrid = { + query: { + featureTypes: { + "mapstore:TEST_LAYER": { + "original": { + "elementFormDefault": "qualified", + "targetNamespace": "http://localhost:8080/geoserver/mapstore", + "targetPrefix": "mapstore", + "featureTypes": [ + { + "typeName": "TEST_LAYER", + "properties": [ + { + "name": "Integer", + "maxOccurs": 1, + "minOccurs": 0, + "nillable": true, + "type": "xsd:int", + "localType": "int" + }, + { + "name": "Long", + "maxOccurs": 1, + "minOccurs": 0, + "nillable": true, + "type": "xsd:int", + "localType": "int" + }, + { + "name": "Point", + "maxOccurs": 1, + "minOccurs": 0, + "nillable": true, + "type": "gml:Point", + "localType": "Point" + } + ] + } + ] + } + } + }, + filterObj: { + featureTypeName: "mapstore:TEST_LAYER" + }, + searchUrl: "https://localhost:8080/geoserver/wfs?authkey=29031b3b8afc" + }, + featuregrid: { + open: true, + selectedLayer: "TEST_LAYER", + mode: 'EDIT', + select: [{id: 'TEST_LAYER', geometry_name: "Point"}], + changes: [ + { + "id": "TEST_LAYER.13", + "updated": { + "Integer": 50 + } + }, + { + "id": "TEST_LAYER.13", + "updated": { + "Long": 55 + } + } + ] + } + }; + const payloadSample = `Integer50,Long55`; + mockAxios.onPost(stateFeaturegrid.query.searchUrl, payloadSample).replyOnce(200); + testEpic( + savePendingFeatureGridChanges, + 2, + saveChanges(), + ([a, b]) => { + expect(a.type).toEqual(SAVING); + expect(b.type).toEqual(SAVE_SUCCESS); + done(); + }, stateFeaturegrid + ); + }); it('on Save', (done) => { testEpic( updateSelectedOnSaveOrCloseFeatureGrid, diff --git a/web/client/epics/featuregrid.js b/web/client/epics/featuregrid.js index 16b9a27a94..a8738f19f9 100644 --- a/web/client/epics/featuregrid.js +++ b/web/client/epics/featuregrid.js @@ -11,8 +11,7 @@ import {get, head, isEmpty, find, castArray, includes, reduce} from 'lodash'; import { LOCATION_CHANGE } from 'connected-react-router'; import axios from '../libs/ajax'; import bbox from '@turf/bbox'; -import { fidFilter } from '../utils/ogc/Filter/filter'; -import { getDefaultFeatureProjection, getPagesToLoad, gridUpdateToQueryUpdate, updatePages } from '../utils/FeatureGridUtils'; +import { createChangesTransaction, getDefaultFeatureProjection, getPagesToLoad, gridUpdateToQueryUpdate, updatePages } from '../utils/FeatureGridUtils'; import assign from 'object-assign'; import { @@ -232,15 +231,6 @@ const addPagination = (filterObj, pagination) => ({ pagination }); -const createChangesTransaction = (changes, newFeatures, {insert, update, propertyChange, getPropertyName, transaction})=> - transaction( - newFeatures.map(f => insert(f)), - Object.keys(changes).map( id => - Object.keys(changes[id]).map(name => - update([propertyChange(getPropertyName(name), changes[id][name]), fidFilter("ogc", id)]) - ) - ) - ); const createDeleteTransaction = (features, {transaction, deleteFeature}) => transaction( features.map(deleteFeature) ); diff --git a/web/client/utils/FeatureGridUtils.js b/web/client/utils/FeatureGridUtils.js index b5406acd93..b35c34341f 100644 --- a/web/client/utils/FeatureGridUtils.js +++ b/web/client/utils/FeatureGridUtils.js @@ -18,6 +18,7 @@ import { } from './ogc/WFS/base'; import { applyDefaultToLocalizedString } from '../components/I18N/LocalizedString'; +import { fidFilter } from './ogc/Filter/filter'; const getGeometryName = (describe) => get(findGeometryProperty(describe), "name"); const getPropertyName = (name, describe) => name === "geometry" ? getGeometryName(describe) : name; @@ -392,3 +393,17 @@ export const supportsFeatureEditing = (layer) => includes(supportedEditLayerType * @returns {boolean} flag */ export const areLayerFeaturesEditable = (layer) => !layer?.disableFeaturesEditing && supportsFeatureEditing(layer); +/** + * Create wfs-t xml payload for insert/edit features in featuregrid + * @param {object} changes object that contains updates e.g: {LAYER_NAME.id: {"FIELD1": 55, "FIELD2":"edit 02"}} + * @param {object[]} newFeatures array of new inserted features + * @param {object} wfsutils object of wfs utils that includes insert/update/propertyChange/getPropertyName/transaction + * @returns {string} wfs-transaction xml payload + */ +export const createChangesTransaction = (changes, newFeatures, {insert, update, propertyChange, getPropertyName: getPropertyNameFunc, transaction})=> + transaction( + newFeatures.map(f => insert(f)), + Object.keys(changes).map( id =>{ + return update(Object.keys(changes[id]).map(prop => propertyChange(getPropertyNameFunc(prop), changes[id][prop])), fidFilter("ogc", id)); + }) + ); diff --git a/web/client/utils/__tests__/FeatureGridUtils-test.js b/web/client/utils/__tests__/FeatureGridUtils-test.js index 53f140dcd3..5609f2b76d 100644 --- a/web/client/utils/__tests__/FeatureGridUtils-test.js +++ b/web/client/utils/__tests__/FeatureGridUtils-test.js @@ -16,8 +16,10 @@ import { getAttributesNames, featureTypeToGridColumns, supportsFeatureEditing, - areLayerFeaturesEditable + areLayerFeaturesEditable, + createChangesTransaction } from '../FeatureGridUtils'; +import requestBuilder from "../ogc/WFST/RequestBuilder"; describe('FeatureGridUtils', () => { @@ -447,4 +449,57 @@ describe('FeatureGridUtils', () => { expect(areLayerFeaturesEditable({type: "wmts"})).toBeFalsy(); }); }); + describe('test featuregrid transactions utils', ()=>{ + const describeFeatureType = { + "elementFormDefault": "qualified", + "targetNamespace": "http://localhost:8080/geoserver/mapstore", + "targetPrefix": "mapstore", + "featureTypes": [ + { + "typeName": "TEST_LAYER", + "properties": [ + { + "name": "Integer", + "maxOccurs": 1, + "minOccurs": 0, + "nillable": true, + "type": "xsd:int", + "localType": "int" + }, + { + "name": "Long", + "maxOccurs": 1, + "minOccurs": 0, + "nillable": true, + "type": "xsd:int", + "localType": "int" + }, + { + "name": "Point", + "maxOccurs": 1, + "minOccurs": 0, + "nillable": true, + "type": "gml:Point", + "localType": "Point" + } + ] + } + ] + + }; + it('test createChangesTransaction for single edit', (done) => { + const singleChanges = {"TEST_LAYER.13": { "Integer": 50}}; + const transactionPayload = createChangesTransaction(singleChanges, [], requestBuilder(describeFeatureType)); + const samplePayload = `Integer50`; + expect(transactionPayload).toEqual(samplePayload); + done(); + }); + it('test createChangesTransaction for multi-edit', (done) => { + const multiChanges = {"TEST_LAYER.13": { "Integer": 50, "Long": 55 }}; + const transactionPayload = createChangesTransaction(multiChanges, [], requestBuilder(describeFeatureType)); + const multieditPayload = `Integer50,Long55`; + expect(transactionPayload).toEqual(multieditPayload); + done(); + }); + }); });