Skip to content

Commit

Permalink
Merge pull request #2306 from posit-dev/feature/connections-data
Browse files Browse the repository at this point in the history
Use the `contains_data` request to show the Preview table UI
  • Loading branch information
dfalbel authored Feb 27, 2024
2 parents 9cfb594 + 4ac8674 commit 5143bd2
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 59 deletions.
123 changes: 67 additions & 56 deletions extensions/positron-connections/src/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ export class ConnectionItem {
readonly name: string,
readonly client: positron.RuntimeClientInstance) {
}

async icon(): Promise<string | vscode.Uri | { light: vscode.Uri; dark: vscode.Uri }> {
return '';
}

async contains_data(): Promise<boolean> {
return false;
}
}

/**
Expand All @@ -50,7 +58,9 @@ export class ConnectionItem {
export class ConnectionItemNode extends ConnectionItem {
readonly kind: string;
readonly path: Array<{ name: string; kind: string }>;
iconPath?: string | vscode.Uri | { light: vscode.Uri; dark: vscode.Uri };
private iconPath?: string | vscode.Uri | { light: vscode.Uri; dark: vscode.Uri };
private containsData?: boolean;

constructor(readonly name: string, kind: string, path: Array<{ name: string; kind: string }>, client: positron.RuntimeClientInstance) {
super(name, client);
this.kind = kind;
Expand All @@ -72,6 +82,26 @@ export class ConnectionItemNode extends ConnectionItem {
this.iconPath = this.kind;
return this.iconPath;
}

override async contains_data() {
if (this.containsData) {
return this.containsData;
}

const response = await this.client.performRpc({ msg_type: 'contains_data_request', path: this.path }) as any;
this.containsData = response.contains_data as boolean;

return this.containsData;
}

async preview() {
if (!this.contains_data()) {
// This should never happen, as no UI is provided to preview data when the item
// does not contain data.
throw new Error('This item does not contain data');
}
await this.client.performRpc({ msg_type: 'preview_table', table: this.name, path: this.path });
}
}

/**
Expand All @@ -87,18 +117,6 @@ export class ConnectionItemDatabase extends ConnectionItemNode {
}
}

/**
* A connection item representing a table in a database
*/
export class ConnectionItemTable extends ConnectionItemNode {
/**
* Preview the table's contents
*/
preview() {
this.client.performRpc({ msg_type: 'preview_table', table: this.name, path: this.path });
}
}

/**
* A connection item representing a field in a table
*/
Expand All @@ -107,6 +125,10 @@ export class ConnectionItemField extends ConnectionItem {
super(name, client);
this.dtype = dtype;
}

override async icon() {
return 'field';
}
}

/**
Expand Down Expand Up @@ -155,16 +177,15 @@ export class ConnectionItemsProvider implements vscode.TreeDataProvider<Connecti
vscode.TreeItemCollapsibleState.Collapsed :
vscode.TreeItemCollapsibleState.None);

if (item instanceof ConnectionItemTable) {
// Set the icon for tables
treeItem.iconPath = await this.getTreeItemIcon(item);
treeItem.iconPath = await this.getTreeItemIcon(item);

if (await item.contains_data()) {
// if the item contains data, we set the contextValue enabling the UI for previewing the data
treeItem.contextValue = 'table';
} else if (item instanceof ConnectionItemNode) {
// Set the icon for other levels in the hierarchy.
treeItem.iconPath = await this.getTreeItemIcon(item);
} else if (item instanceof ConnectionItemField) {
// Set the icon for fields
treeItem.iconPath = vscode.Uri.file(path.join(this.context.extensionPath, 'media', 'field.svg'));
}

if (item instanceof ConnectionItemField) {
// shows the field datatype as the treeItem description
treeItem.description = '<' + item.dtype + '>';
}

Expand All @@ -177,7 +198,7 @@ export class ConnectionItemsProvider implements vscode.TreeDataProvider<Connecti
return treeItem;
}

async getTreeItemIcon(item: ConnectionItemNode): Promise<vscode.Uri | { light: vscode.Uri; dark: vscode.Uri }> {
async getTreeItemIcon(item: ConnectionItem): Promise<vscode.Uri | { light: vscode.Uri; dark: vscode.Uri }> {
const icon = await item.icon();

if (typeof icon === 'string') {
Expand Down Expand Up @@ -221,44 +242,34 @@ export class ConnectionItemsProvider implements vscode.TreeDataProvider<Connecti
* @param element The element to get the children for
* @returns The children of the element
*/
getChildren(element?: ConnectionItem): Thenable<ConnectionItem[]> {
async getChildren(element?: ConnectionItemNode): Promise<ConnectionItem[]> {

if (!element) {
// At the root, return the top-level connections
return this._connections;
}

// Fields don't have children
if (element instanceof ConnectionItemField) {
return Promise.resolve([]);
return [];
}

if (element) {
return new Promise((resolve, _reject) => {
if (element instanceof ConnectionItemTable) {
element.client.performRpc({ msg_type: 'fields_request', table: element.name, path: element.path }).then(
(response: any) => {
const fields = response.fields as Array<{ name: string; dtype: string }>;
const fieldItems = fields.map((field) => {
return new ConnectionItemField(field.name, field.dtype, element.client);
});
resolve(fieldItems);
}
);
} else if (element instanceof ConnectionItemNode) {
element.client.performRpc({ msg_type: 'tables_request', name: element.name, kind: element.kind, path: element.path }).then(
(response: any) => {
const objects = response.tables as Array<{ name: string; kind: string }>;
const objectItems = objects.map((obj) => {
const path = [...element.path, { name: obj.name, kind: obj.kind }];
if (obj.kind === 'table') {
return new ConnectionItemTable(obj.name, obj.kind, path, element.client);
} else {
return new ConnectionItemNode(obj.name, obj.kind, path, element.client);
}
});
resolve(objectItems);
}
);
}
// The node is a view or a table so we want to get the fields on it.
if (await element.contains_data()) {
const response = await element.client.performRpc({ msg_type: 'fields_request', table: element.name, path: element.path }) as any;
const fields = response.fields as Array<{ name: string; dtype: string }>;
return fields.map((field) => {
return new ConnectionItemField(field.name, field.dtype, element.client);
});
} else {
// At the root, return the top-level connections
return Promise.resolve(this._connections);
}

// The node is a database, schema, or catalog, so we want to get the next set of elements in
// the tree.
const response = await element.client.performRpc({ msg_type: 'tables_request', name: element.name, kind: element.kind, path: element.path }) as any;
const children = response.tables as Array<{ name: string; kind: string }>;
return children.map((obj) => {
const path = [...element.path, { name: obj.name, kind: obj.kind }];
return new ConnectionItemNode(obj.name, obj.kind, path, element.client);
});
}
}
4 changes: 2 additions & 2 deletions extensions/positron-connections/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import * as vscode from 'vscode';
import * as positron from 'positron';
import { ConnectionItemDatabase, ConnectionItemTable, ConnectionItemsProvider } from './connection';
import { ConnectionItemDatabase, ConnectionItemNode, ConnectionItemsProvider } from './connection';

/**
* Activates the extension.
Expand Down Expand Up @@ -32,7 +32,7 @@ export function activate(context: vscode.ExtensionContext) {
// Register a command to preview a table
context.subscriptions.push(
vscode.commands.registerCommand('positron.connections.previewTable',
(item: ConnectionItemTable) => {
(item: ConnectionItemNode) => {
item.preview();
}));

Expand Down
2 changes: 1 addition & 1 deletion extensions/positron-r/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@
},
"positron": {
"binaryDependencies": {
"ark": "0.1.57"
"ark": "0.1.58"
}
}
}

0 comments on commit 5143bd2

Please sign in to comment.