Skip to content

Commit 0baae49

Browse files
ngondaliahriday-panchasara
authored andcommittedMar 27, 2024·
Handling for Files with Open Trace Button
This commit adds a handler that manages and detects resource type dialog that should be opened when opening a trace. It provides the ability to handle one or both file and folder trace types. If both is handled, a quick pick is shown to select which type of resource dialog should be opened. Signed-off-by: Neel Gondalia <ngondalia@blackberry.com>
1 parent ac6f8a9 commit 0baae49

File tree

8 files changed

+162
-13
lines changed

8 files changed

+162
-13
lines changed
 

‎README.md

+11
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ onWebviewPanelCreated(listener: (data: vscode.WebviewPanel) => void): void
249249
onSignalManagerSignal(event: string | symbol, listener: (...args: unknown[]) => void): void;
250250
offSignalManagerSignal(event: string | symbol, listener: (...args: unknown[]) => void): void;
251251
addTraceServerContributor(contributor: TraceServerContributor): void;
252+
setHandleTraceResourceType(handleFiles: boolean, handleFolders: boolean): void;
252253
```
253254

254255
### Using the API from Adopter Extensions
@@ -337,3 +338,13 @@ const contributor: TraceServerContributor = {
337338

338339
importedApi.addTraceServerContributor(contributor);
339340
```
341+
342+
If adopter extensions want to customize the type of trace resources (File and/or Folder) that the base extension should handle, it can be set by calling `setHandleTraceResourceType`.
343+
344+
```javascript
345+
const handleTraceFiles = true;
346+
const handleTraceFolders = false;
347+
348+
//The base extension will only provide support for trace files, and not for trace folders
349+
importedApi.setHandleTraceResourceType(handleTraceFiles, handleTraceFolders);
350+
```

‎vscode-trace-extension/package.json

+3-4
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
"icon": "assets/media/dep.svg"
8686
},
8787
{
88-
"command": "openedTraces.openTraceFolder",
88+
"command": "openedTraces.openTrace",
8989
"title": "Open Trace",
9090
"icon": "$(new-folder)"
9191
},
@@ -165,7 +165,7 @@
165165
{
166166
"view": "welcome",
167167
"name": "Open Trace",
168-
"contents": "There are currently no opened traces. \n[Open Trace](command:openedTraces.openTraceFolder)",
168+
"contents": "There are currently no opened traces.\n[Open Trace](command:openedTraces.openTrace)",
169169
"when": "trace-explorer.noExperiments || !traceViewer.serverUp"
170170
},
171171
{
@@ -178,7 +178,7 @@
178178
"menus": {
179179
"view/title": [
180180
{
181-
"command": "openedTraces.openTraceFolder",
181+
"command": "openedTraces.openTrace",
182182
"when": "view == traceExplorer.openedTracesView && !trace-explorer.noExperiments",
183183
"group": "navigation"
184184
},
@@ -304,7 +304,6 @@
304304
"eslint-plugin-import": "^2.21.2",
305305
"eslint-plugin-no-null": "^1.0.2",
306306
"eslint-plugin-react": "^7.20.0",
307-
308307
"rimraf": "^2.6.3",
309308
"source-map-loader": "^1.0.2",
310309
"style-loader": "^2.0.0",

‎vscode-trace-extension/src/extension.ts

+16-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { TraceExtensionWebviewManager } from './utils/trace-extension-webview-ma
2626
import { VSCODE_MESSAGES } from 'vscode-trace-common/lib/messages/vscode-message-manager';
2727
import { TraceViewerPanel } from './trace-viewer-panel/trace-viewer-webview-panel';
2828
import { TraceServerManager } from './utils/trace-server-manager';
29+
import { ResourceType, TraceExplorerResourceTypeHandler } from './utils/trace-explorer-resource-type-handler';
2930

3031
export let traceLogger: TraceExtensionLogger;
3132
export const traceExtensionWebviewManager: TraceExtensionWebviewManager = new TraceExtensionWebviewManager();
@@ -34,6 +35,8 @@ export const traceServerManager: TraceServerManager = new TraceServerManager();
3435
export function activate(context: vscode.ExtensionContext): ExternalAPI {
3536
traceLogger = new TraceExtensionLogger('Trace Extension');
3637

38+
const resourceTypeHandler: TraceExplorerResourceTypeHandler = TraceExplorerResourceTypeHandler.getInstance();
39+
3740
const serverStatusBarItemPriority = 1;
3841
const serverStatusBarItem = vscode.window.createStatusBarItem(
3942
vscode.StatusBarAlignment.Right,
@@ -159,8 +162,19 @@ export function activate(context: vscode.ExtensionContext): ExternalAPI {
159162
);
160163

161164
context.subscriptions.push(
162-
vscode.commands.registerCommand('openedTraces.openTraceFolder', async () => {
163-
const traceUri = await openDialog();
165+
vscode.commands.registerCommand('openedTraces.openTrace', async (resourceType?: ResourceType) => {
166+
let traceUri = undefined;
167+
if (resourceType && resourceType === 'File') {
168+
traceUri = await openDialog(true);
169+
} else if (resourceType && resourceType === 'Folder') {
170+
traceUri = await openDialog(false);
171+
} else {
172+
const type: ResourceType | undefined = await resourceTypeHandler.detectOrPromptForTraceResouceType();
173+
if (!type) return;
174+
const selectFiles = type === 'File' ? true : false;
175+
traceUri = await openDialog(selectFiles);
176+
}
177+
164178
if (!traceUri) {
165179
return;
166180
}

‎vscode-trace-extension/src/external-api/external-api.ts

+13
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import * as vscode from 'vscode';
99
import { traceExtensionWebviewManager, traceServerManager } from '../extension';
1010
import { TraceServerContributor } from '../utils/trace-server-manager';
1111
import { signalManager } from 'traceviewer-base/lib/signals/signal-manager';
12+
import { TraceExplorerResourceTypeHandler } from '../utils/trace-explorer-resource-type-handler';
1213

1314
export interface ExternalAPI {
1415
getActiveExperiment(): Experiment | undefined;
@@ -19,6 +20,7 @@ export interface ExternalAPI {
1920
onSignalManagerSignal(event: string | symbol, listener: (...args: unknown[]) => void): void;
2021
offSignalManagerSignal(event: string | symbol, listener: (...args: unknown[]) => void): void;
2122
addTraceServerContributor(contributor: TraceServerContributor): void;
23+
setHandleTraceResourceType(handleFiles: boolean, handleFolders: boolean): void;
2224
}
2325

2426
export const traceExtensionAPI: ExternalAPI = {
@@ -94,5 +96,16 @@ export const traceExtensionAPI: ExternalAPI = {
9496
*/
9597
addTraceServerContributor(contributor: TraceServerContributor): void {
9698
traceServerManager.addTraceServerContributor(contributor);
99+
},
100+
101+
/**
102+
* Sets the trace resouce types that the extension can handle. Extending can set if the extension handles
103+
* trace files, trace folders or both.
104+
*
105+
* @param handleFiles sets handling of trace files
106+
* @param handleFolders sets handling of trace folders
107+
*/
108+
setHandleTraceResourceType(handleFiles: boolean, handleFolders: boolean): void {
109+
TraceExplorerResourceTypeHandler.getInstance().setHandleResourceTypes(handleFiles, handleFolders);
97110
}
98111
};

‎vscode-trace-extension/src/test/extension-test.spec.ts

+30-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { test, expect } from '@playwright/test';
1+
import { test, expect, Locator } from '@playwright/test';
22

33
test.beforeEach(async ({ page }) => {
44
await page.goto('http://localhost:3000');
@@ -16,12 +16,40 @@ test('Open Trace from Explorer', async ({ page }) => {
1616

1717
test('Open Trace from Trace Viewer', async ({ page }) => {
1818
await page.getByRole('tab', { name: 'Trace Viewer' }).locator('a').click();
19-
await page.getByLabel('Opened Traces Section').hover();
19+
20+
// Locate the welcome view button or the open traces view (when trace exists already)
21+
const index = await waitForFirstLocator([
22+
page.getByLabel('Opened Traces Section'),
23+
page.getByRole('button', { name: 'Open Trace' })
24+
]);
25+
26+
if (index === 0) {
27+
await page.getByLabel('Opened Traces Section').hover();
28+
}
29+
2030
await page.getByRole('button', { name: 'Open Trace' }).hover();
2131
await page.getByRole('button', { name: 'Open Trace' }).click();
32+
await page.getByRole('option', { name: 'Folder' }).locator('a').click();
2233
await page.getByRole('option', { name: '202-bug-hunt' }).locator('a').click();
2334
await page.getByRole('option', { name: 'cat-kernel' }).locator('a').click();
2435
await page.waitForTimeout(1000);
2536
await page.getByRole('button', { name: 'OK' }).click();
2637
await expect(page.getByRole('tab', { name: 'cat-kernel' })).toBeVisible();
2738
});
39+
40+
export async function waitForFirstLocator(locators: Locator[]): Promise<number> {
41+
// return the first promise that resolves
42+
const res = await Promise.race([
43+
...locators.map(async (locator, index): Promise<number> => {
44+
let timedOut = false;
45+
await locator.waitFor({ state: 'visible' }).catch(() => (timedOut = true));
46+
return timedOut ? -1 : index;
47+
})
48+
]);
49+
50+
// None of the locators resolved - throw error
51+
if (res === -1) {
52+
throw new Error('TimedOut: locators provided were not visible');
53+
}
54+
return res;
55+
}

‎vscode-trace-extension/src/trace-explorer/opened-traces/trace-explorer-opened-traces-webview-provider.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export class TraceExplorerOpenedTracesViewProvider extends AbstractTraceExplorer
106106
updateNoExperimentsContext();
107107
return;
108108
case VSCODE_MESSAGES.OPEN_TRACE:
109-
vscode.commands.executeCommand('openedTraces.openTraceFolder');
109+
vscode.commands.executeCommand('openedTraces.openTrace');
110110
return;
111111
case VSCODE_MESSAGES.EXPERIMENT_SELECTED: {
112112
let experiment: Experiment | undefined;

‎vscode-trace-extension/src/trace-explorer/trace-utils.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ export const zoomHandler = (hasZoomedIn: boolean): void => {
3535
TraceViewerPanel.zoomOnCurrent(hasZoomedIn);
3636
};
3737

38-
export const openDialog = async (): Promise<vscode.Uri | undefined> => {
38+
export const openDialog = async (selectFiles = false): Promise<vscode.Uri | undefined> => {
3939
const props: vscode.OpenDialogOptions = {
40-
title: 'Open Trace',
41-
canSelectFolders: true,
42-
canSelectFiles: false,
40+
title: selectFiles ? 'Open Trace File' : 'Open Trace Folder',
41+
canSelectFolders: !selectFiles,
42+
canSelectFiles: selectFiles,
4343
canSelectMany: false
4444
};
4545
let traceURI = undefined;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/***************************************************************************************
2+
* Copyright (c) 2024 BlackBerry Limited and others.
3+
*
4+
* Licensed under the MIT license. See LICENSE file in the project root for details.
5+
***************************************************************************************/
6+
import * as vscode from 'vscode';
7+
8+
export type ResourceType = 'File' | 'Folder';
9+
export type ResourceTypeQuickPickItem = vscode.QuickPickItem & { type: ResourceType };
10+
11+
export class TraceExplorerResourceTypeHandler {
12+
private static instance: TraceExplorerResourceTypeHandler;
13+
private doHandleFiles = true;
14+
private doHandleFolders = true;
15+
private quickpickItems: ResourceTypeQuickPickItem[] = [
16+
{ label: 'File', type: 'File' },
17+
{ label: 'Folder', type: 'Folder' }
18+
];
19+
20+
private constructor() {
21+
/** Empty constructor */
22+
}
23+
24+
public static getInstance(): TraceExplorerResourceTypeHandler {
25+
if (!TraceExplorerResourceTypeHandler.instance) {
26+
TraceExplorerResourceTypeHandler.instance = new TraceExplorerResourceTypeHandler();
27+
}
28+
return TraceExplorerResourceTypeHandler.instance;
29+
}
30+
31+
/**
32+
* Updates the stored value of whether files and/or folders should be handled by the extension
33+
*
34+
* @param handleFiles trace files to be handled
35+
* @param handleFolders trace folders to be handled
36+
*/
37+
setHandleResourceTypes(handleFiles: boolean | undefined, handleFolders: boolean | undefined): void {
38+
if (handleFiles !== undefined) {
39+
this.doHandleFiles = handleFiles;
40+
}
41+
if (handleFolders !== undefined) {
42+
this.doHandleFolders = handleFolders;
43+
}
44+
}
45+
46+
/**
47+
* Checks if trace folders are handled to by the extension
48+
*
49+
* @returns true if folders are handled
50+
*/
51+
handleFolders(): boolean {
52+
return this.doHandleFolders;
53+
}
54+
55+
/**
56+
* Checks if trace files are handled to by the extension
57+
* @returns true if files are handled
58+
*/
59+
handleFiles(): boolean {
60+
return this.doHandleFiles;
61+
}
62+
63+
/**
64+
* Detects what resource type should be handled by the extension based on the set values.
65+
* If both file and folder resource types are to be handled, a quick pick is prompted to let the user
66+
* decide which type should be handled.
67+
*
68+
* @returns TraceResourceType to be handled
69+
*/
70+
async detectOrPromptForTraceResouceType(): Promise<ResourceType | undefined> {
71+
// Try to figure out from context set
72+
if (this.handleFiles() && !this.handleFolders()) {
73+
return 'File';
74+
} else if (!this.handleFiles() && this.handleFolders()) {
75+
return 'Folder';
76+
} else {
77+
const selection = await vscode.window.showQuickPick(this.quickpickItems, {
78+
title: 'Select the trace resource type to open'
79+
});
80+
if (!selection) return undefined;
81+
return selection.type;
82+
}
83+
}
84+
}

0 commit comments

Comments
 (0)
Please sign in to comment.