mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-04-28 12:35:10 +01:00
subpartition keys
This commit is contained in:
parent
cec621443d
commit
c33c497fd9
@ -127,6 +127,7 @@ async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Co
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TO DO: Remove when we get RP API Spec with materializedViews
|
// TO DO: Remove when we get RP API Spec with materializedViews
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
return rpResponse?.value?.map((collection: any) => {
|
return rpResponse?.value?.map((collection: any) => {
|
||||||
const collectionDataModel: DataModels.Collection = collection.properties?.resource as DataModels.Collection;
|
const collectionDataModel: DataModels.Collection = collection.properties?.resource as DataModels.Collection;
|
||||||
collectionDataModel.materializedViews = collection.properties?.resource?.materializedViews;
|
collectionDataModel.materializedViews = collection.properties?.resource?.materializedViews;
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
|
import { MaterializedViewsLabels } from "Common/Constants";
|
||||||
|
import { isMaterializedViewsEnabled } from "Common/DatabaseAccountUtility";
|
||||||
import { TreeNodeMenuItem } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
|
import { TreeNodeMenuItem } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
|
||||||
|
import {
|
||||||
|
AddMaterializedViewPanel,
|
||||||
|
AddMaterializedViewPanelProps,
|
||||||
|
} from "Explorer/Panes/AddMaterializedViewPanel/AddMaterializedViewPanel";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||||
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
|
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
|
||||||
@ -163,6 +169,24 @@ export const createCollectionContextMenuButton = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isMaterializedViewsEnabled() && !selectedCollection.materializedViewDefinition()) {
|
||||||
|
items.push({
|
||||||
|
label: MaterializedViewsLabels.NewMaterializedView,
|
||||||
|
onClick: () => {
|
||||||
|
const addMaterializedViewPanelProps: AddMaterializedViewPanelProps = {
|
||||||
|
explorer: container,
|
||||||
|
sourceContainer: selectedCollection,
|
||||||
|
};
|
||||||
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel(
|
||||||
|
MaterializedViewsLabels.NewMaterializedView,
|
||||||
|
<AddMaterializedViewPanel {...addMaterializedViewPanelProps} />,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ import type NotebookManager from "./Notebook/NotebookManager";
|
|||||||
import { NotebookPaneContent } from "./Notebook/NotebookManager";
|
import { NotebookPaneContent } from "./Notebook/NotebookManager";
|
||||||
import { NotebookUtil } from "./Notebook/NotebookUtil";
|
import { NotebookUtil } from "./Notebook/NotebookUtil";
|
||||||
import { useNotebook } from "./Notebook/useNotebook";
|
import { useNotebook } from "./Notebook/useNotebook";
|
||||||
import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
|
import { AddCollectionPanel } from "./Panes/AddCollectionPanel/AddCollectionPanel";
|
||||||
import { CassandraAddCollectionPane } from "./Panes/CassandraAddCollectionPane/CassandraAddCollectionPane";
|
import { CassandraAddCollectionPane } from "./Panes/CassandraAddCollectionPane/CassandraAddCollectionPane";
|
||||||
import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane";
|
import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane";
|
||||||
import { StringInputPane } from "./Panes/StringInputPane/StringInputPane";
|
import { StringInputPane } from "./Panes/StringInputPane/StringInputPane";
|
||||||
|
@ -26,6 +26,12 @@ import {
|
|||||||
getFullTextLanguageOptions,
|
getFullTextLanguageOptions,
|
||||||
} from "Explorer/Controls/FullTextSeach/FullTextPoliciesComponent";
|
} from "Explorer/Controls/FullTextSeach/FullTextPoliciesComponent";
|
||||||
import { VectorEmbeddingPoliciesComponent } from "Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent";
|
import { VectorEmbeddingPoliciesComponent } from "Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent";
|
||||||
|
import {
|
||||||
|
getPartitionKey,
|
||||||
|
getPartitionKeyName,
|
||||||
|
getPartitionKeyPlaceHolder,
|
||||||
|
getPartitionKeyTooltipText,
|
||||||
|
} from "Explorer/Panes/AddCollectionPanel/AddCollectionPanelUtility";
|
||||||
import { useSidePanel } from "hooks/useSidePanel";
|
import { useSidePanel } from "hooks/useSidePanel";
|
||||||
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
@ -41,15 +47,15 @@ import {
|
|||||||
isVectorSearchEnabled,
|
isVectorSearchEnabled,
|
||||||
} from "Utils/CapabilityUtils";
|
} from "Utils/CapabilityUtils";
|
||||||
import { getUpsellMessage } from "Utils/PricingUtils";
|
import { getUpsellMessage } from "Utils/PricingUtils";
|
||||||
import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
import { CollapsibleSectionComponent } from "../../Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
||||||
import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput";
|
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
|
||||||
import "../Controls/ThroughputInput/ThroughputInput.less";
|
import "../../Controls/ThroughputInput/ThroughputInput.less";
|
||||||
import { ContainerSampleGenerator } from "../DataSamples/ContainerSampleGenerator";
|
import { ContainerSampleGenerator } from "../../DataSamples/ContainerSampleGenerator";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { useDatabases } from "../useDatabases";
|
import { useDatabases } from "../../useDatabases";
|
||||||
import { PanelFooterComponent } from "./PanelFooterComponent";
|
import { PanelFooterComponent } from "../PanelFooterComponent";
|
||||||
import { PanelInfoErrorComponent } from "./PanelInfoErrorComponent";
|
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
|
||||||
import { PanelLoadingScreen } from "./PanelLoadingScreen";
|
import { PanelLoadingScreen } from "../PanelLoadingScreen";
|
||||||
|
|
||||||
export interface AddCollectionPanelProps {
|
export interface AddCollectionPanelProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
@ -143,7 +149,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
collectionId: props.isQuickstart ? `Sample${getCollectionName()}` : "",
|
collectionId: props.isQuickstart ? `Sample${getCollectionName()}` : "",
|
||||||
enableIndexing: true,
|
enableIndexing: true,
|
||||||
isSharded: userContext.apiType !== "Tables",
|
isSharded: userContext.apiType !== "Tables",
|
||||||
partitionKey: this.getPartitionKey(),
|
partitionKey: getPartitionKey(props.isQuickstart),
|
||||||
subPartitionKeys: [],
|
subPartitionKeys: [],
|
||||||
enableDedicatedThroughput: false,
|
enableDedicatedThroughput: false,
|
||||||
createMongoWildCardIndex:
|
createMongoWildCardIndex:
|
||||||
@ -576,17 +582,14 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<span className="mandatoryStar">* </span>
|
<span className="mandatoryStar">* </span>
|
||||||
<Text className="panelTextBold" variant="small">
|
<Text className="panelTextBold" variant="small">
|
||||||
{this.getPartitionKeyName()}
|
{getPartitionKeyName()}
|
||||||
</Text>
|
</Text>
|
||||||
<TooltipHost
|
<TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={getPartitionKeyTooltipText()}>
|
||||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
|
||||||
content={this.getPartitionKeyTooltipText()}
|
|
||||||
>
|
|
||||||
<Icon
|
<Icon
|
||||||
iconName="Info"
|
iconName="Info"
|
||||||
className="panelInfoIcon"
|
className="panelInfoIcon"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
ariaLabel={this.getPartitionKeyTooltipText()}
|
ariaLabel={getPartitionKeyTooltipText()}
|
||||||
/>
|
/>
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</Stack>
|
</Stack>
|
||||||
@ -600,8 +603,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
required
|
required
|
||||||
size={40}
|
size={40}
|
||||||
className="panelTextField"
|
className="panelTextField"
|
||||||
placeholder={this.getPartitionKeyPlaceHolder()}
|
placeholder={getPartitionKeyPlaceHolder()}
|
||||||
aria-label={this.getPartitionKeyName()}
|
aria-label={getPartitionKeyName()}
|
||||||
pattern={userContext.apiType === "Gremlin" ? "^/[^/]*" : ".*"}
|
pattern={userContext.apiType === "Gremlin" ? "^/[^/]*" : ".*"}
|
||||||
title={userContext.apiType === "Gremlin" ? "May not use composite partition key" : ""}
|
title={userContext.apiType === "Gremlin" ? "May not use composite partition key" : ""}
|
||||||
value={this.state.partitionKey}
|
value={this.state.partitionKey}
|
||||||
@ -639,8 +642,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
tabIndex={index > 0 ? 1 : 0}
|
tabIndex={index > 0 ? 1 : 0}
|
||||||
className="panelTextField"
|
className="panelTextField"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
placeholder={this.getPartitionKeyPlaceHolder(index)}
|
placeholder={getPartitionKeyPlaceHolder(index)}
|
||||||
aria-label={this.getPartitionKeyName()}
|
aria-label={getPartitionKeyName()}
|
||||||
pattern={".*"}
|
pattern={".*"}
|
||||||
title={""}
|
title={""}
|
||||||
value={subPartitionKey}
|
value={subPartitionKey}
|
||||||
@ -1053,31 +1056,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private getPartitionKeyName(isLowerCase?: boolean): string {
|
|
||||||
const partitionKeyName = userContext.apiType === "Mongo" ? "Shard key" : "Partition key";
|
|
||||||
|
|
||||||
return isLowerCase ? partitionKeyName.toLocaleLowerCase() : partitionKeyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getPartitionKeyPlaceHolder(index?: number): string {
|
|
||||||
switch (userContext.apiType) {
|
|
||||||
case "Mongo":
|
|
||||||
return "e.g., categoryId";
|
|
||||||
case "Gremlin":
|
|
||||||
return "e.g., /address";
|
|
||||||
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"
|
|
||||||
}`;
|
|
||||||
default:
|
|
||||||
return "e.g., /address/zipCode";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private onCreateNewDatabaseRadioBtnChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
private onCreateNewDatabaseRadioBtnChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||||
if (event.target.checked && !this.state.createNewDatabase) {
|
if (event.target.checked && !this.state.createNewDatabase) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -1175,38 +1153,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
: "Indexing will be turned off. Recommended if you don't need to run queries or only have key value operations.";
|
: "Indexing will be turned off. Recommended if you don't need to run queries or only have key value operations.";
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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";
|
|
||||||
}
|
|
||||||
if (this.props.isQuickstart) {
|
|
||||||
return userContext.apiType === "SQL" ? "/categoryId" : "categoryId";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private getPartitionKeySubtext(): string {
|
private getPartitionKeySubtext(): string {
|
||||||
if (
|
if (
|
||||||
userContext.features.partitionKeyDefault &&
|
userContext.features.partitionKeyDefault &&
|
@ -0,0 +1,58 @@
|
|||||||
|
import { userContext } from "UserContext";
|
||||||
|
|
||||||
|
export function 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 ${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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPartitionKeyName(isLowerCase?: boolean): string {
|
||||||
|
const partitionKeyName = userContext.apiType === "Mongo" ? "Shard key" : "Partition key";
|
||||||
|
|
||||||
|
return isLowerCase ? partitionKeyName.toLocaleLowerCase() : partitionKeyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPartitionKeyPlaceHolder(index?: number): string {
|
||||||
|
switch (userContext.apiType) {
|
||||||
|
case "Mongo":
|
||||||
|
return "e.g., categoryId";
|
||||||
|
case "Gremlin":
|
||||||
|
return "e.g., /address";
|
||||||
|
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"
|
||||||
|
}`;
|
||||||
|
default:
|
||||||
|
return "e.g., /address/zipCode";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPartitionKey(isQuickstart?: boolean): 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";
|
||||||
|
}
|
||||||
|
if (isQuickstart) {
|
||||||
|
return userContext.apiType === "SQL" ? "/categoryId" : "categoryId";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
@ -1,34 +0,0 @@
|
|||||||
import { Dropdown, Stack } from "@fluentui/react";
|
|
||||||
import Explorer from "Explorer/Explorer";
|
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
export interface AddMaterializedViewPanelProps {
|
|
||||||
explorer: Explorer;
|
|
||||||
}
|
|
||||||
export const AddMaterializedViewPanel = (addMaterializedViewPanelProps: AddMaterializedViewPanelProps): JSX.Element => {
|
|
||||||
console.log(useDatabases.getState().databases);
|
|
||||||
console.log(
|
|
||||||
useDatabases.getState().databases.forEach((db) => {
|
|
||||||
console.log(db);
|
|
||||||
console.log(db.collections());
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form className="panelFormWrapper" id="panelMaterializedView">
|
|
||||||
<div className="panelMainContent">
|
|
||||||
<Stack>
|
|
||||||
<Dropdown
|
|
||||||
label="Source container id"
|
|
||||||
placeholder="Choose existing container"
|
|
||||||
options={[]}
|
|
||||||
required
|
|
||||||
styles={{ title: { height: 27, lineHeight: 27 }, dropdownItem: { fontSize: 12 } }}
|
|
||||||
style={{ width: 300, fontSize: 12 }}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
};
|
|
@ -0,0 +1,131 @@
|
|||||||
|
import { DefaultButton, DirectionalHint, Icon, IconButton, Link, Stack, Text, TooltipHost } from "@fluentui/react";
|
||||||
|
import * as Constants from "Common/Constants";
|
||||||
|
import {
|
||||||
|
getPartitionKeyName,
|
||||||
|
getPartitionKeyPlaceHolder,
|
||||||
|
getPartitionKeyTooltipText,
|
||||||
|
} from "Explorer/Panes/AddCollectionPanel/AddCollectionPanelUtility";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export interface AddMVPartitionKeyProps {
|
||||||
|
partitionKey?: string;
|
||||||
|
setPartitionKey: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
subPartitionKeys: string[];
|
||||||
|
setSubPartitionKeys: React.Dispatch<React.SetStateAction<string[]>>;
|
||||||
|
useHashV1: boolean;
|
||||||
|
setUseHashV1: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AddMVPartitionKey = (props: AddMVPartitionKeyProps): JSX.Element => {
|
||||||
|
const { partitionKey, setPartitionKey, subPartitionKeys, setSubPartitionKeys, useHashV1, setUseHashV1 } = props;
|
||||||
|
|
||||||
|
const partitionKeyValueOnChange = (value: string): void => {
|
||||||
|
if (!partitionKey && !value.startsWith("/")) {
|
||||||
|
setPartitionKey("/" + value);
|
||||||
|
} else {
|
||||||
|
setPartitionKey(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const subPartitionKeysValueOnChange = (value: string, index: number): void => {
|
||||||
|
const updatedSubPartitionKeys: string[] = [...subPartitionKeys];
|
||||||
|
if (!updatedSubPartitionKeys[index] && !value.startsWith("/")) {
|
||||||
|
updatedSubPartitionKeys[index] = "/" + value.trim();
|
||||||
|
} else {
|
||||||
|
updatedSubPartitionKeys[index] = value.trim();
|
||||||
|
}
|
||||||
|
setSubPartitionKeys(updatedSubPartitionKeys);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Stack horizontal>
|
||||||
|
<span className="mandatoryStar">* </span>
|
||||||
|
<Text className="panelTextBold" variant="small">
|
||||||
|
Partition key
|
||||||
|
</Text>
|
||||||
|
<TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={getPartitionKeyTooltipText()}>
|
||||||
|
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||||
|
</TooltipHost>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="addmaterializedView-partitionKeyValue"
|
||||||
|
aria-required
|
||||||
|
required
|
||||||
|
size={40}
|
||||||
|
className="panelTextField"
|
||||||
|
placeholder={getPartitionKeyPlaceHolder()}
|
||||||
|
aria-label={getPartitionKeyName()}
|
||||||
|
pattern=".*"
|
||||||
|
value={partitionKey}
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
partitionKeyValueOnChange(event.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{subPartitionKeys.map((subPartitionKey: string, subPartitionKeyIndex: number) => {
|
||||||
|
return (
|
||||||
|
<Stack style={{ marginBottom: 8 }} key={`uniqueKey${subPartitionKeyIndex}`} horizontal>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: "20px",
|
||||||
|
border: "solid",
|
||||||
|
borderWidth: "0px 0px 1px 1px",
|
||||||
|
marginRight: "5px",
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="addMaterializedView-partitionKeyValue"
|
||||||
|
key={`addMaterializedView-partitionKeyValue_${subPartitionKeyIndex}`}
|
||||||
|
aria-required
|
||||||
|
required
|
||||||
|
size={40}
|
||||||
|
tabIndex={subPartitionKeyIndex > 0 ? 1 : 0}
|
||||||
|
className="panelTextField"
|
||||||
|
autoComplete="off"
|
||||||
|
placeholder={getPartitionKeyPlaceHolder(subPartitionKeyIndex)}
|
||||||
|
aria-label={getPartitionKeyName()}
|
||||||
|
pattern={".*"}
|
||||||
|
value={subPartitionKey}
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
subPartitionKeysValueOnChange(event.target.value, subPartitionKeyIndex);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
iconProps={{ iconName: "Delete" }}
|
||||||
|
style={{ height: 27 }}
|
||||||
|
onClick={() => {
|
||||||
|
const updatedSubPartitionKeys = subPartitionKeys.filter(
|
||||||
|
(_, subPartitionKeyIndexToRemove) => subPartitionKeyIndex !== subPartitionKeyIndexToRemove,
|
||||||
|
);
|
||||||
|
setSubPartitionKeys(updatedSubPartitionKeys);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<Stack className="panelGroupSpacing">
|
||||||
|
<DefaultButton
|
||||||
|
styles={{ root: { padding: 0, width: 200, height: 30 }, label: { fontSize: 12 } }}
|
||||||
|
hidden={useHashV1}
|
||||||
|
disabled={subPartitionKeys.length >= Constants.BackendDefaults.maxNumMultiHashPartition}
|
||||||
|
onClick={() => setSubPartitionKeys([...subPartitionKeys, ""])}
|
||||||
|
>
|
||||||
|
Add hierarchical partition key
|
||||||
|
</DefaultButton>
|
||||||
|
{subPartitionKeys.length > 0 && (
|
||||||
|
<Text variant="small">
|
||||||
|
<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">
|
||||||
|
Learn more
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,10 @@
|
|||||||
|
import { Stack } from "@fluentui/react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export interface AddMVThroughputProps {
|
||||||
|
// isQuickstart: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AddMVThroughput = (props: AddMVThroughputProps): JSX.Element => {
|
||||||
|
return <Stack></Stack>;
|
||||||
|
};
|
@ -0,0 +1,138 @@
|
|||||||
|
import {
|
||||||
|
DirectionalHint,
|
||||||
|
Dropdown,
|
||||||
|
DropdownMenuItemType,
|
||||||
|
Icon,
|
||||||
|
IDropdownOption,
|
||||||
|
Link,
|
||||||
|
Separator,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TooltipHost,
|
||||||
|
} from "@fluentui/react";
|
||||||
|
import { Collection, Database } from "Contracts/ViewModels";
|
||||||
|
import Explorer from "Explorer/Explorer";
|
||||||
|
import { getPartitionKey } from "Explorer/Panes/AddCollectionPanel/AddCollectionPanelUtility";
|
||||||
|
import { AddMVPartitionKey } from "Explorer/Panes/AddMaterializedViewPanel/AddMVPartitionKey";
|
||||||
|
import { AddMVThroughput } from "Explorer/Panes/AddMaterializedViewPanel/AddMVThroughput";
|
||||||
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export interface AddMaterializedViewPanelProps {
|
||||||
|
explorer: Explorer;
|
||||||
|
sourceContainer?: Collection;
|
||||||
|
}
|
||||||
|
export const AddMaterializedViewPanel = (props: AddMaterializedViewPanelProps): JSX.Element => {
|
||||||
|
const { explorer, sourceContainer } = props;
|
||||||
|
|
||||||
|
const [sourceContainerOptions, setSourceContainerOptions] = useState<IDropdownOption[]>();
|
||||||
|
const [selectedSourceContainer, setSelectedSourceContainer] = useState<Collection>();
|
||||||
|
const [materializedViewId, setMaterializedViewId] = useState<string>();
|
||||||
|
const [materializedViewDefinition, setMaterializedViewDefinition] = useState<string>();
|
||||||
|
const [partitionKey, setPartitionKey] = useState<string>(getPartitionKey());
|
||||||
|
const [subPartitionKeys, setSubPartitionKeys] = useState<string[]>([]);
|
||||||
|
const [useHashV1, setUseHashV1] = useState<boolean>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const sourceContainerOptions: IDropdownOption[] = [];
|
||||||
|
useDatabases.getState().databases.forEach((database: Database) => {
|
||||||
|
sourceContainerOptions.push({
|
||||||
|
key: database.rid,
|
||||||
|
text: database.id(),
|
||||||
|
itemType: DropdownMenuItemType.Header,
|
||||||
|
});
|
||||||
|
|
||||||
|
database.collections().forEach((collection: Collection) => {
|
||||||
|
const isMaterializedView: boolean = !!collection.materializedViewDefinition();
|
||||||
|
sourceContainerOptions.push({
|
||||||
|
key: collection.rid,
|
||||||
|
text: collection.id(),
|
||||||
|
disabled: isMaterializedView,
|
||||||
|
...(isMaterializedView && {
|
||||||
|
title: "This is a materialized view.",
|
||||||
|
}),
|
||||||
|
data: collection,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
setSourceContainerOptions(sourceContainerOptions);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className="panelFormWrapper" id="panelMaterializedView">
|
||||||
|
<div className="panelMainContent">
|
||||||
|
<Stack>
|
||||||
|
<Stack horizontal>
|
||||||
|
<span className="mandatoryStar">* </span>
|
||||||
|
<Text className="panelTextBold" variant="small">
|
||||||
|
Source container id
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
<Dropdown
|
||||||
|
placeholder="Choose existing container"
|
||||||
|
options={sourceContainerOptions}
|
||||||
|
defaultSelectedKey={sourceContainer?.rid}
|
||||||
|
styles={{ title: { height: 27, lineHeight: 27 }, dropdownItem: { fontSize: 12 } }}
|
||||||
|
style={{ width: 300, fontSize: 12 }}
|
||||||
|
onChange={(_, options: IDropdownOption) => setSelectedSourceContainer(options.data as Collection)}
|
||||||
|
/>
|
||||||
|
<Separator className="panelSeparator" />
|
||||||
|
<Stack horizontal>
|
||||||
|
<span className="mandatoryStar">* </span>
|
||||||
|
<Text className="panelTextBold" variant="small">
|
||||||
|
View container id
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
<input
|
||||||
|
id="materializedViewId"
|
||||||
|
type="text"
|
||||||
|
aria-required
|
||||||
|
required
|
||||||
|
autoComplete="off"
|
||||||
|
pattern="[^/?#\\]*[^/?# \\]"
|
||||||
|
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
||||||
|
placeholder={`e.g., viewByEmailId`}
|
||||||
|
size={40}
|
||||||
|
className="panelTextField"
|
||||||
|
value={materializedViewId}
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => setMaterializedViewId(event.target.value)}
|
||||||
|
/>
|
||||||
|
<Stack horizontal>
|
||||||
|
<span className="mandatoryStar">* </span>
|
||||||
|
<Text className="panelTextBold" variant="small">
|
||||||
|
Materialized View Definition
|
||||||
|
</Text>
|
||||||
|
<TooltipHost
|
||||||
|
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||||
|
content={
|
||||||
|
<Link
|
||||||
|
href="https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/materialized-views#defining-materialized-views"
|
||||||
|
target="blank"
|
||||||
|
>
|
||||||
|
Learn more about defining materialized views.
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Icon role="button" iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||||
|
</TooltipHost>
|
||||||
|
</Stack>
|
||||||
|
<input
|
||||||
|
id="materializedViewDefinition"
|
||||||
|
type="text"
|
||||||
|
aria-required
|
||||||
|
required
|
||||||
|
autoComplete="off"
|
||||||
|
placeholder={"SELECT c.email, c.accountId FROM c"}
|
||||||
|
size={40}
|
||||||
|
className="panelTextField"
|
||||||
|
value={materializedViewDefinition}
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => setMaterializedViewDefinition(event.target.value)}
|
||||||
|
/>
|
||||||
|
<AddMVPartitionKey {...{ partitionKey, setPartitionKey, subPartitionKeys, setSubPartitionKeys }} />
|
||||||
|
<AddMVThroughput />
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
@ -18,7 +18,7 @@ import { createCollection } from "Common/dataAccess/createCollection";
|
|||||||
import * as DataModels from "Contracts/DataModels";
|
import * as DataModels from "Contracts/DataModels";
|
||||||
import { ContainerSampleGenerator } from "Explorer/DataSamples/ContainerSampleGenerator";
|
import { ContainerSampleGenerator } from "Explorer/DataSamples/ContainerSampleGenerator";
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { AllPropertiesIndexed } from "Explorer/Panes/AddCollectionPanel";
|
import { AllPropertiesIndexed } from "Explorer/Panes/AddCollectionPanel/AddCollectionPanel";
|
||||||
import { PromptCard } from "Explorer/QueryCopilot/PromptCard";
|
import { PromptCard } from "Explorer/QueryCopilot/PromptCard";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
import { useCarousel } from "hooks/useCarousel";
|
import { useCarousel } from "hooks/useCarousel";
|
||||||
|
@ -18,7 +18,10 @@ import { isMaterializedViewsEnabled } from "Common/DatabaseAccountUtility";
|
|||||||
import { Platform, configContext } from "ConfigContext";
|
import { Platform, configContext } from "ConfigContext";
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { AddDatabasePanel } from "Explorer/Panes/AddDatabasePanel/AddDatabasePanel";
|
import { AddDatabasePanel } from "Explorer/Panes/AddDatabasePanel/AddDatabasePanel";
|
||||||
import { AddMaterializedViewPanel, AddMaterializedViewPanelProps } from "Explorer/Panes/AddMaterializedViewPanel";
|
import {
|
||||||
|
AddMaterializedViewPanel,
|
||||||
|
AddMaterializedViewPanelProps,
|
||||||
|
} from "Explorer/Panes/AddMaterializedViewPanel/AddMaterializedViewPanel";
|
||||||
import { Tabs } from "Explorer/Tabs/Tabs";
|
import { Tabs } from "Explorer/Tabs/Tabs";
|
||||||
import { CosmosFluentProvider, cosmosShorthands, tokens } from "Explorer/Theme/ThemeUtil";
|
import { CosmosFluentProvider, cosmosShorthands, tokens } from "Explorer/Theme/ThemeUtil";
|
||||||
import { ResourceTree } from "Explorer/Tree/ResourceTree";
|
import { ResourceTree } from "Explorer/Tree/ResourceTree";
|
||||||
|
@ -19,7 +19,7 @@ import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
|||||||
import { useSidePanel } from "../../hooks/useSidePanel";
|
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||||
import { useTabs } from "../../hooks/useTabs";
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { AddCollectionPanel } from "../Panes/AddCollectionPanel";
|
import { AddCollectionPanel } from "../Panes/AddCollectionPanel/AddCollectionPanel";
|
||||||
import { DatabaseSettingsTabV2 } from "../Tabs/SettingsTabV2";
|
import { DatabaseSettingsTabV2 } from "../Tabs/SettingsTabV2";
|
||||||
import { useDatabases } from "../useDatabases";
|
import { useDatabases } from "../useDatabases";
|
||||||
import { useSelectedNode } from "../useSelectedNode";
|
import { useSelectedNode } from "../useSelectedNode";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user