mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-03-07 08:44:49 +00:00
Compare commits
4 Commits
master
...
loc_batch4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3385b62b60 | ||
|
|
79a00080bd | ||
|
|
8bc875ae92 | ||
|
|
47b8737ab1 |
@@ -17,6 +17,8 @@ import {
|
||||
} from "@fluentui/react";
|
||||
import React, { FC, useEffect } from "react";
|
||||
import create, { UseStore } from "zustand";
|
||||
import { Keys } from "../../Localization/Keys.generated";
|
||||
import { t } from "../../Localization/t";
|
||||
|
||||
export interface DialogState {
|
||||
visible: boolean;
|
||||
@@ -88,7 +90,7 @@ export const useDialog: UseStore<DialogState> = create((set, get) => ({
|
||||
isModal: true,
|
||||
title,
|
||||
subText,
|
||||
primaryButtonText: "Close",
|
||||
primaryButtonText: t(Keys.common.close),
|
||||
secondaryButtonText: undefined,
|
||||
onPrimaryButtonClick: () => {
|
||||
get().closeDialog();
|
||||
|
||||
@@ -44,6 +44,8 @@ import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter
|
||||
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
|
||||
import "./SettingsComponent.less";
|
||||
import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";
|
||||
import { Keys } from "../../../Localization/Keys.generated";
|
||||
import { t } from "../../../Localization/t";
|
||||
import {
|
||||
ConflictResolutionComponent,
|
||||
ConflictResolutionComponentProps,
|
||||
@@ -689,12 +691,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
private onDataMaskingContentChange = (newDataMasking: DataModels.DataMaskingPolicy): void => {
|
||||
const validationErrors = [];
|
||||
if (newDataMasking.includedPaths === undefined || newDataMasking.includedPaths === null) {
|
||||
validationErrors.push("includedPaths is required");
|
||||
validationErrors.push(t(Keys.controls.settings.dataMasking.includedPathsRequired));
|
||||
} else if (!Array.isArray(newDataMasking.includedPaths)) {
|
||||
validationErrors.push("includedPaths must be an array");
|
||||
validationErrors.push(t(Keys.controls.settings.dataMasking.includedPathsMustBeArray));
|
||||
}
|
||||
if (newDataMasking.excludedPaths !== undefined && !Array.isArray(newDataMasking.excludedPaths)) {
|
||||
validationErrors.push("excludedPaths must be an array if provided");
|
||||
validationErrors.push(t(Keys.controls.settings.dataMasking.excludedPathsMustBeArray));
|
||||
}
|
||||
|
||||
this.setState({
|
||||
@@ -896,7 +898,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
const buttons: CommandButtonComponentProps[] = [];
|
||||
const isExecuting = this.props.settingsTab.isExecuting();
|
||||
if (this.saveSettingsButton.isVisible()) {
|
||||
const label = "Save";
|
||||
const label = t(Keys.common.save);
|
||||
buttons.push({
|
||||
iconSrc: SaveIcon,
|
||||
iconAlt: label,
|
||||
@@ -909,7 +911,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
}
|
||||
|
||||
if (this.discardSettingsChangesButton.isVisible()) {
|
||||
const label = "Discard";
|
||||
const label = t(Keys.common.discard);
|
||||
buttons.push({
|
||||
iconSrc: DiscardIcon,
|
||||
iconAlt: label,
|
||||
@@ -934,9 +936,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1;
|
||||
const throughputDelta = (newThroughput - this.offer.autoscaleMaxThroughput) * numberOfRegions;
|
||||
if (throughputCap && throughputCap !== -1 && throughputCap - this.totalThroughputUsed < throughputDelta) {
|
||||
throughputError = `Your account is currently configured with a total throughput limit of ${throughputCap} RU/s. This update isn't possible because it would increase the total throughput to ${
|
||||
this.totalThroughputUsed + throughputDelta
|
||||
} RU/s. Change total throughput limit in cost management.`;
|
||||
throughputError = t(Keys.controls.settings.throughput.throughputCapError, {
|
||||
throughputCap: String(throughputCap),
|
||||
newTotalThroughput: String(this.totalThroughputUsed + throughputDelta),
|
||||
});
|
||||
}
|
||||
this.setState({ autoPilotThroughput: newThroughput, throughputError });
|
||||
};
|
||||
@@ -947,9 +950,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1;
|
||||
const throughputDelta = (newThroughput - this.offer.manualThroughput) * numberOfRegions;
|
||||
if (throughputCap && throughputCap !== -1 && throughputCap - this.totalThroughputUsed < throughputDelta) {
|
||||
throughputError = `Your account is currently configured with a total throughput limit of ${throughputCap} RU/s. This update isn't possible because it would increase the total throughput to ${
|
||||
this.totalThroughputUsed + throughputDelta
|
||||
} RU/s. Change total throughput limit in cost management.`;
|
||||
throughputError = t(Keys.controls.settings.throughput.throughputCapError, {
|
||||
throughputCap: String(throughputCap),
|
||||
newTotalThroughput: String(this.totalThroughputUsed + throughputDelta),
|
||||
});
|
||||
}
|
||||
this.setState({ throughput: newThroughput, throughputError });
|
||||
};
|
||||
@@ -1560,7 +1564,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
}
|
||||
>
|
||||
{this.shouldShowKeyspaceSharedThroughputMessage() && (
|
||||
<div>This table shared throughput is configured at the keyspace</div>
|
||||
<div>{t(Keys.controls.settings.scale.keyspaceSharedThroughput)}</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
|
||||
@@ -25,6 +25,8 @@ import {
|
||||
import * as React from "react";
|
||||
import { Urls } from "../../../Common/Constants";
|
||||
import { StyleConstants } from "../../../Common/StyleConstants";
|
||||
import { Keys } from "../../../Localization/Keys.generated";
|
||||
import { t } from "../../../Localization/t";
|
||||
import { hoursInAMonth } from "../../../Shared/Constants";
|
||||
import {
|
||||
computeRUUsagePriceHourly,
|
||||
@@ -338,10 +340,12 @@ export const getEstimatedSpendingElement = (
|
||||
const ruRange: string = isAutoscale ? throughput / 10 + " RU/s - " : "";
|
||||
return (
|
||||
<Stack>
|
||||
<Text style={{ fontWeight: 600, color: "var(--colorNeutralForeground1)" }}>Cost estimate*</Text>
|
||||
<Text style={{ fontWeight: 600, color: "var(--colorNeutralForeground1)" }}>
|
||||
{t(Keys.controls.settings.costEstimate.title)}
|
||||
</Text>
|
||||
{costElement}
|
||||
<Text style={{ fontWeight: 600, marginTop: 15, color: "var(--colorNeutralForeground1)" }}>
|
||||
How we calculate this
|
||||
{t(Keys.controls.settings.costEstimate.howWeCalculate)}
|
||||
</Text>
|
||||
<Stack id="throughputSpendElement" style={{ marginTop: 5 }}>
|
||||
<span>
|
||||
@@ -353,7 +357,8 @@ export const getEstimatedSpendingElement = (
|
||||
</span>
|
||||
<span>
|
||||
{priceBreakdown.currencySign}
|
||||
{priceBreakdown.pricePerRu}/RU
|
||||
{priceBreakdown.pricePerRu}
|
||||
{t(Keys.controls.settings.costEstimate.perRu)}
|
||||
</span>
|
||||
</Stack>
|
||||
<Text style={{ marginTop: 15, color: "var(--colorNeutralForeground1)" }}>
|
||||
@@ -365,18 +370,16 @@ export const getEstimatedSpendingElement = (
|
||||
|
||||
export const manualToAutoscaleDisclaimerElement: JSX.Element = (
|
||||
<Text styles={infoAndToolTipTextStyle} id="manualToAutoscaleDisclaimerElement">
|
||||
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings
|
||||
and storage of your resource. After autoscale has been enabled, you can change the max RU/s.{" "}
|
||||
{t(Keys.controls.settings.throughput.manualToAutoscaleDisclaimer)}{" "}
|
||||
<Link href={Urls.autoscaleMigration}>Learn more</Link>
|
||||
</Text>
|
||||
);
|
||||
|
||||
export const ttlWarning: JSX.Element = (
|
||||
<Text styles={infoAndToolTipTextStyle}>
|
||||
The system will automatically delete items based on the TTL value (in seconds) you provide, without needing a delete
|
||||
operation explicitly issued by a client application. For more information see,{" "}
|
||||
{t(Keys.controls.settings.throughput.ttlWarningText)}{" "}
|
||||
<Link target="_blank" href="https://aka.ms/cosmos-db-ttl">
|
||||
Time to Live (TTL) in Azure Cosmos DB
|
||||
{t(Keys.controls.settings.throughput.ttlWarningLinkText)}
|
||||
</Link>
|
||||
.
|
||||
</Text>
|
||||
@@ -384,29 +387,28 @@ export const ttlWarning: JSX.Element = (
|
||||
|
||||
export const unsavedEditorWarningMessage = (editor: editorType): JSX.Element => (
|
||||
<Text styles={infoAndToolTipTextStyle}>
|
||||
You have not saved the latest changes made to your{" "}
|
||||
{t(Keys.controls.settings.throughput.unsavedEditorWarningPrefix)}{" "}
|
||||
{editor === "indexPolicy"
|
||||
? "indexing policy"
|
||||
? t(Keys.controls.settings.throughput.unsavedIndexingPolicy)
|
||||
: editor === "dataMasking"
|
||||
? "data masking policy"
|
||||
: "computed properties"}
|
||||
. Please click save to confirm the changes.
|
||||
? t(Keys.controls.settings.throughput.unsavedDataMaskingPolicy)
|
||||
: t(Keys.controls.settings.throughput.unsavedComputedProperties)}
|
||||
{t(Keys.controls.settings.throughput.unsavedEditorWarningSuffix)}
|
||||
</Text>
|
||||
);
|
||||
|
||||
export const updateThroughputDelayedApplyWarningMessage: JSX.Element = (
|
||||
<Text styles={infoAndToolTipTextStyle} id="updateThroughputDelayedApplyWarningMessage">
|
||||
You are about to request an increase in throughput beyond the pre-allocated capacity. This operation will take some
|
||||
time to complete.
|
||||
{t(Keys.controls.settings.throughput.updateDelayedApplyWarning)}
|
||||
</Text>
|
||||
);
|
||||
|
||||
export const getUpdateThroughputBeyondInstantLimitMessage = (instantMaximumThroughput: number): JSX.Element => {
|
||||
return (
|
||||
<Text id="updateThroughputDelayedApplyWarningMessage">
|
||||
Scaling up will take 4-6 hours as it exceeds what Azure Cosmos DB can instantly support currently based on your
|
||||
number of physical partitions. You can increase your throughput to {instantMaximumThroughput} instantly or proceed
|
||||
with this value and wait until the scale-up is completed.
|
||||
{t(Keys.controls.settings.throughput.scalingUpDelayMessage, {
|
||||
instantMaximumThroughput: String(instantMaximumThroughput),
|
||||
})}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
@@ -418,22 +420,26 @@ export const getUpdateThroughputBeyondSupportLimitMessage = (
|
||||
return (
|
||||
<>
|
||||
<Text styles={infoAndToolTipTextStyle} id="updateThroughputDelayedApplyWarningMessage">
|
||||
Your request to increase throughput exceeds the pre-allocated capacity which may take longer than expected.
|
||||
There are three options you can choose from to proceed:
|
||||
{t(Keys.controls.settings.throughput.exceedPreAllocatedMessage)}
|
||||
</Text>
|
||||
<ol style={{ fontSize: 14, color: "var(--colorNeutralForeground1)", marginTop: "5px" }}>
|
||||
<li>You can instantly scale up to {instantMaximumThroughput} RU/s.</li>
|
||||
<li>
|
||||
{t(Keys.controls.settings.throughput.instantScaleOption, {
|
||||
instantMaximumThroughput: String(instantMaximumThroughput),
|
||||
})}
|
||||
</li>
|
||||
{instantMaximumThroughput < maximumThroughput && (
|
||||
<li>You can asynchronously scale up to any value under {maximumThroughput} RU/s in 4-6 hours.</li>
|
||||
<li>
|
||||
{t(Keys.controls.settings.throughput.asyncScaleOption, { maximumThroughput: String(maximumThroughput) })}
|
||||
</li>
|
||||
)}
|
||||
<li>
|
||||
Your current quota max is {maximumThroughput} RU/s. To go over this limit, you must request a quota increase
|
||||
and the Azure Cosmos DB team will review.
|
||||
{t(Keys.controls.settings.throughput.quotaMaxOption, { maximumThroughput: String(maximumThroughput) })}
|
||||
<Link
|
||||
href="https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/create-support-request-quota-increase"
|
||||
target="_blank"
|
||||
>
|
||||
Learn more
|
||||
{t(Keys.common.learnMore)}
|
||||
</Link>
|
||||
</li>
|
||||
</ol>
|
||||
@@ -444,23 +450,19 @@ export const getUpdateThroughputBeyondSupportLimitMessage = (
|
||||
export const getUpdateThroughputBelowMinimumMessage = (minimum: number): JSX.Element => {
|
||||
return (
|
||||
<Text styles={infoAndToolTipTextStyle}>
|
||||
You are not able to lower throughput below your current minimum of {minimum} RU/s. For more information on this
|
||||
limit, please refer to our service quote documentation.
|
||||
{t(Keys.controls.settings.throughput.belowMinimumMessage, { minimum: String(minimum) })}
|
||||
<Link
|
||||
href="https://learn.microsoft.com/en-us/azure/cosmos-db/concepts-limits#minimum-throughput-limits"
|
||||
target="_blank"
|
||||
>
|
||||
Learn more
|
||||
{t(Keys.common.learnMore)}
|
||||
</Link>
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
export const saveThroughputWarningMessage: JSX.Element = (
|
||||
<Text>
|
||||
Your bill will be affected as you update your throughput settings. Please review the updated cost estimate below
|
||||
before saving your changes
|
||||
</Text>
|
||||
<Text>{t(Keys.controls.settings.throughput.saveThroughputWarning)}</Text>
|
||||
);
|
||||
|
||||
const getCurrentThroughput = (
|
||||
@@ -472,23 +474,29 @@ const getCurrentThroughput = (
|
||||
if (targetThroughput) {
|
||||
if (throughput) {
|
||||
return isAutoscale
|
||||
? `, Current autoscale throughput: ${Math.round(
|
||||
? `, ${t(Keys.controls.settings.throughput.currentAutoscaleThroughput)} ${Math.round(
|
||||
throughput / 10,
|
||||
)} - ${throughput} ${throughputUnit}, Target autoscale throughput: ${Math.round(
|
||||
targetThroughput / 10,
|
||||
)} - ${targetThroughput} ${throughputUnit}`
|
||||
: `, Current manual throughput: ${throughput} ${throughputUnit}, Target manual throughput: ${targetThroughput}`;
|
||||
)} - ${throughput} ${throughputUnit}, ${t(
|
||||
Keys.controls.settings.throughput.targetAutoscaleThroughput,
|
||||
)} ${Math.round(targetThroughput / 10)} - ${targetThroughput} ${throughputUnit}`
|
||||
: `, ${t(Keys.controls.settings.throughput.currentManualThroughput)} ${throughput} ${throughputUnit}, ${t(
|
||||
Keys.controls.settings.throughput.targetManualThroughput,
|
||||
)} ${targetThroughput}`;
|
||||
} else {
|
||||
return isAutoscale
|
||||
? `, Target autoscale throughput: ${Math.round(targetThroughput / 10)} - ${targetThroughput} ${throughputUnit}`
|
||||
: `, Target manual throughput: ${targetThroughput} ${throughputUnit}`;
|
||||
? `, ${t(Keys.controls.settings.throughput.targetAutoscaleThroughput)} ${Math.round(
|
||||
targetThroughput / 10,
|
||||
)} - ${targetThroughput} ${throughputUnit}`
|
||||
: `, ${t(Keys.controls.settings.throughput.targetManualThroughput)} ${targetThroughput} ${throughputUnit}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetThroughput && throughput) {
|
||||
return isAutoscale
|
||||
? `, Current autoscale throughput: ${Math.round(throughput / 10)} - ${throughput} ${throughputUnit}`
|
||||
: `, Current manual throughput: ${throughput} ${throughputUnit}`;
|
||||
? `, ${t(Keys.controls.settings.throughput.currentAutoscaleThroughput)} ${Math.round(
|
||||
throughput / 10,
|
||||
)} - ${throughput} ${throughputUnit}`
|
||||
: `, ${t(Keys.controls.settings.throughput.currentManualThroughput)} ${throughput} ${throughputUnit}`;
|
||||
}
|
||||
|
||||
return "";
|
||||
@@ -503,10 +511,10 @@ export const getThroughputApplyDelayedMessage = (
|
||||
requestedThroughput: number,
|
||||
): JSX.Element => (
|
||||
<Text styles={infoAndToolTipTextStyle}>
|
||||
The request to increase the throughput has successfully been submitted. This operation will take 1-3 business days
|
||||
to complete. View the latest status in Notifications.
|
||||
{t(Keys.controls.settings.throughput.applyDelayedMessage)}
|
||||
<br />
|
||||
Database: {databaseName}, Container: {collectionName}{" "}
|
||||
{t(Keys.controls.settings.throughput.databaseLabel)} {databaseName},{" "}
|
||||
{t(Keys.controls.settings.throughput.containerLabel)} {collectionName}{" "}
|
||||
{getCurrentThroughput(isAutoscale, throughput, throughputUnit, requestedThroughput)}
|
||||
</Text>
|
||||
);
|
||||
@@ -519,9 +527,13 @@ export const getThroughputApplyShortDelayMessage = (
|
||||
collectionName: string,
|
||||
): JSX.Element => (
|
||||
<Text styles={infoAndToolTipTextStyle} id="throughputApplyShortDelayMessage">
|
||||
A request to increase the throughput is currently in progress. This operation will take some time to complete.
|
||||
{t(Keys.controls.settings.throughput.applyShortDelayMessage)}
|
||||
<br />
|
||||
{collectionName ? `Database: ${databaseName}, Container: ${collectionName} ` : `Database: ${databaseName} `}
|
||||
{collectionName
|
||||
? `${t(Keys.controls.settings.throughput.databaseLabel)} ${databaseName}, ${t(
|
||||
Keys.controls.settings.throughput.containerLabel,
|
||||
)} ${collectionName} `
|
||||
: `${t(Keys.controls.settings.throughput.databaseLabel)} ${databaseName} `}
|
||||
{getCurrentThroughput(isAutoscale, throughput, throughputUnit)}
|
||||
</Text>
|
||||
);
|
||||
@@ -535,10 +547,13 @@ export const getThroughputApplyLongDelayMessage = (
|
||||
requestedThroughput: number,
|
||||
): JSX.Element => (
|
||||
<Text styles={infoAndToolTipTextStyle} id="throughputApplyLongDelayMessage">
|
||||
A request to increase the throughput is currently in progress. This operation will take 1-3 business days to
|
||||
complete. View the latest status in Notifications.
|
||||
{t(Keys.controls.settings.throughput.applyLongDelayMessage)}
|
||||
<br />
|
||||
{collectionName ? `Database: ${databaseName}, Container: ${collectionName} ` : `Database: ${databaseName} `}
|
||||
{collectionName
|
||||
? `${t(Keys.controls.settings.throughput.databaseLabel)} ${databaseName}, ${t(
|
||||
Keys.controls.settings.throughput.containerLabel,
|
||||
)} ${collectionName} `
|
||||
: `${t(Keys.controls.settings.throughput.databaseLabel)} ${databaseName} `}
|
||||
{getCurrentThroughput(isAutoscale, throughput, throughputUnit, requestedThroughput)}
|
||||
</Text>
|
||||
);
|
||||
@@ -547,63 +562,49 @@ export const getToolTipContainer = (content: string | JSX.Element): JSX.Element
|
||||
content ? <Text styles={infoAndToolTipTextStyle}>{content}</Text> : undefined;
|
||||
|
||||
export const conflictResolutionLwwTooltip: JSX.Element = (
|
||||
<Text styles={infoAndToolTipTextStyle}>
|
||||
Gets or sets the name of a integer property in your documents which is used for the Last Write Wins (LWW) based
|
||||
conflict resolution scheme. By default, the system uses the system defined timestamp property, _ts to decide the
|
||||
winner for the conflicting versions of the document. Specify your own integer property if you want to override the
|
||||
default timestamp based conflict resolution.
|
||||
</Text>
|
||||
<Text styles={infoAndToolTipTextStyle}>{t(Keys.controls.settings.conflictResolution.lwwTooltip)}</Text>
|
||||
);
|
||||
|
||||
export const conflictResolutionCustomToolTip: JSX.Element = (
|
||||
<Text styles={infoAndToolTipTextStyle}>
|
||||
Gets or sets the name of a stored procedure (aka merge procedure) for resolving the conflicts. You can write
|
||||
application defined logic to determine the winner of the conflicting versions of a document. The stored procedure
|
||||
will get executed transactionally, exactly once, on the server side. If you do not provide a stored procedure, the
|
||||
conflicts will be populated in the
|
||||
{t(Keys.controls.settings.conflictResolution.customTooltip)}
|
||||
<Link className="linkDarkBackground" href="https://aka.ms/dataexplorerconflics" target="_blank">
|
||||
{` conflicts feed`}
|
||||
{t(Keys.controls.settings.conflictResolution.customTooltipConflictsFeed)}
|
||||
</Link>
|
||||
. You can update/re-register the stored procedure at any time.
|
||||
{t(Keys.controls.settings.conflictResolution.customTooltipSuffix)}
|
||||
</Text>
|
||||
);
|
||||
|
||||
export const changeFeedPolicyToolTip: JSX.Element = (
|
||||
<Text styles={infoAndToolTipTextStyle}>
|
||||
Enable change feed log retention policy to retain last 10 minutes of history for items in the container by default.
|
||||
To support this, the request unit (RU) charge for this container will be multiplied by a factor of two for writes.
|
||||
Reads are unaffected.
|
||||
</Text>
|
||||
<Text styles={infoAndToolTipTextStyle}>{t(Keys.controls.settings.changeFeed.tooltip)}</Text>
|
||||
);
|
||||
|
||||
export const mongoIndexingPolicyDisclaimer: JSX.Element = (
|
||||
<Text style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||
For queries that filter on multiple properties, create multiple single field indexes instead of a compound index.
|
||||
{t(Keys.controls.settings.mongoIndexing.disclaimer)}
|
||||
<Link
|
||||
href="https://docs.microsoft.com/azure/cosmos-db/mongodb-indexing#index-types"
|
||||
target="_blank"
|
||||
style={{ color: "var(--colorBrandForeground1)" }}
|
||||
>
|
||||
{` Compound indexes `}
|
||||
{t(Keys.controls.settings.mongoIndexing.disclaimerCompoundIndexesLink)}
|
||||
</Link>
|
||||
are only used for sorting query results. If you need to add a compound index, you can create one using the Mongo
|
||||
shell.
|
||||
{t(Keys.controls.settings.mongoIndexing.disclaimerSuffix)}
|
||||
</Text>
|
||||
);
|
||||
|
||||
export const mongoCompoundIndexNotSupportedMessage: JSX.Element = (
|
||||
<Text style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||
Collections with compound indexes are not yet supported in the indexing editor. To modify indexing policy for this
|
||||
collection, use the Mongo Shell.
|
||||
{t(Keys.controls.settings.mongoIndexing.compoundNotSupported)}
|
||||
</Text>
|
||||
);
|
||||
|
||||
export const mongoIndexingPolicyAADError: JSX.Element = (
|
||||
<MessageBar messageBarType={MessageBarType.error}>
|
||||
<Text>
|
||||
To use the indexing policy editor, please login to the
|
||||
{t(Keys.controls.settings.mongoIndexing.aadError)}
|
||||
<Link target="_blank" href="https://portal.azure.com">
|
||||
{"azure portal."}
|
||||
{t(Keys.controls.settings.mongoIndexing.aadErrorLink)}
|
||||
</Link>
|
||||
</Text>
|
||||
</MessageBar>
|
||||
@@ -611,7 +612,7 @@ export const mongoIndexingPolicyAADError: JSX.Element = (
|
||||
|
||||
export const mongoIndexTransformationRefreshingMessage: JSX.Element = (
|
||||
<Stack horizontal {...mongoWarningStackProps}>
|
||||
<Text styles={infoAndToolTipTextStyle}>Refreshing index transformation progress</Text>
|
||||
<Text styles={infoAndToolTipTextStyle}>{t(Keys.controls.settings.mongoIndexing.refreshingProgress)}</Text>
|
||||
<Spinner size={SpinnerSize.small} />
|
||||
</Stack>
|
||||
);
|
||||
@@ -623,15 +624,18 @@ export const renderMongoIndexTransformationRefreshMessage = (
|
||||
if (progress === 0) {
|
||||
return (
|
||||
<Text styles={infoAndToolTipTextStyle}>
|
||||
{"You can make more indexing changes once the current index transformation is complete. "}
|
||||
<Link onClick={performRefresh}>{"Refresh to check if it has completed."}</Link>
|
||||
{t(Keys.controls.settings.mongoIndexing.canMakeMoreChangesZero)}
|
||||
<Link onClick={performRefresh}>{t(Keys.controls.settings.mongoIndexing.refreshToCheck)}</Link>
|
||||
</Text>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Text styles={infoAndToolTipTextStyle}>
|
||||
{`You can make more indexing changes once the current index transformation has completed. It is ${progress}% complete. `}
|
||||
<Link onClick={performRefresh}>{"Refresh to check the progress."}</Link>
|
||||
{`${t(Keys.controls.settings.mongoIndexing.canMakeMoreChangesProgress).replace(
|
||||
"{{progress}}",
|
||||
String(progress),
|
||||
)} `}
|
||||
<Link onClick={performRefresh}>{t(Keys.controls.settings.mongoIndexing.refreshToCheckProgress)}</Link>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { titleAndInputStackProps, unsavedEditorWarningMessage } from "Explorer/C
|
||||
import { isDirty } from "Explorer/Controls/Settings/SettingsUtils";
|
||||
import { loadMonaco } from "Explorer/LazyMonaco";
|
||||
import { monacoTheme, useThemeStore } from "hooks/useTheme";
|
||||
import { Keys } from "../../../../Localization/Keys.generated";
|
||||
import { t } from "../../../../Localization/t";
|
||||
import * as monaco from "monaco-editor";
|
||||
import * as React from "react";
|
||||
export interface ComputedPropertiesComponentProps {
|
||||
@@ -107,7 +109,7 @@ export class ComputedPropertiesComponent extends React.Component<
|
||||
this.computedPropertiesEditor = monaco.editor.create(this.computedPropertiesDiv.current, {
|
||||
value: value,
|
||||
language: "json",
|
||||
ariaLabel: "Computed properties",
|
||||
ariaLabel: t(Keys.controls.settings.computedProperties.ariaLabel),
|
||||
theme: monacoTheme(),
|
||||
});
|
||||
if (this.computedPropertiesEditor) {
|
||||
@@ -151,9 +153,9 @@ export class ComputedPropertiesComponent extends React.Component<
|
||||
)}
|
||||
<Text style={{ marginLeft: "30px", marginBottom: "10px", color: "var(--colorNeutralForeground1)" }}>
|
||||
<Link target="_blank" href="https://aka.ms/computed-properties-preview/">
|
||||
{"Learn more"} <FontIcon iconName="NavigateExternalInline" />
|
||||
{t(Keys.common.learnMore)} <FontIcon iconName="NavigateExternalInline" />
|
||||
</Link>
|
||||
  about how to define computed properties and how to use them.
|
||||
  {t(Keys.controls.settings.computedProperties.learnMorePrefix)}
|
||||
</Text>
|
||||
<div
|
||||
className="settingsV2Editor"
|
||||
|
||||
@@ -2,6 +2,8 @@ import { ChoiceGroup, IChoiceGroupOption, ITextFieldProps, Stack, TextField } fr
|
||||
import * as React from "react";
|
||||
import * as DataModels from "../../../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../../../Contracts/ViewModels";
|
||||
import { Keys } from "../../../../Localization/Keys.generated";
|
||||
import { t } from "../../../../Localization/t";
|
||||
import {
|
||||
conflictResolutionCustomToolTip,
|
||||
conflictResolutionLwwTooltip,
|
||||
@@ -32,9 +34,12 @@ export class ConflictResolutionComponent extends React.Component<ConflictResolut
|
||||
private conflictResolutionChoiceGroupOptions: IChoiceGroupOption[] = [
|
||||
{
|
||||
key: DataModels.ConflictResolutionMode.LastWriterWins,
|
||||
text: "Last Write Wins (default)",
|
||||
text: t(Keys.controls.settings.conflictResolution.lwwDefault),
|
||||
},
|
||||
{
|
||||
key: DataModels.ConflictResolutionMode.Custom,
|
||||
text: t(Keys.controls.settings.conflictResolution.customMergeProcedure),
|
||||
},
|
||||
{ key: DataModels.ConflictResolutionMode.Custom, text: "Merge Procedure (custom)" },
|
||||
];
|
||||
|
||||
componentDidMount(): void {
|
||||
@@ -85,7 +90,7 @@ export class ConflictResolutionComponent extends React.Component<ConflictResolut
|
||||
|
||||
private getConflictResolutionModeComponent = (): JSX.Element => (
|
||||
<ChoiceGroup
|
||||
label="Mode"
|
||||
label={t(Keys.controls.settings.conflictResolution.mode)}
|
||||
selectedKey={this.props.conflictResolutionPolicyMode}
|
||||
options={this.conflictResolutionChoiceGroupOptions}
|
||||
onChange={this.onConflictResolutionPolicyModeChange}
|
||||
@@ -103,7 +108,7 @@ export class ConflictResolutionComponent extends React.Component<ConflictResolut
|
||||
private getConflictResolutionLWWComponent = (): JSX.Element => (
|
||||
<TextField
|
||||
id="conflictResolutionLwwTextField"
|
||||
label={"Conflict Resolver Property"}
|
||||
label={t(Keys.controls.settings.conflictResolution.conflictResolverProperty)}
|
||||
onRenderLabel={this.onRenderLwwComponentTextField}
|
||||
styles={{
|
||||
fieldGroup: {
|
||||
@@ -158,7 +163,7 @@ export class ConflictResolutionComponent extends React.Component<ConflictResolut
|
||||
return (
|
||||
<TextField
|
||||
id="conflictResolutionCustomTextField"
|
||||
label="Stored procedure"
|
||||
label={t(Keys.controls.settings.conflictResolution.storedProcedure)}
|
||||
onRenderLabel={this.onRenderCustomComponentTextField}
|
||||
styles={{
|
||||
fieldGroup: {
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
import { titleAndInputStackProps } from "Explorer/Controls/Settings/SettingsRenderUtils";
|
||||
import { ContainerPolicyTabTypes, isDirty } from "Explorer/Controls/Settings/SettingsUtils";
|
||||
import { VectorEmbeddingPoliciesComponent } from "Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent";
|
||||
import { Keys } from "../../../../Localization/Keys.generated";
|
||||
import { t } from "../../../../Localization/t";
|
||||
import React from "react";
|
||||
|
||||
export interface ContainerPolicyComponentProps {
|
||||
@@ -153,7 +155,7 @@ export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> =
|
||||
<PivotItem
|
||||
itemKey={ContainerPolicyTabTypes[ContainerPolicyTabTypes.VectorPolicyTab]}
|
||||
style={{ marginTop: 20, color: "var(--colorNeutralForeground1)" }}
|
||||
headerText="Vector Policy"
|
||||
headerText={t(Keys.controls.settings.containerPolicy.vectorPolicy)}
|
||||
>
|
||||
<Stack {...titleAndInputStackProps} styles={{ root: { position: "relative", maxWidth: "400px" } }}>
|
||||
{vectorEmbeddings && (
|
||||
@@ -175,7 +177,7 @@ export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> =
|
||||
<PivotItem
|
||||
itemKey={ContainerPolicyTabTypes[ContainerPolicyTabTypes.FullTextPolicyTab]}
|
||||
style={{ marginTop: 20, color: "var(--colorNeutralForeground1)" }}
|
||||
headerText="Full Text Policy"
|
||||
headerText={t(Keys.controls.settings.containerPolicy.fullTextPolicy)}
|
||||
>
|
||||
<Stack {...titleAndInputStackProps} styles={{ root: { position: "relative", maxWidth: "400px" } }}>
|
||||
{fullTextSearchPolicy ? (
|
||||
@@ -218,7 +220,7 @@ export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> =
|
||||
});
|
||||
}}
|
||||
>
|
||||
Create new full text search policy
|
||||
{t(Keys.controls.settings.containerPolicy.createFullTextPolicy)}
|
||||
</DefaultButton>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -2,6 +2,8 @@ import { MessageBar, MessageBarType, Stack } from "@fluentui/react";
|
||||
import * as monaco from "monaco-editor";
|
||||
import * as React from "react";
|
||||
import * as DataModels from "../../../../Contracts/DataModels";
|
||||
import { Keys } from "../../../../Localization/Keys.generated";
|
||||
import { t } from "../../../../Localization/t";
|
||||
import { loadMonaco } from "../../../LazyMonaco";
|
||||
import { titleAndInputStackProps, unsavedEditorWarningMessage } from "../SettingsRenderUtils";
|
||||
import { isDirty as isContentDirty, isDataMaskingEnabled } from "../SettingsUtils";
|
||||
@@ -89,7 +91,7 @@ export class DataMaskingComponent extends React.Component<DataMaskingComponentPr
|
||||
value: value,
|
||||
language: "json",
|
||||
automaticLayout: true,
|
||||
ariaLabel: "Data Masking Policy",
|
||||
ariaLabel: t(Keys.controls.settings.dataMasking.ariaLabel),
|
||||
fontSize: 13,
|
||||
minimap: { enabled: false },
|
||||
wordWrap: "off",
|
||||
@@ -142,7 +144,7 @@ export class DataMaskingComponent extends React.Component<DataMaskingComponentPr
|
||||
)}
|
||||
{this.props.validationErrors.length > 0 && (
|
||||
<MessageBar messageBarType={MessageBarType.error}>
|
||||
Validation failed: {this.props.validationErrors.join(", ")}
|
||||
{t(Keys.controls.settings.dataMasking.validationFailed)} {this.props.validationErrors.join(", ")}
|
||||
</MessageBar>
|
||||
)}
|
||||
<div className="settingsV2Editor" tabIndex={0} ref={this.dataMaskingDiv}></div>
|
||||
|
||||
@@ -2,6 +2,8 @@ import { FontIcon, Link, Stack, Text } from "@fluentui/react";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import React from "react";
|
||||
import * as ViewModels from "../../../../Contracts/ViewModels";
|
||||
import { Keys } from "../../../../Localization/Keys.generated";
|
||||
import { t } from "../../../../Localization/t";
|
||||
import { GlobalSecondaryIndexSourceComponent } from "./GlobalSecondaryIndexSourceComponent";
|
||||
import { GlobalSecondaryIndexTargetComponent } from "./GlobalSecondaryIndexTargetComponent";
|
||||
|
||||
@@ -21,7 +23,9 @@ export const GlobalSecondaryIndexComponent: React.FC<GlobalSecondaryIndexCompone
|
||||
<Stack tokens={{ childrenGap: 8 }} styles={{ root: { maxWidth: 600 } }}>
|
||||
<Stack horizontal verticalAlign="center" wrap tokens={{ childrenGap: 8 }}>
|
||||
{isSourceContainer && (
|
||||
<Text styles={{ root: { fontWeight: 600 } }}>This container has the following indexes defined for it.</Text>
|
||||
<Text styles={{ root: { fontWeight: 600 } }}>
|
||||
{t(Keys.controls.settings.globalSecondaryIndex.indexesDefined)}
|
||||
</Text>
|
||||
)}
|
||||
<Text>
|
||||
<Link
|
||||
@@ -31,7 +35,7 @@ export const GlobalSecondaryIndexComponent: React.FC<GlobalSecondaryIndexCompone
|
||||
Learn more
|
||||
<FontIcon iconName="NavigateExternalInline" style={{ marginLeft: "4px" }} />
|
||||
</Link>{" "}
|
||||
about how to define global secondary indexes and how to use them.
|
||||
{t(Keys.controls.settings.globalSecondaryIndex.learnMoreSuffix)}
|
||||
</Text>
|
||||
</Stack>
|
||||
{isSourceContainer && <GlobalSecondaryIndexSourceComponent collection={collection} explorer={explorer} />}
|
||||
|
||||
@@ -9,6 +9,8 @@ import { useSidePanel } from "hooks/useSidePanel";
|
||||
import * as monaco from "monaco-editor";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import * as ViewModels from "../../../../Contracts/ViewModels";
|
||||
import { Keys } from "../../../../Localization/Keys.generated";
|
||||
import { t } from "../../../../Localization/t";
|
||||
|
||||
export interface GlobalSecondaryIndexSourceComponentProps {
|
||||
collection: ViewModels.Collection;
|
||||
@@ -67,7 +69,7 @@ export const GlobalSecondaryIndexSourceComponent: React.FC<GlobalSecondaryIndexS
|
||||
editorRef.current = monacoInstance.editor.create(editorContainerRef.current, {
|
||||
value: jsonValue,
|
||||
language: "json",
|
||||
ariaLabel: "Global Secondary Index JSON",
|
||||
ariaLabel: t(Keys.controls.settings.globalSecondaryIndex.jsonAriaLabel),
|
||||
readOnly: true,
|
||||
});
|
||||
};
|
||||
@@ -98,7 +100,7 @@ export const GlobalSecondaryIndexSourceComponent: React.FC<GlobalSecondaryIndexS
|
||||
}}
|
||||
/>
|
||||
<PrimaryButton
|
||||
text="Add index"
|
||||
text={t(Keys.controls.settings.globalSecondaryIndex.addIndex)}
|
||||
styles={{ root: { width: "fit-content", marginTop: 12 } }}
|
||||
onClick={() =>
|
||||
useSidePanel
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Stack, Text } from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import * as ViewModels from "../../../../Contracts/ViewModels";
|
||||
import { Keys } from "../../../../Localization/Keys.generated";
|
||||
import { t } from "../../../../Localization/t";
|
||||
|
||||
export interface GlobalSecondaryIndexTargetComponentProps {
|
||||
collection: ViewModels.Collection;
|
||||
@@ -25,17 +27,21 @@ export const GlobalSecondaryIndexTargetComponent: React.FC<GlobalSecondaryIndexT
|
||||
|
||||
return (
|
||||
<Stack tokens={{ childrenGap: 15 }} styles={{ root: { maxWidth: 600 } }}>
|
||||
<Text styles={textHeadingStyle}>Global Secondary Index Settings</Text>
|
||||
<Text styles={textHeadingStyle}>{t(Keys.controls.settings.globalSecondaryIndex.settingsTitle)}</Text>
|
||||
|
||||
<Stack tokens={{ childrenGap: 5 }}>
|
||||
<Text styles={{ root: { fontWeight: "600" } }}>Source container</Text>
|
||||
<Text styles={{ root: { fontWeight: "600" } }}>
|
||||
{t(Keys.controls.settings.globalSecondaryIndex.sourceContainer)}
|
||||
</Text>
|
||||
<Stack styles={valueBoxStyle}>
|
||||
<Text>{globalSecondaryIndexDefinition?.sourceCollectionId}</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Stack tokens={{ childrenGap: 5 }}>
|
||||
<Text styles={{ root: { fontWeight: "600" } }}>Global secondary index definition</Text>
|
||||
<Text styles={{ root: { fontWeight: "600" } }}>
|
||||
{t(Keys.controls.settings.globalSecondaryIndex.indexDefinition)}
|
||||
</Text>
|
||||
<Stack styles={valueBoxStyle}>
|
||||
<Text>{globalSecondaryIndexDefinition?.definition}</Text>
|
||||
</Stack>
|
||||
|
||||
@@ -3,6 +3,8 @@ import { monacoTheme, useThemeStore } from "hooks/useTheme";
|
||||
import * as monaco from "monaco-editor";
|
||||
import * as React from "react";
|
||||
import * as DataModels from "../../../../Contracts/DataModels";
|
||||
import { Keys } from "../../../../Localization/Keys.generated";
|
||||
import { t } from "../../../../Localization/t";
|
||||
import { loadMonaco } from "../../../LazyMonaco";
|
||||
import { titleAndInputStackProps, unsavedEditorWarningMessage } from "../SettingsRenderUtils";
|
||||
import { isDirty, isIndexTransforming } from "../SettingsUtils";
|
||||
@@ -119,7 +121,7 @@ export class IndexingPolicyComponent extends React.Component<
|
||||
value: value,
|
||||
language: "json",
|
||||
readOnly: isIndexTransforming(this.props.indexTransformationProgress),
|
||||
ariaLabel: "Indexing Policy",
|
||||
ariaLabel: t(Keys.controls.settings.indexingPolicy.ariaLabel),
|
||||
theme: monacoTheme(),
|
||||
});
|
||||
if (this.indexingPolicyEditor) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { MessageBar, MessageBarType } from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import { handleError } from "../../../../../Common/ErrorHandlingUtils";
|
||||
import { Keys } from "../../../../../Localization/Keys.generated";
|
||||
import { t } from "../../../../../Localization/t";
|
||||
import {
|
||||
mongoIndexTransformationRefreshingMessage,
|
||||
renderMongoIndexTransformationRefreshMessage,
|
||||
@@ -46,7 +48,11 @@ export class IndexingPolicyRefreshComponent extends React.Component<
|
||||
try {
|
||||
await this.props.refreshIndexTransformationProgress();
|
||||
} catch (error) {
|
||||
handleError(error, "RefreshIndexTransformationProgress", "Refreshing index transformation progress failed");
|
||||
handleError(
|
||||
error,
|
||||
"RefreshIndexTransformationProgress",
|
||||
t(Keys.controls.settings.indexingPolicyRefresh.refreshFailed),
|
||||
);
|
||||
} finally {
|
||||
this.setState({ isRefreshing: false });
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ exports[`IndexingPolicyRefreshComponent renders 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
You can make more indexing changes once the current index transformation has completed. It is 90% complete.
|
||||
You can make more indexing changes once the current index transformation has completed. It is 90% complete.
|
||||
<StyledLinkBase
|
||||
onClick={[Function]}
|
||||
>
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
IDropdownOption,
|
||||
ITextField,
|
||||
} from "@fluentui/react";
|
||||
import { Keys } from "../../../../../Localization/Keys.generated";
|
||||
import { t } from "../../../../../Localization/t";
|
||||
import {
|
||||
addMongoIndexSubElementsTokens,
|
||||
mongoErrorMessageStyles,
|
||||
@@ -66,7 +68,7 @@ export class AddMongoIndexComponent extends React.Component<AddMongoIndexCompone
|
||||
<Stack {...mongoWarningStackProps}>
|
||||
<Stack horizontal tokens={addMongoIndexSubElementsTokens}>
|
||||
<TextField
|
||||
ariaLabel={"Index Field Name " + this.props.position}
|
||||
ariaLabel={t(Keys.controls.settings.mongoIndexing.indexFieldName) + " " + this.props.position}
|
||||
disabled={this.props.disabled}
|
||||
styles={shortWidthTextFieldStyles}
|
||||
componentRef={this.setRef}
|
||||
@@ -76,17 +78,17 @@ export class AddMongoIndexComponent extends React.Component<AddMongoIndexCompone
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
ariaLabel={"Index Type " + this.props.position}
|
||||
ariaLabel={t(Keys.controls.settings.mongoIndexing.indexType) + " " + this.props.position}
|
||||
disabled={this.props.disabled}
|
||||
styles={shortWidthDropDownStyles}
|
||||
placeholder="Select an index type"
|
||||
placeholder={t(Keys.controls.settings.mongoIndexing.selectIndexType)}
|
||||
selectedKey={this.props.type}
|
||||
options={this.indexTypes}
|
||||
onChange={this.onTypeChange}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
ariaLabel={"Undo Button " + this.props.position}
|
||||
ariaLabel={t(Keys.controls.settings.mongoIndexing.undoButton) + " " + this.props.position}
|
||||
iconProps={{ iconName: "Undo" }}
|
||||
disabled={!this.props.description && !this.props.type}
|
||||
onClick={() => this.props.onDiscard()}
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
} from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import { MongoIndex } from "../../../../../Utils/arm/generatedClients/cosmos/types";
|
||||
import { Keys } from "../../../../../Localization/Keys.generated";
|
||||
import { t } from "../../../../../Localization/t";
|
||||
import { CollapsibleSectionComponent } from "../../../CollapsiblePanel/CollapsibleSectionComponent";
|
||||
import {
|
||||
addMongoIndexStackProps,
|
||||
@@ -83,11 +85,25 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
||||
};
|
||||
|
||||
private initialIndexesColumns: IColumn[] = [
|
||||
{ key: "definition", name: "Definition", fieldName: "definition", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||
{ key: "type", name: "Type", fieldName: "type", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||
{
|
||||
key: "definition",
|
||||
name: t(Keys.controls.settings.mongoIndexing.definitionColumn),
|
||||
fieldName: "definition",
|
||||
minWidth: 100,
|
||||
maxWidth: 200,
|
||||
isResizable: true,
|
||||
},
|
||||
{
|
||||
key: "type",
|
||||
name: t(Keys.controls.settings.mongoIndexing.typeColumn),
|
||||
fieldName: "type",
|
||||
minWidth: 100,
|
||||
maxWidth: 200,
|
||||
isResizable: true,
|
||||
},
|
||||
{
|
||||
key: "actionButton",
|
||||
name: "Drop Index",
|
||||
name: t(Keys.controls.settings.mongoIndexing.dropIndexColumn),
|
||||
fieldName: "actionButton",
|
||||
minWidth: 100,
|
||||
maxWidth: 200,
|
||||
@@ -96,11 +112,25 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
||||
];
|
||||
|
||||
private indexesToBeDroppedColumns: IColumn[] = [
|
||||
{ key: "definition", name: "Definition", fieldName: "definition", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||
{ key: "type", name: "Type", fieldName: "type", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||
{
|
||||
key: "definition",
|
||||
name: t(Keys.controls.settings.mongoIndexing.definitionColumn),
|
||||
fieldName: "definition",
|
||||
minWidth: 100,
|
||||
maxWidth: 200,
|
||||
isResizable: true,
|
||||
},
|
||||
{
|
||||
key: "type",
|
||||
name: t(Keys.controls.settings.mongoIndexing.typeColumn),
|
||||
fieldName: "type",
|
||||
minWidth: 100,
|
||||
maxWidth: 200,
|
||||
isResizable: true,
|
||||
},
|
||||
{
|
||||
key: "actionButton",
|
||||
name: "Add index back",
|
||||
name: t(Keys.controls.settings.mongoIndexing.addIndexBackColumn),
|
||||
fieldName: "actionButton",
|
||||
minWidth: 100,
|
||||
maxWidth: 200,
|
||||
@@ -161,7 +191,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
||||
private getActionButton = (arrayPosition: number, isCurrentIndex: boolean): JSX.Element => {
|
||||
return isCurrentIndex ? (
|
||||
<IconButton
|
||||
ariaLabel="Delete index Button"
|
||||
ariaLabel={t(Keys.controls.settings.mongoIndexing.deleteIndexButton)}
|
||||
iconProps={{ iconName: "Delete" }}
|
||||
disabled={isIndexTransforming(this.props.indexTransformationProgress)}
|
||||
onClick={() => {
|
||||
@@ -170,7 +200,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
||||
/>
|
||||
) : (
|
||||
<IconButton
|
||||
ariaLabel="Add back Index Button"
|
||||
ariaLabel={t(Keys.controls.settings.mongoIndexing.addBackIndexButton)}
|
||||
iconProps={{ iconName: "Add" }}
|
||||
onClick={() => {
|
||||
this.props.onRevertIndexDrop(arrayPosition);
|
||||
@@ -258,7 +288,10 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
||||
|
||||
return (
|
||||
<Stack {...createAndAddMongoIndexStackProps} styles={mediumWidthStackStyles}>
|
||||
<CollapsibleSectionComponent title="Current index(es)" isExpandedByDefault={true}>
|
||||
<CollapsibleSectionComponent
|
||||
title={t(Keys.controls.settings.mongoIndexing.currentIndexes)}
|
||||
isExpandedByDefault={true}
|
||||
>
|
||||
{
|
||||
<>
|
||||
<DetailsList
|
||||
@@ -285,7 +318,10 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
||||
|
||||
return (
|
||||
<Stack styles={mediumWidthStackStyles}>
|
||||
<CollapsibleSectionComponent title="Index(es) to be dropped" isExpandedByDefault={true}>
|
||||
<CollapsibleSectionComponent
|
||||
title={t(Keys.controls.settings.mongoIndexing.indexesToBeDropped)}
|
||||
isExpandedByDefault={true}
|
||||
>
|
||||
{indexesToBeDropped.length > 0 && (
|
||||
<DetailsList
|
||||
styles={customDetailsListStyles}
|
||||
|
||||
@@ -18,6 +18,8 @@ import { cancelDataTransferJob, pollDataTransferJob } from "Common/dataAccess/da
|
||||
import { Platform, configContext } from "ConfigContext";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { ChangePartitionKeyPane } from "Explorer/Panes/ChangePartitionKeyPane/ChangePartitionKeyPane";
|
||||
import { Keys } from "../../../../Localization/Keys.generated";
|
||||
import { t } from "../../../../Localization/t";
|
||||
import {
|
||||
CosmosSqlDataTransferDataSourceSink,
|
||||
DataTransferJobGetResults,
|
||||
@@ -80,7 +82,7 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
|
||||
return (collection.partitionKeyProperties || []).map((property) => "/" + property).join(", ");
|
||||
};
|
||||
|
||||
const partitionKeyName = "Partition key";
|
||||
const partitionKeyName = t(Keys.controls.settings.partitionKey.partitionKey);
|
||||
const partitionKeyValue = getPartitionKeyValue();
|
||||
|
||||
const textHeadingStyle = {
|
||||
@@ -148,7 +150,13 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
|
||||
const getProgressDescription = (): string => {
|
||||
const processedCount = portalDataTransferJob?.properties?.processedCount;
|
||||
const totalCount = portalDataTransferJob?.properties?.totalCount;
|
||||
const processedCountString = totalCount > 0 ? `(${processedCount} of ${totalCount} documents processed)` : "";
|
||||
const processedCountString =
|
||||
totalCount > 0
|
||||
? t(Keys.controls.settings.partitionKeyEditor.documentsProcessed, {
|
||||
processedCount: String(processedCount),
|
||||
totalCount: String(totalCount),
|
||||
})
|
||||
: "";
|
||||
return `${portalDataTransferJob?.properties?.status} ${processedCountString}`;
|
||||
};
|
||||
|
||||
@@ -181,16 +189,28 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
|
||||
return (
|
||||
<Stack tokens={{ childrenGap: 20 }} styles={{ root: { maxWidth: 600 } }}>
|
||||
<Stack tokens={{ childrenGap: 10 }}>
|
||||
{!isReadOnly && <Text styles={textHeadingStyle}>Change {partitionKeyName.toLowerCase()}</Text>}
|
||||
{!isReadOnly && (
|
||||
<Text styles={textHeadingStyle}>
|
||||
{t(Keys.controls.settings.partitionKeyEditor.changePartitionKey, {
|
||||
partitionKeyName: partitionKeyName.toLowerCase(),
|
||||
})}
|
||||
</Text>
|
||||
)}
|
||||
<Stack horizontal tokens={{ childrenGap: 20 }}>
|
||||
<Stack tokens={{ childrenGap: 5 }}>
|
||||
<Text styles={textSubHeadingStyle}>Current {partitionKeyName.toLowerCase()}</Text>
|
||||
<Text styles={textSubHeadingStyle}>Partitioning</Text>
|
||||
<Text styles={textSubHeadingStyle}>
|
||||
{t(Keys.controls.settings.partitionKeyEditor.currentPartitionKey, {
|
||||
partitionKeyName: partitionKeyName.toLowerCase(),
|
||||
})}
|
||||
</Text>
|
||||
<Text styles={textSubHeadingStyle}>{t(Keys.controls.settings.partitionKeyEditor.partitioning)}</Text>
|
||||
</Stack>
|
||||
<Stack tokens={{ childrenGap: 5 }} data-test="partition-key-values">
|
||||
<Text styles={textSubHeadingStyle1}>{partitionKeyValue}</Text>
|
||||
<Text styles={textSubHeadingStyle1}>
|
||||
{isHierarchicalPartitionedContainer() ? "Hierarchical" : "Non-hierarchical"}
|
||||
{isHierarchicalPartitionedContainer()
|
||||
? t(Keys.controls.settings.partitionKeyEditor.hierarchical)
|
||||
: t(Keys.controls.settings.partitionKeyEditor.nonHierarchical)}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
@@ -204,33 +224,33 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
|
||||
messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}
|
||||
styles={darkThemeMessageBarStyles}
|
||||
>
|
||||
To safeguard the integrity of the data being copied to the new container, ensure that no updates are made to
|
||||
the source container for the entire duration of the partition key change process.
|
||||
{t(Keys.controls.settings.partitionKeyEditor.safeguardWarning)}
|
||||
<Link
|
||||
href="https://learn.microsoft.com/azure/cosmos-db/container-copy#how-does-container-copy-work"
|
||||
target="_blank"
|
||||
underline
|
||||
style={{ color: "var(--colorBrandForeground1)" }}
|
||||
>
|
||||
Learn more
|
||||
{t(Keys.common.learnMore)}
|
||||
</Link>
|
||||
</MessageBar>
|
||||
<Text styles={{ root: { color: "var(--colorNeutralForeground1)" } }}>
|
||||
To change the partition key, a new destination container must be created or an existing destination
|
||||
container selected. Data will then be copied to the destination container.
|
||||
{t(Keys.controls.settings.partitionKeyEditor.changeDescription)}
|
||||
</Text>
|
||||
{configContext.platform !== Platform.Emulator && (
|
||||
<PrimaryButton
|
||||
data-test="change-partition-key-button"
|
||||
styles={{ root: { width: "fit-content" } }}
|
||||
text="Change"
|
||||
text={t(Keys.controls.settings.partitionKeyEditor.changeButton)}
|
||||
onClick={startPartitionkeyChangeWorkflow}
|
||||
disabled={isCurrentJobInProgress(portalDataTransferJob)}
|
||||
/>
|
||||
)}
|
||||
{portalDataTransferJob && (
|
||||
<Stack>
|
||||
<Text styles={textHeadingStyle}>{partitionKeyName} change job</Text>
|
||||
<Text styles={textHeadingStyle}>
|
||||
{t(Keys.controls.settings.partitionKeyEditor.changeJob, { partitionKeyName })}
|
||||
</Text>
|
||||
<Stack
|
||||
horizontal
|
||||
tokens={{ childrenGap: 20 }}
|
||||
@@ -251,7 +271,10 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
|
||||
}}
|
||||
></ProgressIndicator>
|
||||
{isCurrentJobInProgress(portalDataTransferJob) && (
|
||||
<DefaultButton text="Cancel" onClick={() => cancelRunningDataTransferJob(portalDataTransferJob)} />
|
||||
<DefaultButton
|
||||
text={t(Keys.controls.settings.partitionKeyEditor.cancelButton)}
|
||||
onClick={() => cancelRunningDataTransferJob(portalDataTransferJob)}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -4,6 +4,8 @@ import * as Constants from "../../../../Common/Constants";
|
||||
import { Platform, configContext } from "../../../../ConfigContext";
|
||||
import * as DataModels from "../../../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../../../Contracts/ViewModels";
|
||||
import { Keys } from "../../../../Localization/Keys.generated";
|
||||
import { t } from "../../../../Localization/t";
|
||||
import * as SharedConstants from "../../../../Shared/Constants";
|
||||
import { userContext } from "../../../../UserContext";
|
||||
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
|
||||
@@ -156,14 +158,12 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||
const freeTierLimits = SharedConstants.FreeTierLimits;
|
||||
return (
|
||||
<Text>
|
||||
With free tier, you will get the first {freeTierLimits.RU} RU/s and {freeTierLimits.Storage} GB of storage in
|
||||
this account for free. To keep your account free, keep the total RU/s across all resources in the account to{" "}
|
||||
{freeTierLimits.RU} RU/s.
|
||||
{t(Keys.controls.settings.scale.freeTierInfo, { ru: freeTierLimits.RU, storage: freeTierLimits.Storage })}
|
||||
<Link
|
||||
href="https://docs.microsoft.com/en-us/azure/cosmos-db/understand-your-bill#billing-examples-with-free-tier-accounts"
|
||||
target="_blank"
|
||||
>
|
||||
Learn more.
|
||||
{t(Keys.controls.settings.scale.freeTierLearnMore)}
|
||||
</Link>
|
||||
</Text>
|
||||
);
|
||||
@@ -188,12 +188,9 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||
{/* TODO: Replace link with call to the Azure Support blade */}
|
||||
{this.isAutoScaleEnabled() && (
|
||||
<Stack {...titleAndInputStackProps}>
|
||||
<Text>Throughput (RU/s)</Text>
|
||||
<Text>{t(Keys.controls.settings.scale.throughputRuS)}</Text>
|
||||
<TextField disabled styles={getTextFieldStyles(undefined, undefined)} />
|
||||
<Text>
|
||||
Your account has custom settings that prevents setting throughput at the container level. Please work with
|
||||
your Cosmos DB engineering team point of contact to make changes.
|
||||
</Text>
|
||||
<Text>{t(Keys.controls.settings.scale.autoScaleCustomSettings)}</Text>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
} from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import * as ViewModels from "../../../../Contracts/ViewModels";
|
||||
import { Keys } from "../../../../Localization/Keys.generated";
|
||||
import { t } from "../../../../Localization/t";
|
||||
import { userContext } from "../../../../UserContext";
|
||||
import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
||||
import {
|
||||
@@ -85,9 +87,12 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
||||
constructor(props: SubSettingsComponentProps) {
|
||||
super(props);
|
||||
this.geospatialVisible = userContext.apiType === "SQL";
|
||||
this.partitionKeyName = userContext.apiType === "Mongo" ? "Shard key" : "Partition key";
|
||||
this.partitionKeyName =
|
||||
userContext.apiType === "Mongo"
|
||||
? t(Keys.controls.settings.partitionKey.shardKey)
|
||||
: t(Keys.controls.settings.partitionKey.partitionKey);
|
||||
this.partitionKeyValue = this.getPartitionKeyValue();
|
||||
this.uniqueKeyName = "Unique keys";
|
||||
this.uniqueKeyName = t(Keys.controls.settings.subSettings.uniqueKeys);
|
||||
this.uniqueKeyValue = this.getUniqueKeyValue();
|
||||
}
|
||||
|
||||
@@ -143,9 +148,13 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
||||
};
|
||||
|
||||
private ttlChoiceGroupOptions: IChoiceGroupOption[] = [
|
||||
{ key: TtlType.Off, text: "Off", ariaLabel: "ttl-off-option" },
|
||||
{ key: TtlType.OnNoDefault, text: "On (no default)", ariaLabel: "ttl-on-no-default-option" },
|
||||
{ key: TtlType.On, text: "On", ariaLabel: "ttl-on-option" },
|
||||
{ key: TtlType.Off, text: t(Keys.controls.settings.subSettings.ttlOff), ariaLabel: "ttl-off-option" },
|
||||
{
|
||||
key: TtlType.OnNoDefault,
|
||||
text: t(Keys.controls.settings.subSettings.ttlOnNoDefault),
|
||||
ariaLabel: "ttl-on-no-default-option",
|
||||
},
|
||||
{ key: TtlType.On, text: t(Keys.controls.settings.subSettings.ttlOn), ariaLabel: "ttl-on-option" },
|
||||
];
|
||||
|
||||
public getTtlValue = (value: string): TtlType => {
|
||||
@@ -216,13 +225,13 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||
To enable time-to-live (TTL) for your collection/documents,{" "}
|
||||
{t(Keys.controls.settings.subSettings.mongoTtlMessage)}{" "}
|
||||
<Link
|
||||
href="https://docs.microsoft.com/en-us/azure/cosmos-db/mongodb-time-to-live"
|
||||
target="_blank"
|
||||
style={{ color: "var(--colorBrandForeground1)" }}
|
||||
>
|
||||
create a TTL index
|
||||
{t(Keys.controls.settings.subSettings.mongoTtlLinkText)}
|
||||
</Link>
|
||||
.
|
||||
</Text>
|
||||
@@ -231,7 +240,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
||||
<Stack {...titleAndInputStackProps}>
|
||||
<ChoiceGroup
|
||||
id="timeToLive"
|
||||
label="Time to Live"
|
||||
label={t(Keys.controls.settings.subSettings.timeToLive)}
|
||||
selectedKey={this.props.timeToLive}
|
||||
options={this.ttlChoiceGroupOptions}
|
||||
onChange={this.onTtlChange}
|
||||
@@ -255,8 +264,8 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
||||
max={Int32.Max}
|
||||
value={this.props.displayedTtlSeconds}
|
||||
onChange={this.onTimeToLiveSecondsChange}
|
||||
suffix="second(s)"
|
||||
ariaLabel={`Time to live in seconds`}
|
||||
suffix={t(Keys.controls.settings.subSettings.seconds)}
|
||||
ariaLabel={t(Keys.controls.settings.subSettings.timeToLiveInSeconds)}
|
||||
data-test="ttl-input"
|
||||
/>
|
||||
)}
|
||||
@@ -264,16 +273,16 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
||||
);
|
||||
|
||||
private analyticalTtlChoiceGroupOptions: IChoiceGroupOption[] = [
|
||||
{ key: TtlType.Off, text: "Off", disabled: true },
|
||||
{ key: TtlType.OnNoDefault, text: "On (no default)" },
|
||||
{ key: TtlType.On, text: "On" },
|
||||
{ key: TtlType.Off, text: t(Keys.controls.settings.subSettings.ttlOff), disabled: true },
|
||||
{ key: TtlType.OnNoDefault, text: t(Keys.controls.settings.subSettings.ttlOnNoDefault) },
|
||||
{ key: TtlType.On, text: t(Keys.controls.settings.subSettings.ttlOn) },
|
||||
];
|
||||
|
||||
private getAnalyticalStorageTtlComponent = (): JSX.Element => (
|
||||
<Stack {...titleAndInputStackProps}>
|
||||
<ChoiceGroup
|
||||
id="analyticalStorageTimeToLive"
|
||||
label="Analytical Storage Time to Live"
|
||||
label={t(Keys.controls.settings.subSettings.analyticalStorageTtl)}
|
||||
selectedKey={this.props.analyticalStorageTtlSelection}
|
||||
options={this.analyticalTtlChoiceGroupOptions}
|
||||
onChange={this.onAnalyticalStorageTtlSelectionChange}
|
||||
@@ -294,7 +303,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
||||
min={1}
|
||||
max={Int32.Max}
|
||||
value={this.props.analyticalStorageTtlSeconds?.toString()}
|
||||
suffix="second(s)"
|
||||
suffix={t(Keys.controls.settings.subSettings.seconds)}
|
||||
onChange={this.onAnalyticalStorageTtlSecondsChange}
|
||||
/>
|
||||
)}
|
||||
@@ -302,14 +311,22 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
||||
);
|
||||
|
||||
private geoSpatialConfigTypeChoiceGroupOptions: IChoiceGroupOption[] = [
|
||||
{ key: GeospatialConfigType.Geography, text: "Geography", ariaLabel: "geography-option" },
|
||||
{ key: GeospatialConfigType.Geometry, text: "Geometry", ariaLabel: "geometry-option" },
|
||||
{
|
||||
key: GeospatialConfigType.Geography,
|
||||
text: t(Keys.controls.settings.subSettings.geography),
|
||||
ariaLabel: "geography-option",
|
||||
},
|
||||
{
|
||||
key: GeospatialConfigType.Geometry,
|
||||
text: t(Keys.controls.settings.subSettings.geometry),
|
||||
ariaLabel: "geometry-option",
|
||||
},
|
||||
];
|
||||
|
||||
private getGeoSpatialComponent = (): JSX.Element => (
|
||||
<ChoiceGroup
|
||||
id="geoSpatialConfig"
|
||||
label="Geospatial Configuration"
|
||||
label={t(Keys.controls.settings.subSettings.geospatialConfiguration)}
|
||||
selectedKey={this.props.geospatialConfigType}
|
||||
options={this.geoSpatialConfigTypeChoiceGroupOptions}
|
||||
onChange={this.onGeoSpatialConfigTypeChange}
|
||||
@@ -318,8 +335,8 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
||||
);
|
||||
|
||||
private changeFeedChoiceGroupOptions: IChoiceGroupOption[] = [
|
||||
{ key: ChangeFeedPolicyState.Off, text: "Off" },
|
||||
{ key: ChangeFeedPolicyState.On, text: "On" },
|
||||
{ key: ChangeFeedPolicyState.Off, text: t(Keys.controls.settings.subSettings.ttlOff) },
|
||||
{ key: ChangeFeedPolicyState.On, text: t(Keys.controls.settings.subSettings.ttlOn) },
|
||||
];
|
||||
|
||||
private getChangeFeedComponent = (): JSX.Element => {
|
||||
@@ -328,7 +345,10 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
||||
return (
|
||||
<Stack>
|
||||
<Label id={labelId}>
|
||||
<ToolTipLabelComponent label="Change feed log retention policy" toolTipElement={changeFeedPolicyToolTip} />
|
||||
<ToolTipLabelComponent
|
||||
label={t(Keys.controls.settings.changeFeed.label)}
|
||||
toolTipElement={changeFeedPolicyToolTip}
|
||||
/>
|
||||
</Label>
|
||||
<ChoiceGroup
|
||||
id="changeFeedPolicy"
|
||||
@@ -373,14 +393,20 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
||||
)}
|
||||
|
||||
{userContext.apiType === "SQL" && this.isLargePartitionKeyEnabled() && (
|
||||
<Text className={classNames.hintText}>Large {this.partitionKeyName.toLowerCase()} has been enabled.</Text>
|
||||
<Text className={classNames.hintText}>
|
||||
{t(Keys.controls.settings.subSettings.largePartitionKeyEnabled, {
|
||||
partitionKeyName: this.partitionKeyName.toLowerCase(),
|
||||
})}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{userContext.apiType === "SQL" &&
|
||||
(this.isHierarchicalPartitionedContainer() ? (
|
||||
<Text className={classNames.hintText}>Hierarchically partitioned container.</Text>
|
||||
<Text className={classNames.hintText}>{t(Keys.controls.settings.subSettings.hierarchicalPartitioned)}</Text>
|
||||
) : (
|
||||
<Text className={classNames.hintText}>Non-hierarchically partitioned container.</Text>
|
||||
<Text className={classNames.hintText}>
|
||||
{t(Keys.controls.settings.subSettings.nonHierarchicalPartitioned)}
|
||||
</Text>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Label, Slider, Stack, TextField, Toggle } from "@fluentui/react";
|
||||
import { ThroughputBucket } from "Contracts/DataModels";
|
||||
import { Keys } from "../../../../../Localization/Keys.generated";
|
||||
import { t } from "../../../../../Localization/t";
|
||||
import React, { FC, useEffect, useState } from "react";
|
||||
import { isDirty } from "../../SettingsUtils";
|
||||
|
||||
@@ -65,7 +67,9 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
|
||||
|
||||
return (
|
||||
<Stack tokens={{ childrenGap: "m" }} styles={{ root: { width: "70%", maxWidth: 700 } }}>
|
||||
<Label styles={{ root: { color: "var(--colorNeutralForeground1)" } }}>Throughput Buckets</Label>
|
||||
<Label styles={{ root: { color: "var(--colorNeutralForeground1)" } }}>
|
||||
{t(Keys.controls.settings.throughputBuckets.label)}
|
||||
</Label>
|
||||
<Stack>
|
||||
{throughputBuckets?.map((bucket) => (
|
||||
<Stack key={bucket.id} horizontal tokens={{ childrenGap: 8 }} verticalAlign="center">
|
||||
@@ -76,7 +80,9 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
|
||||
value={bucket.maxThroughputPercentage}
|
||||
onChange={(newValue) => handleBucketChange(bucket.id, newValue)}
|
||||
showValue={false}
|
||||
label={`Bucket ${bucket.id}${bucket.id === 1 ? " (Data Explorer Query Bucket)" : ""}`}
|
||||
label={`${t(Keys.controls.settings.throughputBuckets.bucketLabel, { id: String(bucket.id) })}${
|
||||
bucket.id === 1 ? t(Keys.controls.settings.throughputBuckets.dataExplorerQueryBucket) : ""
|
||||
}`}
|
||||
styles={{
|
||||
root: { flex: 2, maxWidth: 400 },
|
||||
titleLabel: {
|
||||
@@ -99,8 +105,8 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
|
||||
disabled={bucket.maxThroughputPercentage === 100}
|
||||
/>
|
||||
<Toggle
|
||||
onText="Active"
|
||||
offText="Inactive"
|
||||
onText={t(Keys.controls.settings.throughputBuckets.active)}
|
||||
offText={t(Keys.controls.settings.throughputBuckets.inactive)}
|
||||
checked={bucket.maxThroughputPercentage !== 100}
|
||||
onChange={(event, checked) => onToggle(bucket.id, checked)}
|
||||
styles={{
|
||||
|
||||
@@ -18,6 +18,8 @@ import {
|
||||
} from "@fluentui/react";
|
||||
import React from "react";
|
||||
import * as DataModels from "../../../../../Contracts/DataModels";
|
||||
import { Keys } from "../../../../../Localization/Keys.generated";
|
||||
import { t } from "../../../../../Localization/t";
|
||||
import * as SharedConstants from "../../../../../Shared/Constants";
|
||||
import { Action, ActionModifiers } from "../../../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../../../Shared/Telemetry/TelemetryProcessor";
|
||||
@@ -97,8 +99,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
private throughputInputMaxValue: number;
|
||||
private autoPilotInputMaxValue: number;
|
||||
private options: IChoiceGroupOption[] = [
|
||||
{ key: "true", text: "Autoscale" },
|
||||
{ key: "false", text: "Manual" },
|
||||
{ key: "true", text: t(Keys.controls.settings.throughputInput.autoscale) },
|
||||
{ key: "false", text: t(Keys.controls.settings.throughputInput.manual) },
|
||||
];
|
||||
|
||||
// Style constants for theme-aware colors and layout
|
||||
@@ -244,7 +246,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
return (
|
||||
<div>
|
||||
<Text style={{ fontWeight: 600, color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY }}>
|
||||
Updated cost per month
|
||||
{t(Keys.controls.settings.costEstimate.updatedCostPerMonth)}
|
||||
</Text>
|
||||
<Stack horizontal style={{ marginTop: 5, marginBottom: 10 }}>
|
||||
<Text
|
||||
@@ -253,7 +255,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY,
|
||||
}}
|
||||
>
|
||||
{newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice / 10)} min
|
||||
{newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice / 10)}{" "}
|
||||
{t(Keys.controls.settings.throughputInput.min)}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
@@ -261,7 +264,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY,
|
||||
}}
|
||||
>
|
||||
{newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice)} max
|
||||
{newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice)}{" "}
|
||||
{t(Keys.controls.settings.throughputInput.max)}
|
||||
</Text>
|
||||
</Stack>
|
||||
</div>
|
||||
@@ -274,7 +278,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
<Stack {...checkBoxAndInputStackProps} style={{ marginTop: 15 }}>
|
||||
{newThroughput && newThroughputCostElement()}
|
||||
<Text style={{ fontWeight: 600, color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY }}>
|
||||
Current cost per month
|
||||
{t(Keys.controls.settings.costEstimate.currentCostPerMonth)}
|
||||
</Text>
|
||||
<Stack horizontal style={{ marginTop: 5, color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY }}>
|
||||
<Text
|
||||
@@ -283,7 +287,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY,
|
||||
}}
|
||||
>
|
||||
{prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice / 10)} min
|
||||
{prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice / 10)}{" "}
|
||||
{t(Keys.controls.settings.throughputInput.min)}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
@@ -291,7 +296,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY,
|
||||
}}
|
||||
>
|
||||
{prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice)} max
|
||||
{prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice)}{" "}
|
||||
{t(Keys.controls.settings.throughputInput.max)}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
@@ -326,7 +332,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
return (
|
||||
<div>
|
||||
<Text style={{ fontWeight: 600, color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY }}>
|
||||
Updated cost per month
|
||||
{t(Keys.controls.settings.costEstimate.updatedCostPerMonth)}
|
||||
</Text>
|
||||
<Stack horizontal style={{ marginTop: 5, marginBottom: 10 }}>
|
||||
<Text style={this.settingsAndScaleStyle.root}>
|
||||
@@ -349,7 +355,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
<Stack {...checkBoxAndInputStackProps} style={{ marginTop: 15 }}>
|
||||
{newThroughput && newThroughputCostElement()}
|
||||
<Text style={{ fontWeight: 600, color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY }}>
|
||||
Current cost per month
|
||||
{t(Keys.controls.settings.costEstimate.currentCostPerMonth)}
|
||||
</Text>
|
||||
<Stack horizontal style={{ marginTop: 5 }}>
|
||||
<Text style={this.settingsAndScaleStyle.root}>
|
||||
@@ -444,10 +450,14 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
this.setState({ spendAckChecked: checked });
|
||||
|
||||
private getStorageCapacityTitle = (): JSX.Element => {
|
||||
const capacity: string = this.props.isFixed ? "Fixed" : "Unlimited";
|
||||
const capacity: string = this.props.isFixed
|
||||
? t(Keys.controls.settings.throughputInput.fixed)
|
||||
: t(Keys.controls.settings.throughputInput.unlimited);
|
||||
return (
|
||||
<Stack {...titleAndInputStackProps}>
|
||||
<Label style={{ color: "var(--colorNeutralForeground1)" }}>Storage capacity</Label>
|
||||
<Label style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||
{t(Keys.controls.settings.throughputInput.storageCapacity)}
|
||||
</Label>
|
||||
<Text style={{ color: "var(--colorNeutralForeground1)" }}>{capacity}</Text>
|
||||
</Stack>
|
||||
);
|
||||
@@ -558,10 +568,14 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
/>
|
||||
<Stack horizontal>
|
||||
<Stack.Item style={{ width: "34%", paddingRight: "5px" }}>
|
||||
<Separator styles={this.thoughputRangeSeparatorStyles}>Instant</Separator>
|
||||
<Separator styles={this.thoughputRangeSeparatorStyles}>
|
||||
{t(Keys.controls.settings.throughputInput.instant)}
|
||||
</Separator>
|
||||
</Stack.Item>
|
||||
<Stack.Item style={{ width: "66%", paddingLeft: "5px" }}>
|
||||
<Separator styles={this.thoughputRangeSeparatorStyles}>4-6 hrs</Separator>
|
||||
<Separator styles={this.thoughputRangeSeparatorStyles}>
|
||||
{t(Keys.controls.settings.throughputInput.fourToSixHrs)}
|
||||
</Separator>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack>
|
||||
@@ -641,7 +655,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
variant="small"
|
||||
style={{ lineHeight: "20px", fontWeight: 600, color: "var(--colorNeutralForeground1)" }}
|
||||
>
|
||||
Minimum RU/s
|
||||
{t(Keys.controls.settings.throughputInput.minimumRuS)}
|
||||
</Text>
|
||||
<FontIcon iconName="Info" style={{ fontSize: 12, color: "var(--colorNeutralForeground2)" }} />
|
||||
</Stack>
|
||||
@@ -675,7 +689,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
color: "var(--colorNeutralForeground1)",
|
||||
}}
|
||||
>
|
||||
x 10 =
|
||||
{t(Keys.controls.settings.throughputInput.x10Equals)}
|
||||
</Text>
|
||||
|
||||
{/* Column 3: Maximum RU/s */}
|
||||
@@ -685,7 +699,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
variant="small"
|
||||
style={{ lineHeight: "20px", fontWeight: 600, color: "var(--colorNeutralForeground1)" }}
|
||||
>
|
||||
Maximum RU/s
|
||||
{t(Keys.controls.settings.throughputInput.maximumRuS)}
|
||||
</Text>
|
||||
<FontIcon iconName="Info" style={{ fontSize: 12, color: "var(--colorNeutralForeground2)" }} />
|
||||
</Stack>
|
||||
@@ -726,7 +740,9 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
onGetErrorMessage={(value: string) => {
|
||||
const sanitizedValue = getSanitizedInputValue(value);
|
||||
const errorMessage: string =
|
||||
sanitizedValue % 1000 ? "Throughput value must be in increments of 1000" : this.props.throughputError;
|
||||
sanitizedValue % 1000
|
||||
? t(Keys.controls.settings.throughput.throughputIncrementError)
|
||||
: this.props.throughputError;
|
||||
return <span data-test="autopilot-throughput-input-error">{errorMessage}</span>;
|
||||
}}
|
||||
validateOnLoad={false}
|
||||
@@ -772,7 +788,9 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
)}
|
||||
{this.props.isAutoPilotSelected ? (
|
||||
<Text style={{ marginTop: "40px", color: "var(--colorNeutralForeground1)" }}>
|
||||
Based on usage, your {this.props.collectionName ? "container" : "database"} throughput will scale from{" "}
|
||||
{t(Keys.controls.settings.throughputInput.autoscaleDescription, {
|
||||
resourceType: this.props.collectionName ? "container" : "database",
|
||||
})}{" "}
|
||||
<b>
|
||||
{AutoPilotUtils.getMinRUsBasedOnUserInput(this.props.maxAutoPilotThroughput)} RU/s (10% of max RU/s) -{" "}
|
||||
{this.props.maxAutoPilotThroughput} RU/s
|
||||
@@ -787,16 +805,19 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
styles={this.darkThemeMessageBarStyles}
|
||||
style={{ marginTop: "40px" }}
|
||||
>
|
||||
{`Billing will apply if you provision more than ${SharedConstants.FreeTierLimits.RU} RU/s of manual throughput, or if the resource scales beyond ${SharedConstants.FreeTierLimits.RU} RU/s with autoscale.`}
|
||||
{t(Keys.controls.settings.throughputInput.freeTierWarning, {
|
||||
ru: String(SharedConstants.FreeTierLimits.RU),
|
||||
})}
|
||||
</MessageBar>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!this.overrideWithProvisionedThroughputSettings() && (
|
||||
<Text style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||
Estimate your required RU/s with
|
||||
{t(Keys.controls.settings.throughputInput.capacityCalculator)}
|
||||
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/">
|
||||
{` capacity calculator`} <FontIcon iconName="NavigateExternalInline" />
|
||||
{t(Keys.controls.settings.throughputInput.capacityCalculatorLink)}{" "}
|
||||
<FontIcon iconName="NavigateExternalInline" />
|
||||
</Link>
|
||||
</Text>
|
||||
)}
|
||||
@@ -809,9 +830,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
onChange={this.onSpendAckChecked}
|
||||
/>
|
||||
)}
|
||||
{this.props.isFixed && (
|
||||
<p>When using a collection with fixed storage capacity, you can set up to 10,000 RU/s.</p>
|
||||
)}
|
||||
{this.props.isFixed && <p>{t(Keys.controls.settings.throughputInput.fixedStorageNote)}</p>}
|
||||
{this.props.collectionName && (
|
||||
<Stack.Item style={{ marginTop: "40px" }}>{this.getStorageCapacityTitle()}</Stack.Item>
|
||||
)}
|
||||
|
||||
@@ -561,9 +561,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
You are not able to lower throughput below your current minimum of
|
||||
10000
|
||||
RU/s. For more information on this limit, please refer to our service quote documentation.
|
||||
You are not able to lower throughput below your current minimum of 10000 RU/s. For more information on this limit, please refer to our service quote documentation.
|
||||
<StyledLinkBase
|
||||
href="https://learn.microsoft.com/en-us/azure/cosmos-db/concepts-limits#minimum-throughput-limits"
|
||||
target="_blank"
|
||||
@@ -581,9 +579,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
Based on usage, your
|
||||
container
|
||||
throughput will scale from
|
||||
Based on usage, your container throughput will scale from
|
||||
|
||||
<b>
|
||||
400
|
||||
@@ -692,7 +688,8 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
||||
$
|
||||
|
||||
35.04
|
||||
min
|
||||
|
||||
min
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
@@ -705,7 +702,8 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
||||
$
|
||||
|
||||
350.40
|
||||
max
|
||||
|
||||
max
|
||||
</Text>
|
||||
</Stack>
|
||||
</div>
|
||||
@@ -739,7 +737,8 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
||||
$
|
||||
|
||||
35.04
|
||||
min
|
||||
|
||||
min
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
@@ -752,7 +751,8 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
||||
$
|
||||
|
||||
350.40
|
||||
max
|
||||
|
||||
max
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
@@ -1169,9 +1169,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
You are not able to lower throughput below your current minimum of
|
||||
10000
|
||||
RU/s. For more information on this limit, please refer to our service quote documentation.
|
||||
You are not able to lower throughput below your current minimum of 10000 RU/s. For more information on this limit, please refer to our service quote documentation.
|
||||
<StyledLinkBase
|
||||
href="https://learn.microsoft.com/en-us/azure/cosmos-db/concepts-limits#minimum-throughput-limits"
|
||||
target="_blank"
|
||||
@@ -1755,9 +1753,7 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
You are not able to lower throughput below your current minimum of
|
||||
10000
|
||||
RU/s. For more information on this limit, please refer to our service quote documentation.
|
||||
You are not able to lower throughput below your current minimum of 10000 RU/s. For more information on this limit, please refer to our service quote documentation.
|
||||
<StyledLinkBase
|
||||
href="https://learn.microsoft.com/en-us/azure/cosmos-db/concepts-limits#minimum-throughput-limits"
|
||||
target="_blank"
|
||||
|
||||
@@ -27,7 +27,8 @@ exports[`ComputedPropertiesComponent renders 1`] = `
|
||||
iconName="NavigateExternalInline"
|
||||
/>
|
||||
</StyledLinkBase>
|
||||
about how to define computed properties and how to use them.
|
||||
|
||||
about how to define computed properties and how to use them.
|
||||
</Text>
|
||||
<div
|
||||
className="settingsV2Editor"
|
||||
|
||||
@@ -33,8 +33,7 @@ exports[`PartitionKeyComponent renders default component and matches snapshot 1`
|
||||
}
|
||||
}
|
||||
>
|
||||
Change
|
||||
partition key
|
||||
Change partition key
|
||||
</Text>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
@@ -61,8 +60,7 @@ exports[`PartitionKeyComponent renders default component and matches snapshot 1`
|
||||
}
|
||||
}
|
||||
>
|
||||
Current
|
||||
partition key
|
||||
Current partition key
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
@@ -223,8 +221,7 @@ exports[`PartitionKeyComponent renders read-only component and matches snapshot
|
||||
}
|
||||
}
|
||||
>
|
||||
Current
|
||||
partition key
|
||||
Current partition key
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
|
||||
@@ -410,9 +410,7 @@ exports[`SubSettingsComponent analyticalTimeToLive hidden 1`] = `
|
||||
<Text
|
||||
className="hintText-115"
|
||||
>
|
||||
Large
|
||||
partition key
|
||||
has been enabled.
|
||||
Large partition key has been enabled.
|
||||
</Text>
|
||||
<Text
|
||||
className="hintText-115"
|
||||
@@ -984,9 +982,7 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
|
||||
<Text
|
||||
className="hintText-115"
|
||||
>
|
||||
Large
|
||||
partition key
|
||||
has been enabled.
|
||||
Large partition key has been enabled.
|
||||
</Text>
|
||||
<Text
|
||||
className="hintText-115"
|
||||
@@ -1522,9 +1518,7 @@ exports[`SubSettingsComponent changeFeedPolicy hidden 1`] = `
|
||||
<Text
|
||||
className="hintText-115"
|
||||
>
|
||||
Large
|
||||
partition key
|
||||
has been enabled.
|
||||
Large partition key has been enabled.
|
||||
</Text>
|
||||
<Text
|
||||
className="hintText-115"
|
||||
@@ -2157,9 +2151,7 @@ exports[`SubSettingsComponent renders 1`] = `
|
||||
<Text
|
||||
className="hintText-115"
|
||||
>
|
||||
Large
|
||||
partition key
|
||||
has been enabled.
|
||||
Large partition key has been enabled.
|
||||
</Text>
|
||||
<Text
|
||||
className="hintText-115"
|
||||
@@ -2729,9 +2721,7 @@ exports[`SubSettingsComponent timeToLiveSeconds hidden 1`] = `
|
||||
<Text
|
||||
className="hintText-115"
|
||||
>
|
||||
Large
|
||||
partition key
|
||||
has been enabled.
|
||||
Large partition key has been enabled.
|
||||
</Text>
|
||||
<Text
|
||||
className="hintText-115"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { Keys } from "../../../Localization/Keys.generated";
|
||||
import { t } from "../../../Localization/t";
|
||||
import { isFabricNative } from "../../../Platform/Fabric/FabricUtil";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { isCapabilityEnabled } from "../../../Utils/CapabilityUtils";
|
||||
@@ -175,25 +177,27 @@ const getStringValue = (value: isDirtyTypes, type: string): string => {
|
||||
export const getTabTitle = (tab: SettingsV2TabTypes): string => {
|
||||
switch (tab) {
|
||||
case SettingsV2TabTypes.ScaleTab:
|
||||
return "Scale";
|
||||
return t(Keys.controls.settings.tabTitles.scale);
|
||||
case SettingsV2TabTypes.ConflictResolutionTab:
|
||||
return "Conflict Resolution";
|
||||
return t(Keys.controls.settings.tabTitles.conflictResolution);
|
||||
case SettingsV2TabTypes.SubSettingsTab:
|
||||
return "Settings";
|
||||
return t(Keys.controls.settings.tabTitles.settings);
|
||||
case SettingsV2TabTypes.IndexingPolicyTab:
|
||||
return "Indexing Policy";
|
||||
return t(Keys.controls.settings.tabTitles.indexingPolicy);
|
||||
case SettingsV2TabTypes.PartitionKeyTab:
|
||||
return isFabricNative() ? "Partition Keys" : "Partition Keys (preview)";
|
||||
return isFabricNative()
|
||||
? t(Keys.controls.settings.tabTitles.partitionKeys)
|
||||
: t(Keys.controls.settings.tabTitles.partitionKeysPreview);
|
||||
case SettingsV2TabTypes.ComputedPropertiesTab:
|
||||
return "Computed Properties";
|
||||
return t(Keys.controls.settings.tabTitles.computedProperties);
|
||||
case SettingsV2TabTypes.ContainerVectorPolicyTab:
|
||||
return "Container Policies";
|
||||
return t(Keys.controls.settings.tabTitles.containerPolicies);
|
||||
case SettingsV2TabTypes.ThroughputBucketsTab:
|
||||
return "Throughput Buckets";
|
||||
return t(Keys.controls.settings.tabTitles.throughputBuckets);
|
||||
case SettingsV2TabTypes.GlobalSecondaryIndexTab:
|
||||
return "Global Secondary Index (Preview)";
|
||||
return t(Keys.controls.settings.tabTitles.globalSecondaryIndexPreview);
|
||||
case SettingsV2TabTypes.DataMaskingTab:
|
||||
return "Masking Policy (preview)";
|
||||
return t(Keys.controls.settings.tabTitles.maskingPolicyPreview);
|
||||
default:
|
||||
throw new Error(`Unknown tab ${tab}`);
|
||||
}
|
||||
@@ -203,19 +207,19 @@ export const getMongoNotification = (description: string, type: MongoIndexTypes)
|
||||
if (description && !type) {
|
||||
return {
|
||||
type: MongoNotificationType.Warning,
|
||||
message: "Please select a type for each index.",
|
||||
message: t(Keys.controls.settings.mongoNotifications.selectTypeWarning),
|
||||
};
|
||||
}
|
||||
|
||||
if (type && (!description || description.trim().length === 0)) {
|
||||
return {
|
||||
type: MongoNotificationType.Error,
|
||||
message: "Please enter a field name.",
|
||||
message: t(Keys.controls.settings.mongoNotifications.enterFieldNameError),
|
||||
};
|
||||
} else if (type === MongoIndexTypes.Wildcard && description?.indexOf("$**") === -1) {
|
||||
return {
|
||||
type: MongoNotificationType.Error,
|
||||
message: "Wildcard path is not present in the field name. Use a pattern like " + MongoWildcardPlaceHolder,
|
||||
message: t(Keys.controls.settings.mongoNotifications.wildcardPathError) + MongoWildcardPlaceHolder,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -249,28 +253,29 @@ export const isIndexTransforming = (indexTransformationProgress: number): boolea
|
||||
indexTransformationProgress !== undefined && indexTransformationProgress !== 100;
|
||||
|
||||
export const getPartitionKeyName = (apiType: string, isLowerCase?: boolean): string => {
|
||||
const partitionKeyName = apiType === "Mongo" ? "Shard key" : "Partition key";
|
||||
const partitionKeyName =
|
||||
apiType === "Mongo"
|
||||
? t(Keys.controls.settings.partitionKey.shardKey)
|
||||
: t(Keys.controls.settings.partitionKey.partitionKey);
|
||||
return isLowerCase ? partitionKeyName.toLocaleLowerCase() : partitionKeyName;
|
||||
};
|
||||
|
||||
export const getPartitionKeyTooltipText = (apiType: string): string => {
|
||||
if (apiType === "Mongo") {
|
||||
return "The shard key (field) is used to split your data across many replica sets (shards) to achieve unlimited scalability. It’s critical to choose a field that will evenly distribute your data.";
|
||||
return t(Keys.controls.settings.partitionKey.shardKeyTooltip);
|
||||
}
|
||||
let tooltipText = `The ${getPartitionKeyName(
|
||||
apiType,
|
||||
true,
|
||||
)} is used to automatically distribute data across partitions for scalability. Choose a property in your JSON document that has a wide range of values and evenly distributes request volume.`;
|
||||
let tooltipText = `The ${getPartitionKeyName(apiType, true)} ${t(
|
||||
Keys.controls.settings.partitionKey.partitionKeyTooltip,
|
||||
)}`;
|
||||
if (apiType === "SQL") {
|
||||
tooltipText += " For small read-heavy workloads or write-heavy workloads of any size, id is often a good choice.";
|
||||
tooltipText += t(Keys.controls.settings.partitionKey.sqlPartitionKeyTooltipSuffix);
|
||||
}
|
||||
return tooltipText;
|
||||
};
|
||||
|
||||
export const getPartitionKeySubtext = (partitionKeyDefault: boolean, apiType: string): string => {
|
||||
if (partitionKeyDefault && (apiType === "SQL" || apiType === "Mongo")) {
|
||||
const subtext = "For small workloads, the item ID is a suitable choice for the partition key.";
|
||||
return subtext;
|
||||
return t(Keys.controls.settings.partitionKey.partitionKeySubtext);
|
||||
}
|
||||
return "";
|
||||
};
|
||||
@@ -278,18 +283,18 @@ export const getPartitionKeySubtext = (partitionKeyDefault: boolean, apiType: st
|
||||
export const getPartitionKeyPlaceHolder = (apiType: string, index?: number): string => {
|
||||
switch (apiType) {
|
||||
case "Mongo":
|
||||
return "e.g., categoryId";
|
||||
return t(Keys.controls.settings.partitionKey.mongoPlaceholder);
|
||||
case "Gremlin":
|
||||
return "e.g., /address";
|
||||
return t(Keys.controls.settings.partitionKey.gremlinPlaceholder);
|
||||
case "SQL":
|
||||
return `${
|
||||
index === undefined
|
||||
? "Required - first partition key e.g., /TenantId"
|
||||
? t(Keys.controls.settings.partitionKey.sqlFirstPartitionKey)
|
||||
: index === 0
|
||||
? "second partition key e.g., /UserId"
|
||||
: "third partition key e.g., /SessionId"
|
||||
? t(Keys.controls.settings.partitionKey.sqlSecondPartitionKey)
|
||||
: t(Keys.controls.settings.partitionKey.sqlThirdPartitionKey)
|
||||
}`;
|
||||
default:
|
||||
return "e.g., /address/zipCode";
|
||||
return t(Keys.controls.settings.partitionKey.defaultPlaceholder);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -127,9 +127,13 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
>
|
||||
The request to increase the throughput has successfully been submitted. This operation will take 1-3 business days to complete. View the latest status in Notifications.
|
||||
<br />
|
||||
Database:
|
||||
Database:
|
||||
|
||||
sampleDb
|
||||
, Container:
|
||||
,
|
||||
|
||||
Container:
|
||||
|
||||
sampleCollection
|
||||
|
||||
, Current manual throughput: 1000 RU/s, Target manual throughput: 2000
|
||||
@@ -309,7 +313,7 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
You can make more indexing changes once the current index transformation has completed. It is 90% complete.
|
||||
You can make more indexing changes once the current index transformation has completed. It is 90% complete.
|
||||
<StyledLinkBase
|
||||
onClick={[Function]}
|
||||
>
|
||||
|
||||
@@ -723,5 +723,216 @@
|
||||
"information": "Information",
|
||||
"moreDetails": "More details"
|
||||
}
|
||||
},
|
||||
"controls": {
|
||||
"settings": {
|
||||
"tabTitles": {
|
||||
"scale": "Scale",
|
||||
"conflictResolution": "Conflict Resolution",
|
||||
"settings": "Settings",
|
||||
"indexingPolicy": "Indexing Policy",
|
||||
"partitionKeys": "Partition Keys",
|
||||
"partitionKeysPreview": "Partition Keys (preview)",
|
||||
"computedProperties": "Computed Properties",
|
||||
"containerPolicies": "Container Policies",
|
||||
"throughputBuckets": "Throughput Buckets",
|
||||
"globalSecondaryIndexPreview": "Global Secondary Index (Preview)",
|
||||
"maskingPolicyPreview": "Masking Policy (preview)"
|
||||
},
|
||||
"mongoNotifications": {
|
||||
"selectTypeWarning": "Please select a type for each index.",
|
||||
"enterFieldNameError": "Please enter a field name.",
|
||||
"wildcardPathError": "Wildcard path is not present in the field name. Use a pattern like "
|
||||
},
|
||||
"partitionKey": {
|
||||
"shardKey": "Shard key",
|
||||
"partitionKey": "Partition key",
|
||||
"shardKeyTooltip": "The shard key (field) is used to split your data across many replica sets (shards) to achieve unlimited scalability. It's critical to choose a field that will evenly distribute your data.",
|
||||
"partitionKeyTooltip": "is used to automatically distribute data across partitions for scalability. Choose a property in your JSON document that has a wide range of values and evenly distributes request volume.",
|
||||
"sqlPartitionKeyTooltipSuffix": " For small read-heavy workloads or write-heavy workloads of any size, id is often a good choice.",
|
||||
"partitionKeySubtext": "For small workloads, the item ID is a suitable choice for the partition key.",
|
||||
"mongoPlaceholder": "e.g., categoryId",
|
||||
"gremlinPlaceholder": "e.g., /address",
|
||||
"sqlFirstPartitionKey": "Required - first partition key e.g., /TenantId",
|
||||
"sqlSecondPartitionKey": "second partition key e.g., /UserId",
|
||||
"sqlThirdPartitionKey": "third partition key e.g., /SessionId",
|
||||
"defaultPlaceholder": "e.g., /address/zipCode"
|
||||
},
|
||||
"costEstimate": {
|
||||
"title": "Cost estimate*",
|
||||
"howWeCalculate": "How we calculate this",
|
||||
"updatedCostPerMonth": "Updated cost per month",
|
||||
"currentCostPerMonth": "Current cost per month",
|
||||
"perRu": "/RU"
|
||||
},
|
||||
"throughput": {
|
||||
"manualToAutoscaleDisclaimer": "The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s.",
|
||||
"ttlWarningText": "The system will automatically delete items based on the TTL value (in seconds) you provide, without needing a delete operation explicitly issued by a client application. For more information see,",
|
||||
"ttlWarningLinkText": "Time to Live (TTL) in Azure Cosmos DB",
|
||||
"unsavedIndexingPolicy": "indexing policy",
|
||||
"unsavedDataMaskingPolicy": "data masking policy",
|
||||
"unsavedComputedProperties": "computed properties",
|
||||
"unsavedEditorWarningPrefix": "You have not saved the latest changes made to your",
|
||||
"unsavedEditorWarningSuffix": ". Please click save to confirm the changes.",
|
||||
"updateDelayedApplyWarning": "You are about to request an increase in throughput beyond the pre-allocated capacity. This operation will take some time to complete.",
|
||||
"scalingUpDelayMessage": "Scaling up will take 4-6 hours as it exceeds what Azure Cosmos DB can instantly support currently based on your number of physical partitions. You can increase your throughput to {{instantMaximumThroughput}} instantly or proceed with this value and wait until the scale-up is completed.",
|
||||
"exceedPreAllocatedMessage": "Your request to increase throughput exceeds the pre-allocated capacity which may take longer than expected. There are three options you can choose from to proceed:",
|
||||
"instantScaleOption": "You can instantly scale up to {{instantMaximumThroughput}} RU/s.",
|
||||
"asyncScaleOption": "You can asynchronously scale up to any value under {{maximumThroughput}} RU/s in 4-6 hours.",
|
||||
"quotaMaxOption": "Your current quota max is {{maximumThroughput}} RU/s. To go over this limit, you must request a quota increase and the Azure Cosmos DB team will review.",
|
||||
"belowMinimumMessage": "You are not able to lower throughput below your current minimum of {{minimum}} RU/s. For more information on this limit, please refer to our service quote documentation.",
|
||||
"saveThroughputWarning": "Your bill will be affected as you update your throughput settings. Please review the updated cost estimate below before saving your changes",
|
||||
"currentAutoscaleThroughput": "Current autoscale throughput:",
|
||||
"targetAutoscaleThroughput": "Target autoscale throughput:",
|
||||
"currentManualThroughput": "Current manual throughput:",
|
||||
"targetManualThroughput": "Target manual throughput:",
|
||||
"applyDelayedMessage": "The request to increase the throughput has successfully been submitted. This operation will take 1-3 business days to complete. View the latest status in Notifications.",
|
||||
"databaseLabel": "Database:",
|
||||
"containerLabel": "Container:",
|
||||
"applyShortDelayMessage": "A request to increase the throughput is currently in progress. This operation will take some time to complete.",
|
||||
"applyLongDelayMessage": "A request to increase the throughput is currently in progress. This operation will take 1-3 business days to complete. View the latest status in Notifications.",
|
||||
"throughputCapError": "Your account is currently configured with a total throughput limit of {{throughputCap}} RU/s. This update isn't possible because it would increase the total throughput to {{newTotalThroughput}} RU/s. Change total throughput limit in cost management.",
|
||||
"throughputIncrementError": "Throughput value must be in increments of 1000"
|
||||
},
|
||||
"conflictResolution": {
|
||||
"lwwDefault": "Last Write Wins (default)",
|
||||
"customMergeProcedure": "Merge Procedure (custom)",
|
||||
"mode": "Mode",
|
||||
"conflictResolverProperty": "Conflict Resolver Property",
|
||||
"storedProcedure": "Stored procedure",
|
||||
"lwwTooltip": "Gets or sets the name of a integer property in your documents which is used for the Last Write Wins (LWW) based conflict resolution scheme. By default, the system uses the system defined timestamp property, _ts to decide the winner for the conflicting versions of the document. Specify your own integer property if you want to override the default timestamp based conflict resolution.",
|
||||
"customTooltip": "Gets or sets the name of a stored procedure (aka merge procedure) for resolving the conflicts. You can write application defined logic to determine the winner of the conflicting versions of a document. The stored procedure will get executed transactionally, exactly once, on the server side. If you do not provide a stored procedure, the conflicts will be populated in the",
|
||||
"customTooltipConflictsFeed": " conflicts feed",
|
||||
"customTooltipSuffix": ". You can update/re-register the stored procedure at any time."
|
||||
},
|
||||
"changeFeed": {
|
||||
"label": "Change feed log retention policy",
|
||||
"tooltip": "Enable change feed log retention policy to retain last 10 minutes of history for items in the container by default. To support this, the request unit (RU) charge for this container will be multiplied by a factor of two for writes. Reads are unaffected."
|
||||
},
|
||||
"mongoIndexing": {
|
||||
"disclaimer": "For queries that filter on multiple properties, create multiple single field indexes instead of a compound index.",
|
||||
"disclaimerCompoundIndexesLink": " Compound indexes ",
|
||||
"disclaimerSuffix": "are only used for sorting query results. If you need to add a compound index, you can create one using the Mongo shell.",
|
||||
"compoundNotSupported": "Collections with compound indexes are not yet supported in the indexing editor. To modify indexing policy for this collection, use the Mongo Shell.",
|
||||
"aadError": "To use the indexing policy editor, please login to the",
|
||||
"aadErrorLink": "azure portal.",
|
||||
"refreshingProgress": "Refreshing index transformation progress",
|
||||
"canMakeMoreChangesZero": "You can make more indexing changes once the current index transformation is complete. ",
|
||||
"refreshToCheck": "Refresh to check if it has completed.",
|
||||
"canMakeMoreChangesProgress": "You can make more indexing changes once the current index transformation has completed. It is {{progress}}% complete. ",
|
||||
"refreshToCheckProgress": "Refresh to check the progress.",
|
||||
"definitionColumn": "Definition",
|
||||
"typeColumn": "Type",
|
||||
"dropIndexColumn": "Drop Index",
|
||||
"addIndexBackColumn": "Add index back",
|
||||
"deleteIndexButton": "Delete index Button",
|
||||
"addBackIndexButton": "Add back Index Button",
|
||||
"currentIndexes": "Current index(es)",
|
||||
"indexesToBeDropped": "Index(es) to be dropped",
|
||||
"indexFieldName": "Index Field Name",
|
||||
"indexType": "Index Type",
|
||||
"selectIndexType": "Select an index type",
|
||||
"undoButton": "Undo Button"
|
||||
},
|
||||
"subSettings": {
|
||||
"timeToLive": "Time to Live",
|
||||
"ttlOff": "Off",
|
||||
"ttlOnNoDefault": "On (no default)",
|
||||
"ttlOn": "On",
|
||||
"seconds": "second(s)",
|
||||
"timeToLiveInSeconds": "Time to live in seconds",
|
||||
"analyticalStorageTtl": "Analytical Storage Time to Live",
|
||||
"geospatialConfiguration": "Geospatial Configuration",
|
||||
"geography": "Geography",
|
||||
"geometry": "Geometry",
|
||||
"uniqueKeys": "Unique keys",
|
||||
"mongoTtlMessage": "To enable time-to-live (TTL) for your collection/documents,",
|
||||
"mongoTtlLinkText": "create a TTL index",
|
||||
"partitionKeyTooltipTemplate": "This {{partitionKeyName}} is used to distribute data across multiple partitions for scalability. The value \"{{partitionKeyValue}}\" determines how documents are partitioned.",
|
||||
"largePartitionKeyEnabled": "Large {{partitionKeyName}} has been enabled.",
|
||||
"hierarchicalPartitioned": "Hierarchically partitioned container.",
|
||||
"nonHierarchicalPartitioned": "Non-hierarchically partitioned container."
|
||||
},
|
||||
"scale": {
|
||||
"freeTierInfo": "With free tier, you will get the first {{ru}} RU/s and {{storage}} GB of storage in this account for free. To keep your account free, keep the total RU/s across all resources in the account to {{ru}} RU/s.",
|
||||
"freeTierLearnMore": "Learn more.",
|
||||
"throughputRuS": "Throughput (RU/s)",
|
||||
"autoScaleCustomSettings": "Your account has custom settings that prevents setting throughput at the container level. Please work with your Cosmos DB engineering team point of contact to make changes.",
|
||||
"keyspaceSharedThroughput": "This table shared throughput is configured at the keyspace"
|
||||
},
|
||||
"partitionKeyEditor": {
|
||||
"changePartitionKey": "Change {{partitionKeyName}}",
|
||||
"currentPartitionKey": "Current {{partitionKeyName}}",
|
||||
"partitioning": "Partitioning",
|
||||
"hierarchical": "Hierarchical",
|
||||
"nonHierarchical": "Non-hierarchical",
|
||||
"safeguardWarning": "To safeguard the integrity of the data being copied to the new container, ensure that no updates are made to the source container for the entire duration of the partition key change process.",
|
||||
"changeDescription": "To change the partition key, a new destination container must be created or an existing destination container selected. Data will then be copied to the destination container.",
|
||||
"changeButton": "Change",
|
||||
"changeJob": "{{partitionKeyName}} change job",
|
||||
"cancelButton": "Cancel",
|
||||
"documentsProcessed": "({{processedCount}} of {{totalCount}} documents processed)"
|
||||
},
|
||||
"computedProperties": {
|
||||
"ariaLabel": "Computed properties",
|
||||
"learnMorePrefix": "about how to define computed properties and how to use them."
|
||||
},
|
||||
"indexingPolicy": {
|
||||
"ariaLabel": "Indexing Policy"
|
||||
},
|
||||
"dataMasking": {
|
||||
"ariaLabel": "Data Masking Policy",
|
||||
"validationFailed": "Validation failed:",
|
||||
"includedPathsRequired": "includedPaths is required",
|
||||
"includedPathsMustBeArray": "includedPaths must be an array",
|
||||
"excludedPathsMustBeArray": "excludedPaths must be an array if provided"
|
||||
},
|
||||
"containerPolicy": {
|
||||
"vectorPolicy": "Vector Policy",
|
||||
"fullTextPolicy": "Full Text Policy",
|
||||
"createFullTextPolicy": "Create new full text search policy"
|
||||
},
|
||||
"globalSecondaryIndex": {
|
||||
"indexesDefined": "This container has the following indexes defined for it.",
|
||||
"learnMoreSuffix": "about how to define global secondary indexes and how to use them.",
|
||||
"jsonAriaLabel": "Global Secondary Index JSON",
|
||||
"addIndex": "Add index",
|
||||
"settingsTitle": "Global Secondary Index Settings",
|
||||
"sourceContainer": "Source container",
|
||||
"indexDefinition": "Global secondary index definition"
|
||||
},
|
||||
"indexingPolicyRefresh": {
|
||||
"refreshFailed": "Refreshing index transformation progress failed"
|
||||
},
|
||||
"throughputInput": {
|
||||
"autoscale": "Autoscale",
|
||||
"manual": "Manual",
|
||||
"minimumRuS": "Minimum RU/s",
|
||||
"maximumRuS": "Maximum RU/s",
|
||||
"x10Equals": "x 10 =",
|
||||
"storageCapacity": "Storage capacity",
|
||||
"fixed": "Fixed",
|
||||
"unlimited": "Unlimited",
|
||||
"instant": "Instant",
|
||||
"fourToSixHrs": "4-6 hrs",
|
||||
"autoscaleDescription": "Based on usage, your {{resourceType}} throughput will scale from",
|
||||
"freeTierWarning": "Billing will apply if you provision more than {{ru}} RU/s of manual throughput, or if the resource scales beyond {{ru}} RU/s with autoscale.",
|
||||
"capacityCalculator": "Estimate your required RU/s with",
|
||||
"capacityCalculatorLink": " capacity calculator",
|
||||
"fixedStorageNote": "When using a collection with fixed storage capacity, you can set up to 10,000 RU/s.",
|
||||
"min": "min",
|
||||
"max": "max"
|
||||
},
|
||||
"throughputBuckets": {
|
||||
"label": "Throughput Buckets",
|
||||
"bucketLabel": "Bucket {{id}}",
|
||||
"dataExplorerQueryBucket": " (Data Explorer Query Bucket)",
|
||||
"active": "Active",
|
||||
"inactive": "Inactive"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"defaultCloseButton": "Close"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user