From d4911a51067c2c09752eef00e4f4b6f77cd1dce4 Mon Sep 17 00:00:00 2001 From: Philip Carneiro Date: Wed, 17 Jan 2024 16:17:46 +0000 Subject: [PATCH 1/8] fix types display in results --- src/ipc/cClasses.ts | 10 +-- src/services/resultsPanelProvider.ts | 28 ++++---- src/utils/queryUtils.ts | 95 ++++++++++++++++++++++------ 3 files changed, 100 insertions(+), 33 deletions(-) diff --git a/src/ipc/cClasses.ts b/src/ipc/cClasses.ts index c95ee4b2..bf2a63c5 100644 --- a/src/ipc/cClasses.ts +++ b/src/ipc/cClasses.ts @@ -16,7 +16,7 @@ import Tools from "./tools"; function stringifyTemporal( temporal: DDateClass | DDateTimeClass | DMonthClass, - stringify: () => string + stringify: () => string, ): string { const i = temporal.i; @@ -48,7 +48,9 @@ export class DTimestampClass { } toString(): string { - return this.toDate().formatNano("YYYY-MM-DD HH:mm:ss.SSSSSSSSS"); + return this.toDate() + .formatNano("YYYY-MM-DD HH:mm:ss.SSSSSSSSS") + .replace(" ", "D"); } } export class DMonthClass { @@ -84,7 +86,7 @@ export class DDateClass { toString(): string { return stringifyTemporal(this, () => - moment(new Date(this.i)).utcOffset(0).format("YYYY-MM-DD") + moment(new Date(this.i)).utcOffset(0).format("YYYY-MM-DD"), ); } } @@ -103,7 +105,7 @@ export class DDateTimeClass { toString(): string { return stringifyTemporal(this, () => - this.toDate().format("YYYY-MM-DD HH:mm:ss.SSS") + this.toDate().format("YYYY-MM-DD HH:mm:ss.SSS").replace(" ", "T"), ); } } diff --git a/src/services/resultsPanelProvider.ts b/src/services/resultsPanelProvider.ts index 87da5fe8..fe2e345a 100644 --- a/src/services/resultsPanelProvider.ts +++ b/src/services/resultsPanelProvider.ts @@ -23,6 +23,7 @@ import { ext } from "../extensionVariables"; import * as utils from "../utils/execution"; import { getNonce } from "../utils/getNonce"; import { getUri } from "../utils/getUri"; +import { InsightsNode } from "./kdbTreeProvider"; export class KdbResultsViewProvider implements WebviewViewProvider { public static readonly viewType = "kdb-results"; @@ -60,7 +61,7 @@ export class KdbResultsViewProvider implements WebviewViewProvider { this._view.webview.postMessage(queryResults); this._view.webview.html = this._getWebviewContent( queryResults, - dataSourceType + dataSourceType, ); } } @@ -101,10 +102,18 @@ export class KdbResultsViewProvider implements WebviewViewProvider { utils.exportToCsv(workspaceUri); } - convertToGrid(queryResult: any[]): string { + convertToGrid(results: any): string { + const isInsights = ext.connectionNode instanceof InsightsNode; + const queryResult = isInsights ? results.rows : results; + const columnDefs = Object.keys(queryResult[0]).map((key: string) => { const sanitizedKey = this.sanitizeString(key); - return { field: sanitizedKey, headerName: sanitizedKey }; + let type = ""; + if (isInsights && results.meta.hasOwnProperty(key)) { + type = results.meta[key]; + } + const headerTooltip = type; + return { field: sanitizedKey, headerName: sanitizedKey, headerTooltip }; }); const rowData = queryResult.map((row: any) => { for (const key in row) { @@ -136,6 +145,7 @@ export class KdbResultsViewProvider implements WebviewViewProvider { ensureDomOrder: true, suppressContextMenu: true, suppressDragLeaveHidesColumns: true, + tooltipShowDelay: 200, }); } @@ -189,11 +199,7 @@ export class KdbResultsViewProvider implements WebviewViewProvider { queryResult !== "" ? `

${queryResult}

` : "

No results to show

"; - } else if ( - typeof queryResult === "object" && - queryResult !== null && - queryResult instanceof Array - ) { + } else if (queryResult) { isGrid = true; gridOptionsString = this.convertToGrid(queryResult); } @@ -215,12 +221,12 @@ export class KdbResultsViewProvider implements WebviewViewProvider { Q Results + "ag-grid-community.min.js", + )}">
diff --git a/src/utils/queryUtils.ts b/src/utils/queryUtils.ts index df02d67b..93963fd3 100644 --- a/src/utils/queryUtils.ts +++ b/src/utils/queryUtils.ts @@ -14,9 +14,12 @@ import { readFileSync } from "fs"; import { join } from "path"; import { ext } from "../extensionVariables"; -import { deserialize, isCompressed, uncompress } from "../ipc/c"; +import { DCDS, deserialize, isCompressed, uncompress } from "../ipc/c"; import { Parse } from "../ipc/parse.qlist"; import { ServerType } from "../models/server"; +import { convertStringToArray } from "./execution"; +import { DDateClass, DDateTimeClass, DTimestampClass } from "../ipc/cClasses"; +import { TypeBase } from "../ipc/typeBase"; export function sanitizeQuery(query: string): string { if (query[0] === "`") { @@ -91,31 +94,37 @@ export function handleWSResults(ab: ArrayBuffer): any { return "No results found."; } if (ext.resultsViewProvider.isVisible() || ext.isDatasourceExecution) { - return getValueFromArray(res.rows); + return getValueFromArray(res); } - return convertRows(res.rows); + return convertRows(res); } catch (error) { console.log(error); throw error; } } -export function handleScratchpadTableRes(scratchpadResponse: any): any { +export function handleScratchpadTableRes(results: DCDS): any { + let scratchpadResponse = results.rows; if (!Array.isArray(scratchpadResponse)) { - return scratchpadResponse; + if (typeof scratchpadResponse === "string") { + scratchpadResponse = convertStringToArray(scratchpadResponse); + } } + scratchpadResponse = addIndexKey(scratchpadResponse); const result = []; for (const row of scratchpadResponse) { const newObj = {}; for (const key in row) { - if ( - typeof row[key] === "object" && + row[key] = checkIfIsQDateTypes(row[key]); + if (typeof row[key] === "bigint") { + Object.assign(newObj, { [key]: Number(row[key]) }); + } else if ( row[key] !== null && - "i" in row[key] + typeof row[key] === "number" && + (row[key].toString() === "Infinity" || + row[key].toString() === "-Infinity") ) { Object.assign(newObj, { [key]: row[key].toString() }); - } else if (typeof row[key] === "bigint" && row[key] !== null) { - Object.assign(newObj, { [key]: Number(row[key]) }); } else { Object.assign(newObj, { [key]: row[key] }); } @@ -123,19 +132,69 @@ export function handleScratchpadTableRes(scratchpadResponse: any): any { result.push(newObj); } + results.rows = result; + return results; +} - return result; +export function addIndexKey(input: any) { + let arr: any[]; + + // Verifica se o input é uma array + if (Array.isArray(input)) { + arr = input; + } else { + // Se não for uma array, converte para uma array + arr = [input]; + } + + if (arr.length === 0) { + return arr; + } + + if (!arr[0].hasOwnProperty("Index")) { + arr = arr.map((obj, index) => { + const newObj = { Index: index + 1 }; + + for (const prop in obj) { + newObj[prop] = obj[prop]; + } + + return newObj; + }); + } + + return arr; } -export function getValueFromArray(arr: any[]): string | any[] { - if (arr.length === 1 && typeof arr[0] === "object" && arr[0] !== null) { - const obj = arr[0]; - const keys = Object.keys(obj); - if (keys.length === 1 && keys[0] === "Value") { - return String(obj.Value); +export function getValueFromArray(results: DCDS): any { + const arr = results.rows; + if (arr !== undefined) { + if (arr.length === 1 && typeof arr[0] === "object" && arr[0] !== null) { + results.rows = [checkIfIsQDateTypes(arr[0])]; } } - return arr; + results.meta = generateQTypes(results.meta); + return results; +} + +export function generateQTypes(meta: { [key: string]: number }): any { + const newMeta: { [key: string]: string } = {}; + for (const key in meta) { + const value = meta[key]; + newMeta[key] = TypeBase.typeNames[value] || `Unknown type: ${value}`; + } + return newMeta; +} + +export function checkIfIsQDateTypes(obj: any): any { + if ( + obj?.Value instanceof DTimestampClass || + obj?.Value instanceof DDateTimeClass || + obj?.Value instanceof DDateClass + ) { + return obj.Value.toString(); + } + return obj; } export function convertRows(rows: any[]): any[] { From c8f9cba17b3d10f1c1ebb3d72759f8f5f3e9742a Mon Sep 17 00:00:00 2001 From: Philip Carneiro Date: Wed, 17 Jan 2024 17:54:34 +0000 Subject: [PATCH 2/8] fix tests --- test/suite/commands.test.ts | 108 ++++++++++++++++++++------------ test/suite/panels.test.ts | 75 ++++++++++++++++++++--- test/suite/utils.test.ts | 119 +++++++++++++++++++----------------- 3 files changed, 198 insertions(+), 104 deletions(-) diff --git a/test/suite/commands.test.ts b/test/suite/commands.test.ts index ff83fea6..c4613b0d 100644 --- a/test/suite/commands.test.ts +++ b/test/suite/commands.test.ts @@ -96,7 +96,7 @@ describe("dataSourceCommand", () => { }); await assert.doesNotReject( - dataSourceCommand.renameDataSource("datasource-0", "datasource-1") + dataSourceCommand.renameDataSource("datasource-0", "datasource-1"), ); }); @@ -164,7 +164,7 @@ describe("dataSourceCommand", () => { const item = new KdbDataSourceTreeItem( "datasource-0", vscode.TreeItemCollapsibleState.Collapsed, - [] + [], ); await assert.doesNotReject(dataSourceCommand.deleteDataSource(item)); @@ -189,7 +189,7 @@ describe("dataSourceCommand", () => { const item = new KdbDataSourceTreeItem( "datasource-0", vscode.TreeItemCollapsibleState.Collapsed, - [] + [], ); const uri = vscode.Uri.file("/temp/.kdb-datasources/datasource-0.ds"); @@ -449,15 +449,20 @@ describe("dataSourceCommand2", () => { let checkIfTimeParamIsCorrectStub: sinon.SinonStub; let getDataInsightsStub: sinon.SinonStub; let handleWSResultsStub: sinon.SinonStub; + let handleScratchpadTableRes: sinon.SinonStub; beforeEach(() => { getApiBodyStub = sinon.stub(dataSourceCommand, "getApiBody"); checkIfTimeParamIsCorrectStub = sinon.stub( dataSourceUtils, - "checkIfTimeParamIsCorrect" + "checkIfTimeParamIsCorrect", ); getDataInsightsStub = sinon.stub(srvCommand, "getDataInsights"); handleWSResultsStub = sinon.stub(queryUtils, "handleWSResults"); + handleScratchpadTableRes = sinon.stub( + queryUtils, + "handleScratchpadTableRes", + ); }); afterEach(() => { @@ -473,7 +478,7 @@ describe("dataSourceCommand2", () => { .expects("showErrorMessage") .once() .withArgs( - "The time parameters(startTS and endTS) are not correct, please check the format or if the startTS is before the endTS" + "The time parameters(startTS and endTS) are not correct, please check the format or if the startTS is before the endTS", ); sinon.assert.notCalled(getApiBodyStub); sinon.assert.notCalled(getDataInsightsStub); @@ -489,10 +494,14 @@ describe("dataSourceCommand2", () => { { a: "4", b: "6" }, { a: "6", b: "9" }, ]); + handleScratchpadTableRes.resolves([ + { a: "2", b: "3" }, + { a: "4", b: "6" }, + { a: "6", b: "9" }, + ]); - const result = await dataSourceCommand.runApiDataSource( - dummyDataSourceFiles - ); + const result = + await dataSourceCommand.runApiDataSource(dummyDataSourceFiles); sinon.assert.calledOnce(getDataInsightsStub); sinon.assert.calledOnce(handleWSResultsStub); @@ -507,10 +516,15 @@ describe("dataSourceCommand2", () => { describe("runQsqlDataSource", () => { let getDataInsightsStub: sinon.SinonStub; let handleWSResultsStub: sinon.SinonStub; + let handleScratchpadTableRes: sinon.SinonStub; beforeEach(() => { getDataInsightsStub = sinon.stub(srvCommand, "getDataInsights"); handleWSResultsStub = sinon.stub(queryUtils, "handleWSResults"); + handleScratchpadTableRes = sinon.stub( + queryUtils, + "handleScratchpadTableRes", + ); }); afterEach(() => { @@ -524,10 +538,14 @@ describe("dataSourceCommand2", () => { { a: "4", b: "6" }, { a: "6", b: "9" }, ]); + handleScratchpadTableRes.resolves([ + { a: "2", b: "3" }, + { a: "4", b: "6" }, + { a: "6", b: "9" }, + ]); - const result = await dataSourceCommand.runQsqlDataSource( - dummyDataSourceFiles - ); + const result = + await dataSourceCommand.runQsqlDataSource(dummyDataSourceFiles); sinon.assert.calledOnce(getDataInsightsStub); sinon.assert.calledOnce(handleWSResultsStub); @@ -542,10 +560,15 @@ describe("dataSourceCommand2", () => { describe("runSqlDataSource", () => { let getDataInsightsStub: sinon.SinonStub; let handleWSResultsStub: sinon.SinonStub; + let handleScratchpadTableRes: sinon.SinonStub; beforeEach(() => { getDataInsightsStub = sinon.stub(srvCommand, "getDataInsights"); handleWSResultsStub = sinon.stub(queryUtils, "handleWSResults"); + handleScratchpadTableRes = sinon.stub( + queryUtils, + "handleScratchpadTableRes", + ); }); afterEach(() => { @@ -559,10 +582,14 @@ describe("dataSourceCommand2", () => { { a: "4", b: "6" }, { a: "6", b: "9" }, ]); + handleScratchpadTableRes.resolves([ + { a: "2", b: "3" }, + { a: "4", b: "6" }, + { a: "6", b: "9" }, + ]); - const result = await dataSourceCommand.runSqlDataSource( - dummyDataSourceFiles - ); + const result = + await dataSourceCommand.runSqlDataSource(dummyDataSourceFiles); sinon.assert.calledOnce(getDataInsightsStub); sinon.assert.calledOnce(handleWSResultsStub); @@ -710,9 +737,11 @@ describe("dataSourceCommand2", () => { let isVisibleStub, getMetaStub, handleWSResultsStub, + handleScratchpadTableRes, getDataInsightsStub, writeQueryResultsToViewStub, writeQueryResultsToConsoleStub: sinon.SinonStub; + ext.outputChannel = vscode.window.createOutputChannel("kdb"); beforeEach(() => { @@ -721,14 +750,17 @@ describe("dataSourceCommand2", () => { handleWSResultsStub = sinon .stub(queryUtils, "handleWSResults") .returns("dummy results"); + handleScratchpadTableRes = sinon + .stub(queryUtils, "handleScratchpadTableRes") + .returns("dummy results"); getDataInsightsStub = sinon.stub(srvCommand, "getDataInsights"); writeQueryResultsToViewStub = sinon.stub( srvCommand, - "writeQueryResultsToView" + "writeQueryResultsToView", ); writeQueryResultsToConsoleStub = sinon.stub( srvCommand, - "writeQueryResultsToConsole" + "writeQueryResultsToConsole", ); }); @@ -747,7 +779,7 @@ describe("dataSourceCommand2", () => { getDataInsightsStub.resolves({ arrayBuffer: ab, error: "" }); isVisibleStub.returns(true); await dataSourceCommand.runDataSource( - dummyFileContent as DataSourceFiles + dummyFileContent as DataSourceFiles, ); sinon.assert.neverCalledWith(writeQueryResultsToConsoleStub); sinon.assert.calledOnce(writeQueryResultsToViewStub); @@ -759,7 +791,7 @@ describe("dataSourceCommand2", () => { getDataInsightsStub.resolves({ arrayBuffer: ab, error: "" }); isVisibleStub.returns(false); await dataSourceCommand.runDataSource( - dummyFileContent as DataSourceFiles + dummyFileContent as DataSourceFiles, ); sinon.assert.neverCalledWith(writeQueryResultsToViewStub); sinon.assert.calledOnce(writeQueryResultsToConsoleStub); @@ -771,7 +803,7 @@ describe("dataSourceCommand2", () => { getDataInsightsStub.resolves({ arrayBuffer: ab, error: "" }); isVisibleStub.returns(false); await dataSourceCommand.runDataSource( - dummyFileContent as DataSourceFiles + dummyFileContent as DataSourceFiles, ); sinon.assert.neverCalledWith(writeQueryResultsToViewStub); sinon.assert.calledOnce(writeQueryResultsToConsoleStub); @@ -783,7 +815,7 @@ describe("dataSourceCommand2", () => { getDataInsightsStub.resolves({ arrayBuffer: ab, error: "error" }); isVisibleStub.returns(false); await dataSourceCommand.runDataSource( - dummyFileContent as DataSourceFiles + dummyFileContent as DataSourceFiles, ); sinon.assert.neverCalledWith(writeQueryResultsToViewStub); sinon.assert.neverCalledWith(writeQueryResultsToConsoleStub); @@ -795,7 +827,7 @@ describe("dataSourceCommand2", () => { getDataInsightsStub.resolves({ arrayBuffer: ab, error: "error" }); isVisibleStub.returns(false); await dataSourceCommand.runDataSource( - dummyFileContent as DataSourceFiles + dummyFileContent as DataSourceFiles, ); sinon.assert.neverCalledWith(writeQueryResultsToViewStub); sinon.assert.neverCalledWith(writeQueryResultsToConsoleStub); @@ -807,7 +839,7 @@ describe("dataSourceCommand2", () => { getDataInsightsStub.resolves({ arrayBuffer: ab, error: "error" }); isVisibleStub.returns(false); await dataSourceCommand.runDataSource( - dummyFileContent as DataSourceFiles + dummyFileContent as DataSourceFiles, ); sinon.assert.neverCalledWith(writeQueryResultsToViewStub); sinon.assert.neverCalledWith(writeQueryResultsToConsoleStub); @@ -819,7 +851,7 @@ describe("dataSourceCommand2", () => { getDataInsightsStub.resolves(undefined); isVisibleStub.returns(false); await dataSourceCommand.runDataSource( - dummyFileContent as DataSourceFiles + dummyFileContent as DataSourceFiles, ); sinon.assert.neverCalledWith(writeQueryResultsToViewStub); sinon.assert.neverCalledWith(writeQueryResultsToConsoleStub); @@ -831,7 +863,7 @@ describe("dataSourceCommand2", () => { getDataInsightsStub.resolves(undefined); isVisibleStub.returns(false); await dataSourceCommand.runDataSource( - dummyFileContent as DataSourceFiles + dummyFileContent as DataSourceFiles, ); sinon.assert.neverCalledWith(writeQueryResultsToViewStub); sinon.assert.neverCalledWith(writeQueryResultsToConsoleStub); @@ -843,7 +875,7 @@ describe("dataSourceCommand2", () => { getDataInsightsStub.resolves(undefined); isVisibleStub.returns(false); await dataSourceCommand.runDataSource( - dummyFileContent as DataSourceFiles + dummyFileContent as DataSourceFiles, ); sinon.assert.neverCalledWith(writeQueryResultsToViewStub); sinon.assert.neverCalledWith(writeQueryResultsToConsoleStub); @@ -1098,7 +1130,7 @@ describe("serverCommand", () => { executeCommandStub.firstCall, "kdb.resultsPanel.update", result, - undefined + undefined, ); executeCommandStub.restore(); @@ -1132,7 +1164,7 @@ describe("serverCommand", () => { showErrorMessageStub, "OpenSSL not found, please ensure this is installed", "More Info", - "Cancel" + "Cancel", ); sinon.assert.notCalled(updateServersStub); }); @@ -1147,7 +1179,7 @@ describe("serverCommand", () => { sinon.assert.calledWith( showErrorMessageStub, "Server not found, please ensure this is a correct server", - "Cancel" + "Cancel", ); sinon.assert.calledOnce(getServersStub); sinon.assert.notCalled(updateServersStub); @@ -1212,15 +1244,15 @@ describe("serverCommand", () => { }; queryConsoleErrorStub = sinon.stub( ExecutionConsole.prototype, - "appendQueryError" + "appendQueryError", ); writeQueryResultsToViewStub = sinon.stub( srvCommand, - "writeQueryResultsToView" + "writeQueryResultsToView", ); writeQueryResultsToConsoleStub = sinon.stub( srvCommand, - "writeQueryResultsToConsole" + "writeQueryResultsToConsole", ); isVisibleStub = sinon.stub(ext.resultsViewProvider, "isVisible"); }); @@ -1263,7 +1295,7 @@ describe("serverCommand", () => { alias: "insightsserveralias", auth: true, }, - TreeItemCollapsibleState.None + TreeItemCollapsibleState.None, ); const token: codeFlowLogin.IToken = { @@ -1414,7 +1446,7 @@ describe("serverCommand", () => { alias: "insightsserveralias", auth: true, }, - TreeItemCollapsibleState.None + TreeItemCollapsibleState.None, ); const token: codeFlowLogin.IToken = { accessToken: @@ -1432,7 +1464,7 @@ describe("serverCommand", () => { handleWSResultsStub = sinon.stub(queryUtils, "handleWSResults"); handleScratchpadTableResStub = sinon.stub( queryUtils, - "handleScratchpadTableRes" + "handleScratchpadTableRes", ); }); @@ -1498,7 +1530,7 @@ describe("serverCommand", () => { alias: "insightsserveralias", auth: true, }, - TreeItemCollapsibleState.None + TreeItemCollapsibleState.None, ); let getQueryContextStub, @@ -1540,7 +1572,7 @@ describe("serverCommand", () => { it("runQuery with PythonQueryFile not connected to inisghts node", () => { ext.connectionNode = undefined; const result = serverCommand.runQuery( - ExecutionTypes.PythonQuerySelection + ExecutionTypes.PythonQuerySelection, ); assert.equal(result, undefined); }); @@ -1548,7 +1580,7 @@ describe("serverCommand", () => { it("runQuery with PythonQueryFile connected to inisghts node", () => { ext.connectionNode = insightsNode; const result = serverCommand.runQuery( - ExecutionTypes.PythonQuerySelection + ExecutionTypes.PythonQuerySelection, ); assert.equal(result, undefined); }); @@ -1563,7 +1595,7 @@ describe("serverCommand", () => { ext.connectionNode = undefined; const result = serverCommand.runQuery( ExecutionTypes.ReRunQuery, - "rerun query" + "rerun query", ); assert.equal(result, undefined); }); diff --git a/test/suite/panels.test.ts b/test/suite/panels.test.ts index 71fd5fd7..ed44369f 100644 --- a/test/suite/panels.test.ts +++ b/test/suite/panels.test.ts @@ -19,6 +19,8 @@ import { createDefaultDataSourceFile } from "../../src/models/dataSource"; import { DataSourcesPanel } from "../../src/panels/datasource"; import { KdbResultsViewProvider } from "../../src/services/resultsPanelProvider"; import * as utils from "../../src/utils/execution"; +import { InsightsNode, KdbNode } from "../../src/services/kdbTreeProvider"; +import { TreeItemCollapsibleState } from "vscode"; describe("WebPanels", () => { describe("DataSourcesPanel", () => { @@ -36,7 +38,7 @@ describe("WebPanels", () => { it("should create a new panel", () => { assert.ok( DataSourcesPanel.currentPanel, - "DataSourcesPanel.currentPanel should be truthy" + "DataSourcesPanel.currentPanel should be truthy", ); }); @@ -45,7 +47,7 @@ describe("WebPanels", () => { assert.strictEqual( DataSourcesPanel.currentPanel, undefined, - "DataSourcesPanel.currentPanel should be undefined" + "DataSourcesPanel.currentPanel should be undefined", ); }); @@ -54,7 +56,7 @@ describe("WebPanels", () => { const actualHtml = DataSourcesPanel.currentPanel._panel.webview.html; assert.ok( actualHtml.indexOf(expectedHtml) !== -1, - "Panel HTML should include expected web component" + "Panel HTML should include expected web component", ); }); }); @@ -63,6 +65,17 @@ describe("WebPanels", () => { const uriTest: vscode.Uri = vscode.Uri.parse("test"); let resultsPanel: KdbResultsViewProvider; + const insightsNode = new InsightsNode( + [], + "insightsnode1", + { + server: "insightsservername", + alias: "insightsserveralias", + auth: true, + }, + TreeItemCollapsibleState.None, + ); + beforeEach(() => { resultsPanel = new KdbResultsViewProvider(uriTest); }); @@ -158,12 +171,51 @@ describe("WebPanels", () => { }); describe("convertToGrid()", () => { - it("should return 'gridOptions' if queryResult is an empty string", () => { - const inputQueryResult = [{ a: "1" }, { a: "2" }, { a: "3" }]; - const expectedOutput = - '{"defaultColDef":{"sortable":true,"resizable":true,"filter":true,"flex":1,"minWidth":100},"rowData":[{"a":"1"},{"a":"2"},{"a":"3"}],"columnDefs":[{"field":"a","headerName":"a"}],"domLayout":"autoHeight","pagination":true,"paginationPageSize":100,"cacheBlockSize":100,"enableCellTextSelection":true,"ensureDomOrder":true,"suppressContextMenu":true,"suppressDragLeaveHidesColumns":true}'; - const actualOutput = resultsPanel.convertToGrid(inputQueryResult); - assert.strictEqual(actualOutput, expectedOutput); + it("should convert results to grid format", () => { + const results = { + rows: [ + { prop1: "value1", prop2: "value2" }, + { prop1: "value3", prop2: "value4" }, + ], + meta: { prop1: "type1", prop2: "type2" }, + }; + + const expectedOutput = JSON.stringify({ + defaultColDef: { + sortable: true, + resizable: true, + filter: true, + flex: 1, + minWidth: 100, + }, + rowData: [ + { prop1: "value1", prop2: "value2" }, + { prop1: "value3", prop2: "value4" }, + ], + columnDefs: [ + { field: "prop1", headerName: "prop1", headerTooltip: "type1" }, + { field: "prop2", headerName: "prop2", headerTooltip: "type2" }, + ], + domLayout: "autoHeight", + pagination: true, + paginationPageSize: 100, + cacheBlockSize: 100, + enableCellTextSelection: true, + ensureDomOrder: true, + suppressContextMenu: true, + suppressDragLeaveHidesColumns: true, + tooltipShowDelay: 200, + }); + + // Mock ext.connectionNode + const stub = sinon.stub(ext, "connectionNode"); + stub.get(() => insightsNode); + + const output = resultsPanel.convertToGrid(results); + assert.equal(output, expectedOutput); + + // Restore the stub + stub.restore(); }); }); @@ -227,6 +279,7 @@ describe("WebPanels", () => { describe("_getWebviewContent", () => { const uriTest: vscode.Uri = vscode.Uri.parse("test"); + let resultsPanel: KdbResultsViewProvider; const view: vscode.WebviewView = { visible: true, @@ -258,9 +311,13 @@ describe("WebPanels", () => { { id: 2, test: "test2" }, ]; const expectedOutput = `"rowData":[{"id":1,"test":"test1"},{"id":2,"test":"test2"}],"columnDefs":[{"field":"id","headerName":"id"},{"field":"test","headerName":"test"}]`; + const stub = sinon + .stub(resultsPanel, "convertToGrid") + .returns(expectedOutput); const actualOutput = resultsPanel["_getWebviewContent"](input); assert.strictEqual(typeof actualOutput, "string"); assert.ok(actualOutput.includes(expectedOutput)); + stub.restore(); }); it("returns no results", () => { diff --git a/test/suite/utils.test.ts b/test/suite/utils.test.ts index f242ae4a..1139f36e 100644 --- a/test/suite/utils.test.ts +++ b/test/suite/utils.test.ts @@ -40,6 +40,7 @@ import { showQuickPick, } from "../../src/utils/userInteraction"; import { validateUtils } from "../../src/utils/validateUtils"; +import { DCDS } from "../../src/ipc/c"; interface ITestItem extends vscode.QuickPickItem { id: number; @@ -91,7 +92,7 @@ describe("Utils", () => { beforeEach(() => { getConfigurationStub = sinon.stub( vscode.workspace, - "getConfiguration" + "getConfiguration", ) as sinon.SinonStub; }); @@ -154,12 +155,12 @@ describe("Utils", () => { it("checkIfTimeParamIsCorrect", () => { const result = dataSourceUtils.checkIfTimeParamIsCorrect( "2021-01-01", - "2021-01-02" + "2021-01-02", ); assert.strictEqual(result, true); const result2 = dataSourceUtils.checkIfTimeParamIsCorrect( "2021-01-02", - "2021-01-01" + "2021-01-01", ); assert.strictEqual(result2, false); }); @@ -398,7 +399,7 @@ describe("Utils", () => { auth: false, tls: false, }, - TreeItemCollapsibleState.None + TreeItemCollapsibleState.None, ); const insightsNode = new InsightsNode( @@ -409,7 +410,7 @@ describe("Utils", () => { alias: "insightsserveralias", auth: true, }, - TreeItemCollapsibleState.None + TreeItemCollapsibleState.None, ); beforeEach(() => { @@ -435,7 +436,7 @@ describe("Utils", () => { assert.strictEqual(ext.kdbQueryHistoryList[0].success, true); assert.strictEqual( ext.kdbQueryHistoryList[0].connectionType, - ServerType.KDB + ServerType.KDB, ); getConfigurationStub.restore(); @@ -458,7 +459,7 @@ describe("Utils", () => { assert.strictEqual(ext.kdbQueryHistoryList[0].success, true); assert.strictEqual( ext.kdbQueryHistoryList[0].connectionType, - ServerType.KDB + ServerType.KDB, ); getConfigurationStub.restore(); }); @@ -475,7 +476,7 @@ describe("Utils", () => { assert.strictEqual(ext.kdbQueryHistoryList[0].success, true); assert.strictEqual( ext.kdbQueryHistoryList[0].connectionType, - ServerType.INSIGHTS + ServerType.INSIGHTS, ); }); @@ -491,7 +492,7 @@ describe("Utils", () => { assert.strictEqual(ext.kdbQueryHistoryList[0].success, false); assert.strictEqual( ext.kdbQueryHistoryList[0].connectionType, - ServerType.KDB + ServerType.KDB, ); }); @@ -507,7 +508,7 @@ describe("Utils", () => { assert.strictEqual(ext.kdbQueryHistoryList[0].success, false); assert.strictEqual( ext.kdbQueryHistoryList[0].connectionType, - ServerType.INSIGHTS + ServerType.INSIGHTS, ); }); @@ -523,7 +524,7 @@ describe("Utils", () => { assert.strictEqual(ext.kdbQueryHistoryList[0].success, false); assert.strictEqual( ext.kdbQueryHistoryList[0].connectionType, - ServerType.undefined + ServerType.undefined, ); }); }); @@ -539,7 +540,7 @@ describe("Utils", () => { query, connectionName, connectionType, - true + true, ); assert.strictEqual(ext.kdbQueryHistoryList.length, 1); }); @@ -564,7 +565,7 @@ describe("Utils", () => { "testPanel", "Test Panel", vscode.ViewColumn.One, - {} + {}, ); const webview = panel.webview; const extensionUri = vscode.Uri.parse("file:///path/to/extension"); @@ -578,7 +579,7 @@ describe("Utils", () => { "testPanel", "Test Panel", vscode.ViewColumn.One, - {} + {}, ); const webview = panel.webview; const extensionUri = vscode.Uri.parse("file:///path/to/extension"); @@ -700,25 +701,35 @@ describe("Utils", () => { }); describe("getValueFromArray", () => { + let inputSample: DCDS = undefined; + beforeEach(() => { + inputSample = { + class: "203", + columns: ["Value"], + meta: { Value: 7 }, + rows: [], + }; + }); + it("should return the value of the 'Value' property if the input is an array with a single object with a 'Value' property", () => { - const input = [{ Value: "hello" }]; - const expectedOutput = "hello"; - const actualOutput = queryUtils.getValueFromArray(input); - assert.strictEqual(actualOutput, expectedOutput); + inputSample.rows = [{ Value: "hello" }]; + const expectedOutput = [{ Value: "hello" }]; + const actualOutput = queryUtils.getValueFromArray(inputSample); + console.log(JSON.stringify(actualOutput.rows)); + assert.deepEqual(actualOutput.rows, expectedOutput); }); it("should return the input array if it is not an array with a single object with a 'Value' property", () => { - const input = ["hello", "world"]; - const expectedOutput = ["hello", "world"]; - const actualOutput = queryUtils.getValueFromArray(input); - assert.deepStrictEqual(actualOutput, expectedOutput); + inputSample.rows = [{ Value: "hello" }, { Value: "world" }]; + const expectedOutput = [{ Value: "hello" }, { Value: "world" }]; + const actualOutput = queryUtils.getValueFromArray(inputSample); + assert.deepStrictEqual(actualOutput.rows, expectedOutput); }); it("should return the input array if it is an empty array", () => { - const input: any[] = []; const expectedOutput: any[] = []; - const actualOutput = queryUtils.getValueFromArray(input); - assert.deepStrictEqual(actualOutput, expectedOutput); + const actualOutput = queryUtils.getValueFromArray(inputSample); + assert.deepStrictEqual(actualOutput.rows, expectedOutput); }); }); @@ -731,15 +742,17 @@ describe("Utils", () => { it("should return the result of getValueFromArray if the results are an array with a single object with a 'Value' property", () => { const ab = new ArrayBuffer(128); - const expectedOutput = "10"; - const uriTest: vscode.Uri = vscode.Uri.parse("test"); - ext.resultsViewProvider = new KdbResultsViewProvider(uriTest); - const qtableStub = sinon.stub(QTable.default, "toLegacy").returns({ + const expectedOutput = { class: "203", columns: ["Value"], meta: { Value: 7 }, rows: [{ Value: "10" }], - }); + }; + const uriTest: vscode.Uri = vscode.Uri.parse("test"); + ext.resultsViewProvider = new KdbResultsViewProvider(uriTest); + const qtableStub = sinon + .stub(QTable.default, "toLegacy") + .returns(expectedOutput); const isVisibleStub = sinon .stub(ext.resultsViewProvider, "isVisible") .returns(true); @@ -747,49 +760,41 @@ describe("Utils", () => { const result = queryUtils.handleWSResults(ab); sinon.assert.notCalled(convertRowsSpy); assert.strictEqual(result, expectedOutput); + sinon.restore(); }); }); describe("handleScratchpadTableRes", () => { - it("should return the input if it is not an array", () => { - const input = "not an array"; - const result = queryUtils.handleScratchpadTableRes(input); - assert.strictEqual(result, input); - }); - - it("should convert object values with 'i' property to string", () => { - const input = [ - { key1: { i: 123 }, key2: "value2" }, - { key3: { i: 456 }, key4: "value4" }, - ]; - const expected = [ - { key1: "[object Object]", key2: "value2" }, - { key3: "[object Object]", key4: "value4" }, - ]; - const result = queryUtils.handleScratchpadTableRes(input); - assert.deepStrictEqual(result, expected); + let inputSample: DCDS = undefined; + beforeEach(() => { + inputSample = { + class: "203", + columns: ["Value"], + meta: { Value: 7 }, + rows: [], + }; }); it("should convert bigint values to number", () => { - const input = [ + inputSample.rows = [ { key1: BigInt(123), key2: "value2" }, { key3: BigInt(456), key4: "value4" }, ]; const expected = [ - { key1: 123, key2: "value2" }, - { key3: 456, key4: "value4" }, + { Index: 1, key1: 123, key2: "value2" }, + { Index: 2, key3: 456, key4: "value4" }, ]; - const result = queryUtils.handleScratchpadTableRes(input); - assert.deepStrictEqual(result, expected); + const result = queryUtils.handleScratchpadTableRes(inputSample); + assert.deepStrictEqual(result.rows, expected); }); it("should not modify other values", () => { - const input = [ + inputSample.rows = [ { key1: "value1", key2: "value2" }, { key3: "value3", key4: "value4" }, ]; - const result = queryUtils.handleScratchpadTableRes(input); - assert.deepStrictEqual(result, input); + const result = queryUtils.handleScratchpadTableRes(inputSample); + assert.deepStrictEqual(result.rows, inputSample.rows); }); }); @@ -896,12 +901,12 @@ describe("Utils", () => { getConfigurationStub = sinon.stub(vscode.workspace, "getConfiguration"); showInformationMessageStub = sinon.stub( vscode.window, - "showInformationMessage" + "showInformationMessage", ) as sinon.SinonStub< [ message: string, options: vscode.MessageOptions, - ...items: vscode.MessageItem[] + ...items: vscode.MessageItem[], ], Thenable >; From f67e1712872bafdfad216e8b17057c11bc5a2e8f Mon Sep 17 00:00:00 2001 From: Philip Carneiro Date: Fri, 19 Jan 2024 09:56:12 +0000 Subject: [PATCH 3/8] KXI-35930 fix issue with empty table --- src/services/resultsPanelProvider.ts | 47 ++++++++++++++++++++++------ src/utils/queryUtils.ts | 11 +++++-- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/src/services/resultsPanelProvider.ts b/src/services/resultsPanelProvider.ts index fe2e345a..acb0e98b 100644 --- a/src/services/resultsPanelProvider.ts +++ b/src/services/resultsPanelProvider.ts @@ -102,19 +102,44 @@ export class KdbResultsViewProvider implements WebviewViewProvider { utils.exportToCsv(workspaceUri); } + generateCoumnDefs(results: any, isInsights: boolean): any { + if (isInsights) { + if (results.rows.length === 0) { + return Object.keys(results.meta).map((key: string) => { + const sanitizedKey = this.sanitizeString(key); + const type = results.meta[key]; + const headerTooltip = type; + return { + field: sanitizedKey, + headerName: sanitizedKey, + headerTooltip, + }; + }); + } else { + return Object.keys(results.rows[0]).map((key: string) => { + const sanitizedKey = this.sanitizeString(key); + const type = results.meta[key]; + const headerTooltip = type; + return { + field: sanitizedKey, + headerName: sanitizedKey, + headerTooltip, + }; + }); + } + } else { + return Object.keys(results[0]).map((key: string) => { + const sanitizedKey = this.sanitizeString(key); + return { field: sanitizedKey, headerName: sanitizedKey }; + }); + } + } + convertToGrid(results: any): string { const isInsights = ext.connectionNode instanceof InsightsNode; const queryResult = isInsights ? results.rows : results; - const columnDefs = Object.keys(queryResult[0]).map((key: string) => { - const sanitizedKey = this.sanitizeString(key); - let type = ""; - if (isInsights && results.meta.hasOwnProperty(key)) { - type = results.meta[key]; - } - const headerTooltip = type; - return { field: sanitizedKey, headerName: sanitizedKey, headerTooltip }; - }); + const columnDefs = this.generateCoumnDefs(results, isInsights); const rowData = queryResult.map((row: any) => { for (const key in row) { if (Object.prototype.hasOwnProperty.call(row, key)) { @@ -126,7 +151,9 @@ export class KdbResultsViewProvider implements WebviewViewProvider { } return row; }); - ext.resultPanelCSV = this.convertToCsv(rowData).join("\n"); + if (rowData.length > 0) { + ext.resultPanelCSV = this.convertToCsv(rowData).join("\n"); + } return JSON.stringify({ defaultColDef: { sortable: true, diff --git a/src/utils/queryUtils.ts b/src/utils/queryUtils.ts index 93963fd3..ffdb72c2 100644 --- a/src/utils/queryUtils.ts +++ b/src/utils/queryUtils.ts @@ -90,7 +90,7 @@ export function handleWSResults(ab: ArrayBuffer): any { des = des.values[1]; } res = Parse.reshape(des, ab).toLegacy(); - if (res.rows.length === 0) { + if (res.rows.length === 0 && res.columns.length === 0) { return "No results found."; } if (ext.resultsViewProvider.isVisible() || ext.isDatasourceExecution) { @@ -103,14 +103,19 @@ export function handleWSResults(ab: ArrayBuffer): any { } } -export function handleScratchpadTableRes(results: DCDS): any { +export function handleScratchpadTableRes(results: DCDS | string): any { + if (typeof results === "string" || results?.rows === undefined) { + return results; + } let scratchpadResponse = results.rows; if (!Array.isArray(scratchpadResponse)) { if (typeof scratchpadResponse === "string") { scratchpadResponse = convertStringToArray(scratchpadResponse); } } - scratchpadResponse = addIndexKey(scratchpadResponse); + if (scratchpadResponse?.length !== 0) { + scratchpadResponse = addIndexKey(scratchpadResponse); + } const result = []; for (const row of scratchpadResponse) { const newObj = {}; From 443c21fa82061bc7f62d8c8ba0907b9a02c23a83 Mon Sep 17 00:00:00 2001 From: Philip Carneiro Date: Mon, 22 Jan 2024 10:51:02 +0000 Subject: [PATCH 4/8] fix small bug and add tests --- src/utils/queryUtils.ts | 10 +-- test/suite/utils.test.ts | 136 +++++++++++++++------------------------ 2 files changed, 59 insertions(+), 87 deletions(-) diff --git a/src/utils/queryUtils.ts b/src/utils/queryUtils.ts index ffdb72c2..82a86aeb 100644 --- a/src/utils/queryUtils.ts +++ b/src/utils/queryUtils.ts @@ -144,11 +144,9 @@ export function handleScratchpadTableRes(results: DCDS | string): any { export function addIndexKey(input: any) { let arr: any[]; - // Verifica se o input é uma array if (Array.isArray(input)) { arr = input; } else { - // Se não for uma array, converte para uma array arr = [input]; } @@ -160,8 +158,12 @@ export function addIndexKey(input: any) { arr = arr.map((obj, index) => { const newObj = { Index: index + 1 }; - for (const prop in obj) { - newObj[prop] = obj[prop]; + if (typeof obj === "string") { + newObj["Value"] = obj; + } else { + for (const prop in obj) { + newObj[prop] = obj[prop]; + } } return newObj; diff --git a/test/suite/utils.test.ts b/test/suite/utils.test.ts index 1139f36e..b7ce505a 100644 --- a/test/suite/utils.test.ts +++ b/test/suite/utils.test.ts @@ -607,89 +607,6 @@ describe("Utils", () => { }); }); - // describe("Output", () => { - // let windowCreateOutputChannelStub: sinon.SinonStub; - // let outputChannelAppendStub: sinon.SinonStub; - // let outputChannelAppendLineStub: sinon.SinonStub; - // let outputChannelShowStub: sinon.SinonStub; - // let outputChannelHideStub: sinon.SinonStub; - // let outputChannelDisposeStub: sinon.SinonStub; - // Output._outputChannel = { - // name: "", - // append: sinon.stub(), - // appendLine: sinon.stub(), - // show: sinon.stub(), - // hide: sinon.stub(), - // dispose: sinon.stub(), - // } as unknown as vscode.OutputChannel; - - // beforeEach(() => { - // windowCreateOutputChannelStub = sinon.stub( - // vscode.window, - // "createOutputChannel" - // ); - // outputChannelAppendStub = sinon.stub(Output._outputChannel, "append"); - // outputChannelAppendLineStub = sinon.stub( - // Output._outputChannel, - // "appendLine" - // ); - // outputChannelShowStub = sinon.stub(Output._outputChannel, "show"); - // outputChannelHideStub = sinon.stub(Output._outputChannel, "hide"); - // outputChannelDisposeStub = sinon.stub(Output._outputChannel, "dispose"); - // }); - - // afterEach(() => { - // windowCreateOutputChannelStub.restore(); - // outputChannelAppendStub.restore(); - // outputChannelAppendLineStub.restore(); - // outputChannelShowStub.restore(); - // outputChannelHideStub.restore(); - // outputChannelDisposeStub.restore(); - // }); - - // it("should create an output channel with the correct name", () => { - // Output._outputChannel = undefined as unknown as vscode.OutputChannel; - // windowCreateOutputChannelStub.returns(Output._outputChannel); - // Output._outputChannel = - // Output._outputChannel || - // vscode.window.createOutputChannel("kdb-telemetry"); - // assert.ok(windowCreateOutputChannelStub.calledOnceWith("kdb-telemetry")); - // }); - - // it("should append a message to the output channel", () => { - // const label = "label"; - // const message = "message"; - // Output.output(label, message); - // assert.ok( - // outputChannelAppendStub.calledOnceWith(`[${label}] ${message}`) - // ); - // }); - - // it("should append a message with a newline to the output channel", () => { - // const label = "label"; - // const message = "message"; - // Output.outputLine(label, message); - // assert.ok( - // outputChannelAppendLineStub.calledOnceWith(`[${label}] ${message}`) - // ); - // }); - - // it("should show the output channel", () => { - // Output.show(); - // assert.ok(outputChannelShowStub.calledOnce); - // }); - - // it("should hide the output channel", () => { - // Output.hide(); - // assert.ok(outputChannelHideStub.calledOnce); - // }); - - // it("should dispose the output channel", () => { - // Output.dispose(); - // assert.ok(outputChannelDisposeStub.calledOnce); - // }); - // }); - describe("queryUtils", () => { it("sanitizeQuery", () => { const query1 = "`select from t"; @@ -798,6 +715,59 @@ describe("Utils", () => { }); }); + describe("addIndexKey", () => { + it("should add index key to array of objects", () => { + const input = [ + { prop1: "value1", prop2: "value2" }, + { prop1: "value3", prop2: "value4" }, + ]; + + const expectedOutput = [ + { Index: 1, prop1: "value1", prop2: "value2" }, + { Index: 2, prop1: "value3", prop2: "value4" }, + ]; + + const output = queryUtils.addIndexKey(input); + assert.deepStrictEqual(output, expectedOutput); + }); + + it("should add index key to single object", () => { + const input = { prop1: "value1", prop2: "value2" }; + + const expectedOutput = [{ Index: 1, prop1: "value1", prop2: "value2" }]; + + const output = queryUtils.addIndexKey(input); + assert.deepStrictEqual(output, expectedOutput); + }); + + it("should return empty array when input is empty array", () => { + const input = []; + + const expectedOutput = []; + + const output = queryUtils.addIndexKey(input); + assert.deepStrictEqual(output, expectedOutput); + }); + + it("should not add index key when it already exists", () => { + const input = [{ Index: 5, prop1: "value1", prop2: "value2" }]; + + const expectedOutput = [{ Index: 5, prop1: "value1", prop2: "value2" }]; + + const output = queryUtils.addIndexKey(input); + assert.deepStrictEqual(output, expectedOutput); + }); + + it("should add index key to non-array input", () => { + const input = "not an array"; + + const expectedOutput = [{ Index: 1, Value: "not an array" }]; + + const output = queryUtils.addIndexKey(input); + assert.deepStrictEqual(output, expectedOutput); + }); + }); + it("convertRows", () => { const rows = [ { From 6587024b0862eb17015c90f50b0d36f5d0403b71 Mon Sep 17 00:00:00 2001 From: Philip Carneiro Date: Tue, 23 Jan 2024 11:42:42 +0000 Subject: [PATCH 5/8] fix minor bugs and add test coverage --- src/utils/queryUtils.ts | 4 +-- test/suite/utils.test.ts | 57 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/utils/queryUtils.ts b/src/utils/queryUtils.ts index 82a86aeb..243bf5b7 100644 --- a/src/utils/queryUtils.ts +++ b/src/utils/queryUtils.ts @@ -109,9 +109,7 @@ export function handleScratchpadTableRes(results: DCDS | string): any { } let scratchpadResponse = results.rows; if (!Array.isArray(scratchpadResponse)) { - if (typeof scratchpadResponse === "string") { - scratchpadResponse = convertStringToArray(scratchpadResponse); - } + return results; } if (scratchpadResponse?.length !== 0) { scratchpadResponse = addIndexKey(scratchpadResponse); diff --git a/test/suite/utils.test.ts b/test/suite/utils.test.ts index b7ce505a..2911f6da 100644 --- a/test/suite/utils.test.ts +++ b/test/suite/utils.test.ts @@ -41,6 +41,11 @@ import { } from "../../src/utils/userInteraction"; import { validateUtils } from "../../src/utils/validateUtils"; import { DCDS } from "../../src/ipc/c"; +import { + DDateClass, + DDateTimeClass, + DTimestampClass, +} from "../../src/ipc/cClasses"; interface ITestItem extends vscode.QuickPickItem { id: number; @@ -713,6 +718,58 @@ describe("Utils", () => { const result = queryUtils.handleScratchpadTableRes(inputSample); assert.deepStrictEqual(result.rows, inputSample.rows); }); + + it("should return case results is string type", () => { + const result = queryUtils.handleScratchpadTableRes("test"); + assert.strictEqual(result, "test"); + }); + + it("should return same results case results.rows is undefined", () => { + inputSample.rows = undefined; + const result = queryUtils.handleScratchpadTableRes(inputSample); + assert.strictEqual(result, inputSample); + }); + + it("should return same results case results.rows is an empty array", () => { + const result = queryUtils.handleScratchpadTableRes(inputSample); + assert.strictEqual(result, inputSample); + }); + }); + + describe("checkIfIsQDateTypes", () => { + it("should return string representation of DTimestampClass instance", () => { + const input = { Value: new DTimestampClass(978350400000, 0) }; + const expectedOutput = input.Value.toString(); + + const output = queryUtils.checkIfIsQDateTypes(input); + assert.strictEqual(output, expectedOutput); + }); + + it("should return string representation of DDateTimeClass instance", () => { + const input = { Value: new DDateTimeClass(978350400000) }; + const expectedOutput = input.Value.toString(); + + const output = queryUtils.checkIfIsQDateTypes(input); + assert.strictEqual(output, expectedOutput); + }); + + it("should return string representation of DDateClass instance", () => { + const input = { Value: new DDateClass(978350400000) }; + const expectedOutput = input.Value.toString(); + + const output = queryUtils.checkIfIsQDateTypes(input); + assert.strictEqual(output, expectedOutput); + }); + + it("should return input as is when Value is not an instance of DTimestampClass, DDateTimeClass, or DDateClass", () => { + const input = { + Value: + "not an instance of DTimestampClass, DDateTimeClass, or DDateClass", + }; + + const output = queryUtils.checkIfIsQDateTypes(input); + assert.deepStrictEqual(output, input); + }); }); describe("addIndexKey", () => { From dc516446eebba064e5c805b078235b765820f9e4 Mon Sep 17 00:00:00 2001 From: Philip Carneiro Date: Tue, 23 Jan 2024 11:54:10 +0000 Subject: [PATCH 6/8] fix small issues --- src/utils/queryUtils.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/utils/queryUtils.ts b/src/utils/queryUtils.ts index 243bf5b7..34d2f2c0 100644 --- a/src/utils/queryUtils.ts +++ b/src/utils/queryUtils.ts @@ -17,7 +17,6 @@ import { ext } from "../extensionVariables"; import { DCDS, deserialize, isCompressed, uncompress } from "../ipc/c"; import { Parse } from "../ipc/parse.qlist"; import { ServerType } from "../models/server"; -import { convertStringToArray } from "./execution"; import { DDateClass, DDateTimeClass, DTimestampClass } from "../ipc/cClasses"; import { TypeBase } from "../ipc/typeBase"; @@ -186,7 +185,7 @@ export function generateQTypes(meta: { [key: string]: number }): any { const newMeta: { [key: string]: string } = {}; for (const key in meta) { const value = meta[key]; - newMeta[key] = TypeBase.typeNames[value] || `Unknown type: ${value}`; + newMeta[key] = TypeBase.typeNames[value] ?? `Unknown type: ${value}`; } return newMeta; } From c53dde8fd01077f4a30fc397ee72f6ff9549d828 Mon Sep 17 00:00:00 2001 From: Philip Carneiro Date: Tue, 23 Jan 2024 12:46:50 +0000 Subject: [PATCH 7/8] KXI-35930 for local q --- src/models/connection.ts | 7 ++++++ src/services/resultsPanelProvider.ts | 35 +++++++++++++++++++--------- src/utils/execution.ts | 13 +++++++---- src/webview/styles/resultsPanel.css | 4 ++++ 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/models/connection.ts b/src/models/connection.ts index 23ff1537..64f1ae17 100644 --- a/src/models/connection.ts +++ b/src/models/connection.ts @@ -23,6 +23,7 @@ export class Connection { private options: nodeq.ConnectionParameters; private connection?: nodeq.Connection; public connected: boolean; + private isError: boolean = false; private result?: string; constructor(connectionString: string, creds?: string[], tls?: boolean) { @@ -109,6 +110,7 @@ export class Connection { !!stringify, (err: Error, res: QueryResult) => { if (err) { + this.isError = true; this.result = handleQueryResults( err.toString(), QueryResultType.Error, @@ -123,6 +125,10 @@ export class Connection { const result = await this.waitForResult(); if (ext.resultsViewProvider.isVisible() && stringify) { + if (this.isError) { + this.isError = false; + return result; + } return convertStringToArray(result); } @@ -142,6 +148,7 @@ export class Connection { private handleQueryResult = (res: QueryResult): void => { if (res.errored) { + this.isError = true; this.result = handleQueryResults( res.error + (res.backtrace ? "\n" + res.backtrace : ""), QueryResultType.Error, diff --git a/src/services/resultsPanelProvider.ts b/src/services/resultsPanelProvider.ts index acb0e98b..4013f291 100644 --- a/src/services/resultsPanelProvider.ts +++ b/src/services/resultsPanelProvider.ts @@ -128,6 +128,12 @@ export class KdbResultsViewProvider implements WebviewViewProvider { }); } } else { + if (typeof results[0] === "string") { + return results.map((key: string) => { + const sanitizedKey = this.sanitizeString(key); + return { field: sanitizedKey, headerName: sanitizedKey }; + }); + } return Object.keys(results[0]).map((key: string) => { const sanitizedKey = this.sanitizeString(key); return { field: sanitizedKey, headerName: sanitizedKey }; @@ -140,17 +146,22 @@ export class KdbResultsViewProvider implements WebviewViewProvider { const queryResult = isInsights ? results.rows : results; const columnDefs = this.generateCoumnDefs(results, isInsights); - const rowData = queryResult.map((row: any) => { - for (const key in row) { - if (Object.prototype.hasOwnProperty.call(row, key)) { - row[key] = - row[key] !== undefined && row[key] !== null - ? this.sanitizeString(row[key]) - : ""; + let rowData = []; + if (!isInsights && typeof results[0] === "string") { + rowData = []; + } else { + rowData = queryResult.map((row: any) => { + for (const key in row) { + if (Object.prototype.hasOwnProperty.call(row, key)) { + row[key] = + row[key] !== undefined && row[key] !== null + ? this.sanitizeString(row[key]) + : ""; + } } - } - return row; - }); + return row; + }); + } if (rowData.length > 0) { ext.resultPanelCSV = this.convertToCsv(rowData).join("\n"); } @@ -224,7 +235,9 @@ export class KdbResultsViewProvider implements WebviewViewProvider { if (typeof queryResult === "string" || typeof queryResult === "number") { result = queryResult !== "" - ? `

${queryResult}

` + ? `

${queryResult + .toString() + .replace(/\n/g, "
")}

` : "

No results to show

"; } else if (queryResult) { isGrid = true; diff --git a/src/utils/execution.ts b/src/utils/execution.ts index 2b945f92..a3670073 100644 --- a/src/utils/execution.ts +++ b/src/utils/execution.ts @@ -42,7 +42,7 @@ export function runQFileTerminal(filename?: string): void { export function handleQueryResults( results: any, - type: QueryResultType + type: QueryResultType, ): string { let handledResult: string; switch (type) { @@ -177,7 +177,7 @@ function processLine( line: string, index: number, fieldLengths: number[], - fieldNames: string[] + fieldNames: string[], ): object { let start = 0; const obj: { [key: string]: any } = { Index: index + 1 }; @@ -207,10 +207,15 @@ export function convertStringToArray(str: string): any[] { return lines.flatMap((line, index) => fieldLengths.length > 0 ? processLine(line, index, fieldLengths, fieldNames) - : [] + : [], ); } + if (lines.length === 2 && lines[1].startsWith("---")) { + lines.splice(1, 1); + return lines[0].split(" ").filter((part) => part !== ""); + } + return lines .flatMap((line, index) => { const parts = line.split("|").map((part) => part.trim()); @@ -219,6 +224,6 @@ export function convertStringToArray(str: string): any[] { : processLineWithoutSeparator(line, index); }) .filter( - (obj) => !("Value" in obj && (obj.Value as string).startsWith("-")) + (obj) => !("Value" in obj && (obj.Value as string).startsWith("-")), ); } diff --git a/src/webview/styles/resultsPanel.css b/src/webview/styles/resultsPanel.css index 1ac18633..f4c9a235 100644 --- a/src/webview/styles/resultsPanel.css +++ b/src/webview/styles/resultsPanel.css @@ -23,3 +23,7 @@ body { .results-header-datagrid { background: #00000050; } + +.results-txt { + white-space: pre; +} From bd69b5d4387c2ffb5c66f87342ca47e837ba1cfc Mon Sep 17 00:00:00 2001 From: Philip Carneiro Date: Tue, 23 Jan 2024 12:49:57 +0000 Subject: [PATCH 8/8] fix tests --- test/suite/panels.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/suite/panels.test.ts b/test/suite/panels.test.ts index ed44369f..5695ee09 100644 --- a/test/suite/panels.test.ts +++ b/test/suite/panels.test.ts @@ -320,9 +320,9 @@ describe("WebPanels", () => { stub.restore(); }); - it("returns no results", () => { + it("returns string results", () => { const input = "Test"; - const expectedOutput = `

Test

`; + const expectedOutput = `

Test

`; const actualOutput = resultsPanel["_getWebviewContent"](input); assert.strictEqual(typeof actualOutput, "string"); assert.ok(actualOutput.includes(expectedOutput));