]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard_v2: Add toggle able columns 20806/head
authorStephan Müller <smueller@suse.com>
Mon, 5 Mar 2018 15:04:04 +0000 (16:04 +0100)
committerStephan Müller <smueller@suse.com>
Fri, 9 Mar 2018 13:23:19 +0000 (14:23 +0100)
It's now possible to toggle columns on and off in the data table.

Signed-off-by: Stephan Müller <smueller@suse.com>
17 files changed:
src/pybind/mgr/dashboard_v2/frontend/src/app/app.module.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/mirroring/mirroring.component.spec.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/pool-detail/pool-detail.component.spec.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cephfs/cephfs/cephfs.component.spec.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cephfs/clients/clients.component.spec.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cluster/hosts/hosts.component.spec.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/performance-counter/performance-counter/performance-counter.component.spec.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/performance-counter/services/table-performance-counter.service.spec.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/performance-counter/table-performance-counter/table-performance-counter.component.spec.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.spec.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.spec.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation.module.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/datatable/datatable.module.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/datatable/table/table.component.html
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/datatable/table/table.component.spec.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/datatable/table/table.component.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/models/cd-table-column.ts

index e1ac1f7198fd80624d352912f394911f6e54cfea..525e94729fad71167ac146ea3cbefe1a1d63d897 100644 (file)
@@ -5,7 +5,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 
 import { ToastModule, ToastOptions } from 'ng2-toastr/ng2-toastr';
 
-import { AccordionModule, TabsModule } from 'ngx-bootstrap';
+import { AccordionModule, BsDropdownModule, TabsModule } from 'ngx-bootstrap';
 import { AppRoutingModule } from './app-routing.module';
 import { AppComponent } from './app.component';
 import { CephModule } from './ceph/ceph.module';
@@ -35,6 +35,7 @@ export class CustomOption extends ToastOptions {
     SharedModule,
     CephModule,
     AccordionModule.forRoot(),
+    BsDropdownModule.forRoot(),
     TabsModule.forRoot(),
     HttpClientModule,
     BrowserAnimationsModule
index accc5641819503cf93782b0605716fdbaa199a2d..f20d0484c018d6d459aaa70bf0d6edeb58c587d1 100644 (file)
@@ -1,13 +1,12 @@
 import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 
+import { BsDropdownModule, TabsModule } from 'ngx-bootstrap';
 import { ProgressbarModule } from 'ngx-bootstrap/progressbar';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 import { Observable } from 'rxjs/Observable';
 
 import { RbdMirroringService } from '../../../shared/services/rbd-mirroring.service';
 import { SharedModule } from '../../../shared/shared.module';
-import { BlockModule } from '../block.module';
 import { MirrorHealthColorPipe } from '../mirror-health-color.pipe';
 import { MirroringComponent } from './mirroring.component';
 
@@ -29,6 +28,7 @@ describe('MirroringComponent', () => {
         declarations: [MirroringComponent, MirrorHealthColorPipe],
         imports: [
           SharedModule,
+          BsDropdownModule.forRoot(),
           TabsModule.forRoot(),
           ProgressbarModule.forRoot(),
           HttpClientTestingModule
index 6a208af532691e3d9028f80b664d1a6e9379ee95..aea790cf1da799f542e2b9a0f9342c21ae9da994 100644 (file)
@@ -2,7 +2,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 import { RouterTestingModule } from '@angular/router/testing';
 
-import { AlertModule, TabsModule } from 'ngx-bootstrap';
+import { AlertModule, BsDropdownModule, TabsModule } from 'ngx-bootstrap';
 
 import { ComponentsModule } from '../../../shared/components/components.module';
 import { SharedModule } from '../../../shared/shared.module';
@@ -16,6 +16,7 @@ describe('PoolDetailComponent', () => {
     TestBed.configureTestingModule({
       imports: [
         SharedModule,
+        BsDropdownModule.forRoot(),
         TabsModule.forRoot(),
         AlertModule.forRoot(),
         ComponentsModule,
index 2a92a5dcf20270453e0284db5421824469016468..03e5b1ba3bc4ddabcd2161ecbbeeee7c381c2a10 100644 (file)
@@ -2,7 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 import { RouterTestingModule } from '@angular/router/testing';
 
 import { ChartsModule } from 'ng2-charts/ng2-charts';
-import { ProgressbarModule } from 'ngx-bootstrap/progressbar';
+import { BsDropdownModule, ProgressbarModule } from 'ngx-bootstrap';
 import { Observable } from 'rxjs/Observable';
 
 import { SharedModule } from '../../../shared/shared.module';
@@ -29,7 +29,13 @@ describe('CephfsComponent', () => {
   beforeEach(
     async(() => {
       TestBed.configureTestingModule({
-        imports: [SharedModule, ChartsModule, RouterTestingModule, ProgressbarModule.forRoot()],
+        imports: [
+          SharedModule,
+          ChartsModule,
+          RouterTestingModule,
+          BsDropdownModule.forRoot(),
+          ProgressbarModule.forRoot()
+        ],
         declarations: [CephfsComponent],
         providers: [
           { provide: CephfsService, useValue: fakeFilesystemService }
index e07914b9f818e30d2e0fed5f099f71672deca771..d3506a9068610fdea914329372457a758065f590 100644 (file)
@@ -1,6 +1,7 @@
 import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 import { RouterTestingModule } from '@angular/router/testing';
 
+import { BsDropdownModule } from 'ngx-bootstrap';
 import { Observable } from 'rxjs/Observable';
 
 import { SharedModule } from '../../../shared/shared.module';
@@ -27,7 +28,11 @@ describe('ClientsComponent', () => {
   beforeEach(
     async(() => {
       TestBed.configureTestingModule({
-        imports: [RouterTestingModule, SharedModule],
+        imports: [
+          RouterTestingModule,
+          BsDropdownModule.forRoot(),
+          SharedModule
+        ],
         declarations: [ClientsComponent],
         providers: [{ provide: CephfsService, useValue: fakeFilesystemService }]
       }).compileComponents();
index 6bf9a2d763b95d4d826025ade81ec346c4d996f3..90eb5e6498baa326b21c7a9f77ccc6ac753c1349 100644 (file)
@@ -2,6 +2,8 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 import { RouterTestingModule } from '@angular/router/testing';
 
+import { BsDropdownModule } from 'ngx-bootstrap';
+
 import { ComponentsModule } from '../../../shared/components/components.module';
 import { SharedModule } from '../../../shared/shared.module';
 import { HostsComponent } from './hosts.component';
@@ -16,6 +18,7 @@ describe('HostsComponent', () => {
         SharedModule,
         HttpClientTestingModule,
         ComponentsModule,
+        BsDropdownModule.forRoot(),
         RouterTestingModule
       ],
       declarations: [
index f2e2daeeef051d8cb6dca4f09844c0a664594711..a4cc717023179ee9014230a75f6d7d0194d3cfb1 100644 (file)
@@ -1,6 +1,8 @@
 import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 import { RouterTestingModule } from '@angular/router/testing';
 
+import { BsDropdownModule } from 'ngx-bootstrap';
+
 import { PerformanceCounterModule } from '../performance-counter.module';
 import { TablePerformanceCounterService } from '../services/table-performance-counter.service';
 import { PerformanceCounterComponent } from './performance-counter.component';
@@ -25,7 +27,11 @@ describe('PerformanceCounterComponent', () => {
   beforeEach(
     async(() => {
       TestBed.configureTestingModule({
-        imports: [PerformanceCounterModule, RouterTestingModule],
+        imports: [
+          PerformanceCounterModule,
+          BsDropdownModule.forRoot(),
+          RouterTestingModule
+        ],
         providers: [{ provide: TablePerformanceCounterService, useValue: fakeService }]
       }).compileComponents();
     })
index 37350f76b242bbccc205c4407aecc24f04fb4551..6f0af94e4b603e66a9560a0679882cd4a1258e31 100644 (file)
@@ -2,13 +2,19 @@ import { HttpClientModule } from '@angular/common/http';
 import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { inject, TestBed } from '@angular/core/testing';
 
+import { BsDropdownModule } from 'ngx-bootstrap';
+
 import { TablePerformanceCounterService } from './table-performance-counter.service';
 
 describe('TablePerformanceCounterService', () => {
   beforeEach(() => {
     TestBed.configureTestingModule({
       providers: [TablePerformanceCounterService],
-      imports: [HttpClientTestingModule, HttpClientModule]
+      imports: [
+        HttpClientTestingModule,
+        BsDropdownModule.forRoot(),
+        HttpClientModule
+      ]
     });
   });
 
index dae55c6880eb6a25011a0b4bb43a2208ae10975f..4baefe8911daf857a069294cc38ef4d6faccce6e 100644 (file)
@@ -2,6 +2,8 @@ import { HttpClientModule } from '@angular/common/http';
 import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 
+import { BsDropdownModule } from 'ngx-bootstrap';
+
 import { SharedModule } from '../../../shared/shared.module';
 import { TablePerformanceCounterService } from '../services/table-performance-counter.service';
 import { TablePerformanceCounterComponent } from './table-performance-counter.component';
@@ -16,6 +18,7 @@ describe('TablePerformanceCounterComponent', () => {
       imports: [
         HttpClientTestingModule,
         HttpClientModule,
+        BsDropdownModule.forRoot(),
         SharedModule
       ],
       providers: [ TablePerformanceCounterService ]
index d48f1cce51f1e73f1f801dfaefd1587e46830743..ca2d7e6089f56ae98bdc7431ce438b63b41f761d 100644 (file)
@@ -2,7 +2,7 @@ import { HttpClientModule } from '@angular/common/http';
 import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 
-import { TabsModule } from 'ngx-bootstrap/tabs';
+import { BsDropdownModule, TabsModule } from 'ngx-bootstrap';
 
 import { SharedModule } from '../../../shared/shared.module';
 import { PerformanceCounterModule } from '../../performance-counter/performance-counter.module';
@@ -21,6 +21,7 @@ describe('RgwDaemonDetailsComponent', () => {
         PerformanceCounterModule,
         HttpClientTestingModule,
         HttpClientModule,
+        BsDropdownModule.forRoot(),
         TabsModule.forRoot()
       ],
       providers: [ RgwDaemonService ]
index 5deca8c72e243481ffaf24aed223215b456ad49c..d3c5a11a684444e5c6223a0bacdf32f478c1ba9c 100644 (file)
@@ -2,6 +2,8 @@ import { HttpClientModule } from '@angular/common/http';
 import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 
+import { BsDropdownModule } from 'ngx-bootstrap';
+
 import { DataTableModule } from '../../../shared/datatable/datatable.module';
 import { RgwDaemonService } from '../services/rgw-daemon.service';
 import { RgwDaemonListComponent } from './rgw-daemon-list.component';
@@ -16,6 +18,7 @@ describe('RgwDaemonListComponent', () => {
       imports: [
         DataTableModule,
         HttpClientTestingModule,
+        BsDropdownModule.forRoot(),
         HttpClientModule
       ],
       providers: [ RgwDaemonService ]
index 823d4feea18492767193911210e91d708490288b..6ec240de965e5adbaa131b45d95a691797f7b328 100644 (file)
@@ -13,7 +13,7 @@ import { NavigationComponent } from './navigation/navigation.component';
   imports: [
     CommonModule,
     AuthModule,
-    BsDropdownModule.forRoot(),
+    BsDropdownModule,
     AppRoutingModule,
     SharedModule,
     RouterModule
index df7ca994dad453d04c94dcb2f4b4396d7db43eb8..19d1a45605a6861d4d2f78f2b5bf58de91c50200 100644 (file)
@@ -5,6 +5,7 @@ import { RouterModule } from '@angular/router';
 
 import { NgxDatatableModule } from '@swimlane/ngx-datatable';
 
+import { BsDropdownModule } from 'ngx-bootstrap';
 import { ComponentsModule } from '../components/components.module';
 import { PipesModule } from '../pipes/pipes.module';
 import { TableDetailsDirective } from './table-details.directive';
@@ -16,6 +17,7 @@ import { TableComponent } from './table/table.component';
     CommonModule,
     NgxDatatableModule,
     FormsModule,
+    BsDropdownModule,
     PipesModule,
     ComponentsModule,
     RouterModule
index 7ae84f98ca844db7ae1272c13eacb21a0e62b172..d04378a031f3ee232ffb68e5e09dbca74032d3e5 100644 (file)
     </div>
     <!-- end pagination limit-->
 
+    <!-- show hide columns -->
+    <div class="widget-toolbar">
+      <div dropdown
+           class="dropdown tc_menuitem tc_menuitem_cluster">
+        <a dropdownToggle
+           class="btn btn-sm btn-default dropdown-toggle tc_columnBtn"
+           data-toggle="dropdown">
+          <i class="fa fa-lg fa-table"></i>
+        </a>
+        <ul *dropdownMenu
+            class="dropdown-menu">
+          <li *ngFor="let column of columns">
+            <label>
+              <input type="checkbox"
+                     (change)="toggleColumn($event)"
+                     [name]="column.prop"
+                     [checked]="!column.isHidden">
+              <span>{{ column.name }}</span>
+            </label>
+          </li>
+        </ul>
+      </div>
+    </div>
+    <!-- end show hide columns -->
+
     <!-- refresh button -->
     <div class="widget-toolbar tc_refreshBtn">
       <a (click)="refreshBtn()">
@@ -54,7 +79,7 @@
                  [selected]="selection.selected"
                  (select)="onSelect()"
                  [sorts]="sorts"
-                 [columns]="columns"
+                 [columns]="tableColumns"
                  [columnMode]="columnMode"
                  [rows]="rows"
                  [rowClass]="getRowClass()"
index 43c9ce1a7aa9bf7ba737bb18ee9026ee95017efe..60ec7d013279b52e515d3814167005050ab66dc8 100644 (file)
@@ -87,4 +87,59 @@ describe('TableComponent', () => {
     component.updateFilter();
     expect(component.rows.length).toBe(100);
   });
+
+  describe('after ngInit', () => {
+    const toggleColumn = (prop, checked) => {
+      component.toggleColumn({
+        target: {
+          name: prop,
+          checked: checked
+        }
+      });
+    };
+
+    beforeEach(() => {
+      component.ngOnInit();
+      component.table.sorts = component.sorts;
+    });
+
+    it('should have updated the column definitions', () => {
+      expect(component.columns[0].flexGrow).toBe(1);
+      expect(component.columns[1].flexGrow).toBe(2);
+      expect(component.columns[2].flexGrow).toBe(2);
+      expect(component.columns[2].resizeable).toBe(false);
+    });
+
+    it('should have table columns', () => {
+      expect(component.tableColumns.length).toBe(3);
+      expect(component.tableColumns).toEqual(component.columns);
+    });
+
+    it('should have a unique identifier which is search for', () => {
+      expect(component.identifier).toBe('a');
+      expect(component.sorts[0].prop).toBe('a');
+      expect(component.sorts).toEqual(component.createSortingDefinition('a'));
+    });
+
+    it('should remove column "a"', () => {
+      toggleColumn('a', false);
+      expect(component.table.sorts[0].prop).toBe('b');
+      expect(component.tableColumns.length).toBe(2);
+    });
+
+    it('should not be able to remove all columns', () => {
+      toggleColumn('a', false);
+      toggleColumn('b', false);
+      toggleColumn('c', false);
+      expect(component.table.sorts[0].prop).toBe('c');
+      expect(component.tableColumns.length).toBe(1);
+    });
+
+    it('should enable column "a" again', () => {
+      toggleColumn('a', false);
+      toggleColumn('a', true);
+      expect(component.table.sorts[0].prop).toBe('b');
+      expect(component.tableColumns.length).toBe(3);
+    });
+  });
 });
index 870ed29633d8ff2dd80e6a6e8f2009f37df0d978..7ba073aea31b6b4259ec4ea3f39578bb8ede562a 100644 (file)
@@ -12,8 +12,12 @@ import {
   Type,
   ViewChild
 } from '@angular/core';
-
-import { DatatableComponent, SortDirection, SortPropDir } from '@swimlane/ngx-datatable';
+import {
+  DatatableComponent,
+  SortDirection,
+  SortPropDir,
+  TableColumnProp
+} from '@swimlane/ngx-datatable';
 
 import * as _ from 'lodash';
 import 'rxjs/add/observable/timer';
@@ -43,7 +47,7 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
   // Each item -> { prop: 'attribute name', dir: 'asc'||'desc'}
   @Input() sorts?: SortPropDir[];
   // Method used for setting column widths.
-  @Input() columnMode ?= 'force';
+  @Input() columnMode ?= 'flex';
   // Name of the component e.g. 'TableDetailsComponent'
   @Input() detailsComponent?: string;
   // Display the tool header, including reload button, pagination and search fields?
@@ -85,6 +89,7 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
    */
   selection = new CdTableSelection();
 
+  tableColumns: CdTableColumn[];
   cellTemplates: {
     [key: string]: TemplateRef<any>
   } = {};
@@ -109,26 +114,28 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
 
   ngOnInit() {
     this._addTemplates();
-    this.columns.map((column) => {
-      if (column.cellTransformation) {
-        column.cellTemplate = this.cellTemplates[column.cellTransformation];
+    if (!this.sorts) {
+      this.identifier = this.columns.some(c => c.prop === this.identifier) ?
+        this.identifier :
+        this.columns[0].prop + '';
+      this.sorts = this.createSortingDefinition(this.identifier);
+    }
+    this.columns.map(c => {
+      if (c.cellTransformation) {
+        c.cellTemplate = this.cellTemplates[c.cellTransformation];
       }
-      return column;
+      if (!c.flexGrow) {
+        c.flexGrow = c.prop + '' === this.identifier ? 1 : 2;
+      }
+      if (!c.resizeable) {
+        c.resizeable = false;
+      }
+      return c;
     });
     if (this.detailsComponent) {
       this.selectionType = 'multi';
     }
-    if (!this.sorts) {
-      const sortProp = this.columns.some((c) => c.prop === this.identifier) ?
-        this.identifier :
-        this.columns[0].prop;
-      this.sorts = [
-        {
-          prop: sortProp,
-          dir: SortDirection.asc
-        }
-      ];
-    }
+    this.tableColumns = this.columns.filter(c => !c.isHidden);
     if (this.autoReload) { // Also if nothing is bound to fetchData nothing will be triggered
       // Force showing the loading indicator because it has been set to False in
       // useData() when this method was triggered by ngOnChanges().
@@ -222,6 +229,35 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
     }
   }
 
+  toggleColumn($event: any) {
+    const prop: TableColumnProp = $event.target.name;
+    const hide = !$event.target.checked;
+    if (hide && this.tableColumns.length === 1) {
+      $event.target.checked = true;
+      return;
+    }
+    _.find(this.columns, (c: CdTableColumn) => c.prop === prop).isHidden = hide;
+    this.updateColumns();
+  }
+
+  updateColumns () {
+    this.tableColumns = this.columns.filter(c => !c.isHidden);
+    const sortProp = this.table.sorts[0].prop;
+    if (!_.find(this.tableColumns, (c: CdTableColumn) => c.prop === sortProp)) {
+      this.table.onColumnSort({sorts: this.createSortingDefinition(this.tableColumns[0].prop)});
+    }
+    this.table.recalculate();
+  }
+
+  createSortingDefinition (prop: TableColumnProp): SortPropDir[] {
+    return [
+      {
+        prop: prop,
+        dir: SortDirection.asc
+      }
+    ];
+  }
+
   updateDetailView() {
     if (!this.detailsComponent) {
       return;
index 9f29edd1b0f5db4c62e62d8792803600b1564c45..bf45c48187485d0646c31f4d26a8fbdcfddf4f47 100644 (file)
@@ -3,4 +3,5 @@ import { CellTemplate } from '../enum/cell-template.enum';
 
 export interface CdTableColumn extends TableColumn {
   cellTransformation?: CellTemplate;
+  isHidden?: boolean;
 }