diff --git a/src/Common/ErrorHandlingUtils.ts b/src/Common/ErrorHandlingUtils.ts index f96780065..2cd3409b5 100644 --- a/src/Common/ErrorHandlingUtils.ts +++ b/src/Common/ErrorHandlingUtils.ts @@ -14,12 +14,21 @@ export interface HandleErrorOptions { redactedError?: string | ARMError | Error; } +export const stringifyError = function (err: any) { + var plainObject: { [key: string]: any } = {}; + Object.getOwnPropertyNames(err).forEach(function (key) { + plainObject[key] = err[key]; + }); + return JSON.stringify(plainObject, null, '\r\n'); +}; + export const handleError = ( error: string | ARMError | Error, area: string, consoleErrorPrefix?: string, options?: HandleErrorOptions, ): void => { + console.log("{{cdbp}} in handleError(): raw error: " + stringifyError(error)); //CTODO in case a stray error happens const errorMessage = getErrorMessage(error); const errorCode = error instanceof ARMError ? error.code : undefined; @@ -44,7 +53,7 @@ export const handleError = ( export const getErrorMessage = (error: string | Error = ""): string => { let errorMessage = typeof error === "string" ? error : error.message; if (!errorMessage) { - errorMessage = JSON.stringify(error); + errorMessage = stringifyError(error); } return replaceKnownError(errorMessage); }; diff --git a/src/Common/dataAccess/readDatabases.ts b/src/Common/dataAccess/readDatabases.ts index e9f43e65f..f460d5546 100644 --- a/src/Common/dataAccess/readDatabases.ts +++ b/src/Common/dataAccess/readDatabases.ts @@ -11,7 +11,7 @@ import { listGremlinDatabases } from "../../Utils/arm/generatedClients/cosmos/gr import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources"; import { listSqlDatabases } from "../../Utils/arm/generatedClients/cosmos/sqlResources"; import { client } from "../CosmosClient"; -import { handleError } from "../ErrorHandlingUtils"; +import { handleError, stringifyError } from "../ErrorHandlingUtils"; export async function readDatabases(): Promise { let databases: DataModels.Database[]; @@ -26,6 +26,7 @@ export async function readDatabases(): Promise { (userContext.fabricContext?.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY]).resourceTokenInfo .resourceTokens ) { + console.log("{{cdbp}} in readDatabases(): isFabricMirroredKey && has resourceTokens"); //CTODO should not get here const tokensData = (userContext.fabricContext.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY]) .resourceTokenInfo; @@ -59,6 +60,7 @@ export async function readDatabases(): Promise { clearMessage(); return databases; } else if (isFabricNative() && userContext.fabricContext?.databaseName) { + console.log("{{cdbp}} in readDatabases(): isFabricNative"); //CTODO should not get here const databaseId = userContext.fabricContext.databaseName; databases = [ { @@ -81,9 +83,15 @@ export async function readDatabases(): Promise { userContext.apiType !== "Tables" && !isFabric() ) { + console.log("{{cdbp}} in readDatabases(): authType == AAD, enableSDKOperations, apiType != Tables, !isFabric"); + console.log("{{cdbp}} in readDatabases(): databaseaccount: " + userContext.databaseAccount); + console.log("{{cdbp}} in readDatabases(): calling readDatabasesWithARM"); databases = await readDatabasesWithARM(); + console.log("{{cdbp}} in readDatabases(): done readDatabasesWithARM"); } else { + console.log("{{cdbp}} in readDatabases(): calling SDK"); const sdkResponse = await client().databases.readAll().fetchAll(); + console.log("{{cdbp}} in readDatabases(): done SDK"); databases = sdkResponse.resources as DataModels.Database[]; } } catch (error) { @@ -108,22 +116,30 @@ export async function readDatabasesWithARM(accountOverride?: { const accountName = accountOverride?.accountName ?? userContext?.databaseAccount?.name ?? ""; const apiType = accountOverride?.apiType ?? userContext.apiType; - switch (apiType) { - case "SQL": - rpResponse = await listSqlDatabases(subscriptionId, resourceGroup, accountName); - break; - case "Mongo": - rpResponse = await listMongoDBDatabases(subscriptionId, resourceGroup, accountName); - break; - case "Cassandra": - rpResponse = await listCassandraKeyspaces(subscriptionId, resourceGroup, accountName); - break; - case "Gremlin": - rpResponse = await listGremlinDatabases(subscriptionId, resourceGroup, accountName); - break; - default: - throw new Error(`Unsupported default experience type: ${apiType}`); - } + try { + switch (apiType) { + case "SQL": + console.log("{{cdbp}} in readDatabasesWithARM(): calling listSqlDatabases"); + rpResponse = await listSqlDatabases(subscriptionId, resourceGroup, accountName); + console.log("{{cdbp}} in readDatabasesWithARM(): done listSqlDatabases"); + break; + case "Mongo": + rpResponse = await listMongoDBDatabases(subscriptionId, resourceGroup, accountName); + break; + case "Cassandra": + rpResponse = await listCassandraKeyspaces(subscriptionId, resourceGroup, accountName); + break; + case "Gremlin": + rpResponse = await listGremlinDatabases(subscriptionId, resourceGroup, accountName); + break; + default: + throw new Error(`Unsupported default experience type: ${apiType}`); + } - return rpResponse?.value?.map((database) => database.properties?.resource as DataModels.Database) ?? []; + console.log("{{cdbp}} in readDatabasesWithARM(): response: " + JSON.stringify(rpResponse)); + return rpResponse?.value?.map((database) => database.properties?.resource as DataModels.Database) ?? []; + } catch (error) { + console.log("{{cdbp}} in readDatabasesWithARM(): ERROR: " + stringifyError(error)); + throw error; + } } diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index 23f6674df..223609d2b 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -23,7 +23,7 @@ import { AuthType } from "../AuthType"; import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer"; import * as Constants from "../Common/Constants"; import { Areas, ConnectionStatusType, HttpStatusCodes, Notebook } from "../Common/Constants"; -import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils"; +import { getErrorMessage, getErrorStack, handleError, stringifyError } from "../Common/ErrorHandlingUtils"; import * as Logger from "../Common/Logger"; import { QueriesClient } from "../Common/QueriesClient"; import { readCollection } from "../Common/dataAccess/readCollection"; @@ -287,7 +287,7 @@ export default class Explorer { "We were unable to establish authorization for this account, due to pop-ups being disabled in the browser.\nPlease enable pop-ups for this site and try again", ); } else { - const errorJson = JSON.stringify(error); + const errorJson = stringifyError(error); logConsoleError( `Failed to perform authorization for this account, due to the following error: \n${errorJson}`, ); @@ -401,19 +401,27 @@ export default class Explorer { }, startKey, ); + console.log("{{cdbp}} in refreshAllDatabases(): done readDatabases"); const currentDatabases = useDatabases.getState().databases; + console.log("{{cdbp}} in refreshAllDatabases(): currentDatabases: " + currentDatabases); const deltaDatabases = this.getDeltaDatabases(databases, currentDatabases); + console.log("{{cdbp}} in refreshAllDatabases(): deltaDatabases: " + deltaDatabases); let updatedDatabases = currentDatabases.filter( (database) => !deltaDatabases.toDelete.some((deletedDatabase) => deletedDatabase.id() === database.id()), ); + console.log("{{cdbp}} in refreshAllDatabases(): updatedDatabases after filter: " + updatedDatabases); updatedDatabases = [...updatedDatabases, ...deltaDatabases.toAdd].sort((db1, db2) => db1.id().localeCompare(db2.id()), ); + console.log("{{cdbp}} in refreshAllDatabases(): updatedDatabases after sort: " + updatedDatabases); useDatabases.setState({ databases: updatedDatabases, databasesFetchedSuccessfully: true }); scenarioMonitor.completePhase(MetricScenario.DatabaseLoad, ApplicationMetricPhase.DatabasesFetched); + console.log("{{cdbp}} in refreshAllDatabases(): calling refreshAndExpandNewDatabases"); await this.refreshAndExpandNewDatabases(deltaDatabases.toAdd, updatedDatabases); + console.log("{{cdbp}} in refreshAllDatabases(): done refreshAndExpandNewDatabases"); } catch (error) { + console.log("{{cdbp}} in refreshAllDatabases(): ERROR: " + stringifyError(error)); //CTODO this should be logged already but just in case const errorMessage = getErrorMessage(error); TelemetryProcessor.traceFailure( Action.LoadDatabases, @@ -603,6 +611,7 @@ export default class Explorer { ? databases : databases.filter((db) => db.isDatabaseExpanded() || db.id() === Constants.SavedQueries.DatabaseName); + console.log("{{cdbp}} in refreshAndExpandNewDatabases(): databasesToLoad: " + databasesToLoad); const startKey: number = TelemetryProcessor.traceStart(Action.LoadCollections, { dataExplorerArea: Constants.Areas.ResourceTree, }); @@ -611,6 +620,7 @@ export default class Explorer { try { await Promise.all( databasesToLoad.map(async (database: ViewModels.Database) => { + console.log("{{cdbp}} in refreshAndExpandNewDatabases(): loadCollections for database: " + database.id); await database.loadCollections(true); const isNewDatabase: boolean = _.some(newDatabases, (db: ViewModels.Database) => db.id() === database.id()); if (isNewDatabase) { @@ -630,6 +640,7 @@ export default class Explorer { // Start DatabaseTreeRendered — React render cycle will complete it in ResourceTree scenarioMonitor.startPhase(MetricScenario.DatabaseLoad, ApplicationMetricPhase.DatabaseTreeRendered); } catch (error) { + console.log("{{cdbp}} in refreshAndExpandNewDatabases(): ERROR: " + stringifyError(error)); //CTODO this should be logged already but just in case TelemetryProcessor.traceFailure( Action.LoadCollections, { diff --git a/src/Explorer/Tables/TableDataClient.ts b/src/Explorer/Tables/TableDataClient.ts index 631e19e88..05d8ab448 100644 --- a/src/Explorer/Tables/TableDataClient.ts +++ b/src/Explorer/Tables/TableDataClient.ts @@ -4,7 +4,7 @@ import Q from "q"; import { AuthType } from "../../AuthType"; import * as Constants from "../../Common/Constants"; import { CassandraProxyAPIs } from "../../Common/Constants"; -import { handleError } from "../../Common/ErrorHandlingUtils"; +import { handleError, stringifyError } from "../../Common/ErrorHandlingUtils"; import * as HeadersUtility from "../../Common/HeadersUtility"; import { createDocument } from "../../Common/dataAccess/createDocument"; import { deleteDocument } from "../../Common/dataAccess/deleteDocument"; @@ -32,7 +32,7 @@ export interface CassandraTableKey { } export abstract class TableDataClient { - constructor() {} + constructor() { } public abstract createDocument( collection: ViewModels.Collection, @@ -172,7 +172,7 @@ export class CassandraAPIDataClient extends TableDataClient { deferred.resolve(entity); }, (error) => { - const errorText = error.responseJSON?.message ?? JSON.stringify(error); + const errorText = error.responseJSON?.message ?? stringifyError(error); handleError(errorText, "AddRowCassandra", `Error while adding new row to table ${collection.id()}`); deferred.reject(errorText); }, @@ -361,7 +361,7 @@ export class CassandraAPIDataClient extends TableDataClient { deferred.resolve(); }, (error) => { - const errorText = error.responseJSON?.message ?? JSON.stringify(error); + const errorText = error.responseJSON?.message ?? stringifyError(error); handleError( errorText, "CreateKeyspaceCassandra", @@ -400,7 +400,7 @@ export class CassandraAPIDataClient extends TableDataClient { deferred.resolve(); }, (error) => { - const errorText = error.responseJSON?.message ?? JSON.stringify(error); + const errorText = error.responseJSON?.message ?? stringifyError(error); handleError( errorText, "CreateTableCassandra", @@ -450,7 +450,7 @@ export class CassandraAPIDataClient extends TableDataClient { deferred.resolve(data); }, (error: any) => { - const errorText = error.responseJSON?.message ?? JSON.stringify(error); + const errorText = error.responseJSON?.message ?? stringifyError(error); handleError(errorText, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`); deferred.reject(errorText); }, @@ -492,7 +492,7 @@ export class CassandraAPIDataClient extends TableDataClient { deferred.resolve(data.columns); }, (error: any) => { - const errorText = error.responseJSON?.message ?? JSON.stringify(error); + const errorText = error.responseJSON?.message ?? stringifyError(error); handleError(errorText, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`); deferred.reject(errorText); }, diff --git a/src/Utils/AuthorizationUtils.ts b/src/Utils/AuthorizationUtils.ts index 21bd23195..b28336626 100644 --- a/src/Utils/AuthorizationUtils.ts +++ b/src/Utils/AuthorizationUtils.ts @@ -1,5 +1,6 @@ import * as msal from "@azure/msal-browser"; import { getEnvironmentScopeEndpoint } from "Common/EnvironmentUtility"; +import { stringifyError } from "Common/ErrorHandlingUtils"; import { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants"; import { hasProxyServer, isDataplaneRbacSupported } from "Utils/APITypeUtils"; import { AuthType } from "../AuthType"; @@ -154,9 +155,9 @@ export async function acquireMsalTokenForAccount( traceFailure(Action.SignInAad, { request: JSON.stringify(loginRequest), acquireTokenType: silent ? "silent" : "interactive", - errorMessage: JSON.stringify(error), + errorMessage: stringifyError(error), }); - traceFailure(Action.AcquireMsalToken, { error: JSON.stringify(error) }, msalStartKey); + traceFailure(Action.AcquireMsalToken, { error: stringifyError(error) }, msalStartKey); // Mark expected failure for health metrics so timeout emits healthy if (isExpectedError(error)) { scenarioMonitor.markExpectedFailure(); diff --git a/src/Utils/arm/generatedClients/cosmos/sqlResources.ts b/src/Utils/arm/generatedClients/cosmos/sqlResources.ts index 608d9a953..a78fe5664 100644 --- a/src/Utils/arm/generatedClients/cosmos/sqlResources.ts +++ b/src/Utils/arm/generatedClients/cosmos/sqlResources.ts @@ -6,9 +6,10 @@ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/DocumentDB/preview/2025-11-01-preview/cosmos-db.json */ +import { stringifyError } from "Common/ErrorHandlingUtils"; +import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -import { configContext } from "../../../../ConfigContext"; const apiVersion = "2025-11-01-preview"; /* Lists the SQL databases under an existing Azure Cosmos DB database account. */ @@ -18,7 +19,14 @@ export async function listSqlDatabases( accountName: string, ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases`; - return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "GET", apiVersion }); + console.log("{{cdbp}} in listSqlDatabases(): path: " + path); + try { + console.log("{{cdbp}} in listSqlDatabases(): calling armRequest"); + return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "GET", apiVersion }); + } catch (error) { + console.log("{{cdbp}} in listSqlDatabases(): ERROR: " + stringifyError(error)); + throw error; + } } /* Gets the SQL database under an existing Azure Cosmos DB database account with the provided name. */ diff --git a/src/hooks/useAADAuth.ts b/src/hooks/useAADAuth.ts index 7a5bd2086..c98a6e8b5 100644 --- a/src/hooks/useAADAuth.ts +++ b/src/hooks/useAADAuth.ts @@ -1,5 +1,6 @@ import * as msal from "@azure/msal-browser"; import { useBoolean } from "@fluentui/react-hooks"; +import { stringifyError } from "Common/ErrorHandlingUtils"; import * as React from "react"; import { ConfigContext } from "../ConfigContext"; import { @@ -77,7 +78,7 @@ export function useAADAuth(config?: ConfigContext): ReturnType { localStorage.setItem("cachedTenantId", response.tenantId); } catch (error) { setAuthFailure({ - failureMessage: `Login failed: ${JSON.stringify(error)}`, + failureMessage: `Login failed: ${stringifyError(error)}`, }); } }, [msalInstance, config]); @@ -111,7 +112,7 @@ export function useAADAuth(config?: ConfigContext): ReturnType { localStorage.setItem("cachedTenantId", response.tenantId); } catch (error) { setAuthFailure({ - failureMessage: `Tenant switch failed: ${JSON.stringify(error)}`, + failureMessage: `Tenant switch failed: ${stringifyError(error)}`, }); } }, @@ -144,7 +145,7 @@ export function useAADAuth(config?: ConfigContext): ReturnType { failureLinkAction: acquireTokens, }); } else { - const errorJson = JSON.stringify(error); + const errorJson = stringifyError(error); setAuthFailure({ failureMessage: `We were unable to establish authorization for this account, due to the following error: \n${errorJson}`, });