diff --git a/doc/adr/0002/graphql-server-prototype/index.js b/doc/adr/0002/graphql-server-prototype/index.js index b81309845..8fe8523aa 100644 --- a/doc/adr/0002/graphql-server-prototype/index.js +++ b/doc/adr/0002/graphql-server-prototype/index.js @@ -2,7 +2,7 @@ // [1] https://www.apollographql.com/docs/apollo-server/getting-started/#step-3-define-your-graphql-schema const { ApolloServer, gql } = require('apollo-server'); -const { TspClient } = require('tsp-typescript-client/lib/protocol/tsp-client'); +const { HttpTspClient } = require('tsp-typescript-client/lib/protocol/http-tsp-client'); // A schema is a collection of type definitions (hence "typeDefs"). const typeDefs = gql` @@ -21,7 +21,7 @@ const baseUrl = "http://localhost:8080/tsp/api"; const resolvers = { Query: { async status() { - const tspClient = new TspClient(baseUrl); + const tspClient = new HttpTspClient(baseUrl); try { const response = await tspClient.checkHealth(); if (response.isOk()) { @@ -37,7 +37,7 @@ const resolvers = { }, async traces() { // Same simple approach as above. Returns how many traces only. - const tspClient = new TspClient(baseUrl); + const tspClient = new HttpTspClient(baseUrl); try { const response = await tspClient.fetchTraces(); if (response.isOk()) { diff --git a/package.json b/package.json index 54752501e..b707d558c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "scripts": { - "prepare": "yarn -s tsref && yarn -s tsbuild && yarn -s download:plugins && yarn -s prepare:examples", + "prepare": "yarn -s clean && yarn -s tsref && yarn -s tsbuild && yarn -s download:plugins && yarn -s prepare:examples", "tsref": "node scripts/typescript-references.js", "tsbuild": "tsc -b", "tswatch": "tsc -b -w", diff --git a/packages/base/src/experiment-manager.ts b/packages/base/src/experiment-manager.ts index c5e65e923..f20491dd8 100644 --- a/packages/base/src/experiment-manager.ts +++ b/packages/base/src/experiment-manager.ts @@ -1,5 +1,5 @@ import { Trace } from 'tsp-typescript-client/lib/models/trace'; -import { TspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; +import { ITspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; import { Query } from 'tsp-typescript-client/lib/models/query/query'; import { OutputDescriptor } from 'tsp-typescript-client/lib/models/output-descriptor'; import { Experiment } from 'tsp-typescript-client/lib/models/experiment'; @@ -9,10 +9,10 @@ import { signalManager, Signals } from './signals/signal-manager'; export class ExperimentManager { private fOpenExperiments: Map = new Map(); - private fTspClient: TspClient; + private fTspClient: ITspClient; private fTraceManager: TraceManager; - constructor(tspClient: TspClient, traceManager: TraceManager) { + constructor(tspClient: ITspClient, traceManager: TraceManager) { this.fTspClient = tspClient; this.fTraceManager = traceManager; signalManager().on(Signals.EXPERIMENT_DELETED, (experiment: Experiment) => @@ -79,7 +79,10 @@ export class ExperimentManager { traceURIs.push(traces[i].UUID); } - const tryCreate = async function (tspClient: TspClient, retry: number): Promise> { + const tryCreate = async function ( + tspClient: ITspClient, + retry: number + ): Promise> { return tspClient.createExperiment( new Query({ name: retry === 0 ? name : name + '(' + retry + ')', diff --git a/packages/base/src/lazy-tsp-client.ts b/packages/base/src/lazy-tsp-client.ts index 6a96cb893..3430a2815 100644 --- a/packages/base/src/lazy-tsp-client.ts +++ b/packages/base/src/lazy-tsp-client.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { TspClient } from 'tsp-typescript-client'; +import { ITspClient } from 'tsp-typescript-client'; +import { HttpTspClient } from 'tsp-typescript-client/lib/protocol/http-tsp-client'; /** * Hack! @@ -8,28 +9,39 @@ import { TspClient } from 'tsp-typescript-client'; * Only keep methods, discard properties. */ export type LazyTspClient = { - [K in keyof TspClient]: TspClient[K] extends (...args: infer A) => infer R | Promise + [K in keyof ITspClient]: ITspClient[K] extends (...args: infer A) => infer R | Promise ? (...args: A) => Promise : never; // Discard property. }; export type LazyTspClientFactory = typeof LazyTspClientFactory; -export function LazyTspClientFactory(url: Promise): TspClient { - // All methods from the `TspClient` are asynchronous. The `LazyTspClient` +export function LazyTspClientFactory(provider: () => Promise): ITspClient { + // All methods from the `HttpTspClient` are asynchronous. The `LazyTspClient` // will just delay each call to its methods by first awaiting for the - // asynchronous `baseUrl` resolution to then get a valid `TspClient`. - const tspClientPromise = url.then(baseUrl => new TspClient(baseUrl)); + // asynchronous `baseUrl` resolution to then get a valid `HttpTspClient`. + + // Save the current HttpTspClient and the URL used for it. + let tspClient: HttpTspClient; + let lastUrl: string; // eslint-disable-next-line no-null/no-null return new Proxy(Object.create(null), { get(target, property, _receiver) { let method = target[property]; if (!method) { target[property] = method = async (...args: any[]) => { - const tspClient = (await tspClientPromise) as any; - return tspClient[property](...args); + tspClient = await provider().then(baseUrl => { + // If the url has not been updated keep the same client. + if (lastUrl === baseUrl) { + return tspClient; + } + // If the url has changed save it and create a new client. + lastUrl = baseUrl; + return new HttpTspClient(baseUrl); + }); + return (tspClient as any)[property](...args); }; } return method; } - }) as LazyTspClient as TspClient; + }) as LazyTspClient as ITspClient; } diff --git a/packages/base/src/trace-manager.ts b/packages/base/src/trace-manager.ts index a28ef0ed4..7c7e7d34f 100644 --- a/packages/base/src/trace-manager.ts +++ b/packages/base/src/trace-manager.ts @@ -1,5 +1,5 @@ import { Trace } from 'tsp-typescript-client/lib/models/trace'; -import { TspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; +import { ITspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; import { Query } from 'tsp-typescript-client/lib/models/query/query'; import { OutputDescriptor } from 'tsp-typescript-client/lib/models/output-descriptor'; import { TspClientResponse } from 'tsp-typescript-client/lib/protocol/tsp-client-response'; @@ -7,9 +7,9 @@ import { signalManager } from './signals/signal-manager'; export class TraceManager { private fOpenTraces: Map = new Map(); - private fTspClient: TspClient; + private fTspClient: ITspClient; - constructor(tspClient: TspClient) { + constructor(tspClient: ITspClient) { this.fTspClient = tspClient; } @@ -69,7 +69,7 @@ export class TraceManager { async openTrace(traceURI: string, traceName?: string): Promise { const name = traceName ? traceName : traceURI.replace(/\/$/, '').replace(/(.*\/)?/, ''); - const tryOpen = async function (tspClient: TspClient, retry: number): Promise> { + const tryOpen = async function (tspClient: ITspClient, retry: number): Promise> { return tspClient.openTrace( new Query({ name: retry === 0 ? name : name + '(' + retry + ')', @@ -103,7 +103,7 @@ export class TraceManager { if (currentTrace) { const traceResponse = await this.fTspClient.fetchTrace(currentTrace.UUID); const trace = traceResponse.getModel(); - if (trace && traceResponse.isOk) { + if (trace && traceResponse.isOk()) { this.fOpenTraces.set(traceUUID, trace); return trace; } diff --git a/packages/base/src/tsp-client-provider.ts b/packages/base/src/tsp-client-provider.ts index f22f8b398..5a7b93f5e 100644 --- a/packages/base/src/tsp-client-provider.ts +++ b/packages/base/src/tsp-client-provider.ts @@ -1,9 +1,9 @@ -import { TspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; +import { ITspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; import { ExperimentManager } from './experiment-manager'; import { TraceManager } from './trace-manager'; export interface ITspClientProvider { - getTspClient(): TspClient; + getTspClient(): ITspClient; getTraceManager(): TraceManager; getExperimentManager(): ExperimentManager; /** @@ -11,5 +11,5 @@ export interface ITspClientProvider { * @param listener The listener function to be called when the url is * changed */ - addTspClientChangeListener(listener: (tspClient: TspClient) => void): void; + addTspClientChangeListener(listener: (tspClient: ITspClient) => void): void; } diff --git a/packages/react-components/src/components/__tests__/table-renderer-components.test.tsx b/packages/react-components/src/components/__tests__/table-renderer-components.test.tsx index c62a58234..78afdc08d 100644 --- a/packages/react-components/src/components/__tests__/table-renderer-components.test.tsx +++ b/packages/react-components/src/components/__tests__/table-renderer-components.test.tsx @@ -6,7 +6,7 @@ import { CellRenderer, LoadingRenderer, SearchFilterRenderer } from '../table-re import { AbstractOutputProps } from '../abstract-output-component'; import { TimeGraphUnitController } from 'timeline-chart/lib/time-graph-unit-controller'; import { TimeRange } from 'traceviewer-base/lib/utils/time-range'; -import { TspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; +import { HttpTspClient } from 'tsp-typescript-client/lib/protocol/http-tsp-client'; import { ColDef, Column, ColumnApi, GridApi, IRowModel, RowNode } from 'ag-grid-community'; describe('', () => { @@ -38,7 +38,7 @@ describe('', () => { componentLeft: 0, chartOffset: 0 }, - tspClient: new TspClient('testURL'), + tspClient: new HttpTspClient('testURL'), traceId: '0', outputDescriptor: { id: '0', diff --git a/packages/react-components/src/components/abstract-output-component.tsx b/packages/react-components/src/components/abstract-output-component.tsx index 516a084db..720998b9e 100644 --- a/packages/react-components/src/components/abstract-output-component.tsx +++ b/packages/react-components/src/components/abstract-output-component.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { OutputDescriptor } from 'tsp-typescript-client/lib/models/output-descriptor'; -import { TspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; +import { ITspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faBars, faSpinner, faThumbtack, faTimes } from '@fortawesome/free-solid-svg-icons'; import { TimeGraphUnitController } from 'timeline-chart/lib/time-graph-unit-controller'; @@ -11,16 +11,10 @@ import { TooltipComponent } from './tooltip-component'; import { TooltipXYComponent } from './tooltip-xy-component'; import { ResponseStatus } from 'tsp-typescript-client/lib/models/response/responses'; import { signalManager } from 'traceviewer-base/lib/signals/signal-manager'; -import { - DropDownComponent, - DropDownSubSection, - OptionCheckBoxState, - OptionState, - OptionType -} from './drop-down-component'; +import { DropDownComponent, DropDownSubSection, OptionState } from './drop-down-component'; export interface AbstractOutputProps { - tspClient: TspClient; + tspClient: ITspClient; tooltipComponent: TooltipComponent | null; tooltipXYComponent: TooltipXYComponent | null; traceId: string; diff --git a/packages/react-components/src/components/data-providers/style-provider.ts b/packages/react-components/src/components/data-providers/style-provider.ts index 6985d7d5b..b64253e76 100644 --- a/packages/react-components/src/components/data-providers/style-provider.ts +++ b/packages/react-components/src/components/data-providers/style-provider.ts @@ -1,11 +1,11 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { TspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; +import { ITspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; import { QueryHelper } from 'tsp-typescript-client/lib/models/query/query-helper'; import { OutputStyleModel, OutputElementStyle } from 'tsp-typescript-client/lib/models/styles'; import { StyleProperties } from './style-properties'; export class StyleProvider { - private tspClient: TspClient; + private tspClient: ITspClient; private traceId: string; private outputId: string; @@ -13,7 +13,7 @@ export class StyleProvider { private styleModel: OutputStyleModel | undefined; - constructor(outputId: string, traceId: string, tspClient: TspClient) { + constructor(outputId: string, traceId: string, tspClient: ITspClient) { this.outputId = outputId; this.tspClient = tspClient; this.traceId = traceId; diff --git a/packages/react-components/src/components/data-providers/tsp-data-provider.ts b/packages/react-components/src/components/data-providers/tsp-data-provider.ts index 2f5740d05..95fc99302 100644 --- a/packages/react-components/src/components/data-providers/tsp-data-provider.ts +++ b/packages/react-components/src/components/data-providers/tsp-data-provider.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { TspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; +import { ITspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; import { TimeGraphArrow, TimeGraphEntry, @@ -21,7 +21,7 @@ enum ElementType { } export class TspDataProvider { - private client: TspClient; + private client: ITspClient; private outputId: string; private traceUUID: string; private timeGraphEntries: TimeGraphEntry[]; @@ -29,7 +29,7 @@ export class TspDataProvider { public totalRange: bigint; - constructor(client: TspClient, traceUUID: string, outputId: string) { + constructor(client: ITspClient, traceUUID: string, outputId: string) { this.timeGraphEntries = []; this.timeGraphRows = []; this.client = client; diff --git a/packages/react-components/src/components/trace-context-component.tsx b/packages/react-components/src/components/trace-context-component.tsx index 798f89a6f..2136e8a96 100644 --- a/packages/react-components/src/components/trace-context-component.tsx +++ b/packages/react-components/src/components/trace-context-component.tsx @@ -9,7 +9,7 @@ import { TimelineChart } from 'timeline-chart/lib/time-graph-model'; import { TimeGraphUnitController } from 'timeline-chart/lib/time-graph-unit-controller'; import { OutputDescriptor } from 'tsp-typescript-client/lib/models/output-descriptor'; import { Experiment } from 'tsp-typescript-client/lib/models/experiment'; -import { TspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; +import { ITspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; import { TimeRange, TimeRangeString } from 'traceviewer-base/lib/utils/time-range'; import { TableOutputComponent } from './table-output-component'; import { TimegraphOutputComponent } from './timegraph-output-component'; @@ -34,7 +34,7 @@ import { TimeRangeUpdatePayload } from 'traceviewer-base/lib/signals/time-range- const ResponsiveGridLayout = WidthProvider(Responsive); export interface TraceContextProps { - tspClient: TspClient; + tspClient: ITspClient; experiment: Experiment; outputs: OutputDescriptor[]; overviewDescriptor?: OutputDescriptor; // The default output descriptor for the overview diff --git a/packages/react-components/src/components/trace-overview-selection-dialog-component.tsx b/packages/react-components/src/components/trace-overview-selection-dialog-component.tsx index 133eecf25..d8a88d589 100644 --- a/packages/react-components/src/components/trace-overview-selection-dialog-component.tsx +++ b/packages/react-components/src/components/trace-overview-selection-dialog-component.tsx @@ -1,11 +1,11 @@ import React from 'react'; -import { OutputDescriptor, TspClient } from 'tsp-typescript-client'; +import { OutputDescriptor, ITspClient } from 'tsp-typescript-client'; import { AbstractDialogComponent, DialogComponentProps } from './abstract-dialog-component'; import { signalManager } from 'traceviewer-base/lib/signals/signal-manager'; import { AvailableViewsComponent } from './utils/available-views-component'; export interface TraceOverviewSelectionComponentProps extends DialogComponentProps { - tspClient: TspClient; + tspClient: ITspClient; traceID: string; } diff --git a/theia-extensions/viewer-prototype/src/browser/preferences-frontend-contribution.ts b/theia-extensions/viewer-prototype/src/browser/preferences-frontend-contribution.ts new file mode 100644 index 000000000..df10de5b8 --- /dev/null +++ b/theia-extensions/viewer-prototype/src/browser/preferences-frontend-contribution.ts @@ -0,0 +1,25 @@ +import { FrontendApplicationContribution } from '@theia/core/lib/browser'; +import { inject, injectable } from 'inversify'; +import { PortPreferenceProxy } from '../common/trace-server-url-provider'; +import { TracePreferences, TRACE_PORT } from './trace-server-preference'; + +@injectable() +export class PreferencesFrontendContribution implements FrontendApplicationContribution { + constructor( + @inject(TracePreferences) protected tracePreferences: TracePreferences, + @inject(PortPreferenceProxy) protected portPreferenceProxy: PortPreferenceProxy + ) {} + + async initialize(): Promise { + this.tracePreferences.ready.then(() => { + this.portPreferenceProxy.onPortPreferenceChanged(this.tracePreferences[TRACE_PORT]); + this.tracePreferences.onPreferenceChanged(async event => { + if (event.preferenceName === TRACE_PORT) { + const newValue = typeof event.newValue === 'string' ? parseInt(event.newValue) : event.newValue; + const oldValue = typeof event.oldValue === 'string' ? parseInt(event.oldValue) : event.oldValue; + this.portPreferenceProxy.onPortPreferenceChanged(newValue, oldValue, true); + } + }); + }); + } +} diff --git a/theia-extensions/viewer-prototype/src/browser/theia-rpc-tsp-proxy.ts b/theia-extensions/viewer-prototype/src/browser/theia-rpc-tsp-proxy.ts new file mode 100644 index 000000000..12bb19b6e --- /dev/null +++ b/theia-extensions/viewer-prototype/src/browser/theia-rpc-tsp-proxy.ts @@ -0,0 +1,371 @@ +import { inject, injectable, interfaces } from '@theia/core/shared/inversify'; +import { ITspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; +import { TspClientResponse } from 'tsp-typescript-client/lib/protocol/tsp-client-response'; +import { Trace } from 'tsp-typescript-client/lib/models/trace'; +import { Query } from 'tsp-typescript-client/lib/models/query/query'; +import { Experiment } from 'tsp-typescript-client/lib/models/experiment'; +import { GenericResponse } from 'tsp-typescript-client/lib/models/response/responses'; +import { HealthStatus } from 'tsp-typescript-client/lib/models/health'; +import { XyEntry, XYModel } from 'tsp-typescript-client/lib/models/xy'; +import { TimeGraphEntry, TimeGraphArrow, TimeGraphModel } from 'tsp-typescript-client/lib/models/timegraph'; +import { AnnotationCategoriesModel, AnnotationModel } from 'tsp-typescript-client/lib/models/annotation'; +import { TableModel, ColumnHeaderEntry } from 'tsp-typescript-client/lib/models/table'; +import { OutputDescriptor } from 'tsp-typescript-client/lib/models/output-descriptor'; +import { EntryModel } from 'tsp-typescript-client/lib/models/entry'; +import { OutputStyleModel } from 'tsp-typescript-client/lib/models/styles'; +import { MarkerSet } from 'tsp-typescript-client/lib/models/markerset'; +import { DataTreeEntry } from 'tsp-typescript-client/lib/models/data-tree'; + +export const TspClientProxy = Symbol('TspClientProxy') as symbol & interfaces.Abstract; +export type TspClientProxy = ITspClient; + +@injectable() +export class TheiaRpcTspProxy implements ITspClient { + public constructor(@inject(TspClientProxy) protected tspClient: ITspClient) {} + + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types + protected toTspClientResponse(result: any): TspClientResponse { + const tspClientResponse: TspClientResponse = new TspClientResponse( + result.text, + result.statusCode, + result.statusMessage, + result.responseModel + ); + return tspClientResponse; + } + + /** + * Fetch all available traces on the server + * @returns List of Trace + */ + public async fetchTraces(): Promise> { + return this.toTspClientResponse(await this.tspClient.fetchTraces()); + } + + /** + * Fetch a specific trace information + * @param traceUUID Trace UUID to fetch + */ + public async fetchTrace(traceUUID: string): Promise> { + return this.toTspClientResponse(await this.tspClient.fetchTrace(traceUUID)); + } + + /** + * Open a trace on the server + * @param parameters Query object + * @returns The opened trace + */ + public async openTrace(parameters: Query): Promise> { + return this.toTspClientResponse(await this.tspClient.openTrace(parameters)); + } + + /** + * Delete a trace on the server + * @param traceUUID Trace UUID to delete + * @param deleteTrace Also delete the trace from disk + * @param removeCache Remove all cache for this trace + * @returns The deleted trace + */ + public async deleteTrace( + traceUUID: string, + deleteTrace?: boolean, + removeCache?: boolean + ): Promise> { + return this.toTspClientResponse(await this.tspClient.deleteTrace(traceUUID, deleteTrace, removeCache)); + } + + /** + * Fetch all available experiments on the server + * @returns List of Experiment + */ + public async fetchExperiments(): Promise> { + return this.toTspClientResponse(await this.tspClient.fetchExperiments()); + } + + /** + * Fetch a specific experiment information + * @param expUUID Experiment UUID to fetch + * @returns The experiment + */ + public async fetchExperiment(expUUID: string): Promise> { + return this.toTspClientResponse(await this.tspClient.fetchExperiment(expUUID)); + } + + /** + * Create an experiment on the server + * @param parameters Query object + * @returns The created experiment + */ + public async createExperiment(parameters: Query): Promise> { + return this.toTspClientResponse(await this.tspClient.createExperiment(parameters)); + } + + /** + * Update an experiment + * @param expUUID Experiment UUID to update + * @param parameters Query object + * @returns The updated experiment + */ + public async updateExperiment(expUUID: string, parameters: Query): Promise> { + return this.toTspClientResponse(await this.tspClient.updateExperiment(expUUID, parameters)); + } + + /** + * Delete an experiment on the server + * @param expUUID Experiment UUID to delete + * @returns The deleted experiment + */ + public async deleteExperiment(expUUID: string): Promise> { + return this.toTspClientResponse(await this.tspClient.deleteExperiment(expUUID)); + } + + /** + * List all the outputs associated to this experiment + * @param expUUID Experiment UUID + * @returns List of OutputDescriptor + */ + public async experimentOutputs(expUUID: string): Promise> { + return this.toTspClientResponse(await this.tspClient.experimentOutputs(expUUID)); + } + + /** + * Fetch Data tree + * @param expUUID Experiment UUID + * @param outputID Output ID + * @param parameters Query object + * @returns Generic entry response with entries + */ + public async fetchDataTree( + expUUID: string, + outputID: string, + parameters: Query + ): Promise>>> { + return this.toTspClientResponse>>( + await this.tspClient.fetchDataTree(expUUID, outputID, parameters) + ); + } + + /** + * Fetch XY tree + * @param expUUID Experiment UUID + * @param outputID Output ID + * @param parameters Query object + * @returns Generic entry response with entries + */ + public async fetchXYTree( + expUUID: string, + outputID: string, + parameters: Query + ): Promise>>> { + return this.toTspClientResponse>>( + await this.tspClient.fetchXYTree(expUUID, outputID, parameters) + ); + } + + /** + * Fetch XY. model extends XYModel + * @param expUUID Experiment UUID + * @param outputID Output ID + * @param parameters Query object + * @returns XY model response with the model + */ + public async fetchXY( + expUUID: string, + outputID: string, + parameters: Query + ): Promise>> { + return this.toTspClientResponse>( + await this.tspClient.fetchXY(expUUID, outputID, parameters) + ); + } + + /** + * Fetch XY tooltip + * @param expUUID Experiment UUID + * @param outputID Output ID + * @param xValue X value + * @param yValue Optional Y value + * @param seriesID Optional series ID + * @returns Map of key=name of the property and value=string value associated + */ + public async fetchXYToolTip( + expUUID: string, + outputID: string, + xValue: number, + yValue?: number, + seriesID?: string + ): Promise>> { + return this.toTspClientResponse>( + await this.tspClient.fetchXYToolTip(expUUID, outputID, xValue, yValue, seriesID) + ); + } + + /** + * Fetch Time Graph tree, Model extends TimeGraphEntry + * @param expUUID Experiment UUID + * @param outputID Output ID + * @param parameters Query object + * @returns Time graph entry response with entries of type TimeGraphEntry + */ + public async fetchTimeGraphTree( + expUUID: string, + outputID: string, + parameters: Query + ): Promise>>> { + return this.toTspClientResponse>>( + await this.tspClient.fetchTimeGraphTree(expUUID, outputID, parameters) + ); + } + + /** + * Fetch Time Graph states. Model extends TimeGraphModel + * @param expUUID Experiment UUID + * @param outputID Output ID + * @param parameters Query object + * @returns Generic response with the model + */ + public async fetchTimeGraphStates( + expUUID: string, + outputID: string, + parameters: Query + ): Promise>> { + return this.toTspClientResponse>( + await this.tspClient.fetchTimeGraphStates(expUUID, outputID, parameters) + ); + } + + /** + * Fetch Time Graph arrows. Model extends TimeGraphArrow + * @param expUUID Experiment UUID + * @param outputID Output ID + * @param parameters Query object + * @returns Generic response with the model + */ + public async fetchTimeGraphArrows( + expUUID: string, + outputID: string, + parameters: Query + ): Promise>> { + return this.toTspClientResponse>( + await this.tspClient.fetchTimeGraphArrows(expUUID, outputID, parameters) + ); + } + + /** + * Fetch marker sets. + * @returns Generic response with the model + */ + public async fetchMarkerSets(expUUID: string): Promise>> { + return this.toTspClientResponse>(await this.tspClient.fetchMarkerSets(expUUID)); + } + + /** + * Fetch annotations categories. + * @param expUUID Experiment UUID + * @param outputID Output ID + * @param markerSetId Marker Set ID + * @returns Generic response with the model + */ + public async fetchAnnotationsCategories( + expUUID: string, + outputID: string, + markerSetId?: string + ): Promise>> { + return this.toTspClientResponse>( + await this.tspClient.fetchAnnotationsCategories(expUUID, outputID, markerSetId) + ); + } + + /** + * Fetch annotations. + * @param expUUID Experiment UUID + * @param outputID Output ID + * @param parameters Query object + * @returns Generic response with the model + */ + public async fetchAnnotations( + expUUID: string, + outputID: string, + parameters: Query + ): Promise>> { + return this.toTspClientResponse>( + await this.tspClient.fetchAnnotations(expUUID, outputID, parameters) + ); + } + + /** + * Fetch tooltip for a Time Graph element. + * @param expUUID Experiment UUID + * @param outputID Output ID + * @param parameters Query object + * @returns Map of key=name of the property and value=string value associated + */ + public async fetchTimeGraphTooltip( + expUUID: string, + outputID: string, + parameters: Query + ): Promise>> { + return this.toTspClientResponse>( + await this.tspClient.fetchTimeGraphTooltip(expUUID, outputID, parameters) + ); + } + + /** + * Fetch Table columns + * @param expUUID Experiment UUID + * @param outputID Output ID + * @param parameters Query object + * @returns Generic entry response with columns headers as model + */ + public async fetchTableColumns( + expUUID: string, + outputID: string, + parameters: Query + ): Promise>> { + return this.toTspClientResponse>( + await this.tspClient.fetchTableColumns(expUUID, outputID, parameters) + ); + } + + /** + * Fetch Table lines + * @param expUUID Experiment UUID + * @param outputID Output ID + * @param parameters Query object + * @returns Generic response with the model + */ + public async fetchTableLines( + expUUID: string, + outputID: string, + parameters: Query + ): Promise>> { + return this.toTspClientResponse>( + await this.tspClient.fetchTableLines(expUUID, outputID, parameters) + ); + } + + /** + * Fetch output styles + * @param expUUID Experiment UUID + * @param outputID Output ID + * @param parameters Query object + * @returns Generic response with the model + */ + public async fetchStyles( + expUUID: string, + outputID: string, + parameters: Query + ): Promise>> { + return this.toTspClientResponse>( + await this.tspClient.fetchStyles(expUUID, outputID, parameters) + ); + } + + /** + * Check the health status of the server + * @returns The Health Status + */ + public async checkHealth(): Promise> { + return this.toTspClientResponse(await this.tspClient.checkHealth()); + } +} diff --git a/theia-extensions/viewer-prototype/src/browser/trace-explorer/trace-explorer-widget.tsx b/theia-extensions/viewer-prototype/src/browser/trace-explorer/trace-explorer-widget.tsx index ef2188aad..91e06f7b9 100644 --- a/theia-extensions/viewer-prototype/src/browser/trace-explorer/trace-explorer-widget.tsx +++ b/theia-extensions/viewer-prototype/src/browser/trace-explorer/trace-explorer-widget.tsx @@ -8,7 +8,10 @@ import { TraceExplorerServerStatusWidget } from './trace-explorer-sub-widgets/tr import { TraceExplorerTimeRangeDataWidget } from './trace-explorer-sub-widgets/theia-trace-explorer-time-range-data-widget'; import { signalManager, Signals } from 'traceviewer-base/lib/signals/signal-manager'; import { OpenedTracesUpdatedSignalPayload } from 'traceviewer-base/lib/signals/opened-traces-updated-signal-payload'; -import { TraceServerConnectionStatusService } from '../trace-server-status'; +import { + TraceServerConnectionStatusBackend, + TraceServerConnectionStatusClient +} from '../../common/trace-server-connection-status'; @injectable() export class TraceExplorerWidget extends BaseWidget { @@ -24,8 +27,11 @@ export class TraceExplorerWidget extends BaseWidget { @inject(TraceExplorerServerStatusWidget) protected readonly serverStatusWidget!: TraceExplorerServerStatusWidget; @inject(TraceExplorerTimeRangeDataWidget) protected readonly timeRangeDataWidget!: TraceExplorerTimeRangeDataWidget; @inject(ViewContainer.Factory) protected readonly viewContainerFactory!: ViewContainer.Factory; - @inject(TraceServerConnectionStatusService) - protected readonly connectionStatusService: TraceServerConnectionStatusService; + @inject(TraceServerConnectionStatusClient) + protected readonly connectionStatusClient: TraceServerConnectionStatusClient; + // This is needed to initialize the backend service + @inject(TraceServerConnectionStatusBackend) + protected traceServerConnectionStatusProxy: TraceServerConnectionStatusBackend; openExperiment(traceUUID: string): void { return this.openedTracesWidget.openExperiment(traceUUID); @@ -108,11 +114,13 @@ export class TraceExplorerWidget extends BaseWidget { this.node.focus(); } - protected onAfterShow(): void { - this.connectionStatusService.addConnectionStatusListener(); + protected async onAfterShow(): Promise { + this.connectionStatusClient.addConnectionStatusListener(); + const status = await this.traceServerConnectionStatusProxy.getStatus(); + this.connectionStatusClient.updateStatus(status); } protected onAfterHide(): void { - this.connectionStatusService.removeConnectionStatusListener(); + this.connectionStatusClient.removeConnectionStatusListener(); } } diff --git a/theia-extensions/viewer-prototype/src/browser/trace-server-status.ts b/theia-extensions/viewer-prototype/src/browser/trace-server-connection-status-client-impl.ts similarity index 50% rename from theia-extensions/viewer-prototype/src/browser/trace-server-status.ts rename to theia-extensions/viewer-prototype/src/browser/trace-server-connection-status-client-impl.ts index eb186262c..8ecfd297c 100644 --- a/theia-extensions/viewer-prototype/src/browser/trace-server-status.ts +++ b/theia-extensions/viewer-prototype/src/browser/trace-server-connection-status-client-impl.ts @@ -1,23 +1,23 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import { injectable } from '@theia/core/shared/inversify'; -import { RestClient, ConnectionStatusListener } from 'tsp-typescript-client/lib/protocol/rest-client'; +import { injectable } from 'inversify'; +import { TraceServerConnectionStatusClient } from '../common/trace-server-connection-status'; @injectable() -export class TraceServerConnectionStatusService { - private connectionStatusListener: ConnectionStatusListener; +export class TraceServerConnectionStatusClientImpl implements TraceServerConnectionStatusClient { + protected active = false; - constructor() { - this.connectionStatusListener = (status: boolean) => { - TraceServerConnectionStatusService.renderStatus(status); - }; + updateStatus(status: boolean): void { + if (this.active) { + TraceServerConnectionStatusClientImpl.renderStatus(status); + } } - public addConnectionStatusListener(): void { - RestClient.addConnectionStatusListener(this.connectionStatusListener); + addConnectionStatusListener(): void { + this.active = true; } - public removeConnectionStatusListener(): void { - RestClient.removeConnectionStatusListener(this.connectionStatusListener); + removeConnectionStatusListener(): void { + this.active = false; } static renderStatus(status: boolean): void { diff --git a/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer-contribution.ts b/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer-contribution.ts index 8be211a0f..96d4d420a 100644 --- a/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer-contribution.ts +++ b/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer-contribution.ts @@ -21,12 +21,12 @@ import { } from './trace-viewer-commands'; import { PortBusy, TraceServerConfigService } from '../../common/trace-server-config'; import { TracePreferences, TRACE_PATH, TRACE_ARGS } from '../trace-server-preference'; -import { TspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; import { TspClientProvider } from '../tsp-client-provider-impl'; import { ChartShortcutsDialog } from '../trace-explorer/trace-explorer-sub-widgets/charts-cheatsheet-component'; import { signalManager } from 'traceviewer-base/lib/signals/signal-manager'; -import { TraceServerConnectionStatusService } from '../trace-server-status'; +import { TraceServerConnectionStatusClientImpl } from '../trace-server-connection-status-client-impl'; import { FileStat } from '@theia/filesystem/lib/common/files'; +import { ITspClient } from 'tsp-typescript-client'; interface TraceViewerWidgetOpenerOptions extends WidgetOpenerOptions { traceUUID: string; @@ -37,7 +37,7 @@ export class TraceViewerContribution extends WidgetOpenHandler implements CommandContribution, KeybindingContribution { - private tspClient: TspClient; + private tspClient: ITspClient; constructor(@inject(TspClientProvider) private tspClientProvider: TspClientProvider) { super(); @@ -94,7 +94,7 @@ export class TraceViewerContribution progress.report({ message: 'Trace server started.', work: { done: 100, total: 100 } }); } progress.cancel(); - TraceServerConnectionStatusService.renderStatus(true); + TraceServerConnectionStatusClientImpl.renderStatus(true); signalManager().fireTraceServerStartedSignal(); this.openDialog(rootPath); } @@ -163,7 +163,7 @@ export class TraceViewerContribution } else { progress.report({ message: 'Trace server started.', work: { done: 100, total: 100 } }); } - TraceServerConnectionStatusService.renderStatus(true); + TraceServerConnectionStatusClientImpl.renderStatus(true); signalManager().fireTraceServerStartedSignal(); return super.open(traceURI, options); } @@ -230,7 +230,7 @@ export class TraceViewerContribution } else { progress.report({ message: 'Trace server started.', work: { done: 100, total: 100 } }); } - TraceServerConnectionStatusService.renderStatus(true); + TraceServerConnectionStatusClientImpl.renderStatus(true); signalManager().fireTraceServerStartedSignal(); return; } @@ -261,7 +261,7 @@ export class TraceViewerContribution try { await this.traceServerConfigService.stopTraceServer(); this.messageService.info('Trace server terminated successfully.'); - TraceServerConnectionStatusService.renderStatus(false); + TraceServerConnectionStatusClientImpl.renderStatus(false); } catch (err) { this.messageService.error('Failed to stop the trace server.'); } diff --git a/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer-frontend-module.ts b/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer-frontend-module.ts index 362f80243..5e1e15c07 100644 --- a/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer-frontend-module.ts +++ b/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer-frontend-module.ts @@ -9,7 +9,6 @@ import { } from '@theia/core/lib/browser'; import { TraceViewerWidget, TraceViewerWidgetOptions } from './trace-viewer'; import { TraceViewerContribution } from './trace-viewer-contribution'; -import { TraceServerUrlProvider } from '../../common/trace-server-url-provider'; import { CommandContribution } from '@theia/core/lib/common'; import 'ag-grid-community/dist/styles/ag-grid.css'; import 'ag-grid-community/dist/styles/ag-theme-balham-dark.css'; @@ -20,21 +19,26 @@ import { TraceExplorerContribution } from '../trace-explorer/trace-explorer-cont import { TraceExplorerWidget } from '../trace-explorer/trace-explorer-widget'; import { TspClientProvider } from '../tsp-client-provider-impl'; import { TheiaMessageManager } from '../theia-message-manager'; -import { TraceServerUrlProviderImpl } from '../trace-server-url-provider-frontend-impl'; import { bindTraceServerPreferences } from '../trace-server-bindings'; -import { TraceServerConfigService, traceServerPath } from '../../common/trace-server-config'; +import { TRACE_SERVER_CLIENT, TraceServerConfigService, traceServerPath } from '../../common/trace-server-config'; import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; import { TraceViewerToolbarContribution } from './trace-viewer-toolbar-contribution'; -import { LazyTspClientFactory } from 'traceviewer-base/lib/lazy-tsp-client'; import { BackendFileService, backendFileServicePath } from '../../common/backend-file-service'; -import { TraceServerConnectionStatusService } from '../trace-server-status'; +import { TraceServerConnectionStatusClientImpl } from '../trace-server-connection-status-client-impl'; import { bindTraceOverviewPreferences } from '../trace-overview-binding'; +import { ITspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; +import { PortPreferenceProxy, TRACE_SERVER_PORT } from '../../common/trace-server-url-provider'; +import { PreferencesFrontendContribution } from '../preferences-frontend-contribution'; +import { + TRACE_SERVER_CONNECTION_STATUS, + TraceServerConnectionStatusBackend, + TraceServerConnectionStatusClient +} from '../../common/trace-server-connection-status'; +import { TheiaRpcTspProxy, TspClientProxy } from '../theia-rpc-tsp-proxy'; export default new ContainerModule(bind => { - bind(TraceServerUrlProviderImpl).toSelf().inSingletonScope(); - bind(FrontendApplicationContribution).toService(TraceServerUrlProviderImpl); - bind(TraceServerUrlProvider).toService(TraceServerUrlProviderImpl); - bind(LazyTspClientFactory).toFunction(LazyTspClientFactory); + bind(PreferencesFrontendContribution).toSelf().inSingletonScope(); + bind(FrontendApplicationContribution).toService(PreferencesFrontendContribution); bind(TspClientProvider).toSelf().inSingletonScope(); bind(TheiaMessageManager).toSelf().inSingletonScope(); @@ -42,8 +46,7 @@ export default new ContainerModule(bind => { bind(FrontendApplicationContribution).toService(TraceViewerToolbarContribution); bind(TabBarToolbarContribution).toService(TraceViewerToolbarContribution); bind(CommandContribution).toService(TraceViewerToolbarContribution); - bind(TraceServerConnectionStatusService).toSelf().inSingletonScope(); - bind(FrontendApplicationContribution).toService(TraceServerConnectionStatusService); + bind(TraceServerConnectionStatusClient).to(TraceServerConnectionStatusClientImpl).inSingletonScope(); bind(TraceViewerWidget).toSelf(); bind(WidgetFactory) @@ -88,4 +91,28 @@ export default new ContainerModule(bind => { return connection.createProxy(backendFileServicePath); }) .inSingletonScope(); + + bind(TspClientProxy) + .toDynamicValue(ctx => { + const connection = ctx.container.get(WebSocketConnectionProvider); + return connection.createProxy(TRACE_SERVER_CLIENT); + }) + .inSingletonScope(); + + bind(PortPreferenceProxy) + .toDynamicValue(ctx => { + const connection = ctx.container.get(WebSocketConnectionProvider); + return connection.createProxy(TRACE_SERVER_PORT); + }) + .inSingletonScope(); + + bind(TraceServerConnectionStatusBackend) + .toDynamicValue(ctx => { + const connection = ctx.container.get(WebSocketConnectionProvider); + const client = ctx.container.get(TraceServerConnectionStatusClient); + return connection.createProxy(TRACE_SERVER_CONNECTION_STATUS, client); + }) + .inSingletonScope(); + + bind(TheiaRpcTspProxy).toSelf().inSingletonScope(); }); diff --git a/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer.tsx b/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer.tsx index 1dfa0379f..416ebcf59 100644 --- a/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer.tsx +++ b/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer.tsx @@ -4,7 +4,6 @@ import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget'; import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; import { OutputDescriptor } from 'tsp-typescript-client/lib/models/output-descriptor'; import { Trace } from 'tsp-typescript-client/lib/models/trace'; -import { TspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; import { TspClientProvider } from '../tsp-client-provider-impl'; import { TraceManager } from 'traceviewer-base/lib/trace-manager'; import { ExperimentManager } from 'traceviewer-base/lib/experiment-manager'; @@ -34,6 +33,7 @@ import { getSwitchToDefaultViewErrorMessage, OverviewPreferences } from '../trace-overview-preference'; +import { ITspClient } from 'tsp-typescript-client'; export const TraceViewerWidgetOptions = Symbol('TraceViewerWidgetOptions'); export interface TraceViewerWidgetOptions { @@ -49,7 +49,7 @@ export class TraceViewerWidget extends ReactWidget implements StatefulWidget { protected uri: Path; protected openedExperiment: Experiment | undefined; protected outputDescriptors: OutputDescriptor[] = []; - protected tspClient: TspClient; + protected tspClient: ITspClient; protected traceManager: TraceManager; protected experimentManager: ExperimentManager; protected backgroundTheme: string; diff --git a/theia-extensions/viewer-prototype/src/browser/tsp-client-provider-impl.ts b/theia-extensions/viewer-prototype/src/browser/tsp-client-provider-impl.ts index a238ae9b9..6c556d286 100644 --- a/theia-extensions/viewer-prototype/src/browser/tsp-client-provider-impl.ts +++ b/theia-extensions/viewer-prototype/src/browser/tsp-client-provider-impl.ts @@ -1,41 +1,25 @@ -import { injectable, inject } from '@theia/core/shared/inversify'; -import { TraceServerUrlProvider } from '../common/trace-server-url-provider'; -import { TspClient } from 'tsp-typescript-client/lib/protocol/tsp-client'; +import { injectable, inject } from 'inversify'; import { ExperimentManager } from 'traceviewer-base/lib/experiment-manager'; import { TraceManager } from 'traceviewer-base/lib/trace-manager'; import { ITspClientProvider } from 'traceviewer-base/lib/tsp-client-provider'; -import { LazyTspClientFactory } from 'traceviewer-base/lib/lazy-tsp-client'; +import { ITspClient } from 'tsp-typescript-client'; +import { TheiaRpcTspProxy } from './theia-rpc-tsp-proxy'; @injectable() export class TspClientProvider implements ITspClientProvider { - private _tspClient: TspClient; + private _tspClient: ITspClient; private _traceManager: TraceManager; private _experimentManager: ExperimentManager; - private _listeners: ((tspClient: TspClient) => void)[]; + private _listeners: ((tspClient: ITspClient) => void)[]; - constructor( - @inject(TraceServerUrlProvider) private tspUrlProvider: TraceServerUrlProvider, - @inject(LazyTspClientFactory) private lazyTspClientFactory: LazyTspClientFactory - ) { - const traceServerUrlPromise = this.tspUrlProvider.getTraceServerUrlPromise(); - this._tspClient = this.lazyTspClientFactory(traceServerUrlPromise); + constructor(@inject(TheiaRpcTspProxy) protected client: ITspClient) { + this._tspClient = client; this._traceManager = new TraceManager(this._tspClient); this._experimentManager = new ExperimentManager(this._tspClient, this._traceManager); this._listeners = []; - // Skip the first event fired when the Trace Server URL gets initialized. - traceServerUrlPromise.then(() => { - tspUrlProvider.onDidChangeTraceServerUrl(url => { - this._tspClient = new TspClient(url); - this._traceManager = new TraceManager(this._tspClient); - this._experimentManager = new ExperimentManager(this._tspClient, this._traceManager); - for (const listener of this._listeners) { - listener(this._tspClient); - } - }); - }); } - public getTspClient(): TspClient { + public getTspClient(): ITspClient { return this._tspClient; } @@ -52,7 +36,7 @@ export class TspClientProvider implements ITspClientProvider { * @param listener The listener function to be called when the url is * changed */ - addTspClientChangeListener(listener: (tspClient: TspClient) => void): void { + addTspClientChangeListener(listener: (tspClient: ITspClient) => void): void { this._listeners.push(listener); } } diff --git a/theia-extensions/viewer-prototype/src/common/trace-server-config.ts b/theia-extensions/viewer-prototype/src/common/trace-server-config.ts index fbfc15bda..12474acac 100644 --- a/theia-extensions/viewer-prototype/src/common/trace-server-config.ts +++ b/theia-extensions/viewer-prototype/src/common/trace-server-config.ts @@ -1,6 +1,8 @@ import { ApplicationError } from '@theia/core'; export const traceServerPath = '/services/theia-trace-extension/trace-server-config'; +export const TRACE_SERVER_CLIENT = '/services/theia-trace-extension/trace-server-client'; + export const PortBusy = ApplicationError.declare(-32650, code => ({ message: 'Port busy', data: { code } diff --git a/theia-extensions/viewer-prototype/src/common/trace-server-connection-status.ts b/theia-extensions/viewer-prototype/src/common/trace-server-connection-status.ts new file mode 100644 index 000000000..831a14d2f --- /dev/null +++ b/theia-extensions/viewer-prototype/src/common/trace-server-connection-status.ts @@ -0,0 +1,39 @@ +export const TraceServerConnectionStatusBackend = Symbol('TraceServerConnectionStatusBackend'); + +export const TRACE_SERVER_CONNECTION_STATUS = '/services/theia-trace-extension/trace-server-connection-status'; + +export interface TraceServerConnectionStatusBackend { + /** + * Add a new TraceServerConnectionStatusClient to be notified on status changes. + * @param client the client to be notified. + */ + addClient(client: TraceServerConnectionStatusClient): void; + /** + * Remove a new TraceServerConnectionStatusClient so it won't no longer be notified on status changes. + * @param client the client to be removed. + */ + removeClient(client: TraceServerConnectionStatusClient): void; + + /** + * Get the current status of the trace server + */ + getStatus(): Promise; +} + +export const TraceServerConnectionStatusClient = Symbol('TraceServerConnectionStatusClient'); + +export interface TraceServerConnectionStatusClient { + /** + * Update the status on the client. + * @param status the new value of the status. + */ + updateStatus(status: boolean): void; + /** + * Subscribe this client to the connection status + */ + addConnectionStatusListener(): void; + /** + * Unsubscribe this client from the connection status + */ + removeConnectionStatusListener(): void; +} diff --git a/theia-extensions/viewer-prototype/src/common/trace-server-url-provider.ts b/theia-extensions/viewer-prototype/src/common/trace-server-url-provider.ts index 8bda617d6..bef17f069 100644 --- a/theia-extensions/viewer-prototype/src/common/trace-server-url-provider.ts +++ b/theia-extensions/viewer-prototype/src/common/trace-server-url-provider.ts @@ -22,3 +22,16 @@ export interface TraceServerUrlProvider { */ onDidChangeTraceServerUrl(listener: (url: string) => void): void; } + +export const TRACE_SERVER_PORT = '/services/theia-trace-extension/trace-server-port'; + +export const PortPreferenceProxy = Symbol('PortPreferenceProxy'); +export interface PortPreferenceProxy { + /** + * Notify the backend about a change of the port preference. + * @param newPort the new value of the port preference. + * @param oldValue the old value of the port preference. + * @param preferenceChanged boolean that indicated whether the preference was changed or initialized. + */ + onPortPreferenceChanged(newPort: number | undefined, oldValue?: number, preferenceChanged?: boolean): Promise; +} diff --git a/theia-extensions/viewer-prototype/src/node/trace-server-connection-status-backend-impl.ts b/theia-extensions/viewer-prototype/src/node/trace-server-connection-status-backend-impl.ts new file mode 100644 index 000000000..d8814015c --- /dev/null +++ b/theia-extensions/viewer-prototype/src/node/trace-server-connection-status-backend-impl.ts @@ -0,0 +1,35 @@ +import { injectable } from 'inversify'; +import { RestClient } from 'tsp-typescript-client/lib/protocol/rest-client'; +import { + TraceServerConnectionStatusBackend, + TraceServerConnectionStatusClient +} from '../common/trace-server-connection-status'; + +@injectable() +export class TraceServerConnectionStatusBackendImpl implements TraceServerConnectionStatusBackend { + protected clients: TraceServerConnectionStatusClient[] = []; + protected lastStatus = false; + + constructor() { + const listener = (status: boolean) => { + this.clients.forEach(client => client.updateStatus(status)); + this.lastStatus = status; + }; + RestClient.addConnectionStatusListener(listener); + } + + getStatus(): Promise { + return Promise.resolve(this.lastStatus); + } + + addClient(client: TraceServerConnectionStatusClient): void { + this.clients.push(client); + } + + removeClient(client: TraceServerConnectionStatusClient): void { + const index = this.clients.indexOf(client); + if (index > -1) { + this.clients.splice(index, 1); + } + } +} diff --git a/theia-extensions/viewer-prototype/src/browser/trace-server-url-provider-frontend-impl.ts b/theia-extensions/viewer-prototype/src/node/trace-server-url-provider-impl.ts similarity index 60% rename from theia-extensions/viewer-prototype/src/browser/trace-server-url-provider-frontend-impl.ts rename to theia-extensions/viewer-prototype/src/node/trace-server-url-provider-impl.ts index 605bbb5f9..017e56750 100644 --- a/theia-extensions/viewer-prototype/src/browser/trace-server-url-provider-frontend-impl.ts +++ b/theia-extensions/viewer-prototype/src/node/trace-server-url-provider-impl.ts @@ -1,13 +1,17 @@ -import { Emitter, Event, MessageService } from '@theia/core'; -import { FrontendApplicationContribution } from '@theia/core/lib/browser'; -import { EnvVariablesServer } from '@theia/core/lib/common/env-variables/env-variables-protocol'; -import { inject, injectable } from '@theia/core/shared/inversify'; +import { inject, injectable } from 'inversify'; import { TraceServerConfigService } from '../common/trace-server-config'; -import { TraceServerUrlProvider, TRACE_SERVER_DEFAULT_URL } from '../common/trace-server-url-provider'; -import { TracePreferences, TRACE_PORT } from './trace-server-preference'; +import { + TraceServerUrlProvider, + TRACE_SERVER_DEFAULT_URL, + PortPreferenceProxy +} from '../common/trace-server-url-provider'; +import { Event, Emitter } from '@theia/core'; +import { BackendApplicationContribution } from '@theia/core/lib/node'; @injectable() -export class TraceServerUrlProviderImpl implements TraceServerUrlProvider, FrontendApplicationContribution { +export class TraceServerUrlProviderImpl + implements TraceServerUrlProvider, BackendApplicationContribution, PortPreferenceProxy +{ /** * The Trace Server URL resolved from a URL template and a port number. * Updated each time the port is changed from the preferences. @@ -45,12 +49,7 @@ export class TraceServerUrlProviderImpl implements TraceServerUrlProvider, Front return this._onDidChangeTraceServerUrlEmitter.event; } - constructor( - @inject(EnvVariablesServer) protected environment: EnvVariablesServer, - @inject(TracePreferences) protected tracePreferences: TracePreferences, - @inject(TraceServerConfigService) protected traceServerConfigService: TraceServerConfigService, - @inject(MessageService) protected messageService: MessageService - ) { + constructor(@inject(TraceServerConfigService) protected traceServerConfigService: TraceServerConfigService) { this._traceServerUrlPromise = new Promise(resolve => { const self = this.onDidChangeTraceServerUrl(url => { self.dispose(); @@ -58,28 +57,28 @@ export class TraceServerUrlProviderImpl implements TraceServerUrlProvider, Front }); }); // Get the URL template from the remote environment. - this.environment.getValue('TRACE_SERVER_URL').then(variable => { - const url = variable?.value; - this._traceServerUrlTemplate = url ? this.normalizeUrl(url) : TRACE_SERVER_DEFAULT_URL; - this.updateTraceServerUrl(); - }); - // Get the configurable port from Theia's preferences. - this.tracePreferences.ready.then(() => { - this._traceServerPort = this.tracePreferences[TRACE_PORT]; - this.updateTraceServerUrl(); - this.tracePreferences.onPreferenceChanged(async event => { - if (event.preferenceName === TRACE_PORT) { - this._traceServerPort = event.newValue; - this.updateTraceServerUrl(); - try { - await this.traceServerConfigService.stopTraceServer(); - this.messageService.info(`Trace server disconnected on port: ${event.oldValue}.`); - } catch (_) { - // Do not show the error incase the user tries to modify the port before starting a server - } + const variable = process.env['TRACE_SERVER_URL']; + this._traceServerUrlTemplate = variable ? this.normalizeUrl(variable) : TRACE_SERVER_DEFAULT_URL; + this.updateTraceServerUrl(); + } + + async onPortPreferenceChanged( + newPort: number | undefined, + oldValue?: number, + preferenceChanged = false + ): Promise { + this._traceServerPort = newPort; + this.updateTraceServerUrl(); + if (preferenceChanged) { + try { + await this.traceServerConfigService.stopTraceServer(); + if (oldValue) { + console.info(`Trace server disconnected on port: ${oldValue}.`); } - }); - }); + } catch (_) { + // Do not show the error incase the user tries to modify the port before starting a server + } + } } async initialize(): Promise { diff --git a/theia-extensions/viewer-prototype/src/node/viewer-prototype-backend-module.ts b/theia-extensions/viewer-prototype/src/node/viewer-prototype-backend-module.ts index 814ad246d..1f98fe81e 100644 --- a/theia-extensions/viewer-prototype/src/node/viewer-prototype-backend-module.ts +++ b/theia-extensions/viewer-prototype/src/node/viewer-prototype-backend-module.ts @@ -1,12 +1,28 @@ import { ContainerModule } from '@theia/core/shared/inversify'; import { ConnectionHandler, JsonRpcConnectionHandler } from '@theia/core/lib/common'; -import { traceServerPath } from '../common/trace-server-config'; +import { TRACE_SERVER_CLIENT, traceServerPath } from '../common/trace-server-config'; import { TraceServerConfigService } from '../common/trace-server-config'; import { BackendFileService, backendFileServicePath } from '../common/backend-file-service'; import { BackendFileServiceImpl } from './backend-file-service-impl'; +import { PortPreferenceProxy, TRACE_SERVER_PORT, TraceServerUrlProvider } from '../common/trace-server-url-provider'; +import { TraceServerUrlProviderImpl } from './trace-server-url-provider-impl'; +import { BackendApplicationContribution } from '@theia/core/lib/node'; +import { LazyTspClientFactory } from 'traceviewer-base/lib/lazy-tsp-client'; +import { TraceServerConnectionStatusBackendImpl } from './trace-server-connection-status-backend-impl'; +import { + TRACE_SERVER_CONNECTION_STATUS, + TraceServerConnectionStatusBackend, + TraceServerConnectionStatusClient +} from '../common/trace-server-connection-status'; export default new ContainerModule(bind => { + bind(LazyTspClientFactory).toFunction(LazyTspClientFactory); bind(BackendFileService).to(BackendFileServiceImpl).inSingletonScope(); + bind(TraceServerUrlProviderImpl).toSelf().inSingletonScope(); + bind(TraceServerUrlProvider).to(TraceServerUrlProviderImpl).inSingletonScope(); + bind(BackendApplicationContribution).toService(TraceServerUrlProvider); + bind(PortPreferenceProxy).toService(TraceServerUrlProvider); + bind(TraceServerConnectionStatusBackend).to(TraceServerConnectionStatusBackendImpl).inSingletonScope(); bind(ConnectionHandler) .toDynamicValue( ctx => @@ -23,4 +39,40 @@ export default new ContainerModule(bind => { ) ) .inSingletonScope(); + bind(ConnectionHandler) + .toDynamicValue( + ctx => + new JsonRpcConnectionHandler(TRACE_SERVER_CLIENT, () => { + const provider = ctx.container.get(TraceServerUrlProvider); + const lazyTspClientFactory = ctx.container.get(LazyTspClientFactory); + return lazyTspClientFactory(() => provider.getTraceServerUrlPromise()); + }) + ) + .inSingletonScope(); + bind(ConnectionHandler) + .toDynamicValue( + ctx => + new JsonRpcConnectionHandler(TRACE_SERVER_PORT, () => + ctx.container.get(PortPreferenceProxy) + ) + ) + .inSingletonScope(); + bind(ConnectionHandler) + .toDynamicValue( + ctx => + new JsonRpcConnectionHandler( + TRACE_SERVER_CONNECTION_STATUS, + client => { + const backend = ctx.container.get( + TraceServerConnectionStatusBackend + ); + backend.addClient(client); + client.onDidCloseConnection(() => { + backend.removeClient(client); + }); + return backend; + } + ) + ) + .inSingletonScope(); });