Compare commits

...

4 Commits

Author SHA1 Message Date
sunghyunkang1111
3385b62b60 Merge branch 'master' into loc_batch4 2026-03-06 16:04:36 -06:00
Sung-Hyun Kang
79a00080bd Update test snap 2026-03-06 15:18:20 -06:00
Sung-Hyun Kang
8bc875ae92 run prettier 2026-03-06 14:35:24 -06:00
Sung-Hyun Kang
47b8737ab1 Add localization strings batch 4 2026-03-06 13:37:25 -06:00
27 changed files with 628 additions and 274 deletions

View File

@@ -17,6 +17,8 @@ import {
} from "@fluentui/react"; } from "@fluentui/react";
import React, { FC, useEffect } from "react"; import React, { FC, useEffect } from "react";
import create, { UseStore } from "zustand"; import create, { UseStore } from "zustand";
import { Keys } from "../../Localization/Keys.generated";
import { t } from "../../Localization/t";
export interface DialogState { export interface DialogState {
visible: boolean; visible: boolean;
@@ -88,7 +90,7 @@ export const useDialog: UseStore<DialogState> = create((set, get) => ({
isModal: true, isModal: true,
title, title,
subText, subText,
primaryButtonText: "Close", primaryButtonText: t(Keys.common.close),
secondaryButtonText: undefined, secondaryButtonText: undefined,
onPrimaryButtonClick: () => { onPrimaryButtonClick: () => {
get().closeDialog(); get().closeDialog();

View File

@@ -44,6 +44,8 @@ import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2"; import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
import "./SettingsComponent.less"; import "./SettingsComponent.less";
import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils"; import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";
import { Keys } from "../../../Localization/Keys.generated";
import { t } from "../../../Localization/t";
import { import {
ConflictResolutionComponent, ConflictResolutionComponent,
ConflictResolutionComponentProps, ConflictResolutionComponentProps,
@@ -689,12 +691,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
private onDataMaskingContentChange = (newDataMasking: DataModels.DataMaskingPolicy): void => { private onDataMaskingContentChange = (newDataMasking: DataModels.DataMaskingPolicy): void => {
const validationErrors = []; const validationErrors = [];
if (newDataMasking.includedPaths === undefined || newDataMasking.includedPaths === null) { 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)) { } 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)) { 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({ this.setState({
@@ -896,7 +898,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
const buttons: CommandButtonComponentProps[] = []; const buttons: CommandButtonComponentProps[] = [];
const isExecuting = this.props.settingsTab.isExecuting(); const isExecuting = this.props.settingsTab.isExecuting();
if (this.saveSettingsButton.isVisible()) { if (this.saveSettingsButton.isVisible()) {
const label = "Save"; const label = t(Keys.common.save);
buttons.push({ buttons.push({
iconSrc: SaveIcon, iconSrc: SaveIcon,
iconAlt: label, iconAlt: label,
@@ -909,7 +911,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
} }
if (this.discardSettingsChangesButton.isVisible()) { if (this.discardSettingsChangesButton.isVisible()) {
const label = "Discard"; const label = t(Keys.common.discard);
buttons.push({ buttons.push({
iconSrc: DiscardIcon, iconSrc: DiscardIcon,
iconAlt: label, iconAlt: label,
@@ -934,9 +936,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1; const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1;
const throughputDelta = (newThroughput - this.offer.autoscaleMaxThroughput) * numberOfRegions; const throughputDelta = (newThroughput - this.offer.autoscaleMaxThroughput) * numberOfRegions;
if (throughputCap && throughputCap !== -1 && throughputCap - this.totalThroughputUsed < throughputDelta) { 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 ${ throughputError = t(Keys.controls.settings.throughput.throughputCapError, {
this.totalThroughputUsed + throughputDelta throughputCap: String(throughputCap),
} RU/s. Change total throughput limit in cost management.`; newTotalThroughput: String(this.totalThroughputUsed + throughputDelta),
});
} }
this.setState({ autoPilotThroughput: newThroughput, throughputError }); 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 numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1;
const throughputDelta = (newThroughput - this.offer.manualThroughput) * numberOfRegions; const throughputDelta = (newThroughput - this.offer.manualThroughput) * numberOfRegions;
if (throughputCap && throughputCap !== -1 && throughputCap - this.totalThroughputUsed < throughputDelta) { 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 ${ throughputError = t(Keys.controls.settings.throughput.throughputCapError, {
this.totalThroughputUsed + throughputDelta throughputCap: String(throughputCap),
} RU/s. Change total throughput limit in cost management.`; newTotalThroughput: String(this.totalThroughputUsed + throughputDelta),
});
} }
this.setState({ throughput: newThroughput, throughputError }); this.setState({ throughput: newThroughput, throughputError });
}; };
@@ -1560,7 +1564,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
} }
> >
{this.shouldShowKeyspaceSharedThroughputMessage() && ( {this.shouldShowKeyspaceSharedThroughputMessage() && (
<div>This table shared throughput is configured at the keyspace</div> <div>{t(Keys.controls.settings.scale.keyspaceSharedThroughput)}</div>
)} )}
<div <div

View File

@@ -25,6 +25,8 @@ import {
import * as React from "react"; import * as React from "react";
import { Urls } from "../../../Common/Constants"; import { Urls } from "../../../Common/Constants";
import { StyleConstants } from "../../../Common/StyleConstants"; import { StyleConstants } from "../../../Common/StyleConstants";
import { Keys } from "../../../Localization/Keys.generated";
import { t } from "../../../Localization/t";
import { hoursInAMonth } from "../../../Shared/Constants"; import { hoursInAMonth } from "../../../Shared/Constants";
import { import {
computeRUUsagePriceHourly, computeRUUsagePriceHourly,
@@ -338,10 +340,12 @@ export const getEstimatedSpendingElement = (
const ruRange: string = isAutoscale ? throughput / 10 + " RU/s - " : ""; const ruRange: string = isAutoscale ? throughput / 10 + " RU/s - " : "";
return ( return (
<Stack> <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} {costElement}
<Text style={{ fontWeight: 600, marginTop: 15, color: "var(--colorNeutralForeground1)" }}> <Text style={{ fontWeight: 600, marginTop: 15, color: "var(--colorNeutralForeground1)" }}>
How we calculate this {t(Keys.controls.settings.costEstimate.howWeCalculate)}
</Text> </Text>
<Stack id="throughputSpendElement" style={{ marginTop: 5 }}> <Stack id="throughputSpendElement" style={{ marginTop: 5 }}>
<span> <span>
@@ -353,7 +357,8 @@ export const getEstimatedSpendingElement = (
</span> </span>
<span> <span>
{priceBreakdown.currencySign} {priceBreakdown.currencySign}
{priceBreakdown.pricePerRu}/RU {priceBreakdown.pricePerRu}
{t(Keys.controls.settings.costEstimate.perRu)}
</span> </span>
</Stack> </Stack>
<Text style={{ marginTop: 15, color: "var(--colorNeutralForeground1)" }}> <Text style={{ marginTop: 15, color: "var(--colorNeutralForeground1)" }}>
@@ -365,18 +370,16 @@ export const getEstimatedSpendingElement = (
export const manualToAutoscaleDisclaimerElement: JSX.Element = ( export const manualToAutoscaleDisclaimerElement: JSX.Element = (
<Text styles={infoAndToolTipTextStyle} id="manualToAutoscaleDisclaimerElement"> <Text styles={infoAndToolTipTextStyle} id="manualToAutoscaleDisclaimerElement">
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings {t(Keys.controls.settings.throughput.manualToAutoscaleDisclaimer)}{" "}
and storage of your resource. After autoscale has been enabled, you can change the max RU/s.{" "}
<Link href={Urls.autoscaleMigration}>Learn more</Link> <Link href={Urls.autoscaleMigration}>Learn more</Link>
</Text> </Text>
); );
export const ttlWarning: JSX.Element = ( export const ttlWarning: JSX.Element = (
<Text styles={infoAndToolTipTextStyle}> <Text styles={infoAndToolTipTextStyle}>
The system will automatically delete items based on the TTL value (in seconds) you provide, without needing a delete {t(Keys.controls.settings.throughput.ttlWarningText)}{" "}
operation explicitly issued by a client application. For more information see,{" "}
<Link target="_blank" href="https://aka.ms/cosmos-db-ttl"> <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> </Link>
. .
</Text> </Text>
@@ -384,29 +387,28 @@ export const ttlWarning: JSX.Element = (
export const unsavedEditorWarningMessage = (editor: editorType): JSX.Element => ( export const unsavedEditorWarningMessage = (editor: editorType): JSX.Element => (
<Text styles={infoAndToolTipTextStyle}> <Text styles={infoAndToolTipTextStyle}>
You have not saved the latest changes made to your{" "} {t(Keys.controls.settings.throughput.unsavedEditorWarningPrefix)}{" "}
{editor === "indexPolicy" {editor === "indexPolicy"
? "indexing policy" ? t(Keys.controls.settings.throughput.unsavedIndexingPolicy)
: editor === "dataMasking" : editor === "dataMasking"
? "data masking policy" ? t(Keys.controls.settings.throughput.unsavedDataMaskingPolicy)
: "computed properties"} : t(Keys.controls.settings.throughput.unsavedComputedProperties)}
. Please click save to confirm the changes. {t(Keys.controls.settings.throughput.unsavedEditorWarningSuffix)}
</Text> </Text>
); );
export const updateThroughputDelayedApplyWarningMessage: JSX.Element = ( export const updateThroughputDelayedApplyWarningMessage: JSX.Element = (
<Text styles={infoAndToolTipTextStyle} id="updateThroughputDelayedApplyWarningMessage"> <Text styles={infoAndToolTipTextStyle} id="updateThroughputDelayedApplyWarningMessage">
You are about to request an increase in throughput beyond the pre-allocated capacity. This operation will take some {t(Keys.controls.settings.throughput.updateDelayedApplyWarning)}
time to complete.
</Text> </Text>
); );
export const getUpdateThroughputBeyondInstantLimitMessage = (instantMaximumThroughput: number): JSX.Element => { export const getUpdateThroughputBeyondInstantLimitMessage = (instantMaximumThroughput: number): JSX.Element => {
return ( return (
<Text id="updateThroughputDelayedApplyWarningMessage"> <Text id="updateThroughputDelayedApplyWarningMessage">
Scaling up will take 4-6 hours as it exceeds what Azure Cosmos DB can instantly support currently based on your {t(Keys.controls.settings.throughput.scalingUpDelayMessage, {
number of physical partitions. You can increase your throughput to {instantMaximumThroughput} instantly or proceed instantMaximumThroughput: String(instantMaximumThroughput),
with this value and wait until the scale-up is completed. })}
</Text> </Text>
); );
}; };
@@ -418,22 +420,26 @@ export const getUpdateThroughputBeyondSupportLimitMessage = (
return ( return (
<> <>
<Text styles={infoAndToolTipTextStyle} id="updateThroughputDelayedApplyWarningMessage"> <Text styles={infoAndToolTipTextStyle} id="updateThroughputDelayedApplyWarningMessage">
Your request to increase throughput exceeds the pre-allocated capacity which may take longer than expected. {t(Keys.controls.settings.throughput.exceedPreAllocatedMessage)}
There are three options you can choose from to proceed:
</Text> </Text>
<ol style={{ fontSize: 14, color: "var(--colorNeutralForeground1)", marginTop: "5px" }}> <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 && ( {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> <li>
Your current quota max is {maximumThroughput} RU/s. To go over this limit, you must request a quota increase {t(Keys.controls.settings.throughput.quotaMaxOption, { maximumThroughput: String(maximumThroughput) })}
and the Azure Cosmos DB team will review.
<Link <Link
href="https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/create-support-request-quota-increase" href="https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/create-support-request-quota-increase"
target="_blank" target="_blank"
> >
Learn more {t(Keys.common.learnMore)}
</Link> </Link>
</li> </li>
</ol> </ol>
@@ -444,23 +450,19 @@ export const getUpdateThroughputBeyondSupportLimitMessage = (
export const getUpdateThroughputBelowMinimumMessage = (minimum: number): JSX.Element => { export const getUpdateThroughputBelowMinimumMessage = (minimum: number): JSX.Element => {
return ( return (
<Text styles={infoAndToolTipTextStyle}> <Text styles={infoAndToolTipTextStyle}>
You are not able to lower throughput below your current minimum of {minimum} RU/s. For more information on this {t(Keys.controls.settings.throughput.belowMinimumMessage, { minimum: String(minimum) })}
limit, please refer to our service quote documentation.
<Link <Link
href="https://learn.microsoft.com/en-us/azure/cosmos-db/concepts-limits#minimum-throughput-limits" href="https://learn.microsoft.com/en-us/azure/cosmos-db/concepts-limits#minimum-throughput-limits"
target="_blank" target="_blank"
> >
Learn more {t(Keys.common.learnMore)}
</Link> </Link>
</Text> </Text>
); );
}; };
export const saveThroughputWarningMessage: JSX.Element = ( export const saveThroughputWarningMessage: JSX.Element = (
<Text> <Text>{t(Keys.controls.settings.throughput.saveThroughputWarning)}</Text>
Your bill will be affected as you update your throughput settings. Please review the updated cost estimate below
before saving your changes
</Text>
); );
const getCurrentThroughput = ( const getCurrentThroughput = (
@@ -472,23 +474,29 @@ const getCurrentThroughput = (
if (targetThroughput) { if (targetThroughput) {
if (throughput) { if (throughput) {
return isAutoscale return isAutoscale
? `, Current autoscale throughput: ${Math.round( ? `, ${t(Keys.controls.settings.throughput.currentAutoscaleThroughput)} ${Math.round(
throughput / 10, throughput / 10,
)} - ${throughput} ${throughputUnit}, Target autoscale throughput: ${Math.round( )} - ${throughput} ${throughputUnit}, ${t(
targetThroughput / 10, Keys.controls.settings.throughput.targetAutoscaleThroughput,
)} - ${targetThroughput} ${throughputUnit}` )} ${Math.round(targetThroughput / 10)} - ${targetThroughput} ${throughputUnit}`
: `, Current manual throughput: ${throughput} ${throughputUnit}, Target manual throughput: ${targetThroughput}`; : `, ${t(Keys.controls.settings.throughput.currentManualThroughput)} ${throughput} ${throughputUnit}, ${t(
Keys.controls.settings.throughput.targetManualThroughput,
)} ${targetThroughput}`;
} else { } else {
return isAutoscale return isAutoscale
? `, Target autoscale throughput: ${Math.round(targetThroughput / 10)} - ${targetThroughput} ${throughputUnit}` ? `, ${t(Keys.controls.settings.throughput.targetAutoscaleThroughput)} ${Math.round(
: `, Target manual throughput: ${targetThroughput} ${throughputUnit}`; targetThroughput / 10,
)} - ${targetThroughput} ${throughputUnit}`
: `, ${t(Keys.controls.settings.throughput.targetManualThroughput)} ${targetThroughput} ${throughputUnit}`;
} }
} }
if (!targetThroughput && throughput) { if (!targetThroughput && throughput) {
return isAutoscale return isAutoscale
? `, Current autoscale throughput: ${Math.round(throughput / 10)} - ${throughput} ${throughputUnit}` ? `, ${t(Keys.controls.settings.throughput.currentAutoscaleThroughput)} ${Math.round(
: `, Current manual throughput: ${throughput} ${throughputUnit}`; throughput / 10,
)} - ${throughput} ${throughputUnit}`
: `, ${t(Keys.controls.settings.throughput.currentManualThroughput)} ${throughput} ${throughputUnit}`;
} }
return ""; return "";
@@ -503,10 +511,10 @@ export const getThroughputApplyDelayedMessage = (
requestedThroughput: number, requestedThroughput: number,
): JSX.Element => ( ): JSX.Element => (
<Text styles={infoAndToolTipTextStyle}> <Text styles={infoAndToolTipTextStyle}>
The request to increase the throughput has successfully been submitted. This operation will take 1-3 business days {t(Keys.controls.settings.throughput.applyDelayedMessage)}
to complete. View the latest status in Notifications.
<br /> <br />
Database: {databaseName}, Container: {collectionName}{" "} {t(Keys.controls.settings.throughput.databaseLabel)} {databaseName},{" "}
{t(Keys.controls.settings.throughput.containerLabel)} {collectionName}{" "}
{getCurrentThroughput(isAutoscale, throughput, throughputUnit, requestedThroughput)} {getCurrentThroughput(isAutoscale, throughput, throughputUnit, requestedThroughput)}
</Text> </Text>
); );
@@ -519,9 +527,13 @@ export const getThroughputApplyShortDelayMessage = (
collectionName: string, collectionName: string,
): JSX.Element => ( ): JSX.Element => (
<Text styles={infoAndToolTipTextStyle} id="throughputApplyShortDelayMessage"> <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 /> <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)} {getCurrentThroughput(isAutoscale, throughput, throughputUnit)}
</Text> </Text>
); );
@@ -535,10 +547,13 @@ export const getThroughputApplyLongDelayMessage = (
requestedThroughput: number, requestedThroughput: number,
): JSX.Element => ( ): JSX.Element => (
<Text styles={infoAndToolTipTextStyle} id="throughputApplyLongDelayMessage"> <Text styles={infoAndToolTipTextStyle} id="throughputApplyLongDelayMessage">
A request to increase the throughput is currently in progress. This operation will take 1-3 business days to {t(Keys.controls.settings.throughput.applyLongDelayMessage)}
complete. View the latest status in Notifications.
<br /> <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)} {getCurrentThroughput(isAutoscale, throughput, throughputUnit, requestedThroughput)}
</Text> </Text>
); );
@@ -547,63 +562,49 @@ export const getToolTipContainer = (content: string | JSX.Element): JSX.Element
content ? <Text styles={infoAndToolTipTextStyle}>{content}</Text> : undefined; content ? <Text styles={infoAndToolTipTextStyle}>{content}</Text> : undefined;
export const conflictResolutionLwwTooltip: JSX.Element = ( export const conflictResolutionLwwTooltip: JSX.Element = (
<Text styles={infoAndToolTipTextStyle}> <Text styles={infoAndToolTipTextStyle}>{t(Keys.controls.settings.conflictResolution.lwwTooltip)}</Text>
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>
); );
export const conflictResolutionCustomToolTip: JSX.Element = ( export const conflictResolutionCustomToolTip: JSX.Element = (
<Text styles={infoAndToolTipTextStyle}> <Text styles={infoAndToolTipTextStyle}>
Gets or sets the name of a stored procedure (aka merge procedure) for resolving the conflicts. You can write {t(Keys.controls.settings.conflictResolution.customTooltip)}
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
<Link className="linkDarkBackground" href="https://aka.ms/dataexplorerconflics" target="_blank"> <Link className="linkDarkBackground" href="https://aka.ms/dataexplorerconflics" target="_blank">
{` conflicts feed`} {t(Keys.controls.settings.conflictResolution.customTooltipConflictsFeed)}
</Link> </Link>
. You can update/re-register the stored procedure at any time. {t(Keys.controls.settings.conflictResolution.customTooltipSuffix)}
</Text> </Text>
); );
export const changeFeedPolicyToolTip: JSX.Element = ( export const changeFeedPolicyToolTip: JSX.Element = (
<Text styles={infoAndToolTipTextStyle}> <Text styles={infoAndToolTipTextStyle}>{t(Keys.controls.settings.changeFeed.tooltip)}</Text>
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>
); );
export const mongoIndexingPolicyDisclaimer: JSX.Element = ( export const mongoIndexingPolicyDisclaimer: JSX.Element = (
<Text style={{ color: "var(--colorNeutralForeground1)" }}> <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 <Link
href="https://docs.microsoft.com/azure/cosmos-db/mongodb-indexing#index-types" href="https://docs.microsoft.com/azure/cosmos-db/mongodb-indexing#index-types"
target="_blank" target="_blank"
style={{ color: "var(--colorBrandForeground1)" }} style={{ color: "var(--colorBrandForeground1)" }}
> >
{` Compound indexes `} {t(Keys.controls.settings.mongoIndexing.disclaimerCompoundIndexesLink)}
</Link> </Link>
are only used for sorting query results. If you need to add a compound index, you can create one using the Mongo {t(Keys.controls.settings.mongoIndexing.disclaimerSuffix)}
shell.
</Text> </Text>
); );
export const mongoCompoundIndexNotSupportedMessage: JSX.Element = ( export const mongoCompoundIndexNotSupportedMessage: JSX.Element = (
<Text style={{ color: "var(--colorNeutralForeground1)" }}> <Text style={{ color: "var(--colorNeutralForeground1)" }}>
Collections with compound indexes are not yet supported in the indexing editor. To modify indexing policy for this {t(Keys.controls.settings.mongoIndexing.compoundNotSupported)}
collection, use the Mongo Shell.
</Text> </Text>
); );
export const mongoIndexingPolicyAADError: JSX.Element = ( export const mongoIndexingPolicyAADError: JSX.Element = (
<MessageBar messageBarType={MessageBarType.error}> <MessageBar messageBarType={MessageBarType.error}>
<Text> <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"> <Link target="_blank" href="https://portal.azure.com">
{"azure portal."} {t(Keys.controls.settings.mongoIndexing.aadErrorLink)}
</Link> </Link>
</Text> </Text>
</MessageBar> </MessageBar>
@@ -611,7 +612,7 @@ export const mongoIndexingPolicyAADError: JSX.Element = (
export const mongoIndexTransformationRefreshingMessage: JSX.Element = ( export const mongoIndexTransformationRefreshingMessage: JSX.Element = (
<Stack horizontal {...mongoWarningStackProps}> <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} /> <Spinner size={SpinnerSize.small} />
</Stack> </Stack>
); );
@@ -623,15 +624,18 @@ export const renderMongoIndexTransformationRefreshMessage = (
if (progress === 0) { if (progress === 0) {
return ( return (
<Text styles={infoAndToolTipTextStyle}> <Text styles={infoAndToolTipTextStyle}>
{"You can make more indexing changes once the current index transformation is complete. "} {t(Keys.controls.settings.mongoIndexing.canMakeMoreChangesZero)}
<Link onClick={performRefresh}>{"Refresh to check if it has completed."}</Link> <Link onClick={performRefresh}>{t(Keys.controls.settings.mongoIndexing.refreshToCheck)}</Link>
</Text> </Text>
); );
} else { } else {
return ( return (
<Text styles={infoAndToolTipTextStyle}> <Text styles={infoAndToolTipTextStyle}>
{`You can make more indexing changes once the current index transformation has completed. It is ${progress}% complete. `} {`${t(Keys.controls.settings.mongoIndexing.canMakeMoreChangesProgress).replace(
<Link onClick={performRefresh}>{"Refresh to check the progress."}</Link> "{{progress}}",
String(progress),
)} `}
<Link onClick={performRefresh}>{t(Keys.controls.settings.mongoIndexing.refreshToCheckProgress)}</Link>
</Text> </Text>
); );
} }

View File

@@ -4,6 +4,8 @@ import { titleAndInputStackProps, unsavedEditorWarningMessage } from "Explorer/C
import { isDirty } from "Explorer/Controls/Settings/SettingsUtils"; import { isDirty } from "Explorer/Controls/Settings/SettingsUtils";
import { loadMonaco } from "Explorer/LazyMonaco"; import { loadMonaco } from "Explorer/LazyMonaco";
import { monacoTheme, useThemeStore } from "hooks/useTheme"; 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 monaco from "monaco-editor";
import * as React from "react"; import * as React from "react";
export interface ComputedPropertiesComponentProps { export interface ComputedPropertiesComponentProps {
@@ -107,7 +109,7 @@ export class ComputedPropertiesComponent extends React.Component<
this.computedPropertiesEditor = monaco.editor.create(this.computedPropertiesDiv.current, { this.computedPropertiesEditor = monaco.editor.create(this.computedPropertiesDiv.current, {
value: value, value: value,
language: "json", language: "json",
ariaLabel: "Computed properties", ariaLabel: t(Keys.controls.settings.computedProperties.ariaLabel),
theme: monacoTheme(), theme: monacoTheme(),
}); });
if (this.computedPropertiesEditor) { if (this.computedPropertiesEditor) {
@@ -151,9 +153,9 @@ export class ComputedPropertiesComponent extends React.Component<
)} )}
<Text style={{ marginLeft: "30px", marginBottom: "10px", color: "var(--colorNeutralForeground1)" }}> <Text style={{ marginLeft: "30px", marginBottom: "10px", color: "var(--colorNeutralForeground1)" }}>
<Link target="_blank" href="https://aka.ms/computed-properties-preview/"> <Link target="_blank" href="https://aka.ms/computed-properties-preview/">
{"Learn more"} <FontIcon iconName="NavigateExternalInline" /> {t(Keys.common.learnMore)} <FontIcon iconName="NavigateExternalInline" />
</Link> </Link>
&#160; about how to define computed properties and how to use them. &#160; {t(Keys.controls.settings.computedProperties.learnMorePrefix)}
</Text> </Text>
<div <div
className="settingsV2Editor" className="settingsV2Editor"

View File

@@ -2,6 +2,8 @@ import { ChoiceGroup, IChoiceGroupOption, ITextFieldProps, Stack, TextField } fr
import * as React from "react"; import * as React from "react";
import * as DataModels from "../../../../Contracts/DataModels"; import * as DataModels from "../../../../Contracts/DataModels";
import * as ViewModels from "../../../../Contracts/ViewModels"; import * as ViewModels from "../../../../Contracts/ViewModels";
import { Keys } from "../../../../Localization/Keys.generated";
import { t } from "../../../../Localization/t";
import { import {
conflictResolutionCustomToolTip, conflictResolutionCustomToolTip,
conflictResolutionLwwTooltip, conflictResolutionLwwTooltip,
@@ -32,9 +34,12 @@ export class ConflictResolutionComponent extends React.Component<ConflictResolut
private conflictResolutionChoiceGroupOptions: IChoiceGroupOption[] = [ private conflictResolutionChoiceGroupOptions: IChoiceGroupOption[] = [
{ {
key: DataModels.ConflictResolutionMode.LastWriterWins, 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 { componentDidMount(): void {
@@ -85,7 +90,7 @@ export class ConflictResolutionComponent extends React.Component<ConflictResolut
private getConflictResolutionModeComponent = (): JSX.Element => ( private getConflictResolutionModeComponent = (): JSX.Element => (
<ChoiceGroup <ChoiceGroup
label="Mode" label={t(Keys.controls.settings.conflictResolution.mode)}
selectedKey={this.props.conflictResolutionPolicyMode} selectedKey={this.props.conflictResolutionPolicyMode}
options={this.conflictResolutionChoiceGroupOptions} options={this.conflictResolutionChoiceGroupOptions}
onChange={this.onConflictResolutionPolicyModeChange} onChange={this.onConflictResolutionPolicyModeChange}
@@ -103,7 +108,7 @@ export class ConflictResolutionComponent extends React.Component<ConflictResolut
private getConflictResolutionLWWComponent = (): JSX.Element => ( private getConflictResolutionLWWComponent = (): JSX.Element => (
<TextField <TextField
id="conflictResolutionLwwTextField" id="conflictResolutionLwwTextField"
label={"Conflict Resolver Property"} label={t(Keys.controls.settings.conflictResolution.conflictResolverProperty)}
onRenderLabel={this.onRenderLwwComponentTextField} onRenderLabel={this.onRenderLwwComponentTextField}
styles={{ styles={{
fieldGroup: { fieldGroup: {
@@ -158,7 +163,7 @@ export class ConflictResolutionComponent extends React.Component<ConflictResolut
return ( return (
<TextField <TextField
id="conflictResolutionCustomTextField" id="conflictResolutionCustomTextField"
label="Stored procedure" label={t(Keys.controls.settings.conflictResolution.storedProcedure)}
onRenderLabel={this.onRenderCustomComponentTextField} onRenderLabel={this.onRenderCustomComponentTextField}
styles={{ styles={{
fieldGroup: { fieldGroup: {

View File

@@ -7,6 +7,8 @@ import {
import { titleAndInputStackProps } from "Explorer/Controls/Settings/SettingsRenderUtils"; import { titleAndInputStackProps } from "Explorer/Controls/Settings/SettingsRenderUtils";
import { ContainerPolicyTabTypes, isDirty } from "Explorer/Controls/Settings/SettingsUtils"; import { ContainerPolicyTabTypes, isDirty } from "Explorer/Controls/Settings/SettingsUtils";
import { VectorEmbeddingPoliciesComponent } from "Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent"; import { VectorEmbeddingPoliciesComponent } from "Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent";
import { Keys } from "../../../../Localization/Keys.generated";
import { t } from "../../../../Localization/t";
import React from "react"; import React from "react";
export interface ContainerPolicyComponentProps { export interface ContainerPolicyComponentProps {
@@ -153,7 +155,7 @@ export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> =
<PivotItem <PivotItem
itemKey={ContainerPolicyTabTypes[ContainerPolicyTabTypes.VectorPolicyTab]} itemKey={ContainerPolicyTabTypes[ContainerPolicyTabTypes.VectorPolicyTab]}
style={{ marginTop: 20, color: "var(--colorNeutralForeground1)" }} style={{ marginTop: 20, color: "var(--colorNeutralForeground1)" }}
headerText="Vector Policy" headerText={t(Keys.controls.settings.containerPolicy.vectorPolicy)}
> >
<Stack {...titleAndInputStackProps} styles={{ root: { position: "relative", maxWidth: "400px" } }}> <Stack {...titleAndInputStackProps} styles={{ root: { position: "relative", maxWidth: "400px" } }}>
{vectorEmbeddings && ( {vectorEmbeddings && (
@@ -175,7 +177,7 @@ export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> =
<PivotItem <PivotItem
itemKey={ContainerPolicyTabTypes[ContainerPolicyTabTypes.FullTextPolicyTab]} itemKey={ContainerPolicyTabTypes[ContainerPolicyTabTypes.FullTextPolicyTab]}
style={{ marginTop: 20, color: "var(--colorNeutralForeground1)" }} 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" } }}> <Stack {...titleAndInputStackProps} styles={{ root: { position: "relative", maxWidth: "400px" } }}>
{fullTextSearchPolicy ? ( {fullTextSearchPolicy ? (
@@ -218,7 +220,7 @@ export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> =
}); });
}} }}
> >
Create new full text search policy {t(Keys.controls.settings.containerPolicy.createFullTextPolicy)}
</DefaultButton> </DefaultButton>
)} )}
</Stack> </Stack>

View File

@@ -2,6 +2,8 @@ import { MessageBar, MessageBarType, Stack } from "@fluentui/react";
import * as monaco from "monaco-editor"; import * as monaco from "monaco-editor";
import * as React from "react"; import * as React from "react";
import * as DataModels from "../../../../Contracts/DataModels"; import * as DataModels from "../../../../Contracts/DataModels";
import { Keys } from "../../../../Localization/Keys.generated";
import { t } from "../../../../Localization/t";
import { loadMonaco } from "../../../LazyMonaco"; import { loadMonaco } from "../../../LazyMonaco";
import { titleAndInputStackProps, unsavedEditorWarningMessage } from "../SettingsRenderUtils"; import { titleAndInputStackProps, unsavedEditorWarningMessage } from "../SettingsRenderUtils";
import { isDirty as isContentDirty, isDataMaskingEnabled } from "../SettingsUtils"; import { isDirty as isContentDirty, isDataMaskingEnabled } from "../SettingsUtils";
@@ -89,7 +91,7 @@ export class DataMaskingComponent extends React.Component<DataMaskingComponentPr
value: value, value: value,
language: "json", language: "json",
automaticLayout: true, automaticLayout: true,
ariaLabel: "Data Masking Policy", ariaLabel: t(Keys.controls.settings.dataMasking.ariaLabel),
fontSize: 13, fontSize: 13,
minimap: { enabled: false }, minimap: { enabled: false },
wordWrap: "off", wordWrap: "off",
@@ -142,7 +144,7 @@ export class DataMaskingComponent extends React.Component<DataMaskingComponentPr
)} )}
{this.props.validationErrors.length > 0 && ( {this.props.validationErrors.length > 0 && (
<MessageBar messageBarType={MessageBarType.error}> <MessageBar messageBarType={MessageBarType.error}>
Validation failed: {this.props.validationErrors.join(", ")} {t(Keys.controls.settings.dataMasking.validationFailed)} {this.props.validationErrors.join(", ")}
</MessageBar> </MessageBar>
)} )}
<div className="settingsV2Editor" tabIndex={0} ref={this.dataMaskingDiv}></div> <div className="settingsV2Editor" tabIndex={0} ref={this.dataMaskingDiv}></div>

View File

@@ -2,6 +2,8 @@ import { FontIcon, Link, Stack, Text } from "@fluentui/react";
import Explorer from "Explorer/Explorer"; import Explorer from "Explorer/Explorer";
import React from "react"; import React from "react";
import * as ViewModels from "../../../../Contracts/ViewModels"; import * as ViewModels from "../../../../Contracts/ViewModels";
import { Keys } from "../../../../Localization/Keys.generated";
import { t } from "../../../../Localization/t";
import { GlobalSecondaryIndexSourceComponent } from "./GlobalSecondaryIndexSourceComponent"; import { GlobalSecondaryIndexSourceComponent } from "./GlobalSecondaryIndexSourceComponent";
import { GlobalSecondaryIndexTargetComponent } from "./GlobalSecondaryIndexTargetComponent"; import { GlobalSecondaryIndexTargetComponent } from "./GlobalSecondaryIndexTargetComponent";
@@ -21,7 +23,9 @@ export const GlobalSecondaryIndexComponent: React.FC<GlobalSecondaryIndexCompone
<Stack tokens={{ childrenGap: 8 }} styles={{ root: { maxWidth: 600 } }}> <Stack tokens={{ childrenGap: 8 }} styles={{ root: { maxWidth: 600 } }}>
<Stack horizontal verticalAlign="center" wrap tokens={{ childrenGap: 8 }}> <Stack horizontal verticalAlign="center" wrap tokens={{ childrenGap: 8 }}>
{isSourceContainer && ( {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> <Text>
<Link <Link
@@ -31,7 +35,7 @@ export const GlobalSecondaryIndexComponent: React.FC<GlobalSecondaryIndexCompone
Learn more Learn more
<FontIcon iconName="NavigateExternalInline" style={{ marginLeft: "4px" }} /> <FontIcon iconName="NavigateExternalInline" style={{ marginLeft: "4px" }} />
</Link>{" "} </Link>{" "}
about how to define global secondary indexes and how to use them. {t(Keys.controls.settings.globalSecondaryIndex.learnMoreSuffix)}
</Text> </Text>
</Stack> </Stack>
{isSourceContainer && <GlobalSecondaryIndexSourceComponent collection={collection} explorer={explorer} />} {isSourceContainer && <GlobalSecondaryIndexSourceComponent collection={collection} explorer={explorer} />}

View File

@@ -9,6 +9,8 @@ import { useSidePanel } from "hooks/useSidePanel";
import * as monaco from "monaco-editor"; import * as monaco from "monaco-editor";
import React, { useEffect, useRef } from "react"; import React, { useEffect, useRef } from "react";
import * as ViewModels from "../../../../Contracts/ViewModels"; import * as ViewModels from "../../../../Contracts/ViewModels";
import { Keys } from "../../../../Localization/Keys.generated";
import { t } from "../../../../Localization/t";
export interface GlobalSecondaryIndexSourceComponentProps { export interface GlobalSecondaryIndexSourceComponentProps {
collection: ViewModels.Collection; collection: ViewModels.Collection;
@@ -67,7 +69,7 @@ export const GlobalSecondaryIndexSourceComponent: React.FC<GlobalSecondaryIndexS
editorRef.current = monacoInstance.editor.create(editorContainerRef.current, { editorRef.current = monacoInstance.editor.create(editorContainerRef.current, {
value: jsonValue, value: jsonValue,
language: "json", language: "json",
ariaLabel: "Global Secondary Index JSON", ariaLabel: t(Keys.controls.settings.globalSecondaryIndex.jsonAriaLabel),
readOnly: true, readOnly: true,
}); });
}; };
@@ -98,7 +100,7 @@ export const GlobalSecondaryIndexSourceComponent: React.FC<GlobalSecondaryIndexS
}} }}
/> />
<PrimaryButton <PrimaryButton
text="Add index" text={t(Keys.controls.settings.globalSecondaryIndex.addIndex)}
styles={{ root: { width: "fit-content", marginTop: 12 } }} styles={{ root: { width: "fit-content", marginTop: 12 } }}
onClick={() => onClick={() =>
useSidePanel useSidePanel

View File

@@ -1,6 +1,8 @@
import { Stack, Text } from "@fluentui/react"; import { Stack, Text } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import * as ViewModels from "../../../../Contracts/ViewModels"; import * as ViewModels from "../../../../Contracts/ViewModels";
import { Keys } from "../../../../Localization/Keys.generated";
import { t } from "../../../../Localization/t";
export interface GlobalSecondaryIndexTargetComponentProps { export interface GlobalSecondaryIndexTargetComponentProps {
collection: ViewModels.Collection; collection: ViewModels.Collection;
@@ -25,17 +27,21 @@ export const GlobalSecondaryIndexTargetComponent: React.FC<GlobalSecondaryIndexT
return ( return (
<Stack tokens={{ childrenGap: 15 }} styles={{ root: { maxWidth: 600 } }}> <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 }}> <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}> <Stack styles={valueBoxStyle}>
<Text>{globalSecondaryIndexDefinition?.sourceCollectionId}</Text> <Text>{globalSecondaryIndexDefinition?.sourceCollectionId}</Text>
</Stack> </Stack>
</Stack> </Stack>
<Stack tokens={{ childrenGap: 5 }}> <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}> <Stack styles={valueBoxStyle}>
<Text>{globalSecondaryIndexDefinition?.definition}</Text> <Text>{globalSecondaryIndexDefinition?.definition}</Text>
</Stack> </Stack>

View File

@@ -3,6 +3,8 @@ import { monacoTheme, useThemeStore } from "hooks/useTheme";
import * as monaco from "monaco-editor"; import * as monaco from "monaco-editor";
import * as React from "react"; import * as React from "react";
import * as DataModels from "../../../../Contracts/DataModels"; import * as DataModels from "../../../../Contracts/DataModels";
import { Keys } from "../../../../Localization/Keys.generated";
import { t } from "../../../../Localization/t";
import { loadMonaco } from "../../../LazyMonaco"; import { loadMonaco } from "../../../LazyMonaco";
import { titleAndInputStackProps, unsavedEditorWarningMessage } from "../SettingsRenderUtils"; import { titleAndInputStackProps, unsavedEditorWarningMessage } from "../SettingsRenderUtils";
import { isDirty, isIndexTransforming } from "../SettingsUtils"; import { isDirty, isIndexTransforming } from "../SettingsUtils";
@@ -119,7 +121,7 @@ export class IndexingPolicyComponent extends React.Component<
value: value, value: value,
language: "json", language: "json",
readOnly: isIndexTransforming(this.props.indexTransformationProgress), readOnly: isIndexTransforming(this.props.indexTransformationProgress),
ariaLabel: "Indexing Policy", ariaLabel: t(Keys.controls.settings.indexingPolicy.ariaLabel),
theme: monacoTheme(), theme: monacoTheme(),
}); });
if (this.indexingPolicyEditor) { if (this.indexingPolicyEditor) {

View File

@@ -1,6 +1,8 @@
import { MessageBar, MessageBarType } from "@fluentui/react"; import { MessageBar, MessageBarType } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import { handleError } from "../../../../../Common/ErrorHandlingUtils"; import { handleError } from "../../../../../Common/ErrorHandlingUtils";
import { Keys } from "../../../../../Localization/Keys.generated";
import { t } from "../../../../../Localization/t";
import { import {
mongoIndexTransformationRefreshingMessage, mongoIndexTransformationRefreshingMessage,
renderMongoIndexTransformationRefreshMessage, renderMongoIndexTransformationRefreshMessage,
@@ -46,7 +48,11 @@ export class IndexingPolicyRefreshComponent extends React.Component<
try { try {
await this.props.refreshIndexTransformationProgress(); await this.props.refreshIndexTransformationProgress();
} catch (error) { } catch (error) {
handleError(error, "RefreshIndexTransformationProgress", "Refreshing index transformation progress failed"); handleError(
error,
"RefreshIndexTransformationProgress",
t(Keys.controls.settings.indexingPolicyRefresh.refreshFailed),
);
} finally { } finally {
this.setState({ isRefreshing: false }); this.setState({ isRefreshing: false });
} }

View File

@@ -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 <StyledLinkBase
onClick={[Function]} onClick={[Function]}
> >

View File

@@ -9,6 +9,8 @@ import {
IDropdownOption, IDropdownOption,
ITextField, ITextField,
} from "@fluentui/react"; } from "@fluentui/react";
import { Keys } from "../../../../../Localization/Keys.generated";
import { t } from "../../../../../Localization/t";
import { import {
addMongoIndexSubElementsTokens, addMongoIndexSubElementsTokens,
mongoErrorMessageStyles, mongoErrorMessageStyles,
@@ -66,7 +68,7 @@ export class AddMongoIndexComponent extends React.Component<AddMongoIndexCompone
<Stack {...mongoWarningStackProps}> <Stack {...mongoWarningStackProps}>
<Stack horizontal tokens={addMongoIndexSubElementsTokens}> <Stack horizontal tokens={addMongoIndexSubElementsTokens}>
<TextField <TextField
ariaLabel={"Index Field Name " + this.props.position} ariaLabel={t(Keys.controls.settings.mongoIndexing.indexFieldName) + " " + this.props.position}
disabled={this.props.disabled} disabled={this.props.disabled}
styles={shortWidthTextFieldStyles} styles={shortWidthTextFieldStyles}
componentRef={this.setRef} componentRef={this.setRef}
@@ -76,17 +78,17 @@ export class AddMongoIndexComponent extends React.Component<AddMongoIndexCompone
/> />
<Dropdown <Dropdown
ariaLabel={"Index Type " + this.props.position} ariaLabel={t(Keys.controls.settings.mongoIndexing.indexType) + " " + this.props.position}
disabled={this.props.disabled} disabled={this.props.disabled}
styles={shortWidthDropDownStyles} styles={shortWidthDropDownStyles}
placeholder="Select an index type" placeholder={t(Keys.controls.settings.mongoIndexing.selectIndexType)}
selectedKey={this.props.type} selectedKey={this.props.type}
options={this.indexTypes} options={this.indexTypes}
onChange={this.onTypeChange} onChange={this.onTypeChange}
/> />
<IconButton <IconButton
ariaLabel={"Undo Button " + this.props.position} ariaLabel={t(Keys.controls.settings.mongoIndexing.undoButton) + " " + this.props.position}
iconProps={{ iconName: "Undo" }} iconProps={{ iconName: "Undo" }}
disabled={!this.props.description && !this.props.type} disabled={!this.props.description && !this.props.type}
onClick={() => this.props.onDiscard()} onClick={() => this.props.onDiscard()}

View File

@@ -15,6 +15,8 @@ import {
} from "@fluentui/react"; } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import { MongoIndex } from "../../../../../Utils/arm/generatedClients/cosmos/types"; 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 { CollapsibleSectionComponent } from "../../../CollapsiblePanel/CollapsibleSectionComponent";
import { import {
addMongoIndexStackProps, addMongoIndexStackProps,
@@ -83,11 +85,25 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
}; };
private initialIndexesColumns: IColumn[] = [ 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", key: "actionButton",
name: "Drop Index", name: t(Keys.controls.settings.mongoIndexing.dropIndexColumn),
fieldName: "actionButton", fieldName: "actionButton",
minWidth: 100, minWidth: 100,
maxWidth: 200, maxWidth: 200,
@@ -96,11 +112,25 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
]; ];
private indexesToBeDroppedColumns: IColumn[] = [ 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", key: "actionButton",
name: "Add index back", name: t(Keys.controls.settings.mongoIndexing.addIndexBackColumn),
fieldName: "actionButton", fieldName: "actionButton",
minWidth: 100, minWidth: 100,
maxWidth: 200, maxWidth: 200,
@@ -161,7 +191,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
private getActionButton = (arrayPosition: number, isCurrentIndex: boolean): JSX.Element => { private getActionButton = (arrayPosition: number, isCurrentIndex: boolean): JSX.Element => {
return isCurrentIndex ? ( return isCurrentIndex ? (
<IconButton <IconButton
ariaLabel="Delete index Button" ariaLabel={t(Keys.controls.settings.mongoIndexing.deleteIndexButton)}
iconProps={{ iconName: "Delete" }} iconProps={{ iconName: "Delete" }}
disabled={isIndexTransforming(this.props.indexTransformationProgress)} disabled={isIndexTransforming(this.props.indexTransformationProgress)}
onClick={() => { onClick={() => {
@@ -170,7 +200,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
/> />
) : ( ) : (
<IconButton <IconButton
ariaLabel="Add back Index Button" ariaLabel={t(Keys.controls.settings.mongoIndexing.addBackIndexButton)}
iconProps={{ iconName: "Add" }} iconProps={{ iconName: "Add" }}
onClick={() => { onClick={() => {
this.props.onRevertIndexDrop(arrayPosition); this.props.onRevertIndexDrop(arrayPosition);
@@ -258,7 +288,10 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
return ( return (
<Stack {...createAndAddMongoIndexStackProps} styles={mediumWidthStackStyles}> <Stack {...createAndAddMongoIndexStackProps} styles={mediumWidthStackStyles}>
<CollapsibleSectionComponent title="Current index(es)" isExpandedByDefault={true}> <CollapsibleSectionComponent
title={t(Keys.controls.settings.mongoIndexing.currentIndexes)}
isExpandedByDefault={true}
>
{ {
<> <>
<DetailsList <DetailsList
@@ -285,7 +318,10 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
return ( return (
<Stack styles={mediumWidthStackStyles}> <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 && ( {indexesToBeDropped.length > 0 && (
<DetailsList <DetailsList
styles={customDetailsListStyles} styles={customDetailsListStyles}

View File

@@ -18,6 +18,8 @@ import { cancelDataTransferJob, pollDataTransferJob } from "Common/dataAccess/da
import { Platform, configContext } from "ConfigContext"; import { Platform, configContext } from "ConfigContext";
import Explorer from "Explorer/Explorer"; import Explorer from "Explorer/Explorer";
import { ChangePartitionKeyPane } from "Explorer/Panes/ChangePartitionKeyPane/ChangePartitionKeyPane"; import { ChangePartitionKeyPane } from "Explorer/Panes/ChangePartitionKeyPane/ChangePartitionKeyPane";
import { Keys } from "../../../../Localization/Keys.generated";
import { t } from "../../../../Localization/t";
import { import {
CosmosSqlDataTransferDataSourceSink, CosmosSqlDataTransferDataSourceSink,
DataTransferJobGetResults, DataTransferJobGetResults,
@@ -80,7 +82,7 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
return (collection.partitionKeyProperties || []).map((property) => "/" + property).join(", "); return (collection.partitionKeyProperties || []).map((property) => "/" + property).join(", ");
}; };
const partitionKeyName = "Partition key"; const partitionKeyName = t(Keys.controls.settings.partitionKey.partitionKey);
const partitionKeyValue = getPartitionKeyValue(); const partitionKeyValue = getPartitionKeyValue();
const textHeadingStyle = { const textHeadingStyle = {
@@ -148,7 +150,13 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
const getProgressDescription = (): string => { const getProgressDescription = (): string => {
const processedCount = portalDataTransferJob?.properties?.processedCount; const processedCount = portalDataTransferJob?.properties?.processedCount;
const totalCount = portalDataTransferJob?.properties?.totalCount; 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}`; return `${portalDataTransferJob?.properties?.status} ${processedCountString}`;
}; };
@@ -181,16 +189,28 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
return ( return (
<Stack tokens={{ childrenGap: 20 }} styles={{ root: { maxWidth: 600 } }}> <Stack tokens={{ childrenGap: 20 }} styles={{ root: { maxWidth: 600 } }}>
<Stack tokens={{ childrenGap: 10 }}> <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 horizontal tokens={{ childrenGap: 20 }}>
<Stack tokens={{ childrenGap: 5 }}> <Stack tokens={{ childrenGap: 5 }}>
<Text styles={textSubHeadingStyle}>Current {partitionKeyName.toLowerCase()}</Text> <Text styles={textSubHeadingStyle}>
<Text styles={textSubHeadingStyle}>Partitioning</Text> {t(Keys.controls.settings.partitionKeyEditor.currentPartitionKey, {
partitionKeyName: partitionKeyName.toLowerCase(),
})}
</Text>
<Text styles={textSubHeadingStyle}>{t(Keys.controls.settings.partitionKeyEditor.partitioning)}</Text>
</Stack> </Stack>
<Stack tokens={{ childrenGap: 5 }} data-test="partition-key-values"> <Stack tokens={{ childrenGap: 5 }} data-test="partition-key-values">
<Text styles={textSubHeadingStyle1}>{partitionKeyValue}</Text> <Text styles={textSubHeadingStyle1}>{partitionKeyValue}</Text>
<Text styles={textSubHeadingStyle1}> <Text styles={textSubHeadingStyle1}>
{isHierarchicalPartitionedContainer() ? "Hierarchical" : "Non-hierarchical"} {isHierarchicalPartitionedContainer()
? t(Keys.controls.settings.partitionKeyEditor.hierarchical)
: t(Keys.controls.settings.partitionKeyEditor.nonHierarchical)}
</Text> </Text>
</Stack> </Stack>
</Stack> </Stack>
@@ -204,33 +224,33 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }} messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}
styles={darkThemeMessageBarStyles} styles={darkThemeMessageBarStyles}
> >
To safeguard the integrity of the data being copied to the new container, ensure that no updates are made to {t(Keys.controls.settings.partitionKeyEditor.safeguardWarning)}
the source container for the entire duration of the partition key change process.
<Link <Link
href="https://learn.microsoft.com/azure/cosmos-db/container-copy#how-does-container-copy-work" href="https://learn.microsoft.com/azure/cosmos-db/container-copy#how-does-container-copy-work"
target="_blank" target="_blank"
underline underline
style={{ color: "var(--colorBrandForeground1)" }} style={{ color: "var(--colorBrandForeground1)" }}
> >
Learn more {t(Keys.common.learnMore)}
</Link> </Link>
</MessageBar> </MessageBar>
<Text styles={{ root: { color: "var(--colorNeutralForeground1)" } }}> <Text styles={{ root: { color: "var(--colorNeutralForeground1)" } }}>
To change the partition key, a new destination container must be created or an existing destination {t(Keys.controls.settings.partitionKeyEditor.changeDescription)}
container selected. Data will then be copied to the destination container.
</Text> </Text>
{configContext.platform !== Platform.Emulator && ( {configContext.platform !== Platform.Emulator && (
<PrimaryButton <PrimaryButton
data-test="change-partition-key-button" data-test="change-partition-key-button"
styles={{ root: { width: "fit-content" } }} styles={{ root: { width: "fit-content" } }}
text="Change" text={t(Keys.controls.settings.partitionKeyEditor.changeButton)}
onClick={startPartitionkeyChangeWorkflow} onClick={startPartitionkeyChangeWorkflow}
disabled={isCurrentJobInProgress(portalDataTransferJob)} disabled={isCurrentJobInProgress(portalDataTransferJob)}
/> />
)} )}
{portalDataTransferJob && ( {portalDataTransferJob && (
<Stack> <Stack>
<Text styles={textHeadingStyle}>{partitionKeyName} change job</Text> <Text styles={textHeadingStyle}>
{t(Keys.controls.settings.partitionKeyEditor.changeJob, { partitionKeyName })}
</Text>
<Stack <Stack
horizontal horizontal
tokens={{ childrenGap: 20 }} tokens={{ childrenGap: 20 }}
@@ -251,7 +271,10 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
}} }}
></ProgressIndicator> ></ProgressIndicator>
{isCurrentJobInProgress(portalDataTransferJob) && ( {isCurrentJobInProgress(portalDataTransferJob) && (
<DefaultButton text="Cancel" onClick={() => cancelRunningDataTransferJob(portalDataTransferJob)} /> <DefaultButton
text={t(Keys.controls.settings.partitionKeyEditor.cancelButton)}
onClick={() => cancelRunningDataTransferJob(portalDataTransferJob)}
/>
)} )}
</Stack> </Stack>
</Stack> </Stack>

View File

@@ -4,6 +4,8 @@ import * as Constants from "../../../../Common/Constants";
import { Platform, configContext } from "../../../../ConfigContext"; import { Platform, configContext } from "../../../../ConfigContext";
import * as DataModels from "../../../../Contracts/DataModels"; import * as DataModels from "../../../../Contracts/DataModels";
import * as ViewModels from "../../../../Contracts/ViewModels"; import * as ViewModels from "../../../../Contracts/ViewModels";
import { Keys } from "../../../../Localization/Keys.generated";
import { t } from "../../../../Localization/t";
import * as SharedConstants from "../../../../Shared/Constants"; import * as SharedConstants from "../../../../Shared/Constants";
import { userContext } from "../../../../UserContext"; import { userContext } from "../../../../UserContext";
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils"; import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
@@ -156,14 +158,12 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
const freeTierLimits = SharedConstants.FreeTierLimits; const freeTierLimits = SharedConstants.FreeTierLimits;
return ( return (
<Text> <Text>
With free tier, you will get the first {freeTierLimits.RU} RU/s and {freeTierLimits.Storage} GB of storage in {t(Keys.controls.settings.scale.freeTierInfo, { ru: freeTierLimits.RU, storage: freeTierLimits.Storage })}
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.
<Link <Link
href="https://docs.microsoft.com/en-us/azure/cosmos-db/understand-your-bill#billing-examples-with-free-tier-accounts" href="https://docs.microsoft.com/en-us/azure/cosmos-db/understand-your-bill#billing-examples-with-free-tier-accounts"
target="_blank" target="_blank"
> >
Learn more. {t(Keys.controls.settings.scale.freeTierLearnMore)}
</Link> </Link>
</Text> </Text>
); );
@@ -188,12 +188,9 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
{/* TODO: Replace link with call to the Azure Support blade */} {/* TODO: Replace link with call to the Azure Support blade */}
{this.isAutoScaleEnabled() && ( {this.isAutoScaleEnabled() && (
<Stack {...titleAndInputStackProps}> <Stack {...titleAndInputStackProps}>
<Text>Throughput (RU/s)</Text> <Text>{t(Keys.controls.settings.scale.throughputRuS)}</Text>
<TextField disabled styles={getTextFieldStyles(undefined, undefined)} /> <TextField disabled styles={getTextFieldStyles(undefined, undefined)} />
<Text> <Text>{t(Keys.controls.settings.scale.autoScaleCustomSettings)}</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>
</Stack> </Stack>
)} )}
</Stack> </Stack>

View File

@@ -12,6 +12,8 @@ import {
} from "@fluentui/react"; } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import * as ViewModels from "../../../../Contracts/ViewModels"; import * as ViewModels from "../../../../Contracts/ViewModels";
import { Keys } from "../../../../Localization/Keys.generated";
import { t } from "../../../../Localization/t";
import { userContext } from "../../../../UserContext"; import { userContext } from "../../../../UserContext";
import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon"; import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
import { import {
@@ -85,9 +87,12 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
constructor(props: SubSettingsComponentProps) { constructor(props: SubSettingsComponentProps) {
super(props); super(props);
this.geospatialVisible = userContext.apiType === "SQL"; 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.partitionKeyValue = this.getPartitionKeyValue();
this.uniqueKeyName = "Unique keys"; this.uniqueKeyName = t(Keys.controls.settings.subSettings.uniqueKeys);
this.uniqueKeyValue = this.getUniqueKeyValue(); this.uniqueKeyValue = this.getUniqueKeyValue();
} }
@@ -143,9 +148,13 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
}; };
private ttlChoiceGroupOptions: IChoiceGroupOption[] = [ private ttlChoiceGroupOptions: IChoiceGroupOption[] = [
{ key: TtlType.Off, text: "Off", ariaLabel: "ttl-off-option" }, { key: TtlType.Off, text: t(Keys.controls.settings.subSettings.ttlOff), 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.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 => { public getTtlValue = (value: string): TtlType => {
@@ -216,13 +225,13 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
}} }}
> >
<Text style={{ color: "var(--colorNeutralForeground1)" }}> <Text style={{ color: "var(--colorNeutralForeground1)" }}>
To enable time-to-live (TTL) for your collection/documents,{" "} {t(Keys.controls.settings.subSettings.mongoTtlMessage)}{" "}
<Link <Link
href="https://docs.microsoft.com/en-us/azure/cosmos-db/mongodb-time-to-live" href="https://docs.microsoft.com/en-us/azure/cosmos-db/mongodb-time-to-live"
target="_blank" target="_blank"
style={{ color: "var(--colorBrandForeground1)" }} style={{ color: "var(--colorBrandForeground1)" }}
> >
create a TTL index {t(Keys.controls.settings.subSettings.mongoTtlLinkText)}
</Link> </Link>
. .
</Text> </Text>
@@ -231,7 +240,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
<Stack {...titleAndInputStackProps}> <Stack {...titleAndInputStackProps}>
<ChoiceGroup <ChoiceGroup
id="timeToLive" id="timeToLive"
label="Time to Live" label={t(Keys.controls.settings.subSettings.timeToLive)}
selectedKey={this.props.timeToLive} selectedKey={this.props.timeToLive}
options={this.ttlChoiceGroupOptions} options={this.ttlChoiceGroupOptions}
onChange={this.onTtlChange} onChange={this.onTtlChange}
@@ -255,8 +264,8 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
max={Int32.Max} max={Int32.Max}
value={this.props.displayedTtlSeconds} value={this.props.displayedTtlSeconds}
onChange={this.onTimeToLiveSecondsChange} onChange={this.onTimeToLiveSecondsChange}
suffix="second(s)" suffix={t(Keys.controls.settings.subSettings.seconds)}
ariaLabel={`Time to live in seconds`} ariaLabel={t(Keys.controls.settings.subSettings.timeToLiveInSeconds)}
data-test="ttl-input" data-test="ttl-input"
/> />
)} )}
@@ -264,16 +273,16 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
); );
private analyticalTtlChoiceGroupOptions: IChoiceGroupOption[] = [ private analyticalTtlChoiceGroupOptions: IChoiceGroupOption[] = [
{ key: TtlType.Off, text: "Off", disabled: true }, { key: TtlType.Off, text: t(Keys.controls.settings.subSettings.ttlOff), disabled: true },
{ key: TtlType.OnNoDefault, text: "On (no default)" }, { key: TtlType.OnNoDefault, text: t(Keys.controls.settings.subSettings.ttlOnNoDefault) },
{ key: TtlType.On, text: "On" }, { key: TtlType.On, text: t(Keys.controls.settings.subSettings.ttlOn) },
]; ];
private getAnalyticalStorageTtlComponent = (): JSX.Element => ( private getAnalyticalStorageTtlComponent = (): JSX.Element => (
<Stack {...titleAndInputStackProps}> <Stack {...titleAndInputStackProps}>
<ChoiceGroup <ChoiceGroup
id="analyticalStorageTimeToLive" id="analyticalStorageTimeToLive"
label="Analytical Storage Time to Live" label={t(Keys.controls.settings.subSettings.analyticalStorageTtl)}
selectedKey={this.props.analyticalStorageTtlSelection} selectedKey={this.props.analyticalStorageTtlSelection}
options={this.analyticalTtlChoiceGroupOptions} options={this.analyticalTtlChoiceGroupOptions}
onChange={this.onAnalyticalStorageTtlSelectionChange} onChange={this.onAnalyticalStorageTtlSelectionChange}
@@ -294,7 +303,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
min={1} min={1}
max={Int32.Max} max={Int32.Max}
value={this.props.analyticalStorageTtlSeconds?.toString()} value={this.props.analyticalStorageTtlSeconds?.toString()}
suffix="second(s)" suffix={t(Keys.controls.settings.subSettings.seconds)}
onChange={this.onAnalyticalStorageTtlSecondsChange} onChange={this.onAnalyticalStorageTtlSecondsChange}
/> />
)} )}
@@ -302,14 +311,22 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
); );
private geoSpatialConfigTypeChoiceGroupOptions: IChoiceGroupOption[] = [ 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 => ( private getGeoSpatialComponent = (): JSX.Element => (
<ChoiceGroup <ChoiceGroup
id="geoSpatialConfig" id="geoSpatialConfig"
label="Geospatial Configuration" label={t(Keys.controls.settings.subSettings.geospatialConfiguration)}
selectedKey={this.props.geospatialConfigType} selectedKey={this.props.geospatialConfigType}
options={this.geoSpatialConfigTypeChoiceGroupOptions} options={this.geoSpatialConfigTypeChoiceGroupOptions}
onChange={this.onGeoSpatialConfigTypeChange} onChange={this.onGeoSpatialConfigTypeChange}
@@ -318,8 +335,8 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
); );
private changeFeedChoiceGroupOptions: IChoiceGroupOption[] = [ private changeFeedChoiceGroupOptions: IChoiceGroupOption[] = [
{ key: ChangeFeedPolicyState.Off, text: "Off" }, { key: ChangeFeedPolicyState.Off, text: t(Keys.controls.settings.subSettings.ttlOff) },
{ key: ChangeFeedPolicyState.On, text: "On" }, { key: ChangeFeedPolicyState.On, text: t(Keys.controls.settings.subSettings.ttlOn) },
]; ];
private getChangeFeedComponent = (): JSX.Element => { private getChangeFeedComponent = (): JSX.Element => {
@@ -328,7 +345,10 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
return ( return (
<Stack> <Stack>
<Label id={labelId}> <Label id={labelId}>
<ToolTipLabelComponent label="Change feed log retention policy" toolTipElement={changeFeedPolicyToolTip} /> <ToolTipLabelComponent
label={t(Keys.controls.settings.changeFeed.label)}
toolTipElement={changeFeedPolicyToolTip}
/>
</Label> </Label>
<ChoiceGroup <ChoiceGroup
id="changeFeedPolicy" id="changeFeedPolicy"
@@ -373,14 +393,20 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
)} )}
{userContext.apiType === "SQL" && this.isLargePartitionKeyEnabled() && ( {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" && {userContext.apiType === "SQL" &&
(this.isHierarchicalPartitionedContainer() ? ( (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> </Stack>
); );

View File

@@ -1,5 +1,7 @@
import { Label, Slider, Stack, TextField, Toggle } from "@fluentui/react"; import { Label, Slider, Stack, TextField, Toggle } from "@fluentui/react";
import { ThroughputBucket } from "Contracts/DataModels"; import { ThroughputBucket } from "Contracts/DataModels";
import { Keys } from "../../../../../Localization/Keys.generated";
import { t } from "../../../../../Localization/t";
import React, { FC, useEffect, useState } from "react"; import React, { FC, useEffect, useState } from "react";
import { isDirty } from "../../SettingsUtils"; import { isDirty } from "../../SettingsUtils";
@@ -65,7 +67,9 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
return ( return (
<Stack tokens={{ childrenGap: "m" }} styles={{ root: { width: "70%", maxWidth: 700 } }}> <Stack tokens={{ childrenGap: "m" }} styles={{ root: { width: "70%", maxWidth: 700 } }}>
<Label styles={{ root: { color: "var(--colorNeutralForeground1)" } }}>Throughput Buckets</Label> <Label styles={{ root: { color: "var(--colorNeutralForeground1)" } }}>
{t(Keys.controls.settings.throughputBuckets.label)}
</Label>
<Stack> <Stack>
{throughputBuckets?.map((bucket) => ( {throughputBuckets?.map((bucket) => (
<Stack key={bucket.id} horizontal tokens={{ childrenGap: 8 }} verticalAlign="center"> <Stack key={bucket.id} horizontal tokens={{ childrenGap: 8 }} verticalAlign="center">
@@ -76,7 +80,9 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
value={bucket.maxThroughputPercentage} value={bucket.maxThroughputPercentage}
onChange={(newValue) => handleBucketChange(bucket.id, newValue)} onChange={(newValue) => handleBucketChange(bucket.id, newValue)}
showValue={false} showValue={false}
label={`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={{ styles={{
root: { flex: 2, maxWidth: 400 }, root: { flex: 2, maxWidth: 400 },
titleLabel: { titleLabel: {
@@ -99,8 +105,8 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
disabled={bucket.maxThroughputPercentage === 100} disabled={bucket.maxThroughputPercentage === 100}
/> />
<Toggle <Toggle
onText="Active" onText={t(Keys.controls.settings.throughputBuckets.active)}
offText="Inactive" offText={t(Keys.controls.settings.throughputBuckets.inactive)}
checked={bucket.maxThroughputPercentage !== 100} checked={bucket.maxThroughputPercentage !== 100}
onChange={(event, checked) => onToggle(bucket.id, checked)} onChange={(event, checked) => onToggle(bucket.id, checked)}
styles={{ styles={{

View File

@@ -18,6 +18,8 @@ import {
} from "@fluentui/react"; } from "@fluentui/react";
import React from "react"; import React from "react";
import * as DataModels from "../../../../../Contracts/DataModels"; import * as DataModels from "../../../../../Contracts/DataModels";
import { Keys } from "../../../../../Localization/Keys.generated";
import { t } from "../../../../../Localization/t";
import * as SharedConstants from "../../../../../Shared/Constants"; import * as SharedConstants from "../../../../../Shared/Constants";
import { Action, ActionModifiers } from "../../../../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../../../../Shared/Telemetry/TelemetryProcessor";
@@ -97,8 +99,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
private throughputInputMaxValue: number; private throughputInputMaxValue: number;
private autoPilotInputMaxValue: number; private autoPilotInputMaxValue: number;
private options: IChoiceGroupOption[] = [ private options: IChoiceGroupOption[] = [
{ key: "true", text: "Autoscale" }, { key: "true", text: t(Keys.controls.settings.throughputInput.autoscale) },
{ key: "false", text: "Manual" }, { key: "false", text: t(Keys.controls.settings.throughputInput.manual) },
]; ];
// Style constants for theme-aware colors and layout // Style constants for theme-aware colors and layout
@@ -244,7 +246,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
return ( return (
<div> <div>
<Text style={{ fontWeight: 600, color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY }}> <Text style={{ fontWeight: 600, color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY }}>
Updated cost per month {t(Keys.controls.settings.costEstimate.updatedCostPerMonth)}
</Text> </Text>
<Stack horizontal style={{ marginTop: 5, marginBottom: 10 }}> <Stack horizontal style={{ marginTop: 5, marginBottom: 10 }}>
<Text <Text
@@ -253,7 +255,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY, 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>
<Text <Text
style={{ style={{
@@ -261,7 +264,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY, color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY,
}} }}
> >
{newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice)} max {newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice)}{" "}
{t(Keys.controls.settings.throughputInput.max)}
</Text> </Text>
</Stack> </Stack>
</div> </div>
@@ -274,7 +278,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
<Stack {...checkBoxAndInputStackProps} style={{ marginTop: 15 }}> <Stack {...checkBoxAndInputStackProps} style={{ marginTop: 15 }}>
{newThroughput && newThroughputCostElement()} {newThroughput && newThroughputCostElement()}
<Text style={{ fontWeight: 600, color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY }}> <Text style={{ fontWeight: 600, color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY }}>
Current cost per month {t(Keys.controls.settings.costEstimate.currentCostPerMonth)}
</Text> </Text>
<Stack horizontal style={{ marginTop: 5, color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY }}> <Stack horizontal style={{ marginTop: 5, color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY }}>
<Text <Text
@@ -283,7 +287,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY, 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>
<Text <Text
style={{ style={{
@@ -291,7 +296,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY, color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY,
}} }}
> >
{prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice)} max {prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice)}{" "}
{t(Keys.controls.settings.throughputInput.max)}
</Text> </Text>
</Stack> </Stack>
</Stack> </Stack>
@@ -326,7 +332,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
return ( return (
<div> <div>
<Text style={{ fontWeight: 600, color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY }}> <Text style={{ fontWeight: 600, color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY }}>
Updated cost per month {t(Keys.controls.settings.costEstimate.updatedCostPerMonth)}
</Text> </Text>
<Stack horizontal style={{ marginTop: 5, marginBottom: 10 }}> <Stack horizontal style={{ marginTop: 5, marginBottom: 10 }}>
<Text style={this.settingsAndScaleStyle.root}> <Text style={this.settingsAndScaleStyle.root}>
@@ -349,7 +355,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
<Stack {...checkBoxAndInputStackProps} style={{ marginTop: 15 }}> <Stack {...checkBoxAndInputStackProps} style={{ marginTop: 15 }}>
{newThroughput && newThroughputCostElement()} {newThroughput && newThroughputCostElement()}
<Text style={{ fontWeight: 600, color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY }}> <Text style={{ fontWeight: 600, color: ThroughputInputAutoPilotV3Component.TEXT_COLOR_PRIMARY }}>
Current cost per month {t(Keys.controls.settings.costEstimate.currentCostPerMonth)}
</Text> </Text>
<Stack horizontal style={{ marginTop: 5 }}> <Stack horizontal style={{ marginTop: 5 }}>
<Text style={this.settingsAndScaleStyle.root}> <Text style={this.settingsAndScaleStyle.root}>
@@ -444,10 +450,14 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
this.setState({ spendAckChecked: checked }); this.setState({ spendAckChecked: checked });
private getStorageCapacityTitle = (): JSX.Element => { 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 ( return (
<Stack {...titleAndInputStackProps}> <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> <Text style={{ color: "var(--colorNeutralForeground1)" }}>{capacity}</Text>
</Stack> </Stack>
); );
@@ -558,10 +568,14 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
/> />
<Stack horizontal> <Stack horizontal>
<Stack.Item style={{ width: "34%", paddingRight: "5px" }}> <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>
<Stack.Item style={{ width: "66%", paddingLeft: "5px" }}> <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.Item>
</Stack> </Stack>
</Stack> </Stack>
@@ -641,7 +655,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
variant="small" variant="small"
style={{ lineHeight: "20px", fontWeight: 600, color: "var(--colorNeutralForeground1)" }} style={{ lineHeight: "20px", fontWeight: 600, color: "var(--colorNeutralForeground1)" }}
> >
Minimum RU/s {t(Keys.controls.settings.throughputInput.minimumRuS)}
</Text> </Text>
<FontIcon iconName="Info" style={{ fontSize: 12, color: "var(--colorNeutralForeground2)" }} /> <FontIcon iconName="Info" style={{ fontSize: 12, color: "var(--colorNeutralForeground2)" }} />
</Stack> </Stack>
@@ -675,7 +689,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
color: "var(--colorNeutralForeground1)", color: "var(--colorNeutralForeground1)",
}} }}
> >
x 10 = {t(Keys.controls.settings.throughputInput.x10Equals)}
</Text> </Text>
{/* Column 3: Maximum RU/s */} {/* Column 3: Maximum RU/s */}
@@ -685,7 +699,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
variant="small" variant="small"
style={{ lineHeight: "20px", fontWeight: 600, color: "var(--colorNeutralForeground1)" }} style={{ lineHeight: "20px", fontWeight: 600, color: "var(--colorNeutralForeground1)" }}
> >
Maximum RU/s {t(Keys.controls.settings.throughputInput.maximumRuS)}
</Text> </Text>
<FontIcon iconName="Info" style={{ fontSize: 12, color: "var(--colorNeutralForeground2)" }} /> <FontIcon iconName="Info" style={{ fontSize: 12, color: "var(--colorNeutralForeground2)" }} />
</Stack> </Stack>
@@ -726,7 +740,9 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
onGetErrorMessage={(value: string) => { onGetErrorMessage={(value: string) => {
const sanitizedValue = getSanitizedInputValue(value); const sanitizedValue = getSanitizedInputValue(value);
const errorMessage: string = 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>; return <span data-test="autopilot-throughput-input-error">{errorMessage}</span>;
}} }}
validateOnLoad={false} validateOnLoad={false}
@@ -772,7 +788,9 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
)} )}
{this.props.isAutoPilotSelected ? ( {this.props.isAutoPilotSelected ? (
<Text style={{ marginTop: "40px", color: "var(--colorNeutralForeground1)" }}> <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> <b>
{AutoPilotUtils.getMinRUsBasedOnUserInput(this.props.maxAutoPilotThroughput)} RU/s (10% of max RU/s) -{" "} {AutoPilotUtils.getMinRUsBasedOnUserInput(this.props.maxAutoPilotThroughput)} RU/s (10% of max RU/s) -{" "}
{this.props.maxAutoPilotThroughput} RU/s {this.props.maxAutoPilotThroughput} RU/s
@@ -787,16 +805,19 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
styles={this.darkThemeMessageBarStyles} styles={this.darkThemeMessageBarStyles}
style={{ marginTop: "40px" }} 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> </MessageBar>
)} )}
</> </>
)} )}
{!this.overrideWithProvisionedThroughputSettings() && ( {!this.overrideWithProvisionedThroughputSettings() && (
<Text style={{ color: "var(--colorNeutralForeground1)" }}> <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/"> <Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/">
{` capacity calculator`} <FontIcon iconName="NavigateExternalInline" /> {t(Keys.controls.settings.throughputInput.capacityCalculatorLink)}{" "}
<FontIcon iconName="NavigateExternalInline" />
</Link> </Link>
</Text> </Text>
)} )}
@@ -809,9 +830,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
onChange={this.onSpendAckChecked} onChange={this.onSpendAckChecked}
/> />
)} )}
{this.props.isFixed && ( {this.props.isFixed && <p>{t(Keys.controls.settings.throughputInput.fixedStorageNote)}</p>}
<p>When using a collection with fixed storage capacity, you can set up to 10,000 RU/s.</p>
)}
{this.props.collectionName && ( {this.props.collectionName && (
<Stack.Item style={{ marginTop: "40px" }}>{this.getStorageCapacityTitle()}</Stack.Item> <Stack.Item style={{ marginTop: "40px" }}>{this.getStorageCapacityTitle()}</Stack.Item>
)} )}

View File

@@ -561,9 +561,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
} }
} }
> >
You are not able to lower throughput below your current minimum of 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.
10000
RU/s. For more information on this limit, please refer to our service quote documentation.
<StyledLinkBase <StyledLinkBase
href="https://learn.microsoft.com/en-us/azure/cosmos-db/concepts-limits#minimum-throughput-limits" href="https://learn.microsoft.com/en-us/azure/cosmos-db/concepts-limits#minimum-throughput-limits"
target="_blank" target="_blank"
@@ -581,9 +579,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
} }
} }
> >
Based on usage, your Based on usage, your container throughput will scale from
container
throughput will scale from
<b> <b>
400 400
@@ -692,7 +688,8 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
$ $
35.04 35.04
min
min
</Text> </Text>
<Text <Text
style={ style={
@@ -705,7 +702,8 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
$ $
350.40 350.40
max
max
</Text> </Text>
</Stack> </Stack>
</div> </div>
@@ -739,7 +737,8 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
$ $
35.04 35.04
min
min
</Text> </Text>
<Text <Text
style={ style={
@@ -752,7 +751,8 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
$ $
350.40 350.40
max
max
</Text> </Text>
</Stack> </Stack>
</Stack> </Stack>
@@ -1169,9 +1169,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
} }
} }
> >
You are not able to lower throughput below your current minimum of 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.
10000
RU/s. For more information on this limit, please refer to our service quote documentation.
<StyledLinkBase <StyledLinkBase
href="https://learn.microsoft.com/en-us/azure/cosmos-db/concepts-limits#minimum-throughput-limits" href="https://learn.microsoft.com/en-us/azure/cosmos-db/concepts-limits#minimum-throughput-limits"
target="_blank" target="_blank"
@@ -1755,9 +1753,7 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
} }
} }
> >
You are not able to lower throughput below your current minimum of 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.
10000
RU/s. For more information on this limit, please refer to our service quote documentation.
<StyledLinkBase <StyledLinkBase
href="https://learn.microsoft.com/en-us/azure/cosmos-db/concepts-limits#minimum-throughput-limits" href="https://learn.microsoft.com/en-us/azure/cosmos-db/concepts-limits#minimum-throughput-limits"
target="_blank" target="_blank"

View File

@@ -27,7 +27,8 @@ exports[`ComputedPropertiesComponent renders 1`] = `
iconName="NavigateExternalInline" iconName="NavigateExternalInline"
/> />
</StyledLinkBase> </StyledLinkBase>
  about how to define computed properties and how to use them.  
about how to define computed properties and how to use them.
</Text> </Text>
<div <div
className="settingsV2Editor" className="settingsV2Editor"

View File

@@ -33,8 +33,7 @@ exports[`PartitionKeyComponent renders default component and matches snapshot 1`
} }
} }
> >
Change Change partition key
partition key
</Text> </Text>
<Stack <Stack
horizontal={true} horizontal={true}
@@ -61,8 +60,7 @@ exports[`PartitionKeyComponent renders default component and matches snapshot 1`
} }
} }
> >
Current Current partition key
partition key
</Text> </Text>
<Text <Text
styles={ styles={
@@ -223,8 +221,7 @@ exports[`PartitionKeyComponent renders read-only component and matches snapshot
} }
} }
> >
Current Current partition key
partition key
</Text> </Text>
<Text <Text
styles={ styles={

View File

@@ -410,9 +410,7 @@ exports[`SubSettingsComponent analyticalTimeToLive hidden 1`] = `
<Text <Text
className="hintText-115" className="hintText-115"
> >
Large Large partition key has been enabled.
partition key
has been enabled.
</Text> </Text>
<Text <Text
className="hintText-115" className="hintText-115"
@@ -984,9 +982,7 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
<Text <Text
className="hintText-115" className="hintText-115"
> >
Large Large partition key has been enabled.
partition key
has been enabled.
</Text> </Text>
<Text <Text
className="hintText-115" className="hintText-115"
@@ -1522,9 +1518,7 @@ exports[`SubSettingsComponent changeFeedPolicy hidden 1`] = `
<Text <Text
className="hintText-115" className="hintText-115"
> >
Large Large partition key has been enabled.
partition key
has been enabled.
</Text> </Text>
<Text <Text
className="hintText-115" className="hintText-115"
@@ -2157,9 +2151,7 @@ exports[`SubSettingsComponent renders 1`] = `
<Text <Text
className="hintText-115" className="hintText-115"
> >
Large Large partition key has been enabled.
partition key
has been enabled.
</Text> </Text>
<Text <Text
className="hintText-115" className="hintText-115"
@@ -2729,9 +2721,7 @@ exports[`SubSettingsComponent timeToLiveSeconds hidden 1`] = `
<Text <Text
className="hintText-115" className="hintText-115"
> >
Large Large partition key has been enabled.
partition key
has been enabled.
</Text> </Text>
<Text <Text
className="hintText-115" className="hintText-115"

View File

@@ -1,6 +1,8 @@
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";
import * as DataModels from "../../../Contracts/DataModels"; import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { Keys } from "../../../Localization/Keys.generated";
import { t } from "../../../Localization/t";
import { isFabricNative } from "../../../Platform/Fabric/FabricUtil"; import { isFabricNative } from "../../../Platform/Fabric/FabricUtil";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
import { isCapabilityEnabled } from "../../../Utils/CapabilityUtils"; import { isCapabilityEnabled } from "../../../Utils/CapabilityUtils";
@@ -175,25 +177,27 @@ const getStringValue = (value: isDirtyTypes, type: string): string => {
export const getTabTitle = (tab: SettingsV2TabTypes): string => { export const getTabTitle = (tab: SettingsV2TabTypes): string => {
switch (tab) { switch (tab) {
case SettingsV2TabTypes.ScaleTab: case SettingsV2TabTypes.ScaleTab:
return "Scale"; return t(Keys.controls.settings.tabTitles.scale);
case SettingsV2TabTypes.ConflictResolutionTab: case SettingsV2TabTypes.ConflictResolutionTab:
return "Conflict Resolution"; return t(Keys.controls.settings.tabTitles.conflictResolution);
case SettingsV2TabTypes.SubSettingsTab: case SettingsV2TabTypes.SubSettingsTab:
return "Settings"; return t(Keys.controls.settings.tabTitles.settings);
case SettingsV2TabTypes.IndexingPolicyTab: case SettingsV2TabTypes.IndexingPolicyTab:
return "Indexing Policy"; return t(Keys.controls.settings.tabTitles.indexingPolicy);
case SettingsV2TabTypes.PartitionKeyTab: 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: case SettingsV2TabTypes.ComputedPropertiesTab:
return "Computed Properties"; return t(Keys.controls.settings.tabTitles.computedProperties);
case SettingsV2TabTypes.ContainerVectorPolicyTab: case SettingsV2TabTypes.ContainerVectorPolicyTab:
return "Container Policies"; return t(Keys.controls.settings.tabTitles.containerPolicies);
case SettingsV2TabTypes.ThroughputBucketsTab: case SettingsV2TabTypes.ThroughputBucketsTab:
return "Throughput Buckets"; return t(Keys.controls.settings.tabTitles.throughputBuckets);
case SettingsV2TabTypes.GlobalSecondaryIndexTab: case SettingsV2TabTypes.GlobalSecondaryIndexTab:
return "Global Secondary Index (Preview)"; return t(Keys.controls.settings.tabTitles.globalSecondaryIndexPreview);
case SettingsV2TabTypes.DataMaskingTab: case SettingsV2TabTypes.DataMaskingTab:
return "Masking Policy (preview)"; return t(Keys.controls.settings.tabTitles.maskingPolicyPreview);
default: default:
throw new Error(`Unknown tab ${tab}`); throw new Error(`Unknown tab ${tab}`);
} }
@@ -203,19 +207,19 @@ export const getMongoNotification = (description: string, type: MongoIndexTypes)
if (description && !type) { if (description && !type) {
return { return {
type: MongoNotificationType.Warning, 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)) { if (type && (!description || description.trim().length === 0)) {
return { return {
type: MongoNotificationType.Error, type: MongoNotificationType.Error,
message: "Please enter a field name.", message: t(Keys.controls.settings.mongoNotifications.enterFieldNameError),
}; };
} else if (type === MongoIndexTypes.Wildcard && description?.indexOf("$**") === -1) { } else if (type === MongoIndexTypes.Wildcard && description?.indexOf("$**") === -1) {
return { return {
type: MongoNotificationType.Error, 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; indexTransformationProgress !== undefined && indexTransformationProgress !== 100;
export const getPartitionKeyName = (apiType: string, isLowerCase?: boolean): string => { 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; return isLowerCase ? partitionKeyName.toLocaleLowerCase() : partitionKeyName;
}; };
export const getPartitionKeyTooltipText = (apiType: string): string => { export const getPartitionKeyTooltipText = (apiType: string): string => {
if (apiType === "Mongo") { if (apiType === "Mongo") {
return "The shard key (field) is used to split your data across many replica sets (shards) to achieve unlimited scalability. Its critical to choose a field that will evenly distribute your data."; return t(Keys.controls.settings.partitionKey.shardKeyTooltip);
} }
let tooltipText = `The ${getPartitionKeyName( let tooltipText = `The ${getPartitionKeyName(apiType, true)} ${t(
apiType, Keys.controls.settings.partitionKey.partitionKeyTooltip,
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.`;
if (apiType === "SQL") { 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; return tooltipText;
}; };
export const getPartitionKeySubtext = (partitionKeyDefault: boolean, apiType: string): string => { export const getPartitionKeySubtext = (partitionKeyDefault: boolean, apiType: string): string => {
if (partitionKeyDefault && (apiType === "SQL" || apiType === "Mongo")) { if (partitionKeyDefault && (apiType === "SQL" || apiType === "Mongo")) {
const subtext = "For small workloads, the item ID is a suitable choice for the partition key."; return t(Keys.controls.settings.partitionKey.partitionKeySubtext);
return subtext;
} }
return ""; return "";
}; };
@@ -278,18 +283,18 @@ export const getPartitionKeySubtext = (partitionKeyDefault: boolean, apiType: st
export const getPartitionKeyPlaceHolder = (apiType: string, index?: number): string => { export const getPartitionKeyPlaceHolder = (apiType: string, index?: number): string => {
switch (apiType) { switch (apiType) {
case "Mongo": case "Mongo":
return "e.g., categoryId"; return t(Keys.controls.settings.partitionKey.mongoPlaceholder);
case "Gremlin": case "Gremlin":
return "e.g., /address"; return t(Keys.controls.settings.partitionKey.gremlinPlaceholder);
case "SQL": case "SQL":
return `${ return `${
index === undefined index === undefined
? "Required - first partition key e.g., /TenantId" ? t(Keys.controls.settings.partitionKey.sqlFirstPartitionKey)
: index === 0 : index === 0
? "second partition key e.g., /UserId" ? t(Keys.controls.settings.partitionKey.sqlSecondPartitionKey)
: "third partition key e.g., /SessionId" : t(Keys.controls.settings.partitionKey.sqlThirdPartitionKey)
}`; }`;
default: default:
return "e.g., /address/zipCode"; return t(Keys.controls.settings.partitionKey.defaultPlaceholder);
} }
}; };

View File

@@ -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. 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 /> <br />
Database: Database:
sampleDb sampleDb
, Container: ,
Container:
sampleCollection sampleCollection
, Current manual throughput: 1000 RU/s, Target manual throughput: 2000 , 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 <StyledLinkBase
onClick={[Function]} onClick={[Function]}
> >

View File

@@ -723,5 +723,216 @@
"information": "Information", "information": "Information",
"moreDetails": "More details" "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"
}
} }
} }