Skip to content

Commit d4df603

Browse files
authored
HParams: Create hparams effects to trigger data to be fetched from the hparams plugin (#6540)
## Motivation for features / changes This is a natural continuation of the work done in #6535 I added a new effects file which fires a newly created action whenever the runs table is shown, a navigation occurs, or the user reloads the data. The data loading is then filtered and throttled by the existence of experiment ids. Finally the raw response from the `hparams_data_source` is written to the redux state. In a future PR I will add a series of selectors to remap and inject that data into the runs metadata. ## Alternate designs / implementations considered (or N/A) I've opted to include the effects and data source in the hparams module but hide the functionality behind the feature flag. This should make it easier to develop changes downstream from this pr. In the future I will gate the existing behavior behind the inverse of this flag and that should allow us to test the full flow without needing to remove the old code (I have tested removing the old code). The reducer change could have been split out from this but it's quite small and I thought including it made it easier to see the entire write path. Because I added two new attributes to the hparams redux state there are a few changes to test utilities that are not strictly necessary but are useful to have.
1 parent 4669e6d commit d4df603

13 files changed

+597
-97
lines changed

tensorboard/webapp/hparams/_redux/BUILD

+40
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,13 @@ tf_ng_module(
3030
"hparams_module.ts",
3131
],
3232
deps = [
33+
":hparams_data_source",
34+
":hparams_effects",
3335
":hparams_reducers",
3436
":types",
3537
"//tensorboard/webapp/hparams:types",
3638
"@npm//@angular/core",
39+
"@npm//@ngrx/effects",
3740
"@npm//@ngrx/store",
3841
],
3942
)
@@ -95,6 +98,31 @@ tf_ng_module(
9598
],
9699
)
97100

101+
tf_ng_module(
102+
name = "hparams_effects",
103+
srcs = [
104+
"hparams_effects.ts",
105+
],
106+
deps = [
107+
":hparams_actions",
108+
":hparams_data_source",
109+
":types",
110+
"//tensorboard/webapp:app_state",
111+
"//tensorboard/webapp/app_routing:types",
112+
"//tensorboard/webapp/app_routing/actions",
113+
"//tensorboard/webapp/app_routing/store",
114+
"//tensorboard/webapp/core/actions",
115+
"//tensorboard/webapp/feature_flag/store",
116+
"//tensorboard/webapp/hparams:types",
117+
"//tensorboard/webapp/runs/actions",
118+
"//tensorboard/webapp/webapp_data_source:http_client",
119+
"@npm//@angular/core",
120+
"@npm//@ngrx/effects",
121+
"@npm//@ngrx/store",
122+
"@npm//rxjs",
123+
],
124+
)
125+
98126
tf_ts_library(
99127
name = "testing",
100128
testonly = True,
@@ -105,6 +133,7 @@ tf_ts_library(
105133
":types",
106134
":utils",
107135
"//tensorboard/webapp/hparams:_types",
136+
"//tensorboard/webapp/util:types",
108137
],
109138
)
110139

@@ -113,6 +142,7 @@ tf_ts_library(
113142
testonly = True,
114143
srcs = [
115144
"hparams_data_source_test.ts",
145+
"hparams_effects_test.ts",
116146
"hparams_reducers_test.ts",
117147
"hparams_selectors_test.ts",
118148
"hparams_selectors_utils_test.ts",
@@ -121,16 +151,26 @@ tf_ts_library(
121151
deps = [
122152
":hparams_actions",
123153
":hparams_data_source",
154+
":hparams_effects",
124155
":hparams_reducers",
125156
":hparams_selectors",
126157
":testing",
127158
":utils",
159+
"//tensorboard/webapp:app_state",
160+
"//tensorboard/webapp:selectors",
128161
"//tensorboard/webapp/angular:expect_angular_core_testing",
162+
"//tensorboard/webapp/angular:expect_ngrx_store_testing",
163+
"//tensorboard/webapp/app_routing:types",
164+
"//tensorboard/webapp/app_routing/actions",
165+
"//tensorboard/webapp/core/actions",
129166
"//tensorboard/webapp/hparams:types",
130167
"//tensorboard/webapp/runs/actions",
131168
"//tensorboard/webapp/runs/data_source:testing",
132169
"//tensorboard/webapp/runs/store:testing",
170+
"//tensorboard/webapp/testing:utils",
133171
"//tensorboard/webapp/webapp_data_source:http_client_testing",
172+
"@npm//@ngrx/effects",
134173
"@npm//@types/jasmine",
174+
"@npm//rxjs",
135175
],
136176
)

tensorboard/webapp/hparams/_redux/hparams_actions.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ limitations under the License.
1717
*/
1818

1919
import {createAction, props} from '@ngrx/store';
20-
import {DiscreteHparamValues} from '../types';
20+
import {
21+
DiscreteHparamValues,
22+
HparamAndMetricSpec,
23+
SessionGroup,
24+
} from '../types';
2125

2226
export const hparamsDiscreteHparamFilterChanged = createAction(
2327
'[Hparams] Hparams Discrete Hparam Filter Changed',
@@ -50,3 +54,11 @@ export const hparamsMetricFilterChanged = createAction(
5054
includeUndefined: boolean;
5155
}>()
5256
);
57+
58+
export const hparamsFetchSessionGroupsSucceeded = createAction(
59+
'[Hparams] Hparams Fetch Session Groups Succeeded',
60+
props<{
61+
hparamsAndMetricsSpecs: HparamAndMetricSpec;
62+
sessionGroups: SessionGroup[];
63+
}>()
64+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/* Copyright 2023 The TensorFlow Authors. All Rights Reserved.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
14+
==============================================================================*/
15+
import {Injectable} from '@angular/core';
16+
import {Actions, createEffect, ofType} from '@ngrx/effects';
17+
import {Store} from '@ngrx/store';
18+
import {Observable, of, throwError, merge} from 'rxjs';
19+
import {
20+
catchError,
21+
filter,
22+
map,
23+
switchMap,
24+
withLatestFrom,
25+
throttleTime,
26+
combineLatestWith,
27+
} from 'rxjs/operators';
28+
29+
import {navigated} from '../../app_routing/actions';
30+
import {
31+
getActiveRoute,
32+
getExperimentIdsFromRoute,
33+
} from '../../app_routing/store/app_routing_selectors';
34+
import {State} from '../../app_state';
35+
import * as coreActions from '../../core/actions';
36+
import * as runsActions from '../../runs/actions/runs_actions';
37+
import {HttpErrorResponse} from '../../webapp_data_source/tb_http_client';
38+
39+
import * as hparamsActions from './hparams_actions';
40+
import {HparamsDataSource} from './hparams_data_source';
41+
import {HparamAndMetricSpec, SessionGroup} from '../types';
42+
import {getEnableHparamsInTimeSeries} from '../../feature_flag/store/feature_flag_selectors';
43+
import {RouteKind} from '../../app_routing/types';
44+
45+
/**
46+
* Effects for fetching the hparams data from the backend.
47+
*/
48+
@Injectable()
49+
export class HparamsEffects {
50+
constructor(
51+
private readonly actions$: Actions,
52+
private readonly store: Store<State>,
53+
private readonly dataSource: HparamsDataSource
54+
) {}
55+
56+
private readonly runTableShown$: Observable<string[]> = this.actions$.pipe(
57+
ofType(runsActions.runTableShown),
58+
map(({experimentIds}) => experimentIds)
59+
);
60+
61+
private readonly loadHparamsOnNavigationOrReload$: Observable<string[]> =
62+
this.actions$.pipe(
63+
ofType(navigated, coreActions.reload, coreActions.manualReload),
64+
withLatestFrom(this.store.select(getExperimentIdsFromRoute)),
65+
filter(([, experimentIds]) => Boolean(experimentIds)),
66+
map(([, experimentIds]) => experimentIds as string[])
67+
);
68+
69+
/** @export */
70+
loadHparamsData$ = createEffect(() => {
71+
return merge(
72+
this.runTableShown$,
73+
this.loadHparamsOnNavigationOrReload$
74+
).pipe(
75+
combineLatestWith(
76+
this.store.select(getEnableHparamsInTimeSeries),
77+
this.store.select(getActiveRoute)
78+
),
79+
filter(
80+
([, getEnableHparamsInTimeSeries]) => getEnableHparamsInTimeSeries
81+
),
82+
filter(
83+
([, , activeRoute]) =>
84+
activeRoute?.routeKind === RouteKind.EXPERIMENT ||
85+
activeRoute?.routeKind === RouteKind.COMPARE_EXPERIMENT
86+
),
87+
throttleTime(10),
88+
switchMap(([experimentIds]) =>
89+
this.loadHparamsForExperiments(experimentIds)
90+
),
91+
map((resp) => hparamsActions.hparamsFetchSessionGroupsSucceeded(resp))
92+
);
93+
});
94+
95+
private loadHparamsForExperiments(experimentIds: string[]): Observable<{
96+
hparamsAndMetricsSpecs: HparamAndMetricSpec;
97+
sessionGroups: SessionGroup[];
98+
}> {
99+
return this.dataSource.fetchExperimentInfo(experimentIds).pipe(
100+
switchMap((hparamsAndMetricsSpecs) => {
101+
return this.dataSource
102+
.fetchSessionGroups(experimentIds, hparamsAndMetricsSpecs)
103+
.pipe(
104+
catchError((error) => {
105+
// HParam plugin return 400 when there are no hparams
106+
// for an experiment.
107+
if (error instanceof HttpErrorResponse && error.status === 400) {
108+
return of([] as SessionGroup[]);
109+
}
110+
return throwError(() => error);
111+
}),
112+
map((sessionGroups) => ({hparamsAndMetricsSpecs, sessionGroups}))
113+
);
114+
})
115+
);
116+
}
117+
}

0 commit comments

Comments
 (0)