diff --git a/tensorboard/webapp/runs/views/runs_table/runs_data_table.ng.html b/tensorboard/webapp/runs/views/runs_table/runs_data_table.ng.html
index 0097393986..4848283c26 100644
--- a/tensorboard/webapp/runs/views/runs_table/runs_data_table.ng.html
+++ b/tensorboard/webapp/runs/views/runs_table/runs_data_table.ng.html
@@ -23,17 +23,14 @@
placeholder="Filter runs (regex)"
>
-
-
-
+
+
+
diff --git a/tensorboard/webapp/widgets/data_table/data_table_component.scss b/tensorboard/webapp/widgets/data_table/data_table_component.scss
index 02a53f75fa..125e8781a4 100644
--- a/tensorboard/webapp/widgets/data_table/data_table_component.scss
+++ b/tensorboard/webapp/widgets/data_table/data_table_component.scss
@@ -45,6 +45,16 @@ $_accent: map-get(mat.get-color-config($tb-theme), accent);
}
}
+.loading {
+ align-items: center;
+ border: 0;
+ @include tb-theme-foreground-prop(border-bottom, border, 1px solid);
+ display: flex;
+ height: 48px;
+ padding: 0 24px;
+ justify-content: center;
+}
+
.add-button-cell {
display: table-cell;
width: 40px;
diff --git a/tensorboard/webapp/widgets/data_table/data_table_component.ts b/tensorboard/webapp/widgets/data_table/data_table_component.ts
index 336d1e676d..95993ae8cd 100644
--- a/tensorboard/webapp/widgets/data_table/data_table_component.ts
+++ b/tensorboard/webapp/widgets/data_table/data_table_component.ts
@@ -65,6 +65,7 @@ export class DataTableComponent implements OnDestroy, AfterContentInit {
@Input() columnCustomizationEnabled!: boolean;
@Input() selectableColumns?: ColumnHeader[];
@Input() columnFilters!: Map;
+ @Input() loading: boolean = false;
@ContentChildren(HeaderCellComponent)
headerCells!: QueryList;
diff --git a/tensorboard/webapp/widgets/data_table/data_table_module.ts b/tensorboard/webapp/widgets/data_table/data_table_module.ts
index 62ced1d9f5..2220aef24d 100644
--- a/tensorboard/webapp/widgets/data_table/data_table_module.ts
+++ b/tensorboard/webapp/widgets/data_table/data_table_module.ts
@@ -17,6 +17,7 @@ import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {MatIconModule} from '@angular/material/icon';
import {MatButtonModule} from '@angular/material/button';
+import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
import {DataTableComponent} from './data_table_component';
import {HeaderCellComponent} from './header_cell_component';
import {DataTableHeaderModule} from './data_table_header_module';
@@ -43,6 +44,7 @@ import {FilterDialogModule} from './filter_dialog_module';
CommonModule,
MatIconModule,
MatButtonModule,
+ MatProgressSpinnerModule,
DataTableHeaderModule,
CustomModalModule,
ColumnSelectorModule,
diff --git a/tensorboard/webapp/widgets/data_table/data_table_test.ts b/tensorboard/webapp/widgets/data_table/data_table_test.ts
index ec9a2ab96c..3a575be9c1 100644
--- a/tensorboard/webapp/widgets/data_table/data_table_test.ts
+++ b/tensorboard/webapp/widgets/data_table/data_table_test.ts
@@ -46,6 +46,7 @@ import {FilterDialog} from './filter_dialog_component';
[sortingInfo]="sortingInfo"
[selectableColumns]="selectableColumns"
[columnFilters]="columnFilters"
+ [loading]="loading"
(sortDataBy)="sortDataBy($event)"
(orderColumns)="orderColumns($event)"
(addColumn)="addColumn.emit($event)"
@@ -88,6 +89,7 @@ class TestableComponent {
@Input() orderColumns!: (newOrder: ColumnHeaderType[]) => void;
@Input() selectableColumns!: ColumnHeader[];
@Input() columnFilters!: Map;
+ @Input() loading!: boolean;
@Output() addColumn = new EventEmitter<{
header: ColumnHeader;
@@ -123,6 +125,7 @@ describe('data table', () => {
data?: TableData[];
potentialColumns?: ColumnHeader[];
columnFilters?: Map;
+ loading?: boolean;
}): ComponentFixture {
const fixture = TestBed.createComponent(TestableComponent);
@@ -140,6 +143,10 @@ describe('data table', () => {
fixture.componentInstance.selectableColumns = input.potentialColumns;
}
+ if (input.loading !== undefined) {
+ fixture.componentInstance.loading = input.loading;
+ }
+
fixture.componentInstance.columnFilters = input.columnFilters || new Map();
sortDataBySpy = jasmine.createSpy();
@@ -159,6 +166,20 @@ describe('data table', () => {
expect(dataTable).toBeTruthy();
});
+ it('renders spinner when loading', () => {
+ const fixture = createComponent({loading: true});
+ fixture.detectChanges();
+ const spinner = fixture.debugElement.query(By.css('.loading'));
+ expect(spinner).toBeTruthy();
+ });
+
+ it('does not renders spinner when not loading', () => {
+ const fixture = createComponent({loading: false});
+ fixture.detectChanges();
+ const spinner = fixture.debugElement.query(By.css('.loading'));
+ expect(spinner).toBeFalsy();
+ });
+
it('emits sortDataBy event when header emits headerClicked event', () => {
const fixture = createComponent({
headers: [