From 50b20a0e5adee63bc33ccc9e95195e664b090cc3 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Sat, 27 Jan 2018 17:26:40 +0100 Subject: [PATCH] dashfolders: use react component for dashboard permissions Switch out the angular component for the new react component on the dashboard permissions editview on the settings page. --- .../ManageDashboards/FolderPermissions.tsx | 9 +- public/app/core/angular_wrappers.ts | 4 +- .../Permissions/DashboardPermissions.tsx | 29 +++ .../Permissions/Permissions.jest.tsx | 73 ++++++ .../components/Permissions/Permissions.tsx | 4 +- .../Permissions/PermissionsListItem.tsx | 3 +- public/app/core/core.ts | 1 - public/app/core/directives/dash_edit_link.js | 150 ------------- public/app/features/dashboard/acl/acl.html | 132 ----------- public/app/features/dashboard/acl/acl.ts | 207 ------------------ .../features/dashboard/acl/specs/acl.jest.ts | 169 -------------- public/app/features/dashboard/all.ts | 3 - .../partials/folder_permissions.html | 7 +- .../features/dashboard/settings/settings.html | 8 +- .../PermissionsStore/PermissionsStore.jest.ts | 87 +++++++- .../PermissionsStore/PermissionsStore.ts | 9 +- 16 files changed, 200 insertions(+), 695 deletions(-) create mode 100644 public/app/core/components/Permissions/DashboardPermissions.tsx create mode 100644 public/app/core/components/Permissions/Permissions.jest.tsx delete mode 100644 public/app/core/directives/dash_edit_link.js delete mode 100644 public/app/features/dashboard/acl/acl.html delete mode 100644 public/app/features/dashboard/acl/acl.ts delete mode 100644 public/app/features/dashboard/acl/specs/acl.jest.ts diff --git a/public/app/containers/ManageDashboards/FolderPermissions.tsx b/public/app/containers/ManageDashboards/FolderPermissions.tsx index 473ac45beaf..80f2c646f28 100644 --- a/public/app/containers/ManageDashboards/FolderPermissions.tsx +++ b/public/app/containers/ManageDashboards/FolderPermissions.tsx @@ -21,7 +21,7 @@ export class FolderPermissions extends Component { } render() { - const { nav, folder, permissions } = this.props; + const { nav, folder, permissions, backendSrv } = this.props; if (!folder.folder || !nav.main) { return

Loading

; @@ -34,12 +34,7 @@ export class FolderPermissions extends Component {

Folder Permissions

- +
); diff --git a/public/app/core/angular_wrappers.ts b/public/app/core/angular_wrappers.ts index 0521d85cb30..da3c48a94dd 100644 --- a/public/app/core/angular_wrappers.ts +++ b/public/app/core/angular_wrappers.ts @@ -6,7 +6,7 @@ import LoginBackground from './components/Login/LoginBackground'; import { SearchResult } from './components/search/SearchResult'; import { TagFilter } from './components/TagFilter/TagFilter'; import UserPicker from './components/Picker/UserPicker'; -import Permissions from './components/Permissions/Permissions'; +import DashboardPermissions from './components/Permissions/DashboardPermissions'; export function registerAngularDirectives() { react2AngularDirective('passwordStrength', PasswordStrength, ['password']); @@ -20,5 +20,5 @@ export function registerAngularDirectives() { ['tagOptions', { watchDepth: 'reference' }], ]); react2AngularDirective('selectUserPicker', UserPicker, ['backendSrv', 'handlePicked']); - react2AngularDirective('permissions', Permissions, ['error', 'aclTypes', 'typeChanged', 'backendSrv', 'dashboardId']); + react2AngularDirective('dashboardPermissions', DashboardPermissions, ['backendSrv', 'dashboardId']); } diff --git a/public/app/core/components/Permissions/DashboardPermissions.tsx b/public/app/core/components/Permissions/DashboardPermissions.tsx new file mode 100644 index 00000000000..c004507a2d1 --- /dev/null +++ b/public/app/core/components/Permissions/DashboardPermissions.tsx @@ -0,0 +1,29 @@ +import React, { Component } from 'react'; +import { observer } from 'mobx-react'; +import { store } from 'app/stores/store'; +import Permissions from 'app/core/components/Permissions/Permissions'; + +export interface IProps { + dashboardId: number; + backendSrv: any; +} + +@observer +class DashboardPermissions extends Component { + permissions: any; + + constructor(props) { + super(props); + this.permissions = store.permissions; + } + + render() { + const { dashboardId, backendSrv } = this.props; + + return ( + + ); + } +} + +export default DashboardPermissions; diff --git a/public/app/core/components/Permissions/Permissions.jest.tsx b/public/app/core/components/Permissions/Permissions.jest.tsx new file mode 100644 index 00000000000..0a608ee5842 --- /dev/null +++ b/public/app/core/components/Permissions/Permissions.jest.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import Permissions from './Permissions'; +import { RootStore } from 'app/stores/RootStore/RootStore'; +import { backendSrv } from 'test/mocks/common'; +import { shallow } from 'enzyme'; + +describe('Permissions', () => { + let wrapper; + + beforeAll(() => { + backendSrv.get.mockReturnValue( + Promise.resolve([ + { id: 2, dashboardId: 1, role: 'Viewer', permission: 1, permissionName: 'View' }, + { id: 3, dashboardId: 1, role: 'Editor', permission: 1, permissionName: 'Edit' }, + { + id: 4, + dashboardId: 1, + userId: 2, + userLogin: 'danlimerick', + userEmail: 'dan.limerick@gmail.com', + permission: 4, + permissionName: 'Admin', + }, + ]) + ); + + backendSrv.post = jest.fn(); + + const store = RootStore.create( + {}, + { + backendSrv: backendSrv, + } + ); + + wrapper = shallow(); + return wrapper.instance().loadStore(1, true); + }); + + describe('when permission for a user is added', () => { + it('should save permission to db', () => { + const userItem = { + id: 2, + login: 'user2', + }; + + wrapper + .instance() + .userPicked(userItem) + .then(() => { + expect(backendSrv.post.mock.calls.length).toBe(1); + expect(backendSrv.post.mock.calls[0][0]).toBe('/api/dashboards/id/1/acl'); + }); + }); + }); + + describe('when permission for team is added', () => { + it('should save permission to db', () => { + const teamItem = { + id: 2, + name: 'ug1', + }; + + wrapper + .instance() + .teamPicked(teamItem) + .then(() => { + expect(backendSrv.post.mock.calls.length).toBe(1); + expect(backendSrv.post.mock.calls[0][0]).toBe('/api/dashboards/id/1/acl'); + }); + }); + }); +}); diff --git a/public/app/core/components/Permissions/Permissions.tsx b/public/app/core/components/Permissions/Permissions.tsx index 25fefcd05f7..c5b8682f1ee 100644 --- a/public/app/core/components/Permissions/Permissions.tsx +++ b/public/app/core/components/Permissions/Permissions.tsx @@ -77,12 +77,12 @@ class Permissions extends Component { userPicked(user: User) { const { permissions } = this.props; - permissions.addStoreItem({ userId: user.id, userLogin: user.login, permission: 1 }); + return permissions.addStoreItem({ userId: user.id, userLogin: user.login, permission: 1 }); } teamPicked(team: Team) { const { permissions } = this.props; - permissions.addStoreItem({ teamId: team.id, team: team.name, permission: 1 }); + return permissions.addStoreItem({ teamId: team.id, team: team.name, permission: 1 }); } render() { diff --git a/public/app/core/components/Permissions/PermissionsListItem.tsx b/public/app/core/components/Permissions/PermissionsListItem.tsx index 0de296b8d8a..b29c9788ff0 100644 --- a/public/app/core/components/Permissions/PermissionsListItem.tsx +++ b/public/app/core/components/Permissions/PermissionsListItem.tsx @@ -23,7 +23,6 @@ export default observer(({ item, removeItem, permissionChanged, itemIndex }) => return ( - {/* style="width: 100%;" */} @@ -55,7 +54,7 @@ export default observer(({ item, removeItem, permissionChanged, itemIndex }) => {!item.inherited ? ( - + ) : null} diff --git a/public/app/core/core.ts b/public/app/core/core.ts index bf041d608eb..353d8762a9a 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -1,5 +1,4 @@ import './directives/dash_class'; -import './directives/dash_edit_link'; import './directives/dropdown_typeahead'; import './directives/metric_segment'; import './directives/misc'; diff --git a/public/app/core/directives/dash_edit_link.js b/public/app/core/directives/dash_edit_link.js deleted file mode 100644 index f0dc6f59a6a..00000000000 --- a/public/app/core/directives/dash_edit_link.js +++ /dev/null @@ -1,150 +0,0 @@ -define([ - 'jquery', - 'angular', - '../core_module', - 'lodash', -], -function ($, angular, coreModule, _) { - 'use strict'; - - var editViewMap = { - 'settings': { src: 'public/app/features/dashboard/partials/settings.html'}, - 'annotations': { src: 'public/app/features/annotations/partials/editor.html'}, - 'templating': { src: 'public/app/features/templating/partials/editor.html'}, - 'history': { html: ''}, - 'timepicker': { src: 'public/app/features/dashboard/timepicker/dropdown.html' }, - 'import': { html: '', isModal: true }, - 'permissions': { html: '', isModal: true }, - 'new-folder': { - isModal: true, - html: '', - modalClass: 'modal--narrow' - } - }; - - coreModule.default.directive('dashEditorView', function($compile, $location, $rootScope) { - return { - restrict: 'A', - link: function(scope, elem) { - var editorScope; - var modalScope; - var lastEditView; - - function hideEditorPane(hideToShowOtherView) { - if (editorScope) { - editorScope.dismiss(hideToShowOtherView); - } - } - - function showEditorPane(evt, options) { - if (options.editview) { - _.defaults(options, editViewMap[options.editview]); - } - - if (lastEditView && lastEditView === options.editview) { - hideEditorPane(false); - return; - } - - hideEditorPane(true); - - lastEditView = options.editview; - editorScope = options.scope ? options.scope.$new() : scope.$new(); - - editorScope.dismiss = function(hideToShowOtherView) { - if (modalScope) { - modalScope.dismiss(); - modalScope = null; - } - - editorScope.$destroy(); - lastEditView = null; - editorScope = null; - elem.removeClass('dash-edit-view--open'); - - if (!hideToShowOtherView) { - setTimeout(function() { - elem.empty(); - }, 250); - } - - if (options.editview) { - var urlParams = $location.search(); - if (options.editview === urlParams.editview) { - delete urlParams.editview; - - // even though we always are in apply phase here - // some angular bug is causing location search updates to - // not happen always so this is a hack fix or that - setTimeout(function() { - $rootScope.$apply(function() { - $location.search(urlParams); - }); - }); - } - } - }; - - if (options.isModal) { - modalScope = $rootScope.$new(); - modalScope.$on("$destroy", function() { - editorScope.dismiss(); - }); - - $rootScope.appEvent('show-modal', { - templateHtml: options.html, - scope: modalScope, - backdrop: 'static', - modalClass: options.modalClass, - }); - - return; - } - - var view; - if (options.src) { - view = angular.element(document.createElement('div')); - view.html('
'); - } else { - view = angular.element(document.createElement('div')); - view.addClass('tabbed-view'); - view.html(options.html); - } - - $compile(view)(editorScope); - - setTimeout(function() { - elem.empty(); - elem.append(view); - setTimeout(function() { - elem.addClass('dash-edit-view--open'); - }, 10); - }, 10); - } - - scope.$watch("ctrl.dashboardViewState.state.editview", function(newValue, oldValue) { - if (newValue) { - showEditorPane(null, {editview: newValue}); - } else if (oldValue) { - if (lastEditView === oldValue) { - hideEditorPane(); - } - } - }); - - scope.$on("$destroy", hideEditorPane); - - scope.onAppEvent('hide-dash-editor', function() { - hideEditorPane(false); - }); - - scope.onAppEvent('show-dash-editor', showEditorPane); - - scope.onAppEvent('panel-fullscreen-enter', function() { - scope.appEvent('hide-dash-editor'); - }); - } - }; - }); -}); - diff --git a/public/app/features/dashboard/acl/acl.html b/public/app/features/dashboard/acl/acl.html deleted file mode 100644 index 7631680b864..00000000000 --- a/public/app/features/dashboard/acl/acl.html +++ /dev/null @@ -1,132 +0,0 @@ - - -
- - - - - - - - - - - -
- - - - Inherited from folder - Can -
- -
-
- - - -
- No permissions are set. Will only be accessible by admins. -
- -
-
-
Add Permission For
-
-
-
- -
-
-
- -
-
- -
-
-
-
- - - {{ctrl.error}} - -
-
- -
- -
-
- -
-
-
What are Permissions?
-

An Access Control List (ACL) model is used for to limit access to Dashboard Folders. A user or a Team can be assigned permissions for a folder or for a single dashboard.

-

The permissions that can be assigned for a folder/dashboard are:

-

View, Edit and Admin.

- Checkout the Dashboard Folders documentation for more information. -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/app/features/dashboard/acl/acl.ts b/public/app/features/dashboard/acl/acl.ts deleted file mode 100644 index b9a3337432e..00000000000 --- a/public/app/features/dashboard/acl/acl.ts +++ /dev/null @@ -1,207 +0,0 @@ -import coreModule from 'app/core/core_module'; -import _ from 'lodash'; - -export class AclCtrl { - dashboard: any; - meta: any; - - items: DashboardAcl[]; - permissionOptions = [{ value: 1, text: 'View' }, { value: 2, text: 'Edit' }, { value: 4, text: 'Admin' }]; - aclTypes = [ - { value: 'Group', text: 'Team' }, - { value: 'User', text: 'User' }, - { value: 'Viewer', text: 'Everyone With Viewer Role' }, - { value: 'Editor', text: 'Everyone With Editor Role' }, - ]; - - newType: string; - canUpdate: boolean; - error: string; - - readonly duplicateError = 'This permission exists already.'; - - /** @ngInject */ - constructor(private backendSrv, private $sce, private $scope) { - this.items = []; - this.resetNewType(); - this.getAcl(this.dashboard.id); - } - - resetNewType() { - this.newType = 'Group'; - } - - getAcl(dashboardId: number) { - return this.backendSrv.get(`/api/dashboards/id/${dashboardId}/acl`).then(result => { - this.items = _.map(result, this.prepareViewModel.bind(this)); - this.sortItems(); - }); - } - - sortItems() { - this.items = _.orderBy(this.items, ['sortRank', 'sortName'], ['desc', 'asc']); - } - - prepareViewModel(item: DashboardAcl): DashboardAcl { - item.inherited = - !this.meta.isFolder && this.dashboard.id !== item.dashboardId; - item.sortRank = 0; - - if (item.userId > 0) { - item.icon = 'fa fa-fw fa-user'; - item.nameHtml = this.$sce.trustAsHtml(item.userLogin); - item.sortName = item.userLogin; - item.sortRank = 10; - } else if (item.teamId > 0) { - item.icon = 'fa fa-fw fa-users'; - item.nameHtml = this.$sce.trustAsHtml(item.team); - item.sortName = item.team; - item.sortRank = 20; - } else if (item.role) { - item.icon = 'fa fa-fw fa-street-view'; - item.nameHtml = this.$sce.trustAsHtml(`Everyone with ${item.role} Role`); - item.sortName = item.role; - item.sortRank = 30; - if (item.role === 'Viewer') { - item.sortRank += 1; - } - } - - if (item.inherited) { - item.sortRank += 100; - } - - return item; - } - - update() { - var updated = []; - for (let item of this.items) { - if (item.inherited) { - continue; - } - updated.push({ - id: item.id, - userId: item.userId, - teamId: item.teamId, - role: item.role, - permission: item.permission, - }); - } - - return this.backendSrv - .post(`/api/dashboards/id/${this.dashboard.id}/acl`, { - items: updated, - }) - .then(() => { - this.canUpdate = false; - }); - } - - typeChanged() { - if (this.newType === 'Viewer' || this.newType === 'Editor') { - this.addNewItem({ permission: 1, role: this.newType }); - this.canUpdate = true; - this.resetNewType(); - } - } - - permissionChanged() { - this.canUpdate = true; - } - - addNewItem(item) { - if (!this.isValid(item)) { - return; - } - this.error = ''; - - item.dashboardId = this.dashboard.id; - - this.items.push(this.prepareViewModel(item)); - this.sortItems(); - - this.canUpdate = true; - } - - isValid(item) { - const dupe = _.find(this.items, it => { - return this.isDuplicate(it, item); - }); - - if (dupe) { - this.error = this.duplicateError; - return false; - } - - return true; - } - - isDuplicate(origItem, newItem) { - if (origItem.inherited) { - return false; - } - - return ( - (origItem.role && newItem.role && origItem.role === newItem.role) || - (origItem.userId && newItem.userId && origItem.userId === newItem.userId) || - (origItem.teamId && newItem.teamId && origItem.teamId === newItem.teamId) - ); - } - - userPicked(user) { - this.addNewItem({ userId: user.id, userLogin: user.login, permission: 1 }); - this.$scope.$broadcast('user-picker-reset'); - } - - groupPicked(group) { - this.addNewItem({ teamId: group.id, team: group.name, permission: 1 }); - this.$scope.$broadcast('team-picker-reset'); - } - - removeItem(index) { - this.items.splice(index, 1); - this.canUpdate = true; - } -} - -export function dashAclModal() { - return { - restrict: 'E', - templateUrl: 'public/app/features/dashboard/acl/acl.html', - controller: AclCtrl, - bindToController: true, - controllerAs: 'ctrl', - scope: { - dashboard: '=', - meta: '=', - }, - }; -} - -export interface FormModel { - dashboardId: number; - userId?: number; - teamId?: number; - PermissionType: number; -} - -export interface DashboardAcl { - id?: number; - dashboardId?: number; - userId?: number; - userLogin?: string; - userEmail?: string; - teamId?: number; - team?: string; - permission?: number; - permissionName?: string; - role?: string; - icon?: string; - nameHtml?: string; - inherited?: boolean; - sortName?: string; - sortRank?: number; -} - -coreModule.directive('dashAclModal', dashAclModal); diff --git a/public/app/features/dashboard/acl/specs/acl.jest.ts b/public/app/features/dashboard/acl/specs/acl.jest.ts deleted file mode 100644 index 5d4b65ce3d1..00000000000 --- a/public/app/features/dashboard/acl/specs/acl.jest.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { AclCtrl } from '../acl'; - -describe('AclCtrl', () => { - const backendSrv = { - getDashboard: jest.fn(() => - Promise.resolve({ id: 1, meta: { isFolder: false } }) - ), - get: jest.fn(() => Promise.resolve([])), - post: jest.fn(() => Promise.resolve([])), - }; - - let ctrl; - let backendSrvPostMock; - - beforeEach(() => { - AclCtrl.prototype.dashboard = { id: 1 }; - AclCtrl.prototype.meta = { isFolder: false }; - - ctrl = new AclCtrl( - backendSrv, - { trustAsHtml: t => t }, - { $broadcast: () => {} } - ); - backendSrvPostMock = backendSrv.post as any; - }); - - describe('when permissions are added', () => { - beforeEach(() => { - const userItem = { - id: 2, - login: 'user2', - }; - - ctrl.userPicked(userItem); - - const teamItem = { - id: 2, - name: 'ug1', - }; - - ctrl.groupPicked(teamItem); - - ctrl.newType = 'Editor'; - ctrl.typeChanged(); - - ctrl.newType = 'Viewer'; - ctrl.typeChanged(); - - return ctrl.update(); - }); - - it('should sort the result by role, team and user', () => { - expect(ctrl.items[0].role).toBe('Viewer'); - expect(ctrl.items[1].role).toBe('Editor'); - expect(ctrl.items[2].teamId).toBe(2); - expect(ctrl.items[3].userId).toBe(2); - }); - - it('should save permissions to db', () => { - expect(backendSrvPostMock.mock.calls[0][0]).toBe( - '/api/dashboards/id/1/acl' - ); - expect(backendSrvPostMock.mock.calls[0][1].items[0].role).toBe('Viewer'); - expect(backendSrvPostMock.mock.calls[0][1].items[0].permission).toBe(1); - expect(backendSrvPostMock.mock.calls[0][1].items[1].role).toBe('Editor'); - expect(backendSrvPostMock.mock.calls[0][1].items[1].permission).toBe(1); - expect(backendSrvPostMock.mock.calls[0][1].items[2].teamId).toBe(2); - expect(backendSrvPostMock.mock.calls[0][1].items[2].permission).toBe(1); - expect(backendSrvPostMock.mock.calls[0][1].items[3].userId).toBe(2); - expect(backendSrvPostMock.mock.calls[0][1].items[3].permission).toBe(1); - }); - }); - - describe('when duplicate role permissions are added', () => { - beforeEach(() => { - ctrl.items = []; - - ctrl.newType = 'Editor'; - ctrl.typeChanged(); - - ctrl.newType = 'Editor'; - ctrl.typeChanged(); - }); - - it('should throw a validation error', () => { - expect(ctrl.error).toBe(ctrl.duplicateError); - }); - - it('should not add the duplicate permission', () => { - expect(ctrl.items.length).toBe(1); - }); - }); - - describe('when duplicate user permissions are added', () => { - beforeEach(() => { - ctrl.items = []; - - const userItem = { - id: 2, - login: 'user2', - }; - - ctrl.userPicked(userItem); - ctrl.userPicked(userItem); - }); - - it('should throw a validation error', () => { - expect(ctrl.error).toBe(ctrl.duplicateError); - }); - - it('should not add the duplicate permission', () => { - expect(ctrl.items.length).toBe(1); - }); - }); - - describe('when duplicate team permissions are added', () => { - beforeEach(() => { - ctrl.items = []; - - const teamItem = { - id: 2, - name: 'ug1', - }; - - ctrl.groupPicked(teamItem); - ctrl.groupPicked(teamItem); - }); - - it('should throw a validation error', () => { - expect(ctrl.error).toBe(ctrl.duplicateError); - }); - - it('should not add the duplicate permission', () => { - expect(ctrl.items.length).toBe(1); - }); - }); - - describe('when one inherited and one not inherited team permission are added', () => { - beforeEach(() => { - ctrl.items = []; - - const inheritedTeamItem = { - id: 2, - name: 'ug1', - dashboardId: -1, - }; - - ctrl.items.push(inheritedTeamItem); - - const teamItem = { - id: 2, - name: 'ug1', - }; - ctrl.groupPicked(teamItem); - }); - - it('should not throw a validation error', () => { - expect(ctrl.error).toBe(''); - }); - - it('should add both permissions', () => { - expect(ctrl.items.length).toBe(2); - }); - }); - - afterEach(() => { - backendSrvPostMock.mockClear(); - }); -}); diff --git a/public/app/features/dashboard/all.ts b/public/app/features/dashboard/all.ts index fd79b7b1f03..f2e2e3dcdc0 100644 --- a/public/app/features/dashboard/all.ts +++ b/public/app/features/dashboard/all.ts @@ -23,7 +23,6 @@ import './repeat_option/repeat_option'; import './dashgrid/DashboardGridDirective'; import './dashgrid/PanelLoader'; import './dashgrid/RowOptions'; -import './acl/acl'; import './folder_picker/folder_picker'; import './move_to_folder_modal/move_to_folder'; import './settings/settings'; @@ -31,14 +30,12 @@ import './settings/settings'; import coreModule from 'app/core/core_module'; import { DashboardListCtrl } from './dashboard_list_ctrl'; import { FolderDashboardsCtrl } from './folder_dashboards_ctrl'; -import { FolderPermissionsCtrl } from './folder_permissions_ctrl'; import { FolderSettingsCtrl } from './folder_settings_ctrl'; import { DashboardImportCtrl } from './dashboard_import_ctrl'; import { CreateFolderCtrl } from './create_folder_ctrl'; coreModule.controller('DashboardListCtrl', DashboardListCtrl); coreModule.controller('FolderDashboardsCtrl', FolderDashboardsCtrl); -coreModule.controller('FolderPermissionsCtrl', FolderPermissionsCtrl); coreModule.controller('FolderSettingsCtrl', FolderSettingsCtrl); coreModule.controller('DashboardImportCtrl', DashboardImportCtrl); coreModule.controller('CreateFolderCtrl', CreateFolderCtrl); diff --git a/public/app/features/dashboard/partials/folder_permissions.html b/public/app/features/dashboard/partials/folder_permissions.html index 0ce933f4d5c..be44c1051f2 100644 --- a/public/app/features/dashboard/partials/folder_permissions.html +++ b/public/app/features/dashboard/partials/folder_permissions.html @@ -1,8 +1,7 @@
- - +
diff --git a/public/app/features/dashboard/settings/settings.html b/public/app/features/dashboard/settings/settings.html index 9431584c775..d6dd1069c68 100644 --- a/public/app/features/dashboard/settings/settings.html +++ b/public/app/features/dashboard/settings/settings.html @@ -97,10 +97,10 @@

Permissions

- - + +
diff --git a/public/app/stores/PermissionsStore/PermissionsStore.jest.ts b/public/app/stores/PermissionsStore/PermissionsStore.jest.ts index a84773a6a4a..34206ba41c9 100644 --- a/public/app/stores/PermissionsStore/PermissionsStore.jest.ts +++ b/public/app/stores/PermissionsStore/PermissionsStore.jest.ts @@ -11,12 +11,11 @@ describe('PermissionsStore', () => { { id: 3, dashboardId: 1, role: 'Editor', permission: 1, permissionName: 'Edit' }, { id: 4, - dashboardId: 1, - userId: 2, - userLogin: 'danlimerick', - userEmail: 'dan.limerick@gmail.com', - permission: 4, - permissionName: 'Admin', + dashboardId: 10, + permission: 1, + permissionName: 'View', + teamId: 1, + teamName: 'MyTestTeam', }, ]) ); @@ -33,7 +32,7 @@ describe('PermissionsStore', () => { } ); - return store.load(1, true); + return store.load(1, false); }); it('should save update on permission change', () => { @@ -72,4 +71,78 @@ describe('PermissionsStore', () => { expect(backendSrv.post.mock.calls.length).toBe(1); expect(backendSrv.post.mock.calls[0][0]).toBe('/api/dashboards/id/1/acl'); }); + + describe('when duplicate user permissions are added', () => { + beforeEach(() => { + const newItem = { + userId: 10, + userLogin: 'tester1', + permission: 1, + }; + store.addStoreItem(newItem); + store.addStoreItem(newItem); + }); + + it('should return a validation error', () => { + expect(store.items.length).toBe(4); + expect(store.error).toBe('This permission exists already.'); + expect(backendSrv.post.mock.calls.length).toBe(1); + }); + }); + + describe('when duplicate team permissions are added', () => { + beforeEach(() => { + const newItem = { + teamId: 1, + teamName: 'testerteam', + permission: 1, + }; + store.addStoreItem(newItem); + store.addStoreItem(newItem); + }); + + it('should return a validation error', () => { + expect(store.items.length).toBe(4); + expect(store.error).toBe('This permission exists already.'); + expect(backendSrv.post.mock.calls.length).toBe(1); + }); + }); + + describe('when duplicate role permissions are added', () => { + beforeEach(() => { + const newItem = { + team: 'MyTestTeam', + teamId: 1, + permission: 1, + }; + store.addStoreItem(newItem); + store.addStoreItem(newItem); + }); + + it('should return a validation error', () => { + expect(store.items.length).toBe(4); + expect(store.error).toBe('This permission exists already.'); + expect(backendSrv.post.mock.calls.length).toBe(1); + }); + }); + + describe('when one inherited and one not inherited team permission are added', () => { + beforeEach(() => { + const teamItem = { + team: 'MyTestTeam', + dashboardId: 1, + teamId: 1, + permission: 2, + }; + store.addStoreItem(teamItem); + }); + + it('should not throw a validation error', () => { + expect(store.error).toBe(null); + }); + + it('should add both permissions', () => { + expect(store.items.length).toBe(4); + }); + }); }); diff --git a/public/app/stores/PermissionsStore/PermissionsStore.ts b/public/app/stores/PermissionsStore/PermissionsStore.ts index 54078051d4b..0cf5d278ea3 100644 --- a/public/app/stores/PermissionsStore/PermissionsStore.ts +++ b/public/app/stores/PermissionsStore/PermissionsStore.ts @@ -57,12 +57,12 @@ export const PermissionsStore = types } self.items.push(prepareItem(item, self.dashboardId, self.isFolder)); - updateItems(self); + return updateItems(self); }), removeStoreItem: flow(function* removeStoreItem(idx: number) { self.error = null; self.items.splice(idx, 1); - updateItems(self); + return updateItems(self); }), updatePermissionOnIndex: flow(function* updatePermissionOnIndex( idx: number, @@ -71,7 +71,7 @@ export const PermissionsStore = types ) { self.error = null; self.items[idx].updatePermission(permission, permissionName); - updateItems(self); + return updateItems(self); }), setNewType(newType: string) { self.newType = newType; @@ -118,8 +118,7 @@ const prepareServerResponse = (response, dashboardId: number, isFolder: boolean) }; const prepareItem = (item, dashboardId: number, isFolder: boolean) => { - item.inherited = !isFolder && dashboardId !== item.dashboardId; - + item.inherited = !isFolder && item.dashboardId > 0 && dashboardId !== item.dashboardId; item.sortRank = 0; if (item.userId > 0) { item.icon = 'fa fa-fw fa-user';