diff --git a/tensorboard/webapp/BUILD b/tensorboard/webapp/BUILD index 93668394cc..e701e6ad82 100644 --- a/tensorboard/webapp/BUILD +++ b/tensorboard/webapp/BUILD @@ -283,6 +283,7 @@ tf_ng_web_test_suite( "//tensorboard/webapp/metrics/views/card_renderer:card_renderer_tests", "//tensorboard/webapp/metrics/views/main_view:main_view_tests", "//tensorboard/webapp/metrics/views/right_pane:right_pane_test", + "//tensorboard/webapp/metrics/views/right_pane/saving_pins_dialog:saving_pins_dialog_test", "//tensorboard/webapp/metrics/views/right_pane/scalar_column_editor:scalar_column_editor_test", "//tensorboard/webapp/plugins:plugins_container_test_lib", "//tensorboard/webapp/reloader:test_lib", diff --git a/tensorboard/webapp/metrics/views/right_pane/BUILD b/tensorboard/webapp/metrics/views/right_pane/BUILD index cfeb419d5e..2c6c21901e 100644 --- a/tensorboard/webapp/metrics/views/right_pane/BUILD +++ b/tensorboard/webapp/metrics/views/right_pane/BUILD @@ -11,17 +11,28 @@ tf_sass_binary( ], ) +tf_sass_binary( + name = "saving_pins_checkbox_styles", + src = "saving_pins_checkbox_component.scss", + deps = [ + "//tensorboard/webapp:angular_material_sass_deps", + "//tensorboard/webapp/theme", + ], +) + tf_ng_module( name = "right_pane", srcs = [ "right_pane_component.ts", "right_pane_module.ts", + "saving_pins_checkbox_component.ts", "settings_view_component.ts", "settings_view_container.ts", ], assets = [ ":settings_view_styles", "settings_view_component.ng.html", + ":saving_pins_checkbox_styles", ], deps = [ "//tensorboard/webapp:app_state", @@ -29,12 +40,14 @@ tf_ng_module( "//tensorboard/webapp/angular:expect_angular_material_button", "//tensorboard/webapp/angular:expect_angular_material_button_toggle", "//tensorboard/webapp/angular:expect_angular_material_checkbox", + "//tensorboard/webapp/angular:expect_angular_material_dialog", "//tensorboard/webapp/angular:expect_angular_material_icon", "//tensorboard/webapp/angular:expect_angular_material_select", "//tensorboard/webapp/angular:expect_angular_material_slider", "//tensorboard/webapp/feature_flag", "//tensorboard/webapp/metrics:types", "//tensorboard/webapp/metrics/actions", + "//tensorboard/webapp/metrics/views/right_pane/saving_pins_dialog", "//tensorboard/webapp/widgets/card_fob:types", "//tensorboard/webapp/widgets/dropdown", "//tensorboard/webapp/widgets/range_input", @@ -56,6 +69,7 @@ tf_ts_library( ":right_pane", "//tensorboard/webapp:app_state", "//tensorboard/webapp:selectors", + "//tensorboard/webapp/angular:expect_angular_cdk_overlay", "//tensorboard/webapp/angular:expect_angular_core_testing", "//tensorboard/webapp/angular:expect_angular_material_button_toggle", "//tensorboard/webapp/angular:expect_angular_material_checkbox", @@ -66,6 +80,7 @@ tf_ts_library( "//tensorboard/webapp/metrics:types", "//tensorboard/webapp/metrics/actions", "//tensorboard/webapp/metrics/store", + "//tensorboard/webapp/metrics/views/right_pane/saving_pins_dialog", "//tensorboard/webapp/testing:utils", "//tensorboard/webapp/widgets/card_fob:types", "//tensorboard/webapp/widgets/dropdown", diff --git a/tensorboard/webapp/metrics/views/right_pane/right_pane_module.ts b/tensorboard/webapp/metrics/views/right_pane/right_pane_module.ts index 06e8efa53f..598550c8bf 100644 --- a/tensorboard/webapp/metrics/views/right_pane/right_pane_module.ts +++ b/tensorboard/webapp/metrics/views/right_pane/right_pane_module.ts @@ -26,12 +26,15 @@ import {RangeInputModule} from '../../../widgets/range_input/range_input_module' import {RightPaneComponent} from './right_pane_component'; import {SettingsViewComponent} from './settings_view_component'; import {SettingsViewContainer} from './settings_view_container'; +import {SavingPinsCheckboxComponent} from './saving_pins_checkbox_component'; +import {SavingPinsDialogModule} from './saving_pins_dialog/saving_pins_dialog_module'; @NgModule({ declarations: [ RightPaneComponent, SettingsViewComponent, SettingsViewContainer, + SavingPinsCheckboxComponent, ], exports: [RightPaneComponent], imports: [ @@ -40,6 +43,7 @@ import {SettingsViewContainer} from './settings_view_container'; MatButtonModule, MatButtonToggleModule, MatCheckboxModule, + SavingPinsDialogModule, MatIconModule, MatSelectModule, MatSliderModule, diff --git a/tensorboard/webapp/metrics/views/right_pane/right_pane_test.ts b/tensorboard/webapp/metrics/views/right_pane/right_pane_test.ts index c62a70e836..2be04642fd 100644 --- a/tensorboard/webapp/metrics/views/right_pane/right_pane_test.ts +++ b/tensorboard/webapp/metrics/views/right_pane/right_pane_test.ts @@ -19,6 +19,7 @@ import { TestBed, tick, } from '@angular/core/testing'; +import {OverlayContainer} from '@angular/cdk/overlay'; import {MatButtonToggleModule} from '@angular/material/button-toggle'; import {MatCheckboxModule} from '@angular/material/checkbox'; import {MatSelectModule} from '@angular/material/select'; @@ -37,10 +38,13 @@ import {HistogramMode, TooltipSort, XAxisType} from '../../types'; import {RightPaneComponent} from './right_pane_component'; import {SettingsViewComponent, TEST_ONLY} from './settings_view_component'; import {SettingsViewContainer} from './settings_view_container'; +import {SavingPinsDialogModule} from './saving_pins_dialog/saving_pins_dialog_module'; +import {SavingPinsCheckboxComponent} from './saving_pins_checkbox_component'; describe('metrics right_pane', () => { let store: MockStore; let dispatchSpy: jasmine.Spy; + let overlayContainer: OverlayContainer; beforeEach(async () => { await TestBed.configureTestingModule({ @@ -49,6 +53,7 @@ describe('metrics right_pane', () => { DropdownModule, MatButtonToggleModule, MatCheckboxModule, + SavingPinsDialogModule, MatSelectModule, MatSliderModule, ], @@ -56,6 +61,7 @@ describe('metrics right_pane', () => { RightPaneComponent, SettingsViewComponent, SettingsViewContainer, + SavingPinsCheckboxComponent, ], providers: [provideMockTbStore()], // Ignore errors from components that are out-of-scope for this test: @@ -65,6 +71,7 @@ describe('metrics right_pane', () => { store = TestBed.inject>(Store) as MockStore; dispatchSpy = spyOn(store, 'dispatch'); + overlayContainer = TestBed.inject(OverlayContainer); }); afterEach(() => { @@ -574,17 +581,64 @@ describe('metrics right_pane', () => { ).toBeTrue(); }); - it('dispatches metricsEnableSavingPinsToggled on toggle', () => { + it('dispatches metricsEnableSavingPinsToggled if user checks the box', () => { const fixture = TestBed.createComponent(SettingsViewContainer); fixture.detectChanges(); const checkbox = select(fixture, '.saving-pins input'); checkbox.nativeElement.click(); + const savingPinsDialog = overlayContainer + .getContainerElement() + .querySelectorAll('saving-pins-dialog'); + expect(savingPinsDialog.length).toBe(0); expect(dispatchSpy).toHaveBeenCalledWith( actions.metricsEnableSavingPinsToggled() ); }); + + it('dispatches metricsEnableSavingPinsToggled if users clicks disable in the dialog', async () => { + store.overrideSelector(selectors.getMetricsSavingPinsEnabled, true); + const fixture = TestBed.createComponent(SettingsViewContainer); + fixture.detectChanges(); + + const checkbox = select(fixture, '.saving-pins input'); + checkbox.nativeElement.click(); + + const savingPinsDialog = overlayContainer + .getContainerElement() + .querySelectorAll('saving-pins-dialog'); + + const disableButton = overlayContainer + .getContainerElement() + .querySelector('.disable-button'); + (disableButton as HTMLButtonElement).click(); + fixture.detectChanges(); + await fixture.whenStable(); + + expect(savingPinsDialog.length).toBe(1); + expect(dispatchSpy).toHaveBeenCalledWith( + actions.metricsEnableSavingPinsToggled() + ); + }); + + it('does not dispatch metricsEnableSavingPinsToggled if users cancels the dialog', async () => { + store.overrideSelector(selectors.getMetricsSavingPinsEnabled, true); + const fixture = TestBed.createComponent(SettingsViewContainer); + fixture.detectChanges(); + + const checkbox = select(fixture, '.saving-pins input'); + checkbox.nativeElement.click(); + + const disableButton = overlayContainer + .getContainerElement() + .querySelector('.cancel-button'); + (disableButton as HTMLButtonElement).click(); + fixture.detectChanges(); + await fixture.whenStable(); + + expect(dispatchSpy).not.toHaveBeenCalled(); + }); }); }); }); diff --git a/tensorboard/webapp/metrics/views/right_pane/saving_pins_checkbox_component.scss b/tensorboard/webapp/metrics/views/right_pane/saving_pins_checkbox_component.scss new file mode 100644 index 0000000000..e67a7a6094 --- /dev/null +++ b/tensorboard/webapp/metrics/views/right_pane/saving_pins_checkbox_component.scss @@ -0,0 +1,48 @@ +/* Copyright 2024 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +@use '@angular/material' as mat; +@import 'tensorboard/webapp/theme/tb_theme'; + +:host { + @include tb-theme-foreground-prop(color, secondary-text); + font-size: 12px; +} + +mat-checkbox { + // Counteract the padding of the checkbox in order to align it vertically + // with other items in the pane. + margin-left: -11px; + + ::ng-deep label { + @include tb-theme-foreground-prop(color, secondary-text); + font-size: 12px; + letter-spacing: normal; + padding-left: 0px; + white-space: nowrap; + } +} + +.saving-pins-checkbox { + align-items: center; + display: flex; + + .info { + $_dim: 15px; + height: $_dim; + margin-left: 5px; + width: $_dim; + min-width: $_dim; + } +} diff --git a/tensorboard/webapp/metrics/views/right_pane/saving_pins_checkbox_component.ts b/tensorboard/webapp/metrics/views/right_pane/saving_pins_checkbox_component.ts new file mode 100644 index 0000000000..e6e7766cdf --- /dev/null +++ b/tensorboard/webapp/metrics/views/right_pane/saving_pins_checkbox_component.ts @@ -0,0 +1,54 @@ +/* Copyright 2024 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + Output, +} from '@angular/core'; +import { + MAT_CHECKBOX_DEFAULT_OPTIONS, + MatCheckboxDefaultOptions, +} from '@angular/material/checkbox'; + +@Component({ + selector: 'saving-pins-checkbox', + template: ` +
+ Enable saving pins (Scalars only) + +
+ `, + changeDetection: ChangeDetectionStrategy.OnPush, + styleUrls: ['saving_pins_checkbox_component.css'], + providers: [ + { + provide: MAT_CHECKBOX_DEFAULT_OPTIONS, + useValue: {clickAction: 'noop'} as MatCheckboxDefaultOptions, + }, + ], +}) +export class SavingPinsCheckboxComponent { + @Input() isChecked!: boolean; + + @Output() onCheckboxToggled = new EventEmitter(); +} diff --git a/tensorboard/webapp/metrics/views/right_pane/saving_pins_dialog/BUILD b/tensorboard/webapp/metrics/views/right_pane/saving_pins_dialog/BUILD new file mode 100644 index 0000000000..540bcff6a8 --- /dev/null +++ b/tensorboard/webapp/metrics/views/right_pane/saving_pins_dialog/BUILD @@ -0,0 +1,41 @@ +load("//tensorboard/defs:defs.bzl", "tf_ng_module", "tf_sass_binary", "tf_ts_library") + +package(default_visibility = ["//tensorboard:internal"]) + +tf_sass_binary( + name = "saving_pins_dialog_component_styles", + src = "saving_pins_dialog_component.scss", +) + +tf_ng_module( + name = "saving_pins_dialog", + srcs = [ + "saving_pins_dialog_component.ts", + "saving_pins_dialog_module.ts", + ], + assets = [ + ":saving_pins_dialog_component_styles", + "saving_pins_dialog_component.ng.html", + ], + deps = [ + "//tensorboard/webapp/angular:expect_angular_material_button", + "//tensorboard/webapp/angular:expect_angular_material_dialog", + "@npm//@angular/common", + "@npm//@angular/core", + ], +) + +tf_ts_library( + name = "saving_pins_dialog_test", + testonly = True, + srcs = ["saving_pins_dialog_test.ts"], + deps = [ + ":saving_pins_dialog", + "//tensorboard/webapp/angular:expect_angular_core_testing", + "//tensorboard/webapp/angular:expect_angular_material_dialog", + "@npm//@angular/common", + "@npm//@angular/core", + "@npm//@angular/platform-browser", + "@npm//@types/jasmine", + ], +) diff --git a/tensorboard/webapp/metrics/views/right_pane/saving_pins_dialog/saving_pins_dialog_component.ng.html b/tensorboard/webapp/metrics/views/right_pane/saving_pins_dialog/saving_pins_dialog_component.ng.html new file mode 100644 index 0000000000..3b08558536 --- /dev/null +++ b/tensorboard/webapp/metrics/views/right_pane/saving_pins_dialog/saving_pins_dialog_component.ng.html @@ -0,0 +1,36 @@ + +

Disable Saving Pins?

+ +

+ Disabling saving pins will remove locally stored pinned card data and no + longer allow pinned cards to be shared across multiple experiments.
You + can re-enable the feature and re-pin cards at any time. +

+ +
+ + +
diff --git a/tensorboard/webapp/metrics/views/right_pane/saving_pins_dialog/saving_pins_dialog_component.scss b/tensorboard/webapp/metrics/views/right_pane/saving_pins_dialog/saving_pins_dialog_component.scss new file mode 100644 index 0000000000..edda769b96 --- /dev/null +++ b/tensorboard/webapp/metrics/views/right_pane/saving_pins_dialog/saving_pins_dialog_component.scss @@ -0,0 +1,30 @@ +/* Copyright 2024 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +.title { + font-size: 19px; + font-weight: 500; + margin: 0; +} + +.bottom-buttons { + display: flex; + justify-content: flex-end; + margin-top: 10px; + gap: 10px; + + button { + text-transform: uppercase; + } +} diff --git a/tensorboard/webapp/metrics/views/right_pane/saving_pins_dialog/saving_pins_dialog_component.ts b/tensorboard/webapp/metrics/views/right_pane/saving_pins_dialog/saving_pins_dialog_component.ts new file mode 100644 index 0000000000..002d5fe93e --- /dev/null +++ b/tensorboard/webapp/metrics/views/right_pane/saving_pins_dialog/saving_pins_dialog_component.ts @@ -0,0 +1,50 @@ +/* Copyright 2024 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {MatDialogRef} from '@angular/material/dialog'; + +/** + * The result type emitted by MatDialog's observables `beforeClose()`, + * `afterClose()`. + */ +export interface SavingPinsDialogResult { + shouldDisable: boolean; +} + +/** + * A confirmation dialog for disabling saving pin feature. + */ +@Component({ + selector: 'saving-pins-dialog', + templateUrl: './saving_pins_dialog_component.ng.html', + styleUrls: ['saving_pins_dialog_component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SavingPinsDialogComponent { + constructor( + private readonly dialogRef: MatDialogRef< + SavingPinsDialogComponent, + SavingPinsDialogResult + > + ) {} + + closeWithoutDisabling() { + this.dialogRef.close({shouldDisable: false}); + } + + closeAndDisable() { + this.dialogRef.close({shouldDisable: true}); + } +} diff --git a/tensorboard/webapp/metrics/views/right_pane/saving_pins_dialog/saving_pins_dialog_module.ts b/tensorboard/webapp/metrics/views/right_pane/saving_pins_dialog/saving_pins_dialog_module.ts new file mode 100644 index 0000000000..4780e88178 --- /dev/null +++ b/tensorboard/webapp/metrics/views/right_pane/saving_pins_dialog/saving_pins_dialog_module.ts @@ -0,0 +1,30 @@ +/* Copyright 2024 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +import {CommonModule} from '@angular/common'; +import {NgModule} from '@angular/core'; +import {MatButtonModule} from '@angular/material/button'; +import {MatDialogModule} from '@angular/material/dialog'; + +import {SavingPinsDialogComponent} from './saving_pins_dialog_component'; + +/** + * Provides . + */ +@NgModule({ + declarations: [SavingPinsDialogComponent], + exports: [SavingPinsDialogComponent], + imports: [CommonModule, MatButtonModule, MatDialogModule], +}) +export class SavingPinsDialogModule {} diff --git a/tensorboard/webapp/metrics/views/right_pane/saving_pins_dialog/saving_pins_dialog_test.ts b/tensorboard/webapp/metrics/views/right_pane/saving_pins_dialog/saving_pins_dialog_test.ts new file mode 100644 index 0000000000..68ab1b6dfd --- /dev/null +++ b/tensorboard/webapp/metrics/views/right_pane/saving_pins_dialog/saving_pins_dialog_test.ts @@ -0,0 +1,54 @@ +/* Copyright 2024 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +import {CommonModule} from '@angular/common'; +import {TestBed} from '@angular/core/testing'; +import {By} from '@angular/platform-browser'; +import {MatDialogRef} from '@angular/material/dialog'; + +import {SavingPinsDialogModule} from './saving_pins_dialog_module'; +import {SavingPinsDialogComponent} from './saving_pins_dialog_component'; + +describe('saving pins dialog', () => { + let mockMatDialogRef: {close: jasmine.Spy}; + + beforeEach(async () => { + mockMatDialogRef = {close: jasmine.createSpy()}; + await TestBed.configureTestingModule({ + declarations: [SavingPinsDialogComponent], + imports: [CommonModule, SavingPinsDialogModule], + providers: [{provide: MatDialogRef, useValue: mockMatDialogRef}], + }).compileComponents(); + }); + + it('clicks disable button', () => { + const fixture = TestBed.createComponent(SavingPinsDialogComponent); + fixture.detectChanges(); + + const confirmEl = fixture.debugElement.query(By.css('.disable-button')); + confirmEl.nativeElement.click(); + + expect(mockMatDialogRef.close).toHaveBeenCalledWith({shouldDisable: true}); + }); + + it('clicks cancel button', () => { + const fixture = TestBed.createComponent(SavingPinsDialogComponent); + fixture.detectChanges(); + + const cancelEl = fixture.debugElement.query(By.css('.cancel-button')); + cancelEl.nativeElement.click(); + + expect(mockMatDialogRef.close).toHaveBeenCalledWith({shouldDisable: false}); + }); +}); diff --git a/tensorboard/webapp/metrics/views/right_pane/settings_view_component.ng.html b/tensorboard/webapp/metrics/views/right_pane/settings_view_component.ng.html index ec894d0f6e..dbf619d1c9 100644 --- a/tensorboard/webapp/metrics/views/right_pane/settings_view_component.ng.html +++ b/tensorboard/webapp/metrics/views/right_pane/settings_view_component.ng.html @@ -95,16 +95,11 @@

General

- Enable saving pins (Scalars only) - +
diff --git a/tensorboard/webapp/metrics/views/right_pane/settings_view_component.scss b/tensorboard/webapp/metrics/views/right_pane/settings_view_component.scss index 0a798db9b0..5bdf2b2d53 100644 --- a/tensorboard/webapp/metrics/views/right_pane/settings_view_component.scss +++ b/tensorboard/webapp/metrics/views/right_pane/settings_view_component.scss @@ -79,8 +79,7 @@ section .control-row:not(:has(+ .control-row > mat-checkbox)):not(:last-child) { width: 5em; } -.scalars-partition-x, -.saving-pins { +.scalars-partition-x { align-items: center; display: flex; diff --git a/tensorboard/webapp/metrics/views/right_pane/settings_view_container.ts b/tensorboard/webapp/metrics/views/right_pane/settings_view_container.ts index 280dc0770c..2f3d7213f7 100644 --- a/tensorboard/webapp/metrics/views/right_pane/settings_view_container.ts +++ b/tensorboard/webapp/metrics/views/right_pane/settings_view_container.ts @@ -14,6 +14,7 @@ limitations under the License. ==============================================================================*/ import {ChangeDetectionStrategy, Component} from '@angular/core'; import {Store} from '@ngrx/store'; +import {MatDialog} from '@angular/material/dialog'; import {Observable} from 'rxjs'; import {filter, map, take, withLatestFrom} from 'rxjs/operators'; import {State} from '../../../app_state'; @@ -40,6 +41,10 @@ import { stepSelectorToggled, } from '../../actions'; import {HistogramMode, TooltipSort, XAxisType} from '../../types'; +import { + SavingPinsDialogComponent, + SavingPinsDialogResult, +} from './saving_pins_dialog/saving_pins_dialog_component'; @Component({ selector: 'metrics-dashboard-settings', @@ -85,7 +90,7 @@ import {HistogramMode, TooltipSort, XAxisType} from '../../types'; (rangeSelectionToggled)="onRangeSelectionToggled()" (onSlideOutToggled)="onSlideOutToggled()" [isSavingPinsEnabled]="isSavingPinsEnabled$ | async" - (onEnableSavingPinsToggled)="onEnableSavingPinsToggled()" + (onEnableSavingPinsToggled)="onEnableSavingPinsToggled($event)" [globalPinsFeatureEnabled]="globalPinsFeatureEnabled$ | async" > @@ -93,7 +98,10 @@ import {HistogramMode, TooltipSort, XAxisType} from '../../types'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class SettingsViewContainer { - constructor(private readonly store: Store) {} + constructor( + private readonly store: Store, + private readonly dialog: MatDialog + ) {} readonly isScalarStepSelectorEnabled$: Observable = this.store.select(selectors.getMetricsStepSelectorEnabled); @@ -234,7 +242,17 @@ export class SettingsViewContainer { this.store.dispatch(metricsSlideoutMenuToggled()); } - onEnableSavingPinsToggled() { - this.store.dispatch(metricsEnableSavingPinsToggled()); + onEnableSavingPinsToggled(isChecked: boolean) { + if (isChecked) { + // Show a dialog when user disables the saving pins feature. + const dialogRef = this.dialog.open(SavingPinsDialogComponent); + dialogRef.afterClosed().subscribe((result?: SavingPinsDialogResult) => { + if (result?.shouldDisable) { + this.store.dispatch(metricsEnableSavingPinsToggled()); + } + }); + } else { + this.store.dispatch(metricsEnableSavingPinsToggled()); + } } }