diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/BUILD b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/BUILD index 2548ad40c4..b4dd6a468d 100644 --- a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/BUILD +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/BUILD @@ -1,6 +1,7 @@ package(default_visibility = ["//tensorboard:internal"]) load("@npm_angular_bazel//:index.bzl", "ng_module") +load("//tensorboard/defs:defs.bzl", "tf_ng_web_test_suite", "tf_ts_library") licenses(["notice"]) # Apache 2.0 @@ -19,9 +20,50 @@ ng_module( "debugger_component.ng.html", ], deps = [ + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/actions", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/data_source", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/effects", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/alerts", "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/inactive", + "@npm//@angular/common", "@npm//@angular/core", + "@npm//@ngrx/effects", "@npm//@ngrx/store", "@npm//rxjs", ], ) + +tf_ts_library( + name = "test_lib", + testonly = True, + srcs = [ + "debugger_container_test.ts", + ], + tsconfig = "//:tsconfig-test", + deps = [ + ":debugger_v2", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/actions", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store:types", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/testing", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/alerts", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/inactive", + "//tensorboard/webapp/angular:expect_angular_core_testing", + "@npm//@angular/common", + "@npm//@angular/compiler", + "@npm//@angular/core", + "@npm//@angular/platform-browser", + "@npm//@ngrx/store", + "@npm//@types/jasmine", + ], +) + +tf_ng_web_test_suite( + name = "karma_test", + deps = [ + ":test_lib", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/effects:debugger_effects_test_lib", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store:debugger_store_test_lib", + ], +) diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/actions/BUILD b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/actions/BUILD new file mode 100644 index 0000000000..f0b0afe4e7 --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/actions/BUILD @@ -0,0 +1,17 @@ +package(default_visibility = ["//tensorboard:internal"]) + +load("//tensorboard/defs:defs.bzl", "tf_ts_library") + +licenses(["notice"]) # Apache 2. + +tf_ts_library( + name = "actions", + srcs = [ + "debugger_actions.ts", + "index.ts", + ], + deps = [ + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store:types", + "@npm//@ngrx/store", + ], +) diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/actions/debugger_actions.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/actions/debugger_actions.ts new file mode 100644 index 0000000000..f6368f5c81 --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/actions/debugger_actions.ts @@ -0,0 +1,49 @@ +/* Copyright 2019 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 {createAction, props} from '@ngrx/store'; + +import {DebuggerRunListing} from '../store/debugger_types'; + +// HACK: Below import is for type inference. +// https://github.com/bazelbuild/rules_nodejs/issues/1013 +/** @typehack */ import * as _typeHackModels from '@ngrx/store/src/models'; + +/** + * Actions for Debugger V2. + */ + +/** + * Actions for the Debugger Component. + */ +export const debuggerLoaded = createAction('[Debugger] Debugger Loaded'); + +export const debuggerRunsRequested = createAction( + '[Debugger] Debugger Runs Requested' +); + +export const debuggerRunsLoaded = createAction( + '[Debugger] Debugger Runs Loaded', + props<{runs: DebuggerRunListing}>() +); + +export const debuggerRunsRequestFailed = createAction( + '[Debugger] Debugger Runs Request Failed' +); + +/** + * Actions for the Alerts Component. + */ +export const alertsViewLoaded = createAction('[Debugger] Alerts View Loaded'); diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/actions/index.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/actions/index.ts new file mode 100644 index 0000000000..32afbf051f --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/actions/index.ts @@ -0,0 +1,16 @@ +/* Copyright 2019 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. +==============================================================================*/ + +export * from './debugger_actions'; diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/data_source/BUILD b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/data_source/BUILD new file mode 100644 index 0000000000..bb38a37736 --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/data_source/BUILD @@ -0,0 +1,17 @@ +package(default_visibility = ["//tensorboard:internal"]) + +load("@npm_angular_bazel//:index.bzl", "ng_module") + +ng_module( + name = "data_source", + srcs = [ + "tfdbg2_data_source.ts", + "tfdbg2_data_source_module.ts", + ], + deps = [ + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store:types", + "//tensorboard/webapp/angular:expect_angular_common_http", + "@npm//@angular/common", + "@npm//@angular/core", + ], +) diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/data_source/tfdbg2_data_source.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/data_source/tfdbg2_data_source.ts new file mode 100644 index 0000000000..4384c10cd7 --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/data_source/tfdbg2_data_source.ts @@ -0,0 +1,39 @@ +/* Copyright 2019 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 {HttpClient} from '@angular/common/http'; +import {Injectable} from '@angular/core'; +import {Observable} from 'rxjs'; +import {DebuggerRunListing} from '../store/debugger_types'; + +/** @typehack */ import * as _typeHackRxjs from 'rxjs'; + +export abstract class Tfdbg2DataSource { + abstract fetchRuns(): Observable; +} + +@Injectable() +export class Tfdbg2HttpServerDataSource implements Tfdbg2DataSource { + private readonly httpPathPrefix = 'data/plugin/debugger-v2'; + + constructor(private http: HttpClient) {} + + fetchRuns() { + // TODO(cais): Once the backend uses an DataProvider that unifies tfdbg and + // non-tfdbg plugins, switch to using `tf_backend.runStore.refresh()`. + return this.http.get(this.httpPathPrefix + '/runs'); + } + + // TODO(cais): Implement fetchEnvironments(). +} diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/data_source/tfdbg2_data_source_module.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/data_source/tfdbg2_data_source_module.ts new file mode 100644 index 0000000000..803a764f8b --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/data_source/tfdbg2_data_source_module.ts @@ -0,0 +1,23 @@ +/* Copyright 2019 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 {NgModule} from '@angular/core'; +import {HttpClientModule} from '@angular/common/http'; +import {Tfdbg2HttpServerDataSource} from './tfdbg2_data_source'; + +@NgModule({ + imports: [HttpClientModule], + providers: [Tfdbg2HttpServerDataSource], +}) +export class Tfdbg2ServerDataSourceModule {} diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_component.ng.html b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_component.ng.html index 052ee66242..a68e0af079 100644 --- a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_component.ng.html +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_component.ng.html @@ -16,6 +16,11 @@ -->
- - + + + + +
diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_component.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_component.ts index 621c09ed78..0a246d81d6 100644 --- a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_component.ts +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_component.ts @@ -12,11 +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 {Component} from '@angular/core'; +import {Component, Input} from '@angular/core'; +import {DebuggerRunListing} from './store/debugger_types'; @Component({ selector: 'debugger-component', templateUrl: './debugger_component.ng.html', styleUrls: ['./debugger_component.css'], }) -export class DebuggerComponent {} +export class DebuggerComponent { + @Input() + runs: DebuggerRunListing = {}; + + @Input() + runIds: string[] = []; +} diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_container.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_container.ts index 4f41e87c41..051a2637a0 100644 --- a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_container.ts +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_container.ts @@ -12,20 +12,39 @@ 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} from '@angular/core'; -import {Store} from '@ngrx/store'; +import {Component, OnInit} from '@angular/core'; +import {createSelector, select, Store} from '@ngrx/store'; +import {DebuggerRunListing, State} from './store/debugger_types'; -/** @typehack */ import * as _typeHackRxjs from 'rxjs'; +import {debuggerLoaded} from './actions'; +import {getDebuggerRunListing} from './store'; -// TODO(cais): Move to a separate file. -export interface State {} +/** @typehack */ import * as _typeHackRxjs from 'rxjs'; @Component({ selector: 'tf-debugger-v2', template: ` - + `, }) -export class DebuggerContainer { +export class DebuggerContainer implements OnInit { + readonly runs$ = this.store.pipe(select(getDebuggerRunListing)); + + readonly runsIds$ = this.store.pipe( + select( + createSelector( + getDebuggerRunListing, + (runs): string[] => Object.keys(runs) + ) + ) + ); + constructor(private readonly store: Store) {} + + ngOnInit(): void { + this.store.dispatch(debuggerLoaded()); + } } diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_container_test.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_container_test.ts new file mode 100644 index 0000000000..8bd7c0cd80 --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_container_test.ts @@ -0,0 +1,132 @@ +/* Copyright 2019 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. +==============================================================================*/ +/** + * Unit tests for the Debugger Container. + */ +import {CommonModule} from '@angular/common'; +import {TestBed} from '@angular/core/testing'; +import {By} from '@angular/platform-browser'; + +import {Store} from '@ngrx/store'; +import {provideMockStore, MockStore} from '@ngrx/store/testing'; + +import {debuggerLoaded} from './actions'; +import {DebuggerComponent} from './debugger_component'; +import {DebuggerContainer} from './debugger_container'; +import {DataLoadState, State} from './store/debugger_types'; +import {createDebuggerState, createState} from './testing'; +import {AlertsModule} from './views/alerts/alerts_module'; +import {InactiveModule} from './views/inactive/inactive_module'; + +/** @typehack */ import * as _typeHackStore from '@ngrx/store'; + +describe('Debugger Container test', () => { + let store: MockStore; + let dispatchSpy: jasmine.Spy; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [DebuggerComponent, DebuggerContainer], + imports: [AlertsModule, CommonModule, InactiveModule], + providers: [ + provideMockStore({ + initialState: createState(createDebuggerState()), + }), + DebuggerContainer, + ], + }).compileComponents(); + store = TestBed.get(Store); + dispatchSpy = spyOn(store, 'dispatch'); + }); + + it('renders debugger component initially with inactive component', () => { + const fixture = TestBed.createComponent(DebuggerContainer); + fixture.detectChanges(); + + const inactiveElement = fixture.debugElement.query( + By.css('tf-debugger-v2-inactive') + ); + expect(inactiveElement).toBeTruthy(); + const alertsElement = fixture.debugElement.query( + By.css('tf-debugger-v2-alerts') + ); + expect(alertsElement).toBeNull(); + }); + + it('rendering debugger component dispatches debuggeRunsRequested', () => { + const fixture = TestBed.createComponent(DebuggerContainer); + fixture.detectChanges(); + expect(dispatchSpy).toHaveBeenCalledWith(debuggerLoaded()); + }); + + it('updates the UI to hide inactive component when store has 1 run', () => { + const fixture = TestBed.createComponent(DebuggerContainer); + fixture.detectChanges(); + + store.setState( + createState( + createDebuggerState({ + runs: { + foo_run: { + startTimeMs: 111, + tensorFlowVersion: '2.1.0', + }, + }, + runsLoaded: { + state: DataLoadState.LOADED, + lastLoadedTimeInMs: Date.now(), + }, + }) + ) + ); + fixture.detectChanges(); + + const inactiveElement = fixture.debugElement.query( + By.css('tf-debugger-v2-inactive') + ); + expect(inactiveElement).toBeNull(); + const alertsElement = fixture.debugElement.query( + By.css('tf-debugger-v2-alerts') + ); + expect(alertsElement).toBeTruthy(); + }); + + it('updates the UI to hide inactive component when store has no run', () => { + const fixture = TestBed.createComponent(DebuggerContainer); + fixture.detectChanges(); + + store.setState( + createState( + createDebuggerState({ + runs: {}, + runsLoaded: { + state: DataLoadState.LOADED, + lastLoadedTimeInMs: Date.now(), + }, + }) + ) + ); + fixture.detectChanges(); + + const inactiveElement = fixture.debugElement.query( + By.css('tf-debugger-v2-inactive') + ); + expect(inactiveElement).toBeTruthy(); + const alertsElement = fixture.debugElement.query( + By.css('tf-debugger-v2-alerts') + ); + expect(alertsElement).toBeNull(); + }); +}); diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_module.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_module.ts index fa09cde183..155cfa5503 100644 --- a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_module.ts +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/debugger_module.ts @@ -13,15 +13,30 @@ 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 {StoreModule} from '@ngrx/store'; +import {EffectsModule} from '@ngrx/effects'; +import {Tfdbg2ServerDataSourceModule} from './data_source/tfdbg2_data_source_module'; import {DebuggerComponent} from './debugger_component'; import {DebuggerContainer} from './debugger_container'; +import {DebuggerEffects} from './effects'; +import {reducers} from './store/debugger_reducers'; +import {DEBUGGER_FEATURE_KEY} from './store/debugger_types'; +import {AlertsModule} from './views/alerts/alerts_module'; import {InactiveModule} from './views/inactive/inactive_module'; @NgModule({ declarations: [DebuggerComponent, DebuggerContainer], - imports: [InactiveModule], + imports: [ + AlertsModule, + CommonModule, + InactiveModule, + Tfdbg2ServerDataSourceModule, + StoreModule.forFeature(DEBUGGER_FEATURE_KEY, reducers), + EffectsModule.forFeature([DebuggerEffects]), + ], exports: [DebuggerContainer], }) export class DebuggerModule {} diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/effects/BUILD b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/effects/BUILD new file mode 100644 index 0000000000..16065c2d28 --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/effects/BUILD @@ -0,0 +1,46 @@ +package(default_visibility = ["//tensorboard:internal"]) + +load("//tensorboard/defs:defs.bzl", "tf_ts_library") +load("@npm_angular_bazel//:index.bzl", "ng_module") + +ng_module( + name = "effects", + srcs = [ + "debugger_effects.ts", + "index.ts", + ], + deps = [ + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/actions", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/data_source", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store", + "@npm//@angular/common", + "@npm//@angular/core", + "@npm//@ngrx/effects", + "@npm//@ngrx/store", + "@npm//rxjs", + ], +) + +tf_ts_library( + name = "debugger_effects_test_lib", + testonly = True, + srcs = [ + "debugger_effects_test.ts", + ], + deps = [ + ":effects", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/actions", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/data_source", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store:types", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/testing", + "//tensorboard/webapp/angular:expect_angular_common_http_testing", + "//tensorboard/webapp/angular:expect_angular_core_testing", + "@npm//@angular/common", + "@npm//@angular/compiler", + "@npm//@angular/core", + "@npm//@ngrx/effects", + "@npm//@ngrx/store", + "@npm//@types/jasmine", + "@npm//rxjs", + ], +) diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/effects/debugger_effects.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/effects/debugger_effects.ts new file mode 100644 index 0000000000..e252b2f1b9 --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/effects/debugger_effects.ts @@ -0,0 +1,70 @@ +/* Copyright 2019 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 {Injectable} from '@angular/core'; +import {Action, Store} from '@ngrx/store'; +import {Actions, ofType, createEffect} from '@ngrx/effects'; +import {Observable} from 'rxjs'; +import {map, mergeMap, withLatestFrom, filter, tap} from 'rxjs/operators'; +import { + debuggerLoaded, + debuggerRunsRequested, + debuggerRunsLoaded, +} from '../actions'; +import {getDebuggerRunsLoaded} from '../store/debugger_selectors'; +import { + DataLoadState, + State, + DebuggerRunListing, +} from '../store/debugger_types'; +import {Tfdbg2HttpServerDataSource} from '../data_source/tfdbg2_data_source'; + +/** @typehack */ import * as _typeHackRxjs from 'rxjs'; +/** @typehack */ import * as _typeHackNgrxStore from '@ngrx/store/src/models'; +/** @typehack */ import * as _typeHackNgrxEffects from '@ngrx/effects/effects'; + +@Injectable() +export class DebuggerEffects { + /** + * Requires to be exported for JSCompiler. JSCompiler, otherwise, + * think it is unused property and deadcode eliminate away. + */ + /** @export */ + readonly loadRunListing$ = createEffect(() => + this.actions$.pipe( + // TODO(cais): Explore consolidating this effect with the greater + // webapp (in tensorboard/webapp), e.g., during PluginChanged actions. + ofType(debuggerLoaded), + withLatestFrom(this.store.select(getDebuggerRunsLoaded)), + filter(([, {state}]) => state !== DataLoadState.LOADING), + tap(() => this.store.dispatch(debuggerRunsRequested())), + mergeMap(() => { + return this.dataSource.fetchRuns().pipe( + map( + (runs) => { + return debuggerRunsLoaded({runs: runs as DebuggerRunListing}); + } + // TODO(cais): Add catchError() to pipe. + ) + ); + }) + ) + ); + + constructor( + private actions$: Actions, + private store: Store, + private dataSource: Tfdbg2HttpServerDataSource + ) {} +} diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/effects/debugger_effects_test.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/effects/debugger_effects_test.ts new file mode 100644 index 0000000000..e4573a68d9 --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/effects/debugger_effects_test.ts @@ -0,0 +1,92 @@ +/* Copyright 2019 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 {TestBed} from '@angular/core/testing'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {provideMockActions} from '@ngrx/effects/testing'; +import {Action, Store} from '@ngrx/store'; +import {MockStore, provideMockStore} from '@ngrx/store/testing'; +import {ReplaySubject, of} from 'rxjs'; + +import { + debuggerLoaded, + debuggerRunsRequested, + debuggerRunsLoaded, +} from '../actions'; +import {Tfdbg2HttpServerDataSource} from '../data_source/tfdbg2_data_source'; +import {State, DebuggerRunListing} from '../store/debugger_types'; +import {createDebuggerState, createState} from '../testing'; +import {DebuggerEffects} from './debugger_effects'; + +describe('Debugger effects', () => { + let debuggerEffects: DebuggerEffects; + let action: ReplaySubject; + let store: MockStore; + let dispatchSpy: jasmine.Spy; + + beforeEach(async () => { + action = new ReplaySubject(1); + + const initialState = createState(createDebuggerState()); + await TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ + provideMockActions(action), + DebuggerEffects, + Tfdbg2HttpServerDataSource, + provideMockStore({initialState}), + ], + }).compileComponents(); + debuggerEffects = TestBed.get(DebuggerEffects); + store = TestBed.get(Store); + dispatchSpy = spyOn(store, 'dispatch'); + }); + + describe('Runs loading', () => { + let recordedActions: Action[] = []; + + beforeEach(() => { + recordedActions = []; + debuggerEffects.loadRunListing$.subscribe((action: Action) => { + recordedActions.push(action); + }); + }); + + it('debugerLoaded action triggers run loading that succeeeds', () => { + const runListingForTest: DebuggerRunListing = { + foo_run: { + tensorFlowVersion: '2.2.0', + startTimeMs: 1337, + }, + }; + const fetchRuns = spyOn( + TestBed.get(Tfdbg2HttpServerDataSource), + 'fetchRuns' + ) + .withArgs() + .and.returnValue(of(runListingForTest)); + + action.next(debuggerLoaded()); + + expect(fetchRuns).toHaveBeenCalled(); + expect(dispatchSpy).toHaveBeenCalledTimes(1); + expect(dispatchSpy).toHaveBeenCalledWith(debuggerRunsRequested()); + expect(recordedActions).toEqual([ + debuggerRunsLoaded({ + runs: runListingForTest, + }), + ]); + }); + }); +}); diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/effects/index.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/effects/index.ts new file mode 100644 index 0000000000..d6bd299787 --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/effects/index.ts @@ -0,0 +1,16 @@ +/* Copyright 2019 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. +==============================================================================*/ + +export * from './debugger_effects'; diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/BUILD b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/BUILD new file mode 100644 index 0000000000..3407d91d5d --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/BUILD @@ -0,0 +1,45 @@ +package(default_visibility = ["//tensorboard:internal"]) + +load("@npm_angular_bazel//:index.bzl", "ng_module") +load("//tensorboard/defs:defs.bzl", "tf_ts_library") + +ng_module( + name = "store", + srcs = [ + "debugger_reducers.ts", + "debugger_selectors.ts", + "index.ts", + ], + deps = [ + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/actions", + "@npm//@ngrx/store", + ], +) + +ng_module( + name = "types", + srcs = [ + "debugger_types.ts", + ], + deps = [ + "//tensorboard/webapp/types", + "@npm//@ngrx/store", + "@npm//rxjs", + ], +) + +tf_ts_library( + name = "debugger_store_test_lib", + testonly = True, + srcs = [ + "debugger_reducers_test.ts", + ], + tsconfig = "//:tsconfig-test", + deps = [ + ":store", + ":types", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/actions", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/testing", + "@npm//@types/jasmine", + ], +) diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/debugger_reducers.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/debugger_reducers.ts new file mode 100644 index 0000000000..51e7a849fa --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/debugger_reducers.ts @@ -0,0 +1,75 @@ +/* Copyright 2019 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 {Action, createReducer, on} from '@ngrx/store'; + +import * as actions from '../actions'; +import {DataLoadState, DebuggerState} from './debugger_types'; + +// HACK: These imports are for type inference. +// https://github.com/bazelbuild/rules_nodejs/issues/1013 +/** @typehack */ import * as _typeHackStore from '@ngrx/store/store'; + +const initialState: DebuggerState = { + runs: {}, + runsLoaded: { + state: DataLoadState.NOT_LOADED, + lastLoadedTimeInMs: null, + }, +}; + +const reducer = createReducer( + initialState, + on( + actions.debuggerRunsRequested, + (state: DebuggerState): DebuggerState => { + return { + ...state, + runsLoaded: { + ...state.runsLoaded, + state: DataLoadState.LOADING, + }, + }; + } + ), + on( + actions.debuggerRunsRequestFailed, + (state: DebuggerState): DebuggerState => { + return { + ...state, + runsLoaded: { + ...state.runsLoaded, + state: DataLoadState.FAILED, + }, + }; + } + ), + on( + actions.debuggerRunsLoaded, + (state: DebuggerState, {runs}): DebuggerState => { + return { + ...state, + runs, + runsLoaded: { + state: DataLoadState.LOADED, + lastLoadedTimeInMs: Date.now(), + }, + }; + } + ) +); + +export function reducers(state: DebuggerState, action: Action) { + return reducer(state, action); +} diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/debugger_reducers_test.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/debugger_reducers_test.ts new file mode 100644 index 0000000000..fbb415754d --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/debugger_reducers_test.ts @@ -0,0 +1,101 @@ +/* Copyright 2019 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 * as actions from '../actions'; +import {reducers} from './debugger_reducers'; +import {DataLoadState} from './debugger_types'; +import {createDebuggerState} from '../testing'; + +describe('Debugger reducers', () => { + describe('Runs loading', () => { + it('sets runsLoaded to loading on requesting runs', () => { + const state = createDebuggerState(); + const nextState = reducers(state, actions.debuggerRunsRequested()); + expect(nextState.runsLoaded.state).toEqual(DataLoadState.LOADING); + expect(nextState.runsLoaded.lastLoadedTimeInMs).toBeNull(); + }); + + it('set runsLoad to failed on request failure', () => { + const state = createDebuggerState({ + runsLoaded: {state: DataLoadState.LOADING, lastLoadedTimeInMs: null}, + }); + const nextState = reducers(state, actions.debuggerRunsRequestFailed()); + expect(nextState.runsLoaded.state).toEqual(DataLoadState.FAILED); + expect(nextState.runsLoaded.lastLoadedTimeInMs).toBeNull(); + }); + + it('sets runsLoaded and runs on successful runs loading', () => { + const state = createDebuggerState(); + const t0 = Date.now(); + const nextState = reducers( + state, + actions.debuggerRunsLoaded({ + runs: { + foo_debugger_run: { + startTimeMs: 111, + tensorFlowVersion: '2.1.0', + }, + }, + }) + ); + expect(nextState.runsLoaded.state).toEqual(DataLoadState.LOADED); + expect(nextState.runsLoaded.lastLoadedTimeInMs).toBeGreaterThanOrEqual( + t0 + ); + expect(nextState.runs).toEqual({ + foo_debugger_run: { + startTimeMs: 111, + tensorFlowVersion: '2.1.0', + }, + }); + }); + + it('Overrides existing runs on successful runs loading', () => { + const state = createDebuggerState({ + runs: { + foo_debugger_run: { + startTimeMs: 111, + tensorFlowVersion: '2.2.0', + }, + }, + runsLoaded: { + state: DataLoadState.LOADED, + lastLoadedTimeInMs: 0, + }, + }); + const t0 = Date.now(); + const nextState = reducers( + state, + actions.debuggerRunsLoaded({ + runs: { + bar_debugger_run: { + startTimeMs: 222, + tensorFlowVersion: '2.3.0', + }, + }, + }) + ); + expect(nextState.runsLoaded.state).toEqual(DataLoadState.LOADED); + expect(nextState.runsLoaded.lastLoadedTimeInMs).toBeGreaterThanOrEqual( + t0 + ); + expect(nextState.runs).toEqual({ + bar_debugger_run: { + startTimeMs: 222, + tensorFlowVersion: '2.3.0', + }, + }); + }); + }); +}); diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/debugger_selectors.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/debugger_selectors.ts new file mode 100644 index 0000000000..695b08fd0e --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/debugger_selectors.ts @@ -0,0 +1,44 @@ +/* Copyright 2019 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 {createSelector, createFeatureSelector} from '@ngrx/store'; +import { + DEBUGGER_FEATURE_KEY, + DebuggerRunListing, + DebuggerState, + State, + LoadState, +} from './debugger_types'; + +// HACK: These imports are for type inference. +// https://github.com/bazelbuild/rules_nodejs/issues/1013 +/** @typehack */ import * as _typeHackSelector from '@ngrx/store/src/selector'; +/** @typehack */ import * as _typeHackStore from '@ngrx/store/store'; + +const selectDebuggerState = createFeatureSelector( + DEBUGGER_FEATURE_KEY +); + +export const getDebuggerRunListing = createSelector( + selectDebuggerState, + (state: DebuggerState): DebuggerRunListing => { + return state.runs; + } +); + +export const getDebuggerRunsLoaded = createSelector( + selectDebuggerState, + (state: DebuggerState): LoadState => state.runsLoaded +); diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/debugger_types.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/debugger_types.ts new file mode 100644 index 0000000000..072665d506 --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/debugger_types.ts @@ -0,0 +1,42 @@ +/* Copyright 2019 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 {LoadState} from '../../../../webapp/types/data'; + +export {DataLoadState, LoadState} from '../../../../webapp/types/data'; + +export const DEBUGGER_FEATURE_KEY = 'debugger'; + +export interface DebuggerRunMetadata { + // Time at which the debugger run started. Milliseconds since the epoch. + startTimeMs: number; + + // TensorFlow Version string. + tensorFlowVersion: string; +} + +export interface DebuggerRunListing { + [runId: string]: DebuggerRunMetadata; +} + +export interface DebuggerState { + // Names of the runs that are available. + runs: DebuggerRunListing; + runsLoaded: LoadState; +} + +export interface State { + [DEBUGGER_FEATURE_KEY]: DebuggerState; +} diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/index.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/index.ts new file mode 100644 index 0000000000..8608b239c2 --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store/index.ts @@ -0,0 +1,18 @@ +/* Copyright 2019 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. +==============================================================================*/ + +export * from './debugger_reducers'; +export * from './debugger_selectors'; +export {DEBUGGER_FEATURE_KEY, DebuggerState, State} from './debugger_types'; diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/testing/BUILD b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/testing/BUILD new file mode 100644 index 0000000000..b8e65d5c0c --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/testing/BUILD @@ -0,0 +1,14 @@ +package(default_visibility = ["//tensorboard:internal"]) + +load("//tensorboard/defs:defs.bzl", "tf_ts_library") + +tf_ts_library( + name = "testing", + testonly = True, + srcs = ["index.ts"], + deps = [ + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/store:types", + "@npm//@angular/core", + "@npm//@ngrx/store", + ], +) diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/testing/index.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/testing/index.ts new file mode 100644 index 0000000000..103921fd1e --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/testing/index.ts @@ -0,0 +1,59 @@ +/* Copyright 2019 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, NgModule} from '@angular/core'; +import {Store} from '@ngrx/store'; + +import { + DataLoadState, + DEBUGGER_FEATURE_KEY, + DebuggerState, + State, +} from '../store/debugger_types'; + +export function createDebuggerState( + override?: Partial +): DebuggerState { + return { + runs: {}, + runsLoaded: { + state: DataLoadState.NOT_LOADED, + lastLoadedTimeInMs: null, + }, + ...override, + }; +} + +export function createState(debuggerState: DebuggerState): State { + return {[DEBUGGER_FEATURE_KEY]: debuggerState}; +} + +// Below are minimalist Angular contains and modules only for testing. They +// serve to decouple the details of Debugger from the testing of outside modules +// that use it. + +@Component({ + selector: 'tf-debugger-v2', + template: ``, +}) +export class TestingDebuggerContainer { + constructor(private readonly store: Store<{}>) {} +} + +@NgModule({ + declarations: [TestingDebuggerContainer], + exports: [TestingDebuggerContainer], +}) +export class TestingDebuggerModule {} diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/alerts/BUILD b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/alerts/BUILD new file mode 100644 index 0000000000..2fcadb5643 --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/alerts/BUILD @@ -0,0 +1,24 @@ +package(default_visibility = ["//tensorboard:internal"]) + +load("@npm_angular_bazel//:index.bzl", "ng_module") + +licenses(["notice"]) # Apache 2.0 + +ng_module( + name = "alerts", + srcs = [ + "alerts_component.ts", + "alerts_container.ts", + "alerts_module.ts", + ], + assets = [ + "alerts_component.css", + "alerts_component.ng.html", + ], + deps = [ + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/actions", + "@npm//@angular/core", + "@npm//@ngrx/store", + "@npm//rxjs", + ], +) diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/alerts/alerts_component.css b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/alerts/alerts_component.css new file mode 100644 index 0000000000..5beeb910b9 --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/alerts/alerts_component.css @@ -0,0 +1,16 @@ +/* Copyright 2019 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. +==============================================================================*/ + +/* TODO(cais): Flesh out the styling. */ diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/alerts/alerts_component.ng.html b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/alerts/alerts_component.ng.html new file mode 100644 index 0000000000..c7a01a1afb --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/alerts/alerts_component.ng.html @@ -0,0 +1,24 @@ + + +
+
Debugging
+
+ Alerts +
+ +
diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/alerts/alerts_component.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/alerts/alerts_component.ts new file mode 100644 index 0000000000..3c5518db30 --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/alerts/alerts_component.ts @@ -0,0 +1,22 @@ +/* Copyright 2019 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} from '@angular/core'; + +@Component({ + selector: 'alerts-component', + templateUrl: './alerts_component.ng.html', + styleUrls: ['./alerts_component.css'], +}) +export class AlertsComponent {} diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/alerts/alerts_container.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/alerts/alerts_container.ts new file mode 100644 index 0000000000..459dd406dd --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/alerts/alerts_container.ts @@ -0,0 +1,37 @@ +/* Copyright 2019 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, OnInit} from '@angular/core'; +import {Store} from '@ngrx/store'; + +import {alertsViewLoaded} from '../../actions'; + +/** @typehack */ import * as _typeHackRxjs from 'rxjs'; + +// TODO(cais): Move to a separate file. +export interface AlertsState {} + +@Component({ + selector: 'tf-debugger-v2-alerts', + template: ` + + `, +}) +export class AlertsContainer implements OnInit { + constructor(private readonly store: Store) {} + + ngOnInit(): void { + this.store.dispatch(alertsViewLoaded()); + } +} diff --git a/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/alerts/alerts_module.ts b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/alerts/alerts_module.ts new file mode 100644 index 0000000000..c7ca573764 --- /dev/null +++ b/tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/views/alerts/alerts_module.ts @@ -0,0 +1,25 @@ +/* Copyright 2019 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 {NgModule} from '@angular/core'; + +import {AlertsComponent} from './alerts_component'; +import {AlertsContainer} from './alerts_container'; + +@NgModule({ + declarations: [AlertsComponent, AlertsContainer], + exports: [AlertsContainer], +}) +export class AlertsModule {} diff --git a/tensorboard/webapp/core/effects/core_effects.ts b/tensorboard/webapp/core/effects/core_effects.ts index 5f9e61eaad..b98e43b0d6 100644 --- a/tensorboard/webapp/core/effects/core_effects.ts +++ b/tensorboard/webapp/core/effects/core_effects.ts @@ -32,7 +32,7 @@ import { pluginsListingFailed, } from '../actions'; import {getPluginsListLoaded} from '../store'; -import {LoadState} from '../../types/api'; +import {DataLoadState} from '../../types/data'; import {State} from '../store/core_types'; import {TBServerDataSource} from '../../webapp_data_source/tb_server_data_source'; @@ -50,7 +50,7 @@ export class CoreEffects { this.actions$.pipe( ofType(coreLoaded, reload), withLatestFrom(this.store.select(getPluginsListLoaded)), - filter(([, {state}]) => state !== LoadState.LOADING), + filter(([, {state}]) => state !== DataLoadState.LOADING), tap(() => this.store.dispatch(pluginsListingRequested())), mergeMap(() => { return zip( diff --git a/tensorboard/webapp/core/effects/core_effects_test.ts b/tensorboard/webapp/core/effects/core_effects_test.ts index 6170586838..38ea57de72 100644 --- a/tensorboard/webapp/core/effects/core_effects_test.ts +++ b/tensorboard/webapp/core/effects/core_effects_test.ts @@ -28,7 +28,8 @@ import {State} from '../store'; import {createPluginMetadata, createState, createCoreState} from '../testing'; -import {PluginsListing, LoadState as DataLoadState} from '../../types/api'; +import {PluginsListing} from '../../types/api'; +import {DataLoadState} from '../../types/data'; import {TBServerDataSource} from '../../webapp_data_source/tb_server_data_source'; describe('core_effects', () => { diff --git a/tensorboard/webapp/core/store/core_reducers.ts b/tensorboard/webapp/core/store/core_reducers.ts index 985b867efa..9c09ba5985 100644 --- a/tensorboard/webapp/core/store/core_reducers.ts +++ b/tensorboard/webapp/core/store/core_reducers.ts @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ import {Action, createReducer, on} from '@ngrx/store'; -import {LoadState as DataLoadState} from '../../types/api'; +import {DataLoadState} from '../../types/data'; import * as actions from '../actions'; import {CoreState} from './core_types'; diff --git a/tensorboard/webapp/core/store/core_reducers_test.ts b/tensorboard/webapp/core/store/core_reducers_test.ts index b622bd6022..e19ae15698 100644 --- a/tensorboard/webapp/core/store/core_reducers_test.ts +++ b/tensorboard/webapp/core/store/core_reducers_test.ts @@ -15,7 +15,7 @@ limitations under the License. import * as actions from '../actions'; import {reducers} from './core_reducers'; import {createPluginMetadata, createCoreState} from '../testing'; -import {LoadState} from '../../types/api'; +import {DataLoadState} from '../../types/data'; function createPluginsListing() { return { @@ -50,12 +50,12 @@ describe('core reducer', () => { { specSetName: '#pluginsListingRequested', action: actions.pluginsListingRequested(), - expectedState: LoadState.LOADING, + expectedState: DataLoadState.LOADING, }, { specSetName: '#pluginsListingFailed', action: actions.pluginsListingFailed(), - expectedState: LoadState.FAILED, + expectedState: DataLoadState.FAILED, }, ].forEach(({specSetName, action, expectedState}) => { describe(specSetName, () => { @@ -63,7 +63,7 @@ describe('core reducer', () => { const state = createCoreState({ pluginsListLoaded: { lastLoadedTimeInMs: null, - state: LoadState.NOT_LOADED, + state: DataLoadState.NOT_LOADED, }, }); const nextState = reducers(state, action); @@ -75,7 +75,7 @@ describe('core reducer', () => { const state = createCoreState({ pluginsListLoaded: { lastLoadedTimeInMs: 1337, - state: LoadState.NOT_LOADED, + state: DataLoadState.NOT_LOADED, }, }); const nextState = reducers(state, action); @@ -106,7 +106,7 @@ describe('core reducer', () => { activePlugin: 'foo', plugins: {}, pluginsListLoaded: { - state: LoadState.NOT_LOADED, + state: DataLoadState.NOT_LOADED, lastLoadedTimeInMs: null, }, }); @@ -116,7 +116,7 @@ describe('core reducer', () => { ); expect(nextState.pluginsListLoaded).toEqual({ - state: LoadState.LOADED, + state: DataLoadState.LOADED, lastLoadedTimeInMs: 1000, }); }); diff --git a/tensorboard/webapp/core/store/core_selectors.ts b/tensorboard/webapp/core/store/core_selectors.ts index 90b2b8a493..d0698068be 100644 --- a/tensorboard/webapp/core/store/core_selectors.ts +++ b/tensorboard/webapp/core/store/core_selectors.ts @@ -15,7 +15,8 @@ limitations under the License. import {createSelector, createFeatureSelector} from '@ngrx/store'; import {PluginId, PluginsListing} from '../../types/api'; -import {CoreState, State, CORE_FEATURE_KEY, LoadState} from './core_types'; +import {LoadState} from '../../types/data'; +import {CoreState, State, CORE_FEATURE_KEY} from './core_types'; // HACK: These imports are for type inference. // https://github.com/bazelbuild/rules_nodejs/issues/1013 diff --git a/tensorboard/webapp/core/store/core_types.ts b/tensorboard/webapp/core/store/core_types.ts index 64b3854939..cd854ddec8 100644 --- a/tensorboard/webapp/core/store/core_types.ts +++ b/tensorboard/webapp/core/store/core_types.ts @@ -18,20 +18,11 @@ limitations under the License. // redundant copies in sync. If the state shape and the API types need to // diverge in the future, that's straightforward: we'll leave types/api in place, // remove this import, and write the divergent state types explicitly here. -import { - PluginId, - PluginsListing, - LoadState as DataLoadState, -} from '../../types/api'; +import {PluginId, PluginsListing} from '../../types/api'; +import {LoadState} from '../../types/data'; export const CORE_FEATURE_KEY = 'core'; -export interface LoadState { - state: DataLoadState; - // Time since epoch. - lastLoadedTimeInMs: number | null; -} - export interface CoreState { activePlugin: PluginId | null; plugins: PluginsListing; diff --git a/tensorboard/webapp/core/testing/index.ts b/tensorboard/webapp/core/testing/index.ts index e8ad149436..d2d1e6458b 100644 --- a/tensorboard/webapp/core/testing/index.ts +++ b/tensorboard/webapp/core/testing/index.ts @@ -12,7 +12,8 @@ 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 {PluginMetadata, LoadingMechanismType, LoadState} from '../../types/api'; +import {LoadingMechanismType, PluginMetadata} from '../../types/api'; +import {DataLoadState} from '../../types/data'; import {CoreState, State, CORE_FEATURE_KEY} from '../store/core_types'; export function createPluginMetadata(displayName: string): PluginMetadata { @@ -32,7 +33,7 @@ export function createCoreState(override?: Partial): CoreState { activePlugin: null, plugins: {}, pluginsListLoaded: { - state: LoadState.NOT_LOADED, + state: DataLoadState.NOT_LOADED, lastLoadedTimeInMs: null, }, reloadPeriodInMs: 30000, diff --git a/tensorboard/webapp/plugins/BUILD b/tensorboard/webapp/plugins/BUILD index a99e38443d..5c0502b938 100644 --- a/tensorboard/webapp/plugins/BUILD +++ b/tensorboard/webapp/plugins/BUILD @@ -36,7 +36,7 @@ tf_ts_library( tsconfig = "//:tsconfig-test", deps = [ ":plugins", - "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin:debugger_v2", + "//tensorboard/plugins/debugger_v2/tf_debugger_v2_plugin/testing", "//tensorboard/webapp/angular:expect_angular_core_testing", "//tensorboard/webapp/core", "//tensorboard/webapp/core/store", diff --git a/tensorboard/webapp/plugins/plugins_container.ts b/tensorboard/webapp/plugins/plugins_container.ts index 1288e6e8ec..d1f8055b26 100644 --- a/tensorboard/webapp/plugins/plugins_container.ts +++ b/tensorboard/webapp/plugins/plugins_container.ts @@ -17,7 +17,8 @@ import {Store, select, createSelector} from '@ngrx/store'; import {getPlugins, getActivePlugin, getPluginsListLoaded} from '../core/store'; import {PluginMetadata} from '../types/api'; -import {LoadState, State} from '../core/store/core_types'; +import {LoadState} from '../types/data'; +import {State} from '../core/store/core_types'; /** @typehack */ import * as _typeHackRxjs from 'rxjs'; diff --git a/tensorboard/webapp/plugins/plugins_container_test.ts b/tensorboard/webapp/plugins/plugins_container_test.ts index 8310964940..c28a081a4f 100644 --- a/tensorboard/webapp/plugins/plugins_container_test.ts +++ b/tensorboard/webapp/plugins/plugins_container_test.ts @@ -20,13 +20,14 @@ import {provideMockStore, MockStore} from '@ngrx/store/testing'; import {PluginsContainer} from './plugins_container'; import {PluginsComponent} from './plugins_component'; -import {PluginId, LoadingMechanismType, LoadState} from '../types/api'; +import {PluginId, LoadingMechanismType} from '../types/api'; +import {DataLoadState} from '../types/data'; import {createState, createCoreState} from '../core/testing'; import {State} from '../core/store'; // store/index.ts doesn't export this, but it's OK to use for testing import {CoreState} from '../core/store/core_types'; -import {DebuggerModule} from '../../plugins/debugger_v2/tf_debugger_v2_plugin/debugger_module'; +import {TestingDebuggerModule} from '../../plugins/debugger_v2/tf_debugger_v2_plugin/testing'; /** @typehack */ import * as _typeHackStore from '@ngrx/store'; @@ -68,7 +69,7 @@ describe('plugins_component', () => { await TestBed.configureTestingModule({ providers: [provideMockStore({initialState}), PluginsContainer], declarations: [PluginsContainer, PluginsComponent], - imports: [DebuggerModule], + imports: [TestingDebuggerModule], }).compileComponents(); store = TestBed.get(Store); }); @@ -182,7 +183,7 @@ describe('plugins_component', () => { describe('updates', () => { function setLastLoadedTime( timeInMs: number | null, - state = LoadState.LOADED + state = DataLoadState.LOADED ) { store.setState( createState( @@ -201,7 +202,7 @@ describe('plugins_component', () => { it('invokes reload method on the dashboard DOM', () => { const fixture = TestBed.createComponent(PluginsContainer); - setLastLoadedTime(null, LoadState.NOT_LOADED); + setLastLoadedTime(null, DataLoadState.NOT_LOADED); fixture.detectChanges(); const {nativeElement} = fixture.debugElement.query(By.css('.plugins')); diff --git a/tensorboard/webapp/types/BUILD b/tensorboard/webapp/types/BUILD index 57be310a0d..ea17e047df 100644 --- a/tensorboard/webapp/types/BUILD +++ b/tensorboard/webapp/types/BUILD @@ -8,5 +8,6 @@ tf_ts_library( name = "types", srcs = [ "api.ts", + "data.ts", ], ) diff --git a/tensorboard/webapp/types/api.ts b/tensorboard/webapp/types/api.ts index 546c63d3f1..5a012393f4 100644 --- a/tensorboard/webapp/types/api.ts +++ b/tensorboard/webapp/types/api.ts @@ -12,12 +12,6 @@ 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. ==============================================================================*/ -export enum LoadState { - NOT_LOADED, - LOADED, - LOADING, - FAILED, -} export type PluginId = string; diff --git a/tensorboard/webapp/types/data.ts b/tensorboard/webapp/types/data.ts new file mode 100644 index 0000000000..c23c360752 --- /dev/null +++ b/tensorboard/webapp/types/data.ts @@ -0,0 +1,29 @@ +/* Copyright 2019 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. +==============================================================================*/ + +/** Data-related types */ + +export enum DataLoadState { + NOT_LOADED, + LOADED, + LOADING, + FAILED, +} + +export interface LoadState { + state: DataLoadState; + // Time since epoch. + lastLoadedTimeInMs: number | null; +}