Skip to content

Commit 9d55dce

Browse files
danila-grobovDanilaGrobovSEBdependabot[bot]
authored
fix: Table Viewer optimization in ITC (#1466)
* Add changes from file explorer feature. * Use data connection instead of code execution. * Improve columns loading time. * Add column type mapping to the format. * DCO Remediation Commit for Danila Grobov (s4642g) <danila.grobov@seb.se> I, Danila Grobov (s4642g) <danila.grobov@seb.se>, hereby add my Signed-off-by to this commit: 176ab4b I, Danila Grobov (s4642g) <danila.grobov@seb.se>, hereby add my Signed-off-by to this commit: c12016f I, Danila Grobov (s4642g) <danila.grobov@seb.se>, hereby add my Signed-off-by to this commit: 9d1aff6 I, Danila Grobov (s4642g) <danila.grobov@seb.se>, hereby add my Signed-off-by to this commit: 328b22d Signed-off-by: Danila Grobov (s4642g) <danila.grobov@seb.se> * Support empty arrays in json Signed-off-by: Danila Grobov (s4642g) <danila.grobov@seb.se> * Fix incorrect label. * Remove unnecessary parameters in data connection DCO Remediation Commit for Danila Grobov (s4642g) <danila.grobov@seb.se> I, Danila Grobov (s4642g) <danila.grobov@seb.se>, hereby add my Signed-off-by to this commit: 2ab7999 Signed-off-by: Danila Grobov (s4642g) <danila.grobov@seb.se> * Fix tests Signed-off-by: Danila Grobov (s4642g) <danila.grobov@seb.se> * Use proxyquire for mocking uuid. Signed-off-by: Danila Grobov <danila.grob@gmail.com> * Fix linting problems. Signed-off-by: Danila Grobov <danila.grob@gmail.com> * Fix package-lock * Bring the file explorer changes back. * DCO Remediation Commit for Danila Grobov <danila.grob@gmail.com> I, Danila Grobov <danila.grob@gmail.com>, hereby add my Signed-off-by to this commit: 7e4d8a2 Signed-off-by: Danila Grobov <danila.grob@gmail.com> * DCO Remediation Commit for Danila Grobov (s4642g) <danila.grobov@seb.se> I, Danila Grobov (s4642g) <danila.grobov@seb.se>, hereby add my Signed-off-by to this commit: b5785aa Signed-off-by: Danila Grobov (s4642g) <danila.grobov@seb.se> * Fix formatting issues. Signed-off-by: Danila Grobov <danila.grob@gmail.com> * Refactor getColumnIconType. Signed-off-by: Danila Grobov <danila.grob@gmail.com> * chore(deps): bump the docusaurus group in /website with 4 updates (#1502) Bumps the docusaurus group in /website with 4 updates: [@docusaurus/preset-classic](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-preset-classic), [@docusaurus/theme-mermaid](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-theme-mermaid), [@docusaurus/module-type-aliases](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases) and [@docusaurus/tsconfig](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-tsconfig). Updates `@docusaurus/preset-classic` from 3.7.0 to 3.8.0 - [Release notes](https://github.com/facebook/docusaurus/releases) - [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/docusaurus/commits/v3.8.0/packages/docusaurus-preset-classic) Updates `@docusaurus/theme-mermaid` from 3.7.0 to 3.8.0 - [Release notes](https://github.com/facebook/docusaurus/releases) - [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/docusaurus/commits/v3.8.0/packages/docusaurus-theme-mermaid) Updates `@docusaurus/module-type-aliases` from 3.7.0 to 3.8.0 - [Release notes](https://github.com/facebook/docusaurus/releases) - [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/docusaurus/commits/v3.8.0/packages/docusaurus-module-type-aliases) Updates `@docusaurus/tsconfig` from 3.7.0 to 3.8.0 - [Release notes](https://github.com/facebook/docusaurus/releases) - [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/docusaurus/commits/v3.8.0/packages/docusaurus-tsconfig) --- updated-dependencies: - dependency-name: "@docusaurus/preset-classic" dependency-version: 3.8.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: docusaurus - dependency-name: "@docusaurus/theme-mermaid" dependency-version: 3.8.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: docusaurus - dependency-name: "@docusaurus/module-type-aliases" dependency-version: 3.8.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: docusaurus - dependency-name: "@docusaurus/tsconfig" dependency-version: 3.8.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: docusaurus ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump the dev group across 1 directory with 4 updates (#1504) Bumps the dev group with 3 updates in the / directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) and [eslint](https://github.com/eslint/eslint). Updates `@types/node` from 22.15.19 to 22.15.29 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `@typescript-eslint/eslint-plugin` from 8.32.1 to 8.33.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.33.0/packages/eslint-plugin) Updates `@typescript-eslint/parser` from 8.32.1 to 8.33.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.33.0/packages/parser) Updates `eslint` from 9.27.0 to 9.28.0 - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md) - [Commits](eslint/eslint@v9.27.0...v9.28.0) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 22.15.29 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev - dependency-name: "@typescript-eslint/eslint-plugin" dependency-version: 8.33.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev - dependency-name: "@typescript-eslint/parser" dependency-version: 8.33.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev - dependency-name: eslint dependency-version: 9.28.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix formatting. Signed-off-by: Danila Grobov <danila.grob@gmail.com> --------- Signed-off-by: Danila Grobov (s4642g) <danila.grobov@seb.se> Signed-off-by: Danila Grobov <danila.grob@gmail.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Danila Grobov (s4642g) <danila.grobov@seb.se> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1 parent 726bcf2 commit 9d55dce

File tree

7 files changed

+324
-92
lines changed

7 files changed

+324
-92
lines changed

client/src/connection/itc/ItcLibraryAdapter.ts

Lines changed: 30 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import {
1111
TableData,
1212
TableRow,
1313
} from "../../components/LibraryNavigator/types";
14-
import { Column, ColumnCollection } from "../rest/api/compute";
15-
import { runCode } from "./CodeRunner";
14+
import { ColumnCollection } from "../rest/api/compute";
15+
import { getColumnIconType } from "../util";
16+
import { executeRawCode, runCode } from "./CodeRunner";
1617
import { Config } from "./types";
1718

1819
class ItcLibraryAdapter implements LibraryAdapter {
@@ -45,31 +46,14 @@ class ItcLibraryAdapter implements LibraryAdapter {
4546
}
4647

4748
public async getColumns(item: LibraryItem): Promise<ColumnCollection> {
48-
const sql = `
49-
%let OUTPUT;
50-
proc sql;
51-
select catx(',', name, type, varnum) as column into: OUTPUT separated by '~'
52-
from sashelp.vcolumn
53-
where libname='${item.library}' and memname='${item.name}'
54-
order by varnum;
55-
quit;
56-
%put <COLOUTPUT> &OUTPUT; %put </COLOUTPUT>;
49+
const code = `
50+
$runner.GetColumns("${item.library}", "${item.name}")
5751
`;
58-
59-
const columnLines = processQueryRows(
60-
await this.runCode(sql, "<COLOUTPUT>", "</COLOUTPUT>"),
61-
);
62-
63-
const columns = columnLines.map((lineText): Column => {
64-
const [name, type, index] = lineText.split(",");
65-
66-
return {
67-
name,
68-
type,
69-
index: parseInt(index, 10),
70-
};
71-
});
72-
52+
const output = await executeRawCode(code);
53+
const columns = JSON.parse(output).map((column) => ({
54+
...column,
55+
type: getColumnIconType(column),
56+
}));
7357
return {
7458
items: columns,
7559
count: -1,
@@ -213,48 +197,16 @@ class ItcLibraryAdapter implements LibraryAdapter {
213197
start: number,
214198
limit: number,
215199
): Promise<{ rows: Array<string[]>; count: number }> {
216-
const maxTableNameLength = 32;
217-
const tempTable = `${item.name}${hms()}${start}`.substring(
218-
0,
219-
maxTableNameLength,
220-
);
200+
const fullTableName = `${item.library}.${item.name}`;
221201
const code = `
222-
options nonotes nosource nodate nonumber;
223-
%let COUNT;
224-
proc sql;
225-
SELECT COUNT(1) into: COUNT FROM ${item.library}.${item.name};
226-
quit;
227-
data work.${tempTable};
228-
set ${item.library}.${item.name};
229-
if ${start + 1} <= _N_ <= ${start + limit} then output;
230-
run;
231-
232-
filename out temp;
233-
proc json nokeys out=out pretty; export work.${tempTable}; run;
234-
235-
%put <TABLEDATA>;
236-
%put <Count>&COUNT</Count>;
237-
data _null_; infile out; input; put _infile_; run;
238-
%put </TABLEDATA>;
239-
proc datasets library=work nolist nodetails; delete ${tempTable}; run;
240-
options notes source date number;
202+
$runner.GetDatasetRecords("${fullTableName}", ${start}, ${limit})
241203
`;
242-
243-
let output = await this.runCode(code, "<TABLEDATA>", "</TABLEDATA>");
244-
245-
// Extract result count
246-
const countRegex = /<Count>(.*)<\/Count>/;
247-
const countMatches = output.match(countRegex);
248-
const count = parseInt(countMatches[1].replace(/\s|\n/gm, ""), 10);
249-
output = output.replace(countRegex, "");
250-
251-
const rows = output.replace(/\n|\t/gm, "").slice(output.indexOf("{"));
204+
const output = await executeRawCode(code);
252205
try {
253-
const tableData = JSON.parse(rows);
254-
return { rows: tableData[`SASTableData+${tempTable}`], count };
206+
return JSON.parse(output);
255207
} catch (e) {
256208
console.warn("Failed to load table data with error", e);
257-
console.warn("Raw output", rows);
209+
console.warn("Raw output", output);
258210
throw new Error(
259211
l10n.t(
260212
"An error was encountered when loading table data. This usually happens when a table is too large or the data couldn't be processed. See console for more details.",
@@ -263,18 +215,28 @@ class ItcLibraryAdapter implements LibraryAdapter {
263215
}
264216
}
265217

266-
protected async runCode(
267-
code: string,
268-
startTag: string = "",
269-
endTag: string = "",
218+
protected async executionHandler(
219+
callback: () => Promise<string>,
270220
): Promise<string> {
271221
try {
272-
return await runCode(code, startTag, endTag);
222+
return await callback();
273223
} catch (e) {
274224
onRunError(e);
275225
return "";
276226
}
277227
}
228+
229+
protected async runCode(
230+
code: string,
231+
startTag: string = "",
232+
endTag: string = "",
233+
): Promise<string> {
234+
return this.executionHandler(() => runCode(code, startTag, endTag));
235+
}
236+
237+
protected async executeRawCode(code: string): Promise<string> {
238+
return this.executionHandler(() => executeRawCode(code));
239+
}
278240
}
279241

280242
const processQueryRows = (response: string): string[] => {
@@ -288,9 +250,4 @@ const processQueryRows = (response: string): string[] => {
288250
.filter((value, index, array) => array.indexOf(value) === index);
289251
};
290252

291-
const hms = () => {
292-
const date = new Date();
293-
return `${date.getHours()}${date.getMinutes()}${date.getSeconds()}`;
294-
};
295-
296253
export default ItcLibraryAdapter;

client/src/connection/itc/script.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type ScriptProperties = {
1515
export const getScript = ({
1616
interopLibraryFolderPath = "",
1717
}: ScriptProperties) => `
18+
using namespace System.Collections.Generic
1819
function GetInteropDirectory {
1920
# try to load from user specified path first
2021
if ("${interopLibraryFolderPath}") {
@@ -51,6 +52,8 @@ try {
5152
5253
class SASRunner{
5354
[System.__ComObject] $objSAS
55+
[System.__ComObject] $objKeeper
56+
[System.__ComObject] $dataConnection
5457
5558
[void]ResolveSystemVars(){
5659
try {
@@ -89,6 +92,16 @@ class SASRunner{
8992
$password
9093
)
9194
95+
$this.objKeeper = New-Object -ComObject SASObjectManager.ObjectKeeper
96+
$this.objKeeper.AddObject(1, "WorkspaceObject", $this.objSAS)
97+
98+
$this.dataConnection = New-Object -comobject ADODB.Connection
99+
$this.dataConnection.Provider = "sas.IOMProvider"
100+
$this.dataConnection.Properties("Data Source") = (
101+
"iom-id://" + $this.objSAS.UniqueIdentifier
102+
)
103+
$this.dataConnection.Open()
104+
92105
Write-Host "${LineCodes.SessionCreatedCode}"
93106
} catch {
94107
Write-Error "${ERROR_START_TAG}Setup error: $_${ERROR_END_TAG}"
@@ -151,6 +164,8 @@ class SASRunner{
151164
152165
[void]Close(){
153166
try{
167+
$this.dataConnection.Close()
168+
$this.objKeeper.RemoveObject($this.objSAS)
154169
$this.objSAS.Close()
155170
}catch{
156171
Write-Error "${ERROR_START_TAG}Close error: $_${ERROR_END_TAG}"
@@ -228,6 +243,87 @@ class SASRunner{
228243
229244
Write-Host "${LineCodes.ResultsFetchedCode}"
230245
}
246+
247+
[void]GetDatasetRecords([string]$tableName, [int]$start = 0, [int]$limit = 100) {
248+
$objRecordSet = New-Object -comobject ADODB.Recordset
249+
$objRecordSet.ActiveConnection = $this.dataConnection # This is needed to set the properties for sas formats.
250+
$objRecordSet.Properties.Item("SAS Formats").Value = "_ALL_"
251+
252+
$objRecordSet.Open(
253+
$tableName,
254+
[System.Reflection.Missing]::Value, # Use the active connection
255+
2, # adOpenDynamic
256+
1, # adLockReadOnly
257+
512 # adCmdTableDirect
258+
)
259+
260+
$records = [List[List[object]]]::new()
261+
$fields = $objRecordSet.Fields.Count
262+
263+
if ($objRecordSet.EOF) {
264+
Write-Host '{"rows": [], "count": 0}'
265+
return
266+
}
267+
268+
$objRecordSet.AbsolutePosition = $start + 1
269+
270+
for ($j = 0; $j -lt $limit -and $objRecordSet.EOF -eq $False; $j++) {
271+
$cell = [List[object]]::new()
272+
for ($i = 0; $i -lt $fields; $i++) {
273+
$cell.Add($objRecordSet.Fields.Item($i).Value)
274+
}
275+
$records.Add($cell)
276+
$objRecordSet.MoveNext()
277+
}
278+
$objRecordSet.Close()
279+
280+
$objRecordSet.Open(
281+
"SELECT COUNT(1) FROM $tableName",
282+
$this.dataConnection, 3, 1, 1
283+
) # adOpenStatic, adLockReadOnly, adCmdText
284+
$count = $objRecordSet.Fields.Item(0).Value
285+
$objRecordSet.Close()
286+
287+
$result = New-Object psobject
288+
$result | Add-Member -MemberType NoteProperty -Name "rows" -Value $records
289+
$result | Add-Member -MemberType NoteProperty -Name "count" -Value $count
290+
291+
Write-Host $(ConvertTo-Json -Depth 10 -InputObject $result -Compress)
292+
}
293+
294+
[void]GetColumns([string]$libname, [string]$memname) {
295+
$objRecordSet = New-Object -comobject ADODB.Recordset
296+
$objRecordSet.ActiveConnection = $this.dataConnection
297+
$query = @"
298+
select name, type, format
299+
from sashelp.vcolumn
300+
where libname='$libname' and memname='$memname';
301+
"@
302+
$objRecordSet.Open(
303+
$query,
304+
[System.Reflection.Missing]::Value, # Use the active connection
305+
2, # adOpenDynamic
306+
1, # adLockReadOnly
307+
1 # adCmdText
308+
)
309+
310+
$rows = $objRecordSet.GetRows()
311+
312+
$objRecordSet.Close()
313+
314+
$parsedRows = @()
315+
for ($i = 0; $i -lt $rows.GetLength(1); $i++) {
316+
$parsedRow = [PSCustomObject]@{
317+
index = $i + 1
318+
name = $rows[0, $i]
319+
type = $rows[1, $i]
320+
format = $rows[2, $i]
321+
}
322+
$parsedRows += $parsedRow
323+
}
324+
325+
Write-Host $(ConvertTo-Json -Depth 10 -InputObject $parsedRows -Compress)
326+
}
231327
232328
[void]DeleteItemAtPath([string]$filePath,[bool]$recursive) {
233329
if ($recursive) {

client/src/connection/util.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,76 @@ export const extractTextBetweenTags = (
2424
.replace(/\n$/, "")
2525
: text;
2626
};
27+
28+
export const getColumnIconType = ({
29+
type,
30+
format,
31+
}: {
32+
index: number;
33+
type: string;
34+
name: string;
35+
format: string;
36+
}) => {
37+
format = format.toUpperCase();
38+
39+
const isDateFormat = () =>
40+
[
41+
"DAT",
42+
"MM",
43+
"DD",
44+
"YY",
45+
"EURDF",
46+
"JUL",
47+
"YEAR",
48+
"DAY",
49+
"MONTH",
50+
"MON",
51+
"DOWNAME",
52+
].some((f) => format.includes(f)) &&
53+
![
54+
"TIME",
55+
"HH",
56+
"SS",
57+
"COMM",
58+
"DATEAMPM",
59+
"DATETIME",
60+
"NLDATMTM",
61+
"NLDATM",
62+
"NLDATMAP",
63+
"NLDATMW",
64+
].some((f) => format.includes(f));
65+
66+
const isTimeFormat = () =>
67+
["TIME", "TIMAP", "HOUR", "HH", "MM", "SS", "NLDATMTM"].some((f) =>
68+
format.includes(f),
69+
) && !["DATEAMPM", "DATETIME", "COMMA"].some((f) => format.includes(f));
70+
71+
const isDateTimeFormat = () =>
72+
["DATEAMPM", "DATETIME", "NLDATM", "NLDATMAP", "NLDATMW"].some((f) =>
73+
format.includes(f),
74+
);
75+
76+
const isCurrencyFormat = () =>
77+
["NLMNI", "NLMNL", "NLMNY", "YEN", "DOLLAR", "EURO"].some((f) =>
78+
format.includes(f),
79+
);
80+
81+
if (type !== "num") {
82+
return type;
83+
}
84+
85+
if (isDateFormat()) {
86+
return "date";
87+
}
88+
if (isTimeFormat()) {
89+
return "time";
90+
}
91+
if (isDateTimeFormat()) {
92+
return "datetime";
93+
}
94+
if (isCurrencyFormat()) {
95+
return "currency";
96+
}
97+
98+
return type;
99+
};

client/test/connection/itc/Coderunner.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { runCode } from "../../../src/connection/itc/CodeRunner";
66
import { Session } from "../../../src/connection/session";
77

88
export class MockSession extends Session {
9-
private _logFn;
9+
protected _logFn;
1010
private _runMap: Record<string, string> | undefined;
1111
public sasSystemLine = "The Sas System";
1212

0 commit comments

Comments
 (0)