From 87c600809f9568aa72d235436962f54bf5c18299 Mon Sep 17 00:00:00 2001 From: Asier Isayas Date: Wed, 19 Aug 2026 15:19:21 -0700 Subject: [PATCH] Move the Portal Backend connection string calls out of Helpers Helpers holds pure string utilities, and every other *Client in the repo sits at its domain root, so name the module for the endpoints it wraps and place it beside HostedUtils. Also drop the branch's ConnectScreen.less tooltip restyle, which was unrelated to connection string login. --- src/HostedExplorer.test.tsx | 4 +-- src/HostedExplorer.tsx | 2 +- .../Hosted/Components/ConnectExplorer.tsx | 5 +++- src/Platform/Hosted/ConnectScreen.less | 26 ++++++----------- src/Platform/Hosted/HostedUtils.ts | 4 +-- ...=> PortalBackendConnectionStringClient.ts} | 28 ++++--------------- tsconfig.strict.json | 4 +-- 7 files changed, 24 insertions(+), 49 deletions(-) rename src/Platform/Hosted/{Helpers/PortalBackendClient.ts => PortalBackendConnectionStringClient.ts} (63%) diff --git a/src/HostedExplorer.test.tsx b/src/HostedExplorer.test.tsx index 4043f388b..0db2e45ce 100644 --- a/src/HostedExplorer.test.tsx +++ b/src/HostedExplorer.test.tsx @@ -1,6 +1,6 @@ jest.mock("./hooks/useAADAuth"); jest.mock("./hooks/useConfig"); -jest.mock("./Platform/Hosted/Helpers/PortalBackendClient"); +jest.mock("./Platform/Hosted/PortalBackendConnectionStringClient"); jest.mock("./Platform/Hosted/Components/ConnectExplorer"); jest.mock("./Shared/appInsights"); jest.mock("./Platform/Hosted/Components/AccountSwitcher", () => ({ @@ -29,7 +29,7 @@ import { useAADAuth } from "./hooks/useAADAuth"; import { useConfig } from "./hooks/useConfig"; import { App } from "./HostedExplorer"; import { ConnectExplorer } from "./Platform/Hosted/Components/ConnectExplorer"; -import { fetchAccessData, fetchEncryptedToken } from "./Platform/Hosted/Helpers/PortalBackendClient"; +import { fetchAccessData, fetchEncryptedToken } from "./Platform/Hosted/PortalBackendConnectionStringClient"; const mockFetchEncryptedToken = fetchEncryptedToken as jest.MockedFunction; diff --git a/src/HostedExplorer.tsx b/src/HostedExplorer.tsx index 65672e030..c468fc3ac 100644 --- a/src/HostedExplorer.tsx +++ b/src/HostedExplorer.tsx @@ -19,12 +19,12 @@ import { MeControl } from "./Platform/Hosted/Components/MeControl"; import { SignInButton } from "./Platform/Hosted/Components/SignInButton"; import "./Platform/Hosted/ConnectScreen.less"; import { parseConnectionString } from "./Platform/Hosted/Helpers/ConnectionStringParser"; -import { fetchAccessData, fetchEncryptedToken } from "./Platform/Hosted/Helpers/PortalBackendClient"; import { isResourceTokenConnectionString } from "./Platform/Hosted/Helpers/ResourceTokenUtils"; import { extractMasterKeyFromDirectLoginConnectionString, isDirectConnectionStringLoginApi, } from "./Platform/Hosted/HostedUtils"; +import { fetchAccessData, fetchEncryptedToken } from "./Platform/Hosted/PortalBackendConnectionStringClient"; import "./Shared/appInsights"; import { allowedHostedExplorerEndpoints } from "./Utils/EndpointUtils"; import { useAADAuth } from "./hooks/useAADAuth"; diff --git a/src/Platform/Hosted/Components/ConnectExplorer.tsx b/src/Platform/Hosted/Components/ConnectExplorer.tsx index 588ef778e..de77ac8a9 100644 --- a/src/Platform/Hosted/Components/ConnectExplorer.tsx +++ b/src/Platform/Hosted/Components/ConnectExplorer.tsx @@ -7,9 +7,12 @@ import ErrorImage from "../../../../images/error.svg"; import { AuthType } from "../../../AuthType"; import { AccessInputMetadata } from "../../../Contracts/DataModels"; import { parseConnectionString } from "../Helpers/ConnectionStringParser"; -import { fetchEncryptedToken, isAccountRestrictedForConnectionStringLogin } from "../Helpers/PortalBackendClient"; import { isResourceTokenConnectionString } from "../Helpers/ResourceTokenUtils"; import { isDirectConnectionStringLoginApi } from "../HostedUtils"; +import { + fetchEncryptedToken, + isAccountRestrictedForConnectionStringLogin, +} from "../PortalBackendConnectionStringClient"; interface Props { connectionString: string; diff --git a/src/Platform/Hosted/ConnectScreen.less b/src/Platform/Hosted/ConnectScreen.less index f1fb81241..d4123f23f 100644 --- a/src/Platform/Hosted/ConnectScreen.less +++ b/src/Platform/Hosted/ConnectScreen.less @@ -65,35 +65,27 @@ visibility: visible; } .connectExplorerContainer .connectExplorer .connectExplorerContent .errorDetailsInfoTooltip .errorDetails { - top: 50%; - transform: translateY(-50%); - left: 100%; - margin-left: 10px; - width: 320px; - max-height: 240px; - overflow-y: auto; + bottom: 24px; + width: 165px; visibility: hidden; background-color: #393939; color: #ffffff; position: absolute; z-index: 1; + left: -10px; padding: 6px; - text-align: left; - white-space: normal; - overflow-wrap: break-word; } -.connectExplorerContainer .connectExplorer .connectExplorerContent .errorDetailsInfoTooltip:hover:after { - border-width: 8px 8px 8px 0px; +.connectExplorerContainer .connectExplorer .connectExplorerContent .errorDetailsInfoTooltip .errorDetails:after { + border-width: 10px 10px 0px 10px; + bottom: -8px; content: ""; position: absolute; - top: 50%; - transform: translateY(-50%); - left: 100%; - margin-left: 2px; + right: 100%; border-style: solid; + left: 12px; width: 0; height: 0; - border-color: transparent #393939; + border-color: #3b3b3b transparent; } .connectExplorerContainer .connectExplorer .connectExplorerContent .errorDetailsInfoTooltip .errorImg { height: 14px; diff --git a/src/Platform/Hosted/HostedUtils.ts b/src/Platform/Hosted/HostedUtils.ts index f900548cc..561956ea5 100644 --- a/src/Platform/Hosted/HostedUtils.ts +++ b/src/Platform/Hosted/HostedUtils.ts @@ -40,9 +40,7 @@ export function getDatabaseAccountKindFromExperience(apiExperience: typeof userC return AccountKind.GlobalDocumentDB; } -// Returns the master key carried by SQL, Table, and Gremlin connection strings. Mongo and -// Cassandra do not use an AccountKey token, so they must not be passed here. The key value cannot -// contain a semicolon, so we capture everything up to the next delimiter. +// Returns the master key carried by SQL, Table, and Gremlin connection strings. export function extractMasterKeyFromDirectLoginConnectionString(connectionString: string): string | undefined { const matchedParts = connectionString?.match(/AccountKey=([^;]*)/); return (matchedParts && matchedParts.length > 1 && matchedParts[1]) || undefined; diff --git a/src/Platform/Hosted/Helpers/PortalBackendClient.ts b/src/Platform/Hosted/PortalBackendConnectionStringClient.ts similarity index 63% rename from src/Platform/Hosted/Helpers/PortalBackendClient.ts rename to src/Platform/Hosted/PortalBackendConnectionStringClient.ts index 0edf9b4ab..9822aa852 100644 --- a/src/Platform/Hosted/Helpers/PortalBackendClient.ts +++ b/src/Platform/Hosted/PortalBackendConnectionStringClient.ts @@ -1,24 +1,6 @@ -import { HttpHeaders } from "../../../Common/Constants"; -import { configContext } from "../../../ConfigContext"; -import { AccessInputMetadata } from "../../../Contracts/DataModels"; - -// A failed Portal Backend response. Carries the status so callers can tell a rejected connection string -// from a service failure. -export class PortalBackendError extends Error { - constructor( - message: string, - public readonly statusCode: number, - ) { - super(message); - // Set the prototype explicitly so `instanceof` works. - // https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work - Object.setPrototypeOf(this, PortalBackendError.prototype); - } - - static async fromResponse(response: Response): Promise { - return new PortalBackendError(await response.text(), response.status); - } -} +import { HttpHeaders } from "../../Common/Constants"; +import { configContext } from "../../ConfigContext"; +import { AccessInputMetadata } from "../../Contracts/DataModels"; export async function fetchAccessData(portalToken: string): Promise { const headers = new Headers(); @@ -43,7 +25,7 @@ export async function fetchEncryptedToken(connectionString: string): Promise