Skip to content

Commit 1f88136

Browse files
roseayeonAnuarTB
authored andcommitted
[Global Pins] Added a disabling global pins feature (tensorflow#6831)
## Motivation for features / changes Following tensorflow#6828, Since global pins are applied automatically, this PR allows users disable the global pin feature. ## Technical description of changes 2cf2b11 Added `savingPinsEnabled` in the `MetricsSettings` * Also added a selector getting `settings.savingPinsEnabled` value from the store. dec6a63 Introduced new action `metricsEnableSavingPinsToggled` * If `metricsEnableSavingPinsToggled` is dispatched, it toggles the `savingPinsEnabled` store value. 6ea22fd Added a checkbox UI to the settings view component. * Also added a feature flag guard to the component. If check box is clicked, it dispatched `metricsEnableSavingPinsToggled` action. 568febf Added a new effect `disableSavingPins` * When `metricsEnableSavingPinsToggled` action is called, this effect will call `removeAllScalarPins` method if `savingPinsEnabled` value is false. bbf9050 Added saving pins feature in persistent settings to preserve user preferences. 3ff7cd2 Add buildMetricsSettingsOverrides util in the testing to use in tbcorp ## Screenshots of UI changes (or N/A) ![image](https://github.com/tensorflow/tensorboard/assets/24772412/874dbc2b-f01f-4112-b34b-6b07330ea31f) ## Detailed steps to verify changes work correctly (as executed by you) Unit test passes + created a cl/625208577 to pass TAP Presubmit tests ## Alternate designs / implementations considered (or N/A) N/A ## Action Items in the next PR In the next PR, I'll add a "confirmation dialog" that appears when the user unchecks the checkbox to make the user aware that local storage is disappearing. - [ ] Add the confirmation dialog
1 parent 329f20e commit 1f88136

17 files changed

+270
-12
lines changed

tensorboard/webapp/metrics/actions/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -281,5 +281,9 @@ export const metricsClearAllPinnedCards = createAction(
281281
'[Metrics] Clear all pinned cards'
282282
);
283283

284+
export const metricsEnableSavingPinsToggled = createAction(
285+
'[Metrics] Enable Saving Pins Toggled'
286+
);
287+
284288
// TODO(jieweiwu): Delete after internal code is updated.
285289
export const stepSelectorTimeSelectionChanged = timeSelectionChanged;

tensorboard/webapp/metrics/effects/index.ts

+59-11
Original file line numberDiff line numberDiff line change
@@ -268,11 +268,20 @@ export class MetricsEffects implements OnInitEffects {
268268
withLatestFrom(
269269
this.getVisibleCardFetchInfos(),
270270
this.store.select(selectors.getEnableGlobalPins),
271-
this.store.select(selectors.getShouldPersistSettings)
271+
this.store.select(selectors.getShouldPersistSettings),
272+
this.store.select(selectors.getMetricsSavingPinsEnabled)
272273
),
273274
filter(
274-
([, , enableGlobalPins, shouldPersistSettings]) =>
275-
enableGlobalPins && shouldPersistSettings
275+
([
276+
,
277+
,
278+
enableGlobalPinsFeature,
279+
shouldPersistSettings,
280+
isMetricsSavingPinsEnabled,
281+
]) =>
282+
enableGlobalPinsFeature &&
283+
shouldPersistSettings &&
284+
isMetricsSavingPinsEnabled
276285
),
277286
tap(([{cardId, canCreateNewPins, wasPinned}, fetchInfos]) => {
278287
const card = fetchInfos.find((value) => value.id === cardId);
@@ -293,11 +302,19 @@ export class MetricsEffects implements OnInitEffects {
293302
ofType(initAction),
294303
withLatestFrom(
295304
this.store.select(selectors.getEnableGlobalPins),
296-
this.store.select(selectors.getShouldPersistSettings)
305+
this.store.select(selectors.getShouldPersistSettings),
306+
this.store.select(selectors.getMetricsSavingPinsEnabled)
297307
),
298308
filter(
299-
([, enableGlobalPins, shouldPersistSettings]) =>
300-
enableGlobalPins && shouldPersistSettings
309+
([
310+
,
311+
enableGlobalPinsFeature,
312+
shouldPersistSettings,
313+
isMetricsSavingPinsEnabled,
314+
]) =>
315+
enableGlobalPinsFeature &&
316+
shouldPersistSettings &&
317+
isMetricsSavingPinsEnabled
301318
),
302319
tap(() => {
303320
const tags = this.savedPinsDataSource.getSavedScalarPins();
@@ -316,15 +333,46 @@ export class MetricsEffects implements OnInitEffects {
316333
})
317334
);
318335

319-
private readonly removeAllPins$ = this.actions$.pipe(
336+
private readonly removeSavedPinsOnDisable$ = this.actions$.pipe(
320337
ofType(actions.metricsClearAllPinnedCards),
321338
withLatestFrom(
322339
this.store.select(selectors.getEnableGlobalPins),
323-
this.store.select(selectors.getShouldPersistSettings)
340+
this.store.select(selectors.getShouldPersistSettings),
341+
this.store.select(selectors.getMetricsSavingPinsEnabled)
324342
),
325343
filter(
326-
([, enableGlobalPins, shouldPersistSettings]) =>
327-
enableGlobalPins && shouldPersistSettings
344+
([
345+
,
346+
enableGlobalPinsFeature,
347+
shouldPersistSettings,
348+
isMetricsSavingPinsEnabled,
349+
]) =>
350+
enableGlobalPinsFeature &&
351+
shouldPersistSettings &&
352+
isMetricsSavingPinsEnabled
353+
),
354+
tap(() => {
355+
this.savedPinsDataSource.removeAllScalarPins();
356+
})
357+
);
358+
359+
private readonly disableSavingPins$ = this.actions$.pipe(
360+
ofType(actions.metricsEnableSavingPinsToggled),
361+
withLatestFrom(
362+
this.store.select(selectors.getEnableGlobalPins),
363+
this.store.select(selectors.getShouldPersistSettings),
364+
this.store.select(selectors.getMetricsSavingPinsEnabled)
365+
),
366+
filter(
367+
([
368+
,
369+
enableGlobalPins,
370+
getShouldPersistSettings,
371+
getMetricsSavingPinsEnabled,
372+
]) =>
373+
enableGlobalPins &&
374+
getShouldPersistSettings &&
375+
!getMetricsSavingPinsEnabled
328376
),
329377
tap(() => {
330378
this.savedPinsDataSource.removeAllScalarPins();
@@ -375,7 +423,7 @@ export class MetricsEffects implements OnInitEffects {
375423
/**
376424
* Subscribes to: metricsClearAllPinnedCards.
377425
*/
378-
this.removeAllPins$
426+
this.removeSavedPinsOnDisable$
379427
);
380428
},
381429
{dispatch: false}

tensorboard/webapp/metrics/metrics_module.ts

+10
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
getMetricsScalarSmoothing,
3636
getMetricsStepSelectorEnabled,
3737
getMetricsTooltipSort,
38+
getMetricsSavingPinsEnabled,
3839
getRangeSelectionHeaders,
3940
getSingleSelectionHeaders,
4041
isMetricsSettingsPaneOpen,
@@ -125,6 +126,12 @@ export function getMetricsTimeSeriesLinkedTimeEnabled() {
125126
});
126127
}
127128

129+
export function getMetricsTimeSeriesSavingPinsEnabled() {
130+
return createSelector(getMetricsSavingPinsEnabled, (isEnabled) => {
131+
return {savingPinsEnabled: isEnabled};
132+
});
133+
}
134+
128135
export function getSingleSelectionHeadersFactory() {
129136
return createSelector(getSingleSelectionHeaders, (singleSelectionHeaders) => {
130137
return {singleSelectionHeaders};
@@ -188,6 +195,9 @@ export function getRangeSelectionHeadersFactory() {
188195
PersistentSettingsConfigModule.defineGlobalSetting(
189196
getRangeSelectionHeadersFactory
190197
),
198+
PersistentSettingsConfigModule.defineGlobalSetting(
199+
getMetricsTimeSeriesSavingPinsEnabled
200+
),
191201
],
192202
providers: [
193203
{

tensorboard/webapp/metrics/store/metrics_reducers.ts

+16
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,9 @@ const reducer = createReducer(
608608
if (typeof partialSettings.scalarSmoothing === 'number') {
609609
metricsSettings.scalarSmoothing = partialSettings.scalarSmoothing;
610610
}
611+
if (typeof partialSettings.savingPinsEnabled === 'boolean') {
612+
metricsSettings.savingPinsEnabled = partialSettings.savingPinsEnabled;
613+
}
611614

612615
const isSettingsPaneOpen =
613616
partialSettings.timeSeriesSettingsPaneOpened ?? state.isSettingsPaneOpen;
@@ -933,6 +936,19 @@ const reducer = createReducer(
933936
},
934937
};
935938
}),
939+
on(actions.metricsEnableSavingPinsToggled, (state) => {
940+
const nextSavingPinsEnabled = !(
941+
state.settingOverrides.savingPinsEnabled ??
942+
state.settings.savingPinsEnabled
943+
);
944+
return {
945+
...state,
946+
settingOverrides: {
947+
...state.settingOverrides,
948+
savingPinsEnabled: nextSavingPinsEnabled,
949+
},
950+
};
951+
}),
936952
on(
937953
actions.multipleTimeSeriesRequested,
938954
(

tensorboard/webapp/metrics/store/metrics_reducers_test.ts

+22
Original file line numberDiff line numberDiff line change
@@ -1232,6 +1232,25 @@ describe('metrics reducers', () => {
12321232
expect(thirdState.settings.hideEmptyCards).toBe(false);
12331233
expect(thirdState.settingOverrides.hideEmptyCards).toBe(false);
12341234
});
1235+
1236+
it('changes savingPinsEnabled on metricsEnableSavingPinsToggled', () => {
1237+
[{value: true}, {value: false}].forEach(({value: initValue}) => {
1238+
const prevState = buildMetricsState({
1239+
settings: buildMetricsSettingsState({
1240+
savingPinsEnabled: initValue,
1241+
}),
1242+
settingOverrides: {},
1243+
});
1244+
1245+
const nextState = reducers(
1246+
prevState,
1247+
actions.metricsEnableSavingPinsToggled()
1248+
);
1249+
1250+
expect(nextState.settings.savingPinsEnabled).toBe(initValue);
1251+
expect(nextState.settingOverrides.savingPinsEnabled).toBe(!initValue);
1252+
});
1253+
});
12351254
});
12361255

12371256
describe('loading time series data', () => {
@@ -3142,6 +3161,7 @@ describe('metrics reducers', () => {
31423161
scalarSmoothing: 0.3,
31433162
ignoreOutliers: false,
31443163
tooltipSort: TooltipSort.ASCENDING,
3164+
savingPinsEnabled: true,
31453165
}),
31463166
settingOverrides: {
31473167
scalarSmoothing: 0.5,
@@ -3155,13 +3175,15 @@ describe('metrics reducers', () => {
31553175
partialSettings: {
31563176
ignoreOutliers: true,
31573177
tooltipSort: TooltipSort.DESCENDING,
3178+
savingPinsEnabled: false,
31583179
},
31593180
})
31603181
);
31613182

31623183
expect(nextState.settings.scalarSmoothing).toBe(0.3);
31633184
expect(nextState.settings.ignoreOutliers).toBe(true);
31643185
expect(nextState.settings.tooltipSort).toBe(TooltipSort.DESCENDING);
3186+
expect(nextState.settings.savingPinsEnabled).toBe(false);
31653187
expect(nextState.settingOverrides.scalarSmoothing).toBe(0.5);
31663188
expect(nextState.settingOverrides.tooltipSort).toBe(
31673189
TooltipSort.ALPHABETICAL

tensorboard/webapp/metrics/store/metrics_selectors.ts

+5
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,11 @@ export const getMetricsImageShowActualSize = createSelector(
382382
(settings): boolean => settings.imageShowActualSize
383383
);
384384

385+
export const getMetricsSavingPinsEnabled = createSelector(
386+
selectSettings,
387+
(settings): boolean => settings.savingPinsEnabled
388+
);
389+
385390
export const getMetricsTagFilter = createSelector(
386391
selectMetricsState,
387392
(state): string => state.tagFilter

tensorboard/webapp/metrics/store/metrics_selectors_test.ts

+14
Original file line numberDiff line numberDiff line change
@@ -1298,6 +1298,20 @@ describe('metrics selectors', () => {
12981298
);
12991299
expect(selectors.getMetricsCardMinWidth(state)).toBe(400);
13001300
});
1301+
1302+
it('returns savingPinsEnabled when called getMetricsSavingPinsEnabled', () => {
1303+
[{value: true}, {value: false}].forEach(({value}) => {
1304+
selectors.getMetricsSavingPinsEnabled.release();
1305+
const state = appStateFromMetricsState(
1306+
buildMetricsState({
1307+
settings: buildMetricsSettingsState({
1308+
savingPinsEnabled: value,
1309+
}),
1310+
})
1311+
);
1312+
expect(selectors.getMetricsSavingPinsEnabled(state)).toBe(value);
1313+
});
1314+
});
13011315
});
13021316

13031317
describe('getMetricsTagFilter', () => {

tensorboard/webapp/metrics/store/metrics_types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ export interface MetricsSettings {
246246
imageContrastInMilli: number;
247247
imageShowActualSize: boolean;
248248
histogramMode: HistogramMode;
249+
savingPinsEnabled: boolean;
249250
}
250251

251252
export interface MetricsNonNamespacedState {
@@ -287,4 +288,5 @@ export const METRICS_SETTINGS_DEFAULT: MetricsSettings = {
287288
imageContrastInMilli: 1000,
288289
imageShowActualSize: false,
289290
histogramMode: HistogramMode.OFFSET,
291+
savingPinsEnabled: true,
290292
};

tensorboard/webapp/metrics/testing.ts

+22
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,28 @@ import {DataTableMode} from '../widgets/data_table/types';
4747
export function buildMetricsSettingsState(
4848
overrides?: Partial<MetricsSettings>
4949
): MetricsSettings {
50+
return {
51+
cardMinWidth: null,
52+
tooltipSort: TooltipSort.NEAREST,
53+
ignoreOutliers: false,
54+
xAxisType: XAxisType.WALL_TIME,
55+
scalarSmoothing: 0.3,
56+
hideEmptyCards: true,
57+
scalarPartitionNonMonotonicX: false,
58+
imageBrightnessInMilli: 123,
59+
imageContrastInMilli: 123,
60+
imageShowActualSize: true,
61+
histogramMode: HistogramMode.OFFSET,
62+
savingPinsEnabled: true,
63+
...overrides,
64+
};
65+
}
66+
67+
// Since Settings proto has missing fields, we need to build a partial of
68+
// Settings to be used in tests.
69+
export function buildMetricsSettingsOverrides(
70+
overrides?: Partial<MetricsSettings>
71+
): Partial<MetricsSettings> {
5072
return {
5173
cardMinWidth: null,
5274
tooltipSort: TooltipSort.NEAREST,

tensorboard/webapp/metrics/views/right_pane/right_pane_test.ts

+38
Original file line numberDiff line numberDiff line change
@@ -548,5 +548,43 @@ describe('metrics right_pane', () => {
548548
).toHaveClass('toggle-opened');
549549
});
550550
});
551+
552+
describe('saving pins check box', () => {
553+
beforeEach(() => {
554+
store.overrideSelector(selectors.getEnableGlobalPins, true);
555+
store.overrideSelector(selectors.getMetricsSavingPinsEnabled, false);
556+
});
557+
558+
it('does not render if getEnableGlobalPins feature flag is false', () => {
559+
store.overrideSelector(selectors.getEnableGlobalPins, false);
560+
const fixture = TestBed.createComponent(SettingsViewContainer);
561+
fixture.detectChanges();
562+
563+
expect(select(fixture, '.saving-pins')).toBeFalsy();
564+
});
565+
566+
it('renders checked saving pins check box if isSavingpinsEnabled is true', () => {
567+
store.overrideSelector(selectors.getMetricsSavingPinsEnabled, true);
568+
569+
const fixture = TestBed.createComponent(SettingsViewContainer);
570+
fixture.detectChanges();
571+
572+
expect(
573+
select(fixture, '.saving-pins input').componentInstance.checked
574+
).toBeTrue();
575+
});
576+
577+
it('dispatches metricsEnableSavingPinsToggled on toggle', () => {
578+
const fixture = TestBed.createComponent(SettingsViewContainer);
579+
fixture.detectChanges();
580+
581+
const checkbox = select(fixture, '.saving-pins input');
582+
checkbox.nativeElement.click();
583+
584+
expect(dispatchSpy).toHaveBeenCalledWith(
585+
actions.metricsEnableSavingPinsToggled()
586+
);
587+
});
588+
});
551589
});
552590
});

tensorboard/webapp/metrics/views/right_pane/settings_view_component.ng.html

+13
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,19 @@ <h3 class="section-title">General</h3>
9393
</button>
9494
</div>
9595
</div>
96+
97+
<div class="control-row saving-pins" *ngIf="globalPinsFeatureEnabled">
98+
<mat-checkbox
99+
[checked]="isSavingPinsEnabled"
100+
(change)="onEnableSavingPinsToggled.emit()"
101+
>Enable saving pins (Scalars only)</mat-checkbox
102+
>
103+
<mat-icon
104+
class="info"
105+
svgIcon="help_outline_24px"
106+
title="When saving pins are enabled, pinned cards will be visible across multiple experiments."
107+
></mat-icon>
108+
</div>
96109
</section>
97110

98111
<section class="scalars">

tensorboard/webapp/metrics/views/right_pane/settings_view_component.scss

+2-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ section .control-row:not(:has(+ .control-row > mat-checkbox)):not(:last-child) {
7979
width: 5em;
8080
}
8181

82-
.scalars-partition-x {
82+
.scalars-partition-x,
83+
.saving-pins {
8384
align-items: center;
8485
display: flex;
8586

tensorboard/webapp/metrics/views/right_pane/settings_view_component.ts

+3
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,15 @@ export class SettingsViewComponent {
7070
@Input() linkedTimeSelection!: TimeSelection | null;
7171
@Input() stepMinMax!: {min: number; max: number};
7272
@Input() isSlideOutMenuOpen!: boolean;
73+
@Input() isSavingPinsEnabled!: boolean;
74+
@Input() globalPinsFeatureEnabled: boolean = false;
7375

7476
@Output() linkedTimeToggled = new EventEmitter<void>();
7577

7678
@Output() stepSelectorToggled = new EventEmitter<void>();
7779
@Output() rangeSelectionToggled = new EventEmitter<void>();
7880
@Output() onSlideOutToggled = new EventEmitter<void>();
81+
@Output() onEnableSavingPinsToggled = new EventEmitter<void>();
7982

8083
@Input() isImageSupportEnabled!: boolean;
8184

0 commit comments

Comments
 (0)