mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-03-06 16:25:11 +00:00
Compare commits
1 Commits
loca_batch
...
users/aisa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d41990e13 |
@@ -42,8 +42,6 @@ import {
|
||||
} from "Explorer/Panes/AddCollectionPanel/AddCollectionPanelUtility";
|
||||
import { useSidePanel } from "hooks/useSidePanel";
|
||||
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
||||
import { Keys } from "Localization/Keys.generated";
|
||||
import { t } from "Localization/t";
|
||||
import { DEFAULT_FABRIC_NATIVE_CONTAINER_THROUGHPUT, isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||
import React from "react";
|
||||
import { CollectionCreation } from "Shared/Constants";
|
||||
@@ -179,7 +177,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
messageType="info"
|
||||
showErrorDetails={false}
|
||||
link={Constants.Urls.freeTierInformation}
|
||||
linkText={t(Keys.common.learnMore)}
|
||||
linkText="Learn more"
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -276,17 +274,17 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
</Text>
|
||||
<TooltipHost
|
||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||
content={t(Keys.panes.addCollection.databaseTooltip, {
|
||||
collectionName: getCollectionName(true).toLocaleLowerCase(),
|
||||
})}
|
||||
content={`A database is analogous to a namespace. It is the unit of management for a set of ${getCollectionName(
|
||||
true,
|
||||
).toLocaleLowerCase()}.`}
|
||||
>
|
||||
<Icon
|
||||
iconName="Info"
|
||||
className="panelInfoIcon"
|
||||
tabIndex={0}
|
||||
ariaLabel={t(Keys.panes.addCollection.databaseTooltip, {
|
||||
collectionName: getCollectionName(true).toLocaleLowerCase(),
|
||||
})}
|
||||
ariaLabel={`A database is analogous to a namespace. It is the unit of management for a set of ${getCollectionName(
|
||||
true,
|
||||
).toLocaleLowerCase()}.`}
|
||||
/>
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
@@ -306,7 +304,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
tabIndex={0}
|
||||
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
|
||||
/>
|
||||
<span className="panelRadioBtnLabel">{t(Keys.panes.addCollection.createNew)}</span>
|
||||
<span className="panelRadioBtnLabel">Create new</span>
|
||||
|
||||
<input
|
||||
className="panelRadioBtn"
|
||||
@@ -319,7 +317,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
tabIndex={0}
|
||||
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
|
||||
/>
|
||||
<span className="panelRadioBtnLabel">{t(Keys.panes.addCollection.useExisting)}</span>
|
||||
<span className="panelRadioBtnLabel">Use existing</span>
|
||||
</div>
|
||||
</Stack>
|
||||
)}
|
||||
@@ -349,9 +347,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
{!isServerlessAccount() && (
|
||||
<Stack horizontal>
|
||||
<Checkbox
|
||||
label={t(Keys.panes.addCollection.shareThroughput, {
|
||||
collectionName: getCollectionName(true).toLocaleLowerCase(),
|
||||
})}
|
||||
label={`Share throughput across ${getCollectionName(true).toLocaleLowerCase()}`}
|
||||
checked={this.state.isSharedThroughputChecked}
|
||||
styles={{
|
||||
text: { fontSize: 12, color: "var(--colorNeutralForeground1)" },
|
||||
@@ -369,17 +365,17 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
/>
|
||||
<TooltipHost
|
||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||
content={t(Keys.panes.addCollection.shareThroughputTooltip, {
|
||||
collectionName: getCollectionName(true).toLocaleLowerCase(),
|
||||
})}
|
||||
content={`Throughput configured at the database level will be shared across all ${getCollectionName(
|
||||
true,
|
||||
).toLocaleLowerCase()} within the database.`}
|
||||
>
|
||||
<Icon
|
||||
iconName="Info"
|
||||
className="panelInfoIcon"
|
||||
tabIndex={0}
|
||||
ariaLabel={t(Keys.panes.addCollection.shareThroughputTooltip, {
|
||||
collectionName: getCollectionName(true).toLocaleLowerCase(),
|
||||
})}
|
||||
ariaLabel={`Throughput configured at the database level will be shared across all ${getCollectionName(
|
||||
true,
|
||||
).toLocaleLowerCase()} within the database.`}
|
||||
/>
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
@@ -428,18 +424,14 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
</Text>
|
||||
<TooltipHost
|
||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||
content={t(Keys.panes.addCollection.collectionIdTooltip, {
|
||||
collectionName: getCollectionName().toLocaleLowerCase(),
|
||||
})}
|
||||
content={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
|
||||
>
|
||||
<Icon
|
||||
role="button"
|
||||
iconName="Info"
|
||||
className="panelInfoIcon"
|
||||
tabIndex={0}
|
||||
ariaLabel={t(Keys.panes.addCollection.collectionIdTooltip, {
|
||||
collectionName: getCollectionName().toLocaleLowerCase(),
|
||||
})}
|
||||
ariaLabel={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
|
||||
/>
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
@@ -453,10 +445,10 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
autoComplete="off"
|
||||
pattern={ValidCosmosDbIdInputPattern.source}
|
||||
title={ValidCosmosDbIdDescription}
|
||||
placeholder={t(Keys.panes.addCollection.collectionIdPlaceholder, { collectionName: getCollectionName() })}
|
||||
placeholder={`e.g., ${getCollectionName()}1`}
|
||||
size={40}
|
||||
className="panelTextField"
|
||||
aria-label={t(Keys.panes.addCollection.collectionIdAriaLabel, { collectionName: getCollectionName() })}
|
||||
aria-label={`${getCollectionName()} id, Example ${getCollectionName()}1`}
|
||||
value={this.state.collectionId}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
this.setState({ collectionId: event.target.value })
|
||||
@@ -470,7 +462,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
<Stack horizontal style={{ marginTop: -4, marginBottom: -5 }}>
|
||||
<span className="mandatoryStar">* </span>
|
||||
<Text className="panelTextBold" variant="small">
|
||||
{t(Keys.panes.addCollection.indexing)}
|
||||
Indexing
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
@@ -478,32 +470,32 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
<input
|
||||
className="panelRadioBtn"
|
||||
checked={this.state.enableIndexing}
|
||||
aria-label={t(Keys.panes.addCollection.turnOnIndexing)}
|
||||
aria-label="Turn on indexing"
|
||||
aria-checked={this.state.enableIndexing}
|
||||
type="radio"
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
onChange={this.onTurnOnIndexing.bind(this)}
|
||||
/>
|
||||
<span className="panelRadioBtnLabel">{t(Keys.panes.addCollection.automatic)}</span>
|
||||
<span className="panelRadioBtnLabel">Automatic</span>
|
||||
|
||||
<input
|
||||
className="panelRadioBtn"
|
||||
checked={!this.state.enableIndexing}
|
||||
aria-label={t(Keys.panes.addCollection.turnOffIndexing)}
|
||||
aria-label="Turn off indexing"
|
||||
aria-checked={!this.state.enableIndexing}
|
||||
type="radio"
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
onChange={this.onTurnOffIndexing.bind(this)}
|
||||
/>
|
||||
<span className="panelRadioBtnLabel">{t(Keys.panes.addCollection.off)}</span>
|
||||
<span className="panelRadioBtnLabel">Off</span>
|
||||
</Stack>
|
||||
|
||||
<Text variant="small">
|
||||
{this.getFreeTierIndexingText()}{" "}
|
||||
<Link target="_blank" href="https://aka.ms/cosmos-indexing-policy">
|
||||
{t(Keys.common.learnMore)}
|
||||
Learn more
|
||||
</Link>
|
||||
</Text>
|
||||
</Stack>
|
||||
@@ -516,17 +508,21 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
<Stack horizontal style={{ marginTop: -5, marginBottom: -4 }}>
|
||||
<span className="mandatoryStar">* </span>
|
||||
<Text className="panelTextBold" variant="small">
|
||||
{t(Keys.panes.addCollection.sharding)}
|
||||
Sharding
|
||||
</Text>
|
||||
<TooltipHost
|
||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||
content={t(Keys.panes.addCollection.shardingTooltip)}
|
||||
content={
|
||||
"Sharded collections split your data across many replica sets (shards) to achieve unlimited scalability. Sharded collections require choosing a shard key (field) to evenly distribute your data."
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
iconName="Info"
|
||||
className="panelInfoIcon"
|
||||
tabIndex={0}
|
||||
ariaLabel={t(Keys.panes.addCollection.shardingTooltip)}
|
||||
ariaLabel={
|
||||
"Sharded collections split your data across many replica sets (shards) to achieve unlimited scalability. Sharded collections require choosing a shard key (field) to evenly distribute your data."
|
||||
}
|
||||
/>
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
@@ -535,7 +531,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
<input
|
||||
className="panelRadioBtn"
|
||||
checked={!this.state.isSharded}
|
||||
aria-label={t(Keys.panes.addCollection.unsharded)}
|
||||
aria-label="Unsharded"
|
||||
aria-checked={!this.state.isSharded}
|
||||
name="unsharded"
|
||||
type="radio"
|
||||
@@ -544,12 +540,12 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
tabIndex={0}
|
||||
onChange={this.onUnshardedRadioBtnChange.bind(this)}
|
||||
/>
|
||||
<span className="panelRadioBtnLabel">{t(Keys.panes.addCollection.unshardedLabel)}</span>
|
||||
<span className="panelRadioBtnLabel">Unsharded (20GB limit)</span>
|
||||
|
||||
<input
|
||||
className="panelRadioBtn"
|
||||
checked={this.state.isSharded}
|
||||
aria-label={t(Keys.panes.addCollection.sharded)}
|
||||
aria-label="Sharded"
|
||||
aria-checked={this.state.isSharded}
|
||||
name="sharded"
|
||||
type="radio"
|
||||
@@ -558,7 +554,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
tabIndex={0}
|
||||
onChange={this.onShardedRadioBtnChange.bind(this)}
|
||||
/>
|
||||
<span className="panelRadioBtnLabel">{t(Keys.panes.addCollection.sharded)}</span>
|
||||
<span className="panelRadioBtnLabel">Sharded</span>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)}
|
||||
@@ -683,14 +679,15 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
disabled={this.state.subPartitionKeys.length >= Constants.BackendDefaults.maxNumMultiHashPartition}
|
||||
onClick={() => this.setState({ subPartitionKeys: [...this.state.subPartitionKeys, ""] })}
|
||||
>
|
||||
{t(Keys.panes.addCollection.addPartitionKey)}
|
||||
Add hierarchical partition key
|
||||
</DefaultButton>
|
||||
{this.state.subPartitionKeys.length > 0 && (
|
||||
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||
<Icon iconName="InfoSolid" className="removeIcon" tabIndex={0} />{" "}
|
||||
{t(Keys.panes.addCollection.hierarchicalPartitionKeyInfo)}{" "}
|
||||
<Icon iconName="InfoSolid" className="removeIcon" tabIndex={0} /> This feature allows you to
|
||||
partition your data with up to three levels of keys for better data distribution. Requires .NET
|
||||
V3, Java V4 SDK, or preview JavaScript V3 SDK.{" "}
|
||||
<Link href="https://aka.ms/cosmos-hierarchical-partitioning" target="_blank">
|
||||
{t(Keys.common.learnMore)}
|
||||
Learn more
|
||||
</Link>
|
||||
</Text>
|
||||
)}
|
||||
@@ -703,9 +700,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
{!isServerlessAccount() && !this.state.createNewDatabase && this.isSelectedDatabaseSharedThroughput() && (
|
||||
<Stack horizontal verticalAlign="center">
|
||||
<Checkbox
|
||||
label={t(Keys.panes.addCollection.provisionDedicatedThroughput, {
|
||||
collectionName: getCollectionName().toLocaleLowerCase(),
|
||||
})}
|
||||
label={`Provision dedicated throughput for this ${getCollectionName().toLocaleLowerCase()}`}
|
||||
checked={this.state.enableDedicatedThroughput}
|
||||
styles={{
|
||||
text: { fontSize: 12, color: "var(--colorNeutralForeground1)" },
|
||||
@@ -723,19 +718,23 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
/>
|
||||
<TooltipHost
|
||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||
content={t(Keys.panes.addCollection.provisionDedicatedThroughputTooltip, {
|
||||
collectionName: getCollectionName().toLocaleLowerCase(),
|
||||
collectionNamePlural: getCollectionName(true).toLocaleLowerCase(),
|
||||
})}
|
||||
content={`You can optionally provision dedicated throughput for a ${getCollectionName().toLocaleLowerCase()} within a database that has throughput
|
||||
provisioned. This dedicated throughput amount will not be shared with other ${getCollectionName(
|
||||
true,
|
||||
).toLocaleLowerCase()} in the database and
|
||||
does not count towards the throughput you provisioned for the database. This throughput amount will be
|
||||
billed in addition to the throughput amount you provisioned at the database level.`}
|
||||
>
|
||||
<Icon
|
||||
iconName="Info"
|
||||
className="panelInfoIcon"
|
||||
tabIndex={0}
|
||||
ariaLabel={t(Keys.panes.addCollection.provisionDedicatedThroughputTooltip, {
|
||||
collectionName: getCollectionName().toLocaleLowerCase(),
|
||||
collectionNamePlural: getCollectionName(true).toLocaleLowerCase(),
|
||||
})}
|
||||
ariaLabel={`You can optionally provision dedicated throughput for a ${getCollectionName().toLocaleLowerCase()} within a database that has throughput
|
||||
provisioned. This dedicated throughput amount will not be shared with other ${getCollectionName(
|
||||
true,
|
||||
).toLocaleLowerCase()} in the database and
|
||||
does not count towards the throughput you provisioned for the database. This throughput amount will be
|
||||
billed in addition to the throughput amount you provisioned at the database level.`}
|
||||
/>
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
@@ -770,8 +769,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
autoComplete="off"
|
||||
placeholder={
|
||||
userContext.apiType === "Mongo"
|
||||
? t(Keys.panes.addCollection.uniqueKeysPlaceholderMongo)
|
||||
: t(Keys.panes.addCollection.uniqueKeysPlaceholderSql)
|
||||
? "Comma separated paths e.g. firstName,address.zipCode"
|
||||
: "Comma separated paths e.g. /firstName,/address/zipCode"
|
||||
}
|
||||
className="panelTextField"
|
||||
value={uniqueKey}
|
||||
@@ -803,7 +802,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
styles={{ root: { padding: 0 }, label: { fontSize: 12, color: "var(--colorNeutralForeground1)" } }}
|
||||
onClick={() => this.setState({ uniqueKeys: [...this.state.uniqueKeys, ""] })}
|
||||
>
|
||||
{t(Keys.panes.addCollection.addUniqueKey)}
|
||||
Add unique key
|
||||
</ActionButton>
|
||||
</Stack>
|
||||
)}
|
||||
@@ -824,7 +823,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
className="panelRadioBtn"
|
||||
checked={this.state.enableAnalyticalStore}
|
||||
disabled={!isSynapseLinkEnabled()}
|
||||
aria-label={t(Keys.panes.addCollection.enableAnalyticalStore)}
|
||||
aria-label="Enable analytical store"
|
||||
aria-checked={this.state.enableAnalyticalStore}
|
||||
name="analyticalStore"
|
||||
type="radio"
|
||||
@@ -833,13 +832,13 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
tabIndex={0}
|
||||
onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)}
|
||||
/>
|
||||
<span className="panelRadioBtnLabel">{t(Keys.panes.addCollection.on)}</span>
|
||||
<span className="panelRadioBtnLabel">On</span>
|
||||
|
||||
<input
|
||||
className="panelRadioBtn"
|
||||
checked={!this.state.enableAnalyticalStore}
|
||||
disabled={!isSynapseLinkEnabled()}
|
||||
aria-label={t(Keys.panes.addCollection.disableAnalyticalStore)}
|
||||
aria-label="Disable analytical store"
|
||||
aria-checked={!this.state.enableAnalyticalStore}
|
||||
name="analyticalStore"
|
||||
type="radio"
|
||||
@@ -848,28 +847,26 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
tabIndex={0}
|
||||
onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)}
|
||||
/>
|
||||
<span className="panelRadioBtnLabel">{t(Keys.panes.addCollection.off)}</span>
|
||||
<span className="panelRadioBtnLabel">Off</span>
|
||||
</div>
|
||||
</Stack>
|
||||
|
||||
{!isSynapseLinkEnabled() && (
|
||||
<Stack className="panelGroupSpacing">
|
||||
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||
{t(Keys.panes.addCollection.analyticalStoreSynapseLinkRequired, {
|
||||
collectionName: getCollectionName().toLocaleLowerCase(),
|
||||
})}{" "}
|
||||
<br />
|
||||
Azure Synapse Link is required for creating an analytical store{" "}
|
||||
{getCollectionName().toLocaleLowerCase()}. Enable Synapse Link for this Cosmos DB account. <br />
|
||||
<Link
|
||||
href="https://aka.ms/cosmosdb-synapselink"
|
||||
target="_blank"
|
||||
aria-label={Constants.ariaLabelForLearnMoreLink.AzureSynapseLink}
|
||||
className="capacitycalculator-link"
|
||||
>
|
||||
{t(Keys.common.learnMore)}
|
||||
Learn more
|
||||
</Link>
|
||||
</Text>
|
||||
<DefaultButton
|
||||
text={t(Keys.panes.addCollection.enable)}
|
||||
text="Enable"
|
||||
onClick={() => this.props.explorer.openEnableSynapseLinkDialog()}
|
||||
style={{ height: 27, width: 80 }}
|
||||
styles={{ label: { fontSize: 12 } }}
|
||||
@@ -881,7 +878,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
{this.shouldShowVectorSearchParameters() && (
|
||||
<Stack>
|
||||
<CollapsibleSectionComponent
|
||||
title={t(Keys.panes.addCollection.containerVectorPolicy)}
|
||||
title="Container Vector Policy"
|
||||
isExpandedByDefault={false}
|
||||
onExpand={() => {
|
||||
scrollToSection("collapsibleVectorPolicySectionContent");
|
||||
@@ -909,7 +906,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
{this.shouldShowFullTextSearchParameters() && (
|
||||
<Stack>
|
||||
<CollapsibleSectionComponent
|
||||
title={t(Keys.panes.addCollection.containerFullTextSearchPolicy)}
|
||||
title="Container Full Text Search Policy"
|
||||
isExpandedByDefault={false}
|
||||
onExpand={() => {
|
||||
scrollToSection("collapsibleFullTextPolicySectionContent");
|
||||
@@ -938,7 +935,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
)}
|
||||
{!isFabricNative() && userContext.apiType !== "Tables" && (
|
||||
<CollapsibleSectionComponent
|
||||
title={t(Keys.panes.addCollection.advanced)}
|
||||
title="Advanced"
|
||||
isExpandedByDefault={false}
|
||||
onExpand={() => {
|
||||
TelemetryProcessor.traceOpen(Action.ExpandAddCollectionPaneAdvancedSection);
|
||||
@@ -951,23 +948,23 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
<Stack horizontal>
|
||||
<span className="mandatoryStar">* </span>
|
||||
<Text className="panelTextBold" variant="small">
|
||||
{t(Keys.panes.addCollection.indexing)}
|
||||
Indexing
|
||||
</Text>
|
||||
<TooltipHost
|
||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||
content={t(Keys.panes.addCollection.mongoIndexingTooltip)}
|
||||
content="The _id field is indexed by default. Creating a wildcard index for all fields will optimize queries and is recommended for development."
|
||||
>
|
||||
<Icon
|
||||
iconName="Info"
|
||||
className="panelInfoIcon"
|
||||
tabIndex={0}
|
||||
ariaLabel={t(Keys.panes.addCollection.mongoIndexingTooltip)}
|
||||
ariaLabel="The _id field is indexed by default. Creating a wildcard index for all fields will optimize queries and is recommended for development."
|
||||
/>
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
|
||||
<Checkbox
|
||||
label={t(Keys.panes.addCollection.createWildcardIndex)}
|
||||
label="Create a Wildcard Index on all fields"
|
||||
checked={this.state.createMongoWildCardIndex}
|
||||
styles={{
|
||||
text: { fontSize: 12, color: "var(--colorNeutralForeground1)" },
|
||||
@@ -989,7 +986,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
{userContext.apiType === "SQL" && (
|
||||
<Stack className="panelGroupSpacing">
|
||||
<Checkbox
|
||||
label={t(Keys.panes.addCollection.legacySdkCheckbox)}
|
||||
label="My application uses an older Cosmos .NET or Java SDK version (.NET V1 or Java V2)"
|
||||
checked={this.state.useHashV1}
|
||||
styles={{
|
||||
text: { fontSize: 12, color: "var(--colorNeutralForeground1)" },
|
||||
@@ -1006,9 +1003,11 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
}
|
||||
/>
|
||||
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||
<Icon iconName="InfoSolid" className="removeIcon" /> {t(Keys.panes.addCollection.legacySdkInfo)}{" "}
|
||||
<Icon iconName="InfoSolid" className="removeIcon" /> To ensure compatibility with older SDKs, the
|
||||
created container will use a legacy partitioning scheme that supports partition key values of size
|
||||
only up to 101 bytes. If this is enabled, you will not be able to use hierarchical partition keys.{" "}
|
||||
<Link href="https://aka.ms/cosmos-large-pk" target="_blank">
|
||||
{t(Keys.common.learnMore)}
|
||||
Learn more
|
||||
</Link>
|
||||
</Text>
|
||||
</Stack>
|
||||
@@ -1019,7 +1018,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
</div>
|
||||
|
||||
{!this.props.isCopyJobFlow && (
|
||||
<PanelFooterComponent buttonLabel={t(Keys.common.ok)} isButtonDisabled={this.state.isThroughputCapExceeded} />
|
||||
<PanelFooterComponent buttonLabel="OK" isButtonDisabled={this.state.isThroughputCapExceeded} />
|
||||
)}
|
||||
|
||||
{this.state.isExecuting && (
|
||||
@@ -1045,7 +1044,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
progressTrack: { backgroundColor: "#A6A6A6" },
|
||||
progressBar: { background: "white" },
|
||||
}}
|
||||
label={t(Keys.panes.addCollection.addingSampleDataSet)}
|
||||
label="Adding sample data set"
|
||||
/>
|
||||
</TeachingBubble>
|
||||
)}
|
||||
@@ -1151,8 +1150,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
|
||||
private getFreeTierIndexingText(): string {
|
||||
return this.state.enableIndexing
|
||||
? t(Keys.panes.addCollection.indexingOnInfo)
|
||||
: t(Keys.panes.addCollection.indexingOffInfo);
|
||||
? "All properties in your documents will be indexed by default for flexible and efficient queries."
|
||||
: "Indexing will be turned off. Recommended if you don't need to run queries or only have key value operations.";
|
||||
}
|
||||
|
||||
private getPartitionKeySubtext(): string {
|
||||
@@ -1250,14 +1249,14 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
const throughput = this.state.createNewDatabase ? this.newDatabaseThroughput : this.collectionThroughput;
|
||||
if (throughput > CollectionCreation.DefaultCollectionRUs100K && !this.isCostAcknowledged) {
|
||||
const errorMessage = this.isNewDatabaseAutoscale
|
||||
? t(Keys.panes.addCollection.acknowledgeSpendErrorMonthly)
|
||||
: t(Keys.panes.addCollection.acknowledgeSpendErrorDaily);
|
||||
? "Please acknowledge the estimated monthly spend."
|
||||
: "Please acknowledge the estimated daily spend.";
|
||||
this.setState({ errorMessage });
|
||||
return false;
|
||||
}
|
||||
|
||||
if (throughput > CollectionCreation.MaxRUPerPartition && !this.state.isSharded) {
|
||||
this.setState({ errorMessage: t(Keys.panes.addCollection.unshardedMaxRuError) });
|
||||
this.setState({ errorMessage: "Unsharded collections support up to 10,000 RUs" });
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1271,12 +1270,12 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
|
||||
if (this.shouldShowVectorSearchParameters()) {
|
||||
if (!this.state.vectorPolicyValidated) {
|
||||
this.setState({ errorMessage: t(Keys.panes.addCollection.vectorPolicyError) });
|
||||
this.setState({ errorMessage: "Please fix errors in container vector policy" });
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.state.fullTextPolicyValidated) {
|
||||
this.setState({ errorMessage: t(Keys.panes.addCollection.fullTextSearchPolicyError) });
|
||||
this.setState({ errorMessage: "Please fix errors in container full text search polilcy" });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,33 +3,28 @@ import * as Constants from "Common/Constants";
|
||||
import { configContext, Platform } from "ConfigContext";
|
||||
import * as DataModels from "Contracts/DataModels";
|
||||
import { getFullTextLanguageOptions } from "Explorer/Controls/FullTextSeach/FullTextPoliciesComponent";
|
||||
import { Keys } from "Localization/Keys.generated";
|
||||
import { t } from "Localization/t";
|
||||
import { isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||
import React from "react";
|
||||
import { userContext } from "UserContext";
|
||||
|
||||
export function getPartitionKeyTooltipText(): string {
|
||||
if (userContext.apiType === "Mongo") {
|
||||
return t(Keys.panes.addCollectionUtility.shardKeyTooltip);
|
||||
return "The shard key (field) is used to split your data across many replica sets (shards) to achieve unlimited scalability. It’s critical to choose a field that will evenly distribute your data.";
|
||||
}
|
||||
|
||||
let tooltipText = t(Keys.panes.addCollectionUtility.partitionKeyTooltip, {
|
||||
partitionKeyName: getPartitionKeyName(true),
|
||||
});
|
||||
let tooltipText = `The ${getPartitionKeyName(
|
||||
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 (userContext.apiType === "SQL") {
|
||||
tooltipText += t(Keys.panes.addCollectionUtility.partitionKeyTooltipSqlSuffix);
|
||||
tooltipText += " For small read-heavy workloads or write-heavy workloads of any size, id is often a good choice.";
|
||||
}
|
||||
|
||||
return tooltipText;
|
||||
}
|
||||
|
||||
export function getPartitionKeyName(isLowerCase?: boolean): string {
|
||||
const partitionKeyName =
|
||||
userContext.apiType === "Mongo"
|
||||
? t(Keys.panes.addCollectionUtility.shardKeyLabel)
|
||||
: t(Keys.panes.addCollectionUtility.partitionKeyLabel);
|
||||
const partitionKeyName = userContext.apiType === "Mongo" ? "Shard key" : "Partition key";
|
||||
|
||||
return isLowerCase ? partitionKeyName.toLocaleLowerCase() : partitionKeyName;
|
||||
}
|
||||
@@ -37,19 +32,19 @@ export function getPartitionKeyName(isLowerCase?: boolean): string {
|
||||
export function getPartitionKeyPlaceHolder(index?: number): string {
|
||||
switch (userContext.apiType) {
|
||||
case "Mongo":
|
||||
return t(Keys.panes.addCollectionUtility.shardKeyPlaceholder);
|
||||
return "e.g., categoryId";
|
||||
case "Gremlin":
|
||||
return t(Keys.panes.addCollectionUtility.partitionKeyPlaceholderDefault);
|
||||
return "e.g., /address";
|
||||
case "SQL":
|
||||
return `${
|
||||
index === undefined
|
||||
? t(Keys.panes.addCollectionUtility.partitionKeyPlaceholderFirst)
|
||||
? "Required - first partition key e.g., /TenantId"
|
||||
: index === 0
|
||||
? t(Keys.panes.addCollectionUtility.partitionKeyPlaceholderSecond)
|
||||
: t(Keys.panes.addCollectionUtility.partitionKeyPlaceholderThird)
|
||||
? "second partition key e.g., /UserId"
|
||||
: "third partition key e.g., /SessionId"
|
||||
}`;
|
||||
default:
|
||||
return t(Keys.panes.addCollectionUtility.partitionKeyPlaceholderGraph);
|
||||
return "e.g., /address/zipCode";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,12 +69,13 @@ export function isFreeTierAccount(): boolean {
|
||||
}
|
||||
|
||||
export function UniqueKeysHeader(): JSX.Element {
|
||||
const tooltipContent = t(Keys.panes.addCollectionUtility.uniqueKeysTooltip);
|
||||
const tooltipContent =
|
||||
"Unique keys provide developers with the ability to add a layer of data integrity to their database. By creating a unique key policy when a container is created, you ensure the uniqueness of one or more values per partition key.";
|
||||
|
||||
return (
|
||||
<Stack horizontal style={{ marginBottom: -2 }}>
|
||||
<Text className="panelTextBold" variant="small">
|
||||
{t(Keys.panes.addCollectionUtility.uniqueKeysLabel)}
|
||||
Unique keys
|
||||
</Text>
|
||||
<TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={tooltipContent}>
|
||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} ariaLabel={tooltipContent} />
|
||||
@@ -103,11 +99,12 @@ export function shouldShowAnalyticalStoreOptions(): boolean {
|
||||
}
|
||||
|
||||
export function AnalyticalStoreHeader(): JSX.Element {
|
||||
const tooltipContent = t(Keys.panes.addCollectionUtility.analyticalStoreTooltip);
|
||||
const tooltipContent =
|
||||
"Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads.";
|
||||
return (
|
||||
<Stack horizontal style={{ marginBottom: -2 }}>
|
||||
<Text className="panelTextBold" variant="small">
|
||||
{t(Keys.panes.addCollectionUtility.analyticalStoreLabel)}
|
||||
Analytical Store
|
||||
</Text>
|
||||
<TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={tooltipContent}>
|
||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} ariaLabel={tooltipContent} />
|
||||
@@ -119,13 +116,14 @@ export function AnalyticalStoreHeader(): JSX.Element {
|
||||
export function AnalyticalStorageContent(): JSX.Element {
|
||||
return (
|
||||
<Text variant="small">
|
||||
{t(Keys.panes.addCollectionUtility.analyticalStoreDescription)}{" "}
|
||||
Enable analytical store capability to perform near real-time analytics on your operational data, without impacting
|
||||
the performance of transactional workloads.{" "}
|
||||
<Link
|
||||
aria-label={Constants.ariaLabelForLearnMoreLink.AnalyticalStore}
|
||||
target="_blank"
|
||||
href="https://aka.ms/analytical-store-overview"
|
||||
>
|
||||
{t(Keys.common.learnMore)}
|
||||
Learn more
|
||||
</Link>
|
||||
</Text>
|
||||
);
|
||||
@@ -157,9 +155,10 @@ export function scrollToSection(id: string): void {
|
||||
export function ContainerVectorPolicyTooltipContent(): JSX.Element {
|
||||
return (
|
||||
<Text variant="small">
|
||||
{t(Keys.panes.addCollectionUtility.vectorPolicyTooltip)}{" "}
|
||||
Describe any properties in your data that contain vectors, so that they can be made available for similarity
|
||||
queries.{" "}
|
||||
<Link target="_blank" href="https://aka.ms/CosmosDBVectorSetup">
|
||||
{t(Keys.common.learnMore)}
|
||||
Learn more
|
||||
</Link>
|
||||
</Text>
|
||||
);
|
||||
|
||||
@@ -482,8 +482,10 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
}
|
||||
variant="small"
|
||||
>
|
||||
Azure Synapse Link is required for creating an analytical store container. Enable Synapse Link for this Cosmos DB account.
|
||||
Azure Synapse Link is required for creating an analytical store
|
||||
|
||||
container
|
||||
. Enable Synapse Link for this Cosmos DB account.
|
||||
<br />
|
||||
<StyledLinkBase
|
||||
aria-label="Learn more about Azure Synapse Link."
|
||||
@@ -606,8 +608,7 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
className="removeIcon"
|
||||
iconName="InfoSolid"
|
||||
/>
|
||||
|
||||
To ensure compatibility with older SDKs, the created container will use a legacy partitioning scheme that supports partition key values of size only up to 101 bytes. If this is enabled, you will not be able to use hierarchical partition keys.
|
||||
To ensure compatibility with older SDKs, the created container will use a legacy partitioning scheme that supports partition key values of size only up to 101 bytes. If this is enabled, you will not be able to use hierarchical partition keys.
|
||||
|
||||
<StyledLinkBase
|
||||
href="https://aka.ms/cosmos-large-pk"
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Checkbox, Stack, Text, TextField } from "@fluentui/react";
|
||||
import { getNewDatabaseSharedThroughputDefault } from "Common/DatabaseUtility";
|
||||
import { Keys } from "Localization/Keys.generated";
|
||||
import { t } from "Localization/t";
|
||||
import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils";
|
||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
@@ -42,18 +40,15 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
||||
const isCassandraAccount: boolean = userContext.apiType === "Cassandra";
|
||||
const databaseLabel: string = isCassandraAccount ? "keyspace" : "database";
|
||||
const collectionsLabel: string = isCassandraAccount ? "tables" : "collections";
|
||||
const databaseIdLabel: string = isCassandraAccount
|
||||
? t(Keys.panes.addDatabase.keyspaceIdLabel)
|
||||
: t(Keys.panes.addDatabase.databaseIdLabel);
|
||||
const databaseIdPlaceHolder: string = t(Keys.panes.addDatabase.databaseIdPlaceholder, { databaseLabel });
|
||||
const databaseIdLabel: string = isCassandraAccount ? "Keyspace id" : "Database id";
|
||||
const databaseIdPlaceHolder: string = isCassandraAccount ? "Type a new keyspace id" : "Type a new database id";
|
||||
|
||||
const [databaseId, setDatabaseId] = useState<string>("");
|
||||
const databaseIdTooltipText = t(Keys.panes.addDatabase.databaseTooltip, { databaseLabel, collectionsLabel });
|
||||
const databaseIdTooltipText = `A ${
|
||||
isCassandraAccount ? "keyspace" : "database"
|
||||
} is a logical container of one or more ${isCassandraAccount ? "tables" : "collections"}`;
|
||||
|
||||
const databaseLevelThroughputTooltipText = t(Keys.panes.addDatabase.shareThroughputTooltip, {
|
||||
databaseLabel,
|
||||
collectionsLabel,
|
||||
});
|
||||
const databaseLevelThroughputTooltipText = `Provisioned throughput at the ${databaseLabel} level will be shared across all ${collectionsLabel} within the ${databaseLabel}.`;
|
||||
const [databaseCreateNewShared, setDatabaseCreateNewShared] = useState<boolean>(
|
||||
getNewDatabaseSharedThroughputDefault(),
|
||||
);
|
||||
@@ -149,15 +144,15 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
||||
// TODO add feature flag that disables validation for customers with custom accounts
|
||||
if (isAutoscaleSelected) {
|
||||
if (!AutoPilotUtils.isValidAutoPilotThroughput(throughput)) {
|
||||
setFormErrors(t(Keys.panes.addDatabase.greaterThanError, { minValue: AutoPilotUtils.autoPilotThroughput1K }));
|
||||
setFormErrors(
|
||||
`Please enter a value greater than ${AutoPilotUtils.autoPilotThroughput1K} for autopilot throughput`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !isCostAcknowledged) {
|
||||
setFormErrors(
|
||||
t(Keys.panes.addDatabase.acknowledgeSpendError, { period: isAutoscaleSelected ? "monthly" : "daily" }),
|
||||
);
|
||||
setFormErrors(`Please acknowledge the estimated ${isAutoscaleSelected ? "monthly" : "daily"} spend.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -174,7 +169,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
||||
const props: RightPaneFormProps = {
|
||||
formError: formErrors,
|
||||
isExecuting,
|
||||
submitButtonText: t(Keys.common.ok),
|
||||
submitButtonText: "OK",
|
||||
isSubmitButtonDisabled: isThroughputCapExceeded,
|
||||
onSubmit,
|
||||
};
|
||||
@@ -192,7 +187,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
||||
messageType="info"
|
||||
showErrorDetails={false}
|
||||
link={Constants.Urls.freeTierInformation}
|
||||
linkText={t(Keys.common.learnMore)}
|
||||
linkText="Learn more"
|
||||
/>
|
||||
)}
|
||||
<div className="panelMainContent">
|
||||
|
||||
@@ -40,8 +40,6 @@ import { PanelInfoErrorComponent } from "Explorer/Panes/PanelInfoErrorComponent"
|
||||
import { PanelLoadingScreen } from "Explorer/Panes/PanelLoadingScreen";
|
||||
import { useDatabases } from "Explorer/useDatabases";
|
||||
import { useSidePanel } from "hooks/useSidePanel";
|
||||
import { Keys } from "Localization/Keys.generated";
|
||||
import { t } from "Localization/t";
|
||||
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
|
||||
import { CollectionCreation } from "Shared/Constants";
|
||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||
@@ -170,19 +168,19 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
|
||||
}
|
||||
|
||||
if (globalSecondaryIndexThroughput > CollectionCreation.DefaultCollectionRUs100K && !isCostAcknowledged) {
|
||||
const errorMessage: string = t(Keys.panes.addCollection.acknowledgeSpendErrorMonthly);
|
||||
const errorMessage: string = "Please acknowledge the estimated monthly spend.";
|
||||
setErrorMessage(errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (showVectorSearchParameters()) {
|
||||
if (!vectorPolicyValidated) {
|
||||
setErrorMessage(t(Keys.panes.addCollection.vectorPolicyError));
|
||||
setErrorMessage("Please fix errors in container vector policy");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fullTextPolicyValidated) {
|
||||
setErrorMessage(t(Keys.panes.addCollection.fullTextSearchPolicyError));
|
||||
setErrorMessage("Please fix errors in container full text search policy");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -309,7 +307,7 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
|
||||
<Stack horizontal>
|
||||
<span className="mandatoryStar">* </span>
|
||||
<Text className="panelTextBold" variant="small">
|
||||
{t(Keys.panes.addGlobalSecondaryIndex.globalSecondaryIndexId)}
|
||||
Global secondary index container id
|
||||
</Text>
|
||||
</Stack>
|
||||
<input
|
||||
@@ -320,7 +318,7 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
|
||||
autoComplete="off"
|
||||
pattern={ValidCosmosDbIdInputPattern.source}
|
||||
title={ValidCosmosDbIdDescription}
|
||||
placeholder={t(Keys.panes.addGlobalSecondaryIndex.globalSecondaryIndexIdPlaceholder)}
|
||||
placeholder={`e.g., indexbyEmailId`}
|
||||
size={40}
|
||||
className="panelTextField"
|
||||
value={globalSecondaryIndexId}
|
||||
@@ -338,7 +336,7 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
|
||||
href="https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/materialized-views#defining-materialized-views"
|
||||
target="blank"
|
||||
>
|
||||
{t(Keys.panes.addGlobalSecondaryIndex.projectionQueryTooltip)}
|
||||
Learn more about defining global secondary indexes.
|
||||
</Link>
|
||||
}
|
||||
>
|
||||
@@ -351,7 +349,7 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
|
||||
aria-required
|
||||
required
|
||||
autoComplete="off"
|
||||
placeholder={t(Keys.panes.addGlobalSecondaryIndex.projectionQueryPlaceholder)}
|
||||
placeholder={"SELECT c.email, c.accountId FROM c"}
|
||||
size={40}
|
||||
className="panelTextField"
|
||||
value={definition || ""}
|
||||
@@ -395,7 +393,7 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
|
||||
<AdvancedComponent {...{ useHashV1, setUseHashV1, setSubPartitionKeys }} />
|
||||
</Stack>
|
||||
</div>
|
||||
<PanelFooterComponent buttonLabel={t(Keys.common.ok)} isButtonDisabled={isThroughputCapExceeded} />
|
||||
<PanelFooterComponent buttonLabel="OK" isButtonDisabled={isThroughputCapExceeded} />
|
||||
{isExecuting && <PanelLoadingScreen />}
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -2,16 +2,14 @@ import { Checkbox, Dropdown, IDropdownOption, Link, Stack, Text, TextField } fro
|
||||
import * as Constants from "Common/Constants";
|
||||
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
|
||||
import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
|
||||
import { useSidePanel } from "hooks/useSidePanel";
|
||||
import { Keys } from "Localization/Keys.generated";
|
||||
import { t } from "Localization/t";
|
||||
import React, { FunctionComponent, useState } from "react";
|
||||
import * as SharedConstants from "Shared/Constants";
|
||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "UserContext";
|
||||
import { isServerlessAccount } from "Utils/CapabilityUtils";
|
||||
import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils";
|
||||
import { useSidePanel } from "hooks/useSidePanel";
|
||||
import React, { FunctionComponent, useState } from "react";
|
||||
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
|
||||
import Explorer from "../../Explorer";
|
||||
import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
|
||||
@@ -73,8 +71,8 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !isCostAcknowledged) {
|
||||
const errorMessage =
|
||||
isNewKeySpaceAutoscale || isTableAutoscale
|
||||
? t(Keys.panes.addCollection.acknowledgeSpendErrorMonthly)
|
||||
: t(Keys.panes.addCollection.acknowledgeSpendErrorDaily);
|
||||
? "Please acknowledge the estimated monthly spend."
|
||||
: "Please acknowledge the estimated daily spend.";
|
||||
setFormError(errorMessage);
|
||||
return;
|
||||
}
|
||||
@@ -151,7 +149,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
const props: RightPaneFormProps = {
|
||||
formError,
|
||||
isExecuting,
|
||||
submitButtonText: t(Keys.common.ok),
|
||||
submitButtonText: "OK",
|
||||
isSubmitButtonDisabled: isThroughputCapExceeded,
|
||||
onSubmit,
|
||||
};
|
||||
@@ -163,8 +161,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
<Stack horizontal>
|
||||
<span className="mandatoryStar">* </span>
|
||||
<Text className="panelTextBold" variant="small">
|
||||
{t(Keys.panes.cassandraAddCollection.keyspaceLabel)}{" "}
|
||||
<InfoTooltip>{t(Keys.panes.cassandraAddCollection.keyspaceTooltip)}</InfoTooltip>
|
||||
Keyspace name <InfoTooltip>Select an existing keyspace or enter a new keyspace id.</InfoTooltip>
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
@@ -182,7 +179,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
setExistingKeyspaceId("");
|
||||
}}
|
||||
/>
|
||||
<span className="panelRadioBtnLabel">{t(Keys.panes.addCollection.createNew)}</span>
|
||||
<span className="panelRadioBtnLabel">Create new</span>
|
||||
|
||||
<input
|
||||
className="panelRadioBtn"
|
||||
@@ -196,7 +193,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
setIsKeyspaceShared(false);
|
||||
}}
|
||||
/>
|
||||
<span className="panelRadioBtnLabel">{t(Keys.panes.addCollection.useExisting)}</span>
|
||||
<span className="panelRadioBtnLabel">Use existing</span>
|
||||
</Stack>
|
||||
|
||||
{keyspaceCreateNew && (
|
||||
@@ -278,9 +275,9 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
<Stack horizontal>
|
||||
<span className="mandatoryStar">* </span>
|
||||
<Text className="panelTextBold" variant="small">
|
||||
{t(Keys.panes.cassandraAddCollection.tableIdLabel)}{" "}
|
||||
Enter CQL command to create the table.{" "}
|
||||
<Link className="underlinedLink" href="https://aka.ms/cassandra-create-table" target="_blank">
|
||||
{t(Keys.common.learnMore)}
|
||||
Learn More
|
||||
</Link>
|
||||
</Text>
|
||||
</Stack>
|
||||
@@ -298,7 +295,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
autoComplete="off"
|
||||
pattern={ValidCosmosDbIdInputPattern.source}
|
||||
title={ValidCosmosDbIdDescription}
|
||||
placeholder={t(Keys.panes.cassandraAddCollection.enterTableId)}
|
||||
placeholder="Enter table Id"
|
||||
size={20}
|
||||
value={tableId}
|
||||
onChange={(e, newValue) => setTableId(newValue)}
|
||||
@@ -310,7 +307,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
multiline
|
||||
id="editor-area"
|
||||
rows={5}
|
||||
ariaLabel={t(Keys.panes.cassandraAddCollection.tableSchemaAriaLabel)}
|
||||
ariaLabel="Table schema"
|
||||
value={userTableQuery}
|
||||
onChange={(e, newValue) => setUserTableQuery(newValue)}
|
||||
/>
|
||||
@@ -321,12 +318,17 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
<input
|
||||
type="checkbox"
|
||||
id="tableSharedThroughput"
|
||||
title={t(Keys.panes.cassandraAddCollection.provisionDedicatedThroughput)}
|
||||
title="Provision dedicated throughput for this table"
|
||||
checked={dedicateTableThroughput}
|
||||
onChange={(e) => setDedicateTableThroughput(e.target.checked)}
|
||||
/>
|
||||
<span>{t(Keys.panes.cassandraAddCollection.provisionDedicatedThroughput)}</span>
|
||||
<InfoTooltip>{t(Keys.panes.cassandraAddCollection.provisionDedicatedThroughputTooltip)}</InfoTooltip>
|
||||
<span>Provision dedicated throughput for this table</span>
|
||||
<InfoTooltip>
|
||||
You can optionally provision dedicated throughput for a table within a keyspace that has throughput
|
||||
provisioned. This dedicated throughput amount will not be shared with other tables in the keyspace and
|
||||
does not count towards the throughput you provisioned for the keyspace. This throughput amount will be
|
||||
billed in addition to the throughput amount you provisioned at the keyspace level.
|
||||
</InfoTooltip>
|
||||
</Stack>
|
||||
)}
|
||||
{!isServerlessAccount() && (!isKeyspaceShared || dedicateTableThroughput) && (
|
||||
|
||||
@@ -26,8 +26,6 @@ import {
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { RightPaneForm } from "Explorer/Panes/RightPaneForm/RightPaneForm";
|
||||
import { useDatabases } from "Explorer/useDatabases";
|
||||
import { Keys } from "Localization/Keys.generated";
|
||||
import { t } from "Localization/t";
|
||||
import { userContext } from "UserContext";
|
||||
import { getCollectionName } from "Utils/APITypeUtils";
|
||||
import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils";
|
||||
@@ -74,7 +72,7 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
|
||||
await createDataTransferJob();
|
||||
await onClose();
|
||||
} catch (error) {
|
||||
handleError(error, "ChangePartitionKey", t(Keys.panes.changePartitionKey.failedToStartError));
|
||||
handleError(error, "ChangePartitionKey", "Failed to start data transfer job");
|
||||
}
|
||||
setIsExecuting(false);
|
||||
useSidePanel.getState().closeSidePanel();
|
||||
@@ -135,21 +133,17 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<RightPaneForm
|
||||
formError={formError}
|
||||
isExecuting={isExecuting}
|
||||
onSubmit={submit}
|
||||
submitButtonText={t(Keys.common.ok)}
|
||||
>
|
||||
<RightPaneForm formError={formError} isExecuting={isExecuting} onSubmit={submit} submitButtonText="OK">
|
||||
<Stack tokens={{ childrenGap: 10 }} className="panelMainContent">
|
||||
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||
{t(Keys.panes.changePartitionKey.description)}
|
||||
When changing a container’s partition key, you will need to create a destination container with the correct
|
||||
partition key. You may also select an existing destination container.
|
||||
<Link
|
||||
href="https://learn.microsoft.com/en-us/azure/cosmos-db/container-copy#container-copy-within-an-azure-cosmos-db-account"
|
||||
target="_blank"
|
||||
underline
|
||||
>
|
||||
{t(Keys.common.learnMore)}
|
||||
Learn more
|
||||
</Link>
|
||||
</Text>
|
||||
<Stack>
|
||||
@@ -224,18 +218,14 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
|
||||
</Text>
|
||||
<TooltipHost
|
||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||
content={t(Keys.panes.changePartitionKey.collectionIdTooltip, {
|
||||
collectionName: getCollectionName().toLocaleLowerCase(),
|
||||
})}
|
||||
content={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
|
||||
>
|
||||
<Icon
|
||||
role="button"
|
||||
iconName="Info"
|
||||
className="panelInfoIcon"
|
||||
tabIndex={0}
|
||||
ariaLabel={t(Keys.panes.changePartitionKey.collectionIdTooltip, {
|
||||
collectionName: getCollectionName().toLocaleLowerCase(),
|
||||
})}
|
||||
ariaLabel={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
|
||||
/>
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
@@ -249,14 +239,10 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
|
||||
autoComplete="off"
|
||||
pattern={ValidCosmosDbIdInputPattern.source}
|
||||
title={ValidCosmosDbIdDescription}
|
||||
placeholder={t(Keys.panes.changePartitionKey.collectionIdPlaceholder, {
|
||||
collectionName: getCollectionName(),
|
||||
})}
|
||||
placeholder={`e.g., ${getCollectionName()}1`}
|
||||
size={40}
|
||||
className="panelTextField"
|
||||
aria-label={t(Keys.panes.changePartitionKey.collectionIdAriaLabel, {
|
||||
collectionName: getCollectionName(),
|
||||
})}
|
||||
aria-label={`${getCollectionName()} id, Example ${getCollectionName()}1`}
|
||||
value={targetCollectionId}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => setTargetCollectionId(event.target.value)}
|
||||
/>
|
||||
@@ -363,7 +349,7 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
|
||||
disabled={subPartitionKeys.length >= Constants.BackendDefaults.maxNumMultiHashPartition}
|
||||
onClick={() => setSubPartitionKeys([...subPartitionKeys, ""])}
|
||||
>
|
||||
{t(Keys.panes.addCollection.addPartitionKey)}
|
||||
Add hierarchical partition key
|
||||
</DefaultButton>
|
||||
{subPartitionKeys.length > 0 && (
|
||||
<Text
|
||||
@@ -371,10 +357,11 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
|
||||
variant="small"
|
||||
style={{ color: "var(--colorNeutralForeground1)" }}
|
||||
>
|
||||
<Icon iconName="InfoSolid" className="removeIcon" tabIndex={0} />{" "}
|
||||
{t(Keys.panes.addCollection.hierarchicalPartitionKeyInfo)}{" "}
|
||||
<Icon iconName="InfoSolid" className="removeIcon" tabIndex={0} /> This feature allows you to
|
||||
partition your data with up to three levels of keys for better data distribution. Requires .NET V3,
|
||||
Java V4 SDK, or preview JavaScript V3 SDK.{" "}
|
||||
<Link href="https://aka.ms/cosmos-hierarchical-partitioning" target="_blank">
|
||||
{t(Keys.common.learnMore)}
|
||||
Learn more
|
||||
</Link>
|
||||
</Text>
|
||||
)}
|
||||
@@ -390,18 +377,14 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
|
||||
</Text>
|
||||
<TooltipHost
|
||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||
content={t(Keys.panes.changePartitionKey.collectionIdTooltip, {
|
||||
collectionName: getCollectionName().toLocaleLowerCase(),
|
||||
})}
|
||||
content={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
|
||||
>
|
||||
<Icon
|
||||
role="button"
|
||||
iconName="Info"
|
||||
className="panelInfoIcon"
|
||||
tabIndex={0}
|
||||
ariaLabel={t(Keys.panes.changePartitionKey.collectionIdTooltip, {
|
||||
collectionName: getCollectionName().toLocaleLowerCase(),
|
||||
})}
|
||||
ariaLabel={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
|
||||
/>
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
@@ -417,7 +400,7 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
|
||||
}}
|
||||
defaultSelectedKey={targetCollectionId}
|
||||
responsiveMode={999}
|
||||
ariaLabel={t(Keys.panes.changePartitionKey.existingContainers)}
|
||||
ariaLabel="Existing Containers"
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
@@ -4,8 +4,6 @@ import { HttpStatusCodes, PoolIdType } from "../../../Common/Constants";
|
||||
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
||||
import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient";
|
||||
import { Keys } from "../../../Localization/Keys.generated";
|
||||
import { t } from "../../../Localization/t";
|
||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
@@ -84,14 +82,14 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
||||
|
||||
const notebookContentItem = await copyNotebook(selectedLocation);
|
||||
if (!notebookContentItem) {
|
||||
throw new Error(t(Keys.panes.copyNotebook.uploadFailedError, { name }));
|
||||
throw new Error(`Failed to upload ${name}`);
|
||||
}
|
||||
|
||||
NotificationConsoleUtils.logConsoleInfo(`Successfully copied ${name} to ${destination}`);
|
||||
closeSidePanel();
|
||||
} catch (error) {
|
||||
const errorMessage = getErrorMessage(error);
|
||||
setFormError(t(Keys.panes.copyNotebook.copyFailedError, { name, destination }));
|
||||
setFormError(`Failed to copy ${name} to ${destination}`);
|
||||
handleError(errorMessage, "CopyNotebookPaneAdapter/submit", formError);
|
||||
} finally {
|
||||
clearMessage && clearMessage();
|
||||
@@ -138,7 +136,7 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
||||
const props: RightPaneFormProps = {
|
||||
formError,
|
||||
isExecuting: isExecuting,
|
||||
submitButtonText: t(Keys.common.ok),
|
||||
submitButtonText: "OK",
|
||||
onSubmit: () => submit(),
|
||||
};
|
||||
|
||||
|
||||
@@ -12,8 +12,6 @@ import {
|
||||
import { GitHubReposTitle } from "Explorer/Tree/ResourceTree";
|
||||
import React, { FormEvent, FunctionComponent } from "react";
|
||||
import { IPinnedRepo } from "../../../Juno/JunoClient";
|
||||
import { Keys } from "../../../Localization/Keys.generated";
|
||||
import { t } from "../../../Localization/t";
|
||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||
import { useNotebook } from "../../Notebook/useNotebook";
|
||||
|
||||
@@ -98,8 +96,8 @@ export const CopyNotebookPaneComponent: FunctionComponent<CopyNotebookPaneProps>
|
||||
return options;
|
||||
};
|
||||
const dropDownProps: IDropdownProps = {
|
||||
label: t(Keys.panes.copyNotebook.location),
|
||||
ariaLabel: t(Keys.panes.copyNotebook.locationAriaLabel),
|
||||
label: "Location",
|
||||
ariaLabel: "Location",
|
||||
placeholder: "Select an option",
|
||||
onRenderTitle: onRenderDropDownTitle,
|
||||
onRenderOption: onRenderDropDownOption,
|
||||
@@ -111,7 +109,7 @@ export const CopyNotebookPaneComponent: FunctionComponent<CopyNotebookPaneProps>
|
||||
<div className="paneMainContent">
|
||||
<Stack tokens={{ childrenGap: 10 }}>
|
||||
<Stack.Item>
|
||||
<Label htmlFor="notebookName">{t(Keys.panes.copyNotebook.name)}</Label>
|
||||
<Label htmlFor="notebookName">Name</Label>
|
||||
<Text id="notebookName">{name}</Text>
|
||||
</Stack.Item>
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ import DeleteFeedback from "Common/DeleteFeedback";
|
||||
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
|
||||
import { deleteCollection } from "Common/dataAccess/deleteCollection";
|
||||
import { Collection } from "Contracts/ViewModels";
|
||||
import { Keys } from "Localization/Keys.generated";
|
||||
import { t } from "Localization/t";
|
||||
import { DefaultExperienceUtility } from "Shared/DefaultExperienceUtility";
|
||||
import { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
|
||||
@@ -36,15 +34,12 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
||||
useDatabases.getState().isLastCollection() && !useDatabases.getState().findSelectedDatabase()?.isDatabaseShared();
|
||||
|
||||
const collectionName = getCollectionName().toLocaleLowerCase();
|
||||
const paneTitle = t(Keys.panes.deleteCollection.panelTitle, { collectionName });
|
||||
const paneTitle = "Delete " + collectionName;
|
||||
|
||||
const onSubmit = async (): Promise<void> => {
|
||||
const collection = useSelectedNode.getState().findSelectedCollection();
|
||||
if (!collection || inputCollectionName !== collection.id()) {
|
||||
const errorMessage = t(Keys.panes.deleteCollection.inputMismatch, {
|
||||
input: inputCollectionName,
|
||||
selectedId: collection.id(),
|
||||
});
|
||||
const errorMessage = "Input id " + inputCollectionName + " does not match the selected " + collection.id();
|
||||
setFormError(errorMessage);
|
||||
NotificationConsoleUtils.logConsoleError(
|
||||
`Error while deleting ${collectionName} ${collection.id()}: ${errorMessage}`,
|
||||
@@ -111,23 +106,18 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
||||
const props: RightPaneFormProps = {
|
||||
formError: formError,
|
||||
isExecuting,
|
||||
submitButtonText: t(Keys.common.ok),
|
||||
submitButtonText: "OK",
|
||||
onSubmit,
|
||||
};
|
||||
const confirmContainer = t(Keys.panes.deleteCollection.confirmPrompt, {
|
||||
collectionName: collectionName.toLowerCase(),
|
||||
});
|
||||
const reasonInfo =
|
||||
t(Keys.panes.deleteCollection.feedbackTitle) +
|
||||
" " +
|
||||
t(Keys.panes.deleteCollection.feedbackReason, { collectionName });
|
||||
const confirmContainer = `Confirm by typing the ${collectionName.toLowerCase()} id`;
|
||||
const reasonInfo = `Help us improve Azure Cosmos DB! What is the reason why you are deleting this ${collectionName}?`;
|
||||
return (
|
||||
<RightPaneForm {...props}>
|
||||
<div className="panelFormWrapper">
|
||||
<div className="panelMainContent">
|
||||
<div className="confirmDeleteInput">
|
||||
<span className="mandatoryStar">* </span>
|
||||
<Text variant="small">{confirmContainer}</Text>
|
||||
<Text variant="small">Confirm by typing the {collectionName.toLowerCase()} id</Text>
|
||||
<TextField
|
||||
id="confirmCollectionId"
|
||||
autoFocus
|
||||
@@ -143,10 +133,10 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
||||
{shouldRecordFeedback() && (
|
||||
<div className="deleteCollectionFeedback">
|
||||
<Text variant="small" block>
|
||||
{t(Keys.panes.deleteCollection.feedbackTitle)}
|
||||
Help us improve Azure Cosmos DB!
|
||||
</Text>
|
||||
<Text variant="small" block>
|
||||
{t(Keys.panes.deleteCollection.feedbackReason, { collectionName })}
|
||||
What is the reason why you are deleting this {collectionName}?
|
||||
</Text>
|
||||
<TextField
|
||||
id="deleteCollectionFeedbackInput"
|
||||
|
||||
@@ -34,7 +34,9 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
||||
<span
|
||||
className="css-109"
|
||||
>
|
||||
Confirm by typing the container id
|
||||
Confirm by typing the
|
||||
container
|
||||
id
|
||||
</span>
|
||||
</Text>
|
||||
<StyledTextFieldBase
|
||||
|
||||
@@ -5,8 +5,6 @@ import DeleteFeedback from "Common/DeleteFeedback";
|
||||
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
|
||||
import { deleteDatabase } from "Common/dataAccess/deleteDatabase";
|
||||
import { Collection, Database } from "Contracts/ViewModels";
|
||||
import { Keys } from "Localization/Keys.generated";
|
||||
import { t } from "Localization/t";
|
||||
import { DefaultExperienceUtility } from "Shared/DefaultExperienceUtility";
|
||||
import { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
|
||||
@@ -40,19 +38,11 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
||||
const submit = async (): Promise<void> => {
|
||||
if (selectedDatabase?.id() && databaseInput !== selectedDatabase.id()) {
|
||||
setFormError(
|
||||
t(Keys.panes.deleteDatabase.inputMismatch, {
|
||||
databaseName: getDatabaseName(),
|
||||
input: databaseInput,
|
||||
selectedId: selectedDatabase.id(),
|
||||
}),
|
||||
`Input ${getDatabaseName()} name "${databaseInput}" does not match the selected ${getDatabaseName()} "${selectedDatabase.id()}"`,
|
||||
);
|
||||
logConsoleError(`Error while deleting ${getDatabaseName()} ${selectedDatabase && selectedDatabase.id()}`);
|
||||
logConsoleError(
|
||||
t(Keys.panes.deleteDatabase.inputMismatch, {
|
||||
databaseName: getDatabaseName(),
|
||||
input: databaseInput,
|
||||
selectedId: selectedDatabase.id(),
|
||||
}),
|
||||
`Input ${getDatabaseName()} name "${databaseInput}" does not match the selected ${getDatabaseName()} "${selectedDatabase.id()}"`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -124,20 +114,18 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
||||
const props: RightPaneFormProps = {
|
||||
formError,
|
||||
isExecuting: isLoading,
|
||||
submitButtonText: t(Keys.common.ok),
|
||||
submitButtonText: "OK",
|
||||
onSubmit: () => submit(),
|
||||
};
|
||||
|
||||
const errorProps: PanelInfoErrorProps = {
|
||||
messageType: "warning",
|
||||
showErrorDetails: false,
|
||||
message: t(Keys.panes.deleteDatabase.warningMessage),
|
||||
message:
|
||||
"Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.",
|
||||
};
|
||||
const confirmDatabase = t(Keys.panes.deleteDatabase.confirmPrompt, { databaseName: getDatabaseName() });
|
||||
const reasonInfo =
|
||||
t(Keys.panes.deleteDatabase.feedbackTitle) +
|
||||
" " +
|
||||
t(Keys.panes.deleteDatabase.feedbackReason, { databaseName: getDatabaseName() });
|
||||
const confirmDatabase = `Confirm by typing the ${getDatabaseName()} id (name)`;
|
||||
const reasonInfo = `Help us improve Azure Cosmos DB! What is the reason why you are deleting this ${getDatabaseName()}?`;
|
||||
return (
|
||||
<RightPaneForm {...props}>
|
||||
{!formError && <PanelInfoErrorComponent {...errorProps} />}
|
||||
@@ -160,10 +148,10 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
||||
{isLastNonEmptyDatabase() && (
|
||||
<div className="deleteDatabaseFeedback">
|
||||
<Text variant="small" block>
|
||||
{t(Keys.panes.deleteDatabase.feedbackTitle)}
|
||||
Help us improve Azure Cosmos DB!
|
||||
</Text>
|
||||
<Text variant="small" block>
|
||||
{t(Keys.panes.deleteDatabase.feedbackReason, { databaseName: getDatabaseName() })}
|
||||
What is the reason why you are deleting this {getDatabaseName()}?
|
||||
</Text>
|
||||
<TextField
|
||||
id="deleteDatabaseFeedbackInput"
|
||||
|
||||
@@ -3,8 +3,6 @@ import { useBoolean } from "@fluentui/react-hooks";
|
||||
import React, { FunctionComponent, useRef, useState } from "react";
|
||||
import AddPropertyIcon from "../../../../images/Add-property.svg";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { Keys } from "../../../Localization/Keys.generated";
|
||||
import { t } from "../../../Localization/t";
|
||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||
import StoredProcedure from "../../Tree/StoredProcedure";
|
||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||
@@ -47,8 +45,8 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
|
||||
};
|
||||
|
||||
const setInvalidParamError = (invalidParam: string): void => {
|
||||
setFormError(t(Keys.panes.executeStoredProcedure.invalidParamError, { invalidParam }));
|
||||
logConsoleError(t(Keys.panes.executeStoredProcedure.invalidParamConsoleError, { invalidParam }));
|
||||
setFormError(`Invalid param specified: ${invalidParam}`);
|
||||
logConsoleError(`Invalid param specified: ${invalidParam} is not a valid literal value`);
|
||||
};
|
||||
|
||||
const submit = (): void => {
|
||||
@@ -98,7 +96,7 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
|
||||
const props: RightPaneFormProps = {
|
||||
formError: formError,
|
||||
isExecuting: isLoading,
|
||||
submitButtonText: t(Keys.common.execute),
|
||||
submitButtonText: "Execute",
|
||||
onSubmit: () => submit(),
|
||||
};
|
||||
|
||||
@@ -109,9 +107,9 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
|
||||
inputParameters.push(
|
||||
<InputParameter
|
||||
key={paramKeyValue.text + i}
|
||||
dropdownLabel={i === 0 ? t(Keys.panes.executeStoredProcedure.key) : ""}
|
||||
inputParameterTitle={i === 0 ? t(Keys.panes.executeStoredProcedure.enterInputParameters) : ""}
|
||||
inputLabel={i === 0 ? t(Keys.panes.executeStoredProcedure.param) : ""}
|
||||
dropdownLabel={i === 0 ? "Key" : ""}
|
||||
inputParameterTitle={i === 0 ? "Enter input parameters (if any)" : ""}
|
||||
inputLabel={i === 0 ? "Param" : ""}
|
||||
isAddRemoveVisible={true}
|
||||
onDeleteParamKeyPress={() => deleteParamAtIndex(i)}
|
||||
onAddNewParamKeyPress={() => addNewParamAtIndex(i + 1)}
|
||||
@@ -132,9 +130,9 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
|
||||
<RightPaneForm {...props}>
|
||||
<div className="panelMainContent">
|
||||
<InputParameter
|
||||
dropdownLabel={t(Keys.panes.executeStoredProcedure.key)}
|
||||
inputParameterTitle={t(Keys.panes.executeStoredProcedure.partitionKeyValue)}
|
||||
inputLabel={t(Keys.panes.executeStoredProcedure.value)}
|
||||
dropdownLabel="Key"
|
||||
inputParameterTitle="Partition key value"
|
||||
inputLabel="Value"
|
||||
isAddRemoveVisible={false}
|
||||
onParamValueChange={(_event, newInput?: string) => (partitionValueRef.current = newInput)}
|
||||
onParamKeyChange={(_event: React.FormEvent<HTMLDivElement>, item: IDropdownOption) =>
|
||||
@@ -145,8 +143,8 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
|
||||
/>
|
||||
{getInputParameterComponent()}
|
||||
<Stack horizontal onClick={() => addNewParamAtLastIndex()} tabIndex={0}>
|
||||
<Image {...imageProps} src={AddPropertyIcon} alt={t(Keys.panes.executeStoredProcedure.addParam)} />
|
||||
<Text className="addNewParamStyle">{t(Keys.panes.executeStoredProcedure.addNewParam)}</Text>
|
||||
<Image {...imageProps} src={AddPropertyIcon} alt="Add param" />
|
||||
<Text className="addNewParamStyle">Add New Param</Text>
|
||||
</Stack>
|
||||
</div>
|
||||
</RightPaneForm>
|
||||
|
||||
@@ -11,8 +11,6 @@ import {
|
||||
import React, { FunctionComponent } from "react";
|
||||
import AddPropertyIcon from "../../../../images/Add-property.svg";
|
||||
import EntityCancelIcon from "../../../../images/Entity_cancel.svg";
|
||||
import { Keys } from "../../../Localization/Keys.generated";
|
||||
import { t } from "../../../Localization/t";
|
||||
|
||||
const dropdownStyles: Partial<IDropdownStyles> = { dropdown: { width: 100 } };
|
||||
const options = [
|
||||
@@ -76,7 +74,7 @@ export const InputParameter: FunctionComponent<InputParameterProps> = ({
|
||||
<Image
|
||||
{...imageProps}
|
||||
src={EntityCancelIcon}
|
||||
alt={t(Keys.panes.executeStoredProcedure.deleteParam)}
|
||||
alt="Delete param"
|
||||
id="deleteparam"
|
||||
role="button"
|
||||
onClick={onDeleteParamKeyPress}
|
||||
@@ -86,7 +84,7 @@ export const InputParameter: FunctionComponent<InputParameterProps> = ({
|
||||
<Image
|
||||
{...imageProps}
|
||||
src={AddPropertyIcon}
|
||||
alt={t(Keys.panes.executeStoredProcedure.addParam)}
|
||||
alt="Add param"
|
||||
id="addparam"
|
||||
role="button"
|
||||
onClick={onAddNewParamKeyPress}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { Keys } from "../../../Localization/Keys.generated";
|
||||
import { t } from "../../../Localization/t";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { GraphStyleComponent } from "../../Graph/GraphStyleComponent/GraphStyleComponent";
|
||||
import { IGraphConfig } from "../../Tabs/GraphTab";
|
||||
@@ -19,7 +17,7 @@ export const GraphStylingPanel: FunctionComponent<GraphStylingProps> = ({
|
||||
}: GraphStylingProps): JSX.Element => {
|
||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||
|
||||
const buttonLabel = t(Keys.common.ok);
|
||||
const buttonLabel = "Ok";
|
||||
|
||||
const submit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -5,8 +5,6 @@ import folderIcon from "../../../../images/folder_16x16.svg";
|
||||
import { logError } from "../../../Common/Logger";
|
||||
import { Collection } from "../../../Contracts/ViewModels";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { Keys } from "../../../Localization/Keys.generated";
|
||||
import { t } from "../../../Localization/t";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
|
||||
import { useSelectedNode } from "../../useSelectedNode";
|
||||
@@ -35,8 +33,8 @@ export const LoadQueryPane: FunctionComponent = (): JSX.Element => {
|
||||
const submit = async (): Promise<void> => {
|
||||
setFormError("");
|
||||
if (!selectedFiles || selectedFiles.length === 0) {
|
||||
setFormError(t(Keys.panes.loadQuery.noFileSpecifiedError));
|
||||
logConsoleError(t(Keys.panes.loadQuery.noFileSpecifiedError));
|
||||
setFormError("No file specified");
|
||||
logConsoleError("Could not load query -- No file specified. Please input a file.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -50,7 +48,7 @@ export const LoadQueryPane: FunctionComponent = (): JSX.Element => {
|
||||
setLoadingFalse();
|
||||
} catch (error) {
|
||||
setLoadingFalse();
|
||||
setFormError(t(Keys.panes.loadQuery.failedToLoadQueryError));
|
||||
setFormError("Failed to load query");
|
||||
logConsoleError(`Failed to load query from file ${file.name}: ${error}`);
|
||||
}
|
||||
};
|
||||
@@ -73,7 +71,7 @@ export const LoadQueryPane: FunctionComponent = (): JSX.Element => {
|
||||
};
|
||||
|
||||
reader.onerror = (): void => {
|
||||
setFormError(t(Keys.panes.loadQuery.failedToLoadQueryFromFileError, { fileName: file.name }));
|
||||
setFormError("Failed to load query");
|
||||
logConsoleError(`Failed to load query from file ${file.name}`);
|
||||
};
|
||||
return reader.readAsText(file);
|
||||
@@ -81,7 +79,7 @@ export const LoadQueryPane: FunctionComponent = (): JSX.Element => {
|
||||
const props: RightPaneFormProps = {
|
||||
formError: formError,
|
||||
isExecuting: isLoading,
|
||||
submitButtonText: t(Keys.common.load),
|
||||
submitButtonText: "Load",
|
||||
onSubmit: () => submit(),
|
||||
};
|
||||
|
||||
@@ -92,7 +90,7 @@ export const LoadQueryPane: FunctionComponent = (): JSX.Element => {
|
||||
<Stack horizontal>
|
||||
<TextField
|
||||
id="confirmCollectionId"
|
||||
label={t(Keys.panes.loadQuery.selectFilesToOpen)}
|
||||
label="Select a query document"
|
||||
value={selectedFileName}
|
||||
autoFocus
|
||||
readOnly
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useBoolean } from "@fluentui/react-hooks";
|
||||
import React, { FunctionComponent, useState } from "react";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { Keys } from "../../../Localization/Keys.generated";
|
||||
import { t } from "../../../Localization/t";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { NewVertexComponent } from "../../Graph/NewVertexComponent/NewVertexComponent";
|
||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||
@@ -43,7 +41,7 @@ export const NewVertexPanel: FunctionComponent<INewVertexPanelProps> = ({
|
||||
const props: RightPaneFormProps = {
|
||||
formError: errorMessage,
|
||||
isExecuting: isLoading,
|
||||
submitButtonText: t(Keys.common.ok),
|
||||
submitButtonText: "OK",
|
||||
onSubmit: () => submit(),
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { Icon, Link, Stack, Text } from "@fluentui/react";
|
||||
import { Keys } from "Localization/Keys.generated";
|
||||
import { t } from "Localization/t";
|
||||
import React from "react";
|
||||
import { useNotificationConsole } from "../../hooks/useNotificationConsole";
|
||||
|
||||
@@ -22,17 +20,13 @@ export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProp
|
||||
}: PanelInfoErrorProps): JSX.Element => {
|
||||
const expandConsole = useNotificationConsole((state) => state.expandConsole);
|
||||
|
||||
let icon: JSX.Element = (
|
||||
<Icon iconName="InfoSolid" className="panelLargeInfoIcon" aria-label={t(Keys.panes.panelInfo.information)} />
|
||||
);
|
||||
let icon: JSX.Element = <Icon iconName="InfoSolid" className="panelLargeInfoIcon" aria-label="Infomation" />;
|
||||
if (messageType === "error") {
|
||||
icon = <Icon iconName="StatusErrorFull" className="panelErrorIcon" aria-label="error" />;
|
||||
} else if (messageType === "warning") {
|
||||
icon = <Icon iconName="WarningSolid" className="panelWarningIcon" aria-label="warning" />;
|
||||
} else if (messageType === "info") {
|
||||
icon = (
|
||||
<Icon iconName="InfoSolid" className="panelLargeInfoIcon" aria-label={t(Keys.panes.panelInfo.information)} />
|
||||
);
|
||||
icon = <Icon iconName="InfoSolid" className="panelLargeInfoIcon" aria-label="Infomation" />;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -49,7 +43,7 @@ export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProp
|
||||
</Text>
|
||||
{showErrorDetails && (
|
||||
<a className="paneErrorLink" role="button" onClick={expandConsole} tabIndex={0} onKeyPress={expandConsole}>
|
||||
{t(Keys.panes.panelInfo.moreDetails)}
|
||||
More details
|
||||
</a>
|
||||
)}
|
||||
</span>
|
||||
|
||||
@@ -5,8 +5,6 @@ import { getErrorMessage, getErrorStack, handleError } from "../../../Common/Err
|
||||
import { useNotebookSnapshotStore } from "../../../hooks/useNotebookSnapshotStore";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { JunoClient } from "../../../Juno/JunoClient";
|
||||
import { Keys } from "../../../Localization/Keys.generated";
|
||||
import { t } from "../../../Localization/t";
|
||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||
@@ -93,7 +91,7 @@ export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> =
|
||||
let startKey: number;
|
||||
|
||||
if (!notebookName || !notebookDescription || !author || !imageSrc) {
|
||||
setFormError(t(Keys.panes.publishNotebook.publishFailedError, { notebookName }));
|
||||
setFormError(`Failed to publish ${notebookName} to gallery`);
|
||||
setFormErrorDetail("Name, description, author and cover image are required");
|
||||
createFormError(formError, formErrorDetail, "PublishNotebookPaneAdapter/submit");
|
||||
setIsExecuting(false);
|
||||
@@ -145,11 +143,7 @@ export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> =
|
||||
);
|
||||
|
||||
const errorMessage = getErrorMessage(error);
|
||||
setFormError(
|
||||
t(Keys.panes.publishNotebook.publishFailedError, {
|
||||
notebookName: FileSystemUtil.stripExtension(notebookName, "ipynb"),
|
||||
}),
|
||||
);
|
||||
setFormError(`Failed to publish ${FileSystemUtil.stripExtension(notebookName, "ipynb")} to gallery`);
|
||||
setFormErrorDetail(`${errorMessage}`);
|
||||
handleError(errorMessage, "PublishNotebookPaneAdapter/submit", formError);
|
||||
return;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Dropdown, IDropdownProps, ITextFieldProps, Stack, Text, TextField } from "@fluentui/react";
|
||||
import { ImmutableNotebook } from "@nteract/commutable";
|
||||
import React, { FunctionComponent, useState } from "react";
|
||||
import { Keys } from "../../../Localization/Keys.generated";
|
||||
import { t } from "../../../Localization/t";
|
||||
import { GalleryCardComponent } from "../../Controls/NotebookGallery/Cards/GalleryCardComponent";
|
||||
import * as FileSystemUtil from "../../Notebook/FileSystemUtil";
|
||||
import { SnapshotRequest } from "../../Notebook/NotebookComponent/types";
|
||||
@@ -59,11 +57,13 @@ export const PublishNotebookPaneComponent: FunctionComponent<PublishNotebookPane
|
||||
|
||||
const maxImageSizeInMib = 1.5;
|
||||
|
||||
const descriptionPara1 = t(Keys.panes.publishNotebook.publishDescription);
|
||||
const descriptionPara1 =
|
||||
"When published, this notebook will appear in the Azure Cosmos DB notebooks public gallery. Make sure you have removed any sensitive data or output before publishing.";
|
||||
|
||||
const descriptionPara2 = t(Keys.panes.publishNotebook.publishPrompt, {
|
||||
name: FileSystemUtil.stripExtension(notebookName, "ipynb"),
|
||||
});
|
||||
const descriptionPara2 = `Would you like to publish and share "${FileSystemUtil.stripExtension(
|
||||
notebookName,
|
||||
"ipynb",
|
||||
)}" to the gallery?`;
|
||||
|
||||
const options: ImageTypes[] = [ImageTypes.CustomImage, ImageTypes.Url];
|
||||
if (onTakeSnapshot) {
|
||||
@@ -74,9 +74,9 @@ export const PublishNotebookPaneComponent: FunctionComponent<PublishNotebookPane
|
||||
}
|
||||
|
||||
const thumbnailSelectorProps: IDropdownProps = {
|
||||
label: t(Keys.panes.publishNotebook.coverImage),
|
||||
label: "Cover image",
|
||||
selectedKey: type,
|
||||
ariaLabel: t(Keys.panes.publishNotebook.coverImage),
|
||||
ariaLabel: "Cover image",
|
||||
options: options.map((value: string) => ({ text: value, key: value })),
|
||||
onChange: async (event, options) => {
|
||||
setImageSrc("");
|
||||
@@ -99,7 +99,7 @@ export const PublishNotebookPaneComponent: FunctionComponent<PublishNotebookPane
|
||||
notebookContentRef,
|
||||
});
|
||||
} else {
|
||||
firstOutputErrorHandler(new Error(t(Keys.panes.publishNotebook.outputDoesNotExist)));
|
||||
firstOutputErrorHandler(new Error("Output does not exist for any of the cells."));
|
||||
}
|
||||
}
|
||||
setType(options.text);
|
||||
@@ -107,8 +107,8 @@ export const PublishNotebookPaneComponent: FunctionComponent<PublishNotebookPane
|
||||
};
|
||||
|
||||
const thumbnailUrlProps: ITextFieldProps = {
|
||||
label: t(Keys.panes.publishNotebook.coverImageUrl),
|
||||
ariaLabel: t(Keys.panes.publishNotebook.coverImageUrl),
|
||||
label: "Cover image url",
|
||||
ariaLabel: "Cover image url",
|
||||
required: true,
|
||||
onChange: (event, newValue) => {
|
||||
setImageSrc(newValue);
|
||||
@@ -116,7 +116,7 @@ export const PublishNotebookPaneComponent: FunctionComponent<PublishNotebookPane
|
||||
};
|
||||
|
||||
const firstOutputErrorHandler = (error: Error) => {
|
||||
const formError = t(Keys.panes.publishNotebook.failedToCaptureOutput);
|
||||
const formError = "Failed to capture first output";
|
||||
const formErrorDetail = `${error}`;
|
||||
const area = "PublishNotebookPaneComponent/UseFirstOutput";
|
||||
onError(formError, formErrorDetail, area);
|
||||
@@ -130,7 +130,7 @@ export const PublishNotebookPaneComponent: FunctionComponent<PublishNotebookPane
|
||||
};
|
||||
|
||||
reader.onerror = (error) => {
|
||||
const formError = t(Keys.panes.publishNotebook.failedToConvertError, { fileName: file.name });
|
||||
const formError = `Failed to convert ${file.name} to base64 format`;
|
||||
const formErrorDetail = `${error}`;
|
||||
const area = "PublishNotebookPaneComponent/selectImageFile";
|
||||
onError(formError, formErrorDetail, area);
|
||||
@@ -151,7 +151,7 @@ export const PublishNotebookPaneComponent: FunctionComponent<PublishNotebookPane
|
||||
const file = event.target.files[0];
|
||||
if (file.size / 1024 ** 2 > maxImageSizeInMib) {
|
||||
event.target.value = "";
|
||||
const formError = t(Keys.panes.publishNotebook.failedToUploadError, { fileName: file.name });
|
||||
const formError = `Failed to upload ${file.name}`;
|
||||
const formErrorDetail = `Image is larger than ${maxImageSizeInMib} MiB. Please Choose a different image.`;
|
||||
const area = "PublishNotebookPaneComponent/selectImageFile";
|
||||
|
||||
@@ -185,8 +185,8 @@ export const PublishNotebookPaneComponent: FunctionComponent<PublishNotebookPane
|
||||
|
||||
<Stack.Item>
|
||||
<TextField
|
||||
label={t(Keys.panes.publishNotebook.name)}
|
||||
ariaLabel={t(Keys.panes.publishNotebook.name)}
|
||||
label="Name"
|
||||
ariaLabel="Name"
|
||||
defaultValue={FileSystemUtil.stripExtension(notebookName, "ipynb")}
|
||||
required
|
||||
onChange={(event, newValue) => {
|
||||
@@ -198,8 +198,8 @@ export const PublishNotebookPaneComponent: FunctionComponent<PublishNotebookPane
|
||||
|
||||
<Stack.Item>
|
||||
<TextField
|
||||
label={t(Keys.panes.publishNotebook.description)}
|
||||
ariaLabel={t(Keys.panes.publishNotebook.description)}
|
||||
label="Description"
|
||||
ariaLabel="Description"
|
||||
multiline
|
||||
rows={3}
|
||||
required
|
||||
@@ -211,9 +211,9 @@ export const PublishNotebookPaneComponent: FunctionComponent<PublishNotebookPane
|
||||
|
||||
<Stack.Item>
|
||||
<TextField
|
||||
label={t(Keys.panes.publishNotebook.tags)}
|
||||
ariaLabel={t(Keys.panes.publishNotebook.tags)}
|
||||
placeholder={t(Keys.panes.publishNotebook.tagsPlaceholder)}
|
||||
label="Tags"
|
||||
ariaLabel="Tags"
|
||||
placeholder="Optional tag 1, Optional tag 2"
|
||||
onChange={(event, newValue) => {
|
||||
setNotebookTags(newValue);
|
||||
}}
|
||||
@@ -227,7 +227,7 @@ export const PublishNotebookPaneComponent: FunctionComponent<PublishNotebookPane
|
||||
<Stack.Item>{renderThumbnailSelectors(type)}</Stack.Item>
|
||||
|
||||
<Stack.Item>
|
||||
<Text>{t(Keys.panes.publishNotebook.preview)}</Text>
|
||||
<Text>Preview</Text>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<GalleryCardComponent
|
||||
|
||||
@@ -4,8 +4,6 @@ import React, { FunctionComponent, useState } from "react";
|
||||
import { Areas, SavedQueries } from "../../../Common/Constants";
|
||||
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||
import { Query } from "../../../Contracts/DataModels";
|
||||
import { Keys } from "../../../Localization/Keys.generated";
|
||||
import { t } from "../../../Localization/t";
|
||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||
@@ -30,27 +28,27 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({
|
||||
const [formError, setFormError] = useState<string>("");
|
||||
const [queryName, setQueryName] = useState<string>("");
|
||||
|
||||
const setupSaveQueriesText = t(Keys.panes.saveQuery.setupCostMessage, { databaseName: SavedQueries.DatabaseName });
|
||||
const title = t(Keys.panes.saveQuery.panelTitle);
|
||||
const setupSaveQueriesText = `For compliance reasons, we save queries in a container in your Azure Cosmos account, in a separate database called “${SavedQueries.DatabaseName}”. To proceed, we need to create a container in your account, estimated additional cost is $0.77 daily.`;
|
||||
const title = "Save Query";
|
||||
const isSaveQueryEnabled = useDatabases((state) => state.isSaveQueryEnabled);
|
||||
|
||||
const submit = async (): Promise<void> => {
|
||||
setFormError("");
|
||||
if (!isSaveQueryEnabled()) {
|
||||
setFormError("Cannot save query");
|
||||
logConsoleError(t(Keys.panes.saveQuery.accountNotSetupError));
|
||||
logConsoleError("Failed to save query: account not setup to save queries");
|
||||
}
|
||||
|
||||
const queryTab = useTabs.getState().activeTab as NewQueryTab;
|
||||
const query: string = queryToSave || queryTab?.iTabAccessor.onSaveClickEvent();
|
||||
|
||||
if (!queryName || queryName.length === 0) {
|
||||
setFormError(t(Keys.panes.saveQuery.noQueryNameError));
|
||||
logConsoleError(t(Keys.panes.saveQuery.noQueryNameError));
|
||||
setFormError("No query name specified");
|
||||
logConsoleError("Could not save query -- No query name specified. Please specify a query name.");
|
||||
return;
|
||||
} else if (!query || query.length === 0) {
|
||||
setFormError(t(Keys.panes.saveQuery.invalidQueryContentError));
|
||||
logConsoleError(t(Keys.panes.saveQuery.invalidQueryContentError));
|
||||
setFormError("Invalid query content specified");
|
||||
logConsoleError("Could not save query -- Invalid query content specified. Please enter query content.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -82,8 +80,8 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({
|
||||
} catch (error) {
|
||||
setLoadingFalse();
|
||||
const errorMessage = getErrorMessage(error);
|
||||
setFormError(t(Keys.panes.saveQuery.failedToSaveQueryError, { queryName }));
|
||||
logConsoleError(t(Keys.panes.saveQuery.failedToSaveQueryError, { queryName }) + ": " + errorMessage);
|
||||
setFormError("Failed to save query");
|
||||
logConsoleError(`Failed to save query: ${errorMessage}`);
|
||||
traceFailure(
|
||||
Action.SaveQuery,
|
||||
{
|
||||
@@ -128,8 +126,8 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({
|
||||
},
|
||||
startKey,
|
||||
);
|
||||
setFormError(t(Keys.panes.saveQuery.failedToSetupContainerError));
|
||||
logConsoleError(t(Keys.panes.saveQuery.failedToSetupContainerError) + ": " + errorMessage);
|
||||
setFormError("Failed to setup a container for saved queries");
|
||||
logConsoleError(`Failed to setup a container for saved queries: ${errorMessage}`);
|
||||
} finally {
|
||||
setLoadingFalse();
|
||||
}
|
||||
@@ -138,7 +136,7 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({
|
||||
const props: RightPaneFormProps = {
|
||||
formError: formError,
|
||||
isExecuting: isLoading,
|
||||
submitButtonText: isSaveQueryEnabled() ? t(Keys.common.save) : t(Keys.panes.saveQuery.completeSetup),
|
||||
submitButtonText: isSaveQueryEnabled() ? "Save" : "Complete setup",
|
||||
onSubmit: () => {
|
||||
isSaveQueryEnabled() ? submit() : setupQueries();
|
||||
},
|
||||
@@ -162,7 +160,7 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({
|
||||
) : (
|
||||
<TextField
|
||||
id="saveQueryInput"
|
||||
label={t(Keys.panes.saveQuery.name)}
|
||||
label="Name"
|
||||
autoFocus
|
||||
styles={{ fieldGroup: { width: 300 } }}
|
||||
onChange={(event, newInput?: string) => {
|
||||
|
||||
@@ -24,8 +24,6 @@ import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
|
||||
import { Platform, configContext } from "ConfigContext";
|
||||
import { useDialog } from "Explorer/Controls/Dialog";
|
||||
import { useDatabases } from "Explorer/useDatabases";
|
||||
import { Keys } from "Localization/Keys.generated";
|
||||
import { t } from "Localization/t";
|
||||
import { isFabric, isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||
import {
|
||||
AppStateComponentNames,
|
||||
@@ -237,7 +235,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
const regionOptions: IDropdownOption[] = [];
|
||||
regionOptions.push({
|
||||
key: userContext?.databaseAccount?.properties?.documentEndpoint,
|
||||
text: t(Keys.panes.settings.globalDefault),
|
||||
text: `Global (Default)`,
|
||||
data: {
|
||||
isGlobal: true,
|
||||
writeEnabled: true,
|
||||
@@ -248,7 +246,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
uniqueAccountRegions.add(loc.locationName);
|
||||
regionOptions.push({
|
||||
key: loc.documentEndpoint,
|
||||
text: `${loc.locationName} ${t(Keys.panes.settings.readWrite)}`,
|
||||
text: `${loc.locationName} (Read/Write)`,
|
||||
data: {
|
||||
isGlobal: false,
|
||||
writeEnabled: true,
|
||||
@@ -261,7 +259,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
uniqueAccountRegions.add(loc.locationName);
|
||||
regionOptions.push({
|
||||
key: loc.documentEndpoint,
|
||||
text: `${loc.locationName} ${t(Keys.panes.settings.read)}`,
|
||||
text: `${loc.locationName} (Read)`,
|
||||
data: {
|
||||
isGlobal: false,
|
||||
writeEnabled: false,
|
||||
@@ -319,9 +317,13 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
authError instanceof msalAuthError &&
|
||||
authError.errorCode === msalBrowserAuthErrorMessage.popUpWindowError.code
|
||||
) {
|
||||
logConsoleError(t(Keys.panes.settings.popupsDisabledError));
|
||||
logConsoleError(
|
||||
`We were unable to establish authorization for this account, due to pop-ups being disabled in the browser.\nPlease enable pop-ups for this site and click on "Login for Entra ID" button`,
|
||||
);
|
||||
} else {
|
||||
logConsoleError(t(Keys.panes.settings.failedToAcquireTokenError));
|
||||
logConsoleError(
|
||||
`"Failed to acquire authorization token automatically. Please click on "Login for Entra ID" button to enable Entra ID RBAC operations`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -483,33 +485,33 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
const genericPaneProps: RightPaneFormProps = {
|
||||
formError: "",
|
||||
isExecuting,
|
||||
submitButtonText: t(Keys.common.apply),
|
||||
submitButtonText: "Apply",
|
||||
onSubmit: () => handlerOnSubmit(),
|
||||
};
|
||||
const pageOptionList: IChoiceGroupOption[] = [
|
||||
{ key: Constants.Queries.CustomPageOption, text: t(Keys.panes.settings.custom) },
|
||||
{ key: Constants.Queries.UnlimitedPageOption, text: t(Keys.panes.settings.unlimited) },
|
||||
{ key: Constants.Queries.CustomPageOption, text: "Custom" },
|
||||
{ key: Constants.Queries.UnlimitedPageOption, text: "Unlimited" },
|
||||
];
|
||||
|
||||
const graphAutoOptionList: IChoiceGroupOption[] = [
|
||||
{ key: "false", text: t(Keys.panes.settings.graph) },
|
||||
{ key: "true", text: t(Keys.panes.settings.json) },
|
||||
{ key: "false", text: "Graph" },
|
||||
{ key: "true", text: "JSON" },
|
||||
];
|
||||
|
||||
const priorityLevelOptionList: IChoiceGroupOption[] = [
|
||||
{ key: Constants.PriorityLevel.Low, text: t(Keys.panes.settings.low) },
|
||||
{ key: Constants.PriorityLevel.High, text: t(Keys.panes.settings.high) },
|
||||
{ key: Constants.PriorityLevel.Low, text: "Low" },
|
||||
{ key: Constants.PriorityLevel.High, text: "High" },
|
||||
];
|
||||
|
||||
const dataPlaneRBACOptionsList: IChoiceGroupOption[] = [
|
||||
{ key: Constants.RBACOptions.setAutomaticRBACOption, text: t(Keys.panes.settings.automatic) },
|
||||
{ key: Constants.RBACOptions.setTrueRBACOption, text: t(Keys.panes.settings["true"]) },
|
||||
{ key: Constants.RBACOptions.setFalseRBACOption, text: t(Keys.panes.settings["false"]) },
|
||||
{ key: Constants.RBACOptions.setAutomaticRBACOption, text: "Automatic" },
|
||||
{ key: Constants.RBACOptions.setTrueRBACOption, text: "True" },
|
||||
{ key: Constants.RBACOptions.setFalseRBACOption, text: "False" },
|
||||
];
|
||||
|
||||
const defaultQueryResultsViewOptionList: IChoiceGroupOption[] = [
|
||||
{ key: SplitterDirection.Vertical, text: t(Keys.tabs.query.vertical) },
|
||||
{ key: SplitterDirection.Horizontal, text: t(Keys.tabs.query.horizontal) },
|
||||
{ key: SplitterDirection.Vertical, text: "Vertical" },
|
||||
{ key: SplitterDirection.Horizontal, text: "Horizontal" },
|
||||
];
|
||||
|
||||
const mongoGuidRepresentationDropdownOptions: IDropdownOption[] = [
|
||||
@@ -722,12 +724,13 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
{shouldShowQueryPageOptions && (
|
||||
<AccordionItem value="1">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>{t(Keys.panes.settings.pageOptions)}</div>
|
||||
<div className={styles.header}>Page Options</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
{t(Keys.panes.settings.pageOptionsDescription)}
|
||||
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as
|
||||
many query results per page.
|
||||
</div>
|
||||
<ChoiceGroup
|
||||
ariaLabelledBy="pageOptions"
|
||||
@@ -741,14 +744,14 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
{isCustomPageOptionSelected() && (
|
||||
<div className="tabcontent">
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
{t(Keys.panes.settings.queryResultsPerPage)}{" "}
|
||||
Query results per page{" "}
|
||||
<InfoTooltip className={styles.headerIcon}>
|
||||
{t(Keys.panes.settings.queryResultsPerPageTooltip)}
|
||||
Enter the number of query results that should be shown per page.
|
||||
</InfoTooltip>
|
||||
</div>
|
||||
|
||||
<SpinButton
|
||||
ariaLabel={t(Keys.panes.settings.customQueryItemsPerPage)}
|
||||
ariaLabel="Custom query items per page"
|
||||
value={"" + customItemPerPage}
|
||||
onIncrement={(newValue) => {
|
||||
setCustomItemPerPage(parseInt(newValue) + 1 || customItemPerPage);
|
||||
@@ -758,8 +761,8 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
min={1}
|
||||
step={1}
|
||||
className="textfontclr"
|
||||
incrementButtonAriaLabel={t(Keys.common.increaseValueBy1)}
|
||||
decrementButtonAriaLabel={t(Keys.common.decreaseValueBy1)}
|
||||
incrementButtonAriaLabel="Increase value by 1"
|
||||
decrementButtonAriaLabel="Decrease value by 1"
|
||||
styles={spinButtonStyles}
|
||||
/>
|
||||
</div>
|
||||
@@ -771,19 +774,20 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
{showEnableEntraIdRbac && (
|
||||
<AccordionItem value="2">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>{t(Keys.panes.settings.entraIdRbac)}</div>
|
||||
<div className={styles.header}>Enable Entra ID RBAC</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
{t(Keys.panes.settings.entraIdRbacDescription)}
|
||||
Choose Automatic to enable Entra ID RBAC automatically. True/False to force enable/disable Entra
|
||||
ID RBAC.
|
||||
<a
|
||||
href="https://learn.microsoft.com/en-us/azure/cosmos-db/how-to-setup-rbac#use-data-explorer"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{" "}
|
||||
{t(Keys.common.learnMore)}{" "}
|
||||
Learn more{" "}
|
||||
</a>
|
||||
</div>
|
||||
<ChoiceGroup
|
||||
@@ -800,17 +804,17 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
{userContext.apiType === "SQL" && userContext.authType === AuthType.AAD && !isFabric() && (
|
||||
<AccordionItem value="3">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>{t(Keys.panes.settings.regionSelection)}</div>
|
||||
<div className={styles.header}>Region Selection</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
{t(Keys.panes.settings.regionSelectionDescription)}
|
||||
Changes region the Cosmos Client uses to access account.
|
||||
</div>
|
||||
<div>
|
||||
<span className={styles.subHeader}>{t(Keys.panes.settings.selectRegion)}</span>
|
||||
<span className={styles.subHeader}>Select Region</span>
|
||||
<InfoTooltip className={styles.headerIcon}>
|
||||
{t(Keys.panes.settings.selectRegionTooltip)}
|
||||
Changes the account endpoint used to perform client operations.
|
||||
</InfoTooltip>
|
||||
</div>
|
||||
<Dropdown
|
||||
@@ -861,16 +865,17 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
<>
|
||||
<AccordionItem value="4">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>{t(Keys.panes.settings.queryTimeout)}</div>
|
||||
<div className={styles.header}>Query Timeout</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
{t(Keys.panes.settings.queryTimeoutDescription)}
|
||||
When a query reaches a specified time limit, a popup with an option to cancel the query will
|
||||
show unless automatic cancellation has been enabled.
|
||||
</div>
|
||||
<Toggle
|
||||
styles={toggleStyles}
|
||||
label={t(Keys.panes.settings.enableQueryTimeout)}
|
||||
label="Enable query timeout"
|
||||
onChange={handleOnQueryTimeoutToggleChange}
|
||||
defaultChecked={queryTimeoutEnabled}
|
||||
/>
|
||||
@@ -878,18 +883,18 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
{queryTimeoutEnabled && (
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<SpinButton
|
||||
label={t(Keys.panes.settings.queryTimeoutMs)}
|
||||
label="Query timeout (ms)"
|
||||
labelPosition={Position.top}
|
||||
defaultValue={(queryTimeout || 5000).toString()}
|
||||
min={100}
|
||||
step={1000}
|
||||
onChange={handleOnQueryTimeoutSpinButtonChange}
|
||||
incrementButtonAriaLabel={t(Keys.panes.settings.increaseValueBy1000)}
|
||||
decrementButtonAriaLabel={t(Keys.panes.settings.decreaseValueBy1000)}
|
||||
incrementButtonAriaLabel="Increase value by 1000"
|
||||
decrementButtonAriaLabel="Decrease value by 1000"
|
||||
styles={spinButtonStyles}
|
||||
/>
|
||||
<Toggle
|
||||
label={t(Keys.panes.settings.automaticallyCancelQuery)}
|
||||
label="Automatically cancel query after timeout"
|
||||
styles={toggleStyles}
|
||||
onChange={handleOnAutomaticallyCancelQueryToggleChange}
|
||||
defaultChecked={automaticallyCancelQueryAfterTimeout}
|
||||
@@ -900,16 +905,16 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
</AccordionItem>
|
||||
<AccordionItem value="5">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>{t(Keys.panes.settings.ruLimit)}</div>
|
||||
<div className={styles.header}>RU Limit</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
{t(Keys.panes.settings.ruLimitDescription)}
|
||||
If a query exceeds a configured RU limit, the query will be aborted.
|
||||
</div>
|
||||
<Toggle
|
||||
styles={toggleStyles}
|
||||
label={t(Keys.panes.settings.enableRuLimit)}
|
||||
label="Enable RU limit"
|
||||
onChange={handleOnRUThresholdToggleChange}
|
||||
defaultChecked={ruThresholdEnabled}
|
||||
/>
|
||||
@@ -917,14 +922,14 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
{ruThresholdEnabled && (
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<SpinButton
|
||||
label={t(Keys.panes.settings.ruLimitLabel)}
|
||||
label="RU Limit (RU)"
|
||||
labelPosition={Position.top}
|
||||
defaultValue={(ruThreshold || DefaultRUThreshold).toString()}
|
||||
min={1}
|
||||
step={1000}
|
||||
onChange={handleOnRUThresholdSpinButtonChange}
|
||||
incrementButtonAriaLabel={t(Keys.panes.settings.increaseValueBy1000)}
|
||||
decrementButtonAriaLabel={t(Keys.panes.settings.decreaseValueBy1000)}
|
||||
incrementButtonAriaLabel="Increase value by 1000"
|
||||
decrementButtonAriaLabel="Decrease value by 1000"
|
||||
styles={spinButtonStyles}
|
||||
/>
|
||||
</div>
|
||||
@@ -934,12 +939,12 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
|
||||
<AccordionItem value="6">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>{t(Keys.panes.settings.defaultQueryResults)}</div>
|
||||
<div className={styles.header}>Default Query Results View</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
{t(Keys.panes.settings.defaultQueryResultsDescription)}
|
||||
Select the default view to use when displaying query results.
|
||||
</div>
|
||||
<ChoiceGroup
|
||||
ariaLabelledBy="defaultQueryResultsView"
|
||||
@@ -957,17 +962,17 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
{showRetrySettings && (
|
||||
<AccordionItem value="7">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>{t(Keys.panes.settings.retrySettings)}</div>
|
||||
<div className={styles.header}>Retry Settings</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
{t(Keys.panes.settings.retrySettingsDescription)}
|
||||
Retry policy associated with throttled requests during CosmosDB queries.
|
||||
</div>
|
||||
<div>
|
||||
<span className={styles.subHeader}>{t(Keys.panes.settings.maxRetryAttempts)}</span>
|
||||
<span className={styles.subHeader}>Max retry attempts</span>
|
||||
<InfoTooltip className={styles.headerIcon}>
|
||||
{t(Keys.panes.settings.maxRetryAttemptsTooltip)}
|
||||
Max number of retries to be performed for a request. Default value 9.
|
||||
</InfoTooltip>
|
||||
</div>
|
||||
<SpinButton
|
||||
@@ -976,17 +981,18 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
step={1}
|
||||
value={"" + retryAttempts}
|
||||
onChange={handleOnQueryRetryAttemptsSpinButtonChange}
|
||||
incrementButtonAriaLabel={t(Keys.common.increaseValueBy1)}
|
||||
decrementButtonAriaLabel={t(Keys.common.decreaseValueBy1)}
|
||||
incrementButtonAriaLabel="Increase value by 1"
|
||||
decrementButtonAriaLabel="Decrease value by 1"
|
||||
onIncrement={(newValue) => setRetryAttempts(parseInt(newValue) + 1 || retryAttempts)}
|
||||
onDecrement={(newValue) => setRetryAttempts(parseInt(newValue) - 1 || retryAttempts)}
|
||||
onValidate={(newValue) => setRetryAttempts(parseInt(newValue) || retryAttempts)}
|
||||
styles={spinButtonStyles}
|
||||
/>
|
||||
<div>
|
||||
<span className={styles.subHeader}>{t(Keys.panes.settings.fixedRetryInterval)}</span>
|
||||
<span className={styles.subHeader}>Fixed retry interval (ms)</span>
|
||||
<InfoTooltip className={styles.headerIcon}>
|
||||
{t(Keys.panes.settings.fixedRetryIntervalTooltip)}
|
||||
Fixed retry interval in milliseconds to wait between each retry ignoring the retryAfter returned
|
||||
as part of the response. Default value is 0 milliseconds.
|
||||
</InfoTooltip>
|
||||
</div>
|
||||
<SpinButton
|
||||
@@ -995,17 +1001,18 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
step={1000}
|
||||
value={"" + retryInterval}
|
||||
onChange={handleOnRetryIntervalSpinButtonChange}
|
||||
incrementButtonAriaLabel={t(Keys.panes.settings.increaseValueBy1000)}
|
||||
decrementButtonAriaLabel={t(Keys.panes.settings.decreaseValueBy1000)}
|
||||
incrementButtonAriaLabel="Increase value by 1000"
|
||||
decrementButtonAriaLabel="Decrease value by 1000"
|
||||
onIncrement={(newValue) => setRetryInterval(parseInt(newValue) + 1000 || retryInterval)}
|
||||
onDecrement={(newValue) => setRetryInterval(parseInt(newValue) - 1000 || retryInterval)}
|
||||
onValidate={(newValue) => setRetryInterval(parseInt(newValue) || retryInterval)}
|
||||
styles={spinButtonStyles}
|
||||
/>
|
||||
<div>
|
||||
<span className={styles.subHeader}>{t(Keys.panes.settings.maxWaitTime)}</span>
|
||||
<span className={styles.subHeader}>Max wait time (s)</span>
|
||||
<InfoTooltip className={styles.headerIcon}>
|
||||
{t(Keys.panes.settings.maxWaitTimeTooltip)}
|
||||
Max wait time in seconds to wait for a request while the retries are happening. Default value 30
|
||||
seconds.
|
||||
</InfoTooltip>
|
||||
</div>
|
||||
<SpinButton
|
||||
@@ -1014,8 +1021,8 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
step={1}
|
||||
value={"" + MaxWaitTimeInSeconds}
|
||||
onChange={handleOnMaxWaitTimeSpinButtonChange}
|
||||
incrementButtonAriaLabel={t(Keys.common.increaseValueBy1)}
|
||||
decrementButtonAriaLabel={t(Keys.common.decreaseValueBy1)}
|
||||
incrementButtonAriaLabel="Increase value by 1"
|
||||
decrementButtonAriaLabel="Decrease value by 1"
|
||||
onIncrement={(newValue) =>
|
||||
setMaxWaitTimeInSeconds(parseInt(newValue) + 1 || MaxWaitTimeInSeconds)
|
||||
}
|
||||
@@ -1032,26 +1039,24 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
{!isEmulator && (
|
||||
<AccordionItem value="8">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>{t(Keys.panes.settings.enableContainerPagination)}</div>
|
||||
<div className={styles.header}>Enable container pagination</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
{t(Keys.panes.settings.enableContainerPaginationDescription)}
|
||||
Load 50 containers at a time. Currently, containers are not pulled in alphanumeric order.
|
||||
</div>
|
||||
<Checkbox
|
||||
styles={{
|
||||
label: { padding: 0 },
|
||||
}}
|
||||
className="padding"
|
||||
ariaLabel={t(Keys.panes.settings.enableContainerPagination)}
|
||||
ariaLabel="Enable container pagination"
|
||||
checked={containerPaginationEnabled}
|
||||
onChange={() => setContainerPaginationEnabled(!containerPaginationEnabled)}
|
||||
label={t(Keys.panes.settings.enableContainerPagination)}
|
||||
label="Enable container pagination"
|
||||
onRenderLabel={() => (
|
||||
<span style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||
{t(Keys.panes.settings.enableContainerPagination)}
|
||||
</span>
|
||||
<span style={{ color: "var(--colorNeutralForeground1)" }}>Enable container pagination</span>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@@ -1061,25 +1066,24 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
{shouldShowCrossPartitionOption && (
|
||||
<AccordionItem value="9">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>{t(Keys.panes.settings.enableCrossPartitionQuery)}</div>
|
||||
<div className={styles.header}>Enable cross-partition query</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
{t(Keys.panes.settings.enableCrossPartitionQueryDescription)}
|
||||
Send more than one request while executing a query. More than one request is necessary if the
|
||||
query is not scoped to single partition key value.
|
||||
</div>
|
||||
<Checkbox
|
||||
styles={{
|
||||
label: { padding: 0 },
|
||||
}}
|
||||
className="padding"
|
||||
ariaLabel={t(Keys.panes.settings.enableCrossPartitionQuery)}
|
||||
ariaLabel="Enable cross partition query"
|
||||
checked={crossPartitionQueryEnabled}
|
||||
onChange={() => setCrossPartitionQueryEnabled(!crossPartitionQueryEnabled)}
|
||||
onRenderLabel={() => (
|
||||
<span style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||
{t(Keys.panes.settings.enableCrossPartitionQuery)}
|
||||
</span>
|
||||
<span style={{ color: "var(--colorNeutralForeground1)" }}>Enable cross-partition query</span>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@@ -1089,19 +1093,19 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
{shouldShowEnhancedQueryControl && (
|
||||
<AccordionItem value="10">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>{t(Keys.panes.settings.enhancedQueryControl)}</div>
|
||||
<div className={styles.header}>Enhanced query control</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
{t(Keys.panes.settings.maxDegreeOfParallelismQuery)}
|
||||
Query up to the max degree of parallelism.
|
||||
<a
|
||||
href="https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/performance-tips-query-sdk?tabs=v3&pivots=programming-language-nodejs#enhanced-query-control"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{" "}
|
||||
{t(Keys.common.learnMore)}{" "}
|
||||
Learn more{" "}
|
||||
</a>
|
||||
</div>
|
||||
<Checkbox
|
||||
@@ -1109,13 +1113,11 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
label: { padding: 0 },
|
||||
}}
|
||||
className="padding"
|
||||
ariaLabel={t(Keys.panes.settings.enableQueryControl)}
|
||||
ariaLabel="EnableQueryControl"
|
||||
checked={queryControlEnabled}
|
||||
onChange={() => setQueryControlEnabled(!queryControlEnabled)}
|
||||
onRenderLabel={() => (
|
||||
<span style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||
{t(Keys.panes.settings.enableQueryControl)}
|
||||
</span>
|
||||
<span style={{ color: "var(--colorNeutralForeground1)" }}>Enable query control</span>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@@ -1125,12 +1127,14 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
{shouldShowParallelismOption && (
|
||||
<AccordionItem value="10">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>{t(Keys.panes.settings.maxDegreeOfParallelism)}</div>
|
||||
<div className={styles.header}>Max degree of parallelism</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
{t(Keys.panes.settings.maxDegreeOfParallelismDescription)}
|
||||
Gets or sets the number of concurrent operations run client side during parallel query execution.
|
||||
A positive property value limits the number of concurrent operations to the set value. If it is
|
||||
set to less than 0, the system automatically decides the number of concurrent operations to run.
|
||||
</div>
|
||||
<SpinButton
|
||||
min={-1}
|
||||
@@ -1146,8 +1150,8 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
setMaxDegreeOfParallelism(parseInt(newValue) - 1 || maxDegreeOfParallelism)
|
||||
}
|
||||
onValidate={(newValue) => setMaxDegreeOfParallelism(parseInt(newValue) || maxDegreeOfParallelism)}
|
||||
ariaLabel={t(Keys.panes.settings.maxDegreeOfParallelism)}
|
||||
label={t(Keys.panes.settings.maxDegreeOfParallelism)}
|
||||
ariaLabel="Max degree of parallelism"
|
||||
label="Max degree of parallelism"
|
||||
styles={spinButtonStyles}
|
||||
/>
|
||||
</div>
|
||||
@@ -1157,12 +1161,14 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
{shouldShowPriorityLevelOption && (
|
||||
<AccordionItem value="11">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>{t(Keys.panes.settings.priorityLevel)}</div>
|
||||
<div className={styles.header}>Priority Level</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
{t(Keys.panes.settings.priorityLevelDescription)}
|
||||
Sets the priority level for data-plane requests from Data Explorer when using Priority-Based
|
||||
Execution. If "None" is selected, Data Explorer will not specify priority level, and the
|
||||
server-side default priority level will be used.
|
||||
</div>
|
||||
<ChoiceGroup
|
||||
ariaLabelledBy="priorityLevel"
|
||||
@@ -1178,18 +1184,19 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
{shouldShowGraphAutoVizOption && (
|
||||
<AccordionItem value="12">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>{t(Keys.panes.settings.displayGremlinQueryResults)} </div>
|
||||
<div className={styles.header}>Display Gremlin query results as: </div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
{t(Keys.panes.settings.displayGremlinQueryResultsDescription)}
|
||||
Select Graph to automatically visualize the query results as a Graph or JSON to display the
|
||||
results as JSON.
|
||||
</div>
|
||||
<ChoiceGroup
|
||||
selectedKey={graphAutoVizDisabled}
|
||||
options={graphAutoOptionList}
|
||||
onChange={handleOnGremlinChange}
|
||||
aria-label={t(Keys.panes.settings.graphAutoVisualization)}
|
||||
aria-label="Graph Auto-visualization"
|
||||
/>
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
@@ -1198,25 +1205,25 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
{shouldShowCopilotSampleDBOption && (
|
||||
<AccordionItem value="13">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>{t(Keys.panes.settings.enableSampleDatabase)}</div>
|
||||
<div className={styles.header}>Enable sample database</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
{t(Keys.panes.settings.enableSampleDatabaseDescription)}
|
||||
This is a sample database and collection with synthetic product data you can use to explore using
|
||||
NoSQL queries. This will appear as another database in the Data Explorer UI, and is created by,
|
||||
and maintained by Microsoft at no cost to you.
|
||||
</div>
|
||||
<Checkbox
|
||||
styles={{
|
||||
label: { padding: 0 },
|
||||
}}
|
||||
className="padding"
|
||||
ariaLabel={t(Keys.panes.settings.enableSampleDbAriaLabel)}
|
||||
ariaLabel="Enable sample db for query exploration"
|
||||
checked={copilotSampleDBEnabled}
|
||||
onChange={handleSampleDatabaseChange}
|
||||
onRenderLabel={() => (
|
||||
<span style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||
{t(Keys.panes.settings.enableSampleDatabase)}
|
||||
</span>
|
||||
<span style={{ color: "var(--colorNeutralForeground1)" }}>Enable sample database</span>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@@ -1226,12 +1233,13 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
{shouldShowMongoGuidRepresentationOption && (
|
||||
<AccordionItem value="14">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>{t(Keys.panes.settings.guidRepresentation)}</div>
|
||||
<div className={styles.header}>Guid Representation</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
{t(Keys.panes.settings.guidRepresentationDescription)}
|
||||
GuidRepresentation in MongoDB refers to how Globally Unique Identifiers (GUIDs) are serialized and
|
||||
deserialized when stored in BSON documents. This will apply to all document operations.
|
||||
</div>
|
||||
<Dropdown
|
||||
aria-labelledby="mongoGuidRepresentation"
|
||||
@@ -1245,7 +1253,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
)}
|
||||
<AccordionItem value="15">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>{t(Keys.panes.settings.advancedSettings)}</div>
|
||||
<div className={styles.header}>Advanced Settings</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
@@ -1275,13 +1283,14 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
},
|
||||
}}
|
||||
className="padding"
|
||||
ariaLabel={t(Keys.panes.settings.ignorePartitionKey)}
|
||||
ariaLabel="Ignore partition key on document update"
|
||||
checked={ignorePartitionKeyOnDocumentUpdate}
|
||||
onChange={handleOnIgnorePartitionKeyOnDocumentUpdateChange}
|
||||
label={t(Keys.panes.settings.ignorePartitionKey)}
|
||||
label="Ignore partition key on document update"
|
||||
/>
|
||||
<InfoTooltip className={styles.headerIcon}>
|
||||
{t(Keys.panes.settings.ignorePartitionKeyTooltip)}
|
||||
If checked, the partition key value will not be used to locate the document during update
|
||||
operations. Only use this if document updates are failing due to an abnormal partition key.
|
||||
</InfoTooltip>
|
||||
</Stack>
|
||||
</div>
|
||||
@@ -1311,9 +1320,9 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
}}
|
||||
onClick={() => {
|
||||
useDialog.getState().showOkCancelModalDialog(
|
||||
t(Keys.panes.settings.clearHistory),
|
||||
"Clear History",
|
||||
undefined,
|
||||
t(Keys.panes.settings.clearHistoryConfirm),
|
||||
"Are you sure you want to proceed?",
|
||||
() => {
|
||||
deleteAllStates();
|
||||
updateUserContext({
|
||||
@@ -1323,33 +1332,35 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
});
|
||||
useClientWriteEnabled.setState({ clientWriteEnabled: true });
|
||||
},
|
||||
t(Keys.common.cancel),
|
||||
"Cancel",
|
||||
undefined,
|
||||
<>
|
||||
<span>{t(Keys.panes.settings.clearHistoryDescription)}</span>
|
||||
<span>
|
||||
This action will clear the all customizations for this account in this browser, including:
|
||||
</span>
|
||||
<ul className={styles.bulletList}>
|
||||
<li>{t(Keys.panes.settings.clearHistoryTabLayout)}</li>
|
||||
<li>{t(Keys.panes.settings.clearHistoryTableColumns)}</li>
|
||||
<li>{t(Keys.panes.settings.clearHistoryFilters)}</li>
|
||||
<li>{t(Keys.panes.settings.clearHistoryRegion)}</li>
|
||||
<li>Reset your customized tab layout, including the splitter positions</li>
|
||||
<li>Erase your table column preferences, including any custom columns</li>
|
||||
<li>Clear your filter history</li>
|
||||
<li>Reset region selection to global</li>
|
||||
</ul>
|
||||
</>,
|
||||
);
|
||||
}}
|
||||
>
|
||||
{t(Keys.panes.settings.clearHistory)}
|
||||
Clear History
|
||||
</DefaultButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settingsSection">
|
||||
<div className={`settingsSectionPart ${styles.settingsSectionContainer}`}>
|
||||
<div className="settingsSectionLabel">{t(Keys.panes.settings.explorerVersion)}</div>
|
||||
<div className="settingsSectionLabel">Explorer Version</div>
|
||||
<div>{explorerVersion}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settingsSection">
|
||||
<div className="settingsSectionPart">
|
||||
<div className="settingsSectionLabel">{t(Keys.panes.settings.sessionId)}</div>
|
||||
<div className="settingsSectionLabel">Session ID</div>
|
||||
<div>{sessionId}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -660,7 +660,7 @@ exports[`Settings Pane should render Default properly 1`] = `
|
||||
Send more than one request while executing a query. More than one request is necessary if the query is not scoped to single partition key value.
|
||||
</div>
|
||||
<StyledCheckboxBase
|
||||
ariaLabel="Enable cross-partition query"
|
||||
ariaLabel="Enable cross partition query"
|
||||
checked={false}
|
||||
className="padding"
|
||||
onChange={[Function]}
|
||||
@@ -705,7 +705,7 @@ exports[`Settings Pane should render Default properly 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<StyledCheckboxBase
|
||||
ariaLabel="Enable query control"
|
||||
ariaLabel="EnableQueryControl"
|
||||
checked={false}
|
||||
className="padding"
|
||||
onChange={[Function]}
|
||||
@@ -1190,8 +1190,7 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
|
||||
<div
|
||||
className="___j7dlp70_0000000 fq02s40 f19n0e5"
|
||||
>
|
||||
Display Gremlin query results as:
|
||||
|
||||
Display Gremlin query results as:
|
||||
</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
|
||||
@@ -11,8 +11,6 @@ import {
|
||||
import { configContext } from "ConfigContext";
|
||||
import { ColumnDefinition } from "Explorer/Tabs/DocumentsTabV2/DocumentsTableComponent";
|
||||
import { CosmosFluentProvider, getPlatformTheme } from "Explorer/Theme/ThemeUtil";
|
||||
import { Keys } from "Localization/Keys.generated";
|
||||
import { t } from "Localization/t";
|
||||
import React from "react";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
|
||||
@@ -115,13 +113,13 @@ export const TableColumnSelectionPane: React.FC<TableColumnSelectionPaneProps> =
|
||||
<CosmosFluentProvider>
|
||||
<div className="panelFormWrapper">
|
||||
<div className="panelMainContent" style={{ display: "flex", flexDirection: "column" }}>
|
||||
<Text>{t(Keys.panes.tableColumnSelection.selectColumns)}</Text>
|
||||
<Text>Select which columns to display in your view of items in your container.</Text>
|
||||
<div /* Wrap <SearchBox> to avoid margin-bottom set by panelMainContent css */>
|
||||
<SearchBox
|
||||
className={styles.searchBox}
|
||||
value={columnSearchText}
|
||||
onChange={onSearchChange}
|
||||
placeholder={t(Keys.panes.tableColumnSelection.searchFields)}
|
||||
placeholder="Search fields"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -132,9 +130,7 @@ export const TableColumnSelectionPane: React.FC<TableColumnSelectionPaneProps> =
|
||||
key={columnDefinition.id}
|
||||
label={{
|
||||
className: styles.checkboxLabel,
|
||||
children: `${columnDefinition.label}${
|
||||
columnDefinition.isPartitionKey ? t(Keys.panes.tableColumnSelection.partitionKeySuffix) : ""
|
||||
}`,
|
||||
children: `${columnDefinition.label}${columnDefinition.isPartitionKey ? " (partition key)" : ""}`,
|
||||
}}
|
||||
checked={selectedColumnIdsSet.has(columnDefinition.id)}
|
||||
onChange={(_, data) => onCheckedValueChange(columnDefinition.id, data)}
|
||||
@@ -142,15 +138,15 @@ export const TableColumnSelectionPane: React.FC<TableColumnSelectionPaneProps> =
|
||||
))}
|
||||
</div>
|
||||
<Button appearance="secondary" size="small" onClick={() => setNewSelectedColumnIds(defaultSelection)}>
|
||||
{t(Keys.panes.tableColumnSelection.reset)}
|
||||
Reset
|
||||
</Button>
|
||||
</div>
|
||||
<div className="panelFooter" style={{ display: "flex", gap: theme.spacingHorizontalS }}>
|
||||
<Button appearance="primary" onClick={onSave}>
|
||||
{t(Keys.common.save)}
|
||||
Save
|
||||
</Button>
|
||||
<Button appearance="secondary" onClick={closeSidePanel}>
|
||||
{t(Keys.common.cancel)}
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { IDropdownOption, Image, Label, Stack, Text, TextField } from "@fluentui/react";
|
||||
import { useBoolean } from "@fluentui/react-hooks";
|
||||
import { Keys } from "Localization/Keys.generated";
|
||||
import { t } from "Localization/t";
|
||||
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||
import * as _ from "underscore";
|
||||
@@ -102,8 +100,8 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
||||
for (let i = 0; i < entities.length; i++) {
|
||||
const { property, type, value } = entities[i];
|
||||
if ((property === "PartitionKey" && value === "") || (property === "RowKey" && value === "")) {
|
||||
logConsoleError(t(Keys.panes.tables.propertyEmptyError, { property }));
|
||||
setFormError(t(Keys.panes.tables.propertyEmptyError, { property }));
|
||||
logConsoleError(`${property} cannot be empty. Please input a value for ${property}`);
|
||||
setFormError(`${property} cannot be empty. Please input a value for ${property}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -111,13 +109,13 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
||||
(property === "PartitionKey" && containsAnyWhiteSpace(value) === true) ||
|
||||
(property === "RowKey" && containsAnyWhiteSpace(value) === true)
|
||||
) {
|
||||
logConsoleError(t(Keys.panes.tables.whitespaceError, { property }));
|
||||
setFormError(t(Keys.panes.tables.whitespaceError, { property }));
|
||||
logConsoleError(`${property} cannot have whitespace. Please input a value for ${property} without whitespace`);
|
||||
setFormError(`${property} cannot have whitespace. Please input a value for ${property} without whitespace`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!type) {
|
||||
setFormError(t(Keys.panes.tables.propertyTypeEmptyError, { property }));
|
||||
setFormError(`Property type cannot be empty. Please select a type from the dropdown for property ${property}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { IDropdownOption, Image, Label, Stack, Text, TextField } from "@fluentui/react";
|
||||
import { useBoolean } from "@fluentui/react-hooks";
|
||||
import { Keys } from "Localization/Keys.generated";
|
||||
import { t } from "Localization/t";
|
||||
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||
import * as _ from "underscore";
|
||||
@@ -200,7 +198,7 @@ export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps>
|
||||
}
|
||||
|
||||
if (!type) {
|
||||
setFormError(t(Keys.panes.tables.propertyTypeEmptyError, { property }));
|
||||
setFormError(`Property type cannot be empty. Please select a type from the dropdown for property ${property}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -210,8 +208,8 @@ export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps>
|
||||
(property === "RowKey" && value === "") ||
|
||||
(property === "RowKey" && value === undefined)
|
||||
) {
|
||||
logConsoleError(t(Keys.panes.tables.propertyEmptyError, { property }));
|
||||
setFormError(t(Keys.panes.tables.propertyEmptyError, { property }));
|
||||
logConsoleError(`${property} cannot be empty. Please input a value for ${property}`);
|
||||
setFormError(`${property} cannot be empty. Please input a value for ${property}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -405,7 +403,7 @@ export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps>
|
||||
)}
|
||||
</div>
|
||||
<div className="panelNullWarning" style={{ padding: "20px", color: "red" }}>
|
||||
{t(Keys.panes.tables.nullFieldsWarning)}
|
||||
Warning: Null fields will not be displayed for editing.
|
||||
</div>
|
||||
</RightPaneForm>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { Checkbox, Text } from "@fluentui/react";
|
||||
import { Keys } from "Localization/Keys.generated";
|
||||
import { t } from "Localization/t";
|
||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||
import { userContext } from "../../../../UserContext";
|
||||
import { useSidePanel } from "../../../../hooks/useSidePanel";
|
||||
@@ -37,7 +35,7 @@ export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps
|
||||
const props: RightPaneFormProps = {
|
||||
formError: "",
|
||||
isExecuting: false,
|
||||
submitButtonText: t(Keys.common.ok),
|
||||
submitButtonText: "OK",
|
||||
onSubmit,
|
||||
};
|
||||
|
||||
@@ -123,11 +121,11 @@ export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps
|
||||
<RightPaneForm {...props}>
|
||||
<div className="panelFormWrapper">
|
||||
<div className="panelMainContent">
|
||||
<Text>{t(Keys.panes.tableQuerySelect.selectColumns)}</Text>
|
||||
<Text>Select the columns that you want to query.</Text>
|
||||
<div className="column-select-view">
|
||||
<Checkbox
|
||||
id="availableCheckbox"
|
||||
label={t(Keys.panes.tableQuerySelect.availableColumns)}
|
||||
label="Available Columns"
|
||||
checked={isAvailableColumnChecked}
|
||||
onChange={availableColumnsCheckboxClick}
|
||||
/>
|
||||
|
||||
@@ -13,8 +13,6 @@ import {
|
||||
} from "@fluentui/react";
|
||||
import { Upload } from "Common/Upload/Upload";
|
||||
import { UploadDetailsRecord } from "Contracts/ViewModels";
|
||||
import { Keys } from "Localization/Keys.generated";
|
||||
import { t } from "Localization/t";
|
||||
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
||||
import React, { ChangeEvent, FunctionComponent, useReducer, useState } from "react";
|
||||
import { getErrorMessage } from "../../Tables/Utilities";
|
||||
@@ -65,8 +63,8 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ onUpl
|
||||
const onSubmit = () => {
|
||||
setFormError("");
|
||||
if (!files || files.length === 0) {
|
||||
setFormError(t(Keys.panes.uploadItems.noFilesSpecifiedError));
|
||||
logConsoleError(t(Keys.panes.uploadItems.noFilesSpecifiedError));
|
||||
setFormError("No files were specified. Please input at least one file.");
|
||||
logConsoleError("Could not upload items -- No files were specified. Please input at least one file.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -152,7 +150,7 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ onUpl
|
||||
},
|
||||
{
|
||||
key: "fileName",
|
||||
name: t(Keys.panes.uploadItems.fileNameColumn),
|
||||
name: "FILE NAME",
|
||||
fieldName: "fileName",
|
||||
minWidth: 120,
|
||||
maxWidth: 140,
|
||||
@@ -171,7 +169,7 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ onUpl
|
||||
},
|
||||
{
|
||||
key: "status",
|
||||
name: t(Keys.panes.uploadItems.statusColumn),
|
||||
name: "STATUS",
|
||||
fieldName: "numSucceeded",
|
||||
minWidth: 120,
|
||||
maxWidth: 140,
|
||||
@@ -180,11 +178,7 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ onUpl
|
||||
data: "string",
|
||||
isPadded: true,
|
||||
onRender: (item: UploadDetailsRecord, index: number, column: IColumn) => {
|
||||
const fieldContent = t(Keys.panes.uploadItems.uploadStatus, {
|
||||
numSucceeded: item.numSucceeded,
|
||||
numThrottled: item.numThrottled,
|
||||
numFailed: item.numFailed,
|
||||
});
|
||||
const fieldContent = `${item.numSucceeded} created, ${item.numThrottled} throttled, ${item.numFailed} errors`;
|
||||
return (
|
||||
<TooltipHost
|
||||
content={fieldContent}
|
||||
@@ -203,12 +197,12 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ onUpl
|
||||
<div className="paneMainContent">
|
||||
<Upload
|
||||
key={reducer} // Force re-render on state change
|
||||
label={t(Keys.panes.uploadItems.selectJsonFiles)}
|
||||
label="Select JSON Files"
|
||||
onUpload={updateSelectedFiles}
|
||||
accept="application/json"
|
||||
multiple
|
||||
tabIndex={0}
|
||||
tooltip={t(Keys.panes.uploadItems.selectJsonFilesTooltip)}
|
||||
tooltip="Select one or more JSON files to upload. Each file can contain a single JSON document or an array of JSON documents. The combined size of all files in an individual upload operation must be less than 2 MB. You can perform multiple upload operations for larger data sets."
|
||||
/>
|
||||
{uploadFileData?.length > 0 && (
|
||||
<div className="fileUploadSummaryContainer" data-test="file-upload-status">
|
||||
|
||||
@@ -714,7 +714,9 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||
<span
|
||||
className="css-126"
|
||||
>
|
||||
What is the reason why you are deleting this Database?
|
||||
What is the reason why you are deleting this
|
||||
Database
|
||||
?
|
||||
</span>
|
||||
</Text>
|
||||
<StyledTextFieldBase
|
||||
|
||||
@@ -29,9 +29,6 @@
|
||||
"upload": "Upload",
|
||||
"connect": "Connect",
|
||||
"remove": "Remove",
|
||||
"load": "Load",
|
||||
"publish": "Publish",
|
||||
"browse": "Browse",
|
||||
"increaseValueBy1": "Increase value by 1",
|
||||
"decreaseValueBy1": "Decrease value by 1"
|
||||
},
|
||||
@@ -415,313 +412,5 @@
|
||||
"mongoShell": {
|
||||
"title": "Mongo Shell"
|
||||
}
|
||||
},
|
||||
"panes": {
|
||||
"deleteDatabase": {
|
||||
"panelTitle": "Delete {{databaseName}}",
|
||||
"warningMessage": "Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.",
|
||||
"confirmPrompt": "Confirm by typing the {{databaseName}} id (name)",
|
||||
"inputMismatch": "Input {{databaseName}} name \"{{input}}\" does not match the selected {{databaseName}} \"{{selectedId}}\"",
|
||||
"feedbackTitle": "Help us improve Azure Cosmos DB!",
|
||||
"feedbackReason": "What is the reason why you are deleting this {{databaseName}}?"
|
||||
},
|
||||
"deleteCollection": {
|
||||
"panelTitle": "Delete {{collectionName}}",
|
||||
"confirmPrompt": "Confirm by typing the {{collectionName}} id",
|
||||
"inputMismatch": "Input id {{input}} does not match the selected {{selectedId}}",
|
||||
"feedbackTitle": "Help us improve Azure Cosmos DB!",
|
||||
"feedbackReason": "What is the reason why you are deleting this {{collectionName}}?"
|
||||
},
|
||||
"addDatabase": {
|
||||
"databaseLabel": "Database {{suffix}}",
|
||||
"databaseIdLabel": "Database id",
|
||||
"keyspaceIdLabel": "Keyspace id",
|
||||
"databaseIdPlaceholder": "Type a new {{databaseLabel}} id",
|
||||
"databaseTooltip": "A {{databaseLabel}} is a logical container of one or more {{collectionsLabel}}",
|
||||
"shareThroughput": "Share throughput across {{collectionsLabel}}",
|
||||
"shareThroughputTooltip": "Provisioned throughput at the {{databaseLabel}} level will be shared across all {{collectionsLabel}} within the {{databaseLabel}}.",
|
||||
"greaterThanError": "Please enter a value greater than {{minValue}} for autopilot throughput",
|
||||
"acknowledgeSpendError": "Please acknowledge the estimated {{period}} spend."
|
||||
},
|
||||
"addCollection": {
|
||||
"createNew": "Create new",
|
||||
"useExisting": "Use existing",
|
||||
"databaseTooltip": "A database is analogous to a namespace. It is the unit of management for a set of {{collectionName}}.",
|
||||
"shareThroughput": "Share throughput across {{collectionName}}",
|
||||
"shareThroughputTooltip": "Throughput configured at the database level will be shared across all {{collectionName}} within the database.",
|
||||
"collectionIdLabel": "{{collectionName}} id",
|
||||
"collectionIdTooltip": "Unique identifier for the {{collectionName}} and used for id-based routing through REST and all SDKs.",
|
||||
"collectionIdPlaceholder": "e.g., {{collectionName}}1",
|
||||
"collectionIdAriaLabel": "{{collectionName}} id, Example {{collectionName}}1",
|
||||
"existingDatabaseAriaLabel": "Choose existing {{databaseName}} id",
|
||||
"existingDatabasePlaceholder": "Choose existing {{databaseName}} id",
|
||||
"indexing": "Indexing",
|
||||
"turnOnIndexing": "Turn on indexing",
|
||||
"automatic": "Automatic",
|
||||
"turnOffIndexing": "Turn off indexing",
|
||||
"off": "Off",
|
||||
"sharding": "Sharding",
|
||||
"shardingTooltip": "Sharded collections split your data across many replica sets (shards) to achieve unlimited scalability. Sharded collections require choosing a shard key (field) to evenly distribute your data.",
|
||||
"unsharded": "Unsharded",
|
||||
"unshardedLabel": "Unsharded (20GB limit)",
|
||||
"sharded": "Sharded",
|
||||
"addPartitionKey": "Add hierarchical partition key",
|
||||
"hierarchicalPartitionKeyInfo": "This feature allows you to partition your data with up to three levels of keys for better data distribution. Requires .NET V3, Java V4 SDK, or preview JavaScript V3 SDK.",
|
||||
"provisionDedicatedThroughput": "Provision dedicated throughput for this {{collectionName}}",
|
||||
"provisionDedicatedThroughputTooltip": "You can optionally provision dedicated throughput for a {{collectionName}} within a database that has throughput provisioned. This dedicated throughput amount will not be shared with other {{collectionNamePlural}} in the database and does not count towards the throughput you provisioned for the database. This throughput amount will be billed in addition to the throughput amount you provisioned at the database level.",
|
||||
"uniqueKeysPlaceholderMongo": "Comma separated paths e.g. firstName,address.zipCode",
|
||||
"uniqueKeysPlaceholderSql": "Comma separated paths e.g. /firstName,/address/zipCode",
|
||||
"addUniqueKey": "Add unique key",
|
||||
"enableAnalyticalStore": "Enable analytical store",
|
||||
"disableAnalyticalStore": "Disable analytical store",
|
||||
"on": "On",
|
||||
"analyticalStoreSynapseLinkRequired": "Azure Synapse Link is required for creating an analytical store {{collectionName}}. Enable Synapse Link for this Cosmos DB account.",
|
||||
"enable": "Enable",
|
||||
"containerVectorPolicy": "Container Vector Policy",
|
||||
"containerFullTextSearchPolicy": "Container Full Text Search Policy",
|
||||
"advanced": "Advanced",
|
||||
"mongoIndexingTooltip": "The _id field is indexed by default. Creating a wildcard index for all fields will optimize queries and is recommended for development.",
|
||||
"createWildcardIndex": "Create a Wildcard Index on all fields",
|
||||
"legacySdkCheckbox": "My application uses an older Cosmos .NET or Java SDK version (.NET V1 or Java V2)",
|
||||
"legacySdkInfo": "To ensure compatibility with older SDKs, the created container will use a legacy partitioning scheme that supports partition key values of size only up to 101 bytes. If this is enabled, you will not be able to use hierarchical partition keys.",
|
||||
"indexingOnInfo": "All properties in your documents will be indexed by default for flexible and efficient queries.",
|
||||
"indexingOffInfo": "Indexing will be turned off. Recommended if you don't need to run queries or only have key value operations.",
|
||||
"indexingOffWarning": "By creating this container with indexing turned off, you will not be able to make any indexing policy changes. Indexing changes are only allowed on a container with a indexing policy.",
|
||||
"acknowledgeSpendErrorMonthly": "Please acknowledge the estimated monthly spend.",
|
||||
"acknowledgeSpendErrorDaily": "Please acknowledge the estimated daily spend.",
|
||||
"unshardedMaxRuError": "Unsharded collections support up to 10,000 RUs",
|
||||
"acknowledgeShareThroughputError": "Please acknowledge the estimated cost of this dedicated throughput.",
|
||||
"vectorPolicyError": "Please fix errors in container vector policy",
|
||||
"fullTextSearchPolicyError": "Please fix errors in container full text search policy",
|
||||
"addingSampleDataSet": "Adding sample data set"
|
||||
},
|
||||
"addCollectionUtility": {
|
||||
"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": "The {{partitionKeyName}} 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.",
|
||||
"partitionKeyTooltipSqlSuffix": " For small read-heavy workloads or write-heavy workloads of any size, id is often a good choice.",
|
||||
"shardKeyLabel": "Shard key",
|
||||
"partitionKeyLabel": "Partition key",
|
||||
"shardKeyPlaceholder": "e.g., categoryId",
|
||||
"partitionKeyPlaceholderDefault": "e.g., /address",
|
||||
"partitionKeyPlaceholderFirst": "Required - first partition key e.g., /TenantId",
|
||||
"partitionKeyPlaceholderSecond": "second partition key e.g., /UserId",
|
||||
"partitionKeyPlaceholderThird": "third partition key e.g., /SessionId",
|
||||
"partitionKeyPlaceholderGraph": "e.g., /address/zipCode",
|
||||
"uniqueKeysTooltip": "Unique keys provide developers with the ability to add a layer of data integrity to their database. By creating a unique key policy when a container is created, you ensure the uniqueness of one or more values per partition key.",
|
||||
"uniqueKeysLabel": "Unique keys",
|
||||
"analyticalStoreLabel": "Analytical Store",
|
||||
"analyticalStoreTooltip": "Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads.",
|
||||
"analyticalStoreDescription": "Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads.",
|
||||
"vectorPolicyTooltip": "Describe any properties in your data that contain vectors, so that they can be made available for similarity queries."
|
||||
},
|
||||
"settings": {
|
||||
"pageOptions": "Page Options",
|
||||
"pageOptionsDescription": "Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as many query results per page.",
|
||||
"queryResultsPerPage": "Query results per page",
|
||||
"queryResultsPerPageTooltip": "Enter the number of query results that should be shown per page.",
|
||||
"customQueryItemsPerPage": "Custom query items per page",
|
||||
"custom": "Custom",
|
||||
"unlimited": "Unlimited",
|
||||
"entraIdRbac": "Enable Entra ID RBAC",
|
||||
"entraIdRbacDescription": "Choose Automatic to enable Entra ID RBAC automatically. True/False to force enable/disable Entra ID RBAC.",
|
||||
"true": "True",
|
||||
"false": "False",
|
||||
"regionSelection": "Region Selection",
|
||||
"regionSelectionDescription": "Changes region the Cosmos Client uses to access account.",
|
||||
"selectRegion": "Select Region",
|
||||
"selectRegionTooltip": "Changes the account endpoint used to perform client operations.",
|
||||
"globalDefault": "Global (Default)",
|
||||
"readWrite": "(Read/Write)",
|
||||
"read": "(Read)",
|
||||
"queryTimeout": "Query Timeout",
|
||||
"queryTimeoutDescription": "When a query reaches a specified time limit, a popup with an option to cancel the query will show unless automatic cancellation has been enabled.",
|
||||
"enableQueryTimeout": "Enable query timeout",
|
||||
"queryTimeoutMs": "Query timeout (ms)",
|
||||
"automaticallyCancelQuery": "Automatically cancel query after timeout",
|
||||
"ruLimit": "RU Limit",
|
||||
"ruLimitDescription": "If a query exceeds a configured RU limit, the query will be aborted.",
|
||||
"enableRuLimit": "Enable RU limit",
|
||||
"ruLimitLabel": "RU Limit (RU)",
|
||||
"defaultQueryResults": "Default Query Results View",
|
||||
"defaultQueryResultsDescription": "Select the default view to use when displaying query results.",
|
||||
"retrySettings": "Retry Settings",
|
||||
"retrySettingsDescription": "Retry policy associated with throttled requests during CosmosDB queries.",
|
||||
"maxRetryAttempts": "Max retry attempts",
|
||||
"maxRetryAttemptsTooltip": "Max number of retries to be performed for a request. Default value 9.",
|
||||
"fixedRetryInterval": "Fixed retry interval (ms)",
|
||||
"fixedRetryIntervalTooltip": "Fixed retry interval in milliseconds to wait between each retry ignoring the retryAfter returned as part of the response. Default value is 0 milliseconds.",
|
||||
"maxWaitTime": "Max wait time (s)",
|
||||
"maxWaitTimeTooltip": "Max wait time in seconds to wait for a request while the retries are happening. Default value 30 seconds.",
|
||||
"enableContainerPagination": "Enable container pagination",
|
||||
"enableContainerPaginationDescription": "Load 50 containers at a time. Currently, containers are not pulled in alphanumeric order.",
|
||||
"enableCrossPartitionQuery": "Enable cross-partition query",
|
||||
"enableCrossPartitionQueryDescription": "Send more than one request while executing a query. More than one request is necessary if the query is not scoped to single partition key value.",
|
||||
"maxDegreeOfParallelism": "Max degree of parallelism",
|
||||
"maxDegreeOfParallelismDescription": "Gets or sets the number of concurrent operations run client side during parallel query execution. A positive property value limits the number of concurrent operations to the set value. If it is set to less than 0, the system automatically decides the number of concurrent operations to run.",
|
||||
"maxDegreeOfParallelismQuery": "Query up to the max degree of parallelism.",
|
||||
"priorityLevel": "Priority Level",
|
||||
"priorityLevelDescription": "Sets the priority level for data-plane requests from Data Explorer when using Priority-Based Execution. If \"None\" is selected, Data Explorer will not specify priority level, and the server-side default priority level will be used.",
|
||||
"displayGremlinQueryResults": "Display Gremlin query results as:",
|
||||
"displayGremlinQueryResultsDescription": "Select Graph to automatically visualize the query results as a Graph or JSON to display the results as JSON.",
|
||||
"graph": "Graph",
|
||||
"json": "JSON",
|
||||
"graphAutoVisualization": "Graph Auto-visualization",
|
||||
"enableSampleDatabase": "Enable sample database",
|
||||
"enableSampleDatabaseDescription": "This is a sample database and collection with synthetic product data you can use to explore using NoSQL queries. This will appear as another database in the Data Explorer UI, and is created by, and maintained by Microsoft at no cost to you.",
|
||||
"enableSampleDbAriaLabel": "Enable sample db for query exploration",
|
||||
"guidRepresentation": "Guid Representation",
|
||||
"guidRepresentationDescription": "GuidRepresentation in MongoDB refers to how Globally Unique Identifiers (GUIDs) are serialized and deserialized when stored in BSON documents. This will apply to all document operations.",
|
||||
"advancedSettings": "Advanced Settings",
|
||||
"ignorePartitionKey": "Ignore partition key on document update",
|
||||
"ignorePartitionKeyTooltip": "If checked, the partition key value will not be used to locate the document during update operations. Only use this if document updates are failing due to an abnormal partition key.",
|
||||
"clearHistory": "Clear History",
|
||||
"clearHistoryConfirm": "Are you sure you want to proceed?",
|
||||
"clearHistoryDescription": "This action will clear the all customizations for this account in this browser, including:",
|
||||
"clearHistoryTabLayout": "Reset your customized tab layout, including the splitter positions",
|
||||
"clearHistoryTableColumns": "Erase your table column preferences, including any custom columns",
|
||||
"clearHistoryFilters": "Clear your filter history",
|
||||
"clearHistoryRegion": "Reset region selection to global",
|
||||
"increaseValueBy1000": "Increase value by 1000",
|
||||
"decreaseValueBy1000": "Decrease value by 1000",
|
||||
"none": "None",
|
||||
"low": "Low",
|
||||
"high": "High",
|
||||
"automatic": "Automatic",
|
||||
"enhancedQueryControl": "Enhanced query control",
|
||||
"enableQueryControl": "Enable query control",
|
||||
"explorerVersion": "Explorer Version",
|
||||
"accountId": "Account ID",
|
||||
"sessionId": "Session ID",
|
||||
"popupsDisabledError": "We were unable to establish authorization for this account, due to pop-ups being disabled in the browser.\nPlease enable pop-ups for this site and click on \"Login for Entra ID\" button",
|
||||
"failedToAcquireTokenError": "Failed to acquire authorization token automatically. Please click on \"Login for Entra ID\" button to enable Entra ID RBAC operations"
|
||||
},
|
||||
"saveQuery": {
|
||||
"panelTitle": "Save Query",
|
||||
"setupCostMessage": "For compliance reasons, we save queries in a container in your Azure Cosmos account, in a separate database called \u201c{{databaseName}}\u201d. To proceed, we need to create a container in your account, estimated additional cost is $0.77 daily.",
|
||||
"completeSetup": "Complete setup",
|
||||
"noQueryNameError": "No query name specified",
|
||||
"invalidQueryContentError": "Invalid query content specified",
|
||||
"failedToSaveQueryError": "Failed to save query {{queryName}}",
|
||||
"failedToSetupContainerError": "Failed to setup a container for saved queries",
|
||||
"accountNotSetupError": "Failed to save query: account not setup to save queries",
|
||||
"name": "Name"
|
||||
},
|
||||
"loadQuery": {
|
||||
"noFileSpecifiedError": "No file specified",
|
||||
"failedToLoadQueryError": "Failed to load query",
|
||||
"failedToLoadQueryFromFileError": "Failed to load query from file {{fileName}}",
|
||||
"selectFilesToOpen": "Select a query document",
|
||||
"browseFiles": "Browse"
|
||||
},
|
||||
"executeStoredProcedure": {
|
||||
"enterInputParameters": "Enter input parameters (if any)",
|
||||
"key": "Key",
|
||||
"param": "Param",
|
||||
"partitionKeyValue": "Partition key value",
|
||||
"value": "Value",
|
||||
"addNewParam": "Add New Param",
|
||||
"addParam": "Add param",
|
||||
"deleteParam": "Delete param",
|
||||
"invalidParamError": "Invalid param specified: {{invalidParam}}",
|
||||
"invalidParamConsoleError": "Invalid param specified: {{invalidParam}} is not a valid literal value",
|
||||
"stringType": "String",
|
||||
"customType": "Custom"
|
||||
},
|
||||
"uploadItems": {
|
||||
"noFilesSpecifiedError": "No files were specified. Please input at least one file.",
|
||||
"selectJsonFiles": "Select JSON Files",
|
||||
"selectJsonFilesTooltip": "Select one or more JSON files to upload. Each file can contain a single JSON document or an array of JSON documents. The combined size of all files in an individual upload operation must be less than 2 MB. You can perform multiple upload operations for larger data sets.",
|
||||
"fileNameColumn": "FILE NAME",
|
||||
"statusColumn": "STATUS",
|
||||
"uploadStatus": "{{numSucceeded}} created, {{numThrottled}} throttled, {{numFailed}} errors",
|
||||
"uploadedFiles": "Uploaded files"
|
||||
},
|
||||
"copyNotebook": {
|
||||
"copyFailedError": "Failed to copy {{name}} to {{destination}}",
|
||||
"uploadFailedError": "Failed to upload {{name}}",
|
||||
"location": "Location",
|
||||
"locationAriaLabel": "Location",
|
||||
"selectLocation": "Select a notebook location to copy",
|
||||
"name": "Name"
|
||||
},
|
||||
"publishNotebook": {
|
||||
"publishFailedError": "Failed to publish {{notebookName}} to gallery",
|
||||
"publishDescription": "When published, this notebook will appear in the Azure Cosmos DB notebooks public gallery. Make sure you have removed any sensitive data or output before publishing.",
|
||||
"publishPrompt": "Would you like to publish and share \"{{name}}\" to the gallery?",
|
||||
"coverImage": "Cover image",
|
||||
"coverImageUrl": "Cover image url",
|
||||
"name": "Name",
|
||||
"description": "Description",
|
||||
"tags": "Tags",
|
||||
"tagsPlaceholder": "Optional tag 1, Optional tag 2",
|
||||
"preview": "Preview",
|
||||
"urlType": "URL",
|
||||
"customImage": "Custom Image",
|
||||
"takeScreenshot": "Take Screenshot",
|
||||
"useFirstDisplayOutput": "Use First Display Output",
|
||||
"failedToCaptureOutput": "Failed to capture first output",
|
||||
"outputDoesNotExist": "Output does not exist for any of the cells.",
|
||||
"failedToConvertError": "Failed to convert {{fileName}} to base64 format",
|
||||
"failedToUploadError": "Failed to upload {{fileName}}"
|
||||
},
|
||||
"changePartitionKey": {
|
||||
"failedToStartError": "Failed to start data transfer job",
|
||||
"suboptimalPartitionKeyError": "Warning: The system has detected that your collection may be using a suboptimal partition key",
|
||||
"description": "When changing a container\u2019s partition key, you will need to create a destination container with the correct partition key. You may also select an existing destination container.",
|
||||
"sourceContainerId": "Source {{collectionName}} id",
|
||||
"destinationContainerId": "Destination {{collectionName}} id",
|
||||
"collectionIdTooltip": "Unique identifier for the {{collectionName}} and used for id-based routing through REST and all SDKs.",
|
||||
"collectionIdPlaceholder": "e.g., {{collectionName}}1",
|
||||
"collectionIdAriaLabel": "{{collectionName}} id, Example {{collectionName}}1",
|
||||
"existingContainers": "Existing Containers",
|
||||
"partitionKeyWarning": "The destination container must not already exist. Data Explorer will create a new destination container for you."
|
||||
},
|
||||
"cassandraAddCollection": {
|
||||
"keyspaceLabel": "Keyspace name",
|
||||
"keyspaceTooltip": "Select an existing keyspace or enter a new keyspace id.",
|
||||
"tableIdLabel": "Enter CQL command to create the table.",
|
||||
"enterTableId": "Enter table Id",
|
||||
"tableSchemaAriaLabel": "Table schema",
|
||||
"provisionDedicatedThroughput": "Provision dedicated throughput for this table",
|
||||
"provisionDedicatedThroughputTooltip": "You can optionally provision dedicated throughput for a table within a keyspace that has throughput provisioned. This dedicated throughput amount will not be shared with other tables in the keyspace and does not count towards the throughput you provisioned for the keyspace. This throughput amount will be billed in addition to the throughput amount you provisioned at the keyspace level."
|
||||
},
|
||||
"tables": {
|
||||
"addProperty": "Add Property",
|
||||
"addRow": "Add Row",
|
||||
"addEntity": "Add Entity",
|
||||
"back": "back",
|
||||
"nullFieldsWarning": "Warning: Null fields will not be displayed for editing.",
|
||||
"propertyEmptyError": "{{property}} cannot be empty. Please input a value for {{property}}",
|
||||
"whitespaceError": "{{property}} cannot have whitespace. Please input a value for {{property}} without whitespace",
|
||||
"propertyTypeEmptyError": "Property type cannot be empty. Please select a type from the dropdown for property {{property}}"
|
||||
},
|
||||
"tableQuerySelect": {
|
||||
"selectColumns": "Select the columns that you want to query.",
|
||||
"availableColumns": "Available Columns"
|
||||
},
|
||||
"tableColumnSelection": {
|
||||
"selectColumns": "Select which columns to display in your view of items in your container.",
|
||||
"searchFields": "Search fields",
|
||||
"reset": "Reset",
|
||||
"partitionKeySuffix": " (partition key)"
|
||||
},
|
||||
"newVertex": {
|
||||
"addProperty": "Add Property"
|
||||
},
|
||||
"addGlobalSecondaryIndex": {
|
||||
"globalSecondaryIndexId": "Global secondary index container id",
|
||||
"globalSecondaryIndexIdPlaceholder": "e.g., indexbyEmailId",
|
||||
"projectionQuery": "Projection query",
|
||||
"projectionQueryPlaceholder": "SELECT c.email, c.accountId FROM c",
|
||||
"projectionQueryTooltip": "Learn more about defining global secondary indexes.",
|
||||
"disabledTitle": "A global secondary index is already being created. Please wait for it to complete before creating another one."
|
||||
},
|
||||
"stringInput": {
|
||||
"inputMismatchError": "Input {{input}} does not match the selected {{selectedId}}"
|
||||
},
|
||||
"panelInfo": {
|
||||
"information": "Information",
|
||||
"moreDetails": "More details"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ export async function getMsalInstance() {
|
||||
auth: {
|
||||
authority: `${configContext.AAD_ENDPOINT}organizations`,
|
||||
clientId: "203f1145-856a-4232-83d4-a43568fba23d",
|
||||
knownAuthorities: [configContext.AAD_ENDPOINT],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user