]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Provide values with different style in KV-table
authorStephan Müller <smueller@suse.com>
Fri, 11 Jan 2019 11:53:34 +0000 (12:53 +0100)
committerStephan Müller <smueller@suse.com>
Wed, 30 Jan 2019 15:42:57 +0000 (16:42 +0100)
Now it's possible to style values inside the KV-table based on the
values.

Fixes: https://tracker.ceph.com/issues/37951
Signed-off-by: Stephan Müller <smueller@suse.com>
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-key-value/table-key-value.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-key-value/table-key-value.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-key-value/table-key-value.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/enum/cell-template.enum.ts

index 707b7990c406c63e0df71d1ec35a7e3b58048c39..2e2dda4e94148e2b70267d2231d95bced940e671 100644 (file)
@@ -4,6 +4,7 @@
           columnMode="flex"
           [toolHeader]="false"
           [autoReload]="autoReload"
+          [customCss]="customCss"
           [autoSave]="false"
           [header]="false"
           [footer]="false"
index f697b2c1b5368c3c5b4fc1810b0baf84d6d2d4fe..ee7a10c67cc21f37c41c3d7c70482561ea4274dd 100644 (file)
@@ -6,6 +6,8 @@ import { NgxDatatableModule } from '@swimlane/ngx-datatable';
 
 import { configureTestBed } from '../../../../testing/unit-test-helper';
 import { ComponentsModule } from '../../components/components.module';
+import { CellTemplate } from '../../enum/cell-template.enum';
+import { CdTableColumn } from '../../models/cd-table-column';
 import { TableComponent } from '../table/table.component';
 import { TableKeyValueComponent } from './table-key-value.component';
 
@@ -24,6 +26,7 @@ describe('TableKeyValueComponent', () => {
   });
 
   it('should create', () => {
+    fixture.detectChanges();
     expect(component).toBeTruthy();
   });
 
@@ -313,4 +316,36 @@ describe('TableKeyValueComponent', () => {
       ]);
     });
   });
+
+  describe('columns set up', () => {
+    let columns: CdTableColumn[];
+
+    beforeEach(() => {
+      columns = [
+        {
+          prop: 'key',
+          flexGrow: 1,
+          cellTransformation: CellTemplate.bold
+        },
+        {
+          prop: 'value',
+          flexGrow: 3
+        }
+      ];
+    });
+
+    it('should have the following default column set up', () => {
+      component.ngOnInit();
+      expect(component.columns).toEqual(columns);
+    });
+
+    it('should have the following column set up if customCss is defined', () => {
+      component.customCss = {
+        'answer-of-everything': 42
+      };
+      component.ngOnInit();
+      columns[1].cellTransformation = CellTemplate.classAdding;
+      expect(component.columns).toEqual(columns);
+    });
+  });
 });
index 3bc9d2a1281af4cc54ba56457601d052f33a3cdf..ae110a7e77da09933c7add82ee40d325f3811695 100644 (file)
@@ -48,6 +48,10 @@ export class TableKeyValueComponent implements OnInit, OnChanges {
   @Input()
   hideEmpty = false;
 
+  // If set, the classAddingTpl is used to enable different css for different values
+  @Input()
+  customCss?: { [css: string]: number | string | ((any) => boolean) };
+
   columns: Array<CdTableColumn> = [];
   tableData: Item[];
 
@@ -71,6 +75,9 @@ export class TableKeyValueComponent implements OnInit, OnChanges {
         flexGrow: 3
       }
     ];
+    if (this.customCss) {
+      this.columns[1].cellTransformation = CellTemplate.classAdding;
+    }
     // We need to subscribe the 'fetchData' event here and not in the
     // HTML template, otherwise the data table will display the loading
     // indicator infinitely if data is only bound via '[data]="xyz"'.
index 7191e8aff3d0bf7a268910834f275d8e136b50a8..637529ee5d7704c94694ff37254013f1ffddaee9 100644 (file)
      [hidden]="!value"></i>
 </ng-template>
 
-
 <ng-template #perSecondTpl
              let-row="row"
              let-value="value">
   <span *ngIf="row.cdExecuting"
         class="text-muted italic">({{ row.cdExecuting }}... )</span>
 </ng-template>
+
+<ng-template #classAddingTpl
+             let-value="value">
+  <span class="{{useCustomClass(value)}}">{{ value }}</span>
+</ng-template>
index ce40b03b3561679598e8989c0fd53e3ebc3eab9a..9b580de37c7697580ad82cef437340aaced3c27d 100644 (file)
@@ -328,4 +328,44 @@ describe('TableComponent', () => {
       clearLocalStorage();
     });
   });
+
+  describe('useCustomClass', () => {
+    beforeEach(() => {
+      component.customCss = {
+        'label label-danger': 'active',
+        'secret secret-number': 123.456,
+        'btn btn-sm': (v) => _.isString(v) && v.startsWith('http'),
+        secure: (v) => _.isString(v) && v.startsWith('https')
+      };
+    });
+
+    const expectUseCustomClass = (values: any[], expectation: string) => {
+      values.forEach((value) => expect(component.useCustomClass(value)).toBe(expectation));
+    };
+
+    it('should throw an error if custom classes are not set', () => {
+      component.customCss = undefined;
+      expect(() => component.useCustomClass('active')).toThrowError('Custom classes are not set!');
+    });
+
+    it('should not return any class', () => {
+      expectUseCustomClass(['', 'something', 123, { complex: 1 }, [1, 2, 3]], undefined);
+    });
+
+    it('should match a string and return the corresponding class', () => {
+      expect(component.useCustomClass('active')).toBe('label label-danger');
+    });
+
+    it('should match a number and return the corresponding class', () => {
+      expect(component.useCustomClass(123.456)).toBe('secret secret-number');
+    });
+
+    it('should match against a function and return the corresponding class', () => {
+      expect(component.useCustomClass('http://no.ssl')).toBe('btn btn-sm');
+    });
+
+    it('should match against multiple functions and return the corresponding classes', () => {
+      expect(component.useCustomClass('https://secure.it')).toBe('btn btn-sm secure');
+    });
+  });
 });
index bf577adf1545fb10203a2ca08c0b94ba75089d5c..655fd2c91d73a60a6f63f48bed1f6b3966832d6b 100644 (file)
@@ -47,6 +47,8 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
   perSecondTpl: TemplateRef<any>;
   @ViewChild('executingTpl')
   executingTpl: TemplateRef<any>;
+  @ViewChild('classAddingTpl')
+  classAddingTpl: TemplateRef<any>;
 
   // This is the array with the items to be shown.
   @Input()
@@ -100,6 +102,10 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
   @Input()
   autoSave = true;
 
+  // Only needed to set if the classAddingTpl is used
+  @Input()
+  customCss?: { [css: string]: number | string | ((any) => boolean) };
+
   /**
    * Should be a function to update the input data if undefined nothing will be triggered
    *
@@ -311,6 +317,19 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
     this.cellTemplates.routerLink = this.routerLinkTpl;
     this.cellTemplates.perSecond = this.perSecondTpl;
     this.cellTemplates.executing = this.executingTpl;
+    this.cellTemplates.classAdding = this.classAddingTpl;
+  }
+
+  useCustomClass(value: any): string {
+    if (!this.customCss) {
+      throw new Error('Custom classes are not set!');
+    }
+    const classes = Object.keys(this.customCss);
+    const css = Object.values(this.customCss)
+      .map((v, i) => ((_.isFunction(v) && v(value)) || v === value) && classes[i])
+      .filter((x) => x)
+      .join(' ');
+    return (!_.isEmpty(css) && css) || undefined;
   }
 
   ngOnChanges(changes) {
index 28740a53674de33a7f61eebc941b48b4e465ffbb..cf3e11a7ecfb9db7cdb20bcb4c4c3e7b2b678fe1 100644 (file)
@@ -4,5 +4,6 @@ export enum CellTemplate {
   perSecond = 'perSecond',
   checkIcon = 'checkIcon',
   routerLink = 'routerLink',
-  executing = 'executing'
+  executing = 'executing',
+  classAdding = 'classAdding'
 }