]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: OSD custom action button removal 28095/head
authorStephan Müller <smueller@suse.com>
Wed, 17 Apr 2019 12:17:44 +0000 (14:17 +0200)
committerStephan Müller <smueller@suse.com>
Fri, 14 Jun 2019 15:17:14 +0000 (17:17 +0200)
Now the custom action dropdown is removed from the OSD component. It was
nearly a clone of the table actions button component, but it had a
different behavior as it did not show the main action inside the dropdown.

Fixes: https://tracker.ceph.com/issues/39702
Signed-off-by: Stephan Müller <smueller@suse.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.scss
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-actions/table-actions.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-actions/table-actions.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-actions/table-actions.component.ts
src/pybind/mgr/dashboard/frontend/src/styles.scss

index 54182338efd8554a01e5a5034ae620b62df2e4b6..34a099cbcd4b075106a4a19fe864a72dc13089d6 100644 (file)
         <cd-table-actions [permission]="permissions.osd"
                           [selection]="selection"
                           class="btn-group"
+                          id="osd-actions"
                           [tableActions]="tableActions">
         </cd-table-actions>
-
-        <div class="btn-group"
-             dropdown
-             *ngIf="advancedTableActions.length > 0">
-          <button type="button"
-                  class="btn btn-sm btn-default btn-label tc_configureCluster"
-                  (click)="advancedTableActions[0].click()">
-            <i class="fa fa-fw {{ advancedTableActions[0].icon }}"></i><span>{{ advancedTableActions[0].name }}</span>
-          </button>
-          <button type="button"
-                  dropdownToggle
-                  class="btn btn-sm btn-default dropdown-toggle dropdown-toggle-split"
-                  *ngIf="advancedTableActions.length > 1">
-            <span class="caret caret-black"></span>
-          </button>
-          <ul *dropdownMenu
-              class="dropdown-menu"
-              role="menu">
-            <ng-container *ngFor="let action of advancedTableActions | slice:1">
-              <li role="menuitem">
-                <a class="dropdown-item"
-                   (click)="action.click()">
-                  <i class="fa fa-fw {{ action.icon }}"
-                     aria-hidden="true">
-                  </i>
-                  <ng-container>{{ action.name }}</ng-container>
-                </a>
-              </li>
-            </ng-container>
-          </ul>
-        </div>
+        <cd-table-actions [permission]="{read: true}"
+                          [selection]="selection"
+                          dropDownOnly="Cluster-wide configuration"
+                          btnColor="default"
+                          class="btn-group"
+                          id="cluster-wide-actions"
+                          [tableActions]="clusterWideActions">
+        </cd-table-actions>
       </div>
 
       <cd-osd-details cdTableDetail
index 5163bb6b70850f23b0a131189f71f467c65ed562..b1113d7c06adc5cc3aba805a8130cdd6c18d6548 100644 (file)
@@ -37,7 +37,10 @@ describe('OsdListComponent', () => {
 
   const fakeAuthStorageService = {
     getPermissions: () => {
-      return new Permissions({ osd: ['read', 'update', 'create', 'delete'] });
+      return new Permissions({
+        'config-opt': ['read', 'update', 'create', 'delete'],
+        osd: ['read', 'update', 'create', 'delete']
+      });
     }
   };
 
@@ -92,7 +95,6 @@ describe('OsdListComponent', () => {
 
   beforeEach(() => {
     fixture = TestBed.createComponent(OsdListComponent);
-    fixture.detectChanges();
     component = fixture.componentInstance;
     osdService = TestBed.get(OsdService);
     modalServiceShowSpy = spyOn(TestBed.get(BsModalService), 'show').and.stub();
@@ -104,6 +106,7 @@ describe('OsdListComponent', () => {
   });
 
   it('should have columns that are sortable', () => {
+    fixture.detectChanges();
     expect(component.columns.every((column) => Boolean(column.prop))).toBeTruthy();
   });
 
@@ -170,6 +173,49 @@ describe('OsdListComponent', () => {
     });
   });
 
+  describe('show osd actions as defined', () => {
+    const getOsdActions = () => {
+      fixture.detectChanges();
+      return fixture.debugElement.query(By.css('#cluster-wide-actions')).componentInstance
+        .dropDownActions;
+    };
+
+    it('shows osd actions after osd-actions', () => {
+      fixture.detectChanges();
+      expect(fixture.debugElement.query(By.css('#cluster-wide-actions'))).toBe(
+        fixture.debugElement.queryAll(By.directive(TableActionsComponent))[1]
+      );
+    });
+
+    it('shows both osd actions', () => {
+      const osdActions = getOsdActions();
+      expect(osdActions).toEqual(component.clusterWideActions);
+      expect(osdActions.length).toBe(3);
+    });
+
+    it('shows only "Flags" action', () => {
+      component.permissions.configOpt.read = false;
+      const osdActions = getOsdActions();
+      expect(osdActions[0].name).toBe('Flags');
+      expect(osdActions.length).toBe(1);
+    });
+
+    it('shows only "Recovery Priority" action', () => {
+      component.permissions.osd.read = false;
+      const osdActions = getOsdActions();
+      expect(osdActions[0].name).toBe('Recovery Priority');
+      expect(osdActions[1].name).toBe('PG scrub');
+      expect(osdActions.length).toBe(2);
+    });
+
+    it('shows no osd actions', () => {
+      component.permissions.configOpt.read = false;
+      component.permissions.osd.read = false;
+      const osdActions = getOsdActions();
+      expect(osdActions).toEqual([]);
+    });
+  });
+
   describe('show table actions as defined', () => {
     let tableActions: TableActionsComponent;
     let scenario: { fn; empty; single };
@@ -177,7 +223,7 @@ describe('OsdListComponent', () => {
 
     const getTableActionComponent = () => {
       fixture.detectChanges();
-      return fixture.debugElement.query(By.directive(TableActionsComponent)).componentInstance;
+      return fixture.debugElement.query(By.css('#osd-actions')).componentInstance;
     };
 
     beforeEach(() => {
@@ -201,6 +247,10 @@ describe('OsdListComponent', () => {
   });
 
   describe('test table actions in submenu', () => {
+    beforeEach(() => {
+      fixture.detectChanges();
+    });
+
     beforeEach(fakeAsync(() => {
       // The menu needs a click to render the dropdown!
       const dropDownToggle = fixture.debugElement.query(By.css('.dropdown-toggle'));
index 15d685a3c69f61eb37a9140c1e5ad6f29ea78332..d4d5f2945a1d6795b33949b237d0dfe569748b6e 100644 (file)
@@ -47,7 +47,7 @@ export class OsdListComponent implements OnInit {
   tableActions: CdTableAction[];
   bsModalRef: BsModalRef;
   columns: CdTableColumn[];
-  advancedTableActions: any[];
+  clusterWideActions: CdTableAction[];
 
   osds = [];
   selection = new CdTableSelection();
@@ -148,29 +148,32 @@ export class OsdListComponent implements OnInit {
         icon: 'fa-remove'
       }
     ];
-    this.advancedTableActions = [
+  }
+
+  ngOnInit() {
+    this.clusterWideActions = [
       {
-        name: this.i18n('Cluster-wide Flags'),
+        name: this.i18n('Flags'),
         icon: 'fa-flag',
         click: () => this.configureFlagsAction(),
-        permission: this.permissions.osd.read
+        permission: 'read',
+        visible: () => this.permissions.osd.read
       },
       {
-        name: this.i18n('Cluster-wide Recovery Priority'),
+        name: this.i18n('Recovery Priority'),
         icon: 'fa-cog',
         click: () => this.configureQosParamsAction(),
-        permission: this.permissions.configOpt.read
+        permission: 'read',
+        visible: () => this.permissions.configOpt.read
       },
       {
         name: this.i18n('PG scrub'),
         icon: 'fa-stethoscope',
         click: () => this.configurePgScrubAction(),
-        permission: this.permissions.configOpt.read
+        permission: 'read',
+        visible: () => this.permissions.configOpt.read
       }
     ];
-  }
-
-  ngOnInit() {
     this.columns = [
       { prop: 'host.name', name: this.i18n('Host') },
       { prop: 'id', name: this.i18n('ID'), cellTransformation: CellTemplate.bold },
@@ -199,8 +202,6 @@ export class OsdListComponent implements OnInit {
         cellTransformation: CellTemplate.perSecond
       }
     ];
-
-    this.removeActionsWithNoPermissions();
   }
 
   get hasOsdSelected() {
@@ -336,16 +337,4 @@ export class OsdListComponent implements OnInit {
   configurePgScrubAction() {
     this.bsModalRef = this.modalService.show(OsdPgScrubModalComponent, { class: 'modal-lg' });
   }
-
-  /**
-   * Removes all actions from 'advancedTableActions' that need a permission the user doesn't have.
-   */
-  private removeActionsWithNoPermissions() {
-    if (!this.permissions) {
-      this.advancedTableActions = [];
-      return;
-    }
-
-    this.advancedTableActions = this.advancedTableActions.filter((action) => action.permission);
-  }
 }
index e0d6c209f923c5514a9c4151d0aa66d67acd4f12..57761eb1f8f28f16c7354669d4c5a0dcede59818 100644 (file)
@@ -2,7 +2,7 @@
      dropdown>
   <ng-container *ngIf="getCurrentButton() as action">
     <button type="button"
-            class="btn btn-sm btn-primary"
+            class="btn btn-sm btn-{{btnColor}}"
             [ngClass]="{'disabled': disableSelectionAction(action)}"
             (click)="useClickAction(action)"
             [routerLink]="useRouterLink(action)">
   <button type="button"
           dropdownToggle
           *ngIf="showDropDownActions()"
-          class="btn btn-sm btn-primary dropdown-toggle dropdown-toggle-split">
-    <ng-container *ngIf="onlyDropDown">{{ onlyDropDown }}</ng-container>
+          class="btn btn-sm btn-{{btnColor}} dropdown-toggle dropdown-toggle-split">
+    <ng-container *ngIf="dropDownOnly">{{ dropDownOnly }} </ng-container>
     <span class="caret"></span>
-    <span *ngIf="!onlyDropDown"
+    <span *ngIf="!dropDownOnly"
           class="sr-only"></span>
   </button>
   <ul *dropdownMenu
index 45fcf511841d6eb5c396c6971bc694a8f7c2d67a..c8d47c3115457baff95120ecdbed374339d3f40f 100644 (file)
@@ -20,7 +20,7 @@ describe('TableActionsComponent', () => {
   let scenario;
   let permissionHelper: PermissionHelper;
 
-  const setUpTableActions = () => {
+  const getTableActionComponent = (): TableActionsComponent => {
     component.tableActions = [
       addAction,
       editAction,
@@ -29,10 +29,6 @@ describe('TableActionsComponent', () => {
       copyAction,
       deleteAction
     ];
-  };
-
-  const getTableActionComponent = (): TableActionsComponent => {
-    setUpTableActions();
     component.ngOnInit();
     return component;
   };
@@ -236,7 +232,7 @@ describe('TableActionsComponent', () => {
       permissionHelper.testScenarios(scenario);
     });
 
-    it('should not get any button with no permissions', () => {
+    it('should not get any button with no permissions, except the true action', () => {
       hiddenScenario();
       permissionHelper.setPermissionsAndGetActions(0, 0, 0);
       permissionHelper.testScenarios(scenario);
@@ -244,7 +240,7 @@ describe('TableActionsComponent', () => {
 
     it('should not get any button if only a drop down should be shown', () => {
       hiddenScenario();
-      component.onlyDropDown = 'Drop down label';
+      component.dropDownOnly = 'Drop down label that is shown';
       permissionHelper.setPermissionsAndGetActions(1, 1, 1);
       permissionHelper.testScenarios(scenario);
     });
@@ -269,13 +265,56 @@ describe('TableActionsComponent', () => {
     });
   });
 
-  describe('with drop down only', () => {
-    beforeEach(() => {
-      component.onlyDropDown = 'displayMe';
+  describe('all visible actions with all different permissions', () => {
+    it('with create, update and delete', () => {
+      permissionHelper.setPermissionsAndGetActions(1, 1, 1);
+      expect(component.dropDownActions).toEqual([
+        addAction,
+        editAction,
+        unprotectAction,
+        copyAction,
+        deleteAction
+      ]);
     });
 
-    it('should not return any button with getCurrentButton', () => {
-      expect(component.getCurrentButton()).toBeFalsy();
+    it('with create and delete', () => {
+      permissionHelper.setPermissionsAndGetActions(1, 0, 1);
+      expect(component.dropDownActions).toEqual([addAction, copyAction, deleteAction]);
+    });
+
+    it('with create and update', () => {
+      permissionHelper.setPermissionsAndGetActions(1, 1, 0);
+      expect(component.dropDownActions).toEqual([
+        addAction,
+        editAction,
+        unprotectAction,
+        copyAction
+      ]);
+    });
+
+    it('with create', () => {
+      permissionHelper.setPermissionsAndGetActions(1, 0, 0);
+      expect(component.dropDownActions).toEqual([addAction, copyAction]);
+    });
+
+    it('with update and delete', () => {
+      permissionHelper.setPermissionsAndGetActions(0, 1, 1);
+      expect(component.dropDownActions).toEqual([editAction, unprotectAction, deleteAction]);
+    });
+
+    it('with update', () => {
+      permissionHelper.setPermissionsAndGetActions(0, 1, 0);
+      expect(component.dropDownActions).toEqual([editAction, unprotectAction]);
+    });
+
+    it('with delete', () => {
+      permissionHelper.setPermissionsAndGetActions(0, 0, 1);
+      expect(component.dropDownActions).toEqual([deleteAction]);
+    });
+
+    it('without any', () => {
+      permissionHelper.setPermissionsAndGetActions(0, 0, 0);
+      expect(component.dropDownActions).toEqual([]);
     });
   });
 
index 62f33acac186621c976a35fae134054c25491df3..034092862a33c284d0683937cfb197e2760e5612 100644 (file)
@@ -18,12 +18,14 @@ export class TableActionsComponent implements OnInit {
   selection: CdTableSelection;
   @Input()
   tableActions: CdTableAction[];
+  @Input()
+  btnColor = 'primary';
 
   // Use this if you just want to display a drop down button,
   // labeled with the given text, with all actions in it.
   // This disables the main action button.
   @Input()
-  onlyDropDown?: string;
+  dropDownOnly?: string;
 
   // Array with all visible actions
   dropDownActions: CdTableAction[] = [];
@@ -74,7 +76,7 @@ export class TableActionsComponent implements OnInit {
    * @returns {CdTableAction}
    */
   getCurrentButton(): CdTableAction {
-    if (this.onlyDropDown) {
+    if (this.dropDownOnly) {
       return;
     }
     let buttonAction = this.dropDownActions.find((tableAction) => this.showableAction(tableAction));
@@ -115,11 +117,11 @@ export class TableActionsComponent implements OnInit {
    * @returns {Boolean}
    */
   disableSelectionAction(action: CdTableAction): Boolean {
-    const permission = action.permission;
     const disable = action.disable;
     if (disable) {
       return Boolean(disable(this.selection));
     }
+    const permission = action.permission;
     const selected = this.selection.hasSingleSelection && this.selection.first();
     return Boolean(
       ['update', 'delete'].includes(permission) && (!selected || selected.cdExecuting)
index 2e14f32978453ba526a0bc03b04810c06748f9a9..1a267838cc4255a8b13232c9c4c233e42ddc4e03 100644 (file)
@@ -141,9 +141,6 @@ fieldset[disabled] .btn-primary.active {
   color: $color-primary;
   background-color: $color-button-badge;
 }
-.btn-primary .caret {
-  color: $color-button-caret;
-}
 .btn-default {
   border-radius: $button-radius;
 }
@@ -244,10 +241,6 @@ button.btn.btn-label > i.fa {
   padding-left: 30px;
   padding-right: 30px;
 }
-/* Caret */
-.caret {
-  color: $color-caret-text;
-}
 /* Progressbar */
 .progress-bar {
   background-image: none !important;