From 4965db5e6319b41ad4d60e75687ee46b535d9c5e Mon Sep 17 00:00:00 2001 From: Hoang Thuan Pham Date: Wed, 11 Oct 2023 16:53:51 -0400 Subject: [PATCH] Add prototype of React Component displaying configurations source types ADR-0011 introduces the API for managing global configurations. Thus, the Trace Extension needs an interface to expose this API to users. The purpose of this commit is to start creating this UI for the trace extension as a React Component. It provides a skeleton for the component so that it can be further extended in future commits: [1] Added TraceConfigurationManager to handle calls to the TSPClient that relates to trace configuration. [2] Introduce a prototype of the UI as the TraceConfigurationsDialogComponent. This is the main component of the UI that handles the display logic for other sub-components, such as [3]. [3] Added TraceConfigurationListComponent to list the available configuration source types and their instances from the server. To test, open a trace and hover the mouse over the toolbar of the Available Views widget. There should be an icon that appears. Click on the icon to open the trace configuration dialog. In this prototype, the dialog should only display the configuration source types. Signed-off-by: Hoang Thuan Pham --- packages/base/src/signals/signal-manager.ts | 7 +- .../components/abstract-dialog-component.tsx | 5 + .../trace-configuration-dialog-component.tsx | 75 +++++++++++++++ .../trace-configuration-list-component.tsx | 95 +++++++++++++++++++ .../trace-configuration-manager.ts | 22 +++++ .../trace-explorer-views-widget.tsx | 4 + .../trace-viewer/trace-viewer-commands.ts | 5 + .../trace-viewer/trace-viewer-contribution.ts | 10 +- .../trace-viewer-toolbar-commands.ts | 6 ++ .../trace-viewer-toolbar-contribution.tsx | 21 +++- 10 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 packages/react-components/src/components/trace-configurations/trace-configuration-dialog-component.tsx create mode 100644 packages/react-components/src/components/trace-configurations/trace-configuration-list-component.tsx create mode 100644 packages/react-components/src/components/trace-configurations/trace-configuration-manager.ts diff --git a/packages/base/src/signals/signal-manager.ts b/packages/base/src/signals/signal-manager.ts index 2cd3da831..7b526379f 100644 --- a/packages/base/src/signals/signal-manager.ts +++ b/packages/base/src/signals/signal-manager.ts @@ -41,6 +41,7 @@ export declare interface SignalManager { fireSelectionRangeUpdated(payload: TimeRangeUpdatePayload): void; fireViewRangeUpdated(payload: TimeRangeUpdatePayload): void; fireRequestSelectionRangeChange(payload: TimeRangeUpdatePayload): void; + fireShowTraceConfigurations(): void; } export const Signals = { @@ -75,7 +76,8 @@ export const Signals = { VIEW_RANGE_UPDATED: 'view range updated', SELECTION_RANGE_UPDATED: 'selection range updated', REQUEST_SELECTION_RANGE_CHANGE: 'change selection range', - OUTPUT_DATA_CHANGED: 'output data changed' + OUTPUT_DATA_CHANGED: 'output data changed', + SHOW_TRACE_CONFIGURATIONS: 'open trace configurations' }; export class SignalManager extends EventEmitter implements SignalManager { @@ -174,6 +176,9 @@ export class SignalManager extends EventEmitter implements SignalManager { fireRequestSelectionRangeChange(payload: TimeRangeUpdatePayload): void { this.emit(Signals.REQUEST_SELECTION_RANGE_CHANGE, payload); } + fireShowTraceConfigurations(): void { + this.emit(Signals.SHOW_TRACE_CONFIGURATIONS); + } } let instance: SignalManager = new SignalManager(); diff --git a/packages/react-components/src/components/abstract-dialog-component.tsx b/packages/react-components/src/components/abstract-dialog-component.tsx index 4625d0eb4..66c9a1b1f 100644 --- a/packages/react-components/src/components/abstract-dialog-component.tsx +++ b/packages/react-components/src/components/abstract-dialog-component.tsx @@ -22,6 +22,7 @@ export abstract class AbstractDialogComponent

className="dialog" ariaHideApp={false} onRequestClose={this.props.onCloseDialog} + onAfterOpen={() => this.onAfterOpen()} shouldFocusAfterRender={false} >

protected abstract renderDialogBody(): React.ReactElement; protected abstract renderFooter(): React.ReactElement; + + protected onAfterOpen(): void { + return; + } } diff --git a/packages/react-components/src/components/trace-configurations/trace-configuration-dialog-component.tsx b/packages/react-components/src/components/trace-configurations/trace-configuration-dialog-component.tsx new file mode 100644 index 000000000..583ecc98c --- /dev/null +++ b/packages/react-components/src/components/trace-configurations/trace-configuration-dialog-component.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { ConfigurationSourceType } from 'tsp-typescript-client/lib/models/configuration-source'; +import { ITspClient } from 'tsp-typescript-client'; +import { TraceConfigurationListComponent } from './trace-configuration-list-component'; +import { TraceConfigurationManager } from './trace-configuration-manager'; +import { signalManager, Signals } from 'traceviewer-base/lib/signals/signal-manager'; + +const TRACE_CONFIGURATION_TITLE = 'Trace Configuration'; + +export interface TraceConfigurationVisibility { + list: boolean; + add: boolean; +} + +export interface TraceConfigurationsDialogComponentProps { + tspClient: ITspClient; +} +export interface TraceConfigurationsDialogComponentState { + visibility: TraceConfigurationVisibility; + configurationSourceTypes: ConfigurationSourceType[]; +} + +export class TraceConfigurationsDialogComponent extends React.Component< + TraceConfigurationsDialogComponentProps, + TraceConfigurationsDialogComponentState +> { + private traceConfigurationManager: TraceConfigurationManager; + + constructor(props: TraceConfigurationsDialogComponentProps) { + super(props); + this.traceConfigurationManager = new TraceConfigurationManager(this.props.tspClient); + this.state = { + visibility: { + list: false, + add: false + }, + configurationSourceTypes: [] + }; + + signalManager().on(Signals.SHOW_TRACE_CONFIGURATIONS, this.onOpenListView); + } + + render(): React.ReactElement { + return ( + + + + ); + } + + protected onCloseListView = (): void => { + this.updateVisibility({ + list: false, + add: false + }); + }; + + protected onOpenListView = (): void => { + this.updateVisibility({ + list: true, + add: false + }); + }; + + private updateVisibility(visibility: TraceConfigurationVisibility): void { + this.setState({ + visibility: visibility + }); + } +} diff --git a/packages/react-components/src/components/trace-configurations/trace-configuration-list-component.tsx b/packages/react-components/src/components/trace-configurations/trace-configuration-list-component.tsx new file mode 100644 index 000000000..aea1fcbf3 --- /dev/null +++ b/packages/react-components/src/components/trace-configurations/trace-configuration-list-component.tsx @@ -0,0 +1,95 @@ +import { ReactElement } from 'react'; +import React from 'react'; +import { ConfigurationSourceType } from 'tsp-typescript-client/lib/models/configuration-source'; +import { AutoSizer, List, ListRowProps } from 'react-virtualized'; +import { AbstractDialogComponent, DialogComponentProps } from '../abstract-dialog-component'; +import { TraceConfigurationManager } from './trace-configuration-manager'; + +export interface TraceConfigurationListComponentProps extends DialogComponentProps { + traceConfigurationManager: TraceConfigurationManager; +} + +export interface TraceConfigurationListComponentState { + configurationSourceTypes: ConfigurationSourceType[]; +} + +export class TraceConfigurationListComponent extends AbstractDialogComponent< + TraceConfigurationListComponentProps, + TraceConfigurationListComponentState +> { + private _forceUpdateKey = false; + private readonly TRACE_CONFIGURATIONS_ROW_HEIGHT = 20; + + protected renderFooter(): ReactElement { + return
; + } + + constructor(props: TraceConfigurationListComponentProps) { + super(props); + this.state = { + configurationSourceTypes: [] + }; + } + + protected onAfterOpen(): void { + this.props.traceConfigurationManager.getConfigurationSourceTypes().then(result => { + if (result !== undefined) { + this.setState({ + configurationSourceTypes: result + }); + } + }); + } + + protected renderDialogBody(): React.ReactElement { + this._forceUpdateKey = !this._forceUpdateKey; + const key = Number(this._forceUpdateKey); + let outputsRowCount = 0; + const outputs = this.state.configurationSourceTypes; + if (outputs) { + outputsRowCount = outputs.length; + } + const totalHeight = 400; // TODO: Change this value once styling is applied + return ( +
+ List of configurations + + {({ width }) => ( + + )} + +
+ ); + } + + protected renderRowOutputs = (props: ListRowProps): React.ReactNode => this.doRenderRowOutputs(props); + + private doRenderRowOutputs(props: ListRowProps): React.ReactNode { + let outputName = ''; + let output: ConfigurationSourceType | undefined; + const configurationSourceTypes = this.state.configurationSourceTypes; + if ( + configurationSourceTypes && + configurationSourceTypes.length && + props.index < configurationSourceTypes.length + ) { + output = configurationSourceTypes[props.index]; + outputName = output.name; + } + + return ( +
+
+

{outputName}

+
+
+ ); + } +} diff --git a/packages/react-components/src/components/trace-configurations/trace-configuration-manager.ts b/packages/react-components/src/components/trace-configurations/trace-configuration-manager.ts new file mode 100644 index 000000000..a5e15d9c8 --- /dev/null +++ b/packages/react-components/src/components/trace-configurations/trace-configuration-manager.ts @@ -0,0 +1,22 @@ +import { ITspClient } from 'tsp-typescript-client'; +import { ConfigurationSourceType } from 'tsp-typescript-client/lib/models/configuration-source'; + +export class TraceConfigurationManager { + private tspClient: ITspClient; + + constructor(tspClient: ITspClient) { + this.tspClient = tspClient; + } + + /** + * Get an array of OutputDescriptor for a given experiment + * @param experimentUUID experiment UUID + */ + async getConfigurationSourceTypes(): Promise { + const outputsResponse = await this.tspClient.fetchConfigurationSourceTypes(); + if (outputsResponse && outputsResponse.isOk()) { + return outputsResponse.getModel(); + } + return undefined; + } +} diff --git a/packages/react-components/src/trace-explorer/trace-explorer-views-widget.tsx b/packages/react-components/src/trace-explorer/trace-explorer-views-widget.tsx index b2eac9058..9d34be7f0 100644 --- a/packages/react-components/src/trace-explorer/trace-explorer-views-widget.tsx +++ b/packages/react-components/src/trace-explorer/trace-explorer-views-widget.tsx @@ -6,6 +6,7 @@ import { Experiment } from 'tsp-typescript-client/lib/models/experiment'; import { ITspClientProvider } from 'traceviewer-base/lib/tsp-client-provider'; import { ExperimentManager } from 'traceviewer-base/lib/experiment-manager'; import { AvailableViewsComponent } from '../components/utils/available-views-component'; +import { TraceConfigurationsDialogComponent } from '../components/trace-configurations/trace-configuration-dialog-component'; export interface ReactAvailableViewsProps { id: string; @@ -44,6 +45,9 @@ export class ReactAvailableViewsWidget extends React.Component + { + signalManager().fireShowTraceConfigurations(); + } + async open(traceURI: URI, options?: TraceViewerWidgetOpenerOptions): Promise { let healthResponse; try { @@ -272,6 +277,9 @@ export class TraceViewerContribution await new ChartShortcutsDialog({ title: 'Trace Viewer Keyboard and Mouse Shortcuts' }).open(); } }); + registry.registerCommand(OpenTraceConfigurations, { + execute: () => this.openTraceConfigurations() + }); } canHandle(_uri: URI): number { diff --git a/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer-toolbar-commands.ts b/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer-toolbar-commands.ts index 5c182617b..71bf440fa 100644 --- a/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer-toolbar-commands.ts +++ b/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer-toolbar-commands.ts @@ -59,6 +59,12 @@ export namespace TraceViewerToolbarCommands { label: 'Show trace overview', iconClass: 'codicon codicon-graph-line' }; + + export const OPEN_TRACE_CONFIGURATIONS: Command = { + id: 'trace.viewer.openTraceConfigurations', + label: 'Open trace configurations', + iconClass: 'codicon codicon-server-process' + }; } export namespace TraceViewerToolbarMenus { diff --git a/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer-toolbar-contribution.tsx b/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer-toolbar-contribution.tsx index b0d313e70..3547e4e11 100644 --- a/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer-toolbar-contribution.tsx +++ b/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer-toolbar-contribution.tsx @@ -9,7 +9,8 @@ import { ChartShortcutsDialog } from '../trace-explorer/trace-explorer-sub-widge import { TspClientProvider } from '../tsp-client-provider-impl'; import { TraceViewerWidget } from './trace-viewer'; import { TraceViewerToolbarCommands, TraceViewerToolbarMenus } from './trace-viewer-toolbar-commands'; -import { OpenTraceCommand } from './trace-viewer-commands'; +import { OpenTraceCommand, OpenTraceConfigurations } from './trace-viewer-commands'; +import { TraceExplorerViewsWidget } from '../trace-explorer/trace-explorer-sub-widgets/theia-trace-explorer-views-widget'; @injectable() export class TraceViewerToolbarContribution implements TabBarToolbarContribution, CommandContribution { @@ -140,6 +141,18 @@ export class TraceViewerToolbarContribution implements TabBarToolbarContribution await new ChartShortcutsDialog({ title: 'Trace Viewer Keyboard and Mouse Shortcuts' }).open(); } }); + + registry.registerCommand(TraceViewerToolbarCommands.OPEN_TRACE_CONFIGURATIONS, { + isVisible: (w: Widget) => { + if (w instanceof TraceExplorerViewsWidget) { + return true; + } + return false; + }, + execute: async () => { + await registry.executeCommand(OpenTraceConfigurations.id); + } + }); } registerToolbarItems(registry: TabBarToolbarRegistry): void { @@ -348,5 +361,11 @@ export class TraceViewerToolbarContribution implements TabBarToolbarContribution tooltip: TraceViewerToolbarCommands.CHARTS_CHEATSHEET.label, priority: 10 }); + registry.registerItem({ + id: TraceViewerToolbarCommands.OPEN_TRACE_CONFIGURATIONS.id, + command: TraceViewerToolbarCommands.OPEN_TRACE_CONFIGURATIONS.id, + tooltip: TraceViewerToolbarCommands.OPEN_TRACE_CONFIGURATIONS.label, + priority: 11 + }); } }