Skip to content

Commit f0c756e

Browse files
committed
#1977: Move group and owner setting to management panel
1 parent c7d48a5 commit f0c756e

File tree

21 files changed

+328
-67
lines changed

21 files changed

+328
-67
lines changed

geonode_mapstore_client/client/js/api/geonode/v2/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,11 +377,13 @@ export const getUsers = ({
377377
q,
378378
page = 1,
379379
pageSize = 20,
380+
config,
380381
...params
381382
} = {}) => {
382383
return axios.get(
383384
getEndpointUrl(USERS),
384385
{
386+
...config,
385387
params: {
386388
...params,
387389
...(q && {
@@ -406,11 +408,13 @@ export const getGroups = ({
406408
q,
407409
page = 1,
408410
pageSize = 20,
411+
config,
409412
...params
410413
} = {}) => {
411414
return axios.get(
412415
getEndpointUrl(GROUPS),
413416
{
417+
...config,
414418
params: {
415419
...params,
416420
...(q && {
@@ -431,6 +435,11 @@ export const getGroups = ({
431435
});
432436
};
433437

438+
export const transferResource = (pk, body) => {
439+
return axios.post(getEndpointUrl(USERS, `/${pk}/transfer_resources`), body)
440+
.then(({ data }) => (data));
441+
};
442+
434443
export const getUserByPk = (pk, apikey) => {
435444
return axios.get(getEndpointUrl(USERS, `/${pk}`), {
436445
params: {

geonode_mapstore_client/client/js/epics/__tests__/gnsave-test.js

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { SET_EDIT_PERMISSION } from '@mapstore/framework/actions/styleeditor';
3333
import { configureMap } from '@mapstore/framework/actions/config';
3434

3535
import { selectNode, addLayer } from '@mapstore/framework/actions/layers';
36+
import { START_ASYNC_PROCESS } from '@js/actions/resourceservice';
3637

3738

3839
let mockAxios;
@@ -49,13 +50,101 @@ describe('gnsave epics', () => {
4950
setTimeout(done);
5051
});
5152
it('should create new map with success (gnSaveContent)', (done) => {
53+
const NUM_ACTIONS = 5;
54+
const metadata = {
55+
title: 'Title',
56+
description: 'Description',
57+
thumbnail: 'thumbnail.jpeg'
58+
};
59+
mockAxios.onPost().reply(() => [200, { map: {} }]);
60+
mockAxios.onPut().reply(() => [200, { output: {} }]);
61+
testEpic(
62+
gnSaveContent,
63+
NUM_ACTIONS,
64+
saveContent(undefined, metadata, false),
65+
(actions) => {
66+
try {
67+
expect(actions.map(({ type }) => type))
68+
.toEqual([
69+
SAVING_RESOURCE,
70+
SAVE_SUCCESS,
71+
SET_RESOURCE,
72+
UPDATE_SINGLE_RESOURCE,
73+
START_ASYNC_PROCESS
74+
]);
75+
} catch (e) {
76+
done(e);
77+
}
78+
done();
79+
},
80+
{
81+
gnresource: {
82+
data: {
83+
resource_type: "map"
84+
},
85+
isCompactPermissionsChanged: true,
86+
compactPermissions: {
87+
users: [],
88+
organizations: [],
89+
groups: []
90+
}
91+
}
92+
}
93+
);
94+
});
95+
it('should update existing map with success (gnSaveContent)', (done) => {
96+
const NUM_ACTIONS = 5;
97+
const id = 1;
98+
const metadata = {
99+
title: 'Title',
100+
description: 'Description',
101+
thumbnail: 'thumbnail.jpeg'
102+
};
103+
mockAxios.onPatch().reply(() => [200, {}]);
104+
mockAxios.onPut().reply(() => [200, { output: {} }]);
105+
testEpic(
106+
gnSaveContent,
107+
NUM_ACTIONS,
108+
saveContent(id, metadata, false, false),
109+
(actions) => {
110+
try {
111+
expect(actions.map(({ type }) => type))
112+
.toEqual([
113+
SAVING_RESOURCE,
114+
SAVE_SUCCESS,
115+
SET_RESOURCE,
116+
UPDATE_SINGLE_RESOURCE,
117+
START_ASYNC_PROCESS
118+
]);
119+
} catch (e) {
120+
done(e);
121+
}
122+
done();
123+
},
124+
{
125+
gnresource: {
126+
data: {
127+
resource_type: "map"
128+
},
129+
isCompactPermissionsChanged: true,
130+
compactPermissions: {
131+
users: [],
132+
organizations: [],
133+
groups: []
134+
}
135+
}
136+
}
137+
);
138+
});
139+
it('should skip permission update when permission is unchanged', (done) => {
52140
const NUM_ACTIONS = 4;
53141
const metadata = {
54142
title: 'Title',
55143
description: 'Description',
56144
thumbnail: 'thumbnail.jpeg'
57145
};
58146
mockAxios.onPost().reply(() => [200, { map: {} }]);
147+
mockAxios.onPut().reply(() => [200, { output: {} }]);
59148
testEpic(
60149
gnSaveContent,
61150
NUM_ACTIONS,
@@ -110,7 +199,15 @@ describe('gnsave epics', () => {
110199
},
111200
{
112201
gnresource: {
113-
type: "map"
202+
data: {
203+
resource_type: "map"
204+
},
205+
isCompactPermissionsChanged: false,
206+
compactPermissions: {
207+
users: [],
208+
organizations: [],
209+
groups: []
210+
}
114211
}
115212
}
116213
);
@@ -170,7 +267,7 @@ describe('gnsave epics', () => {
170267
'thumbnail_url': 'thumbnail.jpeg'
171268
};
172269
mockAxios.onGet(new RegExp(`resources/${pk}`))
173-
.reply(200, resource);
270+
.reply(200, {resource});
174271
testEpic(
175272
gnSaveDirectContent,
176273
NUM_ACTIONS,

geonode_mapstore_client/client/js/epics/gnsave.js

Lines changed: 64 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import axios from '@mapstore/framework/libs/ajax';
1010
import { Observable } from 'rxjs';
1111
import get from 'lodash/get';
1212
import castArray from 'lodash/castArray';
13+
import isEqual from 'lodash/isEqual';
1314

1415
import { mapInfoSelector } from '@mapstore/framework/selectors/map';
1516
import { userSelector } from '@mapstore/framework/selectors/security';
@@ -76,17 +77,18 @@ import {
7677
cleanCompactPermissions,
7778
toGeoNodeMapConfig,
7879
RESOURCE_MANAGEMENT_PROPERTIES,
79-
getDimensions
80+
getDimensions,
81+
GROUP_OWNER_PROPERTIES
8082
} from '@js/utils/ResourceUtils';
8183
import {
8284
ProcessTypes,
8385
ProcessStatus
8486
} from '@js/utils/ResourceServiceUtils';
85-
import { updateDatasetTimeSeries } from '@js/api/geonode/v2/index';
87+
import { transferResource, updateDatasetTimeSeries } from '@js/api/geonode/v2/index';
8688
import { updateNode } from '@mapstore/framework/actions/layers';
8789
import { layersSelector } from '@mapstore/framework/selectors/layers';
8890

89-
const RESOURCE_MANAGEMENT_PROPERTIES_KEYS = Object.keys(RESOURCE_MANAGEMENT_PROPERTIES);
91+
const RESOURCE_MANAGEMENT_PROPERTIES_KEYS = Object.keys({...RESOURCE_MANAGEMENT_PROPERTIES, ...GROUP_OWNER_PROPERTIES});
9092

9193
function parseMapBody(body) {
9294
const geoNodeMap = toGeoNodeMapConfig(body.data);
@@ -169,11 +171,13 @@ export const gnSaveContent = (action$, store) =>
169171
const contentType = state.gnresource?.type || currentResource?.resource_type;
170172
const data = !currentResource?.['@ms-detail'] ? getDataPayload(state, contentType) : null;
171173
const extent = getExtentPayload(state, contentType);
174+
const { compactPermissions } = getPermissionsPayload(state);
172175
const body = {
173176
'title': action.metadata.name,
174177
...(RESOURCE_MANAGEMENT_PROPERTIES_KEYS.reduce((acc, key) => {
175178
if (currentResource?.[key] !== undefined) {
176-
acc[key] = !!currentResource[key];
179+
const value = typeof currentResource[key] === 'boolean' ? !!currentResource[key] : currentResource[key];
180+
acc[key] = value;
177181
}
178182
return acc;
179183
}, {})),
@@ -193,22 +197,34 @@ export const gnSaveContent = (action$, store) =>
193197
window.location.reload();
194198
return Observable.empty();
195199
}
196-
return Observable.of(
197-
saveSuccess(resource),
198-
setResource({
199-
...currentResource,
200-
...body,
201-
...resource
202-
}),
203-
updateResource(resource),
204-
...(action.showNotifications
205-
? [
206-
action.showNotifications === true
207-
? successNotification({title: "saveDialog.saveSuccessTitle", message: "saveDialog.saveSuccessMessage"})
208-
: warningNotification(action.showNotifications)
209-
]
210-
: []),
211-
...actions // additional actions to be dispatched
200+
return Observable.merge(
201+
Observable.of(
202+
saveSuccess(resource),
203+
setResource({
204+
...currentResource,
205+
...body,
206+
...resource
207+
}),
208+
updateResource(resource),
209+
...(action.showNotifications
210+
? [
211+
action.showNotifications === true
212+
? successNotification({title: "saveDialog.saveSuccessTitle", message: "saveDialog.saveSuccessMessage"})
213+
: warningNotification(action.showNotifications)
214+
]
215+
: []),
216+
...actions // additional actions to be dispatched
217+
),
218+
...(compactPermissions ? [
219+
Observable.defer(() =>
220+
updateCompactPermissionsByPk(action.id, cleanCompactPermissions(compactPermissions))
221+
.then(output => ({ resource: currentResource, output, processType: ProcessTypes.PERMISSIONS_RESOURCE }))
222+
.catch((error) => ({ resource: currentResource, error: error?.data?.detail || error?.statusText || error?.message || true, processType: ProcessTypes.PERMISSIONS_RESOURCE }))
223+
)
224+
.switchMap((payload) => {
225+
return Observable.of(startAsyncProcess(payload));
226+
})
227+
] : [])
212228
);
213229
})
214230
.catch((error) => {
@@ -264,22 +280,35 @@ export const gnSaveDirectContent = (action$, store) =>
264280
const state = store.getState();
265281
const mapInfo = mapInfoSelector(state);
266282
const resourceId = mapInfo?.id || getResourceId(state);
267-
const { compactPermissions, geoLimits } = getPermissionsPayload(state);
283+
const { geoLimits } = getPermissionsPayload(state);
268284
const currentResource = getResourceData(state);
269285

270-
return Observable.defer(() => axios.all([
271-
getResourceByPk(resourceId),
272-
...(geoLimits
273-
? geoLimits.map((limits) =>
274-
limits.features.length === 0
275-
? deleteGeoLimits(resourceId, limits.id, limits.type)
276-
.catch(() => ({ error: true, resourceId, limits }))
277-
: updateGeoLimits(resourceId, limits.id, limits.type, { features: limits.features })
278-
.catch(() => ({ error: true, resourceId, limits }))
279-
)
280-
: [])
281-
]))
282-
.switchMap(([resource, ...geoLimitsResponses]) => {
286+
const newOwner = get(currentResource, 'owner.pk', null);
287+
const currentOwner = get(state, 'gnresource.initialResource.owner.pk', null);
288+
const userId = userSelector(state)?.pk;
289+
let transferOwnership;
290+
if (newOwner && userId && !isEqual(currentOwner, newOwner)) {
291+
transferOwnership = { currentOwner, newOwner, resources: [Number(resourceId)] };
292+
}
293+
294+
// resource information should be saved in a synchronous manner
295+
// i.e transfer ownership (if any) followed by resource data and finally permissions
296+
return Observable.concat(
297+
transferOwnership ? Observable.defer(() => transferResource(userId, transferOwnership)) : Promise.resolve(),
298+
Observable.defer(() => axios.all([
299+
getResourceByPk(resourceId),
300+
...(geoLimits
301+
? geoLimits.map((limits) =>
302+
limits.features.length === 0
303+
? deleteGeoLimits(resourceId, limits.id, limits.type)
304+
.catch(() => ({ error: true, resourceId, limits }))
305+
: updateGeoLimits(resourceId, limits.id, limits.type, { features: limits.features })
306+
.catch(() => ({ error: true, resourceId, limits }))
307+
)
308+
: [])
309+
]))).toArray()
310+
.switchMap(([, responses]) => {
311+
const [resource, ...geoLimitsResponses] = responses;
283312
const geoLimitsErrors = geoLimitsResponses.filter(({ error }) => error);
284313
const name = getResourceName(state);
285314
const description = getResourceDescription(state);
@@ -290,16 +319,6 @@ export const gnSaveDirectContent = (action$, store) =>
290319
href: resource?.href
291320
};
292321
return Observable.concat(
293-
...(compactPermissions ? [
294-
Observable.defer(() =>
295-
updateCompactPermissionsByPk(resourceId, cleanCompactPermissions(compactPermissions))
296-
.then(output => ({ resource: currentResource, output, processType: ProcessTypes.PERMISSIONS_RESOURCE }))
297-
.catch((error) => ({ resource: currentResource, error: error?.data?.detail || error?.statusText || error?.message || true, processType: ProcessTypes.PERMISSIONS_RESOURCE }))
298-
)
299-
.switchMap((payload) => {
300-
return Observable.of(startAsyncProcess(payload));
301-
})
302-
] : []),
303322
Observable.of(
304323
saveContent(
305324
resourceId,
@@ -313,6 +332,7 @@ export const gnSaveDirectContent = (action$, store) =>
313332
: true /* showNotification */),
314333
resetGeoLimits()
315334
)
335+
316336
);
317337
})
318338
.catch((error) => {

0 commit comments

Comments
 (0)