From 1081432bbd05e523f26f867bdb35cbde3e95117f Mon Sep 17 00:00:00 2001 From: sunghyunkang1111 <114709653+sunghyunkang1111@users.noreply.github.com> Date: Tue, 13 May 2025 11:26:47 -0500 Subject: [PATCH] Added AFEC check in userContext (#2140) * Added AFEC check in userContext * Update unit tests and fix Group to Bucket --- .../Settings/SettingsComponent.test.tsx | 3 +- .../Controls/Settings/SettingsComponent.tsx | 5 +- .../ThroughputBucketsComponent.test.tsx | 16 +++++-- .../ThroughputBucketsComponent.tsx | 2 +- src/Explorer/Explorer.tsx | 6 +++ src/UserContext.ts | 1 + src/Utils/FeatureRegistrationUtils.ts | 48 +++++++++++++++++++ 7 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 src/Utils/FeatureRegistrationUtils.ts diff --git a/src/Explorer/Controls/Settings/SettingsComponent.test.tsx b/src/Explorer/Controls/Settings/SettingsComponent.test.tsx index 5a7a47def..825c3fb14 100644 --- a/src/Explorer/Controls/Settings/SettingsComponent.test.tsx +++ b/src/Explorer/Controls/Settings/SettingsComponent.test.tsx @@ -1,7 +1,6 @@ import { AuthType } from "AuthType"; import { shallow } from "enzyme"; import ko from "knockout"; -import { Features } from "Platform/Hosted/extractFeatures"; import React from "react"; import { updateCollection } from "../../../Common/dataAccess/updateCollection"; import { updateOffer } from "../../../Common/dataAccess/updateOffer"; @@ -253,7 +252,7 @@ describe("SettingsComponent", () => { it("should save throughput bucket changes when Save button is clicked", async () => { updateUserContext({ apiType: "SQL", - features: { enableThroughputBuckets: true } as Features, + throughputBucketsEnabled: true, authType: AuthType.AAD, }); diff --git a/src/Explorer/Controls/Settings/SettingsComponent.tsx b/src/Explorer/Controls/Settings/SettingsComponent.tsx index 65000e7ee..e3462c594 100644 --- a/src/Explorer/Controls/Settings/SettingsComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsComponent.tsx @@ -191,10 +191,7 @@ export class SettingsComponent extends React.Component { it("renders the correct number of buckets", () => { render(); - expect(screen.getAllByText(/Group \d+/)).toHaveLength(5); + expect(screen.getAllByText(/Bucket \d+/)).toHaveLength(5); }); it("renders buckets in the correct order even if input is unordered", () => { @@ -36,8 +36,14 @@ describe("ThroughputBucketsComponent", () => { ]; render(); - const bucketLabels = screen.getAllByText(/Group \d+/).map((el) => el.textContent); - expect(bucketLabels).toEqual(["Group 1 (Data Explorer Query Bucket)", "Group 2", "Group 3", "Group 4", "Group 5"]); + const bucketLabels = screen.getAllByText(/Bucket \d+/).map((el) => el.textContent); + expect(bucketLabels).toEqual([ + "Bucket 1 (Data Explorer Query Bucket)", + "Bucket 2", + "Bucket 3", + "Bucket 4", + "Bucket 5", + ]); }); it("renders all provided buckets even if they exceed the max default bucket count", () => { @@ -53,7 +59,7 @@ describe("ThroughputBucketsComponent", () => { render(); - expect(screen.getAllByText(/Group \d+/)).toHaveLength(7); + expect(screen.getAllByText(/Bucket \d+/)).toHaveLength(7); expect(screen.getByDisplayValue("50")).toBeInTheDocument(); expect(screen.getByDisplayValue("60")).toBeInTheDocument(); @@ -171,7 +177,7 @@ describe("ThroughputBucketsComponent", () => { it("ensures default buckets are used when no buckets are provided", () => { render(); - expect(screen.getAllByText(/Group \d+/)).toHaveLength(5); + expect(screen.getAllByText(/Bucket \d+/)).toHaveLength(5); expect(screen.getAllByDisplayValue("100")).toHaveLength(5); }); }); diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent.tsx index a9408b1e4..a0b85cabf 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent.tsx @@ -76,7 +76,7 @@ export const ThroughputBucketsComponent: FC = ( value={bucket.maxThroughputPercentage} onChange={(newValue) => handleBucketChange(bucket.id, newValue)} showValue={false} - label={`Group ${bucket.id}${bucket.id === 1 ? " (Data Explorer Query Bucket)" : ""}`} + label={`Bucket ${bucket.id}${bucket.id === 1 ? " (Data Explorer Query Bucket)" : ""}`} styles={{ root: { flex: 2, maxWidth: 400 } }} disabled={bucket.maxThroughputPercentage === 100} /> diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index 0f0670ecc..335a39fb2 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -12,6 +12,7 @@ import { isFabricMirrored, isFabricMirroredKey, scheduleRefreshFabricToken } fro import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils"; import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils"; +import { featureRegistered } from "Utils/FeatureRegistrationUtils"; import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts"; import { useQueryCopilot } from "hooks/useQueryCopilot"; import * as ko from "knockout"; @@ -1196,6 +1197,11 @@ export default class Explorer { await this.initNotebooks(userContext.databaseAccount); } + if (userContext.authType === AuthType.AAD && userContext.apiType === "SQL") { + const throughputBucketsEnabled = await featureRegistered(userContext.subscriptionId, "ThroughputBucketing"); + updateUserContext({ throughputBucketsEnabled }); + } + this.refreshSampleData(); } diff --git a/src/UserContext.ts b/src/UserContext.ts index 8a880f498..8a723ad91 100644 --- a/src/UserContext.ts +++ b/src/UserContext.ts @@ -117,6 +117,7 @@ export interface UserContext { readonly feedbackPolicies?: AdminFeedbackPolicySettings; readonly dataPlaneRbacEnabled?: boolean; readonly refreshCosmosClient?: boolean; + throughputBucketsEnabled?: boolean; } export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres" | "VCoreMongo"; diff --git a/src/Utils/FeatureRegistrationUtils.ts b/src/Utils/FeatureRegistrationUtils.ts new file mode 100644 index 000000000..9cc5676c9 --- /dev/null +++ b/src/Utils/FeatureRegistrationUtils.ts @@ -0,0 +1,48 @@ +import { configContext } from "ConfigContext"; +import { FeatureRegistration } from "Contracts/DataModels"; +import { AuthorizationTokenHeaderMetadata } from "Contracts/ViewModels"; +import { getAuthorizationHeader } from "Utils/AuthorizationUtils"; + +export const featureRegistered = async (subscriptionId: string, feature: string) => { + const api_version = "2021-07-01"; + const url = `${configContext.ARM_ENDPOINT}/subscriptions/${subscriptionId}/providers/Microsoft.Features/featureProviders/Microsoft.DocumentDB/subscriptionFeatureRegistrations/${feature}?api-version=${api_version}`; + const authorizationHeader: AuthorizationTokenHeaderMetadata = getAuthorizationHeader(); + const headers = { [authorizationHeader.header]: authorizationHeader.token }; + + let response; + + try { + response = await _fetchWithTimeout(url, headers); + } catch (error) { + return false; + } + + if (!response?.ok) { + return false; + } + + const featureRegistration = (await response?.json()) as FeatureRegistration; + return featureRegistration?.properties?.state === "Registered"; +}; + +async function _fetchWithTimeout( + url: string, + headers: { + [x: string]: string; + }, +) { + const timeout = 10000; + const options = { timeout }; + + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), timeout); + + const response = await window.fetch(url, { + headers, + ...options, + signal: controller.signal, + }); + clearTimeout(id); + + return response; +}