Skip to content

Commit 63b3bc6

Browse files
authored
HParams: Use experiment alias in experiment column (#6485)
## Motivation for features / changes The Experiment column in the current runs table uses the tb-experiment-alias widget to nicely display the experiment alias and alias number. Our RunsDataTable simply displayed the experiment name. This changes the RunsDataTable to use the same tb-experiment-alias widget in its Experiment column. ## Technical description of changes Since the ExperimentAlias interface does not conform to the TableData value which is defined as string|number|boolean. I had to add object as an option. Values that use an object require specific logic for sorting. I added that logic into for the experimentAlias. I also added a place for that logic if an object is ever used in the Scalar Table with a comment explaining its purpose. ## Screenshots of UI changes (or N/A) <img width="118" alt="Screenshot 2023-07-07 at 3 53 10 PM" src="https://github.com/tensorflow/tensorboard/assets/8672809/cd1571f5-e4c8-4aa4-808a-7e4307ff34f5"> ## Detailed steps to verify changes work correctly (as executed by you) I imported this and ran internally to ensure this worked. ## Alternate designs / implementations considered (or N/A) I also considered having the experimentAlias DataTable object simply contain the experimentId and then pass the ExperimentIdToAliasMap into the RunsDataTable. However, that would actually make sorting even more difficult. I also think supporting objects might prove useful in the future.
1 parent 862f9bc commit 63b3bc6

File tree

9 files changed

+111
-37
lines changed

9 files changed

+111
-37
lines changed

tensorboard/webapp/metrics/views/card_renderer/scalar_card_data_table.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,8 +303,15 @@ export class ScalarCardDataTable {
303303
}
304304

305305
function makeValueSortable(
306-
value: number | string | boolean | null | undefined
306+
value: number | string | boolean | null | undefined | object
307307
) {
308+
if (typeof value === 'object') {
309+
// The Scalar table does not currently support any objects.
310+
// When support is added specific sorting logic to that object type should
311+
// be added here.
312+
return -Infinity;
313+
}
314+
308315
if (
309316
Number.isNaN(value) ||
310317
value === 'NaN' ||

tensorboard/webapp/runs/store/runs_reducers.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -345,14 +345,14 @@ const {initialState: uiInitialState, reducers: uiNamespaceContextedReducers} =
345345
if (
346346
newRoute.routeKind === RouteKind.COMPARE_EXPERIMENT &&
347347
!state.runsTableHeaders.find(
348-
(header) => header.name === 'experimentName'
348+
(header) => header.name === 'experimentAlias'
349349
)
350350
) {
351351
const newRunsTableHeaders = [
352352
...state.runsTableHeaders,
353353
{
354-
type: ColumnHeaderType.EXPERIMENT,
355-
name: 'experimentName',
354+
type: ColumnHeaderType.CUSTOM,
355+
name: 'experimentAlias',
356356
displayName: 'Experiment',
357357
enabled: true,
358358
movable: true,
@@ -370,7 +370,7 @@ const {initialState: uiInitialState, reducers: uiNamespaceContextedReducers} =
370370
newRoute.routeKind !== RouteKind.COMPARE_EXPERIMENT
371371
) {
372372
const newRunsTableHeaders = state.runsTableHeaders.filter(
373-
(column) => column.name !== 'experimentName'
373+
(column) => column.name !== 'experimentAlias'
374374
);
375375

376376
return {

tensorboard/webapp/runs/store/runs_reducers_test.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,7 +1354,7 @@ describe('runs_reducers', () => {
13541354
expect(nextState.data.initialGroupBy.key).toBe(GroupByKey.RUN);
13551355
});
13561356

1357-
it('adds the experiment name column to the runs table columns list', () => {
1357+
it('adds the experiment alias column to the runs table columns list', () => {
13581358
const state = buildRunsState(
13591359
{},
13601360
{
@@ -1377,18 +1377,18 @@ describe('runs_reducers', () => {
13771377
);
13781378
expect(
13791379
nextState.ui.runsTableHeaders.map((column) => column.name)
1380-
).toEqual(['run', 'experimentName']);
1380+
).toEqual(['run', 'experimentAlias']);
13811381
});
13821382

1383-
it('does not add duplicate experiment name columns', () => {
1383+
it('does not add duplicate experiment alias columns', () => {
13841384
const state = buildRunsState(
13851385
{},
13861386
{
13871387
runsTableHeaders: [
13881388
{
1389-
type: ColumnHeaderType.EXPERIMENT,
1390-
name: 'experimentName',
1391-
displayName: 'ExperimentName',
1389+
type: ColumnHeaderType.CUSTOM,
1390+
name: 'experimentAlias',
1391+
displayName: 'Experiment',
13921392
enabled: true,
13931393
},
13941394
{
@@ -1409,18 +1409,18 @@ describe('runs_reducers', () => {
14091409
);
14101410
expect(
14111411
nextState.ui.runsTableHeaders.map((column) => column.name)
1412-
).toEqual(['experimentName', 'run']);
1412+
).toEqual(['experimentAlias', 'run']);
14131413
});
14141414

1415-
it('removes the experiment name column when changing away comparison view', () => {
1415+
it('removes the experiment alias column when changing away comparison view', () => {
14161416
const state = buildRunsState(
14171417
{},
14181418
{
14191419
runsTableHeaders: [
14201420
{
1421-
type: ColumnHeaderType.EXPERIMENT,
1422-
name: 'experimentName',
1423-
displayName: 'ExperimentName',
1421+
type: ColumnHeaderType.CUSTOM,
1422+
name: 'experimentAlias',
1423+
displayName: 'Experiment',
14241424
enabled: true,
14251425
},
14261426
{

tensorboard/webapp/runs/views/runs_table/runs_data_table.ng.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@
8686
(click)="selectionClick($event, dataRow['id'])"
8787
></mat-checkbox>
8888
</div>
89+
<span *ngSwitchCase="'experimentAlias'">
90+
<tb-experiment-alias
91+
[alias]="dataRow['experimentAlias']"
92+
></tb-experiment-alias>
93+
</span>
8994
</ng-container>
9095
</tb-data-table-content-cell>
9196
</ng-container>

tensorboard/webapp/runs/views/runs_table/runs_data_table_test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,34 @@ describe('runs_data_table', () => {
257257
});
258258
});
259259

260+
it('adds ExperimentAlias widget on experimentAlias column', () => {
261+
const fixture = createComponent({
262+
headers: [
263+
{
264+
name: 'experimentAlias',
265+
type: ColumnHeaderType.CUSTOM,
266+
displayName: 'Experiment Alias',
267+
enabled: true,
268+
},
269+
],
270+
});
271+
272+
const dataTable = fixture.debugElement.query(
273+
By.directive(DataTableComponent)
274+
);
275+
276+
const cells = dataTable.queryAll(By.directive(ContentCellComponent));
277+
278+
const selectedContentCells = cells.filter((cell) => {
279+
return cell.componentInstance.header.name === 'experimentAlias';
280+
});
281+
282+
expect(selectedContentCells.length).toBe(1);
283+
selectedContentCells.forEach((cell) => {
284+
expect(cell.query(By.css('tb-experiment-alias'))).toBeTruthy();
285+
});
286+
});
287+
260288
it('emits onAllSelectionToggle event when selected header checkbox is clicked', () => {
261289
const fixture = createComponent({});
262290

tensorboard/webapp/runs/views/runs_table/runs_table_container.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,13 @@ function sortTableDataItems(
189189
const sortedItems = [...items];
190190

191191
sortedItems.sort((a, b) => {
192-
const aValue = a[sort.name];
193-
const bValue = b[sort.name];
192+
let aValue = a[sort.name];
193+
let bValue = b[sort.name];
194+
195+
if (sort.name === 'experimentAlias') {
196+
aValue = (aValue as ExperimentAlias).aliasNumber;
197+
bValue = (bValue as ExperimentAlias).aliasNumber;
198+
}
194199

195200
if (aValue === bValue) {
196201
return 0;
@@ -376,7 +381,7 @@ export class RunsTableContainer implements OnInit, OnDestroy {
376381
...Object.fromEntries(runTableItem.hparams.entries()),
377382
id: runTableItem.run.id,
378383
run: runTableItem.run.name,
379-
experimentName: runTableItem.experimentName,
384+
experimentAlias: runTableItem.experimentAlias,
380385
selected: runTableItem.selected,
381386
color: runTableItem.runColor,
382387
};

tensorboard/webapp/runs/views/runs_table/runs_table_test.ts

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3210,37 +3210,27 @@ describe('runs_table', () => {
32103210
).toBeTruthy();
32113211
});
32123212

3213-
it('passes run name, selected value, and color to data table', () => {
3213+
it('passes run name, experiment alias, selected value, and color to data table', () => {
32143214
// To make sure we only return the runs when called with the right props.
32153215
const selectSpy = spyOn(store, 'select').and.callThrough();
32163216
selectSpy.withArgs(getFilteredRenderableRunsFromRoute).and.returnValue(
32173217
of([
32183218
{
32193219
run: buildRun({id: 'book1', name: "The Philosopher's Stone"}),
32203220
runColor: '#000',
3221-
experimentName: 'book',
3221+
experimentAlias: {aliasText: 'book', aliasNumber: 1},
32223222
selected: true,
32233223
hparams: new Map(),
32243224
},
32253225
{
32263226
run: buildRun({id: 'book2', name: 'The Chamber Of Secrets'}),
32273227
runColor: '#111',
3228-
experimentName: 'book',
3228+
experimentAlias: {aliasText: 'book', aliasNumber: 1},
32293229
selected: false,
32303230
hparams: new Map(),
32313231
},
32323232
])
32333233
);
3234-
selectSpy.withArgs(getRunsTableHeaders).and.returnValue(
3235-
of([
3236-
{
3237-
type: ColumnHeaderType.RUN,
3238-
name: 'run',
3239-
displayName: 'Run',
3240-
enabled: true,
3241-
},
3242-
])
3243-
);
32443234

32453235
const fixture = createComponent(['book']);
32463236
fixture.detectChanges();
@@ -3253,14 +3243,14 @@ describe('runs_table', () => {
32533243
id: 'book1',
32543244
color: '#000',
32553245
run: "The Philosopher's Stone",
3256-
experimentName: 'book',
3246+
experimentAlias: {aliasNumber: 1, aliasText: 'book'},
32573247
selected: true,
32583248
},
32593249
{
32603250
id: 'book2',
32613251
color: '#111',
32623252
run: 'The Chamber Of Secrets',
3263-
experimentName: 'book',
3253+
experimentAlias: {aliasNumber: 1, aliasText: 'book'},
32643254
selected: false,
32653255
},
32663256
]);
@@ -3351,6 +3341,7 @@ describe('runs_table', () => {
33513341
store.overrideSelector(getFilteredRenderableRunsFromRoute, [
33523342
{
33533343
run: run1,
3344+
experimentAlias: {aliasNumber: 1, aliasText: 'bbb'},
33543345
hparams: new Map<string, number | string | boolean>([
33553346
['batch_size', 2],
33563347
['good_hparam', false],
@@ -3359,13 +3350,15 @@ describe('runs_table', () => {
33593350
} as RunTableItem,
33603351
{
33613352
run: run2,
3353+
experimentAlias: {aliasNumber: 2, aliasText: 'ccc'},
33623354
hparams: new Map<string, number | string | boolean>([
33633355
['batch_size', 1],
33643356
['good_hparam', true],
33653357
]),
33663358
} as RunTableItem,
33673359
{
33683360
run: run3,
3361+
experimentAlias: {aliasNumber: 3, aliasText: 'aaa'},
33693362
hparams: new Map<string, number | string | boolean>([
33703363
['batch_size', 3],
33713364
['good_hparam', false],
@@ -3514,6 +3507,44 @@ describe('runs_table', () => {
35143507
runsDataTable.componentInstance.data[2]['scarce']
35153508
).toBeUndefined();
35163509
});
3510+
3511+
it('sorts experiment alias by alias number.', () => {
3512+
store.overrideSelector(getRunsTableSortingInfo, {
3513+
name: 'experimentAlias',
3514+
order: SortingOrder.ASCENDING,
3515+
});
3516+
const fixture = createComponent(['book']);
3517+
const runsDataTable = fixture.debugElement.query(
3518+
By.directive(RunsDataTable)
3519+
);
3520+
3521+
expect(
3522+
runsDataTable.componentInstance.data[0]['experimentAlias'].aliasNumber
3523+
).toEqual(1);
3524+
expect(
3525+
runsDataTable.componentInstance.data[1]['experimentAlias'].aliasNumber
3526+
).toEqual(2);
3527+
expect(
3528+
runsDataTable.componentInstance.data[2]['experimentAlias'].aliasNumber
3529+
).toEqual(3);
3530+
3531+
store.overrideSelector(getRunsTableSortingInfo, {
3532+
name: 'experimentAlias',
3533+
order: SortingOrder.DESCENDING,
3534+
});
3535+
store.refreshState();
3536+
fixture.detectChanges();
3537+
3538+
expect(
3539+
runsDataTable.componentInstance.data[0]['experimentAlias'].aliasNumber
3540+
).toEqual(3);
3541+
expect(
3542+
runsDataTable.componentInstance.data[1]['experimentAlias'].aliasNumber
3543+
).toEqual(2);
3544+
expect(
3545+
runsDataTable.componentInstance.data[2]['experimentAlias'].aliasNumber
3546+
).toEqual(1);
3547+
});
35173548
});
35183549
});
35193550
});

tensorboard/webapp/widgets/data_table/content_cell_component.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ export class ContentCellComponent {
4747
}
4848
switch (this.header.type) {
4949
case ColumnHeaderType.RUN:
50-
case ColumnHeaderType.EXPERIMENT:
5150
return this.datum as string;
5251
case ColumnHeaderType.VALUE:
5352
case ColumnHeaderType.STEP:

tensorboard/webapp/widgets/data_table/types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ export enum ColumnHeaderType {
2323
RELATIVE_TIME = 'RELATIVE_TIME',
2424
RUN = 'RUN',
2525
STEP = 'STEP',
26-
EXPERIMENT = 'EXPERIMENT',
2726
TIME = 'TIME',
2827
VALUE = 'VALUE',
2928
SMOOTHED = 'SMOOTHED',
@@ -75,7 +74,7 @@ export interface SortingInfo {
7574
* DataTable. It will have a value for each required ColumnHeader for a given
7675
* run.
7776
*/
78-
export type TableData = Record<string, string | number | boolean> & {
77+
export type TableData = Record<string, string | number | boolean | object> & {
7978
id: string;
8079
};
8180

0 commit comments

Comments
 (0)