diff --git a/tensorboard/webapp/runs/actions/BUILD b/tensorboard/webapp/runs/actions/BUILD index d5b90c2047..dd325d1af3 100644 --- a/tensorboard/webapp/runs/actions/BUILD +++ b/tensorboard/webapp/runs/actions/BUILD @@ -14,6 +14,7 @@ tf_ts_library( "//tensorboard/webapp/runs:types", "//tensorboard/webapp/runs/data_source", "//tensorboard/webapp/types:ui", + "//tensorboard/webapp/widgets/data_table:types", "@npm//@ngrx/store", ], ) diff --git a/tensorboard/webapp/runs/actions/runs_actions.ts b/tensorboard/webapp/runs/actions/runs_actions.ts index 8a85a1a491..79d08c6c20 100644 --- a/tensorboard/webapp/runs/actions/runs_actions.ts +++ b/tensorboard/webapp/runs/actions/runs_actions.ts @@ -20,6 +20,7 @@ import {createAction, props} from '@ngrx/store'; import {SortDirection} from '../../types/ui'; import {Run} from '../data_source/runs_data_source_types'; import {ExperimentIdToRunsAndMetadata, GroupBy, SortKey} from '../types'; +import {ColumnHeader, SortingInfo} from '../../widgets/data_table/types'; /** * The action can fire when no requests are actually made (i.e., an empty @@ -96,3 +97,19 @@ export const runGroupByChanged = createAction( '[Runs] Run Group By Changed', props<{experimentIds: string[]; groupBy: GroupBy}>() ); + +/** + * Inserts the provided column header at the specified index. + */ +export const runsTableHeaderAdded = createAction( + '[Runs] Runs Table Header Added', + props<{header: ColumnHeader; index?: number}>() +); + +/** + * Updates the sorting logic used by the runs data tabe. + */ +export const runsTableSortingInfoChanged = createAction( + '[Runs] Runs Table Sorting Info Changed', + props<{sortingInfo: SortingInfo}>() +); diff --git a/tensorboard/webapp/runs/data_source/runs_data_source_types.ts b/tensorboard/webapp/runs/data_source/runs_data_source_types.ts index a62365dadc..1865c8cf04 100644 --- a/tensorboard/webapp/runs/data_source/runs_data_source_types.ts +++ b/tensorboard/webapp/runs/data_source/runs_data_source_types.ts @@ -86,3 +86,8 @@ export abstract class RunsDataSource { experimentId: string ): Observable; } + +export type RunToHParamValues = Record< + string, + Map +>; diff --git a/tensorboard/webapp/runs/store/BUILD b/tensorboard/webapp/runs/store/BUILD index 810cfb745a..3f4c3fe25d 100644 --- a/tensorboard/webapp/runs/store/BUILD +++ b/tensorboard/webapp/runs/store/BUILD @@ -22,6 +22,7 @@ tf_ts_library( "//tensorboard/webapp/types", "//tensorboard/webapp/types:ui", "//tensorboard/webapp/util:ngrx", + "//tensorboard/webapp/widgets/data_table:types", "@npm//@ngrx/store", ], ) @@ -47,6 +48,7 @@ tf_ts_library( "//tensorboard/webapp/runs:types", "//tensorboard/webapp/types", "//tensorboard/webapp/types:ui", + "//tensorboard/webapp/widgets/data_table:types", "@npm//@ngrx/store", ], ) @@ -62,6 +64,7 @@ tf_ts_library( "//tensorboard/webapp/runs/data_source", "//tensorboard/webapp/types", "//tensorboard/webapp/types:ui", + "//tensorboard/webapp/widgets/data_table:types", ], ) @@ -74,6 +77,7 @@ tf_ts_library( "//tensorboard/webapp/runs:types", "//tensorboard/webapp/runs/data_source", "//tensorboard/webapp/types:ui", + "//tensorboard/webapp/widgets/data_table:types", ], ) @@ -101,6 +105,7 @@ tf_ts_library( "//tensorboard/webapp/testing:lang", "//tensorboard/webapp/types", "//tensorboard/webapp/types:ui", + "//tensorboard/webapp/widgets/data_table:types", "@npm//@types/jasmine", ], ) diff --git a/tensorboard/webapp/runs/store/runs_reducers.ts b/tensorboard/webapp/runs/store/runs_reducers.ts index 28fd55fa06..77cb047e25 100644 --- a/tensorboard/webapp/runs/store/runs_reducers.ts +++ b/tensorboard/webapp/runs/store/runs_reducers.ts @@ -35,9 +35,11 @@ import { RunsDataState, RunsState, RunsUiNamespacedState, + RunsUiNonNamespacedState, RunsUiState, } from './runs_types'; import {createGroupBy, groupRuns} from './utils'; +import {ColumnHeaderType, SortingOrder} from '../../widgets/data_table/types'; const { initialState: dataInitialState, @@ -309,7 +311,10 @@ const initialSort: RunsUiNamespacedState['sort'] = { direction: SortDirection.UNSET, }; const {initialState: uiInitialState, reducers: uiNamespaceContextedReducers} = - createNamespaceContextedState( + createNamespaceContextedState< + RunsUiNamespacedState, + RunsUiNonNamespacedState + >( { paginationOption: { pageIndex: 0, @@ -317,6 +322,19 @@ const {initialState: uiInitialState, reducers: uiNamespaceContextedReducers} = }, sort: initialSort, selectionState: new Map(), + runsTableHeaders: [ + { + type: ColumnHeaderType.RUN, + name: 'run', + displayName: 'Run', + enabled: true, + }, + ], + sortingInfo: { + header: ColumnHeaderType.RUN, + name: 'run', + order: SortingOrder.DESCENDING, + }, }, {} ); @@ -407,6 +425,25 @@ const uiReducer: ActionReducer = createReducer( ...state, selectionState: nextSelectionState, }; + }), + on(runsActions.runsTableHeaderAdded, (state, {header, index}) => { + const newRunsTableHeaders = [...state.runsTableHeaders]; + if (index === undefined) { + newRunsTableHeaders.push(header); + } else { + newRunsTableHeaders.splice(index, 0, header); + } + + return { + ...state, + runsTableHeaders: newRunsTableHeaders, + }; + }), + on(runsActions.runsTableSortingInfoChanged, (state, {sortingInfo}) => { + return { + ...state, + sortingInfo, + }; }) ); diff --git a/tensorboard/webapp/runs/store/runs_reducers_test.ts b/tensorboard/webapp/runs/store/runs_reducers_test.ts index 993dc74b63..e0761e36fb 100644 --- a/tensorboard/webapp/runs/store/runs_reducers_test.ts +++ b/tensorboard/webapp/runs/store/runs_reducers_test.ts @@ -22,11 +22,12 @@ import {RouteKind} from '../../app_routing/types'; import {deepFreeze} from '../../testing/lang'; import {DataLoadState} from '../../types/data'; import {SortDirection} from '../../types/ui'; +import {ColumnHeaderType, SortingOrder} from '../../widgets/data_table/types'; import * as actions from '../actions'; import {buildHparamsAndMetadata} from '../data_source/testing'; import {GroupByKey, SortType, URLDeserializedState} from '../types'; import * as runsReducers from './runs_reducers'; -import {MAX_NUM_RUNS_TO_ENABLE_BY_DEFAULT, Run} from './runs_types'; +import {MAX_NUM_RUNS_TO_ENABLE_BY_DEFAULT, Run, RunsState} from './runs_types'; import {buildRun, buildRunsState} from './testing'; describe('runs_reducers', () => { @@ -1353,4 +1354,103 @@ describe('runs_reducers', () => { expect(nextState.data.initialGroupBy.key).toBe(GroupByKey.RUN); }); }); + + describe('runsTableHeaderAdded', () => { + let state: RunsState; + + beforeEach(() => { + state = buildRunsState( + {}, + { + runsTableHeaders: [ + { + type: ColumnHeaderType.RUN, + name: 'run', + displayName: 'Run', + enabled: true, + }, + { + type: ColumnHeaderType.VALUE, + name: 'value', + displayName: 'Value', + enabled: true, + }, + ], + } + ); + }); + + it('adds new column to end of list when no index is provided', () => { + const nextState = runsReducers.reducers( + state, + actions.runsTableHeaderAdded({ + header: { + type: ColumnHeaderType.COLOR, + name: 'color', + displayName: 'Color', + enabled: true, + }, + }) + ); + expect( + nextState.ui.runsTableHeaders.map((header) => header.type) + ).toEqual([ + ColumnHeaderType.RUN, + ColumnHeaderType.VALUE, + ColumnHeaderType.COLOR, + ]); + }); + + it('adds new column at the specified index', () => { + const nextState = runsReducers.reducers( + state, + actions.runsTableHeaderAdded({ + header: { + type: ColumnHeaderType.COLOR, + name: 'color', + displayName: 'Color', + enabled: true, + }, + index: 1, + }) + ); + expect( + nextState.ui.runsTableHeaders.map((header) => header.type) + ).toEqual([ + ColumnHeaderType.RUN, + ColumnHeaderType.COLOR, + ColumnHeaderType.VALUE, + ]); + }); + }); + + describe('runsTableSortingInfoChanged', () => { + it('returns the current runs table sorting info', () => { + const state = buildRunsState( + {}, + { + sortingInfo: { + header: ColumnHeaderType.RUN, + name: 'run', + order: SortingOrder.ASCENDING, + }, + } + ); + const nextState = runsReducers.reducers( + state, + actions.runsTableSortingInfoChanged({ + sortingInfo: { + header: ColumnHeaderType.HPARAM, + name: 'lr', + order: SortingOrder.DESCENDING, + }, + }) + ); + expect(nextState.ui.sortingInfo).toEqual({ + header: ColumnHeaderType.HPARAM, + name: 'lr', + order: SortingOrder.DESCENDING, + }); + }); + }); }); diff --git a/tensorboard/webapp/runs/store/runs_selectors.ts b/tensorboard/webapp/runs/store/runs_selectors.ts index 23ceea336d..5dc28ffd01 100644 --- a/tensorboard/webapp/runs/store/runs_selectors.ts +++ b/tensorboard/webapp/runs/store/runs_selectors.ts @@ -26,6 +26,7 @@ import { RUNS_FEATURE_KEY, } from './runs_types'; import {createGroupBy} from './utils'; +import {ColumnHeader, SortingInfo} from '../../widgets/data_table/types'; const getRunsState = createFeatureSelector(RUNS_FEATURE_KEY); @@ -236,3 +237,23 @@ export const getColorGroupRegexString = createSelector( return state.colorGroupRegexString; } ); + +/** + * Gets the columns to be displayed by the runs table. + */ +export const getRunsTableHeaders = createSelector( + getUiState, + (state: RunsUiState): ColumnHeader[] => { + return state.runsTableHeaders; + } +); + +/** + * Gets the information needed to sort the runs data table. + */ +export const getRunsTableSortingInfo = createSelector( + getUiState, + (state: RunsUiState): SortingInfo => { + return state.sortingInfo; + } +); diff --git a/tensorboard/webapp/runs/store/runs_selectors_test.ts b/tensorboard/webapp/runs/store/runs_selectors_test.ts index 5b5797544a..0671fd136a 100644 --- a/tensorboard/webapp/runs/store/runs_selectors_test.ts +++ b/tensorboard/webapp/runs/store/runs_selectors_test.ts @@ -14,6 +14,7 @@ limitations under the License. ==============================================================================*/ import {DataLoadState} from '../../types/data'; import {SortDirection} from '../../types/ui'; +import {ColumnHeaderType, SortingOrder} from '../../widgets/data_table/types'; import {GroupByKey, SortType} from '../types'; import * as selectors from './runs_selectors'; import {buildRun, buildRunsState, buildStateFromRunsState} from './testing'; @@ -584,4 +585,66 @@ describe('runs_selectors', () => { expect(selectors.getColorGroupRegexString(state)).toEqual('foo(\\d+)'); }); }); + + describe('#getRunsTableHeaders', () => { + it('returns the runs table headers', () => { + const state = buildStateFromRunsState( + buildRunsState( + {}, + { + runsTableHeaders: [ + { + type: ColumnHeaderType.RUN, + name: 'run', + displayName: 'Run', + enabled: true, + }, + { + type: ColumnHeaderType.COLOR, + name: 'color', + displayName: 'Color', + enabled: false, + }, + ], + } + ) + ); + expect(selectors.getRunsTableHeaders(state)).toEqual([ + { + type: ColumnHeaderType.RUN, + name: 'run', + displayName: 'Run', + enabled: true, + }, + { + type: ColumnHeaderType.COLOR, + name: 'color', + displayName: 'Color', + enabled: false, + }, + ]); + }); + }); + + describe('#getRunsTableSortingInfo', () => { + it('returns the runs data table sorting info', () => { + const state = buildStateFromRunsState( + buildRunsState( + {}, + { + sortingInfo: { + header: ColumnHeaderType.RUN, + name: 'run', + order: SortingOrder.ASCENDING, + }, + } + ) + ); + expect(selectors.getRunsTableSortingInfo(state)).toEqual({ + header: ColumnHeaderType.RUN, + name: 'run', + order: SortingOrder.ASCENDING, + }); + }); + }); }); diff --git a/tensorboard/webapp/runs/store/runs_types.ts b/tensorboard/webapp/runs/store/runs_types.ts index 0bfa2b34d3..f170af7f82 100644 --- a/tensorboard/webapp/runs/store/runs_types.ts +++ b/tensorboard/webapp/runs/store/runs_types.ts @@ -19,6 +19,7 @@ limitations under the License. import {NamespaceContextedState} from '../../app_routing/namespaced_state_reducer_helper'; import {LoadState} from '../../types/data'; import {SortDirection} from '../../types/ui'; +import {ColumnHeader, SortingInfo} from '../../widgets/data_table/types'; import {HparamValue} from '../data_source/runs_data_source_types'; import {GroupBy, GroupByKey, SortKey} from '../types'; @@ -79,6 +80,8 @@ export interface RunsUiNamespacedState { * Indicates whether the run is selected. */ selectionState: Map; + runsTableHeaders: ColumnHeader[]; + sortingInfo: SortingInfo; } export interface RunsUiNonNamespacedState {} diff --git a/tensorboard/webapp/runs/store/testing.ts b/tensorboard/webapp/runs/store/testing.ts index 0b1f1fbfbd..3d8839c1eb 100644 --- a/tensorboard/webapp/runs/store/testing.ts +++ b/tensorboard/webapp/runs/store/testing.ts @@ -17,6 +17,7 @@ limitations under the License. */ import {SortDirection} from '../../types/ui'; +import {ColumnHeaderType, SortingOrder} from '../../widgets/data_table/types'; import {GroupByKey} from '../types'; import { Run, @@ -68,6 +69,12 @@ export function buildRunsState( paginationOption: {pageIndex: 0, pageSize: 0}, sort: {key: null, direction: SortDirection.UNSET}, selectionState: new Map(), + runsTableHeaders: [], + sortingInfo: { + header: ColumnHeaderType.RUN, + name: 'run', + order: SortingOrder.DESCENDING, + }, ...uiOverride, }, }; diff --git a/tensorboard/webapp/runs/views/runs_table/BUILD b/tensorboard/webapp/runs/views/runs_table/BUILD index 3a5a0c619f..886bf89ed8 100644 --- a/tensorboard/webapp/runs/views/runs_table/BUILD +++ b/tensorboard/webapp/runs/views/runs_table/BUILD @@ -85,6 +85,7 @@ tf_ng_module( "//tensorboard/webapp/feature_flag/store", "//tensorboard/webapp/hparams", "//tensorboard/webapp/hparams:types", + "//tensorboard/webapp/metrics/views/main_view:common_selectors", "//tensorboard/webapp/runs:types", "//tensorboard/webapp/runs/actions", "//tensorboard/webapp/runs/data_source", @@ -142,6 +143,7 @@ tf_ts_library( "//tensorboard/webapp/hparams", "//tensorboard/webapp/hparams:testing", "//tensorboard/webapp/hparams:types", + "//tensorboard/webapp/metrics/views/main_view:common_selectors", "//tensorboard/webapp/runs:types", "//tensorboard/webapp/runs/actions", "//tensorboard/webapp/runs/data_source", @@ -155,6 +157,7 @@ tf_ts_library( "//tensorboard/webapp/types", "//tensorboard/webapp/types:ui", "//tensorboard/webapp/widgets/data_table", + "//tensorboard/webapp/widgets/data_table:types", "//tensorboard/webapp/widgets/experiment_alias", "//tensorboard/webapp/widgets/filter_input", "//tensorboard/webapp/widgets/range_input", diff --git a/tensorboard/webapp/runs/views/runs_table/runs_table_container.ts b/tensorboard/webapp/runs/views/runs_table/runs_table_container.ts index 566b0f248e..952ff8175c 100644 --- a/tensorboard/webapp/runs/views/runs_table/runs_table_container.ts +++ b/tensorboard/webapp/runs/views/runs_table/runs_table_container.ts @@ -57,6 +57,8 @@ import { getRunSelectorRegexFilter, getRunSelectorSort, getRunsLoadState, + getRunsTableHeaders, + getRunsTableSortingInfo, } from '../../../selectors'; import {DataLoadState, LoadState} from '../../../types/data'; import {SortDirection} from '../../../types/ui'; @@ -64,7 +66,7 @@ import {matchRunToRegex} from '../../../util/matcher'; import {getEnableHparamsInTimeSeries} from '../../../feature_flag/store/feature_flag_selectors'; import { ColumnHeaderType, - SortingOrder, + SortingInfo, TableData, } from '../../../widgets/data_table/types'; import { @@ -75,6 +77,7 @@ import { runSelectorRegexFilterChanged, runSelectorSortChanged, runTableShown, + runsTableSortingInfoChanged, singleRunSelected, } from '../../actions'; import {MAX_NUM_RUNS_TO_ENABLE_BY_DEFAULT} from '../../store/runs_types'; @@ -85,6 +88,8 @@ import { MetricColumn, } from './runs_table_component'; import {RunsTableColumn, RunTableItem} from './types'; +import {getFilteredRenderableRunsFromRoute} from '../../../metrics/views/main_view/common_selectors'; +import {RunToHParamValues} from '../../data_source/runs_data_source_types'; const getRunsLoading = createSelector< State, @@ -229,11 +234,11 @@ function matchFilter( > @@ -250,6 +255,11 @@ function matchFilter( :host.flex-layout > runs-table-component { width: 100%; } + + :host.flex-layout > tb-data-table { + overflow-y: scroll; + width: 100%; + } `, ], changeDetection: ChangeDetectionStrategy.OnPush, @@ -262,23 +272,7 @@ export class RunsTableContainer implements OnInit, OnDestroy { allItemsLength$?: Observable; pageItems$?: Observable; numSelectedItems$?: Observable; - - // TODO(jameshollyer): Move these values to ngrx and make these Observables. - runsColumns = [ - { - type: ColumnHeaderType.RUN, - name: 'run', - displayName: 'Run', - enabled: true, - }, - ]; - sortingInfo = { - header: ColumnHeaderType.RUN, - name: 'run', - order: SortingOrder.ASCENDING, - }; - columnCustomizationEnabled = true; - smoothingEnabled = false; + sortingInfo$ = this.store.select(getRunsTableSortingInfo); hparamColumns$: Observable = of([]); metricColumns$: Observable = of([]); @@ -311,6 +305,19 @@ export class RunsTableContainer implements OnInit, OnDestroy { paginationOption$ = this.store.select(getRunSelectorPaginationOption); regexFilter$ = this.store.select(getRunSelectorRegexFilter); HParamsEnabled = new BehaviorSubject(false); + runsColumns$ = this.store.select(getRunsTableHeaders); + + runToHParamValues$ = this.store + .select(getFilteredRenderableRunsFromRoute) + .pipe( + map((items) => { + return items.reduce((map, item) => { + map[item.run.id] = item.hparams; + return map; + }, {} as RunToHParamValues); + }) + ); + private readonly ngUnsubscribe = new Subject(); constructor(private readonly store: Store) {} @@ -335,8 +342,7 @@ export class RunsTableContainer implements OnInit, OnDestroy { this.allRunsTableData$ = combineLatest(getRunTableDataPerExperiment$).pipe( map((itemsForExperiments: TableData[][]) => { - const items = [] as TableData[]; - return items.concat(...itemsForExperiments); + return itemsForExperiments.flat(); }) ); @@ -575,18 +581,25 @@ export class RunsTableContainer implements OnInit, OnDestroy { return combineLatest([ this.store.select(getRuns, {experimentId}), this.store.select(getRunColorMap), + this.runsColumns$, + this.runToHParamValues$, ]).pipe( - map(([runs, colorMap]) => { + map(([runs, colorMap, runsColumns, runToHParamValues]) => { return runs.map((run) => { const tableData: TableData = { id: run.id, color: colorMap[run.id], }; - this.runsColumns.forEach((column) => { + runsColumns.forEach((column) => { switch (column.type) { case ColumnHeaderType.RUN: tableData[column.name!] = run.name; break; + case ColumnHeaderType.HPARAM: + tableData[column.name] = runToHParamValues[run.id]?.get( + column.name + ) as string | number; + break; default: break; } @@ -597,6 +610,10 @@ export class RunsTableContainer implements OnInit, OnDestroy { ); } + sortDataBy(sortingInfo: SortingInfo) { + this.store.dispatch(runsTableSortingInfoChanged({sortingInfo})); + } + private getRunTableItemsForExperiment( experimentId: string ): Observable { diff --git a/tensorboard/webapp/runs/views/runs_table/runs_table_test.ts b/tensorboard/webapp/runs/views/runs_table/runs_table_test.ts index 57509b353f..cfef3bac89 100644 --- a/tensorboard/webapp/runs/views/runs_table/runs_table_test.ts +++ b/tensorboard/webapp/runs/views/runs_table/runs_table_test.ts @@ -72,6 +72,7 @@ import { getRunSelectorRegexFilter, getRunSelectorSort, getRunsLoadState, + getRunsTableHeaders, } from '../../../selectors'; import {selectors as settingsSelectors} from '../../../settings'; import {buildColorPalette} from '../../../settings/testing'; @@ -104,7 +105,9 @@ import {RunsGroupMenuButtonComponent} from './runs_group_menu_button_component'; import {RunsGroupMenuButtonContainer} from './runs_group_menu_button_container'; import {RunsTableComponent} from './runs_table_component'; import {RunsTableContainer, TEST_ONLY} from './runs_table_container'; -import {HparamSpec, MetricSpec, RunsTableColumn} from './types'; +import {HparamSpec, MetricSpec, RunTableItem, RunsTableColumn} from './types'; +import {ColumnHeaderType} from '../../../widgets/data_table/types'; +import {getFilteredRenderableRunsFromRoute} from '../../../metrics/views/main_view/common_selectors'; @Injectable() class ColorPickerTestHelper { @@ -1863,7 +1866,7 @@ describe('runs_table', () => { describe('"too many runs" alert', () => { function createRuns(runCount: number): Run[] { - const runs = []; + const runs: Run[] = []; for (let i = 0; i < runCount; i++) { runs.push( buildRun({ @@ -3203,6 +3206,16 @@ describe('runs_table', () => { buildRun({id: 'book2', name: 'The Chamber Of Secrets'}), ]) ); + selectSpy.withArgs(getRunsTableHeaders).and.returnValue( + of([ + { + type: ColumnHeaderType.RUN, + name: 'run', + displayName: 'Run', + enabled: true, + }, + ]) + ); store.overrideSelector(getRunColorMap, { book1: '#000', @@ -3220,5 +3233,55 @@ describe('runs_table', () => { {id: 'book2', color: '#111', run: 'The Chamber Of Secrets'}, ]); }); + + it('passes hparam values to data table', () => { + const run1 = buildRun({id: 'book1', name: "The Philosopher's Stone"}); + const run2 = buildRun({id: 'book2', name: 'The Chamber Of Secrets'}); + // To make sure we only return the runs when called with the right props. + const selectSpy = spyOn(store, 'select').and.callThrough(); + selectSpy + .withArgs(getRuns, {experimentId: 'book'}) + .and.returnValue(of([run1, run2])); + + selectSpy.withArgs(getRunsTableHeaders).and.returnValue( + of([ + { + type: ColumnHeaderType.HPARAM, + name: 'batch_size', + displayName: 'Batch Size', + enabled: true, + }, + ]) + ); + + selectSpy.withArgs(getFilteredRenderableRunsFromRoute).and.returnValue( + of([ + { + run: run1, + hparams: new Map([['batch_size', 1]]), + } as RunTableItem, + { + run: run2, + hparams: new Map([['batch_size', 2]]), + } as RunTableItem, + ]) + ); + + store.overrideSelector(getRunColorMap, { + book1: '#000', + book2: '#111', + }); + + const fixture = createComponent(['book']); + fixture.detectChanges(); + const dataTableComponent = fixture.debugElement.query( + By.directive(DataTableComponent) + ); + + expect(dataTableComponent.componentInstance.data).toEqual([ + {id: 'book1', color: '#000', batch_size: 1}, + {id: 'book2', color: '#111', batch_size: 2}, + ]); + }); }); }); diff --git a/tensorboard/webapp/widgets/data_table/types.ts b/tensorboard/webapp/widgets/data_table/types.ts index f9c8884094..f17dcbb072 100644 --- a/tensorboard/webapp/widgets/data_table/types.ts +++ b/tensorboard/webapp/widgets/data_table/types.ts @@ -38,6 +38,7 @@ export enum ColumnHeaderType { STEP_AT_MIN = 'STEP_AT_MIN', MEAN = 'MEAN', RAW_CHANGE = 'RAW_CHANGE', + HPARAM = 'HPARAM', } export interface ColumnHeader {