Skip to content

Commit

Permalink
#10648: Issue editing multiple fields in MapStore Attribute Table (#1…
Browse files Browse the repository at this point in the history
…0651)

* #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'

* #10648: move 'createChangesTransaction' util function from epics/featuregrid to FeatureGridUtils file and a unit test is added for it.

* #10648: handle unit test for 'savePendingFeatureGridChanges'

* #10648: edit jsdoc for util 'createChangesTransaction'
  • Loading branch information
mahmoudadel54 authored Nov 5, 2024
1 parent 8e730ae commit ccd0c60
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 14 deletions.
100 changes: 98 additions & 2 deletions web/client/epics/__tests__/featuregrid-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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: [
Expand Down Expand Up @@ -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 = `<wfs:Transaction service="WFS" version="1.1.0" xmlns:wfs="http://www.opengis.net/wfs" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs" xmlns:mapstore="http://localhost:8080/geoserver/mapstore"><wfs:Update typeName="mapstore:TEST_LAYER"><wfs:Property><wfs:Name>Integer</wfs:Name><wfs:Value>50</wfs:Value></wfs:Property>,<wfs:Property><wfs:Name>Long</wfs:Name><wfs:Value>55</wfs:Value></wfs:Property><ogc:Filter><ogc:FeatureId fid="TEST_LAYER.13"/></ogc:Filter></wfs:Update></wfs:Transaction>`;
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,
Expand Down
12 changes: 1 addition & 11 deletions web/client/epics/featuregrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
);
Expand Down
15 changes: 15 additions & 0 deletions web/client/utils/FeatureGridUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
})
);
57 changes: 56 additions & 1 deletion web/client/utils/__tests__/FeatureGridUtils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ import {
getAttributesNames,
featureTypeToGridColumns,
supportsFeatureEditing,
areLayerFeaturesEditable
areLayerFeaturesEditable,
createChangesTransaction
} from '../FeatureGridUtils';
import requestBuilder from "../ogc/WFST/RequestBuilder";


describe('FeatureGridUtils', () => {
Expand Down Expand Up @@ -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 = `<wfs:Transaction service="WFS" version="1.1.0" xmlns:wfs="http://www.opengis.net/wfs" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs" xmlns:mapstore="http://localhost:8080/geoserver/mapstore"><wfs:Update typeName="mapstore:TEST_LAYER"><wfs:Property><wfs:Name>Integer</wfs:Name><wfs:Value>50</wfs:Value></wfs:Property><ogc:Filter><ogc:FeatureId fid="TEST_LAYER.13"/></ogc:Filter></wfs:Update></wfs:Transaction>`;
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 = `<wfs:Transaction service="WFS" version="1.1.0" xmlns:wfs="http://www.opengis.net/wfs" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs" xmlns:mapstore="http://localhost:8080/geoserver/mapstore"><wfs:Update typeName="mapstore:TEST_LAYER"><wfs:Property><wfs:Name>Integer</wfs:Name><wfs:Value>50</wfs:Value></wfs:Property>,<wfs:Property><wfs:Name>Long</wfs:Name><wfs:Value>55</wfs:Value></wfs:Property><ogc:Filter><ogc:FeatureId fid="TEST_LAYER.13"/></ogc:Filter></wfs:Update></wfs:Transaction>`;
expect(transactionPayload).toEqual(multieditPayload);
done();
});
});
});

0 comments on commit ccd0c60

Please sign in to comment.