+
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,
],