Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 89 additions & 41 deletions src/classes/insightsConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { JwtUser } from "../models/jwt_user";
import { MetaInfoType, MetaObject, MetaObjectPayload } from "../models/meta";
import { StructuredTextResults } from "../models/queryResult";
import { ScratchpadRequestBody } from "../models/scratchpad";
import { ScratchpadResult } from "../models/scratchpadResult";
import { UDARequestBody } from "../models/uda";
import {
getCurrentToken,
Expand All @@ -49,6 +50,27 @@ import { retrieveUDAtoCreateReqBody } from "../utils/uda";

const logger = "insightsConnection";

/**
* Builds a human-readable message from an error thrown by an Insights REST
* request. Handles the cases where the gateway/coordinator goes away mid-query:
* a 500 with a plain-text reason (e.g. "Coordinator connection has closed"), a
* 502 with an HTML error page, or a dropped socket with no response at all.
*/
export function extractInsightsRequestError(error: any): string {
const response = error?.response;
if (response) {
const data = response.data;
const detail =
typeof data === "string" && data.trim() && !data.trim().startsWith("<")
? data.trim()
: response.statusText || "";
return `Request failed with status ${response.status}${
detail ? `: ${detail}` : ""
}`;
}
return error?.message ?? String(error);
}

export class InsightsConnection {
public connected: boolean;
public connLabel: string;
Expand Down Expand Up @@ -617,29 +639,43 @@ export class InsightsConnection {
params: { url: options.url },
});

return await axios(options).then((response: any) => {
if (!token?.isCancellationRequested) {
if (response.data.error) {
return await axios(options)
.then((response: any) => {
if (!token?.isCancellationRequested) {
if (response.data.error) {
notify(
`Error occured while populating scratchpad: ${response.data.errorMsg || "Unknown error"}`,
silent ? MessageKind.DEBUG : MessageKind.ERROR,
{
logger,
params: { status: response.status },
},
);
} else {
notify(
`Scratchpad variable (${variableName}) populated.`,
silent ? MessageKind.DEBUG : MessageKind.INFO,
{
logger,
params: { status: response.status },
},
);
}
}
})
.catch((error: any) => {
if (!token?.isCancellationRequested) {
const errorMsg = extractInsightsRequestError(error);
notify(
"Error occured while populating scratchpad.",
`Error occured while populating scratchpad: ${errorMsg}`,
silent ? MessageKind.DEBUG : MessageKind.ERROR,
{
logger,
params: { status: response.status },
},
);
} else {
notify(
`Scratchpad variable (${variableName}) populated.`,
silent ? MessageKind.DEBUG : MessageKind.INFO,
{
logger,
params: { status: response.status },
params: { status: error?.response?.status },
},
);
}
}
});
});
} else {
this.noConnectionOrEndpoints();
}
Expand Down Expand Up @@ -775,35 +811,47 @@ export class InsightsConnection {
params: { url: options.url },
});

return await axios(options).then((response: any) => {
if (response.data.error) {
return response.data;
} else if (query === "") {
notify(
`Scratchpad created for connection: ${this.connLabel}.`,
MessageKind.DEBUG,
{ logger },
);
} else {
notify(`Status: ${response.status}`, MessageKind.DEBUG, {
logger,
});
if (!response.data.error) {
if (isTableView) {
if (
this.insightsVersion &&
isBaseVersionGreaterOrEqual(this.insightsVersion, "1.12")
) {
response.data = JSON.parse(
response.data.data,
) as StructuredTextResults;
return await axios(options)
.then((response: any) => {
if (response.data.error) {
return response.data;
} else if (query === "") {
notify(
`Scratchpad created for connection: ${this.connLabel}.`,
MessageKind.DEBUG,
{ logger },
);
} else {
notify(`Status: ${response.status}`, MessageKind.DEBUG, {
logger,
});
if (!response.data.error) {
if (isTableView) {
if (
this.insightsVersion &&
isBaseVersionGreaterOrEqual(this.insightsVersion, "1.12")
) {
response.data = JSON.parse(
response.data.data,
) as StructuredTextResults;
}
}
return response.data;
}
return response.data;
}
return response.data;
}
});
})
.catch((error: any) => {
const errorMsg = extractInsightsRequestError(error);
notify(`Scratchpad query failed: ${errorMsg}`, MessageKind.DEBUG, {
logger,
params: { status: error?.response?.status },
});
return {
error: true,
errorMsg,
} as ScratchpadResult;
});
} else {
this.noConnectionOrEndpoints();
}
Expand Down
63 changes: 63 additions & 0 deletions test/suite/classes/insightsConnection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright (c) 1998-2026 KX Systems Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/

import assert from "assert";

import { extractInsightsRequestError } from "../../../src/classes/insightsConnection";

describe("insightsConnection", () => {
describe("extractInsightsRequestError", () => {
it("should surface a plain-text 500 body (coordinator killed)", () => {
const error = {
response: {
status: 500,
statusText: "Internal Server Error",
data: "Coordinator connection has closed",
},
};
assert.strictEqual(
extractInsightsRequestError(error),
"Request failed with status 500: Coordinator connection has closed",
);
});

it("should fall back to statusText for an HTML 502 body (gateway killed)", () => {
const error = {
response: {
status: 502,
statusText: "Bad Gateway",
data: "<html><head><title>502 Bad Gateway</title></head></html>",
},
};
assert.strictEqual(
extractInsightsRequestError(error),
"Request failed with status 502: Bad Gateway",
);
});

it("should use the error message when there is no response (dropped socket)", () => {
const error = { message: "socket hang up" };
assert.strictEqual(
extractInsightsRequestError(error),
"socket hang up",
);
});

it("should stringify an unknown error with no message or response", () => {
assert.strictEqual(
extractInsightsRequestError("boom"),
"boom",
);
});
});
});
Loading