2021-03-18 18:06:13 -07:00
import {
ActionButton ,
Checkbox ,
DefaultButton ,
DirectionalHint ,
Dropdown ,
Icon ,
IconButton ,
IDropdownOption ,
Link ,
2022-05-16 17:45:50 -07:00
ProgressIndicator ,
2021-04-30 10:23:34 -07:00
Separator ,
2021-03-18 18:06:13 -07:00
Stack ,
2022-05-04 18:24:34 -07:00
TeachingBubble ,
2021-03-18 18:06:13 -07:00
Text ,
TooltipHost ,
2021-05-06 04:56:03 +05:30
} from "@fluentui/react" ;
2021-10-12 20:08:34 +05:30
import * as Constants from "Common/Constants" ;
import { createCollection } from "Common/dataAccess/createCollection" ;
import { getErrorMessage , getErrorStack } from "Common/ErrorHandlingUtils" ;
import { configContext , Platform } from "ConfigContext" ;
import * as DataModels from "Contracts/DataModels" ;
import { SubscriptionType } from "Contracts/SubscriptionType" ;
import { useSidePanel } from "hooks/useSidePanel" ;
2022-05-16 17:45:50 -07:00
import { useTeachingBubble } from "hooks/useTeachingBubble" ;
2021-03-18 18:06:13 -07:00
import React from "react" ;
2021-10-12 20:08:34 +05:30
import { CollectionCreation } from "Shared/Constants" ;
import { Action } from "Shared/Telemetry/TelemetryConstants" ;
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor" ;
import { userContext } from "UserContext" ;
import { getCollectionName } from "Utils/APITypeUtils" ;
import { isCapabilityEnabled , isServerlessAccount } from "Utils/CapabilityUtils" ;
import { getUpsellMessage } from "Utils/PricingUtils" ;
2021-03-18 18:06:13 -07:00
import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent" ;
import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput" ;
2022-05-04 18:24:34 -07:00
import { ContainerSampleGenerator } from "../DataSamples/ContainerSampleGenerator" ;
2021-03-18 18:06:13 -07:00
import Explorer from "../Explorer" ;
2021-06-18 11:25:08 -07:00
import { useDatabases } from "../useDatabases" ;
2021-03-18 18:06:13 -07:00
import { PanelFooterComponent } from "./PanelFooterComponent" ;
import { PanelInfoErrorComponent } from "./PanelInfoErrorComponent" ;
import { PanelLoadingScreen } from "./PanelLoadingScreen" ;
export interface AddCollectionPanelProps {
explorer : Explorer ;
2021-04-30 10:23:34 -07:00
databaseId? : string ;
2022-05-04 18:24:34 -07:00
isQuickstart? : boolean ;
2021-03-18 18:06:13 -07:00
}
2021-05-20 20:34:29 -05:00
const SharedDatabaseDefault : DataModels.IndexingPolicy = {
indexingMode : "consistent" ,
automatic : true ,
includedPaths : [ ] ,
excludedPaths : [
{
path : "/*" ,
} ,
] ,
} ;
2023-06-06 11:43:53 -07:00
export const AllPropertiesIndexed : DataModels.IndexingPolicy = {
2021-05-20 20:34:29 -05:00
indexingMode : "consistent" ,
automatic : true ,
includedPaths : [
{
path : "/*" ,
indexes : [
{
kind : "Range" ,
dataType : "Number" ,
precision : - 1 ,
} ,
{
kind : "Range" ,
dataType : "String" ,
precision : - 1 ,
} ,
] ,
} ,
] ,
excludedPaths : [ ] ,
} ;
2021-03-18 18:06:13 -07:00
export interface AddCollectionPanelState {
createNewDatabase : boolean ;
newDatabaseId : string ;
isSharedThroughputChecked : boolean ;
selectedDatabaseId : string ;
collectionId : string ;
enableIndexing : boolean ;
isSharded : boolean ;
partitionKey : string ;
2023-01-23 09:09:29 -06:00
subPartitionKeys : string [ ] ;
2021-03-18 18:06:13 -07:00
enableDedicatedThroughput : boolean ;
createMongoWildCardIndex : boolean ;
2023-01-23 09:09:29 -06:00
useHashV1 : boolean ;
2021-03-18 18:06:13 -07:00
enableAnalyticalStore : boolean ;
uniqueKeys : string [ ] ;
errorMessage : string ;
showErrorDetails : boolean ;
isExecuting : boolean ;
2021-10-30 19:45:16 -07:00
isThroughputCapExceeded : boolean ;
2022-05-04 18:24:34 -07:00
teachingBubbleStep : number ;
2021-03-18 18:06:13 -07:00
}
export class AddCollectionPanel extends React . Component < AddCollectionPanelProps , AddCollectionPanelState > {
private newDatabaseThroughput : number ;
private isNewDatabaseAutoscale : boolean ;
private collectionThroughput : number ;
private isCollectionAutoscale : boolean ;
private isCostAcknowledged : boolean ;
constructor ( props : AddCollectionPanelProps ) {
super ( props ) ;
this . state = {
2021-04-30 10:23:34 -07:00
createNewDatabase : userContext.apiType !== "Tables" && ! this . props . databaseId ,
2022-05-23 20:52:21 -07:00
newDatabaseId : props.isQuickstart ? this . getSampleDBName ( ) : "" ,
2021-03-18 18:06:13 -07:00
isSharedThroughputChecked : this.getSharedThroughputDefault ( ) ,
2021-04-30 10:23:34 -07:00
selectedDatabaseId :
userContext . apiType === "Tables" ? CollectionCreation.TablesAPIDefaultDatabase : this.props.databaseId ,
2022-05-04 18:24:34 -07:00
collectionId : props.isQuickstart ? ` Sample ${ getCollectionName ( ) } ` : "" ,
2021-03-18 18:06:13 -07:00
enableIndexing : true ,
2021-04-29 00:55:04 +05:30
isSharded : userContext.apiType !== "Tables" ,
2021-07-29 06:48:03 -07:00
partitionKey : this.getPartitionKey ( ) ,
2023-01-23 09:09:29 -06:00
subPartitionKeys : [ ] ,
2021-03-18 18:06:13 -07:00
enableDedicatedThroughput : false ,
2022-09-29 15:36:00 -07:00
createMongoWildCardIndex :
isCapabilityEnabled ( "EnableMongo" ) && ! isCapabilityEnabled ( "EnableMongo16MBDocumentSupport" ) ,
2023-01-23 09:09:29 -06:00
useHashV1 : false ,
2021-03-18 18:06:13 -07:00
enableAnalyticalStore : false ,
uniqueKeys : [ ] ,
errorMessage : "" ,
showErrorDetails : false ,
isExecuting : false ,
2021-10-30 19:45:16 -07:00
isThroughputCapExceeded : false ,
2022-05-04 18:24:34 -07:00
teachingBubbleStep : 0 ,
2021-03-18 18:06:13 -07:00
} ;
}
2022-05-04 18:24:34 -07:00
componentDidMount ( ) : void {
2022-05-16 17:45:50 -07:00
if ( this . state . teachingBubbleStep === 0 && this . props . isQuickstart ) {
2022-05-04 18:24:34 -07:00
this . setState ( { teachingBubbleStep : 1 } ) ;
}
}
2021-03-18 18:06:13 -07:00
render ( ) : JSX . Element {
2021-06-18 11:25:08 -07:00
const isFirstResourceCreated = useDatabases . getState ( ) . isFirstResourceCreated ( ) ;
2021-03-18 18:06:13 -07:00
return (
< form className = "panelFormWrapper" onSubmit = { this . submit . bind ( this ) } >
{ this . state . errorMessage && (
< PanelInfoErrorComponent
message = { this . state . errorMessage }
messageType = "error"
showErrorDetails = { this . state . showErrorDetails }
/ >
) }
{ ! this . state . errorMessage && this . isFreeTierAccount ( ) && (
< PanelInfoErrorComponent
2021-06-18 11:25:08 -07:00
message = { getUpsellMessage ( userContext . portalEnv , true , isFirstResourceCreated , true ) }
2021-03-18 18:06:13 -07:00
messageType = "info"
showErrorDetails = { false }
link = { Constants . Urls . freeTierInformation }
linkText = "Learn more"
/ >
) }
2022-05-04 18:24:34 -07:00
{ this . state . teachingBubbleStep === 1 && (
< TeachingBubble
headline = "Create sample database"
target = { "#newDatabaseId" }
calloutProps = { { gapSpace : 16 } }
primaryButtonProps = { { text : "Next" , onClick : ( ) = > this . setState ( { teachingBubbleStep : 2 } ) } }
secondaryButtonProps = { { text : "Cancel" , onClick : ( ) = > this . setState ( { teachingBubbleStep : 0 } ) } }
onDismiss = { ( ) = > this . setState ( { teachingBubbleStep : 0 } ) }
footerContent = "Step 1 of 4"
>
2022-05-23 20:52:21 -07:00
< Stack >
< Text style = { { color : "white" } } >
Database is the parent of a container . You can create a new database or use an existing one . In this
tutorial we are creating a new database named SampleDB .
< / Text >
< Link
style = { { color : "white" , fontWeight : 600 } }
target = "_blank"
href = "https://aka.ms/TeachingbubbleResources"
>
Learn more about resources .
< / Link >
< / Stack >
2022-05-04 18:24:34 -07:00
< / TeachingBubble >
) }
{ this . state . teachingBubbleStep === 2 && (
< TeachingBubble
headline = "Setting throughput"
target = { "#autoscaleRUValueField" }
calloutProps = { { gapSpace : 16 } }
primaryButtonProps = { { text : "Next" , onClick : ( ) = > this . setState ( { teachingBubbleStep : 3 } ) } }
secondaryButtonProps = { { text : "Previous" , onClick : ( ) = > this . setState ( { teachingBubbleStep : 1 } ) } }
onDismiss = { ( ) = > this . setState ( { teachingBubbleStep : 0 } ) }
footerContent = "Step 2 of 4"
>
2022-05-23 20:52:21 -07:00
< Stack >
< Text style = { { color : "white" } } >
Cosmos DB recommends sharing throughput across database . Autoscale will give you a flexible amount of
throughput based on the max RU / s set ( Request Units ) .
< / Text >
< Link style = { { color : "white" , fontWeight : 600 } } target = "_blank" href = "https://aka.ms/teachingbubbleRU" >
Learn more about RU / s .
< / Link >
< / Stack >
2022-05-04 18:24:34 -07:00
< / TeachingBubble >
) }
{ this . state . teachingBubbleStep === 3 && (
< TeachingBubble
headline = "Naming container"
target = { "#collectionId" }
calloutProps = { { gapSpace : 16 } }
primaryButtonProps = { { text : "Next" , onClick : ( ) = > this . setState ( { teachingBubbleStep : 4 } ) } }
secondaryButtonProps = { { text : "Previous" , onClick : ( ) = > this . setState ( { teachingBubbleStep : 2 } ) } }
onDismiss = { ( ) = > this . setState ( { teachingBubbleStep : 0 } ) }
footerContent = "Step 3 of 4"
>
Name your container
< / TeachingBubble >
) }
{ this . state . teachingBubbleStep === 4 && (
< TeachingBubble
headline = "Setting partition key"
target = { "#addCollection-partitionKeyValue" }
calloutProps = { { gapSpace : 16 } }
primaryButtonProps = { {
text : "Create container" ,
onClick : ( ) = > {
2022-05-16 17:45:50 -07:00
this . setState ( { teachingBubbleStep : 5 } ) ;
2022-05-04 18:24:34 -07:00
this . submit ( ) ;
} ,
} }
secondaryButtonProps = { { text : "Previous" , onClick : ( ) = > this . setState ( { teachingBubbleStep : 2 } ) } }
onDismiss = { ( ) = > this . setState ( { teachingBubbleStep : 0 } ) }
footerContent = "Step 4 of 4"
>
Last step - you will need to define a partition key for your collection . / address was chosen for this
particular example . A good partition key should have a wide range of possible value
< / TeachingBubble >
) }
2021-03-18 18:06:13 -07:00
< div className = "panelMainContent" >
2021-04-29 00:55:04 +05:30
< Stack hidden = { userContext . apiType === "Tables" } >
2021-03-18 18:06:13 -07:00
< Stack horizontal >
< span className = "mandatoryStar" > * & nbsp ; < / span >
< Text className = "panelTextBold" variant = "small" >
2021-04-30 10:23:34 -07:00
Database { userContext . apiType === "Mongo" ? "name" : "id" }
2021-03-18 18:06:13 -07:00
< / Text >
< TooltipHost
directionalHint = { DirectionalHint . bottomLeftEdge }
2021-04-30 10:23:34 -07:00
content = { ` A database is analogous to a namespace. It is the unit of management for a set of ${ getCollectionName (
true
) . toLocaleLowerCase ( ) } . ` }
2021-03-18 18:06:13 -07:00
>
2023-02-04 01:51:11 +05:30
< Icon
iconName = "Info"
className = "panelInfoIcon"
tabIndex = { 0 }
ariaLabel = { ` A database is analogous to a namespace. It is the unit of management for a set of ${ getCollectionName (
true
) . toLocaleLowerCase ( ) } . ` }
/ >
2021-03-18 18:06:13 -07:00
< / TooltipHost >
< / Stack >
< Stack horizontal verticalAlign = "center" >
2023-03-14 23:49:21 +05:30
< div role = "radiogroup" >
< input
className = "panelRadioBtn"
checked = { this . state . createNewDatabase }
aria - label = "Create new database"
aria - checked = { this . state . createNewDatabase }
name = "databaseType"
type = "radio"
role = "radio"
id = "databaseCreateNew"
tabIndex = { 0 }
onChange = { this . onCreateNewDatabaseRadioBtnChange . bind ( this ) }
/ >
< span className = "panelRadioBtnLabel" > Create new < / span >
2021-03-18 18:06:13 -07:00
2023-03-14 23:49:21 +05:30
< input
className = "panelRadioBtn"
checked = { ! this . state . createNewDatabase }
aria - label = "Use existing database"
aria - checked = { ! this . state . createNewDatabase }
name = "databaseType"
type = "radio"
role = "radio"
tabIndex = { 0 }
onChange = { this . onUseExistingDatabaseRadioBtnChange . bind ( this ) }
/ >
< span className = "panelRadioBtnLabel" > Use existing < / span >
< / div >
2021-03-18 18:06:13 -07:00
< / Stack >
{ this . state . createNewDatabase && (
< Stack className = "panelGroupSpacing" >
< input
name = "newDatabaseId"
2021-04-30 10:23:34 -07:00
id = "newDatabaseId"
2021-03-18 18:06:13 -07:00
aria - required
required
type = "text"
autoComplete = "off"
pattern = "[^/?#\\]*[^/?# \\]"
title = "May not end with space nor contain characters '\' '/' '#' '?'"
placeholder = "Type a new database id"
size = { 40 }
className = "panelTextField"
2021-04-30 10:23:34 -07:00
aria - label = "New database id"
2021-03-18 18:06:13 -07:00
autoFocus
2021-09-17 02:54:47 +05:30
tabIndex = { 0 }
2021-03-18 18:06:13 -07:00
value = { this . state . newDatabaseId }
onChange = { ( event : React.ChangeEvent < HTMLInputElement > ) = >
this . setState ( { newDatabaseId : event.target.value } )
}
/ >
2021-05-12 11:56:24 -07:00
{ ! isServerlessAccount ( ) && (
2021-03-18 18:06:13 -07:00
< Stack horizontal >
< Checkbox
2021-04-30 10:23:34 -07:00
label = { ` Share throughput across ${ getCollectionName ( true ) . toLocaleLowerCase ( ) } ` }
2021-03-18 18:06:13 -07:00
checked = { this . state . isSharedThroughputChecked }
styles = { {
text : { fontSize : 12 } ,
checkbox : { width : 12 , height : 12 } ,
label : { padding : 0 , alignItems : "center" } ,
} }
onChange = { ( ev : React.FormEvent < HTMLElement > , isChecked : boolean ) = >
this . setState ( { isSharedThroughputChecked : isChecked } )
}
/ >
< TooltipHost
directionalHint = { DirectionalHint . bottomLeftEdge }
2021-04-30 10:23:34 -07:00
content = { ` Throughput configured at the database level will be shared across all ${ getCollectionName (
true
) . toLocaleLowerCase ( ) } within the database . ` }
2021-03-18 18:06:13 -07:00
>
2023-02-04 01:51:11 +05:30
< Icon
iconName = "Info"
className = "panelInfoIcon"
tabIndex = { 0 }
ariaLabel = { ` Throughput configured at the database level will be shared across all ${ getCollectionName (
true
) . toLocaleLowerCase ( ) } within the database . ` }
/ >
2021-03-18 18:06:13 -07:00
< / TooltipHost >
< / Stack >
) }
2021-05-12 11:56:24 -07:00
{ ! isServerlessAccount ( ) && this . state . isSharedThroughputChecked && (
2021-03-18 18:06:13 -07:00
< ThroughputInput
2021-06-18 11:25:08 -07:00
showFreeTierExceedThroughputTooltip = { this . isFreeTierAccount ( ) && ! isFirstResourceCreated }
2021-03-18 18:06:13 -07:00
isDatabase = { true }
2021-04-30 10:23:34 -07:00
isSharded = { this . state . isSharded }
2022-02-25 18:21:58 -08:00
isFreeTier = { this . isFreeTierAccount ( ) }
2022-08-03 13:54:01 -07:00
isQuickstart = { this . props . isQuickstart }
2021-03-18 18:06:13 -07:00
setThroughputValue = { ( throughput : number ) = > ( this . newDatabaseThroughput = throughput ) }
setIsAutoscale = { ( isAutoscale : boolean ) = > ( this . isNewDatabaseAutoscale = isAutoscale ) }
2021-10-30 19:45:16 -07:00
setIsThroughputCapExceeded = { ( isThroughputCapExceeded : boolean ) = >
this . setState ( { isThroughputCapExceeded } )
}
2021-03-18 18:06:13 -07:00
onCostAcknowledgeChange = { ( isAcknowledge : boolean ) = > ( this . isCostAcknowledged = isAcknowledge ) }
/ >
) }
< / Stack >
) }
{ ! this . state . createNewDatabase && (
< Dropdown
styles = { { title : { height : 27 , lineHeight : 27 } , dropdownItem : { fontSize : 12 } } }
style = { { width : 300 , fontSize : 12 } }
placeholder = "Choose an existing database"
options = { this . getDatabaseOptions ( ) }
onChange = { ( event : React.FormEvent < HTMLDivElement > , database : IDropdownOption ) = >
this . setState ( { selectedDatabaseId : database.key as string } )
}
2021-04-30 10:23:34 -07:00
defaultSelectedKey = { this . props . databaseId }
responsiveMode = { 999 }
2021-03-18 18:06:13 -07:00
/ >
) }
2021-04-30 10:23:34 -07:00
< Separator className = "panelSeparator" / >
2021-03-18 18:06:13 -07:00
< / Stack >
< Stack >
< Stack horizontal >
< span className = "mandatoryStar" > * & nbsp ; < / span >
< Text className = "panelTextBold" variant = "small" >
2021-12-28 01:10:12 +05:30
{ ` ${ getCollectionName ( ) } id ` }
2021-03-18 18:06:13 -07:00
< / Text >
< TooltipHost
directionalHint = { DirectionalHint . bottomLeftEdge }
2021-04-30 10:23:34 -07:00
content = { ` Unique identifier for the ${ getCollectionName ( ) . toLocaleLowerCase ( ) } and used for id-based routing through REST and all SDKs. ` }
2021-03-18 18:06:13 -07:00
>
2023-02-04 01:51:11 +05:30
< Icon
2023-04-03 22:11:40 +05:30
role = "button"
2023-02-04 01:51:11 +05:30
iconName = "Info"
className = "panelInfoIcon"
tabIndex = { 0 }
ariaLabel = { ` Unique identifier for the ${ getCollectionName ( ) . toLocaleLowerCase ( ) } and used for id-based routing through REST and all SDKs. ` }
/ >
2021-03-18 18:06:13 -07:00
< / TooltipHost >
< / Stack >
< input
name = "collectionId"
2021-04-30 10:23:34 -07:00
id = "collectionId"
2021-03-18 18:06:13 -07:00
type = "text"
aria - required
required
autoComplete = "off"
pattern = "[^/?#\\]*[^/?# \\]"
title = "May not end with space nor contain characters '\' '/' '#' '?'"
2021-04-30 10:23:34 -07:00
placeholder = { ` e.g., ${ getCollectionName ( ) } 1 ` }
2021-03-18 18:06:13 -07:00
size = { 40 }
className = "panelTextField"
2021-04-30 10:23:34 -07:00
aria - label = { ` ${ getCollectionName ( ) } id ` }
2021-03-18 18:06:13 -07:00
value = { this . state . collectionId }
onChange = { ( event : React.ChangeEvent < HTMLInputElement > ) = >
this . setState ( { collectionId : event.target.value } )
}
/ >
< / Stack >
{ this . shouldShowIndexingOptionsForFreeTierAccount ( ) && (
< Stack >
< Stack horizontal >
< span className = "mandatoryStar" > * & nbsp ; < / span >
< Text className = "panelTextBold" variant = "small" >
Indexing
< / Text >
< / Stack >
< Stack horizontal verticalAlign = "center" >
< input
className = "panelRadioBtn"
checked = { this . state . enableIndexing }
aria - label = "Turn on indexing"
aria - checked = { this . state . enableIndexing }
type = "radio"
role = "radio"
tabIndex = { 0 }
onChange = { this . onTurnOnIndexing . bind ( this ) }
/ >
< span className = "panelRadioBtnLabel" > Automatic < / span >
< input
className = "panelRadioBtn"
checked = { ! this . state . enableIndexing }
aria - label = "Turn off indexing"
aria - checked = { ! this . state . enableIndexing }
type = "radio"
role = "radio"
tabIndex = { 0 }
onChange = { this . onTurnOffIndexing . bind ( this ) }
/ >
< span className = "panelRadioBtnLabel" > Off < / span >
< / Stack >
< Text variant = "small" >
{ this . getFreeTierIndexingText ( ) } { " " }
< Link target = "_blank" href = "https://aka.ms/cosmos-indexing-policy" >
Learn more
< / Link >
< / Text >
< / Stack >
) }
2021-04-29 00:55:04 +05:30
{ userContext . apiType === "Mongo" &&
2021-03-18 18:06:13 -07:00
( ! this . state . isSharedThroughputChecked ||
this . props . explorer . isFixedCollectionWithSharedThroughputSupported ( ) ) && (
< Stack >
< Stack horizontal >
< span className = "mandatoryStar" > * & nbsp ; < / span >
< Text className = "panelTextBold" variant = "small" >
2021-04-30 10:23:34 -07:00
Sharding
2021-03-18 18:06:13 -07:00
< / Text >
< TooltipHost
directionalHint = { DirectionalHint . bottomLeftEdge }
2021-04-30 10:23:34 -07:00
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."
}
2021-03-18 18:06:13 -07:00
>
2023-02-04 01:51:11 +05:30
< Icon
iconName = "Info"
className = "panelInfoIcon"
tabIndex = { 0 }
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."
}
/ >
2021-03-18 18:06:13 -07:00
< / TooltipHost >
< / Stack >
< Stack horizontal verticalAlign = "center" >
< input
className = "panelRadioBtn"
checked = { ! this . state . isSharded }
aria - label = "Unsharded"
aria - checked = { ! this . state . isSharded }
name = "unsharded"
type = "radio"
role = "radio"
id = "unshardedOption"
tabIndex = { 0 }
onChange = { this . onUnshardedRadioBtnChange . bind ( this ) }
/ >
< span className = "panelRadioBtnLabel" > Unsharded ( 20 GB limit ) < / span >
< input
className = "panelRadioBtn"
checked = { this . state . isSharded }
aria - label = "Sharded"
aria - checked = { this . state . isSharded }
name = "sharded"
type = "radio"
role = "radio"
id = "shardedOption"
tabIndex = { 0 }
onChange = { this . onShardedRadioBtnChange . bind ( this ) }
/ >
< span className = "panelRadioBtnLabel" > Sharded < / span >
< / Stack >
< / Stack >
) }
{ this . state . isSharded && (
< Stack >
< Stack horizontal >
< span className = "mandatoryStar" > * & nbsp ; < / span >
< Text className = "panelTextBold" variant = "small" >
{ this . getPartitionKeyName ( ) }
< / Text >
< TooltipHost
directionalHint = { DirectionalHint . bottomLeftEdge }
2021-04-30 10:23:34 -07:00
content = { this . getPartitionKeyTooltipText ( ) }
2021-03-18 18:06:13 -07:00
>
2023-02-04 01:51:11 +05:30
< Icon
iconName = "Info"
className = "panelInfoIcon"
tabIndex = { 0 }
ariaLabel = { this . getPartitionKeyTooltipText ( ) }
/ >
2021-03-18 18:06:13 -07:00
< / TooltipHost >
< / Stack >
2021-07-14 12:10:45 -07:00
< Text variant = "small" aria-label = "pkDescription" >
{ this . getPartitionKeySubtext ( ) }
< / Text >
2021-03-18 18:06:13 -07:00
< input
type = "text"
id = "addCollection-partitionKeyValue"
aria - required
required
size = { 40 }
className = "panelTextField"
placeholder = { this . getPartitionKeyPlaceHolder ( ) }
aria - label = { this . getPartitionKeyName ( ) }
2021-04-29 00:55:04 +05:30
pattern = { userContext . apiType === "Gremlin" ? "^/[^/]*" : ".*" }
title = { userContext . apiType === "Gremlin" ? "May not use composite partition key" : "" }
2021-03-18 18:06:13 -07:00
value = { this . state . partitionKey }
2021-04-30 10:23:34 -07:00
onChange = { ( event : React.ChangeEvent < HTMLInputElement > ) = > {
if (
userContext . apiType !== "Mongo" &&
2021-05-12 11:56:24 -07:00
! this . state . partitionKey &&
2021-04-30 10:23:34 -07:00
! event . target . value . startsWith ( "/" )
) {
this . setState ( { partitionKey : "/" + event . target . value } ) ;
} else {
this . setState ( { partitionKey : event.target.value } ) ;
}
} }
2021-03-18 18:06:13 -07:00
/ >
2023-01-23 09:09:29 -06:00
{ userContext . apiType === "SQL" &&
this . state . subPartitionKeys . map ( ( subPartitionKey : string , index : number ) = > {
return (
< Stack style = { { marginBottom : 8 } } key = { ` uniqueKey ${ index } ` } horizontal >
< div
style = { {
width : "20px" ,
border : "solid" ,
borderWidth : "0px 0px 1px 1px" ,
marginRight : "5px" ,
} }
> < / div >
< input
type = "text"
id = "addCollection-partitionKeyValue"
key = { ` addCollection-partitionKeyValue_ ${ index } ` }
aria - required
required
size = { 40 }
tabIndex = { index > 0 ? 1 : 0 }
className = "panelTextField"
autoComplete = "off"
placeholder = { this . getPartitionKeyPlaceHolder ( index ) }
aria - label = { this . getPartitionKeyName ( ) }
pattern = { ".*" }
title = { "" }
value = { subPartitionKey }
onChange = { ( event : React.ChangeEvent < HTMLInputElement > ) = > {
const subPartitionKeys = [ . . . this . state . subPartitionKeys ] ;
if ( ! this . state . subPartitionKeys [ index ] && ! event . target . value . startsWith ( "/" ) ) {
subPartitionKeys [ index ] = "/" + event . target . value . trim ( ) ;
this . setState ( { subPartitionKeys } ) ;
} else {
subPartitionKeys [ index ] = event . target . value . trim ( ) ;
this . setState ( { subPartitionKeys } ) ;
}
} }
/ >
< IconButton
iconProps = { { iconName : "Delete" } }
style = { { height : 27 } }
onClick = { ( ) = > {
const subPartitionKeys = this . state . subPartitionKeys . filter ( ( uniqueKey , j ) = > index !== j ) ;
this . setState ( { subPartitionKeys } ) ;
} }
/ >
< / Stack >
) ;
} ) }
2023-02-03 10:15:35 -06:00
{ userContext . apiType === "SQL" && (
2023-01-23 09:09:29 -06:00
< Stack className = "panelGroupSpacing" >
< DefaultButton
2023-05-18 14:28:53 -05:00
styles = { { root : { padding : 0 , width : 200 , height : 30 } , label : { fontSize : 12 } } }
2023-01-23 09:09:29 -06:00
hidden = { this . state . useHashV1 }
disabled = { this . state . subPartitionKeys . length >= Constants . BackendDefaults . maxNumMultiHashPartition }
onClick = { ( ) = > this . setState ( { subPartitionKeys : [ . . . this . state . subPartitionKeys , "" ] } ) }
>
2023-05-18 14:28:53 -05:00
Add hierarchical partition key
2023-01-23 09:09:29 -06:00
< / DefaultButton >
{ this . state . subPartitionKeys . length > 0 && (
< Text variant = "small" >
< Icon iconName = "InfoSolid" className = "removeIcon" tabIndex = { 0 } / > This feature allows you to
2023-05-18 14:28:53 -05:00
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 . { " " }
2023-01-23 09:09:29 -06:00
< Link href = "https://aka.ms/cosmos-hierarchical-partitioning" target = "_blank" >
Learn more
< / Link >
< / Text >
) }
< / Stack >
) }
2021-03-18 18:06:13 -07:00
< / Stack >
) }
2021-05-12 11:56:24 -07:00
{ ! isServerlessAccount ( ) && ! this . state . createNewDatabase && this . isSelectedDatabaseSharedThroughput ( ) && (
2021-03-18 18:06:13 -07:00
< Stack horizontal verticalAlign = "center" >
< Checkbox
2021-04-30 10:23:34 -07:00
label = { ` Provision dedicated throughput for this ${ getCollectionName ( ) . toLocaleLowerCase ( ) } ` }
2021-03-18 18:06:13 -07:00
checked = { this . state . enableDedicatedThroughput }
styles = { {
text : { fontSize : 12 } ,
checkbox : { width : 12 , height : 12 } ,
label : { padding : 0 , alignItems : "center" } ,
} }
onChange = { ( ev : React.FormEvent < HTMLElement > , isChecked : boolean ) = >
this . setState ( { enableDedicatedThroughput : isChecked } )
}
/ >
< TooltipHost
directionalHint = { DirectionalHint . bottomLeftEdge }
2021-04-30 10:23:34 -07:00
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
2021-03-18 18:06:13 -07:00
does not count towards the throughput you provisioned for the database . This throughput amount will be
2021-04-30 10:23:34 -07:00
billed in addition to the throughput amount you provisioned at the database level . ` }
2021-03-18 18:06:13 -07:00
>
2023-02-04 01:51:11 +05:30
< Icon
iconName = "Info"
className = "panelInfoIcon"
tabIndex = { 0 }
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 . ` }
/ >
2021-03-18 18:06:13 -07:00
< / TooltipHost >
< / Stack >
) }
{ this . shouldShowCollectionThroughputInput ( ) && (
< ThroughputInput
2021-06-18 11:25:08 -07:00
showFreeTierExceedThroughputTooltip = { this . isFreeTierAccount ( ) && ! isFirstResourceCreated }
2021-03-18 18:06:13 -07:00
isDatabase = { false }
2021-04-30 10:23:34 -07:00
isSharded = { this . state . isSharded }
2022-02-25 18:21:58 -08:00
isFreeTier = { this . isFreeTierAccount ( ) }
2022-08-03 13:54:01 -07:00
isQuickstart = { this . props . isQuickstart }
2021-03-18 18:06:13 -07:00
setThroughputValue = { ( throughput : number ) = > ( this . collectionThroughput = throughput ) }
setIsAutoscale = { ( isAutoscale : boolean ) = > ( this . isCollectionAutoscale = isAutoscale ) }
2021-10-30 19:45:16 -07:00
setIsThroughputCapExceeded = { ( isThroughputCapExceeded : boolean ) = >
this . setState ( { isThroughputCapExceeded } )
}
2021-03-18 18:06:13 -07:00
onCostAcknowledgeChange = { ( isAcknowledged : boolean ) = > {
this . isCostAcknowledged = isAcknowledged ;
} }
/ >
) }
2021-04-29 00:55:04 +05:30
{ userContext . apiType === "SQL" && (
2021-03-18 18:06:13 -07:00
< Stack >
< Stack horizontal >
< Text className = "panelTextBold" variant = "small" >
Unique keys
< / Text >
< TooltipHost
directionalHint = { DirectionalHint . bottomLeftEdge }
2023-01-23 09:09:29 -06:00
content = {
"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."
}
2021-03-18 18:06:13 -07:00
>
2023-02-04 01:51:11 +05:30
< Icon
iconName = "Info"
className = "panelInfoIcon"
tabIndex = { 0 }
ariaLabel = {
"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."
}
/ >
2021-03-18 18:06:13 -07:00
< / TooltipHost >
< / Stack >
{ this . state . uniqueKeys . map (
( uniqueKey : string , i : number ) : JSX . Element = > {
return (
< Stack style = { { marginBottom : 8 } } key = { ` uniqueKey ${ i } ` } horizontal >
< input
type = "text"
autoComplete = "off"
placeholder = {
2021-04-29 00:55:04 +05:30
userContext . apiType === "Mongo"
2021-03-18 18:06:13 -07:00
? "Comma separated paths e.g. firstName,address.zipCode"
: "Comma separated paths e.g. /firstName,/address/zipCode"
}
className = "panelTextField"
autoFocus
value = { uniqueKey }
onChange = { ( event : React.ChangeEvent < HTMLInputElement > ) = > {
const uniqueKeys = this . state . uniqueKeys . map ( ( uniqueKey : string , j : number ) = > {
if ( i === j ) {
return event . target . value ;
}
return uniqueKey ;
} ) ;
this . setState ( { uniqueKeys } ) ;
} }
/ >
< IconButton
iconProps = { { iconName : "Delete" } }
style = { { height : 27 } }
onClick = { ( ) = > {
const uniqueKeys = this . state . uniqueKeys . filter ( ( uniqueKey , j ) = > i !== j ) ;
this . setState ( { uniqueKeys } ) ;
} }
/ >
< / Stack >
) ;
}
) }
< ActionButton
iconProps = { { iconName : "Add" } }
styles = { { root : { padding : 0 } , label : { fontSize : 12 } } }
onClick = { ( ) = > this . setState ( { uniqueKeys : [ . . . this . state . uniqueKeys , "" ] } ) }
>
Add unique key
< / ActionButton >
< / Stack >
) }
2021-08-19 12:18:21 -07:00
{ this . shouldShowAnalyticalStoreOptions ( ) && (
< Stack className = "panelGroupSpacing" >
< Stack horizontal >
< Text className = "panelTextBold" variant = "small" >
Analytical store
< / Text >
< TooltipHost
directionalHint = { DirectionalHint . bottomLeftEdge }
content = { this . getAnalyticalStorageTooltipContent ( ) }
>
2023-02-04 01:51:11 +05:30
< Icon
iconName = "Info"
className = "panelInfoIcon"
tabIndex = { 0 }
2023-02-07 11:48:45 +05:30
ariaLabel = " Enable analytical store capability to perform near real - time analytics on your operational data , without
impacting the performance of transactional workloads . "
2023-02-04 01:51:11 +05:30
/ >
2021-08-19 12:18:21 -07:00
< / TooltipHost >
< / Stack >
< Stack horizontal verticalAlign = "center" >
2023-03-14 23:49:21 +05:30
< div role = "radiogroup" >
< input
className = "panelRadioBtn"
checked = { this . state . enableAnalyticalStore }
disabled = { ! this . isSynapseLinkEnabled ( ) }
aria - label = "Enable analytical store"
aria - checked = { this . state . enableAnalyticalStore }
name = "analyticalStore"
type = "radio"
role = "radio"
id = "enableAnalyticalStoreBtn"
tabIndex = { 0 }
onChange = { this . onEnableAnalyticalStoreRadioBtnChange . bind ( this ) }
/ >
< span className = "panelRadioBtnLabel" > On < / span >
2021-08-19 12:18:21 -07:00
2023-03-14 23:49:21 +05:30
< input
className = "panelRadioBtn"
checked = { ! this . state . enableAnalyticalStore }
disabled = { ! this . isSynapseLinkEnabled ( ) }
aria - label = "Disable analytical store"
aria - checked = { ! this . state . enableAnalyticalStore }
name = "analyticalStore"
type = "radio"
role = "radio"
id = "disableAnalyticalStoreBtn"
tabIndex = { 0 }
onChange = { this . onDisableAnalyticalStoreRadioBtnChange . bind ( this ) }
/ >
< span className = "panelRadioBtnLabel" > Off < / span >
< / div >
2021-08-19 12:18:21 -07:00
< / Stack >
{ ! this . isSynapseLinkEnabled ( ) && (
< Stack className = "panelGroupSpacing" >
< Text variant = "small" >
Azure Synapse Link is required for creating an analytical store { " " }
{ getCollectionName ( ) . toLocaleLowerCase ( ) } . Enable Synapse Link for this Cosmos DB account . { " " }
< Link href = "https://aka.ms/cosmosdb-synapselink" target = "_blank" >
Learn more
< / Link >
< / Text >
< DefaultButton
text = "Enable"
onClick = { ( ) = > this . props . explorer . openEnableSynapseLinkDialog ( ) }
style = { { height : 27 , width : 80 } }
styles = { { label : { fontSize : 12 } } }
/ >
< / Stack >
) }
< / Stack >
) }
2021-04-30 10:23:34 -07:00
{ userContext . apiType !== "Tables" && (
< CollapsibleSectionComponent
title = "Advanced"
isExpandedByDefault = { false }
onExpand = { ( ) = > {
TelemetryProcessor . traceOpen ( Action . ExpandAddCollectionPaneAdvancedSection ) ;
this . scrollToAdvancedSection ( ) ;
} }
>
< Stack className = "panelGroupSpacing" id = "collapsibleSectionContent" >
2022-09-29 15:36:00 -07:00
{ isCapabilityEnabled ( "EnableMongo" ) && ! isCapabilityEnabled ( "EnableMongo16MBDocumentSupport" ) && (
2021-04-30 10:23:34 -07:00
< Stack className = "panelGroupSpacing" >
< Stack horizontal >
< span className = "mandatoryStar" > * & nbsp ; < / span >
< Text className = "panelTextBold" variant = "small" >
Indexing
< / Text >
< TooltipHost
directionalHint = { DirectionalHint . bottomLeftEdge }
content = "The _id field is indexed by default. Creating a wildcard index for all fields will optimize queries and is recommended for development."
>
2023-02-04 01:51:11 +05:30
< Icon
iconName = "Info"
className = "panelInfoIcon"
tabIndex = { 0 }
ariaLabel = "The _id field is indexed by default. Creating a wildcard index for all fields will optimize queries and is recommended for development."
/ >
2021-04-30 10:23:34 -07:00
< / TooltipHost >
< / Stack >
< Checkbox
label = "Create a Wildcard Index on all fields"
checked = { this . state . createMongoWildCardIndex }
styles = { {
text : { fontSize : 12 } ,
checkbox : { width : 12 , height : 12 } ,
label : { padding : 0 , alignItems : "center" } ,
} }
onChange = { ( ev : React.FormEvent < HTMLElement > , isChecked : boolean ) = >
this . setState ( { createMongoWildCardIndex : isChecked } )
}
/ >
2021-03-18 18:06:13 -07:00
< / Stack >
2021-04-30 10:23:34 -07:00
) }
2021-03-18 18:06:13 -07:00
2021-04-30 10:23:34 -07:00
{ userContext . apiType === "SQL" && (
2023-01-23 09:09:29 -06:00
< Stack className = "panelGroupSpacing" >
< Checkbox
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 } ,
checkbox : { width : 12 , height : 12 } ,
label : { padding : 0 , alignItems : "center" , wordWrap : "break-word" , whiteSpace : "break-spaces" } ,
} }
onChange = { ( ev : React.FormEvent < HTMLElement > , isChecked : boolean ) = >
this . setState ( { useHashV1 : isChecked , subPartitionKeys : [ ] } )
}
/ >
< Text variant = "small" >
< Icon iconName = "InfoSolid" className = "removeIcon" tabIndex = { 0 } / > 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" >
Learn more
< / Link >
< / Text >
< / Stack >
2021-04-30 10:23:34 -07:00
) }
< / Stack >
< / CollapsibleSectionComponent >
) }
2021-03-18 18:06:13 -07:00
< / div >
2021-10-30 19:45:16 -07:00
< PanelFooterComponent buttonLabel = "OK" isButtonDisabled = { this . state . isThroughputCapExceeded } / >
2021-03-18 18:06:13 -07:00
2022-05-16 17:45:50 -07:00
{ this . state . isExecuting && (
< div >
< PanelLoadingScreen / >
{ this . state . teachingBubbleStep === 5 && (
< TeachingBubble
headline = "Creating sample container"
target = { "#loadingScreen" }
onDismiss = { ( ) = > this . setState ( { teachingBubbleStep : 0 } ) }
2022-05-23 20:52:21 -07:00
styles = { { footer : { width : "100%" } } }
2022-05-16 17:45:50 -07:00
>
A sample container is now being created and we are adding sample data for you . It should take about 1
minute .
< br / >
< br / >
Once the sample container is created , review your sample dataset and follow next steps
2022-05-23 20:52:21 -07:00
< br / >
< br / >
< ProgressIndicator
styles = { {
itemName : { color : "white" } ,
progressTrack : { backgroundColor : "#A6A6A6" } ,
progressBar : { background : "white" } ,
} }
label = "Adding sample data set"
/ >
2022-05-16 17:45:50 -07:00
< / TeachingBubble >
) }
< / div >
) }
2021-03-18 18:06:13 -07:00
< / form >
) ;
}
private getDatabaseOptions ( ) : IDropdownOption [ ] {
2021-06-18 11:25:08 -07:00
return useDatabases . getState ( ) . databases ? . map ( ( database ) = > ( {
2021-03-18 18:06:13 -07:00
key : database.id ( ) ,
text : database.id ( ) ,
} ) ) ;
}
2021-04-30 10:23:34 -07:00
private getPartitionKeyName ( isLowerCase? : boolean ) : string {
const partitionKeyName = userContext . apiType === "Mongo" ? "Shard key" : "Partition key" ;
2021-03-18 18:06:13 -07:00
2021-04-30 10:23:34 -07:00
return isLowerCase ? partitionKeyName . toLocaleLowerCase ( ) : partitionKeyName ;
2021-03-18 18:06:13 -07:00
}
2023-01-23 09:09:29 -06:00
private getPartitionKeyPlaceHolder ( index? : number ) : string {
2021-04-29 00:55:04 +05:30
switch ( userContext . apiType ) {
case "Mongo" :
2023-02-07 18:34:10 -08:00
return "e.g., categoryId" ;
2021-04-29 00:55:04 +05:30
case "Gremlin" :
2021-03-18 18:06:13 -07:00
return "e.g., /address" ;
2023-01-23 09:09:29 -06:00
case "SQL" :
return ` ${
index === undefined
? "Required - first partition key e.g., /TenantId"
: index === 0
? "second partition key e.g., /UserId"
: "third partition key e.g., /SessionId"
} ` ;
2021-03-18 18:06:13 -07:00
default :
return "e.g., /address/zipCode" ;
}
}
private onCreateNewDatabaseRadioBtnChange ( event : React.ChangeEvent < HTMLInputElement > ) : void {
if ( event . target . checked && ! this . state . createNewDatabase ) {
this . setState ( {
createNewDatabase : true ,
} ) ;
}
}
private onUseExistingDatabaseRadioBtnChange ( event : React.ChangeEvent < HTMLInputElement > ) : void {
if ( event . target . checked && this . state . createNewDatabase ) {
this . setState ( {
createNewDatabase : false ,
} ) ;
}
}
private onUnshardedRadioBtnChange ( event : React.ChangeEvent < HTMLInputElement > ) : void {
if ( event . target . checked && this . state . isSharded ) {
this . setState ( {
isSharded : false ,
} ) ;
}
}
private onShardedRadioBtnChange ( event : React.ChangeEvent < HTMLInputElement > ) : void {
if ( event . target . checked && ! this . state . isSharded ) {
this . setState ( {
isSharded : true ,
} ) ;
}
}
private onEnableAnalyticalStoreRadioBtnChange ( event : React.ChangeEvent < HTMLInputElement > ) : void {
if ( event . target . checked && ! this . state . enableAnalyticalStore ) {
this . setState ( {
enableAnalyticalStore : true ,
} ) ;
}
}
private onDisableAnalyticalStoreRadioBtnChange ( event : React.ChangeEvent < HTMLInputElement > ) : void {
if ( event . target . checked && this . state . enableAnalyticalStore ) {
this . setState ( {
enableAnalyticalStore : false ,
} ) ;
}
}
private onTurnOnIndexing ( event : React.ChangeEvent < HTMLInputElement > ) : void {
if ( event . target . checked && ! this . state . enableIndexing ) {
this . setState ( {
enableIndexing : true ,
} ) ;
}
}
private onTurnOffIndexing ( event : React.ChangeEvent < HTMLInputElement > ) : void {
if ( event . target . checked && this . state . enableIndexing ) {
this . setState ( {
enableIndexing : false ,
} ) ;
}
}
private isSelectedDatabaseSharedThroughput ( ) : boolean {
if ( ! this . state . selectedDatabaseId ) {
return false ;
}
2021-06-18 11:25:08 -07:00
const selectedDatabase = useDatabases
. getState ( )
. databases ? . find ( ( database ) = > database . id ( ) === this . state . selectedDatabaseId ) ;
2021-03-18 18:06:13 -07:00
return ! ! selectedDatabase ? . offer ( ) ;
}
private isFreeTierAccount ( ) : boolean {
return userContext . databaseAccount ? . properties ? . enableFreeTier ;
}
private getSharedThroughputDefault ( ) : boolean {
2021-05-12 11:56:24 -07:00
return userContext . subscriptionType !== SubscriptionType . EA && ! isServerlessAccount ( ) ;
2021-03-18 18:06:13 -07:00
}
private getFreeTierIndexingText ( ) : string {
return this . state . enableIndexing
? "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." ;
}
2021-04-30 10:23:34 -07:00
private getPartitionKeyTooltipText ( ) : string {
if ( userContext . apiType === "Mongo" ) {
return "The shard key (field) is used to split your data across many replica sets (shards) to achieve unlimited scalability. It’ s critical to choose a field that will evenly distribute your data." ;
}
let tooltipText = ` The ${ this . 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 += " For small read-heavy workloads or write-heavy workloads of any size, id is often a good choice." ;
}
return tooltipText ;
}
2021-07-29 06:48:03 -07:00
private getPartitionKey ( ) : string {
if ( userContext . apiType !== "SQL" && userContext . apiType !== "Mongo" ) {
return "" ;
}
if ( userContext . features . partitionKeyDefault ) {
return userContext . apiType === "SQL" ? "/id" : "_id" ;
}
if ( userContext . features . partitionKeyDefault2 ) {
return userContext . apiType === "SQL" ? "/pk" : "pk" ;
}
2022-05-04 18:24:34 -07:00
if ( this . props . isQuickstart ) {
2023-02-07 18:34:10 -08:00
return userContext . apiType === "SQL" ? "/categoryId" : "categoryId" ;
2022-05-04 18:24:34 -07:00
}
2021-07-29 06:48:03 -07:00
return "" ;
}
2021-07-14 12:10:45 -07:00
private getPartitionKeySubtext ( ) : string {
if (
userContext . features . partitionKeyDefault &&
( userContext . apiType === "SQL" || userContext . apiType === "Mongo" )
) {
const subtext = "For small workloads, the item ID is a suitable choice for the partition key." ;
return subtext ;
}
return "" ;
}
2021-04-30 10:23:34 -07:00
private getAnalyticalStorageTooltipContent ( ) : JSX . Element {
return (
< Text variant = "small" >
Enable analytical store capability to perform near real - time analytics on your operational data , without
impacting the performance of transactional workloads . { " " }
< Link target = "_blank" href = "https://aka.ms/analytical-store-overview" >
Learn more
< / Link >
< / Text >
) ;
}
2021-03-18 18:06:13 -07:00
private shouldShowCollectionThroughputInput ( ) : boolean {
2021-05-12 11:56:24 -07:00
if ( isServerlessAccount ( ) ) {
2021-03-18 18:06:13 -07:00
return false ;
}
if ( this . state . createNewDatabase ) {
return ! this . state . isSharedThroughputChecked ;
}
if ( this . state . enableDedicatedThroughput ) {
return true ;
}
return this . state . selectedDatabaseId && ! this . isSelectedDatabaseSharedThroughput ( ) ;
}
private shouldShowIndexingOptionsForFreeTierAccount ( ) : boolean {
if ( ! this . isFreeTierAccount ( ) ) {
return false ;
}
return this . state . createNewDatabase
? this . state . isSharedThroughputChecked
: this . isSelectedDatabaseSharedThroughput ( ) ;
}
private shouldShowAnalyticalStoreOptions ( ) : boolean {
if ( configContext . platform === Platform . Emulator ) {
return false ;
}
2021-04-29 00:55:04 +05:30
switch ( userContext . apiType ) {
case "SQL" :
case "Mongo" :
2021-03-18 18:06:13 -07:00
return true ;
default :
return false ;
}
}
private isSynapseLinkEnabled ( ) : boolean {
2022-05-04 18:24:34 -07:00
if ( ! userContext . databaseAccount ) {
return false ;
}
2021-03-18 18:06:13 -07:00
2022-05-04 18:24:34 -07:00
const { properties } = userContext . databaseAccount ;
2021-03-18 18:06:13 -07:00
if ( ! properties ) {
return false ;
}
if ( properties . enableAnalyticalStorage ) {
return true ;
}
2021-05-06 16:51:22 -07:00
return properties . capabilities ? . some (
2021-03-18 18:06:13 -07:00
( capability ) = > capability . name === Constants . CapabilityNames . EnableStorageAnalytics
) ;
}
private parseUniqueKeys ( ) : DataModels . UniqueKeyPolicy {
if ( this . state . uniqueKeys ? . length === 0 ) {
return undefined ;
}
const uniqueKeyPolicy : DataModels.UniqueKeyPolicy = { uniqueKeys : [ ] } ;
this . state . uniqueKeys . forEach ( ( uniqueKey ) = > {
if ( uniqueKey ) {
const validPaths : string [ ] = uniqueKey . split ( "," ) ? . filter ( ( path ) = > path ? . length > 0 ) ;
const trimmedPaths : string [ ] = validPaths ? . map ( ( path ) = > path . trim ( ) ) ;
if ( trimmedPaths ? . length > 0 ) {
2021-04-29 00:55:04 +05:30
if ( userContext . apiType === "Mongo" ) {
2021-03-18 18:06:13 -07:00
trimmedPaths . map ( ( path ) = > {
const transformedPath = path . split ( "." ) . join ( "/" ) ;
if ( transformedPath [ 0 ] !== "/" ) {
return "/" + transformedPath ;
}
return transformedPath ;
} ) ;
}
uniqueKeyPolicy . uniqueKeys . push ( { paths : trimmedPaths } ) ;
}
}
} ) ;
return uniqueKeyPolicy ;
}
private validateInputs ( ) : boolean {
if ( ! this . state . createNewDatabase && ! this . state . selectedDatabaseId ) {
this . setState ( { errorMessage : "Please select an existing database" } ) ;
return false ;
}
const throughput = this . state . createNewDatabase ? this . newDatabaseThroughput : this.collectionThroughput ;
if ( throughput > CollectionCreation . DefaultCollectionRUs100K && ! this . isCostAcknowledged ) {
const errorMessage = this . isNewDatabaseAutoscale
? "Please acknowledge the estimated monthly spend."
: "Please acknowledge the estimated daily spend." ;
this . setState ( { errorMessage } ) ;
return false ;
}
2021-04-30 10:23:34 -07:00
if ( throughput > CollectionCreation . MaxRUPerPartition && ! this . state . isSharded ) {
this . setState ( { errorMessage : "Unsharded collections support up to 10,000 RUs" } ) ;
return false ;
}
2021-03-18 18:06:13 -07:00
if (
2021-04-29 00:55:04 +05:30
userContext . apiType === "Gremlin" &&
2021-03-18 18:06:13 -07:00
( this . state . partitionKey === "/id" || this . state . partitionKey === "/label" )
) {
this . setState ( { errorMessage : "/id and /label as partition keys are not allowed for graph." } ) ;
return false ;
}
return true ;
}
private getAnalyticalStorageTtl ( ) : number {
2021-05-12 11:49:25 -05:00
if ( ! this . isSynapseLinkEnabled ( ) ) {
return undefined ;
}
2021-03-18 18:06:13 -07:00
if ( ! this . shouldShowAnalyticalStoreOptions ( ) ) {
return undefined ;
}
if ( this . state . enableAnalyticalStore ) {
// TODO: always default to 90 days once the backend hotfix is deployed
2021-03-22 12:04:06 -07:00
return userContext . features . ttl90Days
2021-03-18 18:06:13 -07:00
? Constants . AnalyticalStorageTtl . Days90
: Constants . AnalyticalStorageTtl . Infinite ;
}
return Constants . AnalyticalStorageTtl . Disabled ;
}
2021-04-30 10:23:34 -07:00
private scrollToAdvancedSection ( ) : void {
document . getElementById ( "collapsibleSectionContent" ) ? . scrollIntoView ( ) ;
}
2022-05-23 20:52:21 -07:00
private getSampleDBName ( ) : string {
const existingSampleDBs = useDatabases
. getState ( )
. databases ? . filter ( ( database ) = > database . id ( ) . startsWith ( "SampleDB" ) ) ;
const existingSampleDBNames = existingSampleDBs ? . map ( ( database ) = > database . id ( ) ) ;
if ( ! existingSampleDBNames || existingSampleDBNames . length === 0 ) {
return "SampleDB" ;
}
let i = 1 ;
while ( existingSampleDBNames . indexOf ( ` SampleDB ${ i } ` ) !== - 1 ) {
i ++ ;
}
return ` SampleDB ${ i } ` ;
}
2022-05-04 18:24:34 -07:00
private async submit ( event? : React.FormEvent < HTMLFormElement > ) : Promise < void > {
event ? . preventDefault ( ) ;
2021-03-18 18:06:13 -07:00
if ( ! this . validateInputs ( ) ) {
return ;
}
const collectionId : string = this . state . collectionId . trim ( ) ;
let databaseId = this . state . createNewDatabase ? this . state . newDatabaseId . trim ( ) : this . state . selectedDatabaseId ;
2021-10-11 12:09:38 -07:00
let partitionKeyString = this . state . isSharded ? this . state . partitionKey . trim ( ) : undefined ;
2021-03-18 18:06:13 -07:00
2021-04-29 00:55:04 +05:30
if ( userContext . apiType === "Tables" ) {
2021-03-18 18:06:13 -07:00
// Table require fixed Database: TablesDB, and fixed Partition Key: /'$pk'
databaseId = CollectionCreation . TablesAPIDefaultDatabase ;
partitionKeyString = "/'$pk'" ;
}
const uniqueKeyPolicy : DataModels.UniqueKeyPolicy = this . parseUniqueKeys ( ) ;
2023-01-23 09:09:29 -06:00
const partitionKeyVersion = this . state . useHashV1 ? undefined : 2 ;
2021-03-18 18:06:13 -07:00
const partitionKey : DataModels.PartitionKey = partitionKeyString
? {
2023-01-23 09:09:29 -06:00
paths : [
partitionKeyString ,
. . . ( userContext . apiType === "SQL" && this . state . subPartitionKeys . length > 0
? this . state . subPartitionKeys
: [ ] ) ,
] ,
kind : userContext.apiType === "SQL" && this . state . subPartitionKeys . length > 0 ? "MultiHash" : "Hash" ,
2021-03-18 18:06:13 -07:00
version : partitionKeyVersion ,
}
: undefined ;
const indexingPolicy : DataModels.IndexingPolicy = this . state . enableIndexing
2021-05-20 20:34:29 -05:00
? AllPropertiesIndexed
: SharedDatabaseDefault ;
2021-03-18 18:06:13 -07:00
const telemetryData = {
database : {
id : databaseId ,
new : this . state . createNewDatabase ,
shared : this.state.createNewDatabase
? this . state . isSharedThroughputChecked
: this . isSelectedDatabaseSharedThroughput ( ) ,
} ,
collection : {
id : this.state.collectionId ,
throughput : this.collectionThroughput ,
isAutoscale : this.isCollectionAutoscale ,
partitionKey ,
uniqueKeyPolicy ,
collectionWithDedicatedThroughput : this.state.enableDedicatedThroughput ,
} ,
subscriptionQuotaId : userContext.quotaId ,
dataExplorerArea : Constants.Areas.ContextualPane ,
useIndexingForSharedThroughput : this.state.enableIndexing ,
2022-06-01 15:26:10 -07:00
isQuickstart : ! ! this . props . isQuickstart ,
2021-03-18 18:06:13 -07:00
} ;
const startKey : number = TelemetryProcessor . traceStart ( Action . CreateCollection , telemetryData ) ;
const databaseLevelThroughput : boolean = this . state . createNewDatabase
? this . state . isSharedThroughputChecked
: this . isSelectedDatabaseSharedThroughput ( ) && ! this . state . enableDedicatedThroughput ;
let offerThroughput : number ;
let autoPilotMaxThroughput : number ;
2021-05-21 11:12:13 -07:00
if ( databaseLevelThroughput ) {
if ( this . state . createNewDatabase ) {
if ( this . isNewDatabaseAutoscale ) {
autoPilotMaxThroughput = this . newDatabaseThroughput ;
} else {
offerThroughput = this . newDatabaseThroughput ;
}
2021-03-18 18:06:13 -07:00
}
2021-05-21 11:12:13 -07:00
} else {
2021-03-18 18:06:13 -07:00
if ( this . isCollectionAutoscale ) {
autoPilotMaxThroughput = this . collectionThroughput ;
} else {
offerThroughput = this . collectionThroughput ;
}
}
const createCollectionParams : DataModels.CreateCollectionParams = {
createNewDatabase : this.state.createNewDatabase ,
collectionId ,
databaseId ,
databaseLevelThroughput ,
offerThroughput ,
autoPilotMaxThroughput ,
analyticalStorageTtl : this.getAnalyticalStorageTtl ( ) ,
indexingPolicy ,
partitionKey ,
uniqueKeyPolicy ,
createMongoWildcardIndex : this.state.createMongoWildCardIndex ,
} ;
this . setState ( { isExecuting : true } ) ;
try {
await createCollection ( createCollectionParams ) ;
2022-05-04 18:24:34 -07:00
await this . props . explorer . refreshAllDatabases ( ) ;
if ( this . props . isQuickstart ) {
2022-05-23 20:52:21 -07:00
const database = useDatabases . getState ( ) . findDatabaseWithId ( databaseId ) ;
2022-05-04 18:24:34 -07:00
if ( database ) {
2022-05-23 20:52:21 -07:00
database . isSampleDB = true ;
2022-05-16 17:45:50 -07:00
// populate sample container with sample data
2022-05-04 18:24:34 -07:00
await database . loadCollections ( ) ;
2022-05-23 20:52:21 -07:00
const collection = database . findCollectionWithId ( collectionId ) ;
collection . isSampleCollection = true ;
useTeachingBubble . getState ( ) . setSampleCollection ( collection ) ;
2022-05-04 18:24:34 -07:00
const sampleGenerator = await ContainerSampleGenerator . createSampleGeneratorAsync ( this . props . explorer ) ;
2022-08-03 13:54:01 -07:00
await sampleGenerator . populateContainerAsync ( collection , partitionKeyString ) ;
2022-05-16 17:45:50 -07:00
// auto-expand sample database + container and show teaching bubble
await database . expandDatabase ( ) ;
collection . expandCollection ( ) ;
useDatabases . getState ( ) . updateDatabase ( database ) ;
useTeachingBubble . getState ( ) . setIsSampleDBExpanded ( true ) ;
2022-06-01 15:26:10 -07:00
TelemetryProcessor . traceOpen ( Action . LaunchUITour ) ;
2022-05-04 18:24:34 -07:00
}
}
2021-03-18 18:06:13 -07:00
this . setState ( { isExecuting : false } ) ;
TelemetryProcessor . traceSuccess ( Action . CreateCollection , telemetryData , startKey ) ;
2021-05-27 16:07:07 -05:00
useSidePanel . getState ( ) . closeSidePanel ( ) ;
2021-03-18 18:06:13 -07:00
} catch ( error ) {
const errorMessage : string = getErrorMessage ( error ) ;
this . setState ( { isExecuting : false , errorMessage , showErrorDetails : true } ) ;
const failureTelemetryData = { . . . telemetryData , error : errorMessage , errorStack : getErrorStack ( error ) } ;
TelemetryProcessor . traceFailure ( Action . CreateCollection , failureTelemetryData , startKey ) ;
}
}
}