]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Use table actions component for RBD snapshots
authorStephan Müller <smueller@suse.com>
Fri, 31 Aug 2018 12:46:42 +0000 (14:46 +0200)
committerStephan Müller <smueller@suse.com>
Wed, 19 Sep 2018 15:16:57 +0000 (17:16 +0200)
Signed-off-by: Stephan Müller <smueller@suse.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-actions.model.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts

diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-actions.model.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-actions.model.ts
new file mode 100644 (file)
index 0000000..42ce1a2
--- /dev/null
@@ -0,0 +1,68 @@
+import { CdTableAction } from '../../../shared/models/cd-table-action';
+import { CdTableSelection } from '../../../shared/models/cd-table-selection';
+
+export class RbdSnapshotActionsModel {
+  create: CdTableAction = {
+    permission: 'create',
+    icon: 'fa-plus',
+    buttonCondition: (selection: CdTableSelection) => !selection.hasSingleSelection,
+    name: 'Create'
+  };
+  rename: CdTableAction = {
+    permission: 'update',
+    icon: 'fa-pencil',
+    name: 'Rename'
+  };
+  protect: CdTableAction = {
+    permission: 'update',
+    icon: 'fa-lock',
+    visible: (selection: CdTableSelection) =>
+      selection.hasSingleSelection && !selection.first().is_protected,
+    name: 'Protect'
+  };
+  unprotect: CdTableAction = {
+    permission: 'update',
+    icon: 'fa-unlock',
+    visible: (selection: CdTableSelection) =>
+      selection.hasSingleSelection && selection.first().is_protected,
+    name: 'Unprotect'
+  };
+  clone: CdTableAction = {
+    permission: 'create',
+    buttonCondition: (selection: CdTableSelection) => selection.hasSingleSelection,
+    disable: (selection: CdTableSelection) => !selection.hasSingleSelection,
+    icon: 'fa-clone',
+    name: 'Clone'
+  };
+  copy: CdTableAction = {
+    permission: 'create',
+    buttonCondition: (selection: CdTableSelection) => selection.hasSingleSelection,
+    disable: (selection: CdTableSelection) => !selection.hasSingleSelection,
+    icon: 'fa-copy',
+    name: 'Copy'
+  };
+  rollback: CdTableAction = {
+    permission: 'update',
+    disable: (selection: CdTableSelection) =>
+      selection.hasSingleSelection && !selection.first().parent,
+    icon: 'fa-undo',
+    name: 'Rollback'
+  };
+  deleteSnap: CdTableAction = {
+    permission: 'delete',
+    icon: 'fa-trash-o',
+    disable: (selection: CdTableSelection) =>
+      selection.hasSingleSelection && !selection.first().is_protected,
+    name: 'Delete'
+  };
+  ordering = [
+    this.create,
+    this.rename,
+    this.protect,
+    this.unprotect,
+    this.clone,
+    this.copy,
+    this.rollback,
+    this.deleteSnap
+  ];
+}
index 7ad626865d16686c11215fe08c33852ae1151bce..53237ce7f0575eccc2b53c3b97b81b5be2647d84 100644 (file)
           selectionType="single"
           (updateSelection)="updateSelection($event)"
           [columns]="columns">
-  <div class="table-actions">
-    <div class="btn-group"
-         dropdown>
-      <button type="button"
-              class="btn btn-sm btn-primary"
-              *ngIf="permission.create && (!permission.update || !selection.hasSingleSelection)"
-              (click)="openCreateSnapshotModal()">
-        <i class="fa fa-fw fa-plus"></i>
-        <span i18n>Create</span>
-      </button>
-      <button type="button"
-              class="btn btn-sm btn-primary"
-              *ngIf="permission.update && (!permission.create || permission.create && selection.hasSingleSelection)"
-              [ngClass]="{'disabled': !selection.hasSingleSelection || selection.first().executing}"
-              (click)="openEditSnapshotModal()">
-        <i class="fa fa-fw fa-pencil"></i>
-        <span i18n>Rename</span>
-      </button>
-      <button type="button"
-              class="btn btn-sm btn-primary"
-              *ngIf="permission.delete && !permission.update && !permission.create"
-              [ngClass]="{'disabled': !selection.hasSingleSelection || selection.first().executing}"
-              (click)="deleteSnapshotModal()">
-        <i class="fa fa-fw fa-trash-o"></i>
-        <span i18n>Delete</span>
-      </button>
-      <button type="button"
-              dropdownToggle
-              class="btn btn-sm btn-primary dropdown-toggle dropdown-toggle-split"
-              *ngIf="permission.create || permission.update">
-        <span class="caret"></span>
-        <span class="sr-only"></span>
-      </button>
-      <ul *dropdownMenu
-          class="dropdown-menu"
-          role="menu">
-        <li role="menuitem"
-            *ngIf="permission.create">
-          <a class="dropdown-item"
-             (click)="openCreateSnapshotModal()">
-            <i class="fa fa-fw fa-plus"></i>
-            <span i18n>Create</span>
-          </a>
-        </li>
-        <li role="menuitem"
-            *ngIf="permission.update"
-            [ngClass]="{'disabled': !selection.hasSingleSelection || selection.first().executing}">
-          <a class="dropdown-item" (click)="openEditSnapshotModal()">
-            <i class="fa fa-fw fa-pencil"></i>
-            <span i18n>Rename</span>
-          </a>
-        </li>
-        <li role="menuitem"
-            *ngIf="permission.update"
-            [ngClass]="{'disabled': !selection.hasSingleSelection || selection.first().executing}">
-          <a class="dropdown-item" (click)="toggleProtection()">
-            <span *ngIf="!selection.first()?.is_protected">
-              <i class="fa fa-fw fa-lock"></i>
-              <span i18n>Protect</span>
-            </span>
-            <span *ngIf="selection.first()?.is_protected">
-              <i class="fa fa-fw fa-unlock"></i>
-              <span i18n>Unprotect</span>
-            </span>
-          </a>
-        </li>
-        <li role="menuitem"
-            *ngIf="permission.create"
-            [ngClass]="{'disabled': !selection.hasSingleSelection || selection.first().executing}">
-          <a class="dropdown-item"
-             routerLink="/block/rbd/clone/{{ poolName | encodeUri }}/{{ rbdName | encodeUri }}/{{ selection.first()?.name | encodeUri }}">
-            <i class="fa fa-fw fa-clone"></i>
-            <span i18n>Clone</span>
-          </a>
-        </li>
-        <li role="menuitem"
-            *ngIf="permission.create"
-            [ngClass]="{'disabled': !selection.hasSingleSelection || selection.first().executing}">
-          <a class="dropdown-item"
-             routerLink="/block/rbd/copy/{{ poolName | encodeUri }}/{{ rbdName | encodeUri }}/{{ selection.first()?.name | encodeUri }}">
-            <i class="fa fa-fw fa-copy"></i>
-            <span i18n>Copy</span>
-          </a>
-        </li>
-        <li role="menuitem"
-            *ngIf="permission.update"
-            [ngClass]="{'disabled': !selection.hasSingleSelection || selection.first().executing}">
-          <a class="dropdown-item"
-             (click)="rollbackModal()">
-            <i class="fa fa-fw fa-undo"></i>
-            <span i18n>Rollback</span>
-          </a>
-        </li>
-        <li role="menuitem"
-            *ngIf="permission.delete"
-            [ngClass]="{'disabled': !selection.hasSingleSelection || selection.first().executing || selection.first().is_protected}">
-          <a class="dropdown-item"
-             (click)="deleteSnapshotModal()">
-            <i class="fa fa-fw fa-trash-o"></i>
-            <span i18n>Delete</span>
-          </a>
-        </li>
-      </ul>
-    </div>
-  </div>
+  <cd-table-actions class="table-actions"
+                    [permission]="permission"
+                    [selection]="selection"
+                    [tableActions]="tableActions">
+  </cd-table-actions>
 </cd-table>
 
 <ng-template #protectTpl
index 514f458483ea7e28d6b8ac424cd64c34aed324c8..0bf3454482dc7b32371f14581494ce6d61c0c8f8 100644 (file)
@@ -1,16 +1,18 @@
 import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
 import { RouterTestingModule } from '@angular/router/testing';
 
 import { ToastModule } from 'ng2-toastr';
 import { BsModalRef, BsModalService } from 'ngx-bootstrap';
 import { Subject, throwError as observableThrowError } from 'rxjs';
 
-import { configureTestBed } from '../../../../testing/unit-test-helper';
+import { configureTestBed, PermissionHelper } from '../../../../testing/unit-test-helper';
 import { ApiModule } from '../../../shared/api/api.module';
 import { RbdService } from '../../../shared/api/rbd.service';
 import { ComponentsModule } from '../../../shared/components/components.module';
 import { DataTableModule } from '../../../shared/datatable/datatable.module';
+import { TableActionsComponent } from '../../../shared/datatable/table-actions/table-actions.component';
 import { ExecutingTask } from '../../../shared/models/executing-task';
 import { Permissions } from '../../../shared/models/permissions';
 import { PipesModule } from '../../../shared/pipes/pipes.module';
@@ -59,10 +61,10 @@ describe('RbdSnapshotListComponent', () => {
     fixture = TestBed.createComponent(RbdSnapshotListComponent);
     component = fixture.componentInstance;
     summaryService = TestBed.get(SummaryService);
-    fixture.detectChanges();
   });
 
   it('should create', () => {
+    fixture.detectChanges();
     expect(component).toBeTruthy();
   });
 
@@ -73,6 +75,7 @@ describe('RbdSnapshotListComponent', () => {
     let authStorageService: AuthStorageService;
 
     beforeEach(() => {
+      fixture.detectChanges();
       called = false;
       rbdService = new RbdService(null);
       notificationService = new NotificationService(null, null);
@@ -135,6 +138,7 @@ describe('RbdSnapshotListComponent', () => {
     };
 
     beforeEach(() => {
+      fixture.detectChanges();
       snapshots = [];
       addSnapshot('a');
       addSnapshot('b');
@@ -197,4 +201,210 @@ describe('RbdSnapshotListComponent', () => {
       );
     });
   });
+
+  describe('show action buttons and drop down actions depending on permissions', () => {
+    let tableActions: TableActionsComponent;
+    let scenario: { fn; empty; single };
+    let permissionHelper: PermissionHelper;
+
+    const getTableActionComponent = (): TableActionsComponent => {
+      fixture.detectChanges();
+      return fixture.debugElement.query(By.directive(TableActionsComponent)).componentInstance;
+    };
+
+    beforeEach(() => {
+      permissionHelper = new PermissionHelper(component.permission, () =>
+        getTableActionComponent()
+      );
+      scenario = {
+        fn: () => tableActions.getCurrentButton().name,
+        single: 'Rename',
+        empty: 'Create'
+      };
+    });
+
+    describe('with all', () => {
+      beforeEach(() => {
+        tableActions = permissionHelper.setPermissionsAndGetActions(1, 1, 1);
+      });
+
+      it(`shows 'Rename' for single selection else 'Create' as main action`, () =>
+        permissionHelper.testScenarios(scenario));
+
+      it('shows all actions', () => {
+        expect(tableActions.tableActions.length).toBe(8);
+        expect(tableActions.tableActions).toEqual(component.tableActions);
+      });
+    });
+
+    describe('with read, create and update', () => {
+      beforeEach(() => {
+        tableActions = permissionHelper.setPermissionsAndGetActions(1, 1, 0);
+      });
+
+      it(`shows 'Rename' for single selection else 'Create' as main action`, () =>
+        permissionHelper.testScenarios(scenario));
+
+      it(`shows all actions except for 'Delete'`, () => {
+        expect(tableActions.tableActions.length).toBe(7);
+        component.tableActions.pop();
+        expect(tableActions.tableActions).toEqual(component.tableActions);
+      });
+    });
+
+    describe('with read, create and delete', () => {
+      beforeEach(() => {
+        tableActions = permissionHelper.setPermissionsAndGetActions(1, 0, 1);
+      });
+
+      it(`shows 'Clone' for single selection else 'Create' as main action`, () => {
+        scenario.single = 'Clone';
+        permissionHelper.testScenarios(scenario);
+      });
+
+      it(`shows 'Create', 'Clone', 'Copy' and 'Delete' action`, () => {
+        expect(tableActions.tableActions.length).toBe(4);
+        expect(tableActions.tableActions).toEqual([
+          component.tableActions[0],
+          component.tableActions[4],
+          component.tableActions[5],
+          component.tableActions[7]
+        ]);
+      });
+    });
+
+    describe('with read, edit and delete', () => {
+      beforeEach(() => {
+        tableActions = permissionHelper.setPermissionsAndGetActions(0, 1, 1);
+      });
+
+      it(`shows always 'Rename' as main action`, () => {
+        scenario.empty = 'Rename';
+        permissionHelper.testScenarios(scenario);
+      });
+
+      it(`shows 'Rename', 'Protect', 'Unprotect', 'Rollback' and 'Delete' action`, () => {
+        expect(tableActions.tableActions.length).toBe(5);
+        expect(tableActions.tableActions).toEqual([
+          component.tableActions[1],
+          component.tableActions[2],
+          component.tableActions[3],
+          component.tableActions[6],
+          component.tableActions[7]
+        ]);
+      });
+    });
+
+    describe('with read and create', () => {
+      beforeEach(() => {
+        tableActions = permissionHelper.setPermissionsAndGetActions(1, 0, 0);
+      });
+
+      it(`shows 'Clone' for single selection else 'Create' as main action`, () => {
+        scenario.single = 'Clone';
+        permissionHelper.testScenarios(scenario);
+      });
+
+      it(`shows 'Create', 'Clone' and 'Copy' actions`, () => {
+        expect(tableActions.tableActions.length).toBe(3);
+        expect(tableActions.tableActions).toEqual([
+          component.tableActions[0],
+          component.tableActions[4],
+          component.tableActions[5]
+        ]);
+      });
+    });
+
+    describe('with read and edit', () => {
+      beforeEach(() => {
+        tableActions = permissionHelper.setPermissionsAndGetActions(0, 1, 0);
+      });
+
+      it(`shows always 'Rename' as main action`, () => {
+        scenario.empty = 'Rename';
+        permissionHelper.testScenarios(scenario);
+      });
+
+      it(`shows 'Rename', 'Protect', 'Unprotect' and 'Rollback' actions`, () => {
+        expect(tableActions.tableActions.length).toBe(4);
+        expect(tableActions.tableActions).toEqual([
+          component.tableActions[1],
+          component.tableActions[2],
+          component.tableActions[3],
+          component.tableActions[6]
+        ]);
+      });
+    });
+
+    describe('with read and delete', () => {
+      beforeEach(() => {
+        tableActions = permissionHelper.setPermissionsAndGetActions(0, 0, 1);
+      });
+
+      it(`shows always 'Delete' as main action`, () => {
+        scenario.single = 'Delete';
+        scenario.empty = 'Delete';
+        permissionHelper.testScenarios(scenario);
+      });
+
+      it(`shows only 'Delete' action`, () => {
+        expect(tableActions.tableActions.length).toBe(1);
+        expect(tableActions.tableActions).toEqual([component.tableActions[7]]);
+      });
+    });
+
+    describe('with only read', () => {
+      beforeEach(() => {
+        tableActions = permissionHelper.setPermissionsAndGetActions(0, 0, 0);
+      });
+
+      it('shows no main action', () => {
+        permissionHelper.testScenarios({
+          fn: () => tableActions.getCurrentButton(),
+          single: undefined,
+          empty: undefined
+        });
+      });
+
+      it('shows no actions', () => {
+        expect(tableActions.tableActions.length).toBe(0);
+        expect(tableActions.tableActions).toEqual([]);
+      });
+    });
+
+    describe('test unprotected and protected action cases', () => {
+      beforeEach(() => {
+        tableActions = permissionHelper.setPermissionsAndGetActions(0, 1, 0);
+      });
+
+      it(`shows none of them if nothing is selected`, () => {
+        permissionHelper.setSelection([]);
+        fixture.detectChanges();
+        expect(tableActions.dropDownActions).toEqual([
+          component.tableActions[1],
+          component.tableActions[6]
+        ]);
+      });
+
+      it(`shows 'Protect' of them if nothing is selected`, () => {
+        permissionHelper.setSelection([{ is_protected: false }]);
+        fixture.detectChanges();
+        expect(tableActions.dropDownActions).toEqual([
+          component.tableActions[1],
+          component.tableActions[2],
+          component.tableActions[6]
+        ]);
+      });
+
+      it(`shows 'Unprotect' of them if nothing is selected`, () => {
+        permissionHelper.setSelection([{ is_protected: true }]);
+        fixture.detectChanges();
+        expect(tableActions.dropDownActions).toEqual([
+          component.tableActions[1],
+          component.tableActions[3],
+          component.tableActions[6]
+        ]);
+      });
+    });
+  });
 });
index f001b29cdd85a529be9587321a096dc06a3b2d8f..0c44327bfb7fe723bc24d6718a87bb996fc20c39 100644 (file)
@@ -9,6 +9,7 @@ import { RbdService } from '../../../shared/api/rbd.service';
 import { ConfirmationModalComponent } from '../../../shared/components/confirmation-modal/confirmation-modal.component';
 import { DeletionModalComponent } from '../../../shared/components/deletion-modal/deletion-modal.component';
 import { CellTemplate } from '../../../shared/enum/cell-template.enum';
+import { CdTableAction } from '../../../shared/models/cd-table-action';
 import { CdTableColumn } from '../../../shared/models/cd-table-column';
 import { CdTableSelection } from '../../../shared/models/cd-table-selection';
 import { ExecutingTask } from '../../../shared/models/executing-task';
@@ -22,6 +23,7 @@ import { SummaryService } from '../../../shared/services/summary.service';
 import { TaskListService } from '../../../shared/services/task-list.service';
 import { TaskManagerService } from '../../../shared/services/task-manager.service';
 import { RbdSnapshotFormComponent } from '../rbd-snapshot-form/rbd-snapshot-form.component';
+import { RbdSnapshotActionsModel } from './rbd-snapshot-actions.model';
 import { RbdSnapshotModel } from './rbd-snapshot.model';
 
 @Component({
@@ -45,6 +47,8 @@ export class RbdSnapshotListComponent implements OnInit, OnChanges {
   rollbackTpl: TemplateRef<any>;
 
   permission: Permission;
+  selection = new CdTableSelection();
+  tableActions: CdTableAction[];
 
   data: RbdSnapshotModel[];
 
@@ -52,8 +56,6 @@ export class RbdSnapshotListComponent implements OnInit, OnChanges {
 
   modalRef: BsModalRef;
 
-  selection = new CdTableSelection();
-
   builders = {
     'rbd/snap/create': (metadata) => {
       const model = new RbdSnapshotModel();
@@ -74,6 +76,21 @@ export class RbdSnapshotListComponent implements OnInit, OnChanges {
     private taskListService: TaskListService
   ) {
     this.permission = this.authStorageService.getPermissions().rbdImage;
+    const actions = new RbdSnapshotActionsModel();
+    actions.create.click = () => this.openCreateSnapshotModal();
+    actions.rename.click = () => this.openEditSnapshotModal();
+    actions.protect.click = () => this.toggleProtection();
+    actions.unprotect.click = () => this.toggleProtection();
+    const getImageUri = () =>
+      this.selection.first() &&
+      `${encodeURI(this.poolName)}/${encodeURI(this.rbdName)}/${encodeURI(
+        this.selection.first().name
+      )}`;
+    actions.clone.routerLink = () => `/block/rbd/clone/${getImageUri()}`;
+    actions.copy.routerLink = () => `/block/rbd/copy/${getImageUri()}`;
+    actions.rollback.click = () => this.rollbackModal();
+    actions.deleteSnap.click = () => this.deleteSnapshotModal();
+    this.tableActions = actions.ordering;
   }
 
   ngOnInit() {