diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd741b074..56ed46edc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,7 +83,7 @@ jobs: - run: npm ci - run: npm run build:contracts - name: Restore Build Cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: .cache key: ${{ runner.os }}-build-cache diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts index fff625dc7..840849904 100644 --- a/src/Common/Constants.ts +++ b/src/Common/Constants.ts @@ -97,6 +97,12 @@ export enum CapacityMode { Serverless = "Serverless", } +export enum WorkloadType { + Learning = "Learning", + DevelopmentTesting = "Development/Testing", + Production = "Production", + None = "None", +} // flight names returned from the portal are always lowercase export class Flights { public static readonly SettingsV2 = "settingsv2"; @@ -119,6 +125,7 @@ export class AfecFeatures { export class TagNames { public static defaultExperience: string = "defaultExperience"; + public static WorkloadType: string = "hidden-workload-type"; } export class MongoDBAccounts { diff --git a/src/Common/DatabaseAccountUtility.test.ts b/src/Common/DatabaseAccountUtility.test.ts new file mode 100644 index 000000000..0e0f015e8 --- /dev/null +++ b/src/Common/DatabaseAccountUtility.test.ts @@ -0,0 +1,34 @@ +import { WorkloadType } from "Common/Constants"; +import { getWorkloadType } from "Common/DatabaseAccountUtility"; +import { DatabaseAccount, Tags } from "Contracts/DataModels"; +import { updateUserContext } from "UserContext"; + +describe("Database Account Utility", () => { + describe("Workload Type", () => { + beforeEach(() => { + updateUserContext({ + databaseAccount: { + tags: {} as Tags, + } as DatabaseAccount, + }); + }); + + it("Workload Type should return Learning", () => { + updateUserContext({ + databaseAccount: { + tags: { + "hidden-workload-type": WorkloadType.Learning, + } as Tags, + } as DatabaseAccount, + }); + + const workloadType: WorkloadType = getWorkloadType(); + expect(workloadType).toBe(WorkloadType.Learning); + }); + + it("Workload Type should return None", () => { + const workloadType: WorkloadType = getWorkloadType(); + expect(workloadType).toBe(WorkloadType.None); + }); + }); +}); diff --git a/src/Common/DatabaseAccountUtility.ts b/src/Common/DatabaseAccountUtility.ts index 693f581f8..c72d3baf6 100644 --- a/src/Common/DatabaseAccountUtility.ts +++ b/src/Common/DatabaseAccountUtility.ts @@ -1,3 +1,5 @@ +import { TagNames, WorkloadType } from "Common/Constants"; +import { Tags } from "Contracts/DataModels"; import { userContext } from "../UserContext"; function isVirtualNetworkFilterEnabled() { @@ -15,3 +17,12 @@ function isPrivateEndpointConnectionsEnabled() { export function isPublicInternetAccessAllowed(): boolean { return !isVirtualNetworkFilterEnabled() && !isIpRulesEnabled() && !isPrivateEndpointConnectionsEnabled(); } + +export function getWorkloadType(): WorkloadType { + const tags: Tags = userContext?.databaseAccount?.tags; + const workloadType: WorkloadType = tags && (tags[TagNames.WorkloadType] as WorkloadType); + if (!workloadType) { + return WorkloadType.None; + } + return workloadType; +} diff --git a/src/Contracts/DataModels.ts b/src/Contracts/DataModels.ts index da6ddd28d..47533fee3 100644 --- a/src/Contracts/DataModels.ts +++ b/src/Contracts/DataModels.ts @@ -6,6 +6,7 @@ export interface ArmEntity { location: string; type: string; kind: string; + tags?: Tags; } export interface DatabaseAccount extends ArmEntity { @@ -663,3 +664,5 @@ export interface FeatureRegistration { state: string; }; } + +export type Tags = { [key: string]: string }; diff --git a/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx b/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx index 2001700ad..64676bbc3 100644 --- a/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx +++ b/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx @@ -1,4 +1,5 @@ import { Checkbox, DirectionalHint, Link, Stack, Text, TextField, TooltipHost } from "@fluentui/react"; +import { getWorkloadType } from "Common/DatabaseAccountUtility"; import { useDatabases } from "Explorer/useDatabases"; import React, { FunctionComponent, useEffect, useState } from "react"; import * as Constants from "../../../Common/Constants"; @@ -34,10 +35,15 @@ export const ThroughputInput: FunctionComponent = ({ setIsThroughputCapExceeded, onCostAcknowledgeChange, }: ThroughputInputProps) => { + const defaultThroughput: number = + isFreeTier || + isQuickstart || + [Constants.WorkloadType.Learning, Constants.WorkloadType.DevelopmentTesting].includes(getWorkloadType()) + ? AutoPilotUtils.autoPilotThroughput1K + : AutoPilotUtils.autoPilotThroughput4K; + const [isAutoscaleSelected, setIsAutoScaleSelected] = useState(true); - const [throughput, setThroughput] = useState( - isFreeTier || isQuickstart ? AutoPilotUtils.autoPilotThroughput1K : AutoPilotUtils.autoPilotThroughput4K, - ); + const [throughput, setThroughput] = useState(defaultThroughput); const [isCostAcknowledged, setIsCostAcknowledged] = useState(false); const [throughputError, setThroughputError] = useState(""); const [totalThroughputUsed, setTotalThroughputUsed] = useState(0); @@ -47,7 +53,6 @@ export const ThroughputInput: FunctionComponent = ({ const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit; const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1; - useEffect(() => { // throughput cap check for the initial state let totalThroughput = 0; @@ -157,9 +162,6 @@ export const ThroughputInput: FunctionComponent = ({ const handleOnChangeMode = (event: React.ChangeEvent, mode: string): void => { if (mode === "Autoscale") { - const defaultThroughput = isFreeTier - ? AutoPilotUtils.autoPilotThroughput1K - : AutoPilotUtils.autoPilotThroughput4K; setThroughput(defaultThroughput); setIsAutoScaleSelected(true); setThroughputValue(defaultThroughput); diff --git a/src/Explorer/Sidebar.tsx b/src/Explorer/Sidebar.tsx index a6a0e6a3e..8b96b0422 100644 --- a/src/Explorer/Sidebar.tsx +++ b/src/Explorer/Sidebar.tsx @@ -26,7 +26,7 @@ import { getCollectionName, getDatabaseName } from "Utils/APITypeUtils"; import { Allotment, AllotmentHandle } from "allotment"; import { useSidePanel } from "hooks/useSidePanel"; import { debounce } from "lodash"; -import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; const useSidebarStyles = makeStyles({ sidebar: { @@ -109,6 +109,7 @@ interface GlobalCommand { icon: JSX.Element; onClick: () => void; keyboardAction?: KeyboardAction; + ref?: React.RefObject; } const GlobalCommands: React.FC = ({ explorer }) => { @@ -118,6 +119,7 @@ const GlobalCommands: React.FC = ({ explorer }) => { // However, that messes with the Menu positioning, so we need to get a reference to the 'div' to pass to the Menu. // We can't use a ref though, because it would be set after the Menu is rendered, so we use a state value to force a re-render. const [globalCommandButton, setGlobalCommandButton] = useState(null); + const primaryFocusableRef = useRef(null); const actions = useMemo(() => { if ( @@ -177,6 +179,16 @@ const GlobalCommands: React.FC = ({ explorer }) => { ); }, [actions, setKeyboardActions]); + useLayoutEffect(() => { + if (primaryFocusableRef.current) { + const timer = setTimeout(() => { + primaryFocusableRef.current.focus(); + }, 0); + return () => clearTimeout(timer); + } + return undefined; + }, []); + if (!primaryAction) { return null; } @@ -184,7 +196,7 @@ const GlobalCommands: React.FC = ({ explorer }) => { return (
{actions.length === 1 ? ( - ) : ( @@ -194,7 +206,7 @@ const GlobalCommands: React.FC = ({ explorer }) => {