ソースを参照

dashfolders: permissions tab for dashboard folders

Daniel Lee 8 年 前
コミット
5400692cd4

+ 51 - 65
public/app/features/dashboard/acl/acl.html

@@ -1,75 +1,61 @@
-<div class="modal-body">
-  <div class="modal-header">
-    <h2 class="modal-header-title">
-      <i class="fa fa-lock"></i>
-      <span class="p-l-1">Permissions</span>
-    </h2>
-
-    <a class="modal-header-close" ng-click="ctrl.dismiss();">
-      <i class="fa fa-remove"></i>
-    </a>
-  </div>
+<div>
+  <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="modal-content">
-    <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-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">
           <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. 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>
+            <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>
         </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 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>
       </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>
 
 
-    <div class="gf-form-button-row text-center">
-      <button type="button" class="btn btn-danger" ng-disabled="!ctrl.canUpdate" ng-click="ctrl.update()">
-        Update Permissions
-      </button>
-      <a class="btn-text" ng-click="ctrl.dismiss();">Close</a>
-    </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>
 </div>
 
 

+ 12 - 10
public/app/features/dashboard/acl/acl.ts

@@ -3,6 +3,8 @@ import _ from 'lodash';
 
 
 export class AclCtrl {
 export class AclCtrl {
   dashboard: any;
   dashboard: any;
+  meta: any;
+
   items: DashboardAcl[];
   items: DashboardAcl[];
   permissionOptions = [{ value: 1, text: 'View' }, { value: 2, text: 'Edit' }, { value: 4, text: 'Admin' }];
   permissionOptions = [{ value: 1, text: 'View' }, { value: 2, text: 'Edit' }, { value: 4, text: 'Admin' }];
   aclTypes = [
   aclTypes = [
@@ -12,7 +14,6 @@ export class AclCtrl {
     { value: 'Editor', text: 'Everyone With Editor Role' },
     { value: 'Editor', text: 'Everyone With Editor Role' },
   ];
   ];
 
 
-  dismiss: () => void;
   newType: string;
   newType: string;
   canUpdate: boolean;
   canUpdate: boolean;
   error: string;
   error: string;
@@ -20,18 +21,17 @@ export class AclCtrl {
   readonly duplicateError = 'This permission exists already.';
   readonly duplicateError = 'This permission exists already.';
 
 
   /** @ngInject */
   /** @ngInject */
-  constructor(private backendSrv, dashboardSrv, private $sce, private $scope) {
+  constructor(private backendSrv, private $sce, private $scope) {
     this.items = [];
     this.items = [];
     this.resetNewType();
     this.resetNewType();
-    this.dashboard = dashboardSrv.getCurrent();
-    this.get(this.dashboard.id);
+    this.getAcl(this.dashboard.id);
   }
   }
 
 
   resetNewType() {
   resetNewType() {
     this.newType = 'Group';
     this.newType = 'Group';
   }
   }
 
 
-  get(dashboardId: number) {
+  getAcl(dashboardId: number) {
     return this.backendSrv.get(`/api/dashboards/id/${dashboardId}/acl`).then(result => {
     return this.backendSrv.get(`/api/dashboards/id/${dashboardId}/acl`).then(result => {
       this.items = _.map(result, this.prepareViewModel.bind(this));
       this.items = _.map(result, this.prepareViewModel.bind(this));
       this.sortItems();
       this.sortItems();
@@ -43,7 +43,8 @@ export class AclCtrl {
   }
   }
 
 
   prepareViewModel(item: DashboardAcl): DashboardAcl {
   prepareViewModel(item: DashboardAcl): DashboardAcl {
-    item.inherited = !this.dashboard.meta.isFolder && this.dashboard.id !== item.dashboardId;
+    item.inherited =
+      !this.meta.isFolder && this.dashboard.id !== item.dashboardId;
     item.sortRank = 0;
     item.sortRank = 0;
 
 
     if (item.userId > 0) {
     if (item.userId > 0) {
@@ -88,8 +89,8 @@ export class AclCtrl {
       });
       });
     }
     }
 
 
-    return this.backendSrv.post(`/api/dashboards/id/${this.dashboard.id}/acl`, { items: updated }).then(() => {
-      return this.dismiss();
+    return this.backendSrv.post(`/api/dashboards/id/${this.dashboard.id}/acl`, {
+      items: updated
     });
     });
   }
   }
 
 
@@ -168,8 +169,9 @@ export function dashAclModal() {
     bindToController: true,
     bindToController: true,
     controllerAs: 'ctrl',
     controllerAs: 'ctrl',
     scope: {
     scope: {
-      dismiss: '&',
-    },
+      dashboard: "=",
+      meta: "="
+    }
   };
   };
 }
 }
 
 

+ 169 - 0
public/app/features/dashboard/acl/specs/acl.jest.ts

@@ -0,0 +1,169 @@
+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();
+  });
+});

+ 8 - 1
public/app/features/dashboard/folder_permissions_ctrl.ts

@@ -3,13 +3,20 @@ import { FolderPageLoader } from './folder_page_loader';
 export class FolderPermissionsCtrl {
 export class FolderPermissionsCtrl {
   navModel: any;
   navModel: any;
   folderId: number;
   folderId: number;
+  dashboard: any;
+  meta: any;
 
 
   /** @ngInject */
   /** @ngInject */
   constructor(private backendSrv, navModelSrv, private $routeParams) {
   constructor(private backendSrv, navModelSrv, private $routeParams) {
     if (this.$routeParams.folderId && this.$routeParams.slug) {
     if (this.$routeParams.folderId && this.$routeParams.slug) {
       this.folderId = $routeParams.folderId;
       this.folderId = $routeParams.folderId;
 
 
-      new FolderPageLoader(this.backendSrv, this.$routeParams).load(this, this.folderId, 'manage-folder-permissions');
+      new FolderPageLoader(this.backendSrv, this.$routeParams)
+        .load(this, this.folderId, "manage-folder-permissions")
+        .then(result => {
+          this.dashboard = result.dashboard;
+          this.meta = result.meta;
+        });
     }
     }
   }
   }
 }
 }

+ 4 - 3
public/app/features/dashboard/partials/folder_permissions.html

@@ -1,7 +1,8 @@
 <page-header model="ctrl.navModel"></page-header>
 <page-header model="ctrl.navModel"></page-header>
 
 
 <div class="page-container page-body">
 <div class="page-container page-body">
-	<h2 class="page-sub-heading">
-		Coming soon! Permissions will be added in Grafana 5.0 beta.
-	</h2>
+  <dash-acl-modal ng-if="ctrl.dashboard && ctrl.meta"
+    dashboard="ctrl.dashboard"
+    meta="ctrl.meta">
+  </dash-acl-modal>
 </div>
 </div>