default throughput bucket

This commit is contained in:
Asier Isayas
2026-01-12 10:17:29 -08:00
parent 38823ac86f
commit 7b299aac39
25 changed files with 225 additions and 76 deletions

View File

@@ -76,11 +76,11 @@ describe("ThroughputBucketsComponent", () => {
fireEvent.change(input, { target: { value: "70" } });
expect(mockOnBucketsChange).toHaveBeenCalledWith([
{ id: 1, maxThroughputPercentage: 70 },
{ id: 2, maxThroughputPercentage: 60 },
{ id: 3, maxThroughputPercentage: 100 },
{ id: 4, maxThroughputPercentage: 100 },
{ id: 5, maxThroughputPercentage: 100 },
{ id: 1, maxThroughputPercentage: 70, isDefaultBucket: false },
{ id: 2, maxThroughputPercentage: 60, isDefaultBucket: false },
{ id: 3, maxThroughputPercentage: 100, isDefaultBucket: false },
{ id: 4, maxThroughputPercentage: 100, isDefaultBucket: false },
{ id: 5, maxThroughputPercentage: 100, isDefaultBucket: false },
]);
});
@@ -102,11 +102,11 @@ describe("ThroughputBucketsComponent", () => {
fireEvent.change(input2, { target: { value: "80" } });
expect(mockOnBucketsChange).toHaveBeenCalledWith([
{ id: 1, maxThroughputPercentage: 70 },
{ id: 2, maxThroughputPercentage: 80 },
{ id: 3, maxThroughputPercentage: 100 },
{ id: 4, maxThroughputPercentage: 100 },
{ id: 5, maxThroughputPercentage: 100 },
{ id: 1, maxThroughputPercentage: 70, isDefaultBucket: false },
{ id: 2, maxThroughputPercentage: 80, isDefaultBucket: false },
{ id: 3, maxThroughputPercentage: 100, isDefaultBucket: false },
{ id: 4, maxThroughputPercentage: 100, isDefaultBucket: false },
{ id: 5, maxThroughputPercentage: 100, isDefaultBucket: false },
]);
});
@@ -134,8 +134,8 @@ describe("ThroughputBucketsComponent", () => {
<ThroughputBucketsComponent
{...defaultProps}
currentBuckets={[
{ id: 1, maxThroughputPercentage: 100 },
{ id: 2, maxThroughputPercentage: 50 },
{ id: 1, maxThroughputPercentage: 100, isDefaultBucket: false },
{ id: 2, maxThroughputPercentage: 50, isDefaultBucket: false },
]}
/>,
);
@@ -157,21 +157,21 @@ describe("ThroughputBucketsComponent", () => {
fireEvent.click(toggles[0]);
expect(mockOnBucketsChange).toHaveBeenCalledWith([
{ id: 1, maxThroughputPercentage: 100 },
{ id: 2, maxThroughputPercentage: 60 },
{ id: 3, maxThroughputPercentage: 100 },
{ id: 4, maxThroughputPercentage: 100 },
{ id: 5, maxThroughputPercentage: 100 },
{ id: 1, maxThroughputPercentage: 100, isDefaultBucket: false },
{ id: 2, maxThroughputPercentage: 60, isDefaultBucket: false },
{ id: 3, maxThroughputPercentage: 100, isDefaultBucket: false },
{ id: 4, maxThroughputPercentage: 100, isDefaultBucket: false },
{ id: 5, maxThroughputPercentage: 100, isDefaultBucket: false },
]);
fireEvent.click(toggles[0]);
expect(mockOnBucketsChange).toHaveBeenCalledWith([
{ id: 1, maxThroughputPercentage: 50 },
{ id: 2, maxThroughputPercentage: 60 },
{ id: 3, maxThroughputPercentage: 100 },
{ id: 4, maxThroughputPercentage: 100 },
{ id: 5, maxThroughputPercentage: 100 },
{ id: 1, maxThroughputPercentage: 50, isDefaultBucket: false },
{ id: 2, maxThroughputPercentage: 60, isDefaultBucket: false },
{ id: 3, maxThroughputPercentage: 100, isDefaultBucket: false },
{ id: 4, maxThroughputPercentage: 100, isDefaultBucket: false },
{ id: 5, maxThroughputPercentage: 100, isDefaultBucket: false },
]);
});

View File

@@ -1,4 +1,16 @@
import { Label, Slider, Stack, TextField, Toggle } from "@fluentui/react";
import {
Dropdown,
Icon,
IDropdownOption,
Label,
Link,
Slider,
Stack,
Text,
TextField,
Toggle,
TooltipHost,
} from "@fluentui/react";
import { ThroughputBucket } from "Contracts/DataModels";
import React, { FC, useEffect, useState } from "react";
import { isDirty } from "../../SettingsUtils";
@@ -8,6 +20,7 @@ const MAX_BUCKET_SIZES = 5;
const DEFAULT_BUCKETS = Array.from({ length: MAX_BUCKET_SIZES }, (_, i) => ({
id: i + 1,
maxThroughputPercentage: 100,
isDefaultBucket: false,
}));
export interface ThroughputBucketsComponentProps {
@@ -23,19 +36,46 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
onBucketsChange,
onSaveableChange,
}) => {
const NoDefaultThroughputSelectedKey: number = -1;
const getThroughputBuckets = (buckets: ThroughputBucket[]): ThroughputBucket[] => {
if (!buckets || buckets.length === 0) {
return DEFAULT_BUCKETS;
}
const maxBuckets = Math.max(DEFAULT_BUCKETS.length, buckets.length);
const adjustedDefaultBuckets = Array.from({ length: maxBuckets }, (_, i) => ({
id: i + 1,
maxThroughputPercentage: 100,
}));
return adjustedDefaultBuckets.map(
(defaultBucket) => buckets?.find((bucket) => bucket.id === defaultBucket.id) || defaultBucket,
const adjustedDefaultBuckets: ThroughputBucket[] = Array.from(
{ length: maxBuckets },
(_, i) =>
({
id: i + 1,
maxThroughputPercentage: 100,
isDefaultBucket: false,
}) as ThroughputBucket,
);
return adjustedDefaultBuckets.map((defaultBucket: ThroughputBucket) => {
const incoming: ThroughputBucket = buckets?.find((bucket) => bucket.id === defaultBucket.id);
return {
...defaultBucket,
...incoming,
...(incoming?.isDefaultBucket && { isDefaultBucket: true }),
};
});
};
const getThroughputBucketOptions = (): IDropdownOption[] => {
const noDefaultThroughputBucketSelected: IDropdownOption[] = [
{ key: NoDefaultThroughputSelectedKey, text: "No Default Throughput Bucket Selected" },
];
const throughputBucketOptions: IDropdownOption[] = throughputBuckets
.filter((bucket) => bucket.maxThroughputPercentage !== 100)
.map((bucket) => ({
key: bucket.id,
text: `Bucket ${bucket.id} - ${bucket.maxThroughputPercentage}%`,
}));
return [...noDefaultThroughputBucketSelected, ...throughputBucketOptions];
};
const [throughputBuckets, setThroughputBuckets] = useState<ThroughputBucket[]>(getThroughputBuckets(currentBuckets));
@@ -52,7 +92,13 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
const handleBucketChange = (id: number, newValue: number) => {
const updatedBuckets = throughputBuckets.map((bucket) =>
bucket.id === id ? { ...bucket, maxThroughputPercentage: newValue } : bucket,
bucket.id === id
? {
...bucket,
maxThroughputPercentage: newValue,
isDefaultBucket: newValue === 100 ? false : bucket.isDefaultBucket,
}
: bucket,
);
setThroughputBuckets(updatedBuckets);
const settingsChanged = isDirty(updatedBuckets, throughputBuckets);
@@ -63,6 +109,35 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
handleBucketChange(id, checked ? 50 : 100);
};
const onDefaultBucketToggle = (id: number, checked: boolean): void => {
const updatedBuckets: ThroughputBucket[] = throughputBuckets.map((bucket) =>
bucket.id === id ? { ...bucket, isDefaultBucket: checked } : { ...bucket, isDefaultBucket: false },
);
setThroughputBuckets(updatedBuckets);
const settingsChanged = isDirty(updatedBuckets, throughputBuckets);
settingsChanged && onBucketsChange(updatedBuckets);
};
const onRenderDefaultThroughputBucketLabel = (): JSX.Element => {
const tooltipContent = (): JSX.Element => (
<Text>
The default throughput bucket is used for operations that do not specify a particular bucket.{" "}
<Link href="https://aka.ms/cosmsodb-bucketing" target="_blank">
Learn more.
</Link>
</Text>
);
return (
<Stack horizontal verticalAlign="center">
<Label>Default Throughput Bucket</Label>
<TooltipHost content={tooltipContent()}>
<Icon iconName="Info" styles={{ root: { marginLeft: 4, marginTop: 5 } }} />
</TooltipHost>
</Stack>
);
};
return (
<Stack tokens={{ childrenGap: "m" }} styles={{ root: { width: "70%", maxWidth: 700 } }}>
<Label styles={{ root: { color: "var(--colorNeutralForeground1)" } }}>Throughput Buckets</Label>
@@ -108,9 +183,32 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
text: { fontSize: 12, color: "var(--colorNeutralForeground1)" },
}}
></Toggle>
{/* <Toggle
onText="Default"
offText="Not Default"
checked={bucket.isDefaultBucket || false}
onChange={(_, checked) => onDefaultBucketToggle(bucket.id, checked)}
disabled={bucket.maxThroughputPercentage === 100}
styles={{
root: { marginBottom: 0 },
text: { fontSize: 12, color: "var(--colorNeutralForeground1)" },
}}
/> */}
</Stack>
))}
</Stack>
<Dropdown
placeholder="Select a default throughput bucket"
label="Default Throughput Bucket"
options={getThroughputBucketOptions()}
selectedKey={
throughputBuckets?.find((throughputbucket: ThroughputBucket) => throughputbucket.isDefaultBucket)?.id ||
NoDefaultThroughputSelectedKey
}
onChange={(_, option) => onDefaultBucketToggle(option.key as number, true)}
styles={{ root: { width: "50%" } }}
onRenderLabel={onRenderDefaultThroughputBucketLabel}
/>
</Stack>
);
};