diff --git a/src/Common/dataAccess/readCollections.ts b/src/Common/dataAccess/readCollections.ts index 028933f31..570df8d60 100644 --- a/src/Common/dataAccess/readCollections.ts +++ b/src/Common/dataAccess/readCollections.ts @@ -127,6 +127,7 @@ async function readCollectionsWithARM(databaseId: string): Promise { const collectionDataModel: DataModels.Collection = collection.properties?.resource as DataModels.Collection; collectionDataModel.materializedViews = collection.properties?.resource?.materializedViews; diff --git a/src/Explorer/ContextMenuButtonFactory.tsx b/src/Explorer/ContextMenuButtonFactory.tsx index 8946c1e18..39377f500 100644 --- a/src/Explorer/ContextMenuButtonFactory.tsx +++ b/src/Explorer/ContextMenuButtonFactory.tsx @@ -1,4 +1,10 @@ +import { MaterializedViewsLabels } from "Common/Constants"; +import { isMaterializedViewsEnabled } from "Common/DatabaseAccountUtility"; import { TreeNodeMenuItem } from "Explorer/Controls/TreeComponent/TreeNodeComponent"; +import { + AddMaterializedViewPanel, + AddMaterializedViewPanelProps, +} from "Explorer/Panes/AddMaterializedViewPanel/AddMaterializedViewPanel"; import { useDatabases } from "Explorer/useDatabases"; import { Action } from "Shared/Telemetry/TelemetryConstants"; 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, + , + ); + }, + }); + } + return items; }; diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index fdef1076e..96bc0c9ed 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -55,7 +55,7 @@ import type NotebookManager from "./Notebook/NotebookManager"; import { NotebookPaneContent } from "./Notebook/NotebookManager"; import { NotebookUtil } from "./Notebook/NotebookUtil"; import { useNotebook } from "./Notebook/useNotebook"; -import { AddCollectionPanel } from "./Panes/AddCollectionPanel"; +import { AddCollectionPanel } from "./Panes/AddCollectionPanel/AddCollectionPanel"; import { CassandraAddCollectionPane } from "./Panes/CassandraAddCollectionPane/CassandraAddCollectionPane"; import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane"; import { StringInputPane } from "./Panes/StringInputPane/StringInputPane"; diff --git a/src/Explorer/Panes/AddCollectionPanel.test.tsx b/src/Explorer/Panes/AddCollectionPanel/AddCollectionPanel.test.tsx similarity index 100% rename from src/Explorer/Panes/AddCollectionPanel.test.tsx rename to src/Explorer/Panes/AddCollectionPanel/AddCollectionPanel.test.tsx diff --git a/src/Explorer/Panes/AddCollectionPanel.tsx b/src/Explorer/Panes/AddCollectionPanel/AddCollectionPanel.tsx similarity index 94% rename from src/Explorer/Panes/AddCollectionPanel.tsx rename to src/Explorer/Panes/AddCollectionPanel/AddCollectionPanel.tsx index 61cca9f74..897427d02 100644 --- a/src/Explorer/Panes/AddCollectionPanel.tsx +++ b/src/Explorer/Panes/AddCollectionPanel/AddCollectionPanel.tsx @@ -26,6 +26,12 @@ import { getFullTextLanguageOptions, } from "Explorer/Controls/FullTextSeach/FullTextPoliciesComponent"; import { VectorEmbeddingPoliciesComponent } from "Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent"; +import { + getPartitionKey, + getPartitionKeyName, + getPartitionKeyPlaceHolder, + getPartitionKeyTooltipText, +} from "Explorer/Panes/AddCollectionPanel/AddCollectionPanelUtility"; import { useSidePanel } from "hooks/useSidePanel"; import { useTeachingBubble } from "hooks/useTeachingBubble"; import React from "react"; @@ -41,15 +47,15 @@ import { isVectorSearchEnabled, } from "Utils/CapabilityUtils"; import { getUpsellMessage } from "Utils/PricingUtils"; -import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent"; -import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput"; -import "../Controls/ThroughputInput/ThroughputInput.less"; -import { ContainerSampleGenerator } from "../DataSamples/ContainerSampleGenerator"; -import Explorer from "../Explorer"; -import { useDatabases } from "../useDatabases"; -import { PanelFooterComponent } from "./PanelFooterComponent"; -import { PanelInfoErrorComponent } from "./PanelInfoErrorComponent"; -import { PanelLoadingScreen } from "./PanelLoadingScreen"; +import { CollapsibleSectionComponent } from "../../Controls/CollapsiblePanel/CollapsibleSectionComponent"; +import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput"; +import "../../Controls/ThroughputInput/ThroughputInput.less"; +import { ContainerSampleGenerator } from "../../DataSamples/ContainerSampleGenerator"; +import Explorer from "../../Explorer"; +import { useDatabases } from "../../useDatabases"; +import { PanelFooterComponent } from "../PanelFooterComponent"; +import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent"; +import { PanelLoadingScreen } from "../PanelLoadingScreen"; export interface AddCollectionPanelProps { explorer: Explorer; @@ -143,7 +149,7 @@ export class AddCollectionPanel extends React.Component - {this.getPartitionKeyName()} + {getPartitionKeyName()} - + @@ -600,8 +603,8 @@ export class AddCollectionPanel extends React.Component 0 ? 1 : 0} className="panelTextField" autoComplete="off" - placeholder={this.getPartitionKeyPlaceHolder(index)} - aria-label={this.getPartitionKeyName()} + placeholder={getPartitionKeyPlaceHolder(index)} + aria-label={getPartitionKeyName()} pattern={".*"} title={""} value={subPartitionKey} @@ -1053,31 +1056,6 @@ export class AddCollectionPanel extends React.Component): void { if (event.target.checked && !this.state.createNewDatabase) { this.setState({ @@ -1175,38 +1153,6 @@ export class AddCollectionPanel extends React.Component { - console.log(useDatabases.getState().databases); - console.log( - useDatabases.getState().databases.forEach((db) => { - console.log(db); - console.log(db.collections()); - }), - ); - - return ( -
-
- - - -
-
- ); -}; diff --git a/src/Explorer/Panes/AddMaterializedViewPanel/AddMVPartitionKey.tsx b/src/Explorer/Panes/AddMaterializedViewPanel/AddMVPartitionKey.tsx new file mode 100644 index 000000000..506619de0 --- /dev/null +++ b/src/Explorer/Panes/AddMaterializedViewPanel/AddMVPartitionKey.tsx @@ -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>; + subPartitionKeys: string[]; + setSubPartitionKeys: React.Dispatch>; + useHashV1: boolean; + setUseHashV1: React.Dispatch>; +} + +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 ( + + + + + Partition key + + + + + + + ) => { + partitionKeyValueOnChange(event.target.value); + }} + /> + {subPartitionKeys.map((subPartitionKey: string, subPartitionKeyIndex: number) => { + return ( + +
+ 0 ? 1 : 0} + className="panelTextField" + autoComplete="off" + placeholder={getPartitionKeyPlaceHolder(subPartitionKeyIndex)} + aria-label={getPartitionKeyName()} + pattern={".*"} + value={subPartitionKey} + onChange={(event: React.ChangeEvent) => { + subPartitionKeysValueOnChange(event.target.value, subPartitionKeyIndex); + }} + /> + { + const updatedSubPartitionKeys = subPartitionKeys.filter( + (_, subPartitionKeyIndexToRemove) => subPartitionKeyIndex !== subPartitionKeyIndexToRemove, + ); + setSubPartitionKeys(updatedSubPartitionKeys); + }} + /> +
+ ); + })} + + + {subPartitionKeys.length > 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.{" "} + + Learn more + + + )} + +
+ ); +}; diff --git a/src/Explorer/Panes/AddMaterializedViewPanel/AddMVThroughput.tsx b/src/Explorer/Panes/AddMaterializedViewPanel/AddMVThroughput.tsx new file mode 100644 index 000000000..036ffdab0 --- /dev/null +++ b/src/Explorer/Panes/AddMaterializedViewPanel/AddMVThroughput.tsx @@ -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 ; +}; diff --git a/src/Explorer/Panes/AddMaterializedViewPanel/AddMaterializedViewPanel.tsx b/src/Explorer/Panes/AddMaterializedViewPanel/AddMaterializedViewPanel.tsx new file mode 100644 index 000000000..6e36e0c16 --- /dev/null +++ b/src/Explorer/Panes/AddMaterializedViewPanel/AddMaterializedViewPanel.tsx @@ -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(); + const [selectedSourceContainer, setSelectedSourceContainer] = useState(); + const [materializedViewId, setMaterializedViewId] = useState(); + const [materializedViewDefinition, setMaterializedViewDefinition] = useState(); + const [partitionKey, setPartitionKey] = useState(getPartitionKey()); + const [subPartitionKeys, setSubPartitionKeys] = useState([]); + const [useHashV1, setUseHashV1] = useState(); + + 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 ( +
+
+ + + + + Source container id + + + setSelectedSourceContainer(options.data as Collection)} + /> + + + + + View container id + + + ) => setMaterializedViewId(event.target.value)} + /> + + + + Materialized View Definition + + + Learn more about defining materialized views. + + } + > + + + + ) => setMaterializedViewDefinition(event.target.value)} + /> + + + +
+
+ ); +}; diff --git a/src/Explorer/QueryCopilot/CopilotCarousel.tsx b/src/Explorer/QueryCopilot/CopilotCarousel.tsx index 4a73cacb8..3657bfc84 100644 --- a/src/Explorer/QueryCopilot/CopilotCarousel.tsx +++ b/src/Explorer/QueryCopilot/CopilotCarousel.tsx @@ -18,7 +18,7 @@ import { createCollection } from "Common/dataAccess/createCollection"; import * as DataModels from "Contracts/DataModels"; import { ContainerSampleGenerator } from "Explorer/DataSamples/ContainerSampleGenerator"; 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 { useDatabases } from "Explorer/useDatabases"; import { useCarousel } from "hooks/useCarousel"; diff --git a/src/Explorer/Sidebar.tsx b/src/Explorer/Sidebar.tsx index e48ec770d..2ad443efa 100644 --- a/src/Explorer/Sidebar.tsx +++ b/src/Explorer/Sidebar.tsx @@ -18,7 +18,10 @@ import { isMaterializedViewsEnabled } from "Common/DatabaseAccountUtility"; import { Platform, configContext } from "ConfigContext"; import Explorer from "Explorer/Explorer"; 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 { CosmosFluentProvider, cosmosShorthands, tokens } from "Explorer/Theme/ThemeUtil"; import { ResourceTree } from "Explorer/Tree/ResourceTree"; diff --git a/src/Explorer/Tree/Database.tsx b/src/Explorer/Tree/Database.tsx index 0ed11cb5d..5ad2769d8 100644 --- a/src/Explorer/Tree/Database.tsx +++ b/src/Explorer/Tree/Database.tsx @@ -19,7 +19,7 @@ import { logConsoleError } from "../../Utils/NotificationConsoleUtils"; import { useSidePanel } from "../../hooks/useSidePanel"; import { useTabs } from "../../hooks/useTabs"; import Explorer from "../Explorer"; -import { AddCollectionPanel } from "../Panes/AddCollectionPanel"; +import { AddCollectionPanel } from "../Panes/AddCollectionPanel/AddCollectionPanel"; import { DatabaseSettingsTabV2 } from "../Tabs/SettingsTabV2"; import { useDatabases } from "../useDatabases"; import { useSelectedNode } from "../useSelectedNode";