Updated to a tab

This commit is contained in:
Sung-Hyun Kang
2025-01-26 18:11:50 -06:00
parent 05e2d0ac29
commit 5dfaa9f0f8
4 changed files with 59 additions and 85 deletions

View File

@@ -7,6 +7,10 @@ import {
ContainerPolicyComponent, ContainerPolicyComponent,
ContainerPolicyComponentProps, ContainerPolicyComponentProps,
} from "Explorer/Controls/Settings/SettingsSubComponents/ContainerPolicyComponent"; } from "Explorer/Controls/Settings/SettingsSubComponents/ContainerPolicyComponent";
import {
ThroughputBucketsComponent,
ThroughputBucketsComponentProps,
} from "Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
import { isFullTextSearchEnabled, isVectorSearchEnabled } from "Utils/CapabilityUtils"; import { isFullTextSearchEnabled, isVectorSearchEnabled } from "Utils/CapabilityUtils";
import { isRunningOnPublicCloud } from "Utils/CloudUtils"; import { isRunningOnPublicCloud } from "Utils/CloudUtils";
@@ -338,7 +342,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.state.isIndexingPolicyDirty || this.state.isIndexingPolicyDirty ||
this.state.isConflictResolutionDirty || this.state.isConflictResolutionDirty ||
this.state.isComputedPropertiesDirty || this.state.isComputedPropertiesDirty ||
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicySaveable) (!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicySaveable) ||
this.state.isThroughputBucketsSaveable
); );
}; };
@@ -350,7 +355,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.state.isIndexingPolicyDirty || this.state.isIndexingPolicyDirty ||
this.state.isConflictResolutionDirty || this.state.isConflictResolutionDirty ||
this.state.isComputedPropertiesDirty || this.state.isComputedPropertiesDirty ||
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicyDiscardable) (!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicyDiscardable) ||
this.state.isThroughputBucketsSaveable
); );
}; };
@@ -1055,6 +1061,24 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
} }
} }
if (this.throughputBucketsEnabled && this.state.isThroughputBucketsSaveable) {
const updatedOffer: DataModels.Offer = await updateOffer({
databaseId: this.collection.databaseId,
collectionId: this.collection.id(),
currentOffer: this.collection.offer(),
autopilotThroughput: this.collection.offer().autoscaleMaxThroughput
? this.collection.offer().autoscaleMaxThroughput
: undefined,
manualThroughput: this.collection.offer().manualThroughput
? this.collection.offer().manualThroughput
: undefined,
throughputBuckets: this.state.throughputBuckets,
});
this.collection.offer(updatedOffer);
this.offer = updatedOffer;
this.setState({ isThroughputBucketsSaveable: false });
}
if (this.state.isScaleSaveable) { if (this.state.isScaleSaveable) {
const updateOfferParams: DataModels.UpdateOfferParams = { const updateOfferParams: DataModels.UpdateOfferParams = {
databaseId: this.collection.databaseId, databaseId: this.collection.databaseId,
@@ -1062,8 +1086,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
currentOffer: this.collection.offer(), currentOffer: this.collection.offer(),
autopilotThroughput: this.state.isAutoPilotSelected ? this.state.autoPilotThroughput : undefined, autopilotThroughput: this.state.isAutoPilotSelected ? this.state.autoPilotThroughput : undefined,
manualThroughput: this.state.isAutoPilotSelected ? undefined : this.state.throughput, manualThroughput: this.state.isAutoPilotSelected ? undefined : this.state.throughput,
...(this.throughputBucketsEnabled &&
this.state.isThroughputBucketsSaveable && { throughputBuckets: this.state.throughputBuckets }),
}; };
if (this.hasProvisioningTypeChanged()) { if (this.hasProvisioningTypeChanged()) {
if (this.state.isAutoPilotSelected) { if (this.state.isAutoPilotSelected) {
@@ -1130,11 +1152,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
onMaxAutoPilotThroughputChange: this.onMaxAutoPilotThroughputChange, onMaxAutoPilotThroughputChange: this.onMaxAutoPilotThroughputChange,
onScaleSaveableChange: this.onScaleSaveableChange, onScaleSaveableChange: this.onScaleSaveableChange,
onScaleDiscardableChange: this.onScaleDiscardableChange, onScaleDiscardableChange: this.onScaleDiscardableChange,
throughputBucketsBaseline: this.state.throughputBucketsBaseline,
throughputBuckets: this.state.throughputBuckets,
enableThroughputBuckets: this.isCollectionSettingsTab && this.throughputBucketsEnabled,
onThroughputBucketChange: this.onThroughputBucketChange,
onThroughputBucketsSaveableChange: this.onThroughputBucketsSaveableChange,
throughputError: this.state.throughputError, throughputError: this.state.throughputError,
}; };
@@ -1242,6 +1259,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
onConflictResolutionDirtyChange: this.onConflictResolutionDirtyChange, onConflictResolutionDirtyChange: this.onConflictResolutionDirtyChange,
}; };
const throughputBucketsComponentProps: ThroughputBucketsComponentProps = {
currentBuckets: this.state.throughputBuckets,
throughputBucketsBaseline: this.state.throughputBucketsBaseline,
onBucketsChange: this.onThroughputBucketChange,
onSaveableChange: this.onThroughputBucketsSaveableChange,
};
const partitionKeyComponentProps: PartitionKeyComponentProps = { const partitionKeyComponentProps: PartitionKeyComponentProps = {
database: useDatabases.getState().findDatabaseWithId(this.collection.databaseId), database: useDatabases.getState().findDatabaseWithId(this.collection.databaseId),
collection: this.collection, collection: this.collection,
@@ -1304,6 +1328,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
}); });
} }
if (this.throughputBucketsEnabled) {
tabs.push({
tab: SettingsV2TabTypes.ThroughputBucketsTab,
content: <ThroughputBucketsComponent {...throughputBucketsComponentProps} />,
});
}
const pivotProps: IPivotProps = { const pivotProps: IPivotProps = {
onLinkClick: this.onPivotChange, onLinkClick: this.onPivotChange,
selectedKey: SettingsV2TabTypes[this.state.selectedTab], selectedKey: SettingsV2TabTypes[this.state.selectedTab],

View File

@@ -16,7 +16,6 @@ import {
titleAndInputStackProps, titleAndInputStackProps,
} from "../SettingsRenderUtils"; } from "../SettingsRenderUtils";
import { hasDatabaseSharedThroughput } from "../SettingsUtils"; import { hasDatabaseSharedThroughput } from "../SettingsUtils";
import { ThroughputBucketsComponent } from "./ThroughputInputComponents/ThroughputBucketsComponent";
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component"; import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
export interface ScaleComponentProps { export interface ScaleComponentProps {
@@ -34,22 +33,10 @@ export interface ScaleComponentProps {
onMaxAutoPilotThroughputChange: (newThroughput: number) => void; onMaxAutoPilotThroughputChange: (newThroughput: number) => void;
onScaleSaveableChange: (isScaleSaveable: boolean) => void; onScaleSaveableChange: (isScaleSaveable: boolean) => void;
onScaleDiscardableChange: (isScaleDiscardable: boolean) => void; onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
throughputBuckets: DataModels.ThroughputBucket[];
throughputBucketsBaseline: DataModels.ThroughputBucket[];
enableThroughputBuckets: boolean;
onThroughputBucketChange: (throughputBuckets: DataModels.ThroughputBucket[]) => void;
onThroughputBucketsSaveableChange: (isSaveable: boolean) => void;
throughputError?: string; throughputError?: string;
} }
interface ScaleComponentState { export class ScaleComponent extends React.Component<ScaleComponentProps> {
isThroughputSaveable: boolean;
isThroughputBucketsSaveable: boolean;
isThroughputDiscardable: boolean;
isThroughputBucketsDiscardable: boolean;
}
export class ScaleComponent extends React.Component<ScaleComponentProps, ScaleComponentState> {
private isEmulator: boolean; private isEmulator: boolean;
private offer: DataModels.Offer; private offer: DataModels.Offer;
private databaseId: string; private databaseId: string;
@@ -61,12 +48,6 @@ export class ScaleComponent extends React.Component<ScaleComponentProps, ScaleCo
this.offer = this.props.database?.offer() || this.props.collection?.offer(); this.offer = this.props.database?.offer() || this.props.collection?.offer();
this.databaseId = this.props.database?.id() || this.props.collection.databaseId; this.databaseId = this.props.database?.id() || this.props.collection.databaseId;
this.collectionId = this.props.collection?.id(); this.collectionId = this.props.collection?.id();
this.state = {
isThroughputSaveable: false,
isThroughputBucketsSaveable: false,
isThroughputDiscardable: false,
isThroughputBucketsDiscardable: false,
};
} }
public isAutoScaleEnabled = (): boolean => { public isAutoScaleEnabled = (): boolean => {
@@ -80,6 +61,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps, ScaleCo
capability.name.toLowerCase() === Constants.CapabilityNames.EnableAutoScale.toLowerCase() capability.name.toLowerCase() === Constants.CapabilityNames.EnableAutoScale.toLowerCase()
); );
}); });
return !!enableAutoScaleCapability; return !!enableAutoScaleCapability;
}; };
@@ -99,6 +81,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps, ScaleCo
if (userContext.isTryCosmosDBSubscription) { if (userContext.isTryCosmosDBSubscription) {
return SharedConstants.CollectionCreation.DefaultCollectionRUs400; return SharedConstants.CollectionCreation.DefaultCollectionRUs400;
} }
return this.offer?.minimumThroughput || SharedConstants.CollectionCreation.DefaultCollectionRUs400; return this.offer?.minimumThroughput || SharedConstants.CollectionCreation.DefaultCollectionRUs400;
}; };
@@ -154,8 +137,8 @@ export class ScaleComponent extends React.Component<ScaleComponentProps, ScaleCo
maxAutoPilotThroughputBaseline={this.props.autoPilotThroughputBaseline} maxAutoPilotThroughputBaseline={this.props.autoPilotThroughputBaseline}
onMaxAutoPilotThroughputChange={this.props.onMaxAutoPilotThroughputChange} onMaxAutoPilotThroughputChange={this.props.onMaxAutoPilotThroughputChange}
spendAckChecked={false} spendAckChecked={false}
onScaleSaveableChange={this.handleThroughputSaveableChange} onScaleSaveableChange={this.props.onScaleSaveableChange}
onScaleDiscardableChange={this.handleThroughputDiscardableChange} onScaleDiscardableChange={this.props.onScaleDiscardableChange}
usageSizeInKB={this.props.collection?.usageSizeInKB()} usageSizeInKB={this.props.collection?.usageSizeInKB()}
throughputError={this.props.throughputError} throughputError={this.props.throughputError}
instantMaximumThroughput={this.offer?.instantMaximumThroughput} instantMaximumThroughput={this.offer?.instantMaximumThroughput}
@@ -184,42 +167,6 @@ export class ScaleComponent extends React.Component<ScaleComponentProps, ScaleCo
); );
} }
private updateScaleSettingsState = (updates: Partial<ScaleComponentState>) => {
this.setState(
(prevState) => {
const hasChanges = Object.keys(updates).some(
(key) => prevState[key as keyof ScaleComponentState] !== updates[key as keyof ScaleComponentState],
);
return hasChanges ? { ...prevState, ...updates } : null;
},
() => {
const isSaveable = this.state.isThroughputSaveable
? this.state.isThroughputDiscardable || this.state.isThroughputBucketsSaveable
: this.state.isThroughputBucketsSaveable;
const isDiscardable = this.state.isThroughputDiscardable || this.state.isThroughputBucketsDiscardable;
this.props.onScaleSaveableChange(isSaveable);
this.props.onThroughputBucketsSaveableChange(this.state.isThroughputBucketsSaveable);
this.props.onScaleDiscardableChange(isDiscardable);
},
);
};
private handleThroughputSaveableChange = (isSaveable: boolean) => {
this.updateScaleSettingsState({ isThroughputSaveable: isSaveable });
};
private handleThroughputDiscardableChange = (isDiscardable: boolean) => {
this.updateScaleSettingsState({ isThroughputDiscardable: isDiscardable });
};
private handleThroughputBucketsSaveableChange = (isSaveable: boolean) => {
this.updateScaleSettingsState({ isThroughputBucketsSaveable: isSaveable });
};
private handleThroughputBucketsDiscardableChange = (isDiscardable: boolean) => {
this.updateScaleSettingsState({ isThroughputBucketsDiscardable: isDiscardable });
};
public render(): JSX.Element { public render(): JSX.Element {
return ( return (
<Stack {...subComponentStackProps}> <Stack {...subComponentStackProps}>
@@ -235,15 +182,6 @@ export class ScaleComponent extends React.Component<ScaleComponentProps, ScaleCo
<MessageBar messageBarType={MessageBarType.warning}>{this.getInitialNotificationElement()}</MessageBar> <MessageBar messageBarType={MessageBarType.warning}>{this.getInitialNotificationElement()}</MessageBar>
)} )}
{!this.isAutoScaleEnabled() && <Stack {...subComponentStackProps}>{this.getThroughputInputComponent()}</Stack>} {!this.isAutoScaleEnabled() && <Stack {...subComponentStackProps}>{this.getThroughputInputComponent()}</Stack>}
{this.props.enableThroughputBuckets && (
<ThroughputBucketsComponent
currentBuckets={this.props.throughputBuckets}
throughputBucketsBaseline={this.props.throughputBucketsBaseline}
onBucketsChange={this.props.onThroughputBucketChange}
onSaveableChange={this.handleThroughputBucketsSaveableChange}
onDiscardableChange={this.handleThroughputBucketsDiscardableChange}
/>
)}
{/* TODO: Replace link with call to the Azure Support blade */} {/* TODO: Replace link with call to the Azure Support blade */}
{this.isAutoScaleEnabled() && ( {this.isAutoScaleEnabled() && (

View File

@@ -10,12 +10,11 @@ const DEFAULT_BUCKETS = Array.from({ length: MAX_BUCKET_SIZES }, (_, i) => ({
maxThroughputPercentage: 100, maxThroughputPercentage: 100,
})); }));
interface ThroughputBucketsComponentProps { export interface ThroughputBucketsComponentProps {
currentBuckets: ThroughputBucket[]; currentBuckets: ThroughputBucket[];
throughputBucketsBaseline: ThroughputBucket[]; throughputBucketsBaseline: ThroughputBucket[];
onBucketsChange: (updatedBuckets: ThroughputBucket[]) => void; onBucketsChange: (updatedBuckets: ThroughputBucket[]) => void;
onSaveableChange: (isSaveable: boolean) => void; onSaveableChange: (isSaveable: boolean) => void;
onDiscardableChange: (isDiscardable: boolean) => void;
} }
export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = ({ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = ({
@@ -23,7 +22,6 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
throughputBucketsBaseline, throughputBucketsBaseline,
onBucketsChange, onBucketsChange,
onSaveableChange, onSaveableChange,
onDiscardableChange,
}) => { }) => {
const getThroughputBuckets = (buckets: ThroughputBucket[]): ThroughputBucket[] => { const getThroughputBuckets = (buckets: ThroughputBucket[]): ThroughputBucket[] => {
if (!buckets || buckets.length === 0) { if (!buckets || buckets.length === 0) {
@@ -45,13 +43,13 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
useEffect(() => { useEffect(() => {
setThroughputBuckets(getThroughputBuckets(currentBuckets)); setThroughputBuckets(getThroughputBuckets(currentBuckets));
onSaveableChange(false); onSaveableChange(false);
onDiscardableChange(false); // onDiscardableChange(false);
}, [currentBuckets]); }, [currentBuckets]);
useEffect(() => { useEffect(() => {
const isChanged = isDirty(throughputBuckets, getThroughputBuckets(throughputBucketsBaseline)); const isChanged = isDirty(throughputBuckets, getThroughputBuckets(throughputBucketsBaseline));
onSaveableChange(isChanged); onSaveableChange(isChanged);
onDiscardableChange(isChanged); // onDiscardableChange(isChanged);
}, [throughputBuckets]); }, [throughputBuckets]);
const handleBucketChange = (id: number, newValue: number) => { const handleBucketChange = (id: number, newValue: number) => {
@@ -69,7 +67,7 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
return ( return (
<Stack tokens={{ childrenGap: "m" }} styles={{ root: { width: "70%", maxWidth: 700 } }}> <Stack tokens={{ childrenGap: "m" }} styles={{ root: { width: "70%", maxWidth: 700 } }}>
<Label>Throughput groups</Label> <Label>Throughput Buckets</Label>
<Stack> <Stack>
{throughputBuckets?.map((bucket) => ( {throughputBuckets?.map((bucket) => (
<Stack key={bucket.id} horizontal tokens={{ childrenGap: 8 }} verticalAlign="center"> <Stack key={bucket.id} horizontal tokens={{ childrenGap: 8 }} verticalAlign="center">
@@ -80,7 +78,7 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
value={bucket.maxThroughputPercentage} value={bucket.maxThroughputPercentage}
onChange={(newValue) => handleBucketChange(bucket.id, newValue)} onChange={(newValue) => handleBucketChange(bucket.id, newValue)}
showValue={false} showValue={false}
label={`Group ${bucket.id}${bucket.id === 1 ? " (Data Explorer Query Group)" : ""}`} label={`Group ${bucket.id}${bucket.id === 1 ? " (Data Explorer Query Bucket)" : ""}`}
styles={{ root: { flex: 2, maxWidth: 400 } }} styles={{ root: { flex: 2, maxWidth: 400 } }}
disabled={bucket.maxThroughputPercentage === 100} disabled={bucket.maxThroughputPercentage === 100}
/> />
@@ -94,9 +92,13 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
}} }}
disabled={bucket.maxThroughputPercentage === 100} disabled={bucket.maxThroughputPercentage === 100}
/> />
{/* <IconButton
iconProps={{ iconName: bucket.maxThroughputPercentage === 100 ? "Add" : "Remove" }}
onClick={() => onToggle(bucket.id, bucket.maxThroughputPercentage === 100)}
></IconButton> */}
<Toggle <Toggle
onText="Enabled" onText="Active"
offText="Disabled" offText="Inactive"
checked={bucket.maxThroughputPercentage !== 100} checked={bucket.maxThroughputPercentage !== 100}
onChange={(event, checked) => onToggle(bucket.id, checked)} onChange={(event, checked) => onToggle(bucket.id, checked)}
styles={{ root: { marginBottom: 0 }, text: { fontSize: 12 } }} styles={{ root: { marginBottom: 0 }, text: { fontSize: 12 } }}

View File

@@ -56,6 +56,7 @@ export enum SettingsV2TabTypes {
PartitionKeyTab, PartitionKeyTab,
ComputedPropertiesTab, ComputedPropertiesTab,
ContainerVectorPolicyTab, ContainerVectorPolicyTab,
ThroughputBucketsTab,
} }
export enum ContainerPolicyTabTypes { export enum ContainerPolicyTabTypes {
@@ -168,6 +169,8 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => {
return "Computed Properties"; return "Computed Properties";
case SettingsV2TabTypes.ContainerVectorPolicyTab: case SettingsV2TabTypes.ContainerVectorPolicyTab:
return "Container Policies"; return "Container Policies";
case SettingsV2TabTypes.ThroughputBucketsTab:
return "Throughput Buckets";
default: default:
throw new Error(`Unknown tab ${tab}`); throw new Error(`Unknown tab ${tab}`);
} }