Skip to content

Commit 468e3c4

Browse files
committed
feat(LibraryNavigator) wip for adding properties and column information
Signed-off-by: Kishan Patel <[email protected]>
1 parent 7108662 commit 468e3c4

File tree

10 files changed

+492
-3
lines changed

10 files changed

+492
-3
lines changed

client/src/components/LibraryNavigator/LibraryDataProvider.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class LibraryDataProvider
6666
registerAPI("getColumns", model.fetchColumns.bind(model));
6767
registerAPI("getTables", model.getTables.bind(model));
6868
registerAPI("getLibraries", model.getLibraries.bind(model));
69+
registerAPI("getTableInfo", model.getTableInfo.bind(model));
6970
}
7071

7172
public getSubscriptions(): Disposable[] {
@@ -178,6 +179,14 @@ class LibraryDataProvider
178179
this.model.useAdapter(libraryAdapter);
179180
this._onDidChangeTreeData.fire(undefined);
180181
}
182+
183+
public async getTableInfo(item: LibraryItem) {
184+
return await this.model.getTableInfo(item);
185+
}
186+
187+
public async fetchColumns(item: LibraryItem) {
188+
return await this.model.fetchColumns(item);
189+
}
181190
}
182191

183192
export default LibraryDataProvider;

client/src/components/LibraryNavigator/LibraryModel.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,14 @@ class LibraryModel {
126126
}
127127
}
128128

129+
public async getTableInfo(item: LibraryItem) {
130+
await this.libraryAdapter.setup();
131+
if (this.libraryAdapter.getTableInfo) {
132+
return await this.libraryAdapter.getTableInfo(item);
133+
}
134+
throw new Error("Table properties not supported for this connection type");
135+
}
136+
129137
public async getChildren(item?: LibraryItem): Promise<LibraryItem[]> {
130138
if (!this.libraryAdapter) {
131139
return [];

client/src/components/LibraryNavigator/index.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import * as path from "path";
1717
import { profileConfig } from "../../commands/profile";
1818
import { Column } from "../../connection/rest/api/compute";
1919
import DataViewer from "../../panels/DataViewer";
20+
import TablePropertiesViewer from "../../panels/TablePropertiesViewer";
2021
import { WebViewManager } from "../../panels/WebviewManager";
2122
import { SubscriptionProvider } from "../SubscriptionProvider";
2223
import LibraryAdapterFactory from "./LibraryAdapterFactory";
@@ -101,6 +102,54 @@ class LibraryNavigator implements SubscriptionProvider {
101102
);
102103
},
103104
),
105+
commands.registerCommand(
106+
"SAS.showTableProperties",
107+
async (item: LibraryItem) => {
108+
try {
109+
const tableInfo = await this.libraryDataProvider.getTableInfo(item);
110+
const columns = await this.libraryDataProvider.fetchColumns(item);
111+
112+
this.webviewManager.render(
113+
new TablePropertiesViewer(
114+
this.extensionUri,
115+
item.uid,
116+
tableInfo,
117+
columns,
118+
false, // Show properties tab
119+
),
120+
`properties-${item.uid}`,
121+
);
122+
} catch (error) {
123+
window.showErrorMessage(
124+
`Failed to load table properties: ${error.message}`,
125+
);
126+
}
127+
},
128+
),
129+
commands.registerCommand(
130+
"SAS.showTableColumns",
131+
async (item: LibraryItem) => {
132+
try {
133+
const tableInfo = await this.libraryDataProvider.getTableInfo(item);
134+
const columns = await this.libraryDataProvider.fetchColumns(item);
135+
136+
this.webviewManager.render(
137+
new TablePropertiesViewer(
138+
this.extensionUri,
139+
item.uid,
140+
tableInfo,
141+
columns,
142+
true, // Show columns tab
143+
),
144+
`columns-${item.uid}`,
145+
);
146+
} catch (error) {
147+
window.showErrorMessage(
148+
`Failed to load table columns: ${error.message}`,
149+
);
150+
}
151+
},
152+
),
104153
commands.registerCommand("SAS.collapseAllLibraries", () => {
105154
commands.executeCommand(
106155
"workbench.actions.treeView.librarydataprovider.collapseAll",

client/src/components/LibraryNavigator/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
3-
import { ColumnCollection } from "../../connection/rest/api/compute";
3+
import { ColumnCollection, TableInfo } from "../../connection/rest/api/compute";
44

55
export const LibraryType = "library";
66
export const TableType = "table";
@@ -56,5 +56,6 @@ export interface LibraryAdapter {
5656
items: LibraryItem[];
5757
count: number;
5858
}>;
59+
getTableInfo?(item: LibraryItem): Promise<TableInfo>;
5960
setup(): Promise<void>;
6061
}

client/src/connection/itc/ItcLibraryAdapter.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
TableData,
1212
TableRow,
1313
} from "../../components/LibraryNavigator/types";
14-
import { ColumnCollection } from "../rest/api/compute";
14+
import { ColumnCollection, TableInfo } from "../rest/api/compute";
1515
import { getColumnIconType } from "../util";
1616
import { executeRawCode, runCode } from "./CodeRunner";
1717
import { Config } from "./types";
@@ -196,6 +196,28 @@ class ItcLibraryAdapter implements LibraryAdapter {
196196
}
197197
}
198198

199+
public async getTableInfo(item: LibraryItem): Promise<TableInfo> {
200+
const code = `
201+
$runner.GetTableInfo("${item.library}", "${item.name}")
202+
`;
203+
const output = await executeRawCode(code);
204+
const tableInfo = JSON.parse(output);
205+
206+
return {
207+
name: tableInfo.name,
208+
type: tableInfo.type,
209+
creationTimeStamp: tableInfo.creationTimeStamp,
210+
modifiedTimeStamp: tableInfo.modifiedTimeStamp,
211+
rowCount: tableInfo.rowCount,
212+
columnCount: tableInfo.columnCount,
213+
compressionRoutine: tableInfo.compressionRoutine,
214+
label: tableInfo.label,
215+
engine: tableInfo.engine,
216+
extendedType: tableInfo.extendedType,
217+
libref: tableInfo.libref,
218+
};
219+
}
220+
199221
protected async executionHandler(
200222
callback: () => Promise<string>,
201223
): Promise<string> {

client/src/connection/itc/script.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,44 @@ class SASRunner{
580580
Write-Host $(ConvertTo-Json -Depth 10 -InputObject $result -Compress)
581581
}
582582
583+
[void]GetTableInfo([string]$libname, [string]$memname) {
584+
$objRecordSet = New-Object -comobject ADODB.Recordset
585+
$objRecordSet.ActiveConnection = $this.dataConnection
586+
$query = @"
587+
select memname, memtype, crdate, modate, nobs, nvar, compress,
588+
memlabel, engine, typemem, filesize, delobs
589+
from sashelp.vtable
590+
where libname='$libname' and memname='$memname';
591+
"@
592+
$objRecordSet.Open(
593+
$query,
594+
[System.Reflection.Missing]::Value, # Use the active connection
595+
2, # adOpenDynamic
596+
1, # adLockReadOnly
597+
1 # adCmdText
598+
)
599+
600+
$result = New-Object psobject
601+
if (-not $objRecordSet.EOF) {
602+
$result | Add-Member -MemberType NoteProperty -Name "name" -Value $objRecordSet.Fields.Item(0).Value
603+
$result | Add-Member -MemberType NoteProperty -Name "type" -Value $objRecordSet.Fields.Item(1).Value
604+
$result | Add-Member -MemberType NoteProperty -Name "creationTimeStamp" -Value $objRecordSet.Fields.Item(2).Value
605+
$result | Add-Member -MemberType NoteProperty -Name "modifiedTimeStamp" -Value $objRecordSet.Fields.Item(3).Value
606+
$result | Add-Member -MemberType NoteProperty -Name "rowCount" -Value $objRecordSet.Fields.Item(4).Value
607+
$result | Add-Member -MemberType NoteProperty -Name "columnCount" -Value $objRecordSet.Fields.Item(5).Value
608+
$result | Add-Member -MemberType NoteProperty -Name "compressionRoutine" -Value $objRecordSet.Fields.Item(6).Value
609+
$result | Add-Member -MemberType NoteProperty -Name "label" -Value $objRecordSet.Fields.Item(7).Value
610+
$result | Add-Member -MemberType NoteProperty -Name "engine" -Value $objRecordSet.Fields.Item(8).Value
611+
$result | Add-Member -MemberType NoteProperty -Name "extendedType" -Value $objRecordSet.Fields.Item(9).Value
612+
$result | Add-Member -MemberType NoteProperty -Name "fileSize" -Value $objRecordSet.Fields.Item(10).Value
613+
$result | Add-Member -MemberType NoteProperty -Name "deletedObs" -Value $objRecordSet.Fields.Item(11).Value
614+
$result | Add-Member -MemberType NoteProperty -Name "libref" -Value $libname
615+
}
616+
$objRecordSet.Close()
617+
618+
Write-Host $(ConvertTo-Json -Depth 10 -InputObject $result -Compress)
619+
}
620+
583621
[void]GetTables([string]$libname) {
584622
$objRecordSet = New-Object -comobject ADODB.Recordset
585623
$objRecordSet.ActiveConnection = $this.dataConnection

client/src/connection/rest/RestLibraryAdapter.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ import {
99
TableData,
1010
} from "../../components/LibraryNavigator/types";
1111
import { appendSessionLogFn } from "../../components/logViewer";
12-
import { ColumnCollection, DataAccessApi, RowCollection } from "./api/compute";
12+
import {
13+
ColumnCollection,
14+
DataAccessApi,
15+
RowCollection,
16+
TableInfo,
17+
} from "./api/compute";
1318
import { getApiConfig } from "./common";
1419

1520
const requestOptions = {
@@ -137,6 +142,23 @@ class RestLibraryAdapter implements LibraryAdapter {
137142
return { rowCount: response.data.rowCount, maxNumberOfRowsToRead: 1000 };
138143
}
139144

145+
public async getTableInfo(item: LibraryItem): Promise<TableInfo> {
146+
await this.setup();
147+
const response = await this.retryOnFail(
148+
async () =>
149+
await this.dataAccessApi.getTable(
150+
{
151+
sessionId: this.sessionId,
152+
libref: item.library || "",
153+
tableName: item.name,
154+
},
155+
{ headers: { Accept: "application/json" } },
156+
),
157+
);
158+
159+
return response.data;
160+
}
161+
140162
private async retryOnFail<T>(
141163
callbackFn: () => Promise<AxiosResponse<T>>,
142164
): Promise<AxiosResponse<T>> {

0 commit comments

Comments
 (0)