Skip to content

Commit

Permalink
Implemented external API
Browse files Browse the repository at this point in the history
This commit provides an external API that can be used by other
extensions to communicate with the base trace extension. The
current API provides a way to retrieve the active experiment as well as
fire events and register callbacks. This is the initial API and will be
further expanded in future commits.

Fixes #140

Signed-off-by: Neel Gondalia <[email protected]>
  • Loading branch information
ngondalia committed Jul 14, 2023
1 parent f029c45 commit e289bd1
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 2 deletions.
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,67 @@ yarn playwright test --retries <retries>
[vscode-messages]: https://code.visualstudio.com/api/extension-guides/webview#passing-messages-from-an-extension-to-a-webview
[vscode-webview]: https://github.com/rebornix/vscode-webview-react
[vscode-webview-react]: https://github.com/rebornix/vscode-webview-react

## Using the External API

VSCode Trace Extension provides an external API that adopter extensions can rely on for communication. Currently the API is limited to the following:

```javascript
getActiveExperiment(): Experiment | undefined
getActiveWebviewPanels(): { [key: string]: TraceViewerPanel | undefined; }
getActiveWebviews(): vscode.WebviewView[]
onWebviewCreated(listener: (data: vscode.WebviewView) => void): void
onWebviewPanelCreated(listener: (data: vscode.WebviewPanel) => void): void
```

### Using the API from Adopter Extensions

```javascript
//The following retrieves the API object from the vscode-trace-extension
const ext = vscode.extensions.getExtension("tracecompass-community.vscode-trace-extension");
const importedApi = ext.exports;
```

Once you have the API object, you can proceed to make API calls. For example, if you wish to retrieve the active experiment in the Trace Viewer, the following API call can be used:

```javascript
const experiment = importedApi.getActiveExperiment();
```

The API provides getters to retrieve the active webviews and panels. This can be useful for scenarios when webviews/panels were created before the adopter extension was activated but the adopter extension still wants to handle messages from them.

```javascript
for (const webview of importedApi.getActiveWebviews()) {
webview.webview.onDidReceiveMessage((message) => {
switch (message.command) {
case "webviewReady":
console.log("From adopter extension - webviewReady signal received");
break;
default:
break;
}
});
}
```

The API also provides a way to attach a listener for when webview or webview panel is created. Note that this listener will not be called for webviews and panels created before the registration of the listener. It is recommended to register the listeners during the activation of the adopter extensions.

```javascript
importedApi.onWebviewPanelCreated(_panel => {
// For newly created panel, handle messages from webviews
_panel.webview.onDidReceiveMessage((message) => {
switch (message.command) {
case "webviewReady":
console.log("From adopter extension - webviewReady signal received");
break;
default:
break;
}
});
_panel.onDidDispose(() => {
console.log("panel disposed");
});
});
```

As a general rule, adopter extensions should retrieve and handle the webviews and webview panels once during their activation by calling `getActiveWebviews` and `getActiveWebviewPanels`. This ensures that the webviews and panels created before the activation of the adopter extension are handled. To handle any new webviews and panels created afterwards, listeners can be registered by calling `onWebviewCreated` and `onWebviewPanelCreated`.
7 changes: 6 additions & 1 deletion vscode-trace-extension/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ import { fileHandler, openOverviewHandler, resetZoomHandler, undoRedoHandler, zo
import { TraceServerConnectionStatusService } from './utils/trace-server-status';
import { getTspClientUrl, updateTspClient } from './utils/tspClient';
import { TraceExtensionLogger } from './utils/trace-extension-logger';
import { ExternalAPI, traceExtensionAPI} from './external-api/external-api';
import { TraceExtensionWebviewManager } from './utils/trace-extension-webview-manager';
import { VSCODE_MESSAGES } from 'vscode-trace-common/lib/messages/vscode-message-manager';
import { TraceViewerPanel } from './trace-viewer-panel/trace-viewer-webview-panel';
import { TspClientProvider } from 'vscode-trace-common/lib/client/tsp-client-provider-impl';

export let traceLogger: TraceExtensionLogger;
export const traceExtensionWebviewManager: TraceExtensionWebviewManager = new TraceExtensionWebviewManager();
const tspClientProvider = new TspClientProvider(getTspClientUrl(), undefined);

export function activate(context: vscode.ExtensionContext): void {
export function activate(context: vscode.ExtensionContext): ExternalAPI {
traceLogger = new TraceExtensionLogger('Trace Extension');

const serverStatusBarItemPriority = 1;
Expand Down Expand Up @@ -117,10 +120,12 @@ export function activate(context: vscode.ExtensionContext): void {

vscode.commands.executeCommand('setContext', 'traceViewer.markerSetsPresent', false);
vscode.commands.executeCommand('setContext', 'traceViewer.markerCategoriesPresent', false);
return traceExtensionAPI;
}

export function deactivate(): void {
traceLogger.disposeChannel();
traceExtensionWebviewManager.dispose();
}

async function startTraceServerIfAvailable(): Promise<void> {
Expand Down
64 changes: 64 additions & 0 deletions vscode-trace-extension/src/external-api/external-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/***************************************************************************************
* Copyright (c) 2023 BlackBerry Limited and others.
*
* Licensed under the MIT license. See LICENSE file in the project root for details.
***************************************************************************************/
import { Experiment } from 'tsp-typescript-client/lib/models/experiment';
import { TraceViewerPanel } from '../trace-viewer-panel/trace-viewer-webview-panel';
import * as vscode from 'vscode';
import { traceExtensionWebviewManager } from '../extension';

export interface ExternalAPI {
getActiveExperiment(): Experiment | undefined,
getActiveWebviewPanels(): { [key: string]: TraceViewerPanel | undefined; },
getActiveWebviews(): vscode.WebviewView[],
onWebviewCreated(listener: (data: vscode.WebviewView) => void): void,
onWebviewPanelCreated(listener: (data: vscode.WebviewPanel) => void): void,
}

export const traceExtensionAPI: ExternalAPI = {
/**
* Retrieves the currently active experiment
*
* @returns Experiment if one is currently active, otherwise undefined
*/
getActiveExperiment(): Experiment | undefined {
return TraceViewerPanel.getCurrentExperiment();
},

/**
* Retrieves active trace panels
*
* @returns Key value pairs where the value is of TraceViewerPanel type if panel with that key is active, otherwise undefined
*/
getActiveWebviewPanels(): { [key: string]: TraceViewerPanel | undefined; } {
return TraceViewerPanel.activePanels;
},

/**
* Retrieves active webviews
*
* @returns List of active webviews
*/
getActiveWebviews(): vscode.WebviewView[] {
return traceExtensionWebviewManager.getAllActiveWebviews();
},

/**
* Registers an event listener for onWebviewCreated event
*
* @param listener event listener
*/
onWebviewCreated(listener: (data: vscode.WebviewView) => void): void {
traceExtensionWebviewManager.onWebviewCreated(listener);
},

/**
* Registers an event listener for onWebviewPanelCreated event
*
* @param listener event listener
*/
onWebviewPanelCreated(listener: (data: vscode.WebviewPanel) => void): void {
traceExtensionWebviewManager.onWebviewPanelCreated(listener);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { TraceServerConnectionStatusService } from '../../utils/trace-server-sta
import { getTraceServerUrl, getTspClientUrl } from '../../utils/tspClient';
import { convertSignalExperiment } from 'vscode-trace-common/lib/signals/vscode-signal-converter';
import { VSCODE_MESSAGES } from 'vscode-trace-common/lib/messages/vscode-message-manager';
import { traceExtensionWebviewManager } from 'vscode-trace-extension/src/extension';

const JSONBig = JSONBigConfig({
useNativeBigInt: true,
Expand Down Expand Up @@ -47,6 +48,7 @@ export class TraceExplorerAvailableViewsProvider implements vscode.WebviewViewPr
};

webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
traceExtensionWebviewManager.fireWebviewCreated(webviewView);

// Handle messages from the webview
webviewView.webview.onDidReceiveMessage(message => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { TraceViewerPanel } from '../../trace-viewer-panel/trace-viewer-webview-
import { TraceServerConnectionStatusService } from '../../utils/trace-server-status';
import { getTraceServerUrl, getTspClientUrl } from '../../utils/tspClient';
import { VSCODE_MESSAGES } from 'vscode-trace-common/lib/messages/vscode-message-manager';
import { traceExtensionWebviewManager } from 'vscode-trace-extension/src/extension';

const JSONBig = JSONBigConfig({
useNativeBigInt: true,
Expand All @@ -20,7 +21,7 @@ export class TraceExplorerOpenedTracesViewProvider implements vscode.WebviewView
private _disposables: vscode.Disposable[] = [];
private _selectedExperiment: Experiment | undefined;

constructor(
constructor(
private readonly _extensionUri: vscode.Uri,
private readonly _statusService: TraceServerConnectionStatusService,
) {}
Expand Down Expand Up @@ -69,6 +70,7 @@ export class TraceExplorerOpenedTracesViewProvider implements vscode.WebviewView
};

webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
traceExtensionWebviewManager.fireWebviewCreated(webviewView);

// Handle messages from the webview
webviewView.webview.onDidReceiveMessage(message => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* Licensed under the MIT license. See LICENSE file in the project root for details.
***************************************************************************************/
import * as vscode from 'vscode';
import { traceExtensionWebviewManager } from 'vscode-trace-extension/src/extension';
import { getTraceServerUrl } from 'vscode-trace-extension/src/utils/tspClient';

export class TraceExplorerItemPropertiesProvider implements vscode.WebviewViewProvider {
Expand All @@ -26,6 +27,7 @@ export class TraceExplorerItemPropertiesProvider implements vscode.WebviewViewPr
]
};
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
traceExtensionWebviewManager.fireWebviewCreated(webviewView);
}

postMessagetoWebview(_command: string, _data: unknown): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { VSCODE_MESSAGES } from 'vscode-trace-common/lib/messages/vscode-message
import { MarkerSet } from 'tsp-typescript-client/lib/models/markerset';
import JSONBigConfig from 'json-bigint';
import * as fs from 'fs';
import { traceExtensionWebviewManager } from '../extension';

const JSONBig = JSONBigConfig({
useNativeBigInt: true,
Expand Down Expand Up @@ -99,6 +100,10 @@ export class TraceViewerPanel {
TraceViewerPanel.currentPanel?.showMarkersFilter();
}

public static getCurrentExperiment(): Experiment | undefined {
return TraceViewerPanel.currentPanel?._experiment;
}

private static async saveTraceCsv(csvData: string, defaultFileName: string) {
const saveDialogOptions = {
defaultUri: vscode.workspace.workspaceFolders
Expand Down Expand Up @@ -143,6 +148,7 @@ export class TraceViewerPanel {

// Set the webview's initial html content
this._panel.webview.html = this._getHtmlForWebview();
traceExtensionWebviewManager.fireWebviewPanelCreated(this._panel);

// Listen for when the panel is disposed
// This happens when the user closes the panel or when the panel is closed programmatically
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/***************************************************************************************
* Copyright (c) 2023 BlackBerry Limited and others.
*
* Licensed under the MIT license. See LICENSE file in the project root for details.
***************************************************************************************/
import * as vscode from 'vscode';

/**
* Manages webview panels and webviews creation events
*/
export class TraceExtensionWebviewManager {
private webviews: vscode.WebviewView[] = [];
private webviewCreated: vscode.EventEmitter<vscode.WebviewView> = new vscode.EventEmitter();
private webviewPanelCreated: vscode.EventEmitter<vscode.WebviewPanel> = new vscode.EventEmitter();
private isManagerDisposed = false;

getAllActiveWebviews(): vscode.WebviewView[] {
if (!this.isDisposed()) {
return this.webviews;
}
return [];
}

fireWebviewCreated(_webview: vscode.WebviewView): void {
if (!this.isDisposed()) {
this.addWebview(_webview);
this.webviewCreated.fire(_webview);
}
}

fireWebviewPanelCreated(_webviewPanel: vscode.WebviewPanel): void {
if (!this.isDisposed()) {
this.webviewPanelCreated.fire(_webviewPanel);
}
}

onWebviewCreated(listener: (data: vscode.WebviewView) => unknown): void {
if (!this.isDisposed()) {
this.webviewCreated.event(listener);
}
}

onWebviewPanelCreated(listener: (data: vscode.WebviewPanel) => unknown): void {
if (!this.isDisposed()) {
this.webviewPanelCreated.event(listener);
}
}

dispose(): void {
if (!this.isDisposed()) {
this.webviews = [];
this.webviewCreated.dispose();
this.webviewPanelCreated.dispose();
this.isManagerDisposed = true;
}
}

isDisposed(): boolean {
return this.isManagerDisposed;
}

private addWebview(webview: vscode.WebviewView): void {
// Remove it from the array when the webview disposes
webview.onDidDispose(() => {
this.removeWebview(webview);
});
this.webviews.push(webview);
}

private removeWebview(_webview: vscode.WebviewView): void {
this.webviews.filter(webview => webview === _webview).forEach(webview => {
const index = this.webviews.indexOf(webview);
if (index !== -1) {
this.webviews.splice(index, 1);
}
});
}
}

0 comments on commit e289bd1

Please sign in to comment.