Commit 50b20a0e by Daniel Lee

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.
parent 83548f98
......@@ -21,7 +21,7 @@ export class FolderPermissions extends Component<IContainerProps, any> {
}
render() {
const { nav, folder, permissions } = this.props;
const { nav, folder, permissions, backendSrv } = this.props;
if (!folder.folder || !nav.main) {
return <h2>Loading</h2>;
......@@ -34,12 +34,7 @@ export class FolderPermissions extends Component<IContainerProps, any> {
<PageHeader model={nav as any} />
<div className="page-container page-body">
<h2 className="page-sub-heading">Folder Permissions</h2>
<Permissions
permissions={permissions}
isFolder={true}
dashboardId={dashboardId}
backendSrv={this.props.backendSrv}
/>
<Permissions permissions={permissions} isFolder={true} dashboardId={dashboardId} backendSrv={backendSrv} />
</div>
</div>
);
......
......@@ -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']);
}
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<IProps, any> {
permissions: any;
constructor(props) {
super(props);
this.permissions = store.permissions;
}
render() {
const { dashboardId, backendSrv } = this.props;
return (
<Permissions permissions={this.permissions} isFolder={false} dashboardId={dashboardId} backendSrv={backendSrv} />
);
}
}
export default DashboardPermissions;
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(<Permissions backendSrv={backendSrv} isFolder={true} dashboardId={1} {...store} />);
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');
});
});
});
});
......@@ -77,12 +77,12 @@ class Permissions extends Component<IProps, any> {
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() {
......
......@@ -23,7 +23,6 @@ export default observer(({ item, removeItem, permissionChanged, itemIndex }) =>
return (
<tr className={setClassNameHelper(item.inherited)}>
<td style={{ width: '100%' }}>
{/* style="width: 100%;" */}
<i className={item.icon} />
<span dangerouslySetInnerHTML={{ __html: item.nameHtml }} />
</td>
......@@ -55,7 +54,7 @@ export default observer(({ item, removeItem, permissionChanged, itemIndex }) =>
</td>
<td>
{!item.inherited ? (
<a className="btn btn-inverse btn-small" onClick={handleRemoveItem}>
<a className="btn btn-danger btn-small" onClick={handleRemoveItem}>
<i className="fa fa-remove" />
</a>
) : null}
......
import './directives/dash_class';
import './directives/dash_edit_link';
import './directives/dropdown_typeahead';
import './directives/metric_segment';
import './directives/misc';
......
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: '<gf-dashboard-history dashboard="dashboard"></gf-dashboard-history>'},
'timepicker': { src: 'public/app/features/dashboard/timepicker/dropdown.html' },
'import': { html: '<dash-import dismiss="dismiss()"></dash-import>', isModal: true },
'permissions': { html: '<dash-acl-modal dismiss="dismiss()"></dash-acl-modal>', isModal: true },
'new-folder': {
isModal: true,
html: '<folder-modal dismiss="dismiss()"></folder-modal>',
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('<div class="tabbed-view" ng-include="' + "'" + options.src + "'" + '"></div>');
} 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');
});
}
};
});
});
<permissions
error="{{ctrl.error}}"
newType="ctrl.newType"
aclTypes="{{ctrl.aclTypes}}"
typeChanged="ctrl.typeChanged"
dashboardId="ctrl.dashboard.id"
backendSrv="ctrl.backendSrv" />
<div class="gf-form-group">
<table class="filter-table gf-form-group">
<tr ng-repeat="acl in ctrl.items" ng-class="{'gf-form-disabled': acl.inherited}">
<td style="width: 100%;">
<i class="{{acl.icon}}"></i>
<span ng-bind-html="acl.nameHtml"></span>
</td>
<td>
<em class="muted no-wrap" ng-show="acl.inherited">Inherited from folder</em>
</td>
<td class="query-keyword">Can</td>
<td>
<div class="gf-form-select-wrapper">
<select class="gf-form-input gf-size-auto" ng-model="acl.permission" ng-options="p.value as p.text for p in ctrl.permissionOptions" ng-change="ctrl.permissionChanged(acl)" ng-disabled="acl.inherited"></select>
</div>
</td>
<td>
<a class="btn btn-inverse btn-small" ng-click="ctrl.removeItem($index)" ng-hide="acl.inherited">
<i class="fa fa-remove"></i>
</a>
</td>
</tr>
<tr ng-show="ctrl.aclItems.length === 0">
<td colspan="4">
<em>No permissions are set. Will only be accessible by admins.</em>
</td>
</tr>
</table>
<div class="gf-form-inline">
<form name="addPermission" class="gf-form-group">
<h6 class="muted">Add Permission For</h6>
<div class="gf-form-inline">
<div class="gf-form">
<div class="gf-form-select-wrapper">
<select class="gf-form-input gf-size-auto" ng-model="ctrl.newType" ng-options="p.value as p.text for p in ctrl.aclTypes" ng-change="ctrl.typeChanged()"></select>
</div>
</div>
<div class="gf-form" ng-show="ctrl.newType === 'User'">
<user-picker user-picked="ctrl.userPicked($user)"></user-picker>
</div>
<div class="gf-form" ng-show="ctrl.newType === 'Group'">
<team-picker team-picked="ctrl.groupPicked($group)"></team-picker>
</div>
</div>
</form>
<div class="gf-form width-17">
<span ng-if="ctrl.error" class="text-error p-l-1">
<i class="fa fa-warning"></i>
{{ctrl.error}}
</span>
</div>
</div>
<div class="gf-form-button-row">
<button type="button" class="btn btn-danger" ng-disabled="!ctrl.canUpdate" ng-click="ctrl.update()">
Update Permissions
</button>
</div>
</div>
<div class="empty-list-cta">
<div class="grafana-info-box">
<h5>What are Permissions?</h5>
<p>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.</p>
<p>The permissions that can be assigned for a folder/dashboard are:</p>
<p>View, Edit and Admin.</p>
Checkout the <a class="external-link" target="_blank" href="http://docs.grafana.org/reference/dashboard_folders/">Dashboard Folders documentation</a> for more information.
</div>
</div>
<!-- <br> -->
<!-- <br> -->
<!-- <br> -->
<!-- -->
<!-- <div class="permissionlist"> -->
<!-- <div class="permissionlist__section"> -->
<!-- <div class="permissionlist__section&#45;header"> -->
<!-- <h6>Permissions</h6> -->
<!-- </div> -->
<!-- <table class="filter&#45;table form&#45;inline"> -->
<!-- <thead> -->
<!-- <tr> -->
<!-- <th style="width: 50px;"></th> -->
<!-- <th>Name</th> -->
<!-- <th style="width: 220px;">Permission</th> -->
<!-- <th style="width: 120px"></th> -->
<!-- </tr> -->
<!-- </thead> -->
<!-- <tbody> -->
<!-- <tr ng&#45;repeat="permission in ctrl.userPermissions" class="permissionlist__item"> -->
<!-- <td><i class="fa fa&#45;fw fa&#45;user"></i></td> -->
<!-- <td>{{permission.userLogin}}</td> -->
<!-- <td class="text&#45;right"> -->
<!-- <a ng&#45;click="ctrl.removePermission(permission)" class="btn btn&#45;danger btn&#45;small"> -->
<!-- <i class="fa fa&#45;remove"></i> -->
<!-- </a> -->
<!-- </td> -->
<!-- </tr> -->
<!-- <tr ng&#45;repeat="permission in ctrl.teamPermissions" class="permissionlist__item"> -->
<!-- <td><i class="fa fa&#45;fw fa&#45;users"></i></td> -->
<!-- <td>{{permission.team}}</td> -->
<!-- <td><select class="gf&#45;form&#45;input gf&#45;size&#45;auto" ng&#45;model="permission.permissions" ng&#45;options="p.value as p.text for p in ctrl.permissionTypeOptions" ng&#45;change="ctrl.updatePermission(permission)"></select></td> -->
<!-- <td class="text&#45;right"> -->
<!-- <a ng&#45;click="ctrl.removePermission(permission)" class="btn btn&#45;danger btn&#45;small"> -->
<!-- <i class="fa fa&#45;remove"></i> -->
<!-- </a> -->
<!-- </td> -->
<!-- </tr> -->
<!-- <tr ng&#45;repeat="role in ctrl.roles" class="permissionlist__item"> -->
<!-- <td></td> -->
<!-- <td>{{role.name}}</td> -->
<!-- <td><select class="gf&#45;form&#45;input gf&#45;size&#45;auto" ng&#45;model="role.permissions" ng&#45;options="p.value as p.text for p in ctrl.roleOptions" ng&#45;change="ctrl.updatePermission(role)"></select></td> -->
<!-- <td class="text&#45;right"> -->
<!-- -->
<!-- </td> -->
<!-- </tr> -->
<!-- </tbody> -->
<!-- </table> -->
<!-- </div> -->
<!-- </div> -->
<!-- </div> -->
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 <span class="query-keyword">${item.role}</span> 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);
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();
});
});
......@@ -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);
<page-header model="ctrl.navModel"></page-header>
<div class="page-container page-body">
<dash-acl-modal ng-if="ctrl.dashboard && ctrl.meta"
dashboard="ctrl.dashboard"
meta="ctrl.meta">
</dash-acl-modal>
<dashboard-permissions ng-if="ctrl.dashboard && ctrl.meta"
dashboardId="ctrl.dashboard.id"
/>
</div>
......@@ -97,10 +97,10 @@
<div class="dashboard-settings__content" ng-if="ctrl.viewId === 'permissions'" >
<h3 class="dashboard-settings__header">Permissions</h3>
<dash-acl-modal ng-if="ctrl.dashboard"
dashboard="ctrl.dashboard"
meta="ctrl.dashboard.meta">
</dash-acl-modal>
<dashboard-permissions ng-if="ctrl.dashboard"
dashboardId="ctrl.dashboard.id"
backendSrv="ctrl.backendSrv">
</dashboard-permissions>
</div>
<div class="dashboard-settings__content" ng-if="ctrl.viewId === '404'">
......
......@@ -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);
});
});
});
......@@ -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';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment