From f4dc18719151d66aabb98b2ea05934261d078313 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Stephan=20M=C3=BCller?= Date: Fri, 31 Aug 2018 14:46:42 +0200 Subject: [PATCH] mgr/dashboard: Use table actions component for RBD snapshots MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Stephan Müller --- .../rbd-snapshot-actions.model.ts | 68 ++++++ .../rbd-snapshot-list.component.html | 110 +-------- .../rbd-snapshot-list.component.spec.ts | 214 +++++++++++++++++- .../rbd-snapshot-list.component.ts | 21 +- 4 files changed, 304 insertions(+), 109 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-actions.model.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 index 00000000000..42ce1a28c95 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-actions.model.ts @@ -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 + ]; +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.html index 7ad626865d1..53237ce7f05 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.html @@ -3,111 +3,11 @@ selectionType="single" (updateSelection)="updateSelection($event)" [columns]="columns"> -
-
- - - - - -
-
+ + { 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] + ]); + }); + }); + }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts index f001b29cdd8..0c44327bfb7 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts @@ -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; 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() { -- 2.39.5