Skip to content

Commit 7df7ad9

Browse files
authored
Group colours by regex of the experiment name (UI). (#6847)
**NOTE**: This PR is a continuation of #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 5c8b643 commit 7df7ad9

13 files changed

+233
-43
lines changed

tensorboard/webapp/angular/BUILD

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,15 @@ tf_ts_library(
132132
],
133133
)
134134

135+
# This is a dummy rule used as a @angular/material/core dependency.
136+
tf_ts_library(
137+
name = "expect_angular_material_core",
138+
srcs = [],
139+
deps = [
140+
"@npm//@angular/material",
141+
],
142+
)
143+
135144
# This is a dummy rule used as a @angular/material/dialog dependency.
136145
tf_ts_library(
137146
name = "expect_angular_material_dialog",

tensorboard/webapp/feature_flag/store/feature_flag_metadata.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ export const FeatureFlagMetadataMap: FeatureFlagMetadataMapType<FeatureFlags> =
125125
queryParamOverride: 'enableGlobalPins',
126126
parseValue: parseBoolean,
127127
},
128+
enableColorByExperiment: {
129+
defaultValue: false,
130+
queryParamOverride: 'enableColorByExperiment',
131+
parseValue: parseBoolean,
132+
},
128133
};
129134

130135
/**

tensorboard/webapp/feature_flag/store/feature_flag_selectors.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,10 @@ export const getEnableGlobalPins = createSelector(
160160
return flags.enableGlobalPins;
161161
}
162162
);
163+
164+
export const getEnableColorByExperiment = createSelector(
165+
getFeatureFlags,
166+
(flags: FeatureFlags): boolean => {
167+
return flags.enableColorByExperiment;
168+
}
169+
);

tensorboard/webapp/feature_flag/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,6 @@ export interface FeatureFlags {
5252
enableSuggestedCards: boolean;
5353
// Persists pinned scalar cards across multiple experiments.
5454
enableGlobalPins: boolean;
55+
// Adds a new mode of coloring, by matching the regexp of experiment name.
56+
enableColorByExperiment: boolean;
5557
}

tensorboard/webapp/runs/views/runs_table/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,14 @@ tf_ng_module(
8686
"//tensorboard/webapp/angular:expect_angular_material_menu",
8787
"//tensorboard/webapp/angular:expect_angular_material_paginator",
8888
"//tensorboard/webapp/angular:expect_angular_material_progress_spinner",
89+
"//tensorboard/webapp/angular:expect_angular_material_select",
8990
"//tensorboard/webapp/angular:expect_angular_material_sort",
9091
"//tensorboard/webapp/angular:expect_angular_material_table",
9192
"//tensorboard/webapp/app_routing",
9293
"//tensorboard/webapp/app_routing:types",
9394
"//tensorboard/webapp/core/actions",
9495
"//tensorboard/webapp/experiments:types",
96+
"//tensorboard/webapp/experiments/store:selectors",
9597
"//tensorboard/webapp/feature_flag/store",
9698
"//tensorboard/webapp/hparams",
9799
"//tensorboard/webapp/hparams:types",
@@ -118,6 +120,7 @@ tf_ng_module(
118120
"//tensorboard/webapp/widgets/range_input:types",
119121
"@npm//@angular/common",
120122
"@npm//@angular/core",
123+
"@npm//@angular/forms",
121124
"@npm//@ngrx/store",
122125
"@npm//ngx-color-picker",
123126
"@npm//rxjs",

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,26 @@ <h1 mat-dialog-title>Color runs by regex</h1>
1919

2020
<mat-dialog-content>
2121
<p>Enter a regex with capturing groups to match against run names:</p>
22+
<mat-form-field>
23+
<mat-label>Regex type</mat-label>
24+
<mat-select
25+
[value]="regexTypeFn(selectedGroupBy)"
26+
(selectionChange)="regexTypeChange($event)"
27+
>
28+
<mat-option value="{{ REGEX_BY_RUN_STR }}">Run Name</mat-option>
29+
<mat-option
30+
*ngIf="enableColorByExperiment"
31+
value="{{ REGEX_BY_EXP_STR }}"
32+
>Experiment Name</mat-option
33+
>
34+
</mat-select>
35+
</mat-form-field>
2236
<mat-form-field>
2337
<input
2438
matInput
2539
#regexStringInput
2640
value="{{ regexString }}"
27-
(keydown.enter)="onEnter($event.target.value)"
41+
(keydown.enter)="onEnter()"
2842
(input)="regexInputChange($event.target.value)"
2943
i18n-aria-label="Color Runs by Regex Query"
3044
aria-label="Color Runs by Regex Query"
@@ -95,7 +109,7 @@ <h4>Color group preview</h4>
95109
mat-raised-button
96110
color="primary"
97111
mat-dialog-close
98-
(click)="onSaveClick(regexStringInput.value)"
112+
(click)="onSaveClick()"
99113
>
100114
Save
101115
</button>

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

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ import {
2222
ViewChild,
2323
} from '@angular/core';
2424
import {MatDialogRef} from '@angular/material/dialog';
25-
import {Run} from '../../types';
25+
import {GroupByKey, Run} from '../../types';
26+
import {MatSelectChange} from '@angular/material/select';
27+
import {memoize} from '../../../util/memoize';
2628

2729
export interface ColorGroup {
2830
groupId: string;
@@ -39,9 +41,16 @@ export interface ColorGroup {
3941
export class RegexEditDialogComponent {
4042
@Input() regexString!: string;
4143
@Input() colorRunPairList!: ColorGroup[];
44+
@Input() selectedGroupBy!: GroupByKey;
45+
@Input() enableColorByExperiment!: boolean;
4246

43-
@Output() onSave = new EventEmitter<string>();
47+
@Output() onSave = new EventEmitter();
4448
@Output() regexInputOnChange = new EventEmitter<string>();
49+
@Output() regexTypeOnChange = new EventEmitter<GroupByKey>();
50+
51+
// Constants
52+
REGEX_BY_RUN_STR = 'regex_by_run';
53+
REGEX_BY_EXP_STR = 'regex_by_exp';
4554

4655
timeOutId = 0;
4756
@ViewChild('regexStringInput', {static: true})
@@ -52,20 +61,27 @@ export class RegexEditDialogComponent {
5261
private readonly hostElRef: ElementRef
5362
) {}
5463

64+
regexTypeFn = memoize(this.internalRegexTypeFn.bind(this));
65+
private internalRegexTypeFn(key: GroupByKey) {
66+
return key === GroupByKey.REGEX_BY_EXP
67+
? this.REGEX_BY_EXP_STR
68+
: this.REGEX_BY_RUN_STR;
69+
}
70+
5571
private resetFocus() {
5672
if (!this.hostElRef.nativeElement.contains(document.activeElement)) {
5773
const input = this.regexStringInput.nativeElement;
5874
input.focus();
5975
}
6076
}
6177

62-
onEnter(regexString: string) {
63-
this.onSaveClick(regexString);
78+
onEnter() {
79+
this.onSaveClick();
6480
this.dialogRef.close();
6581
}
6682

67-
onSaveClick(regexString: string) {
68-
this.onSave.emit(regexString);
83+
onSaveClick() {
84+
this.onSave.emit();
6985
}
7086

7187
fillExample(regexExample: string): void {
@@ -81,4 +97,12 @@ export class RegexEditDialogComponent {
8197
clearTimeout(this.timeOutId);
8298
this.timeOutId = setTimeout(this.resetFocus.bind(this), 0);
8399
}
100+
101+
regexTypeChange(event: MatSelectChange) {
102+
this.regexTypeOnChange.emit(
103+
event.value === this.REGEX_BY_RUN_STR
104+
? GroupByKey.REGEX
105+
: GroupByKey.REGEX_BY_EXP
106+
);
107+
}
84108
}

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

Lines changed: 78 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,24 @@ import {
2121
debounceTime,
2222
filter,
2323
map,
24+
shareReplay,
2425
startWith,
2526
take,
2627
} from 'rxjs/operators';
2728
import {State} from '../../../app_state';
28-
import {getDarkModeEnabled} from '../../../selectors';
29+
import {
30+
getDarkModeEnabled,
31+
getEnableColorByExperiment,
32+
} from '../../../selectors';
2933
import {selectors as settingsSelectors} from '../../../settings/';
3034
import {runGroupByChanged} from '../../actions';
3135
import {
3236
getColorGroupRegexString,
3337
getRunIdsForExperiment,
38+
getRunGroupBy,
3439
getRuns,
3540
} from '../../store/runs_selectors';
41+
import {getDashboardExperimentNames} from '../../../selectors';
3642
import {groupRuns} from '../../store/utils';
3743
import {GroupByKey, Run} from '../../types';
3844
import {ColorGroup} from './regex_edit_dialog_component';
@@ -44,8 +50,11 @@ const INPUT_CHANGE_DEBOUNCE_INTERVAL_MS = 500;
4450
template: `<regex-edit-dialog-component
4551
[regexString]="groupByRegexString$ | async"
4652
[colorRunPairList]="colorRunPairList$ | async"
47-
(onSave)="onSave($event)"
53+
[selectedGroupBy]="groupByRegexType$ | async"
54+
[enableColorByExperiment]="enableColorByExperiment$ | async"
55+
(onSave)="onSave()"
4856
(regexInputOnChange)="onRegexInputOnChange($event)"
57+
(regexTypeOnChange)="onRegexTypeOnChange($event)"
4958
></regex-edit-dialog-component>`,
5059
styles: [
5160
`
@@ -62,15 +71,39 @@ export class RegexEditDialogContainer {
6271
private readonly experimentIds: string[];
6372
private readonly runIdToEid$: Observable<Record<string, string>>;
6473
private readonly allRuns$: Observable<Run[]>;
74+
private readonly expNameByExpId$: Observable<Record<string, string>> =
75+
this.store.select(getDashboardExperimentNames);
76+
readonly enableColorByExperiment$: Observable<boolean> = this.store.select(
77+
getEnableColorByExperiment
78+
);
79+
80+
// Tentative regex string and type are used because we don't want to change the state
81+
// every time we type in or select the dropdown option.
6582
private readonly tentativeRegexString$: Subject<string> =
6683
new Subject<string>();
84+
private readonly tentativeRegexType$: Subject<GroupByKey> =
85+
new Subject<GroupByKey>();
6786

6887
readonly groupByRegexString$: Observable<string> = defer(() => {
6988
return merge(
7089
this.store.select(getColorGroupRegexString).pipe(take(1)),
7190
this.tentativeRegexString$
7291
);
73-
}).pipe(startWith(''));
92+
}).pipe(startWith(''), shareReplay(1));
93+
94+
readonly groupByRegexType$: Observable<GroupByKey> = merge(
95+
this.store.select(getRunGroupBy).pipe(
96+
take(1),
97+
map((group) => group.key)
98+
),
99+
this.tentativeRegexType$
100+
).pipe(
101+
startWith(GroupByKey.REGEX),
102+
filter(
103+
(key) => key === GroupByKey.REGEX || key === GroupByKey.REGEX_BY_EXP
104+
),
105+
shareReplay(1)
106+
);
74107

75108
readonly colorRunPairList$: Observable<ColorGroup[]> = defer(() => {
76109
return this.groupByRegexString$.pipe(
@@ -84,18 +117,33 @@ export class RegexEditDialogContainer {
84117
}
85118
}),
86119
combineLatestWith(
120+
this.groupByRegexType$,
87121
this.allRuns$,
88122
this.runIdToEid$,
123+
this.expNameByExpId$,
89124
this.store.select(settingsSelectors.getColorPalette),
90125
this.store.select(getDarkModeEnabled)
91126
),
92127
map(
93-
([regexString, allRuns, runIdToEid, colorPalette, darkModeEnabled]) => {
128+
([
129+
regexString,
130+
regexType,
131+
allRuns,
132+
runIdToEid,
133+
expNameByExpId,
134+
colorPalette,
135+
darkModeEnabled,
136+
]) => {
94137
const groupBy = {
95-
key: GroupByKey.REGEX,
138+
key: regexType,
96139
regexString,
97140
};
98-
const groups = groupRuns(groupBy, allRuns, runIdToEid);
141+
const groups = groupRuns(
142+
groupBy,
143+
allRuns,
144+
runIdToEid,
145+
expNameByExpId
146+
);
99147
const groupKeyToColorString = new Map<string, string>();
100148
const colorRunPairList: ColorGroup[] = [];
101149

@@ -121,7 +169,10 @@ export class RegexEditDialogContainer {
121169
constructor(
122170
private readonly store: Store<State>,
123171
public dialogRef: MatDialogRef<RegexEditDialogContainer>,
124-
@Inject(MAT_DIALOG_DATA) data: {experimentIds: string[]}
172+
@Inject(MAT_DIALOG_DATA)
173+
data: {
174+
experimentIds: string[];
175+
}
125176
) {
126177
this.experimentIds = data.experimentIds;
127178

@@ -158,13 +209,26 @@ export class RegexEditDialogContainer {
158209
this.tentativeRegexString$.next(regexString);
159210
}
160211

161-
onSave(regexString: string): void {
162-
this.store.dispatch(
163-
runGroupByChanged({
164-
experimentIds: this.experimentIds,
165-
groupBy: {key: GroupByKey.REGEX, regexString: regexString},
166-
})
167-
);
212+
onRegexTypeOnChange(regexType: GroupByKey) {
213+
this.tentativeRegexType$.next(regexType);
214+
}
215+
216+
onSave(): void {
217+
combineLatest([
218+
this.groupByRegexString$,
219+
this.groupByRegexType$,
220+
this.expNameByExpId$,
221+
]).subscribe(([regexString, key, expNameByExpId]) => {
222+
if (regexString) {
223+
this.store.dispatch(
224+
runGroupByChanged({
225+
experimentIds: this.experimentIds,
226+
groupBy: {key, regexString},
227+
expNameByExpId,
228+
})
229+
);
230+
}
231+
});
168232
}
169233
}
170234

0 commit comments

Comments
 (0)