diff --git a/tensorboard/webapp/hparams/_redux/hparams_actions.ts b/tensorboard/webapp/hparams/_redux/hparams_actions.ts index 6780c71146..e142279438 100644 --- a/tensorboard/webapp/hparams/_redux/hparams_actions.ts +++ b/tensorboard/webapp/hparams/_redux/hparams_actions.ts @@ -73,3 +73,13 @@ export const dashboardMetricFilterAdded = createAction( '[Hparams] Dashboard Metric Filter Added', props<{name: string; filter: MetricFilter}>() ); + +export const dashboardHparamFilterRemoved = createAction( + '[Hparams] Dashboard Hparam Filter Removed', + props<{name: string}>() +); + +export const dashboardMetricFilterRemoved = createAction( + '[Hparams] Dashboard Metric Filter Removed', + props<{name: string}>() +); diff --git a/tensorboard/webapp/hparams/_redux/hparams_reducers.ts b/tensorboard/webapp/hparams/_redux/hparams_reducers.ts index 2f39e9e873..781dfec9ad 100644 --- a/tensorboard/webapp/hparams/_redux/hparams_reducers.ts +++ b/tensorboard/webapp/hparams/_redux/hparams_reducers.ts @@ -390,6 +390,30 @@ const reducer: ActionReducer = createReducer( const nextMetricFilters = new Map(state.dashboardFilters.metrics); nextMetricFilters.set(action.name, action.filter); + return { + ...state, + dashboardFilters: { + ...state.dashboardFilters, + metrics: nextMetricFilters, + }, + }; + }), + on(actions.dashboardHparamFilterRemoved, (state, action) => { + const nextHparamFilters = new Map(state.dashboardFilters.hparams); + nextHparamFilters.delete(action.name); + + return { + ...state, + dashboardFilters: { + ...state.dashboardFilters, + hparams: nextHparamFilters, + }, + }; + }), + on(actions.dashboardMetricFilterRemoved, (state, action) => { + const nextMetricFilters = new Map(state.dashboardFilters.metrics); + nextMetricFilters.delete(action.name); + return { ...state, dashboardFilters: { diff --git a/tensorboard/webapp/hparams/_redux/hparams_reducers_test.ts b/tensorboard/webapp/hparams/_redux/hparams_reducers_test.ts index 16ff659260..ce3e0ace18 100644 --- a/tensorboard/webapp/hparams/_redux/hparams_reducers_test.ts +++ b/tensorboard/webapp/hparams/_redux/hparams_reducers_test.ts @@ -752,4 +752,114 @@ describe('hparams/_redux/hparams_reducers_test', () => { }); }); }); + + describe('dashboardHparamFilterRemoved', () => { + it('removes entry from dashboardFilters', () => { + const state = buildHparamsState({ + dashboardFilters: { + hparams: new Map([ + [ + 'hparam1', + { + type: DomainType.INTERVAL, + includeUndefined: true, + minValue: 5, + maxValue: 10, + filterLowerBound: 5, + filterUpperBound: 10, + }, + ], + [ + 'hparam2', + { + type: DomainType.INTERVAL, + includeUndefined: true, + minValue: 2, + maxValue: 20, + filterLowerBound: 2, + filterUpperBound: 20, + }, + ], + ]), + }, + }); + const state2 = reducers( + state, + actions.dashboardHparamFilterRemoved({ + name: 'hparam1', + }) + ); + expect(state2.dashboardFilters).toEqual({ + hparams: new Map([ + [ + 'hparam2', + { + type: DomainType.INTERVAL, + includeUndefined: true, + minValue: 2, + maxValue: 20, + filterLowerBound: 2, + filterUpperBound: 20, + }, + ], + ]), + metrics: new Map(), + }); + }); + }); + + describe('dashboardMetricFilterRemoved', () => { + it('removes entry from dashboardFilters', () => { + const state = buildHparamsState({ + dashboardFilters: { + metrics: new Map([ + [ + 'metric 1', + { + type: DomainType.INTERVAL, + includeUndefined: true, + minValue: 5, + maxValue: 10, + filterLowerBound: 5, + filterUpperBound: 10, + }, + ], + [ + 'metric 2', + { + type: DomainType.INTERVAL, + includeUndefined: true, + minValue: 2, + maxValue: 20, + filterLowerBound: 2, + filterUpperBound: 20, + }, + ], + ]), + }, + }); + const state2 = reducers( + state, + actions.dashboardMetricFilterRemoved({ + name: 'metric 1', + }) + ); + expect(state2.dashboardFilters).toEqual({ + hparams: new Map(), + metrics: new Map([ + [ + 'metric 2', + { + type: DomainType.INTERVAL, + includeUndefined: true, + minValue: 2, + maxValue: 20, + filterLowerBound: 2, + filterUpperBound: 20, + }, + ], + ]), + }); + }); + }); }); diff --git a/tensorboard/webapp/runs/effects/BUILD b/tensorboard/webapp/runs/effects/BUILD index f05ca49191..7dc86b69a1 100644 --- a/tensorboard/webapp/runs/effects/BUILD +++ b/tensorboard/webapp/runs/effects/BUILD @@ -17,10 +17,12 @@ tf_ng_module( "//tensorboard/webapp/app_routing:types", "//tensorboard/webapp/app_routing/actions", "//tensorboard/webapp/core/actions", + "//tensorboard/webapp/hparams/_redux:hparams_actions", "//tensorboard/webapp/runs:types", "//tensorboard/webapp/runs/actions", "//tensorboard/webapp/runs/data_source", "//tensorboard/webapp/types", + "//tensorboard/webapp/widgets/data_table:types", "@npm//@angular/core", "@npm//@ngrx/effects", "@npm//@ngrx/store", @@ -43,11 +45,13 @@ tf_ts_library( "//tensorboard/webapp/app_routing:testing", "//tensorboard/webapp/app_routing:types", "//tensorboard/webapp/core/actions", + "//tensorboard/webapp/hparams/_redux:hparams_actions", "//tensorboard/webapp/runs/actions", "//tensorboard/webapp/runs/data_source", "//tensorboard/webapp/runs/data_source:testing", "//tensorboard/webapp/testing:utils", "//tensorboard/webapp/types", + "//tensorboard/webapp/widgets/data_table:types", "@npm//@ngrx/effects", "@npm//@ngrx/store", "@npm//@types/jasmine", diff --git a/tensorboard/webapp/runs/effects/runs_effects.ts b/tensorboard/webapp/runs/effects/runs_effects.ts index f0f719187e..df9f94692d 100644 --- a/tensorboard/webapp/runs/effects/runs_effects.ts +++ b/tensorboard/webapp/runs/effects/runs_effects.ts @@ -39,12 +39,14 @@ import { } from '../../selectors'; import {DataLoadState, LoadState} from '../../types/data'; import * as actions from '../actions'; +import * as hparamsActions from '../../hparams/_redux/hparams_actions'; import { HparamsAndMetadata, Run, RunsDataSource, } from '../data_source/runs_data_source_types'; import {ExperimentIdToRunsAndMetadata} from '../types'; +import {ColumnHeaderType} from '../../widgets/data_table/types'; /** * Runs effect for fetching data from the backend. @@ -172,6 +174,36 @@ export class RunsEffects { {dispatch: false} ); + /** + * Removes hparam filter when column is removed. + * + * @export + */ + removeHparamFilterWhenColumnIsRemoved$ = createEffect( + () => + this.actions$.pipe( + ofType(actions.runsTableHeaderRemoved), + tap(({header}) => { + if (header.type === ColumnHeaderType.HPARAM) { + this.store.dispatch( + hparamsActions.dashboardHparamFilterRemoved({ + name: header.name, + }) + ); + return; + } + if (header.type === ColumnHeaderType.METRIC) { + this.store.dispatch( + hparamsActions.dashboardMetricFilterRemoved({ + name: header.name, + }) + ); + } + }) + ), + {dispatch: false} + ); + /** * IMPORTANT: actions are dispatched even when there are no experiments to * fetch. diff --git a/tensorboard/webapp/runs/effects/runs_effects_test.ts b/tensorboard/webapp/runs/effects/runs_effects_test.ts index f27c830ab1..4149d4d445 100644 --- a/tensorboard/webapp/runs/effects/runs_effects_test.ts +++ b/tensorboard/webapp/runs/effects/runs_effects_test.ts @@ -26,6 +26,7 @@ import { import {RouteKind} from '../../app_routing/types'; import {State} from '../../app_state'; import * as coreActions from '../../core/actions'; +import * as hparamsActions from '../../hparams/_redux/hparams_actions'; import { getActiveRoute, getExperimentIdsFromRoute, @@ -42,6 +43,7 @@ import { TestingRunsDataSource, } from '../data_source/testing'; import {RunsEffects} from './index'; +import {ColumnHeaderType} from '../../widgets/data_table/types'; function createRun(override: Partial = {}) { return { @@ -947,4 +949,50 @@ describe('runs_effects', () => { }); }); }); + + describe('removeHparamFilterWhenColumnIsRemoved$', () => { + beforeEach(() => { + effects.removeHparamFilterWhenColumnIsRemoved$.subscribe(() => {}); + }); + + it('dispatches dashboardHparamFilterRemoved when column type is hparam', () => { + action.next( + actions.runsTableHeaderRemoved({ + header: { + type: ColumnHeaderType.HPARAM, + name: 'some_hparam', + enabled: true, + displayName: 'Some Hparam', + }, + }) + ); + store.refreshState(); + + expect(actualActions).toEqual([ + hparamsActions.dashboardHparamFilterRemoved({ + name: 'some_hparam', + }), + ]); + }); + + it('dispatches dashboardMetricFilterRemoved when column type is metric', () => { + action.next( + actions.runsTableHeaderRemoved({ + header: { + type: ColumnHeaderType.METRIC, + name: 'some_metric', + enabled: true, + displayName: 'Some Metric', + }, + }) + ); + store.refreshState(); + + expect(actualActions).toEqual([ + hparamsActions.dashboardMetricFilterRemoved({ + name: 'some_metric', + }), + ]); + }); + }); }); diff --git a/tensorboard/webapp/widgets/data_table/types.ts b/tensorboard/webapp/widgets/data_table/types.ts index a1e1814c74..22037d5a8e 100644 --- a/tensorboard/webapp/widgets/data_table/types.ts +++ b/tensorboard/webapp/widgets/data_table/types.ts @@ -39,6 +39,7 @@ export enum ColumnHeaderType { MEAN = 'MEAN', RAW_CHANGE = 'RAW_CHANGE', HPARAM = 'HPARAM', + METRIC = 'METRIC', CUSTOM = 'CUSTOM', }