Skip to content

Commit aa54272

Browse files
authored
Group colours by regex of the experiment name. (#6846)
## Motivation for features / changes Users want to color runs by the regex string of the corresponding to runs experiment names. ## Technical description of changes - Added new GroupBy type, REGEX_BY_EXP. - Added a dropdown in dialog window, so users could select between regex for run name or experiment name. ## Screenshots of UI changes (or N/A) N/A since internal change. ## Detailed steps to verify changes work correctly (as executed by you) - Run tensorboard.corp server. - Click on color grouping icon and select `Regex`. - Select `Experiment Name` in dropdown. ## Alternate designs / implementations considered (or N/A) N/A
1 parent 29850f6 commit aa54272

File tree

9 files changed

+611
-45
lines changed

9 files changed

+611
-45
lines changed

tensorboard/webapp/runs/actions/runs_actions.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export const fetchRunsSucceeded = createAction(
4040
experimentIds: string[];
4141
runsForAllExperiments: Run[];
4242
newRuns: ExperimentIdToRuns;
43+
expNameByExpId?: Record<string, string>;
4344
}>()
4445
);
4546

@@ -79,7 +80,11 @@ export const runColorChanged = createAction(
7980

8081
export const runGroupByChanged = createAction(
8182
'[Runs] Run Group By Changed',
82-
props<{experimentIds: string[]; groupBy: GroupBy}>()
83+
props<{
84+
experimentIds: string[];
85+
groupBy: GroupBy;
86+
expNameByExpId?: Record<string, string>;
87+
}>()
8388
);
8489

8590
/**

tensorboard/webapp/runs/effects/runs_effects.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
getExperimentIdsFromRoute,
3737
getRuns,
3838
getRunsLoadState,
39+
getDashboardExperimentNames,
3940
} from '../../selectors';
4041
import {DataLoadState, LoadState} from '../../types/data';
4142
import * as actions from '../actions';
@@ -217,12 +218,15 @@ export class RunsEffects {
217218
}
218219
return {newRuns, runsForAllExperiments};
219220
}),
220-
tap(({newRuns, runsForAllExperiments}) => {
221+
withLatestFrom(this.store.select(getDashboardExperimentNames)),
222+
tap(([runsData, expNameByExpId]) => {
223+
const {newRuns, runsForAllExperiments} = runsData;
221224
this.store.dispatch(
222225
actions.fetchRunsSucceeded({
223226
experimentIds,
224227
newRuns,
225228
runsForAllExperiments,
229+
expNameByExpId,
226230
})
227231
);
228232
}),

tensorboard/webapp/runs/effects/runs_effects_test.ts

+41
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import * as coreActions from '../../core/actions';
2929
import * as hparamsActions from '../../hparams/_redux/hparams_actions';
3030
import {
3131
getActiveRoute,
32+
getDashboardExperimentNames,
3233
getExperimentIdsFromRoute,
3334
getRuns,
3435
getRunsLoadState,
@@ -131,6 +132,10 @@ describe('runs_effects', () => {
131132
buildCompareRoute(['exp1:123', 'exp2:456'])
132133
);
133134
store.overrideSelector(getExperimentIdsFromRoute, ['123', '456']);
135+
store.overrideSelector(getDashboardExperimentNames, {
136+
456: 'exp2',
137+
123: 'exp1',
138+
});
134139
const createFooRuns = () => [
135140
createRun({
136141
id: 'foo/runA',
@@ -166,6 +171,10 @@ describe('runs_effects', () => {
166171
runs: createFooRuns(),
167172
},
168173
},
174+
expNameByExpId: {
175+
456: 'exp2',
176+
123: 'exp1',
177+
},
169178
}),
170179
]);
171180
});
@@ -211,6 +220,10 @@ describe('runs_effects', () => {
211220
buildCompareRoute(['exp1:123', ' exp2:456'])
212221
);
213222
store.overrideSelector(getExperimentIdsFromRoute, ['123', '456']);
223+
store.overrideSelector(getDashboardExperimentNames, {
224+
456: 'exp2',
225+
123: 'exp1',
226+
});
214227
store.refreshState();
215228

216229
action.next(specAction());
@@ -244,6 +257,10 @@ describe('runs_effects', () => {
244257
runs: createBarRuns(),
245258
},
246259
},
260+
expNameByExpId: {
261+
456: 'exp2',
262+
123: 'exp1',
263+
},
247264
}),
248265
]);
249266
});
@@ -310,6 +327,10 @@ describe('runs_effects', () => {
310327
buildCompareRoute(['exp1:123', ' exp2:456'])
311328
);
312329
store.overrideSelector(getExperimentIdsFromRoute, ['123', '456']);
330+
store.overrideSelector(getDashboardExperimentNames, {
331+
456: 'exp1',
332+
123: 'exp2',
333+
});
313334
store.refreshState();
314335

315336
action.next(buildNavigatedAction());
@@ -328,13 +349,18 @@ describe('runs_effects', () => {
328349
runs: createBarRuns(),
329350
},
330351
},
352+
expNameByExpId: {
353+
456: 'exp1',
354+
123: 'exp2',
355+
},
331356
}),
332357
]);
333358
});
334359

335360
it('ignores a navigation to same route and experiments (hash changes)', () => {
336361
store.overrideSelector(getActiveRoute, buildRoute());
337362
store.overrideSelector(getExperimentIdsFromRoute, ['123']);
363+
store.overrideSelector(getDashboardExperimentNames, {123: 'exp1'});
338364
const createFooRuns = () => [
339365
createRun({
340366
id: 'foo/runA',
@@ -363,6 +389,7 @@ describe('runs_effects', () => {
363389
experimentIds: ['123'],
364390
runsForAllExperiments: [...createFooRuns()],
365391
newRuns: {},
392+
expNameByExpId: {123: 'exp1'},
366393
}),
367394
]);
368395

@@ -391,6 +418,7 @@ describe('runs_effects', () => {
391418
})
392419
);
393420
store.overrideSelector(getExperimentIdsFromRoute, ['foo']);
421+
store.overrideSelector(getDashboardExperimentNames, {foo: 'exp1'});
394422
store.refreshState();
395423

396424
action.next(buildNavigatedAction());
@@ -404,6 +432,7 @@ describe('runs_effects', () => {
404432
experimentIds: ['foo'],
405433
runsForAllExperiments: [...createFooRuns()],
406434
newRuns: {},
435+
expNameByExpId: {foo: 'exp1'},
407436
}),
408437
]);
409438
});
@@ -457,6 +486,10 @@ describe('runs_effects', () => {
457486
// Emulate navigation to a new experiment route.
458487
store.overrideSelector(getActiveRoute, buildExperimentRouteFromId('456'));
459488
store.overrideSelector(getExperimentIdsFromRoute, ['456']);
489+
store.overrideSelector(getDashboardExperimentNames, {
490+
456: 'exp1',
491+
123: 'exp2',
492+
});
460493
// Force selectors to re-evaluate with a change in store.
461494
store.refreshState();
462495

@@ -480,13 +513,15 @@ describe('runs_effects', () => {
480513
newRuns: {
481514
456: {runs: createBarRuns()},
482515
},
516+
expNameByExpId: {456: 'exp1', 123: 'exp2'},
483517
}),
484518
actions.fetchRunsSucceeded({
485519
experimentIds: ['123'],
486520
runsForAllExperiments: createFooRuns(),
487521
newRuns: {
488522
123: {runs: createFooRuns()},
489523
},
524+
expNameByExpId: {456: 'exp1', 123: 'exp2'},
490525
}),
491526
]);
492527
});
@@ -552,6 +587,10 @@ describe('runs_effects', () => {
552587
});
553588

554589
store.overrideSelector(getExperimentIdsFromRoute, ['foo']);
590+
store.overrideSelector(getDashboardExperimentNames, {
591+
foo: 'exp1',
592+
bar: 'exp2',
593+
});
555594
selectSpy
556595
.withArgs(getRuns, {experimentId: 'foo'})
557596
.and.returnValue(runsSubject);
@@ -611,6 +650,7 @@ describe('runs_effects', () => {
611650
runs: createFooAfterRuns(),
612651
},
613652
},
653+
expNameByExpId: {foo: 'exp1', bar: 'exp2'},
614654
}),
615655
actions.fetchRunsSucceeded({
616656
experimentIds: ['foo', 'bar'],
@@ -623,6 +663,7 @@ describe('runs_effects', () => {
623663
runs: createBarRuns(),
624664
},
625665
},
666+
expNameByExpId: {foo: 'exp1', bar: 'exp2'},
626667
}),
627668
]);
628669
});

tensorboard/webapp/runs/store/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ tf_ts_library(
6464
],
6565
deps = [
6666
"//tensorboard/webapp/app_routing:namespaced_state_reducer_helper",
67+
"//tensorboard/webapp/experiments:types",
6768
"//tensorboard/webapp/runs:types",
6869
"//tensorboard/webapp/runs/data_source",
6970
"//tensorboard/webapp/types",

tensorboard/webapp/runs/store/runs_reducers.ts

+51-36
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@ const dataReducer: ActionReducer<RunsDataState, Action> = createReducer(
118118
let {colorGroupRegexString, userSetGroupByKey} = state;
119119
if (groupBy) {
120120
const regexString =
121-
groupBy.key === GroupByKey.REGEX
121+
groupBy.key === GroupByKey.REGEX ||
122+
groupBy.key === GroupByKey.REGEX_BY_EXP
122123
? groupBy.regexString
123124
: state.colorGroupRegexString;
124125
colorGroupRegexString = regexString;
@@ -203,48 +204,56 @@ const dataReducer: ActionReducer<RunsDataState, Action> = createReducer(
203204
}
204205
return {...state, runsLoadState: nextRunsLoadState};
205206
}),
206-
on(runsActions.fetchRunsSucceeded, (state, {runsForAllExperiments}) => {
207-
const groupKeyToColorId = new Map(state.groupKeyToColorId);
208-
const defaultRunColorIdForGroupBy = new Map(
209-
state.defaultRunColorIdForGroupBy
210-
);
207+
on(
208+
runsActions.fetchRunsSucceeded,
209+
(state, {runsForAllExperiments, expNameByExpId}) => {
210+
const groupKeyToColorId = new Map(state.groupKeyToColorId);
211+
const defaultRunColorIdForGroupBy = new Map(
212+
state.defaultRunColorIdForGroupBy
213+
);
211214

212-
let groupBy = state.initialGroupBy;
213-
if (state.userSetGroupByKey !== null) {
214-
groupBy = createGroupBy(
215-
state.userSetGroupByKey,
216-
state.colorGroupRegexString
215+
let groupBy = state.initialGroupBy;
216+
if (state.userSetGroupByKey !== null) {
217+
groupBy = createGroupBy(
218+
state.userSetGroupByKey,
219+
state.colorGroupRegexString
220+
);
221+
}
222+
const groups = groupRuns(
223+
groupBy,
224+
runsForAllExperiments,
225+
state.runIdToExpId,
226+
expNameByExpId
217227
);
218-
}
219-
const groups = groupRuns(
220-
groupBy,
221-
runsForAllExperiments,
222-
state.runIdToExpId
223-
);
224228

225-
Object.entries(groups.matches).forEach(([groupId, runs]) => {
226-
const colorId = groupKeyToColorId.get(groupId) ?? groupKeyToColorId.size;
227-
groupKeyToColorId.set(groupId, colorId);
229+
Object.entries(groups.matches).forEach(([groupId, runs]) => {
230+
const colorId =
231+
groupKeyToColorId.get(groupId) ?? groupKeyToColorId.size;
232+
groupKeyToColorId.set(groupId, colorId);
228233

229-
for (const run of runs) {
230-
defaultRunColorIdForGroupBy.set(run.id, colorId);
234+
for (const run of runs) {
235+
defaultRunColorIdForGroupBy.set(run.id, colorId);
236+
}
237+
});
238+
239+
// unassign color for nonmatched runs to apply default unassigned style
240+
for (const run of groups.nonMatches) {
241+
defaultRunColorIdForGroupBy.set(run.id, -1);
231242
}
232-
});
233243

234-
// unassign color for nonmatched runs to apply default unassigned style
235-
for (const run of groups.nonMatches) {
236-
defaultRunColorIdForGroupBy.set(run.id, -1);
244+
return {
245+
...state,
246+
defaultRunColorIdForGroupBy,
247+
groupKeyToColorId,
248+
};
237249
}
238-
239-
return {
240-
...state,
241-
defaultRunColorIdForGroupBy,
242-
groupKeyToColorId,
243-
};
244-
}),
250+
),
245251
on(
246252
runsActions.runGroupByChanged,
247-
(state: RunsDataState, {experimentIds, groupBy}): RunsDataState => {
253+
(
254+
state: RunsDataState,
255+
{experimentIds, groupBy, expNameByExpId}
256+
): RunsDataState => {
248257
// Reset the groupKeyToColorId
249258
const groupKeyToColorId = new Map<string, number>();
250259
const defaultRunColorIdForGroupBy = new Map(
@@ -255,7 +264,12 @@ const dataReducer: ActionReducer<RunsDataState, Action> = createReducer(
255264
.flatMap((experimentId) => state.runIds[experimentId])
256265
.map((runId) => state.runMetadata[runId]);
257266

258-
const groups = groupRuns(groupBy, allRuns, state.runIdToExpId);
267+
const groups = groupRuns(
268+
groupBy,
269+
allRuns,
270+
state.runIdToExpId,
271+
expNameByExpId
272+
);
259273

260274
Object.entries(groups.matches).forEach(([groupId, runs]) => {
261275
const colorId =
@@ -273,7 +287,8 @@ const dataReducer: ActionReducer<RunsDataState, Action> = createReducer(
273287
}
274288

275289
const updatedRegexString =
276-
groupBy.key === GroupByKey.REGEX
290+
groupBy.key === GroupByKey.REGEX ||
291+
groupBy.key === GroupByKey.REGEX_BY_EXP
277292
? groupBy.regexString
278293
: state.colorGroupRegexString;
279294

0 commit comments

Comments
 (0)