Partition Key, Throughput, Unique Keys

This commit is contained in:
Asier Isayas 2025-02-19 15:16:32 -05:00
parent c33c497fd9
commit f0e32491d7
7 changed files with 241 additions and 47 deletions

View File

@ -31,6 +31,8 @@ import {
getPartitionKeyName, getPartitionKeyName,
getPartitionKeyPlaceHolder, getPartitionKeyPlaceHolder,
getPartitionKeyTooltipText, getPartitionKeyTooltipText,
isFreeTierAccount,
UniqueKeysHeader,
} from "Explorer/Panes/AddCollectionPanel/AddCollectionPanelUtility"; } from "Explorer/Panes/AddCollectionPanel/AddCollectionPanelUtility";
import { useSidePanel } from "hooks/useSidePanel"; import { useSidePanel } from "hooks/useSidePanel";
import { useTeachingBubble } from "hooks/useTeachingBubble"; import { useTeachingBubble } from "hooks/useTeachingBubble";
@ -196,7 +198,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
/> />
)} )}
{!this.state.errorMessage && this.isFreeTierAccount() && ( {!this.state.errorMessage && isFreeTierAccount() && (
<PanelInfoErrorComponent <PanelInfoErrorComponent
message={getUpsellMessage(userContext.portalEnv, true, isFirstResourceCreated, true)} message={getUpsellMessage(userContext.portalEnv, true, isFirstResourceCreated, true)}
messageType="info" messageType="info"
@ -403,10 +405,10 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
{!isServerlessAccount() && this.state.isSharedThroughputChecked && ( {!isServerlessAccount() && this.state.isSharedThroughputChecked && (
<ThroughputInput <ThroughputInput
showFreeTierExceedThroughputTooltip={this.isFreeTierAccount() && !isFirstResourceCreated} showFreeTierExceedThroughputTooltip={isFreeTierAccount() && !isFirstResourceCreated}
isDatabase={true} isDatabase={true}
isSharded={this.state.isSharded} isSharded={this.state.isSharded}
isFreeTier={this.isFreeTierAccount()} isFreeTier={isFreeTierAccount()}
isQuickstart={this.props.isQuickstart} isQuickstart={this.props.isQuickstart}
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)} setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)} setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
@ -734,10 +736,10 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
{this.shouldShowCollectionThroughputInput() && ( {this.shouldShowCollectionThroughputInput() && (
<ThroughputInput <ThroughputInput
showFreeTierExceedThroughputTooltip={this.isFreeTierAccount() && !isFirstResourceCreated} showFreeTierExceedThroughputTooltip={isFreeTierAccount() && !isFirstResourceCreated}
isDatabase={false} isDatabase={false}
isSharded={this.state.isSharded} isSharded={this.state.isSharded}
isFreeTier={this.isFreeTierAccount()} isFreeTier={isFreeTierAccount()}
isQuickstart={this.props.isQuickstart} isQuickstart={this.props.isQuickstart}
setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)} setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)} setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)}
@ -752,27 +754,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
{userContext.apiType === "SQL" && ( {userContext.apiType === "SQL" && (
<Stack> <Stack>
<Stack horizontal> {UniqueKeysHeader()}
<Text className="panelTextBold" variant="small">
Unique keys
</Text>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content={
"Unique keys provide developers with the ability to add a layer of data integrity to their database. By creating a unique key policy when a container is created, you ensure the uniqueness of one or more values per partition key."
}
>
<Icon
iconName="Info"
className="panelInfoIcon"
tabIndex={0}
ariaLabel={
"Unique keys provide developers with the ability to add a layer of data integrity to their database. By creating a unique key policy when a container is created, you ensure the uniqueness of one or more values per partition key."
}
/>
</TooltipHost>
</Stack>
{this.state.uniqueKeys.map((uniqueKey: string, i: number): JSX.Element => { {this.state.uniqueKeys.map((uniqueKey: string, i: number): JSX.Element => {
return ( return (
<Stack style={{ marginBottom: 8 }} key={`uniqueKey${i}`} horizontal> <Stack style={{ marginBottom: 8 }} key={`uniqueKey${i}`} horizontal>
@ -1143,10 +1125,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
return !!selectedDatabase?.offer(); return !!selectedDatabase?.offer();
} }
private isFreeTierAccount(): boolean {
return userContext.databaseAccount?.properties?.enableFreeTier;
}
private getFreeTierIndexingText(): string { private getFreeTierIndexingText(): string {
return this.state.enableIndexing return this.state.enableIndexing
? "All properties in your documents will be indexed by default for flexible and efficient queries." ? "All properties in your documents will be indexed by default for flexible and efficient queries."
@ -1222,7 +1200,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
} }
private shouldShowIndexingOptionsForFreeTierAccount(): boolean { private shouldShowIndexingOptionsForFreeTierAccount(): boolean {
if (!this.isFreeTierAccount()) { if (!isFreeTierAccount()) {
return false; return false;
} }

View File

@ -1,3 +1,5 @@
import { DirectionalHint, Icon, Stack, Text, TooltipHost } from "@fluentui/react";
import React from "react";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
export function getPartitionKeyTooltipText(): string { export function getPartitionKeyTooltipText(): string {
@ -56,3 +58,23 @@ export function getPartitionKey(isQuickstart?: boolean): string {
} }
return ""; return "";
} }
export function isFreeTierAccount(): boolean {
return userContext.databaseAccount?.properties?.enableFreeTier;
}
export function UniqueKeysHeader(): JSX.Element {
const tooltipContent =
"Unique keys provide developers with the ability to add a layer of data integrity to their database. By creating a unique key policy when a container is created, you ensure the uniqueness of one or more values per partition key.";
return (
<Stack horizontal>
<Text className="panelTextBold" variant="small">
Unique keys
</Text>
<TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={tooltipContent}>
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} ariaLabel={tooltipContent} />
</TooltipHost>
</Stack>
);
}

View File

@ -7,7 +7,7 @@ import {
} from "Explorer/Panes/AddCollectionPanel/AddCollectionPanelUtility"; } from "Explorer/Panes/AddCollectionPanel/AddCollectionPanelUtility";
import React from "react"; import React from "react";
export interface AddMVPartitionKeyProps { export interface AddMVPartitionKeyComponentProps {
partitionKey?: string; partitionKey?: string;
setPartitionKey: React.Dispatch<React.SetStateAction<string>>; setPartitionKey: React.Dispatch<React.SetStateAction<string>>;
subPartitionKeys: string[]; subPartitionKeys: string[];
@ -16,7 +16,7 @@ export interface AddMVPartitionKeyProps {
setUseHashV1: React.Dispatch<React.SetStateAction<boolean>>; setUseHashV1: React.Dispatch<React.SetStateAction<boolean>>;
} }
export const AddMVPartitionKey = (props: AddMVPartitionKeyProps): JSX.Element => { export const AddMVPartitionKeyComponent = (props: AddMVPartitionKeyComponentProps): JSX.Element => {
const { partitionKey, setPartitionKey, subPartitionKeys, setSubPartitionKeys, useHashV1, setUseHashV1 } = props; const { partitionKey, setPartitionKey, subPartitionKeys, setSubPartitionKeys, useHashV1, setUseHashV1 } = props;
const partitionKeyValueOnChange = (value: string): void => { const partitionKeyValueOnChange = (value: string): void => {
@ -60,6 +60,7 @@ export const AddMVPartitionKey = (props: AddMVPartitionKeyProps): JSX.Element =>
aria-label={getPartitionKeyName()} aria-label={getPartitionKeyName()}
pattern=".*" pattern=".*"
value={partitionKey} value={partitionKey}
style={{ marginBottom: 8 }}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => { onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
partitionKeyValueOnChange(event.target.value); partitionKeyValueOnChange(event.target.value);
}} }}
@ -88,6 +89,7 @@ export const AddMVPartitionKey = (props: AddMVPartitionKeyProps): JSX.Element =>
placeholder={getPartitionKeyPlaceHolder(subPartitionKeyIndex)} placeholder={getPartitionKeyPlaceHolder(subPartitionKeyIndex)}
aria-label={getPartitionKeyName()} aria-label={getPartitionKeyName()}
pattern={".*"} pattern={".*"}
title={""}
value={subPartitionKey} value={subPartitionKey}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => { onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
subPartitionKeysValueOnChange(event.target.value, subPartitionKeyIndex); subPartitionKeysValueOnChange(event.target.value, subPartitionKeyIndex);

View File

@ -1,10 +0,0 @@
import { Stack } from "@fluentui/react";
import React from "react";
export interface AddMVThroughputProps {
// isQuickstart: boolean;
}
export const AddMVThroughput = (props: AddMVThroughputProps): JSX.Element => {
return <Stack></Stack>;
};

View File

@ -0,0 +1,91 @@
import { Checkbox, Stack } from "@fluentui/react";
import { Collection } from "Contracts/ViewModels";
import { ThroughputInput } from "Explorer/Controls/ThroughputInput/ThroughputInput";
import { isFreeTierAccount } from "Explorer/Panes/AddCollectionPanel/AddCollectionPanelUtility";
import { useDatabases } from "Explorer/useDatabases";
import React from "react";
import { getCollectionName } from "Utils/APITypeUtils";
import { isServerlessAccount } from "Utils/CapabilityUtils";
export interface AddMVThroughputComponentProps {
selectedSourceContainer: Collection;
enableDedicatedThroughput: boolean;
setEnabledDedicatedThroughput: React.Dispatch<React.SetStateAction<boolean>>;
materializedViewThroughputOnChange: (materializedViewThroughputValue: number) => void;
isMaterializedViewAutoscaleOnChange: (isMaterializedViewAutoscaleValue: boolean) => void;
setIsThroughputCapExceeded: React.Dispatch<React.SetStateAction<boolean>>;
isCostAknowledgedOnChange: (isCostAknowledgedValue: boolean) => void;
}
export const AddMVThroughputComponent = (props: AddMVThroughputComponentProps): JSX.Element => {
const {
selectedSourceContainer,
enableDedicatedThroughput,
setEnabledDedicatedThroughput,
materializedViewThroughputOnChange,
isMaterializedViewAutoscaleOnChange,
setIsThroughputCapExceeded,
isCostAknowledgedOnChange,
} = props;
const isSelectedSourceContainerSharedThroughput = (): boolean => {
if (!selectedSourceContainer) {
return false;
}
return !!selectedSourceContainer.getDatabase().offer();
};
const showCollectionThroughputInput = (): boolean => {
if (isServerlessAccount()) {
return false;
}
if (enableDedicatedThroughput) {
return true;
}
return !!selectedSourceContainer && !isSelectedSourceContainerSharedThroughput();
};
return (
<Stack>
{!isServerlessAccount() && isSelectedSourceContainerSharedThroughput() && (
<Stack horizontal verticalAlign="center">
<Checkbox
label={`Provision dedicated throughput for this ${getCollectionName().toLocaleLowerCase()}`}
checked={enableDedicatedThroughput}
styles={{
text: { fontSize: 12 },
checkbox: { width: 12, height: 12 },
label: { padding: 0, alignItems: "center" },
}}
onChange={(_, isChecked: boolean) => setEnabledDedicatedThroughput(isChecked)}
/>
</Stack>
)}
{showCollectionThroughputInput() && (
<ThroughputInput
showFreeTierExceedThroughputTooltip={isFreeTierAccount() && !useDatabases.getState().isFirstResourceCreated()}
isDatabase={false}
// isSharded={this.state.isSharded}
isSharded={false}
isFreeTier={isFreeTierAccount()}
isQuickstart={false}
setThroughputValue={(throughput: number) => {
materializedViewThroughputOnChange(throughput);
}}
setIsAutoscale={(isAutoscale: boolean) => {
isMaterializedViewAutoscaleOnChange(isAutoscale);
}}
setIsThroughputCapExceeded={(isThroughputCapExceeded: boolean) => {
setIsThroughputCapExceeded(isThroughputCapExceeded);
}}
onCostAcknowledgeChange={(isAcknowledged: boolean) => {
isCostAknowledgedOnChange(isAcknowledged);
}}
/>
)}
</Stack>
);
};

View File

@ -0,0 +1,78 @@
import { ActionButton, IconButton, Stack } from "@fluentui/react";
import { UniqueKeysHeader } from "Explorer/Panes/AddCollectionPanel/AddCollectionPanelUtility";
import React from "react";
import { userContext } from "UserContext";
export interface AddMVUniqueKeysComponentProps {
uniqueKeys: string[];
setUniqueKeys: React.Dispatch<React.SetStateAction<string[]>>;
}
export const AddMVUniqueKeysComponent = (props: AddMVUniqueKeysComponentProps): JSX.Element => {
const { uniqueKeys, setUniqueKeys } = props;
const updateUniqueKeysOnChange = (value: string, uniqueKeyToReplaceIndex: number): void => {
const updatedUniqueKeys = uniqueKeys.map((uniqueKey: string, uniqueKeyIndex: number) => {
if (uniqueKeyToReplaceIndex === uniqueKeyIndex) {
return value;
}
return uniqueKey;
});
setUniqueKeys(updatedUniqueKeys);
};
const deleteUniqueKeyOnClick = (uniqueKeyToDeleteIndex: number): void => {
const updatedUniqueKeys = uniqueKeys.filter((_, uniqueKeyIndex) => uniqueKeyToDeleteIndex !== uniqueKeyIndex);
setUniqueKeys(updatedUniqueKeys);
};
const addUniqueKeyOnClick = (): void => {
setUniqueKeys([...uniqueKeys, ""]);
};
return (
<Stack>
{UniqueKeysHeader()}
{uniqueKeys.map((uniqueKey: string, uniqueKeyIndex: number): JSX.Element => {
return (
<Stack style={{ marginBottom: 8 }} key={`uniqueKey-${uniqueKeyIndex}`} horizontal>
<input
type="text"
autoComplete="off"
placeholder={
userContext.apiType === "Mongo"
? "Comma separated paths e.g. firstName,address.zipCode"
: "Comma separated paths e.g. /firstName,/address/zipCode"
}
className="panelTextField"
autoFocus
value={uniqueKey}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
updateUniqueKeysOnChange(event.target.value, uniqueKeyIndex);
}}
/>
<IconButton
iconProps={{ iconName: "Delete" }}
style={{ height: 27 }}
onClick={() => {
deleteUniqueKeyOnClick(uniqueKeyIndex);
}}
/>
</Stack>
);
})}
<ActionButton
iconProps={{ iconName: "Add" }}
styles={{ root: { padding: 0 }, label: { fontSize: 12 } }}
onClick={() => {
addUniqueKeyOnClick();
}}
>
Add unique key
</ActionButton>
</Stack>
);
};

View File

@ -13,8 +13,9 @@ import {
import { Collection, Database } from "Contracts/ViewModels"; import { Collection, Database } from "Contracts/ViewModels";
import Explorer from "Explorer/Explorer"; import Explorer from "Explorer/Explorer";
import { getPartitionKey } from "Explorer/Panes/AddCollectionPanel/AddCollectionPanelUtility"; import { getPartitionKey } from "Explorer/Panes/AddCollectionPanel/AddCollectionPanelUtility";
import { AddMVPartitionKey } from "Explorer/Panes/AddMaterializedViewPanel/AddMVPartitionKey"; import { AddMVPartitionKeyComponent } from "Explorer/Panes/AddMaterializedViewPanel/AddMVPartitionKeyComponent";
import { AddMVThroughput } from "Explorer/Panes/AddMaterializedViewPanel/AddMVThroughput"; import { AddMVThroughputComponent } from "Explorer/Panes/AddMaterializedViewPanel/AddMVThroughputComponent";
import { AddMVUniqueKeysComponent } from "Explorer/Panes/AddMaterializedViewPanel/AddMVUniqueKeysComponent";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
@ -32,6 +33,9 @@ export const AddMaterializedViewPanel = (props: AddMaterializedViewPanelProps):
const [partitionKey, setPartitionKey] = useState<string>(getPartitionKey()); const [partitionKey, setPartitionKey] = useState<string>(getPartitionKey());
const [subPartitionKeys, setSubPartitionKeys] = useState<string[]>([]); const [subPartitionKeys, setSubPartitionKeys] = useState<string[]>([]);
const [useHashV1, setUseHashV1] = useState<boolean>(); const [useHashV1, setUseHashV1] = useState<boolean>();
const [enableDedicatedThroughput, setEnabledDedicatedThroughput] = useState<boolean>();
const [isThroughputCapExceeded, setIsThroughputCapExceeded] = useState<boolean>();
const [uniqueKeys, setUniqueKeys] = useState<string[]>([]);
useEffect(() => { useEffect(() => {
const sourceContainerOptions: IDropdownOption[] = []; const sourceContainerOptions: IDropdownOption[] = [];
@ -59,6 +63,22 @@ export const AddMaterializedViewPanel = (props: AddMaterializedViewPanelProps):
setSourceContainerOptions(sourceContainerOptions); setSourceContainerOptions(sourceContainerOptions);
}, []); }, []);
let materializedViewThroughput: number;
let isMaterializedViewAutoscale: boolean;
let isCostAcknowledged: boolean;
const materializedViewThroughputOnChange = (materializedViewThroughputValue: number): void => {
materializedViewThroughput = materializedViewThroughputValue;
};
const isMaterializedViewAutoscaleOnChange = (isMaterializedViewAutoscaleValue: boolean): void => {
isMaterializedViewAutoscale = isMaterializedViewAutoscaleValue;
};
const isCostAknowledgedOnChange = (isCostAcknowledgedValue: boolean): void => {
isCostAcknowledged = isCostAcknowledgedValue;
};
return ( return (
<form className="panelFormWrapper" id="panelMaterializedView"> <form className="panelFormWrapper" id="panelMaterializedView">
<div className="panelMainContent"> <div className="panelMainContent">
@ -129,8 +149,21 @@ export const AddMaterializedViewPanel = (props: AddMaterializedViewPanelProps):
value={materializedViewDefinition} value={materializedViewDefinition}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => setMaterializedViewDefinition(event.target.value)} onChange={(event: React.ChangeEvent<HTMLInputElement>) => setMaterializedViewDefinition(event.target.value)}
/> />
<AddMVPartitionKey {...{ partitionKey, setPartitionKey, subPartitionKeys, setSubPartitionKeys }} /> <AddMVPartitionKeyComponent
<AddMVThroughput /> {...{ partitionKey, setPartitionKey, subPartitionKeys, setSubPartitionKeys, useHashV1, setUseHashV1 }}
/>
<AddMVThroughputComponent
{...{
selectedSourceContainer,
enableDedicatedThroughput,
setEnabledDedicatedThroughput,
materializedViewThroughputOnChange,
isMaterializedViewAutoscaleOnChange,
setIsThroughputCapExceeded,
isCostAknowledgedOnChange,
}}
/>
<AddMVUniqueKeysComponent {...{ uniqueKeys, setUniqueKeys }} />
</Stack> </Stack>
</div> </div>
</form> </form>