Skip to content

Commit

Permalink
[Backport 2.1.x] 157: Read layers permissions from API v2 instead of …
Browse files Browse the repository at this point in the history
…using style-check and edit-check requests (#182)
  • Loading branch information
allyoucanmap committed May 18, 2021
1 parent 9603b35 commit e28c2e7
Show file tree
Hide file tree
Showing 224 changed files with 236 additions and 90 deletions.
16 changes: 16 additions & 0 deletions geonode_mapstore_client/client/js/actions/gnresource.js
Expand Up @@ -14,6 +14,7 @@ export const SET_RESOURCE_TYPE = 'GEONODE:SET_RESOURCE_TYPE';
export const SET_NEW_RESOURCE = 'GEONODE:SET_NEW_RESOURCE';
export const SET_RESOURCE_ID = 'GEONODE:SET_RESOURCE_ID';
export const SET_RESOURCE_PERMISSIONS = 'GEONODE:SET_RESOURCE_PERMISSIONS';
export const SET_SELECTED_LAYER_PERMISSIONS = "GEONODE:SET_SELECTED_LAYER_PERMISSIONS";

/**
* Actions for GeoNode resource
Expand Down Expand Up @@ -114,3 +115,18 @@ export function setResourcePermissions(permissions) {
permissions
};
}

/**
* Set resource permissions
* @memberof actions.gnresource
* @param {object} permissions permissions info
* @param {bool} permissions.canEdit can edit permission
* @param {bool} permissions.canView can view permission
*/

export function setSelectedLayerPermissions(permissions) {
return {
type: SET_SELECTED_LAYER_PERMISSIONS,
permissions
};
}
4 changes: 2 additions & 2 deletions geonode_mapstore_client/client/js/actions/gnsave.js
Expand Up @@ -12,7 +12,7 @@ export const SAVE_ERROR = 'GEONODE:SAVE_ERROR';
export const CLEAR_SAVE = 'GEONODE:CLEAR_SAVE';
export const SAVE_CONTENT = 'GEONODE:SAVE_CONTENT';
export const UPDATE_RESOURCE_BEFORE_SAVE = 'GEONODE:UPDATE_RESOURCE_BEFORE_SAVE';
export const SAVE_DIRECT_CONTENT = 'GEONODE:SAVE_DIRECT_CONTENT'
export const SAVE_DIRECT_CONTENT = 'GEONODE:SAVE_DIRECT_CONTENT';

/**
* Actions for GeoNode save workflow
Expand Down Expand Up @@ -102,4 +102,4 @@ export function saveDirectContent() {
return {
type: SAVE_DIRECT_CONTENT
};
}
}
17 changes: 17 additions & 0 deletions geonode_mapstore_client/client/js/api/geonode/v2/index.js
Expand Up @@ -331,6 +331,23 @@ export const getResourceTypes = ({}, filterKey = 'resource-types') => {
});
};

export const getLayerByName = name => {
const url = parseDevHostname(`${endpoints[LAYERS]}/?filter{alternate}=${name}`);
return axios.get(url)
.then(({data}) => data?.layers[0]);
};

export const getLayersByName = names => {
const url = parseDevHostname(endpoints[LAYERS]);
return axios.get(url, {
params: {
page_size: names.length,
'filter{alternate.in}': names
}
})
.then(({data}) => data?.layers);
};

export const getResourcesTotalCount = () => {
const params = {
page_size: 1
Expand Down
4 changes: 2 additions & 2 deletions geonode_mapstore_client/client/js/apps/gn-geostory.js
Expand Up @@ -20,7 +20,7 @@ import { registerMediaAPI } from '@mapstore/framework/api/media';
import * as geoNodeMediaApi from '@js/observables/media/geonode';
import {
getEndpoints,
getConfiguration,getAccountInfo
getConfiguration, getAccountInfo
} from '@js/api/geonode/v2';
import {
setResourceType,
Expand Down Expand Up @@ -111,7 +111,7 @@ document.addEventListener('DOMContentLoaded', function() {
Promise.all([
getConfiguration(),
getAccountInfo(),
getEndpoints(),
getEndpoints()
])
.then(([localConfig, user]) => {
const {
Expand Down
8 changes: 4 additions & 4 deletions geonode_mapstore_client/client/js/apps/gn-map.js
Expand Up @@ -58,8 +58,8 @@ import { updateGeoNodeSettings } from '@js/actions/gnsettings';

import {
updateMapLayoutEpic,
_setFeatureEditPermission,
_setStyleEditorPermission
gnCheckSelectedLayerPermissions,
setLayersPermissions
} from '@js/epics';
import maplayout from '@mapstore/framework/reducers/maplayout';
import 'react-widgets/dist/css/react-widgets.css';
Expand Down Expand Up @@ -174,8 +174,8 @@ Promise.all([
...standardEpics,
...configEpics,
updateMapLayoutEpic,
_setFeatureEditPermission,
_setStyleEditorPermission,
gnCheckSelectedLayerPermissions,
setLayersPermissions,
...pluginsDefinition.epics
},
geoNodeConfiguration,
Expand Down
59 changes: 56 additions & 3 deletions geonode_mapstore_client/client/js/epics/__tests__/gnsave-test.js
Expand Up @@ -22,13 +22,21 @@ import {
UPDATE_RESOURCE_PROPERTIES,
RESOURCE_LOADING,
SET_RESOURCE,
RESOURCE_ERROR
RESOURCE_ERROR,
SET_SELECTED_LAYER_PERMISSIONS
} from '@js/actions/gnresource';
import {
gnSaveContent,
gnUpdateResource,
gnSaveDirectContent
} from '@js/epics/gnsave';
import {gnCheckSelectedLayerPermissions, setLayersPermissions} from '@js/epics';
import { SET_PERMISSION } from '@mapstore/framework/actions/featuregrid';
import { SET_EDIT_PERMISSION } from '@mapstore/framework/actions/styleeditor';
import { configureMap } from '@mapstore/framework/actions/config';

import { selectNode, addLayer } from '@mapstore/framework/actions/layers';


let mockAxios;

Expand Down Expand Up @@ -172,17 +180,62 @@ describe('gnsave epics', () => {
);
});

it("gnCheckSelectedLayerPermissions should trigger permission actions for style and edit", (done) => {

const NUM_ACTIONS = 3;
testEpic(gnCheckSelectedLayerPermissions,
NUM_ACTIONS, selectNode(1, "layer"), (actions) => {
try {
expect(actions.map(({type}) => type)).toEqual([SET_PERMISSION, SET_SELECTED_LAYER_PERMISSIONS, SET_EDIT_PERMISSION]);
done();
} catch (error) {
done(error);
}
}, {layers: {flat: [{name: "testLayer", id: "test_id", perms: ['download_resourcebase']}], selected: ["test_id"]}});

});

it('test setLayersPermissions trigger updateNode for MAP_CONFIG_LOADED', (done) => {
mockAxios.onGet().reply(() => [200,
{layers: [{perms: ['change_layer_style', 'change_layer_data'], alternate: "testLayer"}]}]);
const NUM_ACTIONS = 1;
testEpic(setLayersPermissions, NUM_ACTIONS, configureMap({map: {layers: [{name: "testLayer", id: "test_id"}]}}), (actions) => {
try {
expect(actions.map(({type}) => type)).toEqual(["UPDATE_NODE"]);
done();
} catch (error) {
done(error);
}
},
{layers: {flat: [{name: "testLayer", id: "test_id", perms: ['download_resourcebase']}], selected: ["test_id"]}});
});

it('test setLayersPermissions trigger updateNode for ADD_LAYER', (done) => {
mockAxios.onGet().reply(() => [200,
{layers: [{perms: ['change_layer_style', 'change_layer_data'], alternate: "testLayer"}]}]);
const NUM_ACTIONS = 1;
testEpic(setLayersPermissions, NUM_ACTIONS, addLayer({name: "testLayer"}), (actions) => {
try {
expect(actions.map(({type}) => type)).toEqual(["UPDATE_NODE"]);
done();
} catch (error) {
done(error);
}
},
{layers: {flat: [{name: "testLayer", id: "test_id", perms: ['download_resourcebase']}], selected: ["test_id"]}});
});

it('should trigger saveResource (gnSaveDirectContent)', (done) => {
const NUM_ACTIONS = 2;
const pk = 1
const pk = 1;
const resource = {
'id': pk,
'title': 'Map',
'abstract': 'Description',
'thumbnail_url': 'thumbnail.jpeg'
};
mockAxios.onGet(new RegExp(`resources/${pk}`))
.reply(200, resource);
.reply(200, resource);
testEpic(
gnSaveDirectContent,
NUM_ACTIONS,
Expand Down
16 changes: 8 additions & 8 deletions geonode_mapstore_client/client/js/epics/gnsave.js
Expand Up @@ -7,7 +7,7 @@
*/

import { Observable } from 'rxjs';
import { mapSelector } from '@mapstore/framework/selectors/map';
import { mapSelector, mapInfoSelector } from '@mapstore/framework/selectors/map';
import { layersSelector, groupsSelector } from '@mapstore/framework/selectors/layers';
import { backgroundListSelector } from '@mapstore/framework/selectors/backgroundselector';
import { mapOptionsToSaveSelector } from '@mapstore/framework/selectors/mapsave';
Expand All @@ -20,7 +20,7 @@ import { getConfigProp } from '@mapstore/framework/utils/ConfigUtils';
import { currentStorySelector } from '@mapstore/framework/selectors/geostory';
import { userSelector } from '@mapstore/framework/selectors/security';
import { error as errorNotification, success as successNotification } from '@mapstore/framework/actions/notifications';
import { mapInfoSelector } from '@mapstore/framework/selectors/map';


import {
creatMapStoreMap,
Expand All @@ -31,8 +31,8 @@ import {
UPDATE_RESOURCE_BEFORE_SAVE,
saveSuccess,
saveError,
savingResource,
SAVE_DIRECT_CONTENT,
savingResource,
SAVE_DIRECT_CONTENT,
saveContent
} from '@js/actions/gnsave';
import {
Expand Down Expand Up @@ -150,10 +150,10 @@ export const gnSaveContent = (action$, store) =>
return Observable.of(
saveError(error.data || error.message),
action.showNotifications && errorNotification({title: "map.mapError.errorTitle", message: "map.mapError.errorDefault"})
);
})
);
});

}).startWith(savingResource());;
}).startWith(savingResource());

export const gnSaveDirectContent = (action$, store) =>
action$.ofType(SAVE_DIRECT_CONTENT)
Expand All @@ -178,7 +178,7 @@ export const gnSaveDirectContent = (action$, store) =>
return Observable.of(
saveError(error.data || error.message),
errorNotification({title: "map.mapError.errorTitle", message: error.data || error.message || "map.mapError.errorDefault"})
);
);
});
}).startWith(savingResource());

Expand Down
76 changes: 46 additions & 30 deletions geonode_mapstore_client/client/js/epics/index.js
Expand Up @@ -12,18 +12,17 @@
import Rx from "rxjs";

import { setEditPermissionStyleEditor, INIT_STYLE_SERVICE } from "@mapstore/framework/actions/styleeditor";
import { layerEditPermissions, styleEditPermissions } from "@js/api/geonode";
import { getSelectedLayer } from "@mapstore/framework/selectors/layers";
import { getSelectedLayer, layersSelector } from "@mapstore/framework/selectors/layers";
import { getConfigProp } from "@mapstore/framework/utils/ConfigUtils";

import { getLayerByName, getLayersByName } from '@js/api/geonode/v2';
import { updateMapLayout } from '@mapstore/framework/actions/maplayout';
import { TOGGLE_CONTROL, SET_CONTROL_PROPERTY, SET_CONTROL_PROPERTIES } from '@mapstore/framework/actions/controls';
import { MAP_CONFIG_LOADED } from '@mapstore/framework/actions/config';
import { SIZE_CHANGE, CLOSE_FEATURE_GRID, OPEN_FEATURE_GRID, setPermission } from '@mapstore/framework/actions/featuregrid';
import { CLOSE_IDENTIFY, ERROR_FEATURE_INFO, TOGGLE_MAPINFO_STATE, LOAD_FEATURE_INFO, EXCEPTIONS_FEATURE_INFO, PURGE_MAPINFO_RESULTS } from '@mapstore/framework/actions/mapInfo';
import { SHOW_SETTINGS, HIDE_SETTINGS, SELECT_NODE } from '@mapstore/framework/actions/layers';
import { SHOW_SETTINGS, HIDE_SETTINGS, SELECT_NODE, updateNode, ADD_LAYER } from '@mapstore/framework/actions/layers';
import { isMapInfoOpen } from '@mapstore/framework/selectors/mapInfo';

import { setSelectedLayerPermissions } from '@js/actions/gnresource';
import { isFeatureGridOpen, getDockSize } from '@mapstore/framework/selectors/featuregrid';
import head from 'lodash/head';
import get from 'lodash/get';
Expand All @@ -34,34 +33,51 @@ import get from 'lodash/get';
import { showCoordinateEditorSelector } from '@mapstore/framework/selectors/controls';

/**
* When a user selects a layer, the app checks for layer editing permission.
* Handles checking and for permissions of a layer when its selected
*/
export const _setFeatureEditPermission = (action$, { getState } = {}) =>
action$.ofType(SELECT_NODE).filter(({ nodeType }) => nodeType === "layer" && !getConfigProp("disableCheckEditPermissions"))
export const gnCheckSelectedLayerPermissions = (action$, { getState } = {}) =>
action$.ofType(SELECT_NODE, INIT_STYLE_SERVICE)
.filter(({ nodeType }) => nodeType && nodeType === "layer" && !getConfigProp("disableCheckEditPermissions")
|| !nodeType && !getConfigProp("disableCheckEditPermissions"))
.switchMap(() => {
const layer = getSelectedLayer(getState() || {});
return layer ? layerEditPermissions(layer)
.map(permissions => setPermission(permissions))
.startWith(setPermission({ canEdit: false }))
.catch(() => Rx.Observable.empty()) : Rx.Observable.of(setPermission({ canEdit: false }));
const state = getState() || {};
const layer = getSelectedLayer(state);
const permissions = layer?.perms || [];
const canEditStyles = permissions.includes("change_layer_style");
const canEdit = permissions.includes("change_layer_data");
return layer ? Rx.Observable.of(
setPermission({canEdit}),
setEditPermissionStyleEditor(canEditStyles),
setSelectedLayerPermissions(permissions)
)
.startWith(setPermission({canEdit: false}), setSelectedLayerPermissions([]), setEditPermissionStyleEditor(false))
.catch(() => {Rx.Observable.empty();}) : Rx.Observable.of(setPermission({canEdit: false}), setEditPermissionStyleEditor(false), setSelectedLayerPermissions([]));
});


/**
* When a user selects a layer, the app checks for style editing permission.
* INIT_STYLE_SERVICE si needed for map editing, it ensures an user has permission to edit style of a specific layer retrieved from catalog
* Checks the permissions for layers when a map is loaded and when a new layer is added
* to a map
*/
export const _setStyleEditorPermission = (action$, { getState } = {}) =>
action$.ofType(INIT_STYLE_SERVICE, SELECT_NODE)
.filter(({ nodeType }) =>
nodeType && nodeType === "layer" && !getConfigProp("disableCheckEditPermissions")
|| !nodeType && !getConfigProp("disableCheckEditPermissions"))
export const setLayersPermissions = (actions$, { getState = () => {}} = {}) =>
actions$.ofType(MAP_CONFIG_LOADED, ADD_LAYER)
.switchMap((action) => {
const layer = getSelectedLayer(getState() || {});
return layer
? styleEditPermissions(layer)
.map(({ canEdit }) => setEditPermissionStyleEditor(canEdit))
.startWith(setEditPermissionStyleEditor(action.canEdit))
.catch(() => Rx.Observable.empty())
: Rx.Observable.of(setEditPermissionStyleEditor(false));
if (action.type === MAP_CONFIG_LOADED) {
const layerNames = action.config?.map?.layers?.filter((l) => l?.group !== "background").map((l) => l.name);
return Rx.Observable.defer(() => getLayersByName(layerNames))
.switchMap((layers = []) => {
const stateLayers = layers.map((l) => ({
...l,
id: layersSelector(getState())?.find((la) => la.name === l.alternate)?.id
}));
return Rx.Observable.of(...stateLayers.map((l) => updateNode(l.id, 'layer', {perms: l.perms || []}) ));
});
}
return Rx.Observable.defer(() => getLayerByName(action.layer?.name))
.switchMap((layer = {}) => {
const layerId = layersSelector(getState())?.find((la) => la.name === layer.alternate)?.id;
return Rx.Observable.of(updateNode(layerId, 'layer', {perms: layer.perms}));
});
});

// Modified to accept map-layout from Config diff less NO_QUERYABLE_LAYERS, SET_CONTROL_PROPERTIES more action$.ofType(PURGE_MAPINFO_RESULTS)
Expand Down Expand Up @@ -135,7 +151,7 @@ export const updateMapLayoutEpic = (action$, store) =>
}));
});
export default {
_setFeatureEditPermission,
_setStyleEditorPermission,
updateMapLayoutEpic
gnCheckSelectedLayerPermissions,
updateMapLayoutEpic,
setLayersPermissions
};
4 changes: 2 additions & 2 deletions geonode_mapstore_client/client/js/plugins/Save.jsx
Expand Up @@ -49,10 +49,10 @@ import { saveDirectContent } from '@js/actions/gnsave';
*/
function Save(props) {
return props.saving ? (<div
style={{ position: 'absolute', width: '100%',
style={{ position: 'absolute', width: '100%',
height: '100%', backgroundColor: 'rgba(255, 255, 255, 0.75)',
top: '0px', zIndex: 1000, display: 'flex',
alignItems: 'center', justifyContent: 'center', right: '0px'}}>
alignItems: 'center', justifyContent: 'center', right: '0px'}}>
<Loader size={150}/>
</div>) : null;
}
Expand Down
10 changes: 8 additions & 2 deletions geonode_mapstore_client/client/js/reducers/gnresource.js
Expand Up @@ -14,10 +14,11 @@ import {
SET_RESOURCE_TYPE,
SET_NEW_RESOURCE,
SET_RESOURCE_ID,
SET_RESOURCE_PERMISSIONS
SET_RESOURCE_PERMISSIONS,
SET_SELECTED_LAYER_PERMISSIONS
} from '@js/actions/gnresource';

function gnresource(state = {}, action) {
function gnresource(state = {selectedLayerPermissions: []}, action) {
switch (action.type) {
case RESOURCE_LOADING: {
return {
Expand Down Expand Up @@ -74,6 +75,11 @@ function gnresource(state = {}, action) {
permissions: action.permissions
};
}
case SET_SELECTED_LAYER_PERMISSIONS:
return {
...state,
selectedLayerPermissions: action.permissions
};
default:
return state;
}
Expand Down

0 comments on commit e28c2e7

Please sign in to comment.