diff --git a/tensorboard/webapp/metrics/actions/index.ts b/tensorboard/webapp/metrics/actions/index.ts index 2c207b5e69..635b7f65b7 100644 --- a/tensorboard/webapp/metrics/actions/index.ts +++ b/tensorboard/webapp/metrics/actions/index.ts @@ -175,3 +175,11 @@ export const selectTimeEnableToggled = createAction( export const useRangeSelectTimeToggled = createAction( '[Metrics] Use Range Select Time Toggle' ); + +export const metricsPromoDismissed = createAction( + '[Metrics] Metrics Promo Dismissed' +); + +export const metricsPromoGoToScalars = createAction( + '[Metrics] Metrics Promo Go To Scalars' +); diff --git a/tensorboard/webapp/metrics/views/BUILD b/tensorboard/webapp/metrics/views/BUILD index b9b983c248..35367ef416 100644 --- a/tensorboard/webapp/metrics/views/BUILD +++ b/tensorboard/webapp/metrics/views/BUILD @@ -14,22 +14,32 @@ tf_sass_binary( src = "metrics_container.scss", ) +tf_sass_binary( + name = "metrics_promo_styles", + src = "metrics_promo_notice_component.scss", +) + tf_ng_module( name = "views", srcs = [ "metrics_container.ts", "metrics_promo_notice_component.ts", "metrics_promo_notice_container.ts", + "metrics_promo_notice_types.ts", "metrics_views_module.ts", ], assets = [ ":metrics_container_styles", + ":metrics_promo_styles", "metrics_promo_notice_component.ng.html", ], deps = [ "//tensorboard/webapp:app_state", "//tensorboard/webapp:selectors", + "//tensorboard/webapp/angular:expect_angular_material_icon", "//tensorboard/webapp/core", + "//tensorboard/webapp/customization", + "//tensorboard/webapp/metrics/actions", "//tensorboard/webapp/metrics/views/main_view", "//tensorboard/webapp/metrics/views/right_pane", "//tensorboard/webapp/runs/views/runs_selector", @@ -75,6 +85,7 @@ tf_ts_library( "//tensorboard/webapp/angular:expect_angular_core_testing", "//tensorboard/webapp/angular:expect_angular_platform_browser_animations", "//tensorboard/webapp/angular:expect_ngrx_store_testing", + "//tensorboard/webapp/metrics/actions", "//tensorboard/webapp/metrics/data_source", "@npm//@angular/core", "@npm//@angular/platform-browser", diff --git a/tensorboard/webapp/metrics/views/metrics_container.scss b/tensorboard/webapp/metrics/views/metrics_container.scss index 46c58d7220..484289eefd 100644 --- a/tensorboard/webapp/metrics/views/metrics_container.scss +++ b/tensorboard/webapp/metrics/views/metrics_container.scss @@ -16,12 +16,17 @@ limitations under the License. :host { display: flex; - height: 100%; flex-direction: column; + height: 100%; + justify-content: stretch; overflow: hidden; } .notice { + background-color: rgba(mat-color($mat-yellow, 200), 0.85); + border-bottom: 1px solid mat-color($mat-yellow, 500); + color: map-get($tb-foreground, text); + display: block; flex: 0 0; } diff --git a/tensorboard/webapp/metrics/views/metrics_container.ts b/tensorboard/webapp/metrics/views/metrics_container.ts index ba72c2fb38..a5126a9f0a 100644 --- a/tensorboard/webapp/metrics/views/metrics_container.ts +++ b/tensorboard/webapp/metrics/views/metrics_container.ts @@ -22,9 +22,10 @@ import {getIsTimeSeriesPromotionEnabled} from '../../selectors'; @Component({ selector: 'metrics-dashboard', template: ` -
- Temporary butter bar content. -
+ diff --git a/tensorboard/webapp/metrics/views/metrics_container_test.ts b/tensorboard/webapp/metrics/views/metrics_container_test.ts index c959e4390b..696de909bc 100644 --- a/tensorboard/webapp/metrics/views/metrics_container_test.ts +++ b/tensorboard/webapp/metrics/views/metrics_container_test.ts @@ -12,16 +12,18 @@ 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 {NO_ERRORS_SCHEMA} from '@angular/core'; +import {DebugElement, NO_ERRORS_SCHEMA} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; -import {Store} from '@ngrx/store'; +import {Action, Store} from '@ngrx/store'; import {MockStore, provideMockStore} from '@ngrx/store/testing'; import {State} from '../../app_state'; import {getIsTimeSeriesPromotionEnabled} from '../../selectors'; +import {metricsPromoDismissed, metricsPromoGoToScalars} from '../actions'; import {MetricsDashboardContainer} from './metrics_container'; +import {MetricsPromoNoticeComponent} from './metrics_promo_notice_component'; import {MetricsPromoNoticeContainer} from './metrics_promo_notice_container'; describe('metrics view', () => { @@ -30,7 +32,11 @@ describe('metrics view', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [NoopAnimationsModule], - declarations: [MetricsDashboardContainer, MetricsPromoNoticeContainer], + declarations: [ + MetricsDashboardContainer, + MetricsPromoNoticeContainer, + MetricsPromoNoticeComponent, + ], providers: [provideMockStore()], // Ignore errors from components that are out-of-scope for this test: // 'runs-selector'. @@ -55,4 +61,33 @@ describe('metrics view', () => { expect(fixture.debugElement.query(By.css('.notice'))).not.toBeNull(); }); + + describe('promotion', () => { + let actions: Action[]; + + function createComponent(): DebugElement { + actions = []; + spyOn(store, 'dispatch').and.callFake((action) => actions.push(action)); + + store.overrideSelector(getIsTimeSeriesPromotionEnabled, true); + const fixture = TestBed.createComponent(MetricsDashboardContainer); + fixture.detectChanges(); + + return fixture.debugElement.query(By.css('metrics-promo-notice')); + } + + it('dispatches action when clicking on dismiss', () => { + const promoEl = createComponent(); + promoEl.query(By.css('.dismiss')).nativeElement.click(); + + expect(actions).toEqual([metricsPromoDismissed()]); + }); + + it('dispatches action when clicking on "Go to scalars"', () => { + const promoEl = createComponent(); + promoEl.query(By.css('.go-to-scalars')).nativeElement.click(); + + expect(actions).toEqual([metricsPromoGoToScalars()]); + }); + }); }); diff --git a/tensorboard/webapp/metrics/views/metrics_promo_notice_component.ng.html b/tensorboard/webapp/metrics/views/metrics_promo_notice_component.ng.html index 6a048659f3..7ed67dc4e4 100644 --- a/tensorboard/webapp/metrics/views/metrics_promo_notice_component.ng.html +++ b/tensorboard/webapp/metrics/views/metrics_promo_notice_component.ng.html @@ -14,4 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. --> -Temporary butter bar content. + + Welcome to new default experience of TensorBoard. Time Series lets you view + all visualizations at once, put them side-by-side with pins, and customize + colors. Scalars and other plugins are still available. + . + + diff --git a/tensorboard/webapp/metrics/views/metrics_promo_notice_component.scss b/tensorboard/webapp/metrics/views/metrics_promo_notice_component.scss new file mode 100644 index 0000000000..9eabd161ba --- /dev/null +++ b/tensorboard/webapp/metrics/views/metrics_promo_notice_component.scss @@ -0,0 +1,48 @@ +/* Copyright 2021 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 'tensorboard/webapp/theme/tb_theme'; + +:host { + display: flex; + font-size: 14px; + gap: 5px; + justify-content: space-between; + line-height: 20px; + padding: 5px 10px; +} + +button { + background-color: transparent; + border: 0; + color: inherit; + cursor: pointer; + font: inherit; + padding: 0; + + &:hover { + text-decoration: underline; + } +} + +tb-customization button { + color: map-get($tb-foreground, link); +} + +.dismiss { + align-self: baseline; + flex: none; + height: 20px; + width: 20px; +} diff --git a/tensorboard/webapp/metrics/views/metrics_promo_notice_component.ts b/tensorboard/webapp/metrics/views/metrics_promo_notice_component.ts index a370cc10c1..488171c1d0 100644 --- a/tensorboard/webapp/metrics/views/metrics_promo_notice_component.ts +++ b/tensorboard/webapp/metrics/views/metrics_promo_notice_component.ts @@ -12,11 +12,33 @@ 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 { + ChangeDetectionStrategy, + Component, + EventEmitter, + Inject, + Optional, + Output, + Type, +} from '@angular/core'; +import {METRICS_PROMO_MESSAGE_COMPONENT} from './metrics_promo_notice_types'; @Component({ selector: 'metrics-promo-notice-component', templateUrl: 'metrics_promo_notice_component.ng.html', + styleUrls: ['metrics_promo_notice_component.css'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MetricsPromoNoticeComponent {} +export class MetricsPromoNoticeComponent { + @Output() + readonly onDismiss = new EventEmitter(); + + @Output() + readonly onGoToScalars = new EventEmitter(); + + constructor( + @Optional() + @Inject(METRICS_PROMO_MESSAGE_COMPONENT) + readonly customPromoMessage: Type + ) {} +} diff --git a/tensorboard/webapp/metrics/views/metrics_promo_notice_container.ts b/tensorboard/webapp/metrics/views/metrics_promo_notice_container.ts index 8924224256..6ed0eb23eb 100644 --- a/tensorboard/webapp/metrics/views/metrics_promo_notice_container.ts +++ b/tensorboard/webapp/metrics/views/metrics_promo_notice_container.ts @@ -13,10 +13,27 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {Store} from '@ngrx/store'; + +import {State} from '../../app_state'; +import {metricsPromoDismissed, metricsPromoGoToScalars} from '../actions'; @Component({ selector: 'metrics-promo-notice', - template: ``, + template: ``, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MetricsPromoNoticeContainer {} +export class MetricsPromoNoticeContainer { + constructor(private readonly store: Store) {} + + onDismiss() { + this.store.dispatch(metricsPromoDismissed()); + } + + onGoToScalars() { + this.store.dispatch(metricsPromoGoToScalars()); + } +} diff --git a/tensorboard/webapp/metrics/views/metrics_promo_notice_types.ts b/tensorboard/webapp/metrics/views/metrics_promo_notice_types.ts new file mode 100644 index 0000000000..b7301d4755 --- /dev/null +++ b/tensorboard/webapp/metrics/views/metrics_promo_notice_types.ts @@ -0,0 +1,24 @@ +/* Copyright 2021 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 {Component, InjectionToken, Type} from '@angular/core'; + +/** + * When this token exists, it will replace Metrics promotion message by the + * content of the provided component. + */ +export const METRICS_PROMO_MESSAGE_COMPONENT = new InjectionToken< + Type +>('[Metrics] METRICS_Promo Message Component'); diff --git a/tensorboard/webapp/metrics/views/metrics_views_module.ts b/tensorboard/webapp/metrics/views/metrics_views_module.ts index e177a16b42..16249c2d4c 100644 --- a/tensorboard/webapp/metrics/views/metrics_views_module.ts +++ b/tensorboard/webapp/metrics/views/metrics_views_module.ts @@ -14,8 +14,10 @@ limitations under the License. ==============================================================================*/ import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; +import {MatIconModule} from '@angular/material/icon'; import {LayoutModule} from '../../core'; +import {CustomizationModule} from '../../customization/customization_module'; import {RunsSelectorModule} from '../../runs/views/runs_selector/runs_selector_module'; import {MainViewModule} from './main_view/main_view_module'; import {MetricsDashboardContainer} from './metrics_container'; @@ -32,8 +34,10 @@ import {RightPaneModule} from './right_pane/right_pane_module'; exports: [MetricsDashboardContainer], imports: [ CommonModule, + CustomizationModule, LayoutModule, MainViewModule, + MatIconModule, RightPaneModule, RunsSelectorModule, ],