From 579312bde4e663a70e38580d9aaed601d1f4b676 Mon Sep 17 00:00:00 2001 From: Niall Farrell Date: Tue, 10 Dec 2024 16:52:55 +0000 Subject: [PATCH 1/2] KXI-52672 Show stack trace on errors against Insights scratchpad --- src/classes/insightsConnection.ts | 14 +++++++++++- src/models/scratchpadResult.ts | 9 ++++++++ src/utils/queryUtils.ts | 36 +++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/classes/insightsConnection.ts b/src/classes/insightsConnection.ts index f740b528..174b4b72 100644 --- a/src/classes/insightsConnection.ts +++ b/src/classes/insightsConnection.ts @@ -27,7 +27,11 @@ import { DataSourceFiles, DataSourceTypes } from "../models/dataSource"; import { jwtDecode } from "jwt-decode"; import { JwtUser } from "../models/jwt_user"; import { Telemetry } from "../utils/telemetryClient"; -import { handleScratchpadTableRes, handleWSResults } from "../utils/queryUtils"; +import { + formatStacktrace, + handleScratchpadTableRes, + handleWSResults, +} from "../utils/queryUtils"; import { compareVersions, invalidUsernameJWT, @@ -464,6 +468,14 @@ export class InsightsConnection { }) .then((response: any) => { if (response.data.error) { + // let msg = `[SCRATCHPAD] Error occured while executing scratchpad: ${response.data.errorMsg}`; + + // if (response.data.stacktrace) { + // msg = msg + "\n" + formatStacktrace(response.data.stacktrace); + // } + + // kdbOutputLog(msg, "ERROR"); + return response.data; } else if (query === "") { kdbOutputLog( diff --git a/src/models/scratchpadResult.ts b/src/models/scratchpadResult.ts index 8ff1bf3d..4323c971 100644 --- a/src/models/scratchpadResult.ts +++ b/src/models/scratchpadResult.ts @@ -11,9 +11,18 @@ * specific language governing permissions and limitations under the License. */ +type StacktraceItem = { + isNested: boolean; + name: string; + text: string[]; +}; + +export type ScratchpadStacktrace = StacktraceItem[]; + export type ScratchpadResult = { data: string; error: boolean; errorMsg: string; sessionID: string; + stacktrace: ScratchpadStacktrace; }; diff --git a/src/utils/queryUtils.ts b/src/utils/queryUtils.ts index f50dee9a..1361d935 100644 --- a/src/utils/queryUtils.ts +++ b/src/utils/queryUtils.ts @@ -22,6 +22,7 @@ import { DataSourceFiles, DataSourceTypes } from "../models/dataSource"; import { QueryHistory } from "../models/queryHistory"; import { kdbOutputLog } from "./core"; import { ServerType } from "../models/connectionsModels"; +import { ScratchpadStacktrace } from "../models/scratchpadResult"; export function sanitizeQuery(query: string): string { if (query[0] === "`") { @@ -360,3 +361,38 @@ export function addQueryHistory( ext.queryHistoryProvider.refresh(); } + +export function formatStacktrace(stacktrace: ScratchpadStacktrace) { + const trace = stacktrace.map((frame) => { + let lines = frame.text[0].split("\n"); + let preline = ""; + // We need to account for the possibility that the error + // occurs in a piece of code containing newlines, so we split + // up the text into lines and inject the caret into the correct + // location. + preline = lines.pop() as string; + const caretline = Array(preline.length).fill(" ").join("") + "^"; + const postlines = (preline + frame.text[1]).split("\n"); + postlines.splice(1, 0, caretline); + lines = lines.concat(postlines); + return { + ...frame, + lines, + }; + }); + + return trace + .map((t, i) => { + let str = "[" + (trace.length - 1 - i) + "] " + t.name; + + if (t.isNested) { + str += " @ "; + } + + const gutter = " ".repeat(str.length); + str += t.lines.map((l, i) => (i > 0 ? gutter + l : l)).join("\n"); + + return str; + }) + .join("\n"); +} From 1cbc79eca8cfc95c5877d2c1ab0e159169ad1e24 Mon Sep 17 00:00:00 2001 From: Niall Farrell Date: Mon, 16 Dec 2024 12:37:10 +0000 Subject: [PATCH 2/2] Update after error log change, add tests --- src/classes/insightsConnection.ts | 14 +-------- src/commands/serverCommand.ts | 21 ++++++++++++-- src/models/scratchpadResult.ts | 2 +- src/utils/queryUtils.ts | 47 +++++++++++++++---------------- test/suite/utils.test.ts | 28 ++++++++++++++++++ 5 files changed, 70 insertions(+), 42 deletions(-) diff --git a/src/classes/insightsConnection.ts b/src/classes/insightsConnection.ts index 174b4b72..f740b528 100644 --- a/src/classes/insightsConnection.ts +++ b/src/classes/insightsConnection.ts @@ -27,11 +27,7 @@ import { DataSourceFiles, DataSourceTypes } from "../models/dataSource"; import { jwtDecode } from "jwt-decode"; import { JwtUser } from "../models/jwt_user"; import { Telemetry } from "../utils/telemetryClient"; -import { - formatStacktrace, - handleScratchpadTableRes, - handleWSResults, -} from "../utils/queryUtils"; +import { handleScratchpadTableRes, handleWSResults } from "../utils/queryUtils"; import { compareVersions, invalidUsernameJWT, @@ -468,14 +464,6 @@ export class InsightsConnection { }) .then((response: any) => { if (response.data.error) { - // let msg = `[SCRATCHPAD] Error occured while executing scratchpad: ${response.data.errorMsg}`; - - // if (response.data.stacktrace) { - // msg = msg + "\n" + formatStacktrace(response.data.stacktrace); - // } - - // kdbOutputLog(msg, "ERROR"); - return response.data; } else if (query === "") { kdbOutputLog( diff --git a/src/commands/serverCommand.ts b/src/commands/serverCommand.ts index ce3d854f..396a897c 100644 --- a/src/commands/serverCommand.ts +++ b/src/commands/serverCommand.ts @@ -52,7 +52,11 @@ import { refreshDataSourcesPanel } from "../utils/dataSource"; import { decodeQUTF } from "../utils/decode"; import { ExecutionConsole } from "../utils/executionConsole"; import { openUrl } from "../utils/openUrl"; -import { checkIfIsDatasource, addQueryHistory } from "../utils/queryUtils"; +import { + checkIfIsDatasource, + addQueryHistory, + formatScratchpadStacktrace, +} from "../utils/queryUtils"; import { validateServerAlias, validateServerName, @@ -1184,9 +1188,20 @@ export function writeScratchpadResult( duration: string, connVersion: number, ): void { + let errorMsg; + + if (result.error) { + errorMsg = result.errorMsg; + + if (result.stacktrace) { + errorMsg = + errorMsg + "\n" + formatScratchpadStacktrace(result.stacktrace); + } + } + if (ext.isResultsTabVisible) { writeQueryResultsToView( - result.error ? result.errorMsg : result, + errorMsg ?? result, query, connLabel, executorName, @@ -1199,7 +1214,7 @@ export function writeScratchpadResult( ); } else { writeQueryResultsToConsole( - result.error ? result.errorMsg : result.data, + errorMsg ?? result.data, query, connLabel, executorName, diff --git a/src/models/scratchpadResult.ts b/src/models/scratchpadResult.ts index 4323c971..997cb7f2 100644 --- a/src/models/scratchpadResult.ts +++ b/src/models/scratchpadResult.ts @@ -24,5 +24,5 @@ export type ScratchpadResult = { error: boolean; errorMsg: string; sessionID: string; - stacktrace: ScratchpadStacktrace; + stacktrace?: ScratchpadStacktrace; }; diff --git a/src/utils/queryUtils.ts b/src/utils/queryUtils.ts index 1361d935..7174b09a 100644 --- a/src/utils/queryUtils.ts +++ b/src/utils/queryUtils.ts @@ -362,35 +362,32 @@ export function addQueryHistory( ext.queryHistoryProvider.refresh(); } -export function formatStacktrace(stacktrace: ScratchpadStacktrace) { - const trace = stacktrace.map((frame) => { - let lines = frame.text[0].split("\n"); - let preline = ""; - // We need to account for the possibility that the error - // occurs in a piece of code containing newlines, so we split - // up the text into lines and inject the caret into the correct - // location. - preline = lines.pop() as string; - const caretline = Array(preline.length).fill(" ").join("") + "^"; - const postlines = (preline + frame.text[1]).split("\n"); - postlines.splice(1, 0, caretline); - lines = lines.concat(postlines); - return { - ...frame, - lines, - }; - }); - - return trace - .map((t, i) => { - let str = "[" + (trace.length - 1 - i) + "] " + t.name; - - if (t.isNested) { +export function formatScratchpadStacktrace(stacktrace: ScratchpadStacktrace) { + return stacktrace + .map((frame, i) => { + let lines = frame.text[0].split("\n"); + let preline = ""; + // We need to account for the possibility that the error + // occurs in a piece of code containing newlines, so we split + // up the text into lines and inject the caret into the correct + // location. + preline = lines.pop() as string; + const caretline = Array(preline.length).fill(" ").join("") + "^"; + const postlines = (preline + frame.text[1]).split("\n"); + postlines.splice(1, 0, caretline); + lines = lines.concat(postlines); + + // main line of trace + let str = "[" + (stacktrace.length - 1 - i) + "] " + frame.name; + + // add indicator for nested anonymous functions + if (frame.isNested) { str += " @ "; } + // add gutter to align other lines with the first one const gutter = " ".repeat(str.length); - str += t.lines.map((l, i) => (i > 0 ? gutter + l : l)).join("\n"); + str += lines.map((l, i) => (i > 0 ? gutter + l : l)).join("\n"); return str; }) diff --git a/test/suite/utils.test.ts b/test/suite/utils.test.ts index e63442c7..ddcfadbf 100644 --- a/test/suite/utils.test.ts +++ b/test/suite/utils.test.ts @@ -901,6 +901,34 @@ describe("Utils", () => { ); assert.strictEqual(ext.kdbQueryHistoryList.length, 1); }); + + it("should format a Scratchpad stacktrace correctly", () => { + const stacktrace = [ + { name: "g", isNested: false, text: ["{a:x*2;a", "+y}"] }, + { name: "f", isNested: false, text: ["{", "g[x;2#y]}"] }, + { name: "", isNested: false, text: ["", 'f[3;"hello"]'] }, + ]; + + const formatted = queryUtils.formatScratchpadStacktrace(stacktrace); + assert.strictEqual( + formatted, + '[2] g{a:x*2;a+y}\n ^\n[1] f{g[x;2#y]}\n ^\n[0] f[3;"hello"]\n ^', + ); + }); + + it("should format a Scratchpad stacktrace with nested function correctly", () => { + const stacktrace = [ + { name: "f", isNested: true, text: ["{a:x*2;a", "+y}"] }, + { name: "f", isNested: false, text: ["{", "{a:x*2;a+y}[x;2#y]}"] }, + { name: "", isNested: false, text: ["", 'f[3;"hello"]'] }, + ]; + + const formatted = queryUtils.formatScratchpadStacktrace(stacktrace); + assert.strictEqual( + formatted, + '[2] f @ {a:x*2;a+y}\n ^\n[1] f{{a:x*2;a+y}[x;2#y]}\n ^\n[0] f[3;"hello"]\n ^', + ); + }); }); describe("selectDSType", () => {