mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-04-18 04:19:11 +01:00
Default throughput bucket (#2460)
* default throughput bucket * nit * show inactive buckets * add e2e tests for default throughput bucket * for test sql containers, use throughput of 4000 * remove container throughput on creation * added offer throughput * add default throughput bucket info link * add text localization * upgrade playwright * Fix flaky permissionsScreen test by using unrouteAll with ignoreErrors * fix: move container creation to beforeAll to reduce CI shard timeout * remove comment --------- Co-authored-by: Asier Isayas <aisayas@microsoft.com>
This commit is contained in:
24
package-lock.json
generated
24
package-lock.json
generated
@@ -130,7 +130,7 @@
|
|||||||
"@babel/preset-env": "7.24.7",
|
"@babel/preset-env": "7.24.7",
|
||||||
"@babel/preset-react": "7.24.7",
|
"@babel/preset-react": "7.24.7",
|
||||||
"@babel/preset-typescript": "7.24.7",
|
"@babel/preset-typescript": "7.24.7",
|
||||||
"@playwright/test": "1.55.1",
|
"@playwright/test": "1.59.1",
|
||||||
"@testing-library/react": "11.2.3",
|
"@testing-library/react": "11.2.3",
|
||||||
"@types/applicationinsights-js": "1.0.7",
|
"@types/applicationinsights-js": "1.0.7",
|
||||||
"@types/codemirror": "0.0.56",
|
"@types/codemirror": "0.0.56",
|
||||||
@@ -8585,12 +8585,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@playwright/test": {
|
"node_modules/@playwright/test": {
|
||||||
"version": "1.55.1",
|
"version": "1.59.1",
|
||||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.1.tgz",
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz",
|
||||||
"integrity": "sha512-IVAh/nOJaw6W9g+RJVlIQJ6gSiER+ae6mKQ5CX1bERzQgbC1VSeBlwdvczT7pxb0GWiyrxH4TGKbMfDb4Sq/ig==",
|
"integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright": "1.55.1"
|
"playwright": "1.59.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
@@ -23953,12 +23953,12 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/playwright": {
|
"node_modules/playwright": {
|
||||||
"version": "1.55.1",
|
"version": "1.59.1",
|
||||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.1.tgz",
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz",
|
||||||
"integrity": "sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==",
|
"integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.55.1"
|
"playwright-core": "1.59.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
@@ -23971,9 +23971,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/playwright-core": {
|
"node_modules/playwright-core": {
|
||||||
"version": "1.55.1",
|
"version": "1.59.1",
|
||||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.1.tgz",
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz",
|
||||||
"integrity": "sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==",
|
"integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright-core": "cli.js"
|
"playwright-core": "cli.js"
|
||||||
|
|||||||
@@ -140,7 +140,7 @@
|
|||||||
"@babel/preset-env": "7.24.7",
|
"@babel/preset-env": "7.24.7",
|
||||||
"@babel/preset-react": "7.24.7",
|
"@babel/preset-react": "7.24.7",
|
||||||
"@babel/preset-typescript": "7.24.7",
|
"@babel/preset-typescript": "7.24.7",
|
||||||
"@playwright/test": "1.55.1",
|
"@playwright/test": "1.59.1",
|
||||||
"@testing-library/react": "11.2.3",
|
"@testing-library/react": "11.2.3",
|
||||||
"@types/applicationinsights-js": "1.0.7",
|
"@types/applicationinsights-js": "1.0.7",
|
||||||
"@types/codemirror": "0.0.56",
|
"@types/codemirror": "0.0.56",
|
||||||
|
|||||||
@@ -347,6 +347,7 @@ export interface Offer {
|
|||||||
export interface ThroughputBucket {
|
export interface ThroughputBucket {
|
||||||
id: number;
|
id: number;
|
||||||
maxThroughputPercentage: number;
|
maxThroughputPercentage: number;
|
||||||
|
isDefaultBucket?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SDKOfferDefinition extends Resource {
|
export interface SDKOfferDefinition extends Resource {
|
||||||
|
|||||||
@@ -76,11 +76,11 @@ describe("ThroughputBucketsComponent", () => {
|
|||||||
fireEvent.change(input, { target: { value: "70" } });
|
fireEvent.change(input, { target: { value: "70" } });
|
||||||
|
|
||||||
expect(mockOnBucketsChange).toHaveBeenCalledWith([
|
expect(mockOnBucketsChange).toHaveBeenCalledWith([
|
||||||
{ id: 1, maxThroughputPercentage: 70 },
|
{ id: 1, maxThroughputPercentage: 70, isDefaultBucket: false },
|
||||||
{ id: 2, maxThroughputPercentage: 60 },
|
{ id: 2, maxThroughputPercentage: 60, isDefaultBucket: false },
|
||||||
{ id: 3, maxThroughputPercentage: 100 },
|
{ id: 3, maxThroughputPercentage: 100, isDefaultBucket: false },
|
||||||
{ id: 4, maxThroughputPercentage: 100 },
|
{ id: 4, maxThroughputPercentage: 100, isDefaultBucket: false },
|
||||||
{ id: 5, maxThroughputPercentage: 100 },
|
{ id: 5, maxThroughputPercentage: 100, isDefaultBucket: false },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -102,11 +102,11 @@ describe("ThroughputBucketsComponent", () => {
|
|||||||
fireEvent.change(input2, { target: { value: "80" } });
|
fireEvent.change(input2, { target: { value: "80" } });
|
||||||
|
|
||||||
expect(mockOnBucketsChange).toHaveBeenCalledWith([
|
expect(mockOnBucketsChange).toHaveBeenCalledWith([
|
||||||
{ id: 1, maxThroughputPercentage: 70 },
|
{ id: 1, maxThroughputPercentage: 70, isDefaultBucket: false },
|
||||||
{ id: 2, maxThroughputPercentage: 80 },
|
{ id: 2, maxThroughputPercentage: 80, isDefaultBucket: false },
|
||||||
{ id: 3, maxThroughputPercentage: 100 },
|
{ id: 3, maxThroughputPercentage: 100, isDefaultBucket: false },
|
||||||
{ id: 4, maxThroughputPercentage: 100 },
|
{ id: 4, maxThroughputPercentage: 100, isDefaultBucket: false },
|
||||||
{ id: 5, maxThroughputPercentage: 100 },
|
{ id: 5, maxThroughputPercentage: 100, isDefaultBucket: false },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -134,8 +134,8 @@ describe("ThroughputBucketsComponent", () => {
|
|||||||
<ThroughputBucketsComponent
|
<ThroughputBucketsComponent
|
||||||
{...defaultProps}
|
{...defaultProps}
|
||||||
currentBuckets={[
|
currentBuckets={[
|
||||||
{ id: 1, maxThroughputPercentage: 100 },
|
{ id: 1, maxThroughputPercentage: 100, isDefaultBucket: false },
|
||||||
{ id: 2, maxThroughputPercentage: 50 },
|
{ id: 2, maxThroughputPercentage: 50, isDefaultBucket: false },
|
||||||
]}
|
]}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
@@ -157,21 +157,21 @@ describe("ThroughputBucketsComponent", () => {
|
|||||||
fireEvent.click(toggles[0]);
|
fireEvent.click(toggles[0]);
|
||||||
|
|
||||||
expect(mockOnBucketsChange).toHaveBeenCalledWith([
|
expect(mockOnBucketsChange).toHaveBeenCalledWith([
|
||||||
{ id: 1, maxThroughputPercentage: 100 },
|
{ id: 1, maxThroughputPercentage: 100, isDefaultBucket: false },
|
||||||
{ id: 2, maxThroughputPercentage: 60 },
|
{ id: 2, maxThroughputPercentage: 60, isDefaultBucket: false },
|
||||||
{ id: 3, maxThroughputPercentage: 100 },
|
{ id: 3, maxThroughputPercentage: 100, isDefaultBucket: false },
|
||||||
{ id: 4, maxThroughputPercentage: 100 },
|
{ id: 4, maxThroughputPercentage: 100, isDefaultBucket: false },
|
||||||
{ id: 5, maxThroughputPercentage: 100 },
|
{ id: 5, maxThroughputPercentage: 100, isDefaultBucket: false },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
fireEvent.click(toggles[0]);
|
fireEvent.click(toggles[0]);
|
||||||
|
|
||||||
expect(mockOnBucketsChange).toHaveBeenCalledWith([
|
expect(mockOnBucketsChange).toHaveBeenCalledWith([
|
||||||
{ id: 1, maxThroughputPercentage: 50 },
|
{ id: 1, maxThroughputPercentage: 50, isDefaultBucket: false },
|
||||||
{ id: 2, maxThroughputPercentage: 60 },
|
{ id: 2, maxThroughputPercentage: 60, isDefaultBucket: false },
|
||||||
{ id: 3, maxThroughputPercentage: 100 },
|
{ id: 3, maxThroughputPercentage: 100, isDefaultBucket: false },
|
||||||
{ id: 4, maxThroughputPercentage: 100 },
|
{ id: 4, maxThroughputPercentage: 100, isDefaultBucket: false },
|
||||||
{ id: 5, maxThroughputPercentage: 100 },
|
{ id: 5, maxThroughputPercentage: 100, isDefaultBucket: false },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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 { ThroughputBucket } from "Contracts/DataModels";
|
||||||
import { Keys, t } from "Localization";
|
import { Keys, t } from "Localization";
|
||||||
import React, { FC, useEffect, useState } from "react";
|
import React, { FC, useEffect, useState } from "react";
|
||||||
@@ -9,6 +21,7 @@ const MAX_BUCKET_SIZES = 5;
|
|||||||
const DEFAULT_BUCKETS = Array.from({ length: MAX_BUCKET_SIZES }, (_, i) => ({
|
const DEFAULT_BUCKETS = Array.from({ length: MAX_BUCKET_SIZES }, (_, i) => ({
|
||||||
id: i + 1,
|
id: i + 1,
|
||||||
maxThroughputPercentage: 100,
|
maxThroughputPercentage: 100,
|
||||||
|
isDefaultBucket: false,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export interface ThroughputBucketsComponentProps {
|
export interface ThroughputBucketsComponentProps {
|
||||||
@@ -24,19 +37,54 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
|
|||||||
onBucketsChange,
|
onBucketsChange,
|
||||||
onSaveableChange,
|
onSaveableChange,
|
||||||
}) => {
|
}) => {
|
||||||
|
const NoDefaultThroughputSelectedKey: number = -1;
|
||||||
const getThroughputBuckets = (buckets: ThroughputBucket[]): ThroughputBucket[] => {
|
const getThroughputBuckets = (buckets: ThroughputBucket[]): ThroughputBucket[] => {
|
||||||
if (!buckets || buckets.length === 0) {
|
if (!buckets || buckets.length === 0) {
|
||||||
return DEFAULT_BUCKETS;
|
return DEFAULT_BUCKETS;
|
||||||
}
|
}
|
||||||
const maxBuckets = Math.max(DEFAULT_BUCKETS.length, buckets.length);
|
const maxBuckets = Math.max(DEFAULT_BUCKETS.length, buckets.length);
|
||||||
const adjustedDefaultBuckets = Array.from({ length: maxBuckets }, (_, i) => ({
|
const adjustedDefaultBuckets: ThroughputBucket[] = Array.from(
|
||||||
id: i + 1,
|
{ length: maxBuckets },
|
||||||
maxThroughputPercentage: 100,
|
(_, 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: t(Keys.controls.settings.throughputBuckets.noDefaultBucketSelected),
|
||||||
|
};
|
||||||
|
|
||||||
|
const throughputBucketOptions: IDropdownOption[] = throughputBuckets.map((bucket) => ({
|
||||||
|
key: bucket.id,
|
||||||
|
text: t(Keys.controls.settings.throughputBuckets.bucketOptionLabel, {
|
||||||
|
id: String(bucket.id),
|
||||||
|
percentage: String(bucket.maxThroughputPercentage),
|
||||||
|
}),
|
||||||
|
disabled: bucket.maxThroughputPercentage === 100,
|
||||||
|
...(bucket.maxThroughputPercentage === 100 && {
|
||||||
|
data: {
|
||||||
|
tooltip: t(Keys.controls.settings.throughputBuckets.bucketNotActive, { id: String(bucket.id) }),
|
||||||
|
},
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return adjustedDefaultBuckets.map(
|
return [noDefaultThroughputBucketSelected, ...throughputBucketOptions];
|
||||||
(defaultBucket) => buckets?.find((bucket) => bucket.id === defaultBucket.id) || defaultBucket,
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const [throughputBuckets, setThroughputBuckets] = useState<ThroughputBucket[]>(getThroughputBuckets(currentBuckets));
|
const [throughputBuckets, setThroughputBuckets] = useState<ThroughputBucket[]>(getThroughputBuckets(currentBuckets));
|
||||||
@@ -53,7 +101,13 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
|
|||||||
|
|
||||||
const handleBucketChange = (id: number, newValue: number) => {
|
const handleBucketChange = (id: number, newValue: number) => {
|
||||||
const updatedBuckets = throughputBuckets.map((bucket) =>
|
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);
|
setThroughputBuckets(updatedBuckets);
|
||||||
const settingsChanged = isDirty(updatedBuckets, throughputBuckets);
|
const settingsChanged = isDirty(updatedBuckets, throughputBuckets);
|
||||||
@@ -64,6 +118,38 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
|
|||||||
handleBucketChange(id, checked ? 50 : 100);
|
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>
|
||||||
|
{t(Keys.controls.settings.throughputBuckets.defaultBucketTooltip)}{" "}
|
||||||
|
<Link
|
||||||
|
href="https://learn.microsoft.com/azure/cosmos-db/throughput-buckets?tabs=dotnet#configuring-default-throughput-bucket"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{t(Keys.controls.settings.throughputBuckets.defaultBucketTooltipLearnMore)}
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack horizontal verticalAlign="center">
|
||||||
|
<Label>{t(Keys.controls.settings.throughputBuckets.defaultBucketLabel)}</Label>
|
||||||
|
<TooltipHost content={tooltipContent()}>
|
||||||
|
<Icon iconName="Info" styles={{ root: { marginLeft: 4, marginTop: 5 } }} />
|
||||||
|
</TooltipHost>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack tokens={{ childrenGap: "m" }} styles={{ root: { width: "70%", maxWidth: 700 } }}>
|
<Stack tokens={{ childrenGap: "m" }} styles={{ root: { width: "70%", maxWidth: 700 } }}>
|
||||||
<Label styles={{ root: { color: "var(--colorNeutralForeground1)" } }}>
|
<Label styles={{ root: { color: "var(--colorNeutralForeground1)" } }}>
|
||||||
@@ -102,6 +188,7 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
|
|||||||
fieldGroup: { width: 80 },
|
fieldGroup: { width: 80 },
|
||||||
}}
|
}}
|
||||||
disabled={bucket.maxThroughputPercentage === 100}
|
disabled={bucket.maxThroughputPercentage === 100}
|
||||||
|
data-test={`bucket-${bucket.id}-percentage-input`}
|
||||||
/>
|
/>
|
||||||
<Toggle
|
<Toggle
|
||||||
onText={t(Keys.controls.settings.throughputBuckets.active)}
|
onText={t(Keys.controls.settings.throughputBuckets.active)}
|
||||||
@@ -112,10 +199,36 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
|
|||||||
root: { marginBottom: 0 },
|
root: { marginBottom: 0 },
|
||||||
text: { fontSize: 12, color: "var(--colorNeutralForeground1)" },
|
text: { fontSize: 12, color: "var(--colorNeutralForeground1)" },
|
||||||
}}
|
}}
|
||||||
|
data-test={`bucket-${bucket.id}-active-toggle`}
|
||||||
></Toggle>
|
></Toggle>
|
||||||
</Stack>
|
</Stack>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
<Dropdown
|
||||||
|
placeholder={t(Keys.controls.settings.throughputBuckets.defaultBucketPlaceholder)}
|
||||||
|
label={t(Keys.controls.settings.throughputBuckets.defaultBucketLabel)}
|
||||||
|
options={getThroughputBucketOptions()}
|
||||||
|
selectedKey={
|
||||||
|
throughputBuckets?.find((throughputbucket: ThroughputBucket) => throughputbucket.isDefaultBucket)?.id ||
|
||||||
|
NoDefaultThroughputSelectedKey
|
||||||
|
}
|
||||||
|
onChange={(_, option) => onDefaultBucketToggle(option.key as number, true)}
|
||||||
|
onRenderLabel={onRenderDefaultThroughputBucketLabel}
|
||||||
|
onRenderOption={(option: IDropdownOption) => {
|
||||||
|
const tooltip: string = option?.data?.tooltip;
|
||||||
|
if (!tooltip) {
|
||||||
|
return <>{option?.text}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TooltipHost content={tooltip}>
|
||||||
|
<span>{option?.text}</span>
|
||||||
|
</TooltipHost>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
styles={{ root: { width: "50%" } }}
|
||||||
|
data-test="default-throughput-bucket-dropdown"
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -961,7 +961,14 @@
|
|||||||
"bucketLabel": "Bucket {{id}}",
|
"bucketLabel": "Bucket {{id}}",
|
||||||
"dataExplorerQueryBucket": " (Data Explorer Query Bucket)",
|
"dataExplorerQueryBucket": " (Data Explorer Query Bucket)",
|
||||||
"active": "Active",
|
"active": "Active",
|
||||||
"inactive": "Inactive"
|
"inactive": "Inactive",
|
||||||
|
"defaultBucketLabel": "Default Throughput Bucket",
|
||||||
|
"defaultBucketPlaceholder": "Select a default throughput bucket",
|
||||||
|
"defaultBucketTooltip": "The default throughput bucket is used for operations that do not specify a particular bucket.",
|
||||||
|
"defaultBucketTooltipLearnMore": "Learn more.",
|
||||||
|
"noDefaultBucketSelected": "No Default Throughput Bucket Selected",
|
||||||
|
"bucketOptionLabel": "Bucket {{id}} - {{percentage}}%",
|
||||||
|
"bucketNotActive": "Bucket {{id}} is not active."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ test.describe("Container Copy - Permission Screen Verification", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test.afterEach("Cleanup after each test", async () => {
|
test.afterEach("Cleanup after each test", async () => {
|
||||||
await page.unroute(/.*/, (route) => route.continue());
|
await page.unrouteAll({ behavior: "ignoreErrors" });
|
||||||
await page.close();
|
await page.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ test.describe("Computed Properties", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test.afterAll("Delete Test Database", async () => {
|
test.afterAll("Delete Test Database", async () => {
|
||||||
await context?.dispose();
|
if (!process.env.CI) {
|
||||||
|
await context?.dispose();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Add valid computed property", async ({ page }) => {
|
test("Add valid computed property", async ({ page }) => {
|
||||||
|
|||||||
108
test/sql/scaleAndSettings/throughputbucket.spec.ts
Normal file
108
test/sql/scaleAndSettings/throughputbucket.spec.ts
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import { expect, test } from "@playwright/test";
|
||||||
|
import { CommandBarButton, DataExplorer, ONE_MINUTE_MS, TestAccount } from "../../fx";
|
||||||
|
import { createTestSQLContainer, TestContainerContext } from "../../testData";
|
||||||
|
|
||||||
|
test.describe("Throughput bucket settings", () => {
|
||||||
|
let context: TestContainerContext = null!;
|
||||||
|
let explorer: DataExplorer = null!;
|
||||||
|
|
||||||
|
test.beforeAll("Create Test Database", async () => {
|
||||||
|
context = await createTestSQLContainer();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.beforeEach("Open Throughput Bucket Settings", async ({ browser }) => {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
explorer = await DataExplorer.open(page, TestAccount.SQL);
|
||||||
|
|
||||||
|
// Click Scale & Settings and open Throughput Bucket Settings tab
|
||||||
|
await explorer.openScaleAndSettings(context);
|
||||||
|
const throughputBucketTab = explorer.frame.getByTestId("settings-tab-header/ThroughputBucketsTab");
|
||||||
|
await throughputBucketTab.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete database only if not running in CI
|
||||||
|
if (!process.env.CI) {
|
||||||
|
test.afterAll("Delete Test Database", async () => {
|
||||||
|
await context?.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Activate throughput bucket #2", async () => {
|
||||||
|
// Activate bucket 2
|
||||||
|
const bucket2Toggle = explorer.frame.getByTestId("bucket-2-active-toggle");
|
||||||
|
await bucket2Toggle.click();
|
||||||
|
|
||||||
|
await explorer.commandBarButton(CommandBarButton.Save).click();
|
||||||
|
await expect(explorer.getConsoleHeaderStatus()).toContainText(
|
||||||
|
`Successfully updated offer for collection ${context.container.id}`,
|
||||||
|
{
|
||||||
|
timeout: 2 * ONE_MINUTE_MS,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Activate throughput buckets #1 and #2", async () => {
|
||||||
|
// Activate bucket 1
|
||||||
|
const bucket1Toggle = explorer.frame.getByTestId("bucket-1-active-toggle");
|
||||||
|
await bucket1Toggle.click();
|
||||||
|
|
||||||
|
// Activate bucket 2
|
||||||
|
const bucket2Toggle = explorer.frame.getByTestId("bucket-2-active-toggle");
|
||||||
|
await bucket2Toggle.click();
|
||||||
|
await explorer.commandBarButton(CommandBarButton.Save).click();
|
||||||
|
await expect(explorer.getConsoleHeaderStatus()).toContainText(
|
||||||
|
`Successfully updated offer for collection ${context.container.id}`,
|
||||||
|
{
|
||||||
|
timeout: 2 * ONE_MINUTE_MS,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Set throughput percentage for bucket #1", async () => {
|
||||||
|
// Set throughput percentage for bucket 1 (inactive) - Should be disabled
|
||||||
|
const bucket1PercentageInput = explorer.frame.getByTestId("bucket-1-percentage-input");
|
||||||
|
expect(bucket1PercentageInput).toBeDisabled();
|
||||||
|
|
||||||
|
// Activate bucket 1
|
||||||
|
const bucket1Toggle = explorer.frame.getByTestId("bucket-1-active-toggle");
|
||||||
|
await bucket1Toggle.click();
|
||||||
|
expect(bucket1PercentageInput).toBeEnabled();
|
||||||
|
await bucket1PercentageInput.fill("40");
|
||||||
|
|
||||||
|
await explorer.commandBarButton(CommandBarButton.Save).click();
|
||||||
|
await expect(explorer.getConsoleHeaderStatus()).toContainText(
|
||||||
|
`Successfully updated offer for collection ${context.container.id}`,
|
||||||
|
{
|
||||||
|
timeout: 2 * ONE_MINUTE_MS,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Set default throughput bucket", async () => {
|
||||||
|
// There are no active throughput buckets so they all should be disabled
|
||||||
|
const defaultThroughputBucketDropdown = explorer.frame.getByTestId("default-throughput-bucket-dropdown");
|
||||||
|
await defaultThroughputBucketDropdown.click();
|
||||||
|
|
||||||
|
const bucket1Option = explorer.frame.getByRole("option", { name: "Bucket 1" });
|
||||||
|
expect(bucket1Option).toBeDisabled();
|
||||||
|
|
||||||
|
// Activate bucket 1
|
||||||
|
const bucket1Toggle = explorer.frame.getByTestId("bucket-1-active-toggle");
|
||||||
|
await bucket1Toggle.click();
|
||||||
|
|
||||||
|
// Open dropdown again
|
||||||
|
await defaultThroughputBucketDropdown.click();
|
||||||
|
expect(bucket1Option).toBeEnabled();
|
||||||
|
|
||||||
|
// Select bucket 1 as default
|
||||||
|
await bucket1Option.click();
|
||||||
|
|
||||||
|
await explorer.commandBarButton(CommandBarButton.Save).click();
|
||||||
|
await expect(explorer.getConsoleHeaderStatus()).toContainText(
|
||||||
|
`Successfully updated offer for collection ${context.container.id}`,
|
||||||
|
{
|
||||||
|
timeout: 2 * ONE_MINUTE_MS,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -223,10 +223,15 @@ export async function createTestSQLContainer({
|
|||||||
|
|
||||||
const { database } = await client.databases.createIfNotExists({ id: databaseId });
|
const { database } = await client.databases.createIfNotExists({ id: databaseId });
|
||||||
try {
|
try {
|
||||||
const { container } = await database.containers.createIfNotExists({
|
const { container } = await database.containers.createIfNotExists(
|
||||||
id: containerId,
|
{
|
||||||
partitionKey,
|
id: containerId,
|
||||||
});
|
partitionKey,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offerThroughput: 4000,
|
||||||
|
},
|
||||||
|
);
|
||||||
if (includeTestData) {
|
if (includeTestData) {
|
||||||
const batchCount = TestData.length / 100;
|
const batchCount = TestData.length / 100;
|
||||||
for (let i = 0; i < batchCount; i++) {
|
for (let i = 0; i < batchCount; i++) {
|
||||||
|
|||||||
Reference in New Issue
Block a user