Merge pull request #20811 from tspmelo/fix-table-details

mgr/dashboard_v2: fix and improve table details

Reviewed-by: Stephan Müller <smueller@suse.com>
Reviewed-by: Ricardo Marques <rimarques@suse.com>
Reviewed-by: Volker Theile <vtheile@suse.com>
This commit is contained in:
Lenz Grimmer 2018-03-13 13:03:00 +01:00 committed by GitHub
commit 6aaa4a9bc4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 111 additions and 127 deletions

View File

@ -3,7 +3,8 @@ import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { TabsModule } from 'ngx-bootstrap';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { ComponentsModule } from '../../shared/components/components.module';
import { SharedModule } from '../../shared/shared.module';
import { PerformanceCounterModule } from '../performance-counter/performance-counter.module';
@ -26,7 +27,7 @@ import { OsdService } from './osd/osd.service';
CommonModule,
PerformanceCounterModule,
ComponentsModule,
TabsModule,
TabsModule.forRoot(),
SharedModule,
RouterModule,
FormsModule

View File

@ -1,4 +1,4 @@
<tabset>
<tabset *ngIf="selection.hasSingleSelection">
<tab heading="Attributes (OSD map)">
<cd-table-key-value *ngIf="osd.loaded"
[data]="osd.details.osd_map">

View File

@ -4,6 +4,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TabsModule } from 'ngx-bootstrap';
import { DataTableModule } from '../../../../shared/datatable/datatable.module';
import { CdTableSelection } from '../../../../shared/models/cd-table-selection';
import { PerformanceCounterModule } from '../../../performance-counter/performance-counter.module';
import {
OsdPerformanceHistogramComponent
@ -35,6 +36,9 @@ describe('OsdDetailsComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(OsdDetailsComponent);
component = fixture.componentInstance;
component.selection = new CdTableSelection();
fixture.detectChanges();
});

View File

@ -1,7 +1,8 @@
import { Component, Input, OnInit } from '@angular/core';
import { Component, Input, OnChanges } from '@angular/core';
import * as _ from 'lodash';
import { CdTableSelection } from '../../../../shared/models/cd-table-selection';
import { OsdService } from '../osd.service';
@Component({
@ -9,19 +10,19 @@ import { OsdService } from '../osd.service';
templateUrl: './osd-details.component.html',
styleUrls: ['./osd-details.component.scss']
})
export class OsdDetailsComponent implements OnInit {
osd: any;
export class OsdDetailsComponent implements OnChanges {
@Input() selection: CdTableSelection;
@Input() selected?: any[] = [];
osd: any;
constructor(private osdService: OsdService) {}
ngOnInit() {
ngOnChanges() {
this.osd = {
loaded: false
};
if (this.selected.length > 0) {
this.osd = this.selected[0];
if (this.selection.hasSelection) {
this.osd = this.selection.first();
this.osd.autoRefresh = () => {
this.refresh();
};

View File

@ -7,15 +7,18 @@
<cd-table [data]="osds"
(fetchData)="getOsdList()"
[columns]="columns"
[detailsComponent]="detailsComponent"
[beforeShowDetails]="beforeShowDetails">
selectionType="single"
(updateSelection)="updateSelection($event)">
<cd-osd-details cdTableDetail
[selection]="selection">
</cd-osd-details>
</cd-table>
<ng-template #statusColor
let-value="value">
<span *ngFor="let state of value; last as last">
<span [class.text-success]="'up' === state || 'in' === state"
[class.text-warning]="'down' === state || 'out' === state"
>
[class.text-warning]="'down' === state || 'out' === state">
{{ state }}</span><span *ngIf="!last">, </span>
<!-- Has to be on the same line to prevent a space between state and comma. -->
</span>

View File

@ -1,7 +1,7 @@
import { HttpClientModule } from '@angular/common/http';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TabsModule } from 'ngx-bootstrap';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { DataTableModule } from '../../../../shared/datatable/datatable.module';
import { DimlessPipe } from '../../../../shared/pipes/dimless.pipe';
@ -23,7 +23,7 @@ describe('OsdListComponent', () => {
imports: [
HttpClientModule,
PerformanceCounterModule,
TabsModule,
TabsModule.forRoot(),
DataTableModule
],
declarations: [

View File

@ -14,9 +14,10 @@ import { OsdService } from '../osd.service';
export class OsdListComponent implements OnInit {
@ViewChild('statusColor') statusColor: TemplateRef<any>;
osds = [];
detailsComponent = 'OsdDetailsComponent';
columns: CdTableColumn[];
selection = new CdTableSelection();
constructor(
private osdService: OsdService,
@ -45,6 +46,10 @@ export class OsdListComponent implements OnInit {
];
}
updateSelection(selection: CdTableSelection) {
this.selection = selection;
}
getOsdList() {
this.osdService.getList().subscribe((data: any[]) => {
this.osds = data;

View File

@ -1,4 +1,4 @@
<tabset>
<tabset *ngIf="selection.hasSingleSelection">
<tab i18n-heading
heading="Details">
<cd-table-key-value [data]="metadata"

View File

@ -1,9 +1,8 @@
import { HttpClientModule } from '@angular/common/http';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { BsDropdownModule, TabsModule } from 'ngx-bootstrap';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { CdTableSelection } from '../../../shared/models/cd-table-selection';
import { SharedModule } from '../../../shared/shared.module';
import { PerformanceCounterModule } from '../../performance-counter/performance-counter.module';
import { RgwDaemonService } from '../services/rgw-daemon.service';
@ -13,25 +12,32 @@ describe('RgwDaemonDetailsComponent', () => {
let component: RgwDaemonDetailsComponent;
let fixture: ComponentFixture<RgwDaemonDetailsComponent>;
const fakeService = {
get: (id: string) => {
return new Promise(function(resolve, reject) {
return [];
});
}
};
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ RgwDaemonDetailsComponent ],
imports: [
SharedModule,
PerformanceCounterModule,
HttpClientTestingModule,
HttpClientModule,
BsDropdownModule.forRoot(),
TabsModule.forRoot()
],
providers: [ RgwDaemonService ]
})
.compileComponents();
providers: [{ provide: RgwDaemonService, useValue: fakeService }]
});
}));
beforeEach(() => {
fixture = TestBed.createComponent(RgwDaemonDetailsComponent);
component = fixture.componentInstance;
component.selection = new CdTableSelection();
fixture.detectChanges();
});

View File

@ -1,7 +1,8 @@
import { Component, Input, OnInit } from '@angular/core';
import { Component, Input, OnChanges } from '@angular/core';
import * as _ from 'lodash';
import { CdTableSelection } from '../../../shared/models/cd-table-selection';
import { RgwDaemonService } from '../services/rgw-daemon.service';
@Component({
@ -9,19 +10,18 @@ import { RgwDaemonService } from '../services/rgw-daemon.service';
templateUrl: './rgw-daemon-details.component.html',
styleUrls: ['./rgw-daemon-details.component.scss']
})
export class RgwDaemonDetailsComponent implements OnInit {
export class RgwDaemonDetailsComponent implements OnChanges {
metadata: any;
serviceId = '';
@Input() selected?: Array<any> = [];
@Input() selection: CdTableSelection;
constructor(private rgwDaemonService: RgwDaemonService) { }
constructor(private rgwDaemonService: RgwDaemonService) {}
ngOnInit() {
ngOnChanges() {
// Get the service id of the first selected row.
if (this.selected.length > 0) {
this.serviceId = this.selected[0].id;
if (this.selection.hasSelection) {
this.serviceId = this.selection.first().id;
}
}
@ -29,9 +29,8 @@ export class RgwDaemonDetailsComponent implements OnInit {
if (_.isEmpty(this.serviceId)) {
return;
}
this.rgwDaemonService.get(this.serviceId)
.then((resp) => {
this.metadata = resp['rgw_metadata'];
});
this.rgwDaemonService.get(this.serviceId).then(resp => {
this.metadata = resp['rgw_metadata'];
});
}
}

View File

@ -5,10 +5,14 @@
aria-current="page">Object Gateway</li>
</ol>
</nav>
<cd-table [data]="daemons"
[columns]="columns"
columnMode="flex"
[detailsComponent]="detailsComponent"
(fetchData)="getDaemonList()"
[beforeShowDetails]="beforeShowDetails">
selectionType="single"
(updateSelection)="updateSelection($event)"
(fetchData)="getDaemonList()">
<cd-rgw-daemon-details cdTableDetail
[selection]="selection">
</cd-rgw-daemon-details>
</cd-table>

View File

@ -2,9 +2,11 @@ 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 { TabsModule } from 'ngx-bootstrap/tabs';
import { DataTableModule } from '../../../shared/datatable/datatable.module';
import { PerformanceCounterModule } from '../../performance-counter/performance-counter.module';
import { RgwDaemonDetailsComponent } from '../rgw-daemon-details/rgw-daemon-details.component';
import { RgwDaemonService } from '../services/rgw-daemon.service';
import { RgwDaemonListComponent } from './rgw-daemon-list.component';
@ -14,12 +16,13 @@ describe('RgwDaemonListComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ RgwDaemonListComponent ],
declarations: [ RgwDaemonListComponent, RgwDaemonDetailsComponent ],
imports: [
DataTableModule,
HttpClientTestingModule,
BsDropdownModule.forRoot(),
HttpClientModule
HttpClientModule,
TabsModule.forRoot(),
PerformanceCounterModule
],
providers: [ RgwDaemonService ]
})

View File

@ -14,8 +14,7 @@ export class RgwDaemonListComponent {
columns: Array<CdTableColumn> = [];
daemons: Array<object> = [];
detailsComponent = 'RgwDaemonDetailsComponent';
selection = new CdTableSelection();
constructor(private rgwDaemonService: RgwDaemonService,
cephShortVersionPipe: CephShortVersionPipe) {
@ -46,7 +45,7 @@ export class RgwDaemonListComponent {
});
}
beforeShowDetails(selection: CdTableSelection) {
return selection.hasSingleSelection;
updateSelection(selection: CdTableSelection) {
this.selection = selection;
}
}

View File

@ -13,7 +13,7 @@ import { NavigationComponent } from './navigation/navigation.component';
imports: [
CommonModule,
AuthModule,
BsDropdownModule,
BsDropdownModule.forRoot(),
AppRoutingModule,
SharedModule,
RouterModule

View File

@ -4,11 +4,10 @@ import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { NgxDatatableModule } from '@swimlane/ngx-datatable';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { BsDropdownModule } from 'ngx-bootstrap';
import { ComponentsModule } from '../components/components.module';
import { PipesModule } from '../pipes/pipes.module';
import { TableDetailsDirective } from './table-details.directive';
import { TableKeyValueComponent } from './table-key-value/table-key-value.component';
import { TableComponent } from './table/table.component';
@ -17,14 +16,13 @@ import { TableComponent } from './table/table.component';
CommonModule,
NgxDatatableModule,
FormsModule,
BsDropdownModule,
BsDropdownModule.forRoot(),
PipesModule,
ComponentsModule,
RouterModule
],
declarations: [
TableComponent,
TableDetailsDirective,
TableKeyValueComponent
],
exports: [

View File

@ -1,8 +0,0 @@
import { TableDetailsDirective } from './table-details.directive';
describe('TableDetailsDirective', () => {
it('should create an instance', () => {
const directive = new TableDetailsDirective(null);
expect(directive).toBeTruthy();
});
});

View File

@ -1,11 +0,0 @@
import { Directive, Input, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[cdTableDetails]'
})
export class TableDetailsDirective {
@Input() selected?: any[];
constructor(public viewContainerRef: ViewContainerRef) { }
}

View File

@ -90,12 +90,11 @@
[loadingIndicator]="loadingIndicator"
[rowIdentity]="rowIdentity()"
[rowHeight]="'auto'">
<!-- Row Detail Template -->
<ngx-datatable-row-detail (toggle)="updateDetailView()">
</ngx-datatable-row-detail>
</ngx-datatable>
</div>
<ng-template cdTableDetails></ng-template>
<!-- Table Details -->
<ng-content select="[cdTableDetail]"></ng-content>
<!-- cell templates that can be accessed from outside -->
<ng-template #tableCellBoldTpl

View File

@ -1,7 +1,6 @@
import {
AfterContentChecked,
Component,
ComponentFactoryResolver,
EventEmitter,
Input,
OnChanges,
@ -25,7 +24,6 @@ import { Observable } from 'rxjs/Observable';
import { CdTableColumn } from '../../models/cd-table-column';
import { CdTableSelection } from '../../models/cd-table-selection';
import { TableDetailsDirective } from '../table-details.directive';
@Component({
selector: 'cd-table',
@ -34,7 +32,6 @@ import { TableDetailsDirective } from '../table-details.directive';
})
export class TableComponent implements AfterContentChecked, OnInit, OnChanges, OnDestroy {
@ViewChild(DatatableComponent) table: DatatableComponent;
@ViewChild(TableDetailsDirective) detailTemplate: TableDetailsDirective;
@ViewChild('tableCellBoldTpl') tableCellBoldTpl: TemplateRef<any>;
@ViewChild('sparklineTpl') sparklineTpl: TemplateRef<any>;
@ViewChild('routerLinkTpl') routerLinkTpl: TemplateRef<any>;
@ -48,8 +45,6 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
@Input() sorts?: SortPropDir[];
// Method used for setting column widths.
@Input() columnMode ?= 'flex';
// Name of the component e.g. 'TableDetailsComponent'
@Input() detailsComponent?: string;
// Display the tool header, including reload button, pagination and search fields?
@Input() toolHeader ?= true;
// Display the table header?
@ -58,10 +53,6 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
@Input() footer ?= true;
// Page size to show. Set to 0 to show unlimited number of rows.
@Input() limit ?= 10;
// An optional function that is called before the details page is show.
// The current selection is passed as function argument. To do not display
// the details page, return false.
@Input() beforeShowDetails: Function;
/**
* Auto reload time in ms - per default every 5s
@ -72,6 +63,9 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
// Which row property is unique for a row
@Input() identifier = 'id';
// Allows other components to specify which type of selection they want,
// e.g. 'single' or 'multi'.
@Input() selectionType: string = undefined;
/**
* Should be a function to update the input data if undefined nothing will be triggered
@ -84,6 +78,16 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
*/
@Output() fetchData = new EventEmitter();
/**
* This should be defined if you need access to the selection object.
*
* Each time the table selection changes, this will be triggered and
* the new selection object will be sent.
*
* @memberof TableComponent
*/
@Output() updateSelection = new EventEmitter();
/**
* Use this variable to access the selected row(s).
*/
@ -93,7 +97,6 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
cellTemplates: {
[key: string]: TemplateRef<any>
} = {};
selectionType: string = undefined;
search = '';
rows = [];
loadingIndicator = true;
@ -110,7 +113,7 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
// table columns after the browser window has been resized.
private currentWidth: number;
constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
constructor() {}
ngOnInit() {
this._addTemplates();
@ -132,9 +135,6 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
}
return c;
});
if (this.detailsComponent) {
this.selectionType = 'multi';
}
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
@ -164,7 +164,7 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
}
}
_addTemplates () {
_addTemplates() {
this.cellTemplates.bold = this.tableCellBoldTpl;
this.cellTemplates.sparkline = this.sparklineTpl;
this.cellTemplates.routerLink = this.routerLinkTpl;
@ -218,15 +218,7 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
onSelect() {
this.selection.update();
this.toggleExpandRow();
}
toggleExpandRow() {
if (this.selection.hasSelection) {
this.table.rowDetail.toggleExpandRow(this.selection.first());
} else {
this.detailTemplate.viewContainerRef.clear();
}
this.updateSelection.emit(_.clone(this.selection));
}
toggleColumn($event: any) {
@ -258,25 +250,6 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
];
}
updateDetailView() {
if (!this.detailsComponent) {
return;
}
if (_.isFunction(this.beforeShowDetails)) {
if (!this.beforeShowDetails(this.selection)) {
this.detailTemplate.viewContainerRef.clear();
return;
}
}
const factories = Array.from(this.componentFactoryResolver['_factories'].keys());
const factoryClass = <Type<any>>factories.find((x: any) => x.name === this.detailsComponent);
this.detailTemplate.viewContainerRef.clear();
const cmpRef = this.detailTemplate.viewContainerRef.createComponent(
this.componentFactoryResolver.resolveComponentFactory(factoryClass)
);
cmpRef.instance.selected = this.selection.selected;
}
updateFilter(event?) {
if (!event) {
this.search = '';
@ -284,11 +257,15 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
const val = this.search.toLowerCase();
const columns = this.columns;
// update the rows
this.rows = this.data.filter(function (d) {
return columns.filter((c) => {
return (typeof d[c.prop] === 'string' || typeof d[c.prop] === 'number')
&& (d[c.prop] + '').toLowerCase().indexOf(val) !== -1;
}).length > 0;
this.rows = this.data.filter((d) => {
return (
columns.filter(c => {
return (
(_.isString(d[c.prop]) || _.isNumber(d[c.prop])) &&
(d[c.prop] + '').toLowerCase().indexOf(val) !== -1
);
}).length > 0
);
});
// Whenever the filter changes, always go back to the first page
this.table.offset = 0;
@ -298,7 +275,7 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
// Return the function used to populate a row's CSS classes.
return () => {
return {
'clickable': !_.isUndefined(this.detailsComponent)
clickable: !_.isUndefined(this.selectionType)
};
};
}

View File

@ -18,6 +18,10 @@ export class CdTableSelection {
this.hasMultiSelection = this.selected.length > 1;
}
/**
* Get the first selected row.
* @return {any | null}
*/
first() {
return this.hasSelection ? this.selected[0] : null;
}