From 71c6d9ffb57c741d0a56c14ade01c2421afdef66 Mon Sep 17 00:00:00 2001 From: mahmoudadel54 Date: Tue, 5 Nov 2024 13:26:38 +0200 Subject: [PATCH 1/4] #10648: Issue editing multiple fields in MapStore Attribute Table Description: - edit in update wfs-t xml payload in case of multi-edit in each sigle row - add unit test for 'savePendingFeatureGridChanges' --- .../epics/__tests__/featuregrid-test.js | 86 ++++++++++++++++++- web/client/epics/featuregrid.js | 8 +- 2 files changed, 87 insertions(+), 7 deletions(-) diff --git a/web/client/epics/__tests__/featuregrid-test.js b/web/client/epics/__tests__/featuregrid-test.js index fad283631f..079df9989e 100644 --- a/web/client/epics/__tests__/featuregrid-test.js +++ b/web/client/epics/__tests__/featuregrid-test.js @@ -60,7 +60,9 @@ import { launchUpdateFilterFunc, LAUNCH_UPDATE_FILTER_FUNC, setLayer, - setViewportFilter, SET_VIEWPORT_FILTER + setViewportFilter, SET_VIEWPORT_FILTER, + SAVING, + saveChanges } from '../../actions/featuregrid'; import { SET_HIGHLIGHT_FEATURES_PATH } from '../../actions/highlight'; @@ -141,7 +143,8 @@ import { toggleSnappingOffOnFeatureGridViewMode, closeFeatureGridOnDrawingToolOpen, setViewportFilterEpic, - deactivateViewportFilterEpic, resetViewportFilter + deactivateViewportFilterEpic, resetViewportFilter, + savePendingFeatureGridChanges } from '../featuregrid'; import { onLocationChanged } from 'connected-react-router'; import { TEST_TIMEOUT, testEpic, addTimeoutEpic } from './epicTestUtils'; @@ -1822,6 +1825,85 @@ describe('featuregrid Epics', () => { })); }); describe('updateSelectedOnSaveOrCloseFeatureGrid', () => { + 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 + } + } + ] + } + }; + testEpic( + savePendingFeatureGridChanges, + 1, + saveChanges(), + ([a]) => { + expect(a.type).toEqual(SAVING); + done(); + }, stateFeaturegrid + ); + }); it('on Save', (done) => { testEpic( updateSelectedOnSaveOrCloseFeatureGrid, diff --git a/web/client/epics/featuregrid.js b/web/client/epics/featuregrid.js index 16b9a27a94..895a0d0889 100644 --- a/web/client/epics/featuregrid.js +++ b/web/client/epics/featuregrid.js @@ -235,11 +235,9 @@ const addPagination = (filterObj, 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)]) - ) - ) + Object.keys(changes).map( id =>{ + return update(Object.keys(changes[id]).map(prop => propertyChange(getPropertyName(prop), changes[id][prop])), fidFilter("ogc", id)); + }) ); const createDeleteTransaction = (features, {transaction, deleteFeature}) => transaction( features.map(deleteFeature) From e76bec33f6eb6c03bcd4b57274a6d9d17251587f Mon Sep 17 00:00:00 2001 From: mahmoudadel54 Date: Tue, 5 Nov 2024 15:46:40 +0200 Subject: [PATCH 2/4] #10648: move 'createChangesTransaction' util function from epics/featuregrid to FeatureGridUtils file and a unit test is added for it. --- web/client/epics/featuregrid.js | 10 +--- web/client/utils/FeatureGridUtils.js | 15 +++++ .../utils/__tests__/FeatureGridUtils-test.js | 57 ++++++++++++++++++- 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/web/client/epics/featuregrid.js b/web/client/epics/featuregrid.js index 895a0d0889..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,13 +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 =>{ - return update(Object.keys(changes[id]).map(prop => propertyChange(getPropertyName(prop), changes[id][prop])), 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..de76dd900e 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/delete features in featuregrid + * @param {array} changes array of update/delete objects + * @param {array} 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(); + }); + }); }); From 2c91bcd12b259701c86d541bb4025581fe2db7e7 Mon Sep 17 00:00:00 2001 From: mahmoudadel54 Date: Tue, 5 Nov 2024 16:56:01 +0200 Subject: [PATCH 3/4] #10648: handle unit test for 'savePendingFeatureGridChanges' --- .../epics/__tests__/featuregrid-test.js | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/web/client/epics/__tests__/featuregrid-test.js b/web/client/epics/__tests__/featuregrid-test.js index 079df9989e..72c7193a99 100644 --- a/web/client/epics/__tests__/featuregrid-test.js +++ b/web/client/epics/__tests__/featuregrid-test.js @@ -62,7 +62,8 @@ import { setLayer, setViewportFilter, SET_VIEWPORT_FILTER, SAVING, - saveChanges + saveChanges, + SAVE_SUCCESS } from '../../actions/featuregrid'; import { SET_HIGHLIGHT_FEATURES_PATH } from '../../actions/highlight'; @@ -150,6 +151,9 @@ 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: [ @@ -1825,6 +1829,13 @@ describe('featuregrid Epics', () => { })); }); describe('updateSelectedOnSaveOrCloseFeatureGrid', () => { + let mockAxios; + beforeEach(() => { + mockAxios = new MockAdapter(axios); + }); + afterEach(() => { + mockAxios.restore(); + }); it("test savePendingFeatureGridChanges", (done) => { const stateFeaturegrid = { query: { @@ -1894,12 +1905,15 @@ describe('featuregrid Epics', () => { ] } }; + const payloadSample = `Integer50,Long55`; + mockAxios.onPost(stateFeaturegrid.query.searchUrl, payloadSample).replyOnce(200); testEpic( savePendingFeatureGridChanges, - 1, + 2, saveChanges(), - ([a]) => { + ([a, b]) => { expect(a.type).toEqual(SAVING); + expect(b.type).toEqual(SAVE_SUCCESS); done(); }, stateFeaturegrid ); From b18fd0156015ddaa85f748f016c7581ed2f8e320 Mon Sep 17 00:00:00 2001 From: mahmoudadel54 Date: Tue, 5 Nov 2024 17:37:35 +0200 Subject: [PATCH 4/4] #10648: edit jsdoc for util 'createChangesTransaction' --- web/client/utils/FeatureGridUtils.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/client/utils/FeatureGridUtils.js b/web/client/utils/FeatureGridUtils.js index de76dd900e..b35c34341f 100644 --- a/web/client/utils/FeatureGridUtils.js +++ b/web/client/utils/FeatureGridUtils.js @@ -394,9 +394,9 @@ export const supportsFeatureEditing = (layer) => includes(supportedEditLayerType */ export const areLayerFeaturesEditable = (layer) => !layer?.disableFeaturesEditing && supportsFeatureEditing(layer); /** - * Create wfs-t xml payload for insert/edit/delete features in featuregrid - * @param {array} changes array of update/delete objects - * @param {array} newFeatures array of new inserted features + * 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 */