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 e2865d65345..6519ca9bc28 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 @@ -38,19 +38,23 @@ import {State} from './store/debugger_types'; ], }) export class DebuggerContainer implements OnInit, OnDestroy { - readonly runs$ = this.store.pipe(select(getDebuggerRunListing)); + readonly runs$; - readonly runsIds$ = this.store.pipe( - select( - createSelector(getDebuggerRunListing, (runs): string[] => - Object.keys(runs) - ) - ) - ); + readonly runsIds$; - readonly activeRunId$ = this.store.pipe(select(getActiveRunId)); + readonly activeRunId$; - constructor(private readonly store: Store) {} + constructor(private readonly store: Store) { + this.runs$ = this.store.pipe(select(getDebuggerRunListing)); + this.runsIds$ = this.store.pipe( + select( + createSelector(getDebuggerRunListing, (runs): string[] => + Object.keys(runs) + ) + ) + ); + this.activeRunId$ = this.store.pipe(select(getActiveRunId)); + } ngOnInit(): void { this.store.dispatch(debuggerLoaded()); 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 index 3dd57590890..c814ebdd317 100644 --- 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 @@ -59,27 +59,31 @@ const ALERT_TYPE_TO_DISPLAY_NAME_AND_SYMBOL: { changeDetection: ChangeDetectionStrategy.OnPush, }) export class AlertsContainer { - readonly numAlerts$ = this.store.pipe(select(getNumAlerts)); + readonly numAlerts$; - readonly alertsBreakdown$ = this.store.pipe( - select( - createSelector(getAlertsBreakdown, (alertsBreakdown) => { - const alertTypes = Object.keys(alertsBreakdown); - alertTypes.sort(); - return alertTypes.map((alertType): AlertTypeDisplay => { - return { - type: alertType as AlertType, - ...ALERT_TYPE_TO_DISPLAY_NAME_AND_SYMBOL[alertType], - count: alertsBreakdown[alertType], - }; - }); - }) - ) - ); + readonly alertsBreakdown$; - readonly focusType$ = this.store.pipe(select(getAlertsFocusType)); + readonly focusType$; - constructor(private readonly store: Store) {} + constructor(private readonly store: Store) { + this.numAlerts$ = this.store.pipe(select(getNumAlerts)); + this.alertsBreakdown$ = this.store.pipe( + select( + createSelector(getAlertsBreakdown, (alertsBreakdown) => { + const alertTypes = Object.keys(alertsBreakdown); + alertTypes.sort(); + return alertTypes.map((alertType): AlertTypeDisplay => { + return { + type: alertType as AlertType, + ...ALERT_TYPE_TO_DISPLAY_NAME_AND_SYMBOL[alertType], + count: alertsBreakdown[alertType], + }; + }); + }) + ) + ); + this.focusType$ = this.store.pipe(select(getAlertsFocusType)); + } onToggleFocusType(alertType: AlertType) { this.store.dispatch(alertTypeFocusToggled({alertType})); diff --git a/tensorboard/webapp/core/effects/core_effects.ts b/tensorboard/webapp/core/effects/core_effects.ts index 4d09d134856..955576874c6 100644 --- a/tensorboard/webapp/core/effects/core_effects.ts +++ b/tensorboard/webapp/core/effects/core_effects.ts @@ -103,173 +103,14 @@ export class CoreEffects { return from(this.tfBackend.ref.runsStore.refresh()); } - private readonly onDashboardLoad$ = - // Loading dashboard data on `coreLoaded` is temporary; not all TB apps - // use the router. For those router-less applications, we have to support - // the legacy `coreLoaded`. - merge( - this.actions$.pipe( - ofType(coreLoaded, navigated), - withLatestFrom(this.store.select(getActiveRoute)), - distinctUntilChanged(([, beforeRoute], [, afterRoute]) => { - return areSameRouteKindAndExperiments(beforeRoute, afterRoute); - }) - ), - this.actions$.pipe(ofType(reload, manualReload)) - ).pipe( - withLatestFrom(this.store.select(getRouteKind)), - filter(([, routeKind]) => DASHBOARD_ROUTE_KIND.has(routeKind)), - throttleTime(DATA_LOAD_CONDITIONAL_THROTTLE_IN_MS, undefined, { - leading: true, - }) - ); + private readonly onDashboardLoad$; /** * Requires to be exported for JSCompiler. JSCompiler, otherwise, * think it is unused property and deadcode eliminate away. */ /** @export */ - readonly fetchWebAppData$ = createEffect( - () => { - const pluginsListingReload$ = this.onDashboardLoad$.pipe( - withLatestFrom( - this.store.select(getPluginsListLoaded), - this.store.select(getEnabledExperimentalPlugins) - ), - filter(([, {state}]) => state !== DataLoadState.LOADING), - tap(() => this.store.dispatch(pluginsListingRequested())), - mergeMap(([, , enabledExperimentalPlugins]) => { - return zip( - this.webappDataSource.fetchPluginsListing( - enabledExperimentalPlugins - ), - // TODO(tensorboard-team): consider brekaing the environments out of - // the pluginsListingLoaded; currently, plugins listing load state - // is connected to the environments which is not ideal. Have its own - // load state. - this.fetchEnvironment() - ).pipe( - map(([plugins]) => { - this.store.dispatch(pluginsListingLoaded({plugins})); - }), - catchError((e) => { - if (e instanceof TBServerError) { - this.store.dispatch( - pluginsListingFailed({failureCode: e.failureCode}) - ); - } else { - this.store.dispatch( - pluginsListingFailed({ - failureCode: PluginsListFailureCode.UNKNOWN, - }) - ); - } - return EMPTY; - }) - ); - }) - ); - - const runsReload$ = this.onDashboardLoad$.pipe( - map(([, routeKind]) => routeKind), - switchMap((routeKind) => { - if (routeKind !== RouteKind.COMPARE_EXPERIMENT) { - return of([]); - } - - // If alias map changes, we need to refetch the list of runs as - // Polymer's run selector and tags rely on run names including the - // alias. - return this.store.select(getExperimentIdToExperimentAliasMap).pipe( - distinctUntilChanged((beforeAliasDict, afterAliasDict) => { - const entries = Object.entries(beforeAliasDict); - const afterAliasMap = new Map(Object.entries(afterAliasDict)); - if (entries.length !== afterAliasMap.size) { - return false; - } - for (const [experimentId, alias] of entries) { - if (!afterAliasMap.get(experimentId)) { - return false; - } - if ( - afterAliasMap.get(experimentId)!.aliasText !== - alias.aliasText || - afterAliasMap.get(experimentId)!.aliasNumber !== - alias.aliasNumber - ) { - return false; - } - } - return true; - }), - // HACK: arbitrary microtask delay. - // An alias change -> route change -> browser url change -> - // `navigated` action. Because we, especially Polymer code, makes - // requests under a relative path, we must make requests only after - // the URL has been modified to reflect new alias or experiment id. - // - // While we can subscribe to `navigated` without - // `distinctUntilChanged` and `areSameRouteExperiments`, it is hard - // to throttle quick alias Map changes while immediately making a - // request for a real navigation. For example, for route A and - // route B: - // - // 0 100 600 700 - // A -> A' -> A" -> B - // ↑ ↑ ↑ - // req noop req req - // - // Above, we would like to make the request immediately when the set - // of experiments change while debouncing alias changes when the set - // of experiments have not changed. - // - // Instead of more elaborate rxjs techniques, we are - // using `delay(0)` to give the router a chance to modify the URL - // before making the request. - delay(0), - // Prevent changes in the alias map not to over-trigger requests; - // However, we want to use throttle instead of debounce since we - // need to emit on `leading` so it does not cause 500ms delay on - // page load. - throttleTime(ALIAS_CHANGE_RUNS_RELOAD_THROTTLE_IN_MS, undefined, { - leading: true, - trailing: true, - }) - ); - }), - withLatestFrom( - this.store.select(getRouteKind), - this.store.select(getPolymerRunsLoadState) - ), - filter(([, routeKind, loadState]) => { - // While the same check was applied earlier, `delay` + `throttleTime` - // makes it unpredictable and we can sometimes make requests for the - // wrong route. This check prevents making the request in wrong - // hostname in a fool proof way. - return ( - DASHBOARD_ROUTE_KIND.has(routeKind) && - loadState.state !== DataLoadState.LOADING - ); - }), - tap(() => { - this.store.dispatch(polymerRunsFetchRequested()); - }), - switchMap(() => { - return this.refreshPolymerRuns(); - }), - tap(() => { - this.store.dispatch(polymerRunsFetchSucceeded()); - }), - catchError(() => { - this.store.dispatch(polymerRunsFetchFailed()); - return EMPTY; - }) - ); - - return merge(pluginsListingReload$, runsReload$); - }, - {dispatch: false} - ); + readonly fetchWebAppData$; /** * HACK: COMPOSITE ACTION -- Fire `changePlugin` on first truthy value of @@ -283,24 +124,7 @@ export class CoreEffects { * * @export */ - readonly dispatchChangePlugin$ = createEffect( - () => { - return merge( - this.onDashboardLoad$, - this.actions$.pipe(ofType(pluginsListingLoaded)) - ).pipe( - withLatestFrom(this.store.select(getActivePlugin)), - map(([, activePlugin]) => activePlugin), - distinctUntilChanged(), - filter((activePlugin) => activePlugin !== null), - take(1), - tap((plugin) => { - this.store.dispatch(changePlugin({plugin: plugin!})); - }) - ); - }, - {dispatch: false} - ); + readonly dispatchChangePlugin$; private fetchEnvironment() { return this.webappDataSource.fetchEnvironment().pipe( @@ -314,7 +138,183 @@ export class CoreEffects { private actions$: Actions, private store: Store, private webappDataSource: TBServerDataSource - ) {} + ) { + this.onDashboardLoad$ = merge( + this.actions$.pipe( + ofType(coreLoaded, navigated), + withLatestFrom(this.store.select(getActiveRoute)), + distinctUntilChanged(([, beforeRoute], [, afterRoute]) => { + return areSameRouteKindAndExperiments(beforeRoute, afterRoute); + }) + ), + this.actions$.pipe(ofType(reload, manualReload)) + ).pipe( + withLatestFrom(this.store.select(getRouteKind)), + filter(([, routeKind]) => DASHBOARD_ROUTE_KIND.has(routeKind)), + throttleTime(DATA_LOAD_CONDITIONAL_THROTTLE_IN_MS, undefined, { + leading: true, + }) + ); + this.fetchWebAppData$ = createEffect( + () => { + const pluginsListingReload$ = this.onDashboardLoad$.pipe( + withLatestFrom( + this.store.select(getPluginsListLoaded), + this.store.select(getEnabledExperimentalPlugins) + ), + filter(([, {state}]) => state !== DataLoadState.LOADING), + tap(() => this.store.dispatch(pluginsListingRequested())), + mergeMap(([, , enabledExperimentalPlugins]) => { + return zip( + this.webappDataSource.fetchPluginsListing( + enabledExperimentalPlugins + ), + // TODO(tensorboard-team): consider brekaing the environments out of + // the pluginsListingLoaded; currently, plugins listing load state + // is connected to the environments which is not ideal. Have its own + // load state. + this.fetchEnvironment() + ).pipe( + map(([plugins]) => { + this.store.dispatch(pluginsListingLoaded({plugins})); + }), + catchError((e) => { + if (e instanceof TBServerError) { + this.store.dispatch( + pluginsListingFailed({failureCode: e.failureCode}) + ); + } else { + this.store.dispatch( + pluginsListingFailed({ + failureCode: PluginsListFailureCode.UNKNOWN, + }) + ); + } + return EMPTY; + }) + ); + }) + ); + + const runsReload$ = this.onDashboardLoad$.pipe( + map(([, routeKind]) => routeKind), + switchMap((routeKind) => { + if (routeKind !== RouteKind.COMPARE_EXPERIMENT) { + return of([]); + } + + // If alias map changes, we need to refetch the list of runs as + // Polymer's run selector and tags rely on run names including the + // alias. + return this.store.select(getExperimentIdToExperimentAliasMap).pipe( + distinctUntilChanged((beforeAliasDict, afterAliasDict) => { + const entries = Object.entries(beforeAliasDict); + const afterAliasMap = new Map(Object.entries(afterAliasDict)); + if (entries.length !== afterAliasMap.size) { + return false; + } + for (const [experimentId, alias] of entries) { + if (!afterAliasMap.get(experimentId)) { + return false; + } + if ( + afterAliasMap.get(experimentId)!.aliasText !== + alias.aliasText || + afterAliasMap.get(experimentId)!.aliasNumber !== + alias.aliasNumber + ) { + return false; + } + } + return true; + }), + // HACK: arbitrary microtask delay. + // An alias change -> route change -> browser url change -> + // `navigated` action. Because we, especially Polymer code, makes + // requests under a relative path, we must make requests only after + // the URL has been modified to reflect new alias or experiment id. + // + // While we can subscribe to `navigated` without + // `distinctUntilChanged` and `areSameRouteExperiments`, it is hard + // to throttle quick alias Map changes while immediately making a + // request for a real navigation. For example, for route A and + // route B: + // + // 0 100 600 700 + // A -> A' -> A" -> B + // ↑ ↑ ↑ + // req noop req req + // + // Above, we would like to make the request immediately when the set + // of experiments change while debouncing alias changes when the set + // of experiments have not changed. + // + // Instead of more elaborate rxjs techniques, we are + // using `delay(0)` to give the router a chance to modify the URL + // before making the request. + delay(0), + // Prevent changes in the alias map not to over-trigger requests; + // However, we want to use throttle instead of debounce since we + // need to emit on `leading` so it does not cause 500ms delay on + // page load. + throttleTime(ALIAS_CHANGE_RUNS_RELOAD_THROTTLE_IN_MS, undefined, { + leading: true, + trailing: true, + }) + ); + }), + withLatestFrom( + this.store.select(getRouteKind), + this.store.select(getPolymerRunsLoadState) + ), + filter(([, routeKind, loadState]) => { + // While the same check was applied earlier, `delay` + `throttleTime` + // makes it unpredictable and we can sometimes make requests for the + // wrong route. This check prevents making the request in wrong + // hostname in a fool proof way. + return ( + DASHBOARD_ROUTE_KIND.has(routeKind) && + loadState.state !== DataLoadState.LOADING + ); + }), + tap(() => { + this.store.dispatch(polymerRunsFetchRequested()); + }), + switchMap(() => { + return this.refreshPolymerRuns(); + }), + tap(() => { + this.store.dispatch(polymerRunsFetchSucceeded()); + }), + catchError(() => { + this.store.dispatch(polymerRunsFetchFailed()); + return EMPTY; + }) + ); + + return merge(pluginsListingReload$, runsReload$); + }, + {dispatch: false} + ); + this.dispatchChangePlugin$ = createEffect( + () => { + return merge( + this.onDashboardLoad$, + this.actions$.pipe(ofType(pluginsListingLoaded)) + ).pipe( + withLatestFrom(this.store.select(getActivePlugin)), + map(([, activePlugin]) => activePlugin), + distinctUntilChanged(), + filter((activePlugin) => activePlugin !== null), + take(1), + tap((plugin) => { + this.store.dispatch(changePlugin({plugin: plugin!})); + }) + ); + }, + {dispatch: false} + ); + } } export const TEST_ONLY = { diff --git a/tensorboard/webapp/feature_flag/views/feature_flag_dialog_container.ts b/tensorboard/webapp/feature_flag/views/feature_flag_dialog_container.ts index 2a90abfd764..80af1f193f0 100644 --- a/tensorboard/webapp/feature_flag/views/feature_flag_dialog_container.ts +++ b/tensorboard/webapp/feature_flag/views/feature_flag_dialog_container.ts @@ -51,27 +51,23 @@ import { >`, }) export class FeatureFlagDialogContainer { - constructor(private readonly store: Store) {} - - readonly showFlagsFilter$ = this.store.select(getOverriddenFeatureFlags).pipe( - map((overriddenFeatureFlags) => { - return overriddenFeatureFlags.showFlags?.toLowerCase(); - }) - ); - - readonly hasFlagsSentToServer$: Observable = this.store - .select(getFeatureFlagsMetadata) - .pipe( - map((flagMetadata) => { - return Object.values(flagMetadata).some((metadata) => { - return (metadata as AdvancedFeatureFlagMetadata) - .sendToServerWhenOverridden; - }); + constructor(private readonly store: Store) { + this.showFlagsFilter$ = this.store.select(getOverriddenFeatureFlags).pipe( + map((overriddenFeatureFlags) => { + return overriddenFeatureFlags.showFlags?.toLowerCase(); }) ); - - readonly featureFlags$: Observable[]> = - this.store.select(getOverriddenFeatureFlags).pipe( + this.hasFlagsSentToServer$ = this.store + .select(getFeatureFlagsMetadata) + .pipe( + map((flagMetadata) => { + return Object.values(flagMetadata).some((metadata) => { + return (metadata as AdvancedFeatureFlagMetadata) + .sendToServerWhenOverridden; + }); + }) + ); + this.featureFlags$ = this.store.select(getOverriddenFeatureFlags).pipe( withLatestFrom( this.store.select(getDefaultFeatureFlags), this.store.select(getFeatureFlagsMetadata), @@ -109,6 +105,13 @@ export class FeatureFlagDialogContainer { } ) ); + } + + readonly showFlagsFilter$; + + readonly hasFlagsSentToServer$: Observable; + + readonly featureFlags$: Observable[]>; onFlagChanged({flag, status}: FeatureFlagStatusEvent) { switch (status) { diff --git a/tensorboard/webapp/metrics/views/card_renderer/scalar_card_line_chart_container.ts b/tensorboard/webapp/metrics/views/card_renderer/scalar_card_line_chart_container.ts index d59869df015..c96008a33b1 100644 --- a/tensorboard/webapp/metrics/views/card_renderer/scalar_card_line_chart_container.ts +++ b/tensorboard/webapp/metrics/views/card_renderer/scalar_card_line_chart_container.ts @@ -38,6 +38,7 @@ import { TimeSelectionWithAffordance, } from '../../../widgets/card_fob/card_fob_types'; import {Extent} from '../../../widgets/line_chart_v2/lib/public_types'; +import {TooltipTemplate} from '../../../widgets/line_chart_v2/line_chart_component'; import {ScaleType} from '../../../widgets/line_chart_v2/types'; import { cardViewBoxChanged, @@ -52,14 +53,13 @@ import { } from '../../store'; import {CardId, XAxisType} from '../../types'; import {CardRenderer} from '../metrics_view_types'; +import {ScalarCardLineChartComponent} from './scalar_card_line_chart_component'; import { MinMaxStep, ScalarCardDataSeries, ScalarCardSeriesMetadataMap, } from './scalar_card_types'; import {TimeSelectionView} from './utils'; -import {TooltipTemplate} from '../../../widgets/line_chart_v2/line_chart_component'; -import {ScalarCardLineChartComponent} from './scalar_card_line_chart_component'; @Component({ standalone: false, @@ -99,7 +99,36 @@ import {ScalarCardLineChartComponent} from './scalar_card_line_chart_component'; export class ScalarCardLineChartContainer implements CardRenderer, OnInit, OnDestroy { - constructor(private readonly store: Store) {} + constructor(private readonly store: Store) { + this.useDarkMode$ = this.store.select(getDarkModeEnabled); + this.tooltipSort$ = this.store.select(getMetricsTooltipSort); + this.forceSvg$ = this.store.select(getForceSvgFeatureFlag); + this.ignoreOutliers$ = this.ignoreOutliers + ? of(this.ignoreOutliers) + : this.store.select(getMetricsIgnoreOutliers); + this.xAxisType$ = this.xAxisType + ? of(this.xAxisType) + : this.store.select(getMetricsXAxisType); + this.xScaleType$ = this.xAxisType + ? ScaleType.LINEAR + : this.store.select(getMetricsXAxisType).pipe( + map((xAxisType) => { + switch (xAxisType) { + case XAxisType.STEP: + case XAxisType.RELATIVE: + return ScaleType.LINEAR; + case XAxisType.WALL_TIME: + return ScaleType.TIME; + default: + const neverType = xAxisType as never; + throw new Error( + `Invalid xAxisType for line chart. ${neverType}` + ); + } + }) + ); + this.ngUnsubscribe = new Subject(); + } @Input() cardId!: CardId; @Input() seriesMetadataMap!: ScalarCardSeriesMetadataMap; @@ -122,35 +151,16 @@ export class ScalarCardLineChartContainer userViewBox$?: Observable; rangeEnabled$?: Observable; - readonly useDarkMode$ = this.store.select(getDarkModeEnabled); - readonly tooltipSort$ = this.store.select(getMetricsTooltipSort); - readonly forceSvg$ = this.store.select(getForceSvgFeatureFlag); - - readonly ignoreOutliers$ = this.ignoreOutliers - ? of(this.ignoreOutliers) - : this.store.select(getMetricsIgnoreOutliers); - - readonly xAxisType$ = this.xAxisType - ? of(this.xAxisType) - : this.store.select(getMetricsXAxisType); - readonly xScaleType$ = this.xAxisType - ? ScaleType.LINEAR - : this.store.select(getMetricsXAxisType).pipe( - map((xAxisType) => { - switch (xAxisType) { - case XAxisType.STEP: - case XAxisType.RELATIVE: - return ScaleType.LINEAR; - case XAxisType.WALL_TIME: - return ScaleType.TIME; - default: - const neverType = xAxisType as never; - throw new Error(`Invalid xAxisType for line chart. ${neverType}`); - } - }) - ); - - private readonly ngUnsubscribe = new Subject(); + readonly useDarkMode$; + readonly tooltipSort$; + readonly forceSvg$; + + readonly ignoreOutliers$; + + readonly xAxisType$; + readonly xScaleType$; + + private readonly ngUnsubscribe; ngOnInit() { this.userViewBox$ = this.store.select( diff --git a/tensorboard/webapp/metrics/views/main_view/card_grid_container.ts b/tensorboard/webapp/metrics/views/main_view/card_grid_container.ts index 0d390bd9335..561ecb165d0 100644 --- a/tensorboard/webapp/metrics/views/main_view/card_grid_container.ts +++ b/tensorboard/webapp/metrics/views/main_view/card_grid_container.ts @@ -24,6 +24,7 @@ import {Store} from '@ngrx/store'; import {BehaviorSubject, combineLatest, Observable, of, Subject} from 'rxjs'; import {map, shareReplay, switchMap, takeUntil, tap} from 'rxjs/operators'; import {State} from '../../../app_state'; +import * as selectors from '../../../selectors'; import { getMetricsCardMinWidth, getMetricsTagGroupExpansionState, @@ -31,7 +32,6 @@ import { import {selectors as settingsSelectors} from '../../../settings'; import {CardObserver} from '../card_renderer/card_lazy_loader'; import {CardIdWithMetadata} from '../metrics_view_types'; -import * as selectors from '../../../selectors'; @Component({ standalone: false, @@ -62,68 +62,76 @@ export class CardGridContainer implements OnChanges, OnDestroy { readonly pageIndex$ = new BehaviorSubject(0); private readonly items$ = new BehaviorSubject([]); private readonly ngUnsubscribe = new Subject(); - readonly cardStateMap$ = this.store.select(selectors.getCardStateMap); - - readonly numPages$ = combineLatest([ - this.items$, - this.store.select(settingsSelectors.getPageSize), - ]).pipe( - map(([items, pageSize]) => { - return Math.ceil(items.length / pageSize); - }) - ); - - readonly isGroupExpanded$: Observable = this.groupName$.pipe( - switchMap((groupName) => { - return groupName !== null - ? this.store.select(getMetricsTagGroupExpansionState, groupName) - : of(true); - }) - ); - - readonly showPaginationControls$: Observable = this.numPages$.pipe( - map((numPages) => numPages > 1) - ); - - readonly normalizedPageIndex$ = combineLatest([ - this.pageIndex$, - this.numPages$, - ]).pipe( - takeUntil(this.ngUnsubscribe), - tap(([pageIndex, numPages]) => { - // Cycle in the Observable but only loops when pageIndex is not - // valid and does not repeat more than once. - if (numPages === 0) { - return; - } - if (pageIndex >= numPages) { - this.pageIndex$.next(numPages - 1); - } else if (pageIndex < 0) { - this.pageIndex$.next(0); - } - }), - map(([pageIndex, numPages]) => { - return Math.min(Math.max(pageIndex, 0), numPages - 1); - }), - shareReplay(1) - ); - - readonly pagedItems$ = combineLatest([ - this.items$, - this.store.select(settingsSelectors.getPageSize), - this.normalizedPageIndex$, - this.isGroupExpanded$, - ]).pipe( - map(([items, pageSize, pageIndex, expanded]) => { - const startIndex = pageSize * pageIndex; - const endIndex = pageSize * pageIndex + (expanded ? pageSize : 0); - return items.slice(startIndex, endIndex); - }) - ); - - readonly cardMinWidth$ = this.store.select(getMetricsCardMinWidth); - - constructor(private readonly store: Store) {} + readonly cardStateMap$; + + readonly numPages$; + + readonly isGroupExpanded$: Observable; + + readonly showPaginationControls$: Observable; + + readonly normalizedPageIndex$; + + readonly pagedItems$; + + readonly cardMinWidth$; + + constructor(private readonly store: Store) { + this.cardStateMap$ = this.store.select(selectors.getCardStateMap); + this.numPages$ = combineLatest([ + this.items$, + this.store.select(settingsSelectors.getPageSize), + ]).pipe( + map(([items, pageSize]) => { + return Math.ceil(items.length / pageSize); + }) + ); + this.isGroupExpanded$ = this.groupName$.pipe( + switchMap((groupName) => { + return groupName !== null + ? this.store.select(getMetricsTagGroupExpansionState, groupName) + : of(true); + }) + ); + this.showPaginationControls$ = this.numPages$.pipe( + map((numPages) => numPages > 1) + ); + this.normalizedPageIndex$ = combineLatest([ + this.pageIndex$, + this.numPages$, + ]).pipe( + takeUntil(this.ngUnsubscribe), + tap(([pageIndex, numPages]) => { + // Cycle in the Observable but only loops when pageIndex is not + // valid and does not repeat more than once. + if (numPages === 0) { + return; + } + if (pageIndex >= numPages) { + this.pageIndex$.next(numPages - 1); + } else if (pageIndex < 0) { + this.pageIndex$.next(0); + } + }), + map(([pageIndex, numPages]) => { + return Math.min(Math.max(pageIndex, 0), numPages - 1); + }), + shareReplay(1) + ); + this.pagedItems$ = combineLatest([ + this.items$, + this.store.select(settingsSelectors.getPageSize), + this.normalizedPageIndex$, + this.isGroupExpanded$, + ]).pipe( + map(([items, pageSize, pageIndex, expanded]) => { + const startIndex = pageSize * pageIndex; + const endIndex = pageSize * pageIndex + (expanded ? pageSize : 0); + return items.slice(startIndex, endIndex); + }) + ); + this.cardMinWidth$ = this.store.select(getMetricsCardMinWidth); + } ngOnChanges(changes: SimpleChanges) { if (changes['cardIdsWithMetadata']) { diff --git a/tensorboard/webapp/metrics/views/main_view/filter_input_container.ts b/tensorboard/webapp/metrics/views/main_view/filter_input_container.ts index d0b586d8be8..4a40c3b653a 100644 --- a/tensorboard/webapp/metrics/views/main_view/filter_input_container.ts +++ b/tensorboard/webapp/metrics/views/main_view/filter_input_container.ts @@ -39,26 +39,20 @@ import {compareTagNames} from '../../utils'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class MetricsFilterInputContainer { - constructor(private readonly store: Store) {} - - readonly tagFilter$: Observable = - this.store.select(getMetricsTagFilter); - - readonly isTagFilterRegexValid$: Observable = this.tagFilter$.pipe( - map((tagFilterString) => { - try { - // tslint:disable-next-line:no-unused-expression Check for validity of filter. - new RegExp(tagFilterString); - return true; - } catch (err) { - return false; - } - }) - ); - - readonly completions$: Observable = this.store - .select(getNonEmptyCardIdsWithMetadata) - .pipe( + constructor(private readonly store: Store) { + this.tagFilter$ = this.store.select(getMetricsTagFilter); + this.isTagFilterRegexValid$ = this.tagFilter$.pipe( + map((tagFilterString) => { + try { + // tslint:disable-next-line:no-unused-expression Check for validity of filter. + new RegExp(tagFilterString); + return true; + } catch (err) { + return false; + } + }) + ); + this.completions$ = this.store.select(getNonEmptyCardIdsWithMetadata).pipe( combineLatestWith(this.store.select(getMetricsFilteredPluginTypes)), map(([cardList, filteredPluginTypes]) => { return cardList @@ -87,6 +81,13 @@ export class MetricsFilterInputContainer { return tags.filter((tag: string) => tagFilterRegex!.test(tag)); }) ); + } + + readonly tagFilter$: Observable; + + readonly isTagFilterRegexValid$: Observable; + + readonly completions$: Observable; onTagFilterChange(tagFilter: string) { this.store.dispatch(metricsTagFilterChanged({tagFilter})); diff --git a/tensorboard/webapp/metrics/views/metrics_container.ts b/tensorboard/webapp/metrics/views/metrics_container.ts index ed9a27cf044..14faf01b786 100644 --- a/tensorboard/webapp/metrics/views/metrics_container.ts +++ b/tensorboard/webapp/metrics/views/metrics_container.ts @@ -33,7 +33,9 @@ import {getRunsTableFullScreen} from '../../core/store/core_selectors'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class MetricsDashboardContainer { - runsTableFullScreen$ = this.store.select(getRunsTableFullScreen); + runsTableFullScreen$; - constructor(readonly store: Store) {} + constructor(readonly store: Store) { + this.runsTableFullScreen$ = this.store.select(getRunsTableFullScreen); + } } diff --git a/tensorboard/webapp/metrics/views/right_pane/settings_view_container.ts b/tensorboard/webapp/metrics/views/right_pane/settings_view_container.ts index 8b7a6fb2fbd..45ae6dcd8c9 100644 --- a/tensorboard/webapp/metrics/views/right_pane/settings_view_container.ts +++ b/tensorboard/webapp/metrics/views/right_pane/settings_view_container.ts @@ -13,8 +13,8 @@ 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 {MatDialog} from '@angular/material/dialog'; +import {Store} from '@ngrx/store'; import {Observable} from 'rxjs'; import {filter, map, take, withLatestFrom} from 'rxjs/operators'; import {State} from '../../../app_state'; @@ -102,70 +102,91 @@ export class SettingsViewContainer { constructor( private readonly store: Store, private readonly dialog: MatDialog - ) {} + ) { + this.isScalarStepSelectorEnabled$ = this.store.select( + selectors.getMetricsStepSelectorEnabled + ); + this.isScalarStepSelectorRangeEnabled$ = this.store.select( + selectors.getMetricsRangeSelectionEnabled + ); + this.isLinkedTimeEnabled$ = this.store.select( + selectors.getMetricsLinkedTimeEnabled + ); + this.isScalarColumnCustomizationEnabled$ = this.store.select( + selectors.getIsScalarColumnCustomizationEnabled + ); + this.linkedTimeSelection$ = this.store.select( + selectors.getMetricsLinkedTimeSelectionSetting + ); + this.stepMinMax$ = this.store.select(selectors.getMetricsStepMinMax); + this.isSlideOutMenuOpen$ = this.store.select( + selectors.isMetricsSlideoutMenuOpen + ); + this.isImageSupportEnabled$ = this.store + .select(selectors.getIsFeatureFlagsLoaded) + .pipe( + filter(Boolean), + take(1), + withLatestFrom( + this.store.select(selectors.getIsMetricsImageSupportEnabled) + ), + map(([, isImagesSupported]) => { + return isImagesSupported; + }) + ); + this.tooltipSort$ = this.store.select(selectors.getMetricsTooltipSort); + this.ignoreOutliers$ = this.store.select( + selectors.getMetricsIgnoreOutliers + ); + this.xAxisType$ = this.store.select(selectors.getMetricsXAxisType); + this.cardMinWidth$ = this.store.select(selectors.getMetricsCardMinWidth); + this.histogramMode$ = this.store.select(selectors.getMetricsHistogramMode); + this.scalarSmoothing$ = this.store.select( + selectors.getMetricsScalarSmoothing + ); + this.scalarPartitionX$ = this.store.select( + selectors.getMetricsScalarPartitionNonMonotonicX + ); + this.imageBrightnessInMilli$ = this.store.select( + selectors.getMetricsImageBrightnessInMilli + ); + this.imageContrastInMilli$ = this.store.select( + selectors.getMetricsImageContrastInMilli + ); + this.imageShowActualSize$ = this.store.select( + selectors.getMetricsImageShowActualSize + ); + this.isSavingPinsEnabled$ = this.store.select( + selectors.getMetricsSavingPinsEnabled + ); + this.globalPinsFeatureEnabled$ = this.store.select( + selectors.getEnableGlobalPins + ); + } - readonly isScalarStepSelectorEnabled$: Observable = - this.store.select(selectors.getMetricsStepSelectorEnabled); - readonly isScalarStepSelectorRangeEnabled$: Observable = - this.store.select(selectors.getMetricsRangeSelectionEnabled); - readonly isLinkedTimeEnabled$: Observable = this.store.select( - selectors.getMetricsLinkedTimeEnabled - ); - readonly isScalarColumnCustomizationEnabled$ = this.store.select( - selectors.getIsScalarColumnCustomizationEnabled - ); - readonly linkedTimeSelection$ = this.store.select( - selectors.getMetricsLinkedTimeSelectionSetting - ); - readonly stepMinMax$ = this.store.select(selectors.getMetricsStepMinMax); - readonly isSlideOutMenuOpen$ = this.store.select( - selectors.isMetricsSlideoutMenuOpen - ); + readonly isScalarStepSelectorEnabled$: Observable; + readonly isScalarStepSelectorRangeEnabled$: Observable; + readonly isLinkedTimeEnabled$: Observable; + readonly isScalarColumnCustomizationEnabled$; + readonly linkedTimeSelection$; + readonly stepMinMax$; + readonly isSlideOutMenuOpen$; - readonly isImageSupportEnabled$ = this.store - .select(selectors.getIsFeatureFlagsLoaded) - .pipe( - filter(Boolean), - take(1), - withLatestFrom( - this.store.select(selectors.getIsMetricsImageSupportEnabled) - ), - map(([, isImagesSupported]) => { - return isImagesSupported; - }) - ); + readonly isImageSupportEnabled$; - readonly tooltipSort$ = this.store.select(selectors.getMetricsTooltipSort); - readonly ignoreOutliers$ = this.store.select( - selectors.getMetricsIgnoreOutliers - ); - readonly xAxisType$ = this.store.select(selectors.getMetricsXAxisType); - readonly cardMinWidth$ = this.store.select(selectors.getMetricsCardMinWidth); - readonly histogramMode$ = this.store.select( - selectors.getMetricsHistogramMode - ); - readonly scalarSmoothing$ = this.store.select( - selectors.getMetricsScalarSmoothing - ); - readonly scalarPartitionX$ = this.store.select( - selectors.getMetricsScalarPartitionNonMonotonicX - ); - readonly imageBrightnessInMilli$ = this.store.select( - selectors.getMetricsImageBrightnessInMilli - ); - readonly imageContrastInMilli$ = this.store.select( - selectors.getMetricsImageContrastInMilli - ); - readonly imageShowActualSize$ = this.store.select( - selectors.getMetricsImageShowActualSize - ); - readonly isSavingPinsEnabled$ = this.store.select( - selectors.getMetricsSavingPinsEnabled - ); + readonly tooltipSort$; + readonly ignoreOutliers$; + readonly xAxisType$; + readonly cardMinWidth$; + readonly histogramMode$; + readonly scalarSmoothing$; + readonly scalarPartitionX$; + readonly imageBrightnessInMilli$; + readonly imageContrastInMilli$; + readonly imageShowActualSize$; + readonly isSavingPinsEnabled$; // Feature flag for global pins. - readonly globalPinsFeatureEnabled$ = this.store.select( - selectors.getEnableGlobalPins - ); + readonly globalPinsFeatureEnabled$; onTooltipSortChanged(sort: TooltipSort) { this.store.dispatch(metricsChangeTooltipSort({sort})); @@ -227,7 +248,9 @@ export class SettingsViewContainer { onStepSelectorToggled() { this.store.dispatch( - stepSelectorToggled({affordance: TimeSelectionToggleAffordance.CHECK_BOX}) + stepSelectorToggled({ + affordance: TimeSelectionToggleAffordance.CHECK_BOX, + }) ); } diff --git a/tensorboard/webapp/plugins/plugins_container.ts b/tensorboard/webapp/plugins/plugins_container.ts index 6a0b0564243..774d3a60219 100644 --- a/tensorboard/webapp/plugins/plugins_container.ts +++ b/tensorboard/webapp/plugins/plugins_container.ts @@ -76,8 +76,8 @@ const activePlugin = createSelector( changeDetection: ChangeDetectionStrategy.OnPush, }) export class PluginsContainer { - readonly activeKnownPlugin$ = this.store.select(activePlugin); - readonly activePluginId$ = this.store.select(getActivePlugin); + readonly activeKnownPlugin$; + readonly activePluginId$; @Input() environmentFailureNotFoundTemplate?: TemplateRef; @@ -88,57 +88,66 @@ export class PluginsContainer { @Input() environmentFailureUnknownTemplate?: TemplateRef; - readonly pluginLoadState$ = combineLatest( - this.activeKnownPlugin$, - this.activePluginId$, - this.store.select(getPluginsListLoaded) - ).pipe( - map(([activePlugin, activePluginId, loadState]) => { - if (loadState.failureCode !== null) { - // Despite its 'Plugins'-specific name, getPluginsListLoaded actually - // encapsulates multiple requests to load different parts of the - // environment. - if (loadState.failureCode === PluginsListFailureCode.NOT_FOUND) { - return PluginLoadState.ENVIRONMENT_FAILURE_NOT_FOUND; - } else if ( - loadState.failureCode === PluginsListFailureCode.PERMISSION_DENIED + readonly pluginLoadState$; + + readonly lastLoadedTimeInMs$; + readonly dataLocation$; + readonly isFeatureFlagsLoaded$; + readonly featureFlags$; + readonly settingsLoadState$; + + constructor(private readonly store: Store) { + this.activeKnownPlugin$ = this.store.select(activePlugin); + this.activePluginId$ = this.store.select(getActivePlugin); + this.pluginLoadState$ = combineLatest( + this.activeKnownPlugin$, + this.activePluginId$, + this.store.select(getPluginsListLoaded) + ).pipe( + map(([activePlugin, activePluginId, loadState]) => { + if (loadState.failureCode !== null) { + // Despite its 'Plugins'-specific name, getPluginsListLoaded actually + // encapsulates multiple requests to load different parts of the + // environment. + if (loadState.failureCode === PluginsListFailureCode.NOT_FOUND) { + return PluginLoadState.ENVIRONMENT_FAILURE_NOT_FOUND; + } else if ( + loadState.failureCode === PluginsListFailureCode.PERMISSION_DENIED + ) { + return PluginLoadState.ENVIRONMENT_FAILURE_PERMISSION_DENIED; + } else { + return PluginLoadState.ENVIRONMENT_FAILURE_UNKNOWN; + } + } + + if (activePlugin !== null) { + return PluginLoadState.LOADED; + } + + if ( + loadState.lastLoadedTimeInMs === null && + loadState.state === DataLoadState.LOADING ) { - return PluginLoadState.ENVIRONMENT_FAILURE_PERMISSION_DENIED; - } else { - return PluginLoadState.ENVIRONMENT_FAILURE_UNKNOWN; + return PluginLoadState.LOADING; } - } - - if (activePlugin !== null) { - return PluginLoadState.LOADED; - } - - if ( - loadState.lastLoadedTimeInMs === null && - loadState.state === DataLoadState.LOADING - ) { - return PluginLoadState.LOADING; - } - - if (activePluginId) { - return PluginLoadState.UNKNOWN_PLUGIN_ID; - } - - return PluginLoadState.NO_ENABLED_PLUGINS; - }) - ); - - readonly lastLoadedTimeInMs$ = this.store.select(getAppLastLoadedTimeInMs); - readonly dataLocation$ = this.store.select(getEnvironment).pipe( - map((env) => { - return env.data_location; - }) - ); - readonly isFeatureFlagsLoaded$ = this.store.select(getIsFeatureFlagsLoaded); - readonly featureFlags$ = this.store.select(getFeatureFlags); - readonly settingsLoadState$ = this.store.select( - settingsSelectors.getSettingsLoadState - ); - - constructor(private readonly store: Store) {} + + if (activePluginId) { + return PluginLoadState.UNKNOWN_PLUGIN_ID; + } + + return PluginLoadState.NO_ENABLED_PLUGINS; + }) + ); + this.lastLoadedTimeInMs$ = this.store.select(getAppLastLoadedTimeInMs); + this.dataLocation$ = this.store.select(getEnvironment).pipe( + map((env) => { + return env.data_location; + }) + ); + this.isFeatureFlagsLoaded$ = this.store.select(getIsFeatureFlagsLoaded); + this.featureFlags$ = this.store.select(getFeatureFlags); + this.settingsLoadState$ = this.store.select( + settingsSelectors.getSettingsLoadState + ); + } } diff --git a/tensorboard/webapp/runs/effects/runs_effects.ts b/tensorboard/webapp/runs/effects/runs_effects.ts index 3f050a22690..969a07a0236 100644 --- a/tensorboard/webapp/runs/effects/runs_effects.ts +++ b/tensorboard/webapp/runs/effects/runs_effects.ts @@ -31,19 +31,19 @@ import {navigated} from '../../app_routing/actions'; import {RouteKind} from '../../app_routing/types'; import {State} from '../../app_state'; import * as coreActions from '../../core/actions'; +import * as hparamsActions from '../../hparams/_redux/hparams_actions'; import { getActiveRoute, + getDashboardExperimentNames, getExperimentIdsFromRoute, getRuns, getRunsLoadState, - getDashboardExperimentNames, } from '../../selectors'; import {DataLoadState, LoadState} from '../../types/data'; +import {ColumnHeaderType} from '../../widgets/data_table/types'; import * as actions from '../actions'; -import * as hparamsActions from '../../hparams/_redux/hparams_actions'; import {Run, RunsDataSource} from '../data_source/runs_data_source_types'; import {ExperimentIdToRuns} from '../types'; -import {ColumnHeaderType} from '../../widgets/data_table/types'; /** * Runs effect for fetching data from the backend. @@ -54,7 +54,88 @@ export class RunsEffects { private readonly actions$: Actions, private readonly store: Store, private readonly runsDataSource: RunsDataSource - ) {} + ) { + this.experimentsWithStaleRunsOnRouteChange$ = this.actions$.pipe( + ofType(navigated), + withLatestFrom(this.store.select(getActiveRoute)), + distinctUntilChanged(([, prevRoute], [, currRoute]) => { + return areSameRouteKindAndExperiments(prevRoute, currRoute); + }), + withLatestFrom(this.store.select(getExperimentIdsFromRoute)), + filter(([, experimentIds]) => !!experimentIds), + map(([, experimentIds]) => experimentIds!), + mergeMap((experimentIds) => { + return this.getExperimentsWithLoadState(experimentIds, (state) => { + return ( + state === DataLoadState.FAILED || state === DataLoadState.NOT_LOADED + ); + }).pipe( + map((experimentIdsToBeFetched) => { + return {experimentIds, experimentIdsToBeFetched}; + }) + ); + }) + ); + this.experimentsWithStaleRunsOnReload$ = this.actions$.pipe( + ofType(coreActions.reload, coreActions.manualReload), + withLatestFrom(this.store.select(getExperimentIdsFromRoute)), + filter(([, experimentIds]) => !!experimentIds), + map(([, experimentIds]) => experimentIds!), + mergeMap((experimentIds) => { + return this.getExperimentsWithLoadState(experimentIds, (state) => { + return state !== DataLoadState.LOADING; + }).pipe( + map((experimentIdsToBeFetched) => { + return {experimentIds, experimentIdsToBeFetched}; + }) + ); + }) + ); + this.loadRunsOnNavigationOrReload$ = createEffect( + () => { + return merge( + this.experimentsWithStaleRunsOnRouteChange$, + this.experimentsWithStaleRunsOnReload$ + ).pipe( + withLatestFrom(this.store.select(getActiveRoute)), + filter( + ([, route]) => route !== null && route.routeKind !== RouteKind.CARD + ), + mergeMap(([{experimentIds, experimentIdsToBeFetched}]) => { + return this.fetchAllRunsList( + experimentIds, + experimentIdsToBeFetched + ); + }) + ); + }, + {dispatch: false} + ); + this.removeHparamFilterWhenColumnIsRemoved$ = createEffect( + () => + this.actions$.pipe( + ofType(actions.runsTableHeaderRemoved), + tap(({header}) => { + if (header.type === ColumnHeaderType.HPARAM) { + this.store.dispatch( + hparamsActions.dashboardHparamFilterRemoved({ + name: header.name, + }) + ); + return; + } + if (header.type === ColumnHeaderType.METRIC) { + this.store.dispatch( + hparamsActions.dashboardMetricFilterRemoved({ + name: header.name, + }) + ); + } + }) + ), + {dispatch: false} + ); + } private getRunsListLoadState(experimentId: string): Observable { return this.store.select(getRunsLoadState, {experimentId}).pipe(take(1)); @@ -77,96 +158,23 @@ export class RunsEffects { ); } - private readonly experimentsWithStaleRunsOnRouteChange$ = this.actions$.pipe( - ofType(navigated), - withLatestFrom(this.store.select(getActiveRoute)), - distinctUntilChanged(([, prevRoute], [, currRoute]) => { - return areSameRouteKindAndExperiments(prevRoute, currRoute); - }), - withLatestFrom(this.store.select(getExperimentIdsFromRoute)), - filter(([, experimentIds]) => !!experimentIds), - map(([, experimentIds]) => experimentIds!), - mergeMap((experimentIds) => { - return this.getExperimentsWithLoadState(experimentIds, (state) => { - return ( - state === DataLoadState.FAILED || state === DataLoadState.NOT_LOADED - ); - }).pipe( - map((experimentIdsToBeFetched) => { - return {experimentIds, experimentIdsToBeFetched}; - }) - ); - }) - ); + private readonly experimentsWithStaleRunsOnRouteChange$; - private readonly experimentsWithStaleRunsOnReload$ = this.actions$.pipe( - ofType(coreActions.reload, coreActions.manualReload), - withLatestFrom(this.store.select(getExperimentIdsFromRoute)), - filter(([, experimentIds]) => !!experimentIds), - map(([, experimentIds]) => experimentIds!), - mergeMap((experimentIds) => { - return this.getExperimentsWithLoadState(experimentIds, (state) => { - return state !== DataLoadState.LOADING; - }).pipe( - map((experimentIdsToBeFetched) => { - return {experimentIds, experimentIdsToBeFetched}; - }) - ); - }) - ); + private readonly experimentsWithStaleRunsOnReload$; /** * Fetches runs on navigation or in-app reload. * * @export */ - loadRunsOnNavigationOrReload$ = createEffect( - () => { - return merge( - this.experimentsWithStaleRunsOnRouteChange$, - this.experimentsWithStaleRunsOnReload$ - ).pipe( - withLatestFrom(this.store.select(getActiveRoute)), - filter( - ([, route]) => route !== null && route.routeKind !== RouteKind.CARD - ), - mergeMap(([{experimentIds, experimentIdsToBeFetched}]) => { - return this.fetchAllRunsList(experimentIds, experimentIdsToBeFetched); - }) - ); - }, - {dispatch: false} - ); + loadRunsOnNavigationOrReload$; /** * Removes hparam filter when column is removed. * * @export */ - removeHparamFilterWhenColumnIsRemoved$ = createEffect( - () => - this.actions$.pipe( - ofType(actions.runsTableHeaderRemoved), - tap(({header}) => { - if (header.type === ColumnHeaderType.HPARAM) { - this.store.dispatch( - hparamsActions.dashboardHparamFilterRemoved({ - name: header.name, - }) - ); - return; - } - if (header.type === ColumnHeaderType.METRIC) { - this.store.dispatch( - hparamsActions.dashboardMetricFilterRemoved({ - name: header.name, - }) - ); - } - }) - ), - {dispatch: false} - ); + removeHparamFilterWhenColumnIsRemoved$; /** * IMPORTANT: actions are dispatched even when there are no experiments to diff --git a/tensorboard/webapp/runs/views/runs_table/runs_table_container.ts b/tensorboard/webapp/runs/views/runs_table/runs_table_container.ts index 5a0feffa336..018ae6c21b5 100644 --- a/tensorboard/webapp/runs/views/runs_table/runs_table_container.ts +++ b/tensorboard/webapp/runs/views/runs_table/runs_table_container.ts @@ -36,17 +36,22 @@ import { actions as hparamsActions, selectors as hparamsSelectors, } from '../../../hparams'; +import { + getCurrentColumnFilters, + getFilteredRenderableRuns, + getSelectableColumns, +} from '../../../metrics/views/main_view/common_selectors'; import { getActiveRoute, getCurrentRouteRunSelection, getExperiment, getExperimentIdToExperimentAliasMap, + getGroupedRunsTableHeaders, getRunColorMap, getRuns, getRunSelectorRegexFilter, getRunsLoadState, getRunsTableSortingInfo, - getGroupedRunsTableHeaders, } from '../../../selectors'; import {DataLoadState, LoadState} from '../../../types/data'; import { @@ -66,13 +71,8 @@ import { singleRunSelected, } from '../../actions'; import {MAX_NUM_RUNS_TO_ENABLE_BY_DEFAULT} from '../../store/runs_types'; -import {RunsTableColumn, RunTableItem} from './types'; -import { - getCurrentColumnFilters, - getFilteredRenderableRuns, - getSelectableColumns, -} from '../../../metrics/views/main_view/common_selectors'; import {sortTableDataItems} from './sorting_utils'; +import {RunsTableColumn, RunTableItem} from './types'; const getRunsLoading = createSelector< State, @@ -133,47 +133,58 @@ const getRunsLoading = createSelector< export class RunsTableContainer implements OnInit, OnDestroy { sortedRunsTableData$: Observable = of([]); loading$: Observable | null = null; - sortingInfo$ = this.store.select(getRunsTableSortingInfo); + sortingInfo$; // Column to disable in the table. The columns are rendered in the order as // defined by this input. @Input() - columns: RunsTableColumn[] = [RunsTableColumn.RUN_NAME]; + columns: RunsTableColumn[]; @Input() experimentIds!: string[]; - regexFilter$ = this.store.select(getRunSelectorRegexFilter); - runsColumns$ = this.store.select(getGroupedRunsTableHeaders); - selectableColumns$ = this.store.select(getSelectableColumns); - numColumnsLoaded$ = this.store.select( - hparamsSelectors.getNumDashboardHparamsLoaded - ); - numColumnsToLoad$ = this.store.select( - hparamsSelectors.getNumDashboardHparamsToLoad - ); - - columnFilters$ = this.store.select(getCurrentColumnFilters); - - allRunsTableData$ = this.store.select(getFilteredRenderableRuns).pipe( - map((filteredRenderableRuns) => { - return filteredRenderableRuns.map((runTableItem) => { - const tableData: TableData = { - ...Object.fromEntries(runTableItem.hparams.entries()), - id: runTableItem.run.id, - run: runTableItem.run.name, - experimentName: runTableItem.experimentName, - experimentAlias: runTableItem.experimentAlias, - selected: runTableItem.selected, - color: runTableItem.runColor, - }; - return tableData; - }); - }) - ); + regexFilter$; + runsColumns$; + selectableColumns$; + numColumnsLoaded$; + numColumnsToLoad$; - private readonly ngUnsubscribe = new Subject(); + columnFilters$; - constructor(private readonly store: Store) {} + allRunsTableData$; + + private readonly ngUnsubscribe; + + constructor(private readonly store: Store) { + this.sortingInfo$ = this.store.select(getRunsTableSortingInfo); + this.columns = [RunsTableColumn.RUN_NAME]; + this.regexFilter$ = this.store.select(getRunSelectorRegexFilter); + this.runsColumns$ = this.store.select(getGroupedRunsTableHeaders); + this.selectableColumns$ = this.store.select(getSelectableColumns); + this.numColumnsLoaded$ = this.store.select( + hparamsSelectors.getNumDashboardHparamsLoaded + ); + this.numColumnsToLoad$ = this.store.select( + hparamsSelectors.getNumDashboardHparamsToLoad + ); + this.columnFilters$ = this.store.select(getCurrentColumnFilters); + this.allRunsTableData$ = this.store.select(getFilteredRenderableRuns).pipe( + map((filteredRenderableRuns) => { + return filteredRenderableRuns.map((runTableItem) => { + const tableData: TableData = { + ...Object.fromEntries(runTableItem.hparams.entries()), + id: runTableItem.run.id, + run: runTableItem.run.name, + experimentName: runTableItem.experimentName, + experimentAlias: runTableItem.experimentAlias, + selected: runTableItem.selected, + color: runTableItem.runColor, + }; + return tableData; + }); + }) + ); + this.ngUnsubscribe = new Subject(); + } ngOnInit() { const getRunTableItemsPerExperiment = this.experimentIds.map((id) =>