Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HParams: Allow runs in the time series dashboard to be filtered by hparam #6488

Merged
merged 7 commits into from
Aug 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions tensorboard/webapp/metrics/views/main_view/common_selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
getDashboardMetricsFilterMap,
getDashboardHparamsAndMetricsSpecs,
getDashboardHparamFilterMap,
getDashboardDefaultHparamFilters,
} from '../../../hparams/_redux/hparams_selectors';
import {
DiscreteFilter,
Expand Down Expand Up @@ -185,6 +186,19 @@ const utils = {
},
};

export const getCurrentColumnFilters = createSelector(
getDashboardDefaultHparamFilters,
getDashboardHparamFilterMap,
getDashboardMetricsFilterMap,
(defaultHparamsFilters, hparamFilters, metricFilters) => {
return new Map([
...defaultHparamsFilters,
...hparamFilters,
...metricFilters,
]);
}
);

const getRenderableRuns = createSelector(
getDashboardRuns,
getDashboardExperimentNames,
Expand Down Expand Up @@ -272,6 +286,7 @@ export const getPotentialHparamColumns = createSelector(
removable: true,
sortable: true,
movable: true,
filterable: true,
}));
}
);
Expand All @@ -290,5 +305,6 @@ export const TEST_ONLY = {
getRenderableRuns,
getRenderableCardIdsWithMetadata,
getScalarTagsForRunSelection,
getCurrentColumnFilters,
utils,
};
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,7 @@ describe('common selectors', () => {
removable: true,
sortable: true,
movable: true,
filterable: true,
},
]);
});
Expand All @@ -995,6 +996,7 @@ describe('common selectors', () => {
removable: true,
sortable: true,
movable: true,
filterable: true,
},
{
type: ColumnHeaderType.HPARAM,
Expand All @@ -1004,6 +1006,7 @@ describe('common selectors', () => {
removable: true,
sortable: true,
movable: true,
filterable: true,
},
]);
});
Expand Down
1 change: 1 addition & 0 deletions tensorboard/webapp/runs/store/runs_reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ const {initialState: uiInitialState, reducers: uiNamespaceContextedReducers} =
enabled: true,
sortable: true,
movable: true,
filterable: false,
JamesHollyer marked this conversation as resolved.
Show resolved Hide resolved
},
],
sortingInfo: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@
[sortingInfo]="sortingInfo"
[columnCustomizationEnabled]="true"
[selectableColumns]="selectableColumns"
[columnFilters]="columnFilters"
(sortDataBy)="sortDataBy.emit($event)"
(orderColumns)="orderColumns.emit($event)"
(addColumn)="addColumn.emit($event)"
(removeColumn)="removeColumn.emit($event)"
(addFilter)="addFilter.emit($event)"
>
<ng-container header>
<ng-container *ngFor="let header of getHeaders()">
Expand Down
6 changes: 5 additions & 1 deletion tensorboard/webapp/runs/views/runs_table/runs_data_table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ import {
TableData,
SortingInfo,
ColumnHeaderType,
FilterAddedEvent,
DiscreteFilter,
IntervalFilter,
} from '../../../widgets/data_table/types';

@Component({
selector: 'runs-data-table',
templateUrl: 'runs_data_table.ng.html',
Expand All @@ -41,6 +43,7 @@ export class RunsDataTable {
@Input() isFullScreen!: boolean;
@Input() selectableColumns!: ColumnHeader[];
@Input() loading!: boolean;
@Input() columnFilters!: Map<string, DiscreteFilter | IntervalFilter>;
JamesHollyer marked this conversation as resolved.
Show resolved Hide resolved

ColumnHeaderType = ColumnHeaderType;

Expand All @@ -60,6 +63,7 @@ export class RunsDataTable {
}>();
@Output() removeColumn = new EventEmitter<ColumnHeader>();
@Output() onSelectionDblClick = new EventEmitter<string>();
@Output() addFilter = new EventEmitter<FilterAddedEvent>();

// These columns must be stored and reused to stop needless re-rendering of
// the content and headers in these columns. This has been known to cause
Expand Down
16 changes: 15 additions & 1 deletion tensorboard/webapp/runs/views/runs_table/runs_table_container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ import {matchRunToRegex} from '../../../util/matcher';
import {getEnableHparamsInTimeSeries} from '../../../feature_flag/store/feature_flag_selectors';
import {
ColumnHeader,
ColumnHeaderType,
FilterAddedEvent,
SortingInfo,
SortingOrder,
TableData,
Expand Down Expand Up @@ -96,6 +96,7 @@ import {
} from './runs_table_component';
import {RunsTableColumn, RunTableItem} from './types';
import {
getCurrentColumnFilters,
getFilteredRenderableRuns,
getPotentialHparamColumns,
} from '../../../metrics/views/main_view/common_selectors';
Expand Down Expand Up @@ -275,6 +276,7 @@ function matchFilter(
[headers]="runsColumns$ | async"
[data]="sortedRunsTableData$ | async"
[selectableColumns]="selectableColumns$ | async"
[columnFilters]="columnFilters$ | async"
[sortingInfo]="sortingInfo$ | async"
[experimentIds]="experimentIds"
[regexFilter]="regexFilter$ | async"
Expand All @@ -290,6 +292,7 @@ function matchFilter(
(toggleFullScreen)="toggleFullScreen()"
(addColumn)="addColumn($event)"
(removeColumn)="removeColumn($event)"
(addFilter)="addHparamFilter($event)"
></runs-data-table>
`,
host: {
Expand Down Expand Up @@ -372,6 +375,8 @@ export class RunsTableContainer implements OnInit, OnDestroy {
})
);

columnFilters$ = this.store.select(getCurrentColumnFilters);

allRunsTableData$ = this.store.select(getFilteredRenderableRuns).pipe(
map((filteredRenderableRuns) => {
return filteredRenderableRuns.map((runTableItem) => {
Expand Down Expand Up @@ -826,6 +831,15 @@ export class RunsTableContainer implements OnInit, OnDestroy {
useDataTable() {
return this.hparamsEnabled.value && !this.forceLegacyTable;
}

addHparamFilter(event: FilterAddedEvent) {
this.store.dispatch(
hparamsActions.dashboardHparamFilterAdded({
name: event.header.name,
filter: event.value,
})
);
}
}

export const TEST_ONLY = {
Expand Down
124 changes: 123 additions & 1 deletion tensorboard/webapp/runs/views/runs_table/runs_table_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,6 @@ describe('runs_table', () => {
RunsGroupMenuButtonContainer,
RunsTableComponent,
RunsTableContainer,
RunsTableContainer,
TestableColorPicker,
],
providers: [provideMockTbStore(), ColorPickerTestHelper],
Expand Down Expand Up @@ -2974,6 +2973,129 @@ describe('runs_table', () => {
expect(action.type).not.toBe(runSelectorSortChanged.type);
}
});

describe('runs data table integration', () => {
JamesHollyer marked this conversation as resolved.
Show resolved Hide resolved
beforeEach(() => {
store.overrideSelector(getEnableHparamsInTimeSeries, true);
store.overrideSelector(getFilteredRenderableRuns, [
{
run: buildRun({
id: 'id1',
name: 'Book 1',
hparams: [{name: 'qaz', value: 0.5}],
}),
experimentAlias: {aliasNumber: 0, aliasText: 'hp'},
experimentName: 'HP',
selected: true,
runColor: 'fff',
hparams: new Map([['qaz', 0.5]]),
metrics: new Map<string, any>(),
},
{
run: buildRun({
id: 'id2',
name: 'Book 2',
hparams: [{name: 'qaz', value: 0.5}],
}),
experimentAlias: {aliasNumber: 0, aliasText: 'hp'},
experimentName: 'HP',
selected: true,
runColor: 'fff',
hparams: new Map([['qaz', 0.5]]),
metrics: new Map<string, any>(),
},
]);

store.overrideSelector(getRunsTableHeaders, [
{
type: ColumnHeaderType.HPARAM,
name: 'foo',
displayName: 'Foo',
enabled: true,
removable: true,
filterable: true,
sortable: true,
movable: true,
},
{
type: ColumnHeaderType.HPARAM,
name: 'qaz',
displayName: 'Qaz',
enabled: true,
removable: true,
filterable: true,
sortable: true,
movable: true,
},
]);
});

it('adds interval filters', () => {
const fixture = createComponent(TEST_HPARAM_SPECS, TEST_METRIC_SPECS);
fixture.detectChanges();
const dataTable = fixture.debugElement.query(
By.directive(RunsDataTable)
);

dataTable.componentInstance.addFilter.emit({
header: {
name: 'qaz',
},
value: {
type: DomainType.INTERVAL,
includeUndefined: true,
filterLowerValue: 10,
filterUpperValue: 20,
minValue: 10,
maxValue: 20,
},
});
expect(dispatchSpy).toHaveBeenCalledWith(
hparamsActions.dashboardHparamFilterAdded({
name: 'qaz',
filter: {
type: DomainType.INTERVAL,
includeUndefined: true,
filterLowerValue: 10,
filterUpperValue: 20,
minValue: 10,
maxValue: 20,
},
})
);
});

it('adds discrete filters', () => {
const fixture = createComponent(TEST_HPARAM_SPECS, TEST_METRIC_SPECS);
fixture.detectChanges();
const dataTable = fixture.debugElement.query(
By.directive(RunsDataTable)
);

dataTable.componentInstance.addFilter.emit({
header: {
name: 'foo',
},
value: {
type: DomainType.DISCRETE,
includeUndefined: true,
filterValues: [2, 4, 6, 8],
possibleValues: [2, 4, 6, 8, 10],
},
});
expect(dispatchSpy).toHaveBeenCalledWith(
hparamsActions.dashboardHparamFilterAdded({
name: 'foo',
filter: {
type: DomainType.DISCRETE,
includeUndefined: true,
filterValues: [2, 4, 6, 8],
possibleValues: [2, 4, 6, 8, 10],
},
})
);
});
});
});

function setNoFilterHparamsAndMetrics(
Expand Down
25 changes: 20 additions & 5 deletions tensorboard/webapp/widgets/data_table/data_table_component.ng.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@

<custom-modal #contextMenu (onClose)="onContextMenuClosed()">
<div class="context-menu">
<div
*ngIf="!contextMenuHeader?.removable && !contextMenuHeader?.sortable && !canContextMenuInsert()"
class="no-actions-message"
>
<div *ngIf="isContextMenuEmpty()" class="no-actions-message">
No Actions Available
</div>
<button
Expand Down Expand Up @@ -46,6 +43,14 @@
<mat-icon svgIcon="arrow_upward_24px"></mat-icon>Sort Ascending
</ng-template>
</button>
<button
*ngIf="contextMenuHeader?.filterable"
class="context-menu-button"
mat-button
(click)="openFilterMenu($event, contextMenuHeader)"
>
<mat-icon svgIcon="filter_alt_24px"></mat-icon> Filter
</button>
<button
mat-button
*ngIf="canContextMenuInsert()"
Expand All @@ -69,14 +74,24 @@
#columnSelectorModal
*ngIf="selectableColumns && selectableColumns.length"
(onOpen)="focusColumnSelector()"
onClose="onColumnSelectorClosed()"
(onClose)="onColumnSelectorClosed()"
JamesHollyer marked this conversation as resolved.
Show resolved Hide resolved
>
<tb-data-table-column-selector-component
[selectableColumns]="selectableColumns"
(columnSelected)="onColumnAdded($event)"
></tb-data-table-column-selector-component>
</custom-modal>

<custom-modal #filterModal (onClose)="onFilterClosed()">
<tb-data-table-filter
*ngIf="getCurrentColumnFilter()"
[filter]="getCurrentColumnFilter()"
(intervalFilterChanged)="intervalFilterChanged($event)"
(discreteFilterChanged)="discreteFilterChanged($event)"
(includeUndefinedToggled)="includeUndefinedToggled()"
></tb-data-table-filter>
</custom-modal>

<div class="data-table">
<div class="header">
<ng-content select="[header]"></ng-content>
Expand Down
Loading