Skip to content

Commit ab050ec

Browse files
Merge pull request #496 from KxSystems/KXI-33288
[KXI-33288] Fix UX for huge data and other fixes
2 parents 63883d7 + 3eb566e commit ab050ec

File tree

7 files changed

+227
-154
lines changed

7 files changed

+227
-154
lines changed

.vscode/launch.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
"name": "Run Extension",
1010
"type": "extensionHost",
1111
"request": "launch",
12-
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
12+
"args": [
13+
"--disable-extensions",
14+
"--extensionDevelopmentPath=${workspaceFolder}"
15+
],
1316
"outFiles": ["${workspaceFolder}/out/extension.js*"],
1417
"preLaunchTask": "npm: watch"
1518
},

src/classes/localConnection.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -195,15 +195,17 @@ export class LocalConnection {
195195
this.isError = true;
196196
result = handleQueryResults(err.toString(), QueryResultType.Error);
197197
}
198-
if (res.errored) {
199-
this.isError = true;
200-
result = handleQueryResults(
201-
res.error + (res.backtrace ? "\n" + res.backtrace : ""),
202-
QueryResultType.Error,
203-
);
204-
} else {
205-
result = res.result === null ? "" : res.result;
206-
base64 = res.base64 || false;
198+
if (res) {
199+
if (res.errored) {
200+
this.isError = true;
201+
result = handleQueryResults(
202+
res.error + (res.backtrace ? "\n" + res.backtrace : ""),
203+
QueryResultType.Error,
204+
);
205+
} else {
206+
result = res.result === null ? "" : res.result;
207+
base64 = res.base64 || false;
208+
}
207209
}
208210
});
209211

@@ -215,6 +217,11 @@ export class LocalConnection {
215217

216218
this.updateGlobal();
217219

220+
if (this.isError) {
221+
this.isError = false;
222+
return result;
223+
}
224+
218225
if (base64) {
219226
return { base64, result };
220227
}
@@ -224,10 +231,6 @@ export class LocalConnection {
224231
}
225232

226233
if (ext.isResultsTabVisible && stringify) {
227-
if (this.isError) {
228-
this.isError = false;
229-
return result;
230-
}
231234
return convertStringToArray(result ? result : "");
232235
}
233236

src/commands/serverCommand.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1226,13 +1226,21 @@ export async function writeQueryResultsToView(
12261226
connVersion,
12271227
isPython,
12281228
);
1229+
let isSuccess = true;
1230+
12291231
if (!checkIfIsDatasource(type)) {
1232+
if (typeof result === "string") {
1233+
const res = decodeQUTF(result);
1234+
if (res.startsWith(queryConstants.error)) {
1235+
isSuccess = false;
1236+
}
1237+
}
12301238
addQueryHistory(
12311239
query,
12321240
executorName,
12331241
connLabel,
12341242
isInsights ? ServerType.INSIGHTS : ServerType.KDB,
1235-
true,
1243+
isSuccess,
12361244
isPython,
12371245
type === "WORKBOOK",
12381246
undefined,

src/services/resultsPanelProvider.ts

Lines changed: 124 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,6 @@ export class KdbResultsViewProvider implements WebviewViewProvider {
5151
guid: "text",
5252
byte: "number",
5353
short: "number",
54-
int: "number",
55-
long: "number",
5654
real: "number",
5755
float: "number",
5856
char: "text",
@@ -199,7 +197,9 @@ export class KdbResultsViewProvider implements WebviewViewProvider {
199197
() => ({}),
200198
);
201199

202-
columns.forEach((column) => {
200+
const columnsArray = Array.isArray(columns) ? columns : [columns];
201+
202+
columnsArray.forEach((column) => {
203203
const { name, values } = column;
204204
values.forEach((value, index) => {
205205
rowData[index][name] = decodeQUTF(value);
@@ -212,7 +212,9 @@ export class KdbResultsViewProvider implements WebviewViewProvider {
212212
updatedExtractColumnDefs(results: StructuredTextResults) {
213213
const { columns } = results;
214214

215-
const columnDefs = columns.map((column) => {
215+
const columnsArray = Array.isArray(columns) ? columns : [columns];
216+
217+
const columnDefs = columnsArray.map((column) => {
216218
const sanitizedKey = this.sanitizeString(column.name);
217219
const cellDataType = this.kdbToAgGridCellType(column.type);
218220
const headerName = column.type
@@ -303,24 +305,8 @@ export class KdbResultsViewProvider implements WebviewViewProvider {
303305
}
304306

305307
const gridOptions: GridOptions = {
306-
defaultColDef: {
307-
sortable: true,
308-
resizable: true,
309-
filter: true,
310-
flex: 1,
311-
minWidth: 100,
312-
},
313308
rowData: rowData,
314309
columnDefs: columnDefs,
315-
domLayout: "autoHeight",
316-
pagination: true,
317-
paginationPageSize: 100,
318-
enableCellTextSelection: true,
319-
ensureDomOrder: true,
320-
suppressContextMenu: true,
321-
suppressDragLeaveHidesColumns: true,
322-
tooltipShowDelay: 200,
323-
loading: true,
324310
};
325311

326312
return gridOptions;
@@ -362,17 +348,16 @@ export class KdbResultsViewProvider implements WebviewViewProvider {
362348
this._results = queryResult;
363349
let result = "";
364350
let gridOptions = undefined;
351+
365352
if (!this._view) {
366353
kdbOutputLog("[Results Tab] No view to update", "ERROR");
367354
return;
368355
}
356+
357+
this._view.webview.postMessage({ command: "loading" });
358+
369359
if (typeof queryResult === "string" || typeof queryResult === "number") {
370-
result =
371-
queryResult !== ""
372-
? `<p class="results-txt">${queryResult
373-
.toString()
374-
.replace(/\n/g, "<br/>")}</p>`
375-
: "<p>No results to show</p>";
360+
result = this.formatResult(queryResult);
376361
} else if (queryResult) {
377362
gridOptions = this.convertToGrid(
378363
queryResult,
@@ -381,16 +366,33 @@ export class KdbResultsViewProvider implements WebviewViewProvider {
381366
this.isPython,
382367
);
383368
}
384-
if (gridOptions) {
385-
this._view.webview.postMessage({
386-
command: "setGridOptions",
387-
gridOptions: gridOptions,
388-
});
389-
} else {
390-
this._view.webview.postMessage({
391-
command: "setResultsContent",
392-
results: result,
393-
});
369+
370+
this.postMessageToWebview(gridOptions, result);
371+
}
372+
373+
private formatResult(queryResult: string | number): string {
374+
return queryResult !== ""
375+
? `<p class="results-txt">${queryResult.toString().replace(/\n/g, "<br/>")}</p>`
376+
: "<p>No results to show</p>";
377+
}
378+
379+
private postMessageToWebview(
380+
gridOptions: GridOptions | undefined,
381+
result: string,
382+
) {
383+
if (this._view) {
384+
if (gridOptions) {
385+
this._view.webview.postMessage({
386+
command: "setGridDatasource",
387+
results: gridOptions.rowData,
388+
columnDefs: gridOptions.columnDefs,
389+
});
390+
} else {
391+
this._view.webview.postMessage({
392+
command: "setResultsContent",
393+
results: result,
394+
});
395+
}
394396
}
395397
}
396398

@@ -420,16 +422,33 @@ export class KdbResultsViewProvider implements WebviewViewProvider {
420422
"ag-grid-community.min.js",
421423
)}"></script>
422424
</head>
423-
<body>
425+
<body>
424426
<div id="results" class="results-view-container">
425427
<div class="content-wrapper"></div>
426-
</div>
428+
</div>
429+
<div id="overlay" class="overlay">
430+
<div class="loading-box">
431+
<div class="spinner"></div>
432+
<div class="loading-text">Loading data...</div>
433+
</div>
434+
</div>
427435
<script type="module" nonce="${nonce}" src="${webviewUri}"></script>
428436
<div id="grid" style="height: 100%; width:100%;" class="${agGridTheme}"></div>
429-
<script nonce="${nonce}" >
437+
<script nonce="${nonce}" >
430438
const vscode = acquireVsCodeApi();
439+
const gridDiv = document.getElementById('grid');
440+
const resultsDiv = document.querySelector('#results .content-wrapper');
441+
const overlay = document.getElementById('overlay');
431442
let gridApi;
432443
444+
function showOverlay() {
445+
overlay.style.display = 'flex';
446+
}
447+
448+
function hideOverlay() {
449+
overlay.style.display = 'none';
450+
}
451+
433452
function saveColumnWidths() {
434453
if (!gridApi) {return null};
435454
return gridApi.getColumnState();
@@ -442,30 +461,78 @@ export class KdbResultsViewProvider implements WebviewViewProvider {
442461
443462
window.addEventListener('message', event => {
444463
const message = event.data;
445-
console.log(event)
446-
if (message.command === 'setGridOptions') {
464+
showOverlay();
465+
466+
const handleSetGridDatasource = () => {
447467
const columnWidths = saveColumnWidths();
448-
const gridOptions = message.gridOptions;
449-
const gridDiv = document.getElementById('grid');
450-
const resultsDiv = document.querySelector('#results .content-wrapper');
451-
resultsDiv.innerHTML = '';
452-
gridDiv.innerHTML = '';
453-
const rowData = gridOptions.rowData;
454-
gridOptions.rowData = [];
468+
const gridOptions = {
469+
defaultColDef: {
470+
sortable: true,
471+
resizable: true,
472+
filter: true,
473+
flex: 1,
474+
minWidth: 100,
475+
},
476+
columnDefs: message.columnDefs,
477+
domLayout: "autoHeight",
478+
pagination: true,
479+
enableCellTextSelection: true,
480+
ensureDomOrder: true,
481+
suppressContextMenu: true,
482+
suppressDragLeaveHidesColumns: true,
483+
tooltipShowDelay: 200,
484+
rowBuffer: 0,
485+
rowModelType: "infinite",
486+
cacheBlockSize: 100,
487+
cacheOverflowSize: 2,
488+
maxConcurrentDatasourceRequests: 1,
489+
infiniteInitialRowCount: 10000,
490+
maxBlocksInCache: 10,
491+
datasource: {
492+
rowCount: undefined,
493+
getRows: function(params) {
494+
showOverlay();
495+
const results = message.results;
496+
setTimeout(() => {
497+
const lastRow = results.length;
498+
const rowsThisPage = results.slice(params.startRow, params.endRow);
499+
params.successCallback(rowsThisPage, lastRow);
500+
hideOverlay();
501+
}, 500);
502+
}
503+
}
504+
};
505+
resultsDiv.innerHTML = '';
506+
gridDiv.innerHTML = '';
455507
gridApi = agGrid.createGrid(gridDiv, gridOptions);
456508
restoreColumnWidths(columnWidths);
457-
setTimeout(() => {
458-
gridApi.setGridOption("rowData", rowData);
459-
gridApi.setGridOption("loading", false);
460-
}, 500);
461509
document.getElementById("results").scrollIntoView();
462-
} else if (message.command === 'setResultsContent') {
510+
};
511+
512+
const handleSetResultsContent = () => {
463513
const resultsContent = message.results;
464-
const resultsDiv = document.querySelector('#results .content-wrapper');
465-
const gridDiv = document.getElementById('grid');
466-
gridDiv.innerHTML = '';
467-
resultsDiv.innerHTML = '';
514+
gridDiv.innerHTML = '';
515+
resultsDiv.innerHTML = '';
468516
resultsDiv.innerHTML = resultsContent;
517+
hideOverlay();
518+
};
519+
520+
const handleLoading = () => {
521+
gridDiv.innerHTML = '';
522+
resultsDiv.innerHTML = '';
523+
};
524+
525+
switch (message.command) {
526+
case 'setGridDatasource':
527+
handleSetGridDatasource();
528+
break;
529+
case 'setResultsContent':
530+
handleSetResultsContent();
531+
break;
532+
default:
533+
case 'loading':
534+
handleLoading();
535+
break;
469536
}
470537
});
471538
document.addEventListener('contextmenu', (e) => {

src/webview/styles/resultsPanel.css

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,49 @@ body {
4949
font-size: 12px;
5050
color: #666666;
5151
}
52+
53+
.overlay {
54+
position: fixed;
55+
top: 0;
56+
left: 0;
57+
width: 100%;
58+
height: 100%;
59+
background: rgba(0, 0, 0, 0.5);
60+
justify-content: center;
61+
align-items: center;
62+
z-index: 1000;
63+
display: none;
64+
}
65+
66+
.loading-box {
67+
background: var(--vscode-editor-background);
68+
color: var(--vscode-editor-foreground);
69+
padding: 20px;
70+
border-radius: 8px;
71+
display: flex;
72+
flex-direction: column;
73+
align-items: center;
74+
}
75+
76+
.spinner {
77+
border: 4px solid rgba(0, 0, 0, 0.1);
78+
border-left-color: var(--vscode-progressBar-background);
79+
border-radius: 50%;
80+
width: 40px;
81+
height: 40px;
82+
animation: spin 1s linear infinite;
83+
}
84+
85+
@keyframes spin {
86+
0% {
87+
transform: rotate(0deg);
88+
}
89+
100% {
90+
transform: rotate(360deg);
91+
}
92+
}
93+
94+
.loading-text {
95+
margin-top: 10px;
96+
font-size: 14px;
97+
}

0 commit comments

Comments
 (0)