diff --git a/web/client/actions/__tests__/catalog-test.js b/web/client/actions/__tests__/catalog-test.js index 17fdf54de5..f93c6839fd 100644 --- a/web/client/actions/__tests__/catalog-test.js +++ b/web/client/actions/__tests__/catalog-test.js @@ -70,7 +70,9 @@ import { FORMAT_OPTIONS_LOADING, formatsLoading, SET_FORMAT_OPTIONS, - setSupportedFormats, addLayerAndDescribe, ADD_LAYER_AND_DESCRIBE + setSupportedFormats, addLayerAndDescribe, ADD_LAYER_AND_DESCRIBE, + INIT_PLUGIN, + initPlugin } from '../catalog'; import { SHOW_NOTIFICATION } from '../notifications'; @@ -373,4 +375,10 @@ describe('Test correctness of the catalog actions', () => { expect(action.formats).toEqual(formats); expect(action.url).toEqual(url); }); + it('test initPlugin', () => { + const options = {editingAllowedRoles: ['test']}; + const action = initPlugin(options); + expect(action.type).toBe(INIT_PLUGIN); + expect(action.options).toEqual(options); + }); }); diff --git a/web/client/actions/catalog.js b/web/client/actions/catalog.js index 41147acc91..fb5c5e26bb 100644 --- a/web/client/actions/catalog.js +++ b/web/client/actions/catalog.js @@ -53,6 +53,7 @@ export const FORMAT_OPTIONS_FETCH = 'CATALOG:FORMAT_OPTIONS_FETCH'; export const FORMAT_OPTIONS_LOADING = 'CATALOG:FORMAT_OPTIONS_LOADING'; export const SET_FORMAT_OPTIONS = 'CATALOG:SET_FORMAT_OPTIONS'; export const NEW_SERVICE_STATUS = 'CATALOG:NEW_SERVICE_STATUS'; +export const INIT_PLUGIN = 'CATALOG:INIT_PLUGIN'; /** * Adds a list of layers from the given catalogs to the map @@ -303,3 +304,10 @@ export const setNewServiceStatus = (status) => { status }; }; + +export const initPlugin = (options) => { + return { + type: INIT_PLUGIN, + options + }; +}; diff --git a/web/client/actions/dashboard.js b/web/client/actions/dashboard.js index 2069b5ed2a..b304077540 100644 --- a/web/client/actions/dashboard.js +++ b/web/client/actions/dashboard.js @@ -28,6 +28,7 @@ export const DASHBOARD_DELETE_SERVICE = "DASHBOARD:DELETE_SERVICE"; export const DASHBOARD_SAVE_SERVICE_LOADING = "DASHBOARD:SAVE_SERVICE_LOADING"; export const DASHBOARD_EXPORT = "DASHBOARD:EXPORT"; export const DASHBOARD_IMPORT = "DASHBOARD:IMPORT"; +export const INIT_PLUGIN = "DASHBOARD:INIT_PLUGIN"; export const setEditing = (editing) => ({type: SET_EDITING, editing }); @@ -92,3 +93,8 @@ export const dashboardDeleteService = (service, services) => ({ type: DASHBOARD_ * @param {bolean} loading the loading state of the dashboard catalog saving */ export const setDashboardServiceSaveLoading = loading => ({ type: DASHBOARD_SAVE_SERVICE_LOADING, loading}); + +/** + * @param {options} options the options to be updated on plugin initialization +*/ +export const initPlugin = (options) => ({ type: INIT_PLUGIN, options }); diff --git a/web/client/components/catalog/Catalog.jsx b/web/client/components/catalog/Catalog.jsx index c00a9979ba..d7c56a674e 100644 --- a/web/client/components/catalog/Catalog.jsx +++ b/web/client/components/catalog/Catalog.jsx @@ -81,7 +81,8 @@ class Catalog extends React.Component { layerBaseConfig: PropTypes.object, service: PropTypes.object, isNewServiceAdded: PropTypes.bool, - setNewServiceStatus: PropTypes.func + setNewServiceStatus: PropTypes.func, + canEdit: PropTypes.func }; static contextTypes = { @@ -323,7 +324,7 @@ class Catalog extends React.Component { - + onChangeSelectedService(val && val.value ? val.value : "")} placeholder={"catalog.servicePlaceholder"} /> - {isValidServiceSelected ? ( onChangeCatalogMode("edit", false)}> ) : null} - onChangeCatalogMode("edit", true)}> + {canEdit && onChangeCatalogMode("edit", true)}> - + } ); diff --git a/web/client/components/catalog/CompactCatalog.jsx b/web/client/components/catalog/CompactCatalog.jsx index 31ebcd0899..94123dbbb3 100644 --- a/web/client/components/catalog/CompactCatalog.jsx +++ b/web/client/components/catalog/CompactCatalog.jsx @@ -127,7 +127,9 @@ export default compose( onChangeSelectedService = () => {}, selectedService, onChangeCatalogMode = () => {}, getItems = (_items) => getCatalogItems(_items, selected), - onItemClick = ({record} = {}) => onRecordSelected(record, catalog)}) => { + onItemClick = ({record} = {}) => onRecordSelected(record, catalog), + canEditService +}) => { return (} + onSearchTextChange={setSearchText} + canEditService={canEditService}/>} footer={
{loading ? : null} {!isNil(total) ? : null} diff --git a/web/client/components/catalog/__tests__/Catalog-test.jsx b/web/client/components/catalog/__tests__/Catalog-test.jsx index 00eb33b71c..fa7e163a51 100644 --- a/web/client/components/catalog/__tests__/Catalog-test.jsx +++ b/web/client/components/catalog/__tests__/Catalog-test.jsx @@ -182,4 +182,46 @@ describe('Test Catalog panel', () => { expect(spyOnSearch.calls[0].arguments[0]).toEqual({ format: 'csw', url: 'url', startPosition: 1, maxRecords: 4, text: '', options: {service: SERVICE} }); expect(catalogPagination.length).toBe(1); // Pagination is displayed }); + it('test manage service with permission', () => { + const SERVICE = { + type: "csw", + url: "url", + title: "csw" + }; + ReactDOM.render(, document.getElementById("container")); + const container = document.getElementById("container"); + expect(container).toBeTruthy(); + let editEl = document.querySelector('.glyphicon-pencil'); + let addEl = document.querySelector('.glyphicon-plus'); + expect(editEl).toBeTruthy(); + expect(addEl).toBeTruthy(); + }); + it('test manage service with no permission', () => { + const SERVICE = { + type: "csw", + url: "url", + title: "csw" + }; + ReactDOM.render(, document.getElementById("container")); + const container = document.getElementById("container"); + expect(container).toBeTruthy(); + let editEl = document.querySelector('.glyphicon-pencil'); + let addEl = document.querySelector('.glyphicon-plus'); + expect(editEl).toBeFalsy(); + expect(addEl).toBeFalsy(); + }); }); diff --git a/web/client/components/catalog/__tests__/CatalogServiceSelector-test.jsx b/web/client/components/catalog/__tests__/CatalogServiceSelector-test.jsx new file mode 100644 index 0000000000..77360f8ced --- /dev/null +++ b/web/client/components/catalog/__tests__/CatalogServiceSelector-test.jsx @@ -0,0 +1,45 @@ +/** + * Copyright 2024, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import CatalogServiceSelector from '../CatalogServiceSelector'; + +describe('Test CatalogServiceEditor', () => { + beforeEach((done) => { + document.body.innerHTML = '
'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById("container")); + document.body.innerHTML = ''; + setTimeout(done); + }); + + it('creates the component with defaults', () => { + ReactDOM.render(, document.getElementById("container")); + expect(document.getElementById('container')).toBeTruthy(); + expect(document.querySelector('input')).toBeTruthy(); + expect(document.querySelector('.glyphicon-pencil')).toBeFalsy(); + expect(document.querySelector('.glyphicon-plus')).toBeFalsy(); + }); + it('test isValidServiceSelected', () => { + ReactDOM.render(, document.getElementById("container")); + expect(document.getElementById('container')).toBeTruthy(); + expect(document.querySelector('input')).toBeTruthy(); + expect(document.querySelector('.glyphicon-pencil')).toBeFalsy(); + expect(document.querySelector('.glyphicon-plus')).toBeTruthy(); + }); + it('test isValidServiceSelected & canEdit', () => { + ReactDOM.render(, document.getElementById("container")); + expect(document.getElementById('container')).toBeTruthy(); + expect(document.querySelector('input')).toBeTruthy(); + expect(document.querySelector('.glyphicon-pencil')).toBeTruthy(); + expect(document.querySelector('.glyphicon-plus')).toBeTruthy(); + }); +}); diff --git a/web/client/plugins/DashboardEditor.jsx b/web/client/plugins/DashboardEditor.jsx index 793180d381..fa7a752815 100644 --- a/web/client/plugins/DashboardEditor.jsx +++ b/web/client/plugins/DashboardEditor.jsx @@ -12,12 +12,12 @@ import { createSelector } from 'reselect'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { createPlugin } from '../utils/PluginsUtils'; -import { isDashboardEditing, isDashboardLoading, isDashboardAvailable } from '../selectors/dashboard'; +import { isDashboardEditing, isDashboardLoading, canEditServiceSelector } from '../selectors/dashboard'; import { dashboardSelector, dashboardsLocalizedSelector } from './widgetbuilder/commons'; import { toggleConnection } from '../actions/widgets'; -import { setEditing, setEditorAvailable, triggerShowConnections } from '../actions/dashboard'; +import { initPlugin, setEditing, setEditorAvailable, triggerShowConnections } from '../actions/dashboard'; import withDashboardExitButton from './widgetbuilder/enhancers/withDashboardExitButton'; import WidgetTypeBuilder from './widgetbuilder/WidgetTypeBuilder'; @@ -42,6 +42,7 @@ const Builder = * @prop {object} cfg.catalog **Deprecated** in favor of `cfg.services`. Can contain a catalog configuration * @prop {object} cfg.services Object with the catalogs available to select layers for maps, charts and tables. The format is the same of the `Catalog` plugin. * @prop {string} cfg.selectedService the key of service selected by default from the list of `cfg.services` + * @prop {string} cfg.servicesPermission object with permission properties to manage catalog service. Configurations are `editingAllowedRoles` & `editingAllowedGroups`. By default `editingAllowedRoles: ["ADMIN"]` * @prop {boolean} cfg.disableEmptyMap disable empty map entry from the available maps of map widget */ class DashboardEditorComponent extends React.Component { @@ -61,7 +62,8 @@ class DashboardEditorComponent extends React.Component { style: PropTypes.object, pluginCfg: PropTypes.object, catalog: PropTypes.object, - disableEmptyMap: PropTypes.bool + disableEmptyMap: PropTypes.bool, + servicesPermission: PropTypes.object }; static defaultProps = { id: "dashboard-editor", @@ -74,9 +76,13 @@ class DashboardEditorComponent extends React.Component { position: "left", onMount: () => { }, onUnmount: () => { }, - setEditing: () => { } + setEditing: () => { }, + servicesPermission: { + editingAllowedRoles: ["ALL"] + } }; componentDidMount() { + this.props.onInit({ servicesPermission: this.props.servicesPermission }); this.props.onMount(); } @@ -94,6 +100,7 @@ class DashboardEditorComponent extends React.Component { disableEmptyMap={this.props.disableEmptyMap} defaultSelectedService={defaultSelectedService} defaultServices={defaultServices} + canEditService={this.props.canEditService} enabled={this.props.editing} onClose={() => this.props.setEditing(false)} catalog={this.props.catalog} @@ -107,10 +114,11 @@ const Plugin = connect( createSelector( isDashboardEditing, isDashboardLoading, - isDashboardAvailable, - (editing, isDashboardOpened) => ({ editing, isDashboardOpened }) + canEditServiceSelector, + (editing, isDashboardOpened, canEditService) => ({ editing, isDashboardOpened, canEditService }) ), { setEditing, + onInit: initPlugin, onMount: () => setEditorAvailable(true), onUnmount: () => setEditorAvailable(false) } diff --git a/web/client/plugins/MetadataExplorer.jsx b/web/client/plugins/MetadataExplorer.jsx index 58e51d870b..4e8abdbd9f 100644 --- a/web/client/plugins/MetadataExplorer.jsx +++ b/web/client/plugins/MetadataExplorer.jsx @@ -39,7 +39,8 @@ import { toggleAdvancedSettings, toggleTemplate, toggleThumbnail, - setNewServiceStatus + setNewServiceStatus, + initPlugin } from '../actions/catalog'; import { setControlProperty, toggleControl } from '../actions/controls'; import { changeLayerProperties } from '../actions/layers'; @@ -74,7 +75,8 @@ import { getSupportedFormatsSelector, getSupportedGFIFormatsSelector, getNewServiceStatusSelector, - showFormatErrorSelector + showFormatErrorSelector, + canEditServiceSelector } from '../selectors/catalog'; import { layersSelector } from '../selectors/layers'; import { currentLocaleSelector, currentMessagesSelector } from '../selectors/locale'; @@ -121,7 +123,8 @@ const metadataExplorerSelector = createStructuredSelector({ formatsLoading: formatsLoadingSelector, formatOptions: getSupportedFormatsSelector, infoFormatOptions: getSupportedGFIFormatsSelector, - isNewServiceAdded: getNewServiceStatusSelector + isNewServiceAdded: getNewServiceStatusSelector, + canEdit: canEditServiceSelector }); @@ -174,7 +177,10 @@ class MetadataExplorerComponent extends React.Component { // side panel properties width: PropTypes.number, dockStyle: PropTypes.object, - group: PropTypes.string + group: PropTypes.string, + onInitPlugin: PropTypes.func, + editingAllowedRoles: PropTypes.array, + editingAllowedGroups: PropTypes.array }; static defaultProps = { @@ -191,6 +197,7 @@ class MetadataExplorerComponent extends React.Component { }, panelClassName: "catalog-panel", closeCatalog: () => {}, + onInitPlugin: () => {}, closeGlyph: "1-close", zoomToLayer: true, @@ -205,9 +212,17 @@ class MetadataExplorerComponent extends React.Component { dockStyle: {}, group: null, services: {}, - servicesWithBackgrounds: {} + servicesWithBackgrounds: {}, + editingAllowedRoles: ["ALL"] }; + componentDidMount() { + this.props.onInitPlugin({ + editingAllowedRoles: this.props.editingAllowedRoles, + editingAllowedGroups: this.props.editingAllowedGroups + }); + } + componentWillUnmount() { this.props.closeCatalog(); } @@ -275,7 +290,8 @@ const MetadataExplorerPlugin = connect(metadataExplorerSelector, { onToggle: toggleControl.bind(null, 'backgroundSelector', null), onLayerChange: setControlProperty.bind(null, 'backgroundSelector'), onStartChange: setControlProperty.bind(null, 'backgroundSelector', 'start'), - setNewServiceStatus + setNewServiceStatus, + onInitPlugin: initPlugin })(MetadataExplorerComponent); /** diff --git a/web/client/plugins/__tests__/DashboardEditor-test.jsx b/web/client/plugins/__tests__/DashboardEditor-test.jsx new file mode 100644 index 0000000000..575fb2247f --- /dev/null +++ b/web/client/plugins/__tests__/DashboardEditor-test.jsx @@ -0,0 +1,40 @@ +/* + * Copyright 2024, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ +import expect from 'expect'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import DashboardEditorPlugin from '../DashboardEditor'; +import { getPluginForTest } from './pluginsTestUtils'; + +describe('DashboardEditorPlugin Plugin', () => { + beforeEach(() => { + document.body.innerHTML = '
'; + }); + + afterEach(() => { + ReactDOM.unmountComponentAtNode(document.getElementById("container")); + document.body.innerHTML = ''; + }); + it('test DashboardEditorPlugin plugin on mount', () => { + const {Plugin, actions} = getPluginForTest(DashboardEditorPlugin, {}); + ReactDOM.render(, document.getElementById("container")); + + expect(actions.length).toBeTruthy(); + const actionTypes = actions.map(a => a.type); + expect(actionTypes.includes("DASHBOARD:INIT_PLUGIN")).toBeTruthy(); + expect(actionTypes.includes("DASHBOARD:SET_AVAILABLE")).toBeTruthy(); + }); + it('test DashboardEditorPlugin plugin on unmount', () => { + const {Plugin, actions} = getPluginForTest(DashboardEditorPlugin, {}); + ReactDOM.render(, document.getElementById("container")); + + ReactDOM.render(
, document.getElementById("container")); + expect(actions).toBeTruthy(); + expect(actions.find(a => a.type === "DASHBOARD:SET_AVAILABLE")).toBeTruthy(); + }); +}); diff --git a/web/client/plugins/__tests__/MetadataExplorer-test.jsx b/web/client/plugins/__tests__/MetadataExplorer-test.jsx index 235103cd24..96096bf314 100644 --- a/web/client/plugins/__tests__/MetadataExplorer-test.jsx +++ b/web/client/plugins/__tests__/MetadataExplorer-test.jsx @@ -26,17 +26,19 @@ describe('MetadataExplorer Plugin', () => { ReactDOM.render(, document.getElementById("container")); expect(document.getElementById('catalog-root')).toBeTruthy(); }); + it('test MetadataExplorerPlugin plugin on mount', () => { + const {Plugin, actions} = getPluginForTest(MetadataExplorerPlugin, {}); + ReactDOM.render(, document.getElementById("container")); + + expect(actions.length).toBeTruthy(); + expect(actions.map(a => a.type).includes("CATALOG:INIT_PLUGIN")).toBeTruthy(); + }); it('test MetadataExplorerPlugin plugin on unmount', () => { const {Plugin, actions} = getPluginForTest(MetadataExplorerPlugin, {}); ReactDOM.render(, document.getElementById("container")); ReactDOM.render(
, document.getElementById("container")); - expect(actions.length).toBe(4); - expect(actions[0].type).toBe("CATALOG:CATALOG_CLOSE"); - expect(actions[1].type).toBe("SET_CONTROL_PROPERTIES"); - expect(actions[1].control).toBe("metadataexplorer"); - expect(actions[2].type).toBe("CATALOG:CHANGE_CATALOG_MODE"); - expect(actions[2].mode).toBe("view"); - expect(actions[3].type).toBe("CATALOG:RESET_CATALOG"); + expect(actions.length).toBeTruthy(); + expect(actions.map(a => a.type).includes("CATALOG:CATALOG_CLOSE")).toBeTruthy(); }); }); diff --git a/web/client/plugins/widgetbuilder/ChartLayerSelector.jsx b/web/client/plugins/widgetbuilder/ChartLayerSelector.jsx index 4dc2f89c1b..daaa011b77 100644 --- a/web/client/plugins/widgetbuilder/ChartLayerSelector.jsx +++ b/web/client/plugins/widgetbuilder/ChartLayerSelector.jsx @@ -56,7 +56,8 @@ export default connect((state) =>({ onItemClick, showLayers, toggleLayerSelector, - selectedCatalog + selectedCatalog, + canEditService }) => { const _canProceed = showLayers ? canProceed && !isEmpty(layer) : canProceed && selected && layer && castArray(selected).length === castArray(layer).length; const getProcessArguments = () => { @@ -111,6 +112,7 @@ export default connect((state) =>({ } + canEditService={canEditService} /> ); }); diff --git a/web/client/plugins/widgetbuilder/LayerSelector.jsx b/web/client/plugins/widgetbuilder/LayerSelector.jsx index 63fde01a1e..8c258ab1c0 100644 --- a/web/client/plugins/widgetbuilder/LayerSelector.jsx +++ b/web/client/plugins/widgetbuilder/LayerSelector.jsx @@ -31,7 +31,7 @@ const Catalog = compose( * @prop {function} [layerValidationStream] */ export default ({ onClose = () => { }, setSelected = () => { }, onLayerChoice = () => { }, stepButtons, selected, error, canProceed, layer, catalog, defaultServices, - onChangeSelectedService, defaultSelectedService, onChangeCatalogMode, dashboardServices, dashboardSelectedService} = {}) => + onChangeSelectedService, defaultSelectedService, onChangeCatalogMode, dashboardServices, dashboardSelectedService, canEditService} = {}) => ( @@ -47,5 +47,6 @@ export default ({ onClose = () => { }, setSelected = () => { }, onLayerChoice = onChangeSelectedService(service, dashboardServices || defaultServices)} services={ dashboardServices || defaultServices} selected={selected} catalog={catalog} onRecordSelected={r => setSelected(r)} /> + onChangeSelectedService={(service) => onChangeSelectedService(service, dashboardServices || defaultServices)} services={ dashboardServices || defaultServices} selected={selected} catalog={catalog} onRecordSelected={r => setSelected(r)} + canEditService={canEditService}/> ); diff --git a/web/client/plugins/widgetbuilder/MapLayerSelector.jsx b/web/client/plugins/widgetbuilder/MapLayerSelector.jsx index bd76b15655..4e8e7e037c 100644 --- a/web/client/plugins/widgetbuilder/MapLayerSelector.jsx +++ b/web/client/plugins/widgetbuilder/MapLayerSelector.jsx @@ -30,7 +30,7 @@ const Catalog = compose( */ export default ({ onClose = () => { }, setSelected = () => { }, onLayerChoice = () => { }, toggleLayerSelector = () => {}, selected, canProceed, layer, catalog, onChangeSelectedService, defaultServices, - defaultSelectedService, onChangeCatalogMode, dashboardServices, dashboardSelectedService} = {}) => + defaultSelectedService, onChangeCatalogMode, dashboardServices, dashboardSelectedService, canEditService} = {}) => ( @@ -62,5 +62,6 @@ export default ({ onClose = () => { }, setSelected = () => { }, onLayerChoice = onChangeSelectedService={(service) => onChangeSelectedService(service, dashboardServices || defaultServices)} services={ dashboardServices || defaultServices} catalog={catalog} selected={selected} - onRecordSelected={r => setSelected(r)} /> + onRecordSelected={r => setSelected(r)} + canEditService={canEditService} /> ); diff --git a/web/client/reducers/__tests__/catalog-test.js b/web/client/reducers/__tests__/catalog-test.js index ac0da650cc..1cb1459de4 100644 --- a/web/client/reducers/__tests__/catalog-test.js +++ b/web/client/reducers/__tests__/catalog-test.js @@ -55,7 +55,8 @@ import { changeServiceFormat, setLoading, formatsLoading, - setSupportedFormats + setSupportedFormats, + initPlugin } from '../../actions/catalog'; import { MAP_CONFIG_LOADED } from '../../actions/config'; @@ -344,4 +345,23 @@ describe('Test the catalog reducer', () => { expect(state.newService.formatUrlUsed).toBe(url); expect(state.newService.supportedFormats).toEqual(formats); }); + it('INIT_PLUGIN ', () => { + const option = { + editingAllowedRoles: ["ADMIN"], + editingAllowedGroups: [] + }; + let state = catalog({ + newService: {} + }, initPlugin(option)); + expect(state.editingAllowedRoles).toEqual(option.editingAllowedRoles); + expect(state.editingAllowedGroups).toEqual(option.editingAllowedGroups); + + state = catalog({ + newService: {}, + editingAllowedRoles: ["ADMIN"], + editingAllowedGroups: [] + }, initPlugin()); + expect(state.editingAllowedRoles).toEqual(option.editingAllowedRoles); + expect(state.editingAllowedGroups).toEqual(option.editingAllowedGroups); + }); }); diff --git a/web/client/reducers/__tests__/dashboard-test.js b/web/client/reducers/__tests__/dashboard-test.js index b5679d1d36..1333adffd4 100644 --- a/web/client/reducers/__tests__/dashboard-test.js +++ b/web/client/reducers/__tests__/dashboard-test.js @@ -22,7 +22,8 @@ import { setDashboardCatalogMode, setDashboardServiceSaveLoading, dashboardDeleteService, - updateDashboardService + updateDashboardService, + initPlugin } from '../../actions/dashboard'; import { insertWidget, updateWidget, deleteWidget } from '../../actions/widgets'; @@ -146,5 +147,14 @@ describe('Test the dashboard reducer', () => { expect(state.selectedService).toBe('test'); }); + it('dashboard onInitPlugin', () => { + let action = initPlugin({option: "test"}); + let state = dashboard({}, action); + expect(state.option).toBeTruthy(); + expect(state.option).toBe("test"); + action = initPlugin(); + state = dashboard({serviceStarted: false}, action); + expect(state).toEqual({serviceStarted: false}); + }); }); diff --git a/web/client/reducers/catalog.js b/web/client/reducers/catalog.js index ccb842e725..6b358854ab 100644 --- a/web/client/reducers/catalog.js +++ b/web/client/reducers/catalog.js @@ -32,7 +32,8 @@ import { FORMAT_OPTIONS_LOADING, SHOW_FORMAT_ERROR, SET_FORMAT_OPTIONS, - NEW_SERVICE_STATUS + NEW_SERVICE_STATUS, + INIT_PLUGIN } from '../actions/catalog'; import { MAP_CONFIG_LOADED } from '../actions/config'; @@ -68,6 +69,12 @@ function catalog(state = { newService: {} }, action) { switch (action.type) { + case INIT_PLUGIN: + return { + ...state, + editingAllowedRoles: action?.options?.editingAllowedRoles || state.editingAllowedRoles, + editingAllowedGroups: action?.options?.editingAllowedGroups || state.editingAllowedGroups + }; case SAVING_SERVICE: return { ...state, diff --git a/web/client/reducers/dashboard.js b/web/client/reducers/dashboard.js index bb49ea01f6..5e3cf8efb6 100644 --- a/web/client/reducers/dashboard.js +++ b/web/client/reducers/dashboard.js @@ -23,7 +23,8 @@ import { DASHBOARD_ADD_NEW_SERVICE, DASHBOARD_CATALOG_MODE, DASHBOARD_DELETE_SERVICE, - DASHBOARD_SAVE_SERVICE_LOADING + DASHBOARD_SAVE_SERVICE_LOADING, + INIT_PLUGIN } from '../actions/dashboard'; import { INSERT, UPDATE, DELETE } from '../actions/widgets'; @@ -36,6 +37,9 @@ function dashboard(state = { saveServiceLoading: false }, action) { switch (action.type) { + case INIT_PLUGIN: { + return { ...state, ...action?.options }; + } case SET_EDITOR_AVAILABLE: { return set("editor.available", action.available, state); } diff --git a/web/client/selectors/__tests__/catalog-test.js b/web/client/selectors/__tests__/catalog-test.js index d341a5f3d8..7ed69533e9 100644 --- a/web/client/selectors/__tests__/catalog-test.js +++ b/web/client/selectors/__tests__/catalog-test.js @@ -33,7 +33,8 @@ import { formatsLoadingSelector, getSupportedFormatsSelector, getSupportedGFIFormatsSelector, - getFormatUrlUsedSelector + getFormatUrlUsedSelector, + canEditServiceSelector } from '../catalog'; import { set } from '../../utils/ImmutableUtils'; @@ -349,4 +350,102 @@ describe('Test catalog selectors', () => { }); expect(toolState).toBe(true); }); + describe("canEditServiceSelector", () => { + it('test ADMIN role ', () => { + const canEdit = canEditServiceSelector({ + catalog: { + editingAllowedRoles: ['ADMIN'], + editingAllowedGroups: [] + }, + security: { + user: { + role: "ADMIN" + } + } + }); + expect(canEdit).toBe(true); + }); + it("test ALL user role", () => { + const canEdit = canEditServiceSelector({ + catalog: { + editingAllowedRoles: ['ALL'], + editingAllowedGroups: [] + }, + security: { + user: { + role: "ADMIN" + } + } + }); + expect(canEdit).toBe(true); + }); + it("test custom user role", () => { + const canEdit = canEditServiceSelector({ + catalog: { + editingAllowedRoles: ['ROLE1'], + editingAllowedGroups: [] + }, + security: { + user: { + role: "ROLE1" + } + } + }); + expect(canEdit).toBe(true); + }); + it("test user role not matching", () => { + const canEdit = canEditServiceSelector({ + catalog: { + editingAllowedRoles: ['ADMIN'], + editingAllowedGroups: [] + }, + security: { + user: { + role: "ROLE1" + } + } + }); + expect(canEdit).toBe(false); + }); + it("test group matching", () => { + const canEdit = canEditServiceSelector({ + catalog: { + editingAllowedRoles: [], + editingAllowedGroups: ['group1'] + }, + security: { + user: { + role: "ROLE1", + groups: { + group: { + enabled: true, + groupName: "group1" + } + } + } + } + }); + expect(canEdit).toBe(true); + }); + it("test group not matching", () => { + const canEdit = canEditServiceSelector({ + catalog: { + editingAllowedRoles: [], + editingAllowedGroups: ['group1'] + }, + security: { + user: { + role: "ROLE1", + groups: { + group: { + enabled: true, + groupName: "geo" + } + } + } + } + }); + expect(canEdit).toBe(false); + }); + }); }); diff --git a/web/client/selectors/__tests__/dashboard-test.js b/web/client/selectors/__tests__/dashboard-test.js index 8e9048c1d3..def125eed8 100644 --- a/web/client/selectors/__tests__/dashboard-test.js +++ b/web/client/selectors/__tests__/dashboard-test.js @@ -25,7 +25,8 @@ import { dashboardSaveServiceSelector, dashboardResourceInfoSelector, dashbaordInfoDetailsUriFromIdSelector, - dashboardInfoDetailsSettingsFromIdSelector + dashboardInfoDetailsSettingsFromIdSelector, + canEditServiceSelector } from '../dashboard'; describe('dashboard selectors', () => { @@ -149,4 +150,114 @@ describe('dashboard selectors', () => { } }})).toBe("detailsSettings"); }); + describe("canEditServiceSelector", () => { + it('test ADMIN role ', () => { + const canEdit = canEditServiceSelector({ + dashboard: { + servicesPermission: { + editingAllowedRoles: ['ADMIN'], + editingAllowedGroups: [] + } + }, + security: { + user: { + role: "ADMIN" + } + } + }); + expect(canEdit).toBe(true); + }); + it("test ALL user role", () => { + const canEdit = canEditServiceSelector({ + dashboard: { + servicesPermission: { + editingAllowedRoles: ['ALL'], + editingAllowedGroups: [] + } + }, + security: { + user: { + role: "ADMIN" + } + } + }); + expect(canEdit).toBe(true); + }); + it("test custom user role", () => { + const canEdit = canEditServiceSelector({ + dashboard: { + servicesPermission: { + editingAllowedRoles: ['ROLE1'], + editingAllowedGroups: [] + } + }, + security: { + user: { + role: "ROLE1" + } + } + }); + expect(canEdit).toBe(true); + }); + it("test user role not matching", () => { + const canEdit = canEditServiceSelector({ + dashboard: { + servicesPermission: { + editingAllowedRoles: ['ADMIN'], + editingAllowedGroups: [] + } + }, + security: { + user: { + role: "ROLE1" + } + } + }); + expect(canEdit).toBe(false); + }); + it("test group matching", () => { + const canEdit = canEditServiceSelector({ + dashboard: { + servicesPermission: { + editingAllowedRoles: [], + editingAllowedGroups: ['group1'] + } + }, + security: { + user: { + role: "ROLE1", + groups: { + group: { + enabled: true, + groupName: "group1" + } + } + } + } + }); + expect(canEdit).toBe(true); + }); + it("test group not matching", () => { + const canEdit = canEditServiceSelector({ + dashboard: { + servicesPermission: { + editingAllowedRoles: [], + editingAllowedGroups: ['group1'] + } + }, + security: { + user: { + role: "ROLE1", + groups: { + group: { + enabled: true, + groupName: "geo" + } + } + } + } + }); + expect(canEdit).toBe(false); + }); + }); }); diff --git a/web/client/selectors/catalog.js b/web/client/selectors/catalog.js index 4f38c24f93..b3656eba9d 100644 --- a/web/client/selectors/catalog.js +++ b/web/client/selectors/catalog.js @@ -11,6 +11,7 @@ import { get } from 'lodash'; import { projectionSelector } from './map'; import { getDefaultSupportedGetFeatureInfoFormats } from '../utils/WMSUtils'; +import { isUserAllowedSelectorCreator } from './security'; export const staticServicesSelector = (state) => get(state, "catalog.default.staticServices"); export const servicesSelector = (state) => get(state, "catalog.services"); @@ -65,3 +66,19 @@ export const getSupportedGFIFormatsSelector = (state) => get(state, "catalog.new export const getFormatUrlUsedSelector = (state) => get(state, "catalog.newService.formatUrlUsed", ''); export const getNewServiceStatusSelector = (state) => get(state, "catalog.isNewServiceAdded", false); export const showFormatErrorSelector = (state) => get(state, "catalog.showFormatError", false); +export const editingAllowedRolesSelector = (state) => get(state, "catalog.editingAllowedRoles", []); +export const editingAllowedGroupsSelector = (state) => get(state, "catalog.editingAllowedGroups", []); +/** + * selects canEdit status of styleeditor service from state + * @memberof selectors.styleeditor + * @param {object} state the state + * @return {bool} + */ +export const canEditServiceSelector = (state) => { + const allowedRoles = editingAllowedRolesSelector(state); + const allowedGroups = editingAllowedGroupsSelector(state); + return isUserAllowedSelectorCreator({ + allowedRoles, + allowedGroups + })(state); +}; diff --git a/web/client/selectors/dashboard.js b/web/client/selectors/dashboard.js index 5f565ce8be..1ba41b10c6 100644 --- a/web/client/selectors/dashboard.js +++ b/web/client/selectors/dashboard.js @@ -8,6 +8,7 @@ import { createSelector } from 'reselect'; import {get} from 'lodash'; import { pathnameSelector } from './router'; +import { isUserAllowedSelectorCreator } from './security'; export const getDashboardId = state => state?.dashboard?.resource?.id; export const isDashboardAvailable = state => state && state.dashboard && state.dashboard.editor && state.dashboard.editor.available; @@ -31,4 +32,14 @@ export const dashboardSaveServiceSelector = state => state.dashboard?.saveServi export const dashboardResourceInfoSelector = state => get(state, "dashboard.resource"); export const dashbaordInfoDetailsUriFromIdSelector = state => state?.dashboard?.resource?.attributes?.details; export const dashboardInfoDetailsSettingsFromIdSelector = state => get(dashboardResource(state), "attributes.detailsSettings"); +export const editingAllowedRolesSelector = state => get(state, "dashboard.servicesPermission.editingAllowedRoles", []); +export const editingAllowedGroupsSelector = state => get(state, "dashboard.servicesPermission.editingAllowedGroups", []); +export const canEditServiceSelector = state => { + const allowedRoles = editingAllowedRolesSelector(state); + const allowedGroups = editingAllowedGroupsSelector(state); + return isUserAllowedSelectorCreator({ + allowedRoles, + allowedGroups + })(state); +};