Skip to content

Commit

Permalink
Server tree view (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
bmingles committed Aug 15, 2024
1 parent 0f9f4c2 commit 754a1d8
Show file tree
Hide file tree
Showing 15 changed files with 409 additions and 10 deletions.
35 changes: 35 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@
{
"command": "vscode-deephaven.downloadLogs",
"title": "Deephaven: Download Logs"
},
{
"command": "vscode-deephaven.createWorker",
"title": "Deephaven: Create Worker",
"icon": "$(add)"
}
],
"menus": {
Expand Down Expand Up @@ -151,6 +156,36 @@
"group": "navigation",
"when": "editorLangId == python || editorLangId == groovy"
}
],
"view/item/context": [
{
"command": "vscode-deephaven.createWorker",
"when": "view == vscode-deephaven.serverTree",
"group": "inline"
}
]
},
"viewsContainers": {
"activitybar": [
{
"id": "vscode-deephaven",
"title": "Deephaven",
"icon": "images/dh-community-on-dark-128.svg"
}
]
},
"views": {
"vscode-deephaven": [
{
"id": "vscode-deephaven.serverTree",
"name": "Servers",
"type": "tree"
},
{
"id": "vscode-deephaven.workerTree",
"name": "Workers",
"type": "tree"
}
]
}
},
Expand Down
7 changes: 7 additions & 0 deletions src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export const RUN_CODE_COMMAND = `${EXTENSION_ID}.runCode`;
export const RUN_SELECTION_COMMAND = `${EXTENSION_ID}.runSelection`;
export const SELECT_CONNECTION_COMMAND = `${EXTENSION_ID}.selectConnection`;

export const SERVER_STATUS_CHECK_INTERVAL = 3000;

export const STATUS_BAR_DISCONNECTED_TEXT = 'Deephaven: Disconnected';
export const STATUS_BAR_DISCONNECT_TEXT = 'Deephaven: Disconnect';
export const STATUS_BAR_CONNECTING_TEXT = 'Deephaven: Connecting...';
Expand All @@ -26,3 +28,8 @@ export const SERVER_LANGUAGE_SET = new Set(['python', 'groovy']) as ReadonlySet<
>;

export const TMP_DIR_ROOT = path.join(__dirname, '..', 'tmp');

export const VIEW_ID = {
serverTree: `${EXTENSION_ID}.serverTree`,
workerTree: `${EXTENSION_ID}.workerTree`,
} as const;
13 changes: 13 additions & 0 deletions src/common/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,16 @@ export interface EnterpriseConnectionConfig {
export interface Disposable {
dispose(): Promise<void>;
}

export type ServerType = 'DHC' | 'DHE';

export interface ServerState {
type: ServerType;
url: string;
isRunning?: boolean;
}

export interface WorkerState {
url: string;
consoleType: ConsoleType;
}
14 changes: 14 additions & 0 deletions src/dh/dhc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { dh as DhType } from '@deephaven/jsapi-types';
import {
downloadFromURL,
getTempDir,
hasStatusCode,
NoConsoleTypesError,
polyfillDh,
urlToDirectoryName,
Expand All @@ -16,6 +17,19 @@ export const AUTH_HANDLER_TYPE_ANONYMOUS =
export const AUTH_HANDLER_TYPE_PSK =
'io.deephaven.authentication.psk.PskAuthenticationHandler';

/**
* Check if a given server is running by checking if the `dh-core.js` file is
* accessible.
* @param serverUrl
*/
export async function isDhcServerRunning(serverUrl: string): Promise<boolean> {
try {
return await hasStatusCode(new URL('jsapi/dh-core.js', serverUrl), 200);
} catch {
return false;
}
}

/**
* Get embed widget url for a widget.
* @param serverUrl
Expand Down
17 changes: 17 additions & 0 deletions src/dh/dhe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { hasStatusCode } from '../util';

/**
* Check if a given server is running by checking if the `irisapi/irisapi.nocache.js`
* file is accessible.
* @param serverUrl
*/
export async function isDheServerRunning(serverUrl: string): Promise<boolean> {
try {
return await hasStatusCode(
new URL('irisapi/irisapi.nocache.js', serverUrl),
200
);
} catch {
return false;
}
}
4 changes: 2 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as vscode from 'vscode';
import { ExtensionController } from './services';
import { ConfigService, ExtensionController } from './services';

export function activate(context: vscode.ExtensionContext): void {
const controller = new ExtensionController(context);
const controller = new ExtensionController(context, ConfigService);

context.subscriptions.push(controller);
}
Expand Down
3 changes: 2 additions & 1 deletion src/services/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
SERVER_LANGUAGE_SET,
} from '../common';
import { InvalidConsoleTypeError, Logger } from '../util';
import type { IConfigService } from './types';

const logger = new Logger('Config');

Expand Down Expand Up @@ -74,7 +75,7 @@ function getEnterpriseServers(): EnterpriseConnectionConfig[] {
}

// eslint-disable-next-line @typescript-eslint/naming-convention
export const Config = {
export const ConfigService: IConfigService = {
getCoreServers,
getEnterpriseServers,
};
64 changes: 60 additions & 4 deletions src/services/ExtensionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
RUN_CODE_COMMAND,
RUN_SELECTION_COMMAND,
SELECT_CONNECTION_COMMAND,
SERVER_STATUS_CHECK_INTERVAL,
VIEW_ID,
} from '../common';
import {
assertDefined,
Expand All @@ -25,12 +27,17 @@ import { RunCommandCodeLensProvider } from './RunCommandCodeLensProvider';
import { DhServiceRegistry } from './DhServiceRegistry';
import { DhService } from './DhService';
import { DhcService } from './DhcService';
import { Config } from './Config';
import { ServerTreeProvider, WorkerTreeProvider } from './TreeProvider';
import { WorkerManager } from './WorkerManager';
import { IConfigService, IWorkerManager } from './types';

const logger = new Logger('ExtensionController');

export class ExtensionController implements Disposable {
constructor(private context: vscode.ExtensionContext) {
constructor(context: vscode.ExtensionContext, configService: IConfigService) {
this.context = context;
this.config = configService;

this.initializeDiagnostics();
this.initializeConfig();
this.initializeCodeLenses();
Expand All @@ -40,6 +47,7 @@ export class ExtensionController implements Disposable {
this.initializeConnectionOptions();
this.initializeConnectionStatusBarItem();
this.initializeCommands();
this.initializeWebViews();

logger.info(
'Congratulations, your extension "vscode-deephaven" is now active!'
Expand All @@ -51,9 +59,12 @@ export class ExtensionController implements Disposable {
return this.clearConnection();
}

context: vscode.ExtensionContext;
config: IConfigService;
selectedConnectionUrl: string | null = null;
selectedDhService: DhService | null = null;
dhcServiceRegistry: DhServiceRegistry<DhcService> | null = null;
workerManager: IWorkerManager | null = null;

connectionOptions: ConnectionOption[] = [];
connectStatusBarItem: vscode.StatusBarItem | null = null;
Expand Down Expand Up @@ -92,13 +103,15 @@ export class ExtensionController implements Disposable {
* Initialize connection options.
*/
initializeConnectionOptions = (): void => {
this.connectionOptions = createConnectionOptions(Config.getCoreServers());
this.connectionOptions = createConnectionOptions(
this.config.getCoreServers()
);

// Update connection options when configuration changes
vscode.workspace.onDidChangeConfiguration(
() => {
this.connectionOptions = createConnectionOptions(
Config.getCoreServers()
this.config.getCoreServers()
);
},
null,
Expand Down Expand Up @@ -211,6 +224,49 @@ export class ExtensionController implements Disposable {
this.registerCommand(SELECT_CONNECTION_COMMAND, this.onSelectConnection);
};

/**
* Register web views for the extension.
*/
initializeWebViews = (): void => {
this.workerManager = new WorkerManager(this.config);

let timeout: NodeJS.Timeout;
const checkStatuses = async (): Promise<void> => {
const start = performance.now();

await this.workerManager?.updateStatus();

// Ensure checks don't run more often than the interval
const elapsed = performance.now() - start;
const remaining = SERVER_STATUS_CHECK_INTERVAL - elapsed;
const wait = Math.max(0, remaining);

timeout = setTimeout(checkStatuses, wait);
};
checkStatuses();

function disposeCheckStatuses(): void {
clearTimeout(timeout);
}

const serversView = vscode.window.registerTreeDataProvider(
VIEW_ID.serverTree,
new ServerTreeProvider(this.workerManager)
);

const workersView = vscode.window.registerTreeDataProvider(
VIEW_ID.workerTree,
new WorkerTreeProvider(this.workerManager)
);

this.context.subscriptions.push(
this.workerManager,
{ dispose: disposeCheckStatuses },
serversView,
workersView
);
};

/*
* Clear connection data
*/
Expand Down
90 changes: 90 additions & 0 deletions src/services/TreeProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import * as vscode from 'vscode';
import { ServerState, WorkerState } from '../common';
import { IWorkerManager } from './types';

/**
* Base class for tree view data providers.
*/
export abstract class TreeProvider<T> implements vscode.TreeDataProvider<T> {
constructor(readonly workerManager: IWorkerManager) {
workerManager.onDidUpdate(() => {
this._onDidChangeTreeData.fire();
});
}

private _onDidChangeTreeData = new vscode.EventEmitter<
T | undefined | void
>();

readonly onDidChangeTreeData = this._onDidChangeTreeData.event;

abstract getTreeItem(element: T): vscode.TreeItem | Thenable<vscode.TreeItem>;

abstract getChildren(element?: T | undefined): vscode.ProviderResult<T[]>;
}

type ServerGroupState = { label: string };
type ServerNode = ServerGroupState | ServerState;

function isServerGroupState(node: ServerNode): node is ServerGroupState {
return 'label' in node;
}

/**
* Provider for the server tree view.
*/
export class ServerTreeProvider extends TreeProvider<ServerNode> {
getTreeItem(
element: ServerNode
): vscode.TreeItem | Thenable<vscode.TreeItem> {
if (isServerGroupState(element)) {
return {
label: element.label,
iconPath: new vscode.ThemeIcon('server'),
collapsibleState: vscode.TreeItemCollapsibleState.Expanded,
};
}

return {
label: new URL(element.url).host,
iconPath: new vscode.ThemeIcon(
element.isRunning === true ? 'circle-large-filled' : 'circle-slash'
),
};
}

getChildren(elementOrRoot?: ServerNode): vscode.ProviderResult<ServerNode[]> {
// Root node
if (elementOrRoot == null) {
return [{ label: 'Running' }, { label: 'Stopped' }];
}

if (isServerGroupState(elementOrRoot)) {
return this.workerManager
.getServers()
.filter(server =>
(elementOrRoot as ServerGroupState).label === 'Running'
? server.isRunning
: !server.isRunning
);
}
}
}

/**
* Provider for the worker tree view.
*/
export class WorkerTreeProvider extends TreeProvider<WorkerState> {
getTreeItem(
element: WorkerState
): vscode.TreeItem | Thenable<vscode.TreeItem> {
return {
label: new URL(element.url).host,
iconPath: new vscode.ThemeIcon('vm-connect'),
};
}

getChildren(): vscode.ProviderResult<WorkerState[]> {
return this.workerManager.getWorkers();
}
}
Loading

0 comments on commit 754a1d8

Please sign in to comment.