Skip to content

Commit

Permalink
ng: conditionally enable debugger_v2 (#3431)
Browse files Browse the repository at this point in the history
Using the new experimentPlugin mechanism (#3344), we will control
whether to show a plugin or not.

Please use
`http://localhost:6006/ng_index.html?experimentalPlugin=debugger-v2` to
see the debugger-v2 in the future.
  • Loading branch information
stephanwlee authored and bileschi committed Apr 15, 2020
1 parent 572f9be commit 5e40895
Show file tree
Hide file tree
Showing 12 changed files with 206 additions and 13 deletions.
1 change: 1 addition & 0 deletions tensorboard/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ py_library(
visibility = ["//visibility:public"],
deps = [
"//tensorboard:expect_pkg_resources_installed",
"//tensorboard/backend:experimental_plugin",
"//tensorboard/plugins:base_plugin",
"//tensorboard/plugins/audio:audio_plugin",
"//tensorboard/plugins/beholder:beholder_plugin_loader",
Expand Down
12 changes: 11 additions & 1 deletion tensorboard/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

import pkg_resources

from tensorboard.backend import experimental_plugin
from tensorboard.compat import tf
from tensorboard.plugins import base_plugin
from tensorboard.plugins.audio import audio_plugin
Expand All @@ -55,13 +56,22 @@

logger = logging.getLogger(__name__)


class ExperimentalDebuggerV2Plugin(
debugger_v2_plugin.DebuggerV2Plugin, experimental_plugin.ExperimentalPlugin
):
"""Debugger v2 plugin marked as experimental."""

pass


# Ordering matters. The order in which these lines appear determines the
# ordering of tabs in TensorBoard's GUI.
_PLUGINS = [
core_plugin.CorePluginLoader,
scalars_plugin.ScalarsPlugin,
custom_scalars_plugin.CustomScalarsPlugin,
debugger_v2_plugin.DebuggerV2Plugin,
ExperimentalDebuggerV2Plugin,
images_plugin.ImagesPlugin,
audio_plugin.AudioPlugin,
debugger_plugin_loader.DebuggerPluginLoader,
Expand Down
12 changes: 12 additions & 0 deletions tensorboard/webapp/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ tf_ts_library(
],
)

ng_module(
name = "app_state",
srcs = [
"app_state.ts",
],
deps = [
"//tensorboard/webapp/core/store",
"//tensorboard/webapp/feature_flag/store:types",
],
)

# Wrapper that prepares Angular app for deployment, dealing with browser
# compatibility, Vulcanization, and configuration (e.g. prod vs. dev).
ng_module(
Expand Down Expand Up @@ -147,6 +158,7 @@ tf_ng_web_test_suite(
"//tensorboard/webapp/reloader:test_lib",
"//tensorboard/webapp/settings:test_lib",
"//tensorboard/webapp/webapp_data_source:feature_flag_test_lib",
"//tensorboard/webapp/webapp_data_source:webapp_data_source_test_lib",
],
)

Expand Down
19 changes: 19 additions & 0 deletions tensorboard/webapp/app_state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* Copyright 2020 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 {State as CoreState} from './core/store/core_types';
import {State as FeatureFlagState} from './feature_flag/store/feature_flag_types';

export type State = CoreState & FeatureFlagState;
4 changes: 4 additions & 0 deletions tensorboard/webapp/core/effects/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ ng_module(
"index.ts",
],
deps = [
"//tensorboard/webapp:app_state",
"//tensorboard/webapp/core/actions",
"//tensorboard/webapp/core/store",
"//tensorboard/webapp/feature_flag/store",
"//tensorboard/webapp/types",
"//tensorboard/webapp/webapp_data_source",
"@npm//@angular/common",
Expand All @@ -30,11 +32,13 @@ tf_ts_library(
],
deps = [
":effects",
"//tensorboard/webapp:app_state",
"//tensorboard/webapp/angular:expect_angular_core_testing",
"//tensorboard/webapp/angular:expect_ngrx_store_testing",
"//tensorboard/webapp/core/actions",
"//tensorboard/webapp/core/store",
"//tensorboard/webapp/core/testing",
"//tensorboard/webapp/feature_flag/store",
"//tensorboard/webapp/types",
"//tensorboard/webapp/webapp_data_source",
"//tensorboard/webapp/webapp_data_source:http_client_testing",
Expand Down
12 changes: 8 additions & 4 deletions tensorboard/webapp/core/effects/core_effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ import {
} from '../actions';
import {getPluginsListLoaded} from '../store';
import {DataLoadState} from '../../types/data';
import {State} from '../store/core_types';
import {TBServerDataSource} from '../../webapp_data_source/tb_server_data_source';
import {getEnabledExperimentalPlugins} from '../../feature_flag/store/feature_flag_selectors';
import {State} from '../../app_state';

/** @typehack */ import * as _typeHackRxjs from 'rxjs';
/** @typehack */ import * as _typeHackNgrx from '@ngrx/store/src/models';
Expand All @@ -51,12 +52,15 @@ export class CoreEffects {
readonly loadPluginsListing$ = createEffect(() =>
this.actions$.pipe(
ofType(coreLoaded, reload, manualReload),
withLatestFrom(this.store.select(getPluginsListLoaded)),
withLatestFrom(
this.store.select(getPluginsListLoaded),
this.store.select(getEnabledExperimentalPlugins)
),
filter(([, {state}]) => state !== DataLoadState.LOADING),
tap(() => this.store.dispatch(pluginsListingRequested())),
mergeMap(() => {
mergeMap(([, , enabledExperimentalPlugins]) => {
return zip(
this.webappDataSource.fetchPluginsListing(),
this.webappDataSource.fetchPluginsListing(enabledExperimentalPlugins),
this.webappDataSource.fetchRuns(),
this.webappDataSource.fetchEnvironments()
).pipe(
Expand Down
48 changes: 46 additions & 2 deletions tensorboard/webapp/core/effects/core_effects_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ import {ReplaySubject, of} from 'rxjs';

import {CoreEffects} from './core_effects';
import * as coreActions from '../actions';
import {State} from '../store';
import {State} from '../../app_state';

import {createPluginMetadata, createState, createCoreState} from '../testing';

import {PluginsListing} from '../../types/api';
import {DataLoadState} from '../../types/data';
import {TBServerDataSource} from '../../webapp_data_source/tb_server_data_source';
import {getEnabledExperimentalPlugins} from '../../feature_flag/store/feature_flag_selectors';
import {
TBHttpClientTestingModule,
HttpTestingController,
Expand All @@ -37,7 +38,7 @@ describe('core_effects', () => {
let httpMock: HttpTestingController;
let coreEffects: CoreEffects;
let action: ReplaySubject<Action>;
let store: MockStore<State>;
let store: MockStore<Partial<State>>;
let fetchRuns: jasmine.Spy;
let fetchEnvironments: jasmine.Spy;
let dispatchSpy: jasmine.Spy;
Expand Down Expand Up @@ -74,6 +75,8 @@ describe('core_effects', () => {
fetchEnvironments = spyOn(dataSource, 'fetchEnvironments')
.withArgs()
.and.returnValue(of(null));

store.overrideSelector(getEnabledExperimentalPlugins, []);
});

afterEach(() => {
Expand All @@ -96,6 +99,9 @@ describe('core_effects', () => {
});

it('fetches plugins listing and fires success action', () => {
store.overrideSelector(getEnabledExperimentalPlugins, []);
store.refreshState();

const pluginsListing: PluginsListing = {
core: createPluginMetadata('Core'),
};
Expand All @@ -118,6 +124,44 @@ describe('core_effects', () => {
expect(recordedActions).toEqual([expected]);
});

it(
'appends query params to the data/plugins_listing when ' +
'getEnabledExperimentalPlugins is non-empty',
() => {
store.overrideSelector(getEnabledExperimentalPlugins, [
'alpha',
'beta',
]);
store.refreshState();

const pluginsListing: PluginsListing = {
core: createPluginMetadata('Core'),
};

action.next(onAction);
// Flushing the request response invokes above subscription sychronously.
httpMock
.expectOne(
'data/plugins_listing?experimentalPlugin=alpha&' +
'experimentalPlugin=beta'
)
.flush(pluginsListing);

expect(fetchRuns).toHaveBeenCalled();
expect(fetchEnvironments).toHaveBeenCalled();

expect(dispatchSpy).toHaveBeenCalledTimes(1);
expect(dispatchSpy).toHaveBeenCalledWith(
coreActions.pluginsListingRequested()
);

const expected = coreActions.pluginsListingLoaded({
plugins: pluginsListing,
});
expect(recordedActions).toEqual([expected]);
}
);

it('ignores the action when loadState is loading', () => {
store.setState(
createState(
Expand Down
14 changes: 13 additions & 1 deletion tensorboard/webapp/feature_flag/store/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ ng_module(
srcs = [
"feature_flag_reducers.ts",
"feature_flag_selectors.ts",
"feature_flag_types.ts",
],
deps = [
":types",
"//tensorboard/webapp:app_state",
"//tensorboard/webapp/feature_flag:types",
"//tensorboard/webapp/feature_flag/actions",
"//tensorboard/webapp/webapp_data_source:feature_flag",
Expand All @@ -18,6 +19,16 @@ ng_module(
],
)

ng_module(
name = "types",
srcs = [
"feature_flag_types.ts",
],
deps = [
"//tensorboard/webapp/feature_flag:types",
],
)

ng_module(
name = "store_test_lib",
testonly = True,
Expand All @@ -28,6 +39,7 @@ ng_module(
],
deps = [
":store",
":types",
"//tensorboard/webapp/feature_flag:types",
"//tensorboard/webapp/feature_flag/actions",
"@npm//@ngrx/store",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ export const getFeature = createSelector(
}
);

export function getEnabledExperimentalPlugins(state: State): string[] {
return getFeature(state, 'enabledExperimentalPlugins') as string[];
}
export const getEnabledExperimentalPlugins = createSelector(
selectFeatureFlagState,
(state) => {
return state.enabledExperimentalPlugins as string[];
}
);
14 changes: 14 additions & 0 deletions tensorboard/webapp/webapp_data_source/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ ng_module(
],
)

ng_module(
name = "webapp_data_source_test_lib",
testonly = True,
srcs = [
"tb_server_data_source_test.ts",
],
deps = [
":http_client_testing",
":webapp_data_source",
"@npm//@angular/core",
"@npm//@types/jasmine",
],
)

ng_module(
name = "http_client",
srcs = [
Expand Down
20 changes: 18 additions & 2 deletions tensorboard/webapp/webapp_data_source/tb_server_data_source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,31 @@ import {TBHttpClient} from './tb_http_client';

/** @typehack */ import * as _typeHackRxjs from 'rxjs';

function getPluginsListingQueryParams(enabledExperimentPluginIds: string[]) {
if (!enabledExperimentPluginIds.length) {
return null;
}

const params = new URLSearchParams();
for (const pluginId of enabledExperimentPluginIds) {
params.append('experimentalPlugin', pluginId);
}
return params;
}

@Injectable()
export class TBServerDataSource {
// TODO(soergel): implements WebappDataSource
private tfBackend = (document.createElement('tf-backend') as any).tf_backend;

constructor(private http: TBHttpClient) {}

fetchPluginsListing() {
return this.http.get<PluginsListing>('data/plugins_listing');
fetchPluginsListing(enabledExperimentPluginIds: string[]) {
const params = getPluginsListingQueryParams(enabledExperimentPluginIds);
const pathWithParams = params
? `data/plugins_listing?${params.toString()}`
: 'data/plugins_listing';
return this.http.get<PluginsListing>(pathWithParams);
}

fetchRuns() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/* Copyright 2020 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 {TBServerDataSource} from './tb_server_data_source';
import {
TBHttpClientTestingModule,
HttpTestingController,
} from './tb_http_client_testing';

describe('tb_server_data_source', () => {
describe('TBServerDataSource', () => {
let dataSource: TBServerDataSource;
let httpMock: HttpTestingController;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TBHttpClientTestingModule],
providers: [TBServerDataSource],
}).compileComponents();

httpMock = TestBed.get(HttpTestingController);
dataSource = TestBed.get(TBServerDataSource);
});

describe('fetchPluginsListing', () => {
it('fetches from "data/plugins_listing"', () => {
dataSource.fetchPluginsListing([]).subscribe(jasmine.createSpy());
httpMock.expectOne('data/plugins_listing');
});

it('passes query parameter, "experimentalPlugin"', () => {
dataSource
.fetchPluginsListing(['foo', 'bar'])
.subscribe(jasmine.createSpy());
httpMock.expectOne(
'data/plugins_listing?experimentalPlugin=foo&experimentalPlugin=bar'
);
});
});
});
});

0 comments on commit 5e40895

Please sign in to comment.