diff --git a/src/Explorer/Controls/Settings/SettingsComponent.tsx b/src/Explorer/Controls/Settings/SettingsComponent.tsx index 720bef874..88e83a83f 100644 --- a/src/Explorer/Controls/Settings/SettingsComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsComponent.tsx @@ -45,6 +45,10 @@ import { ConflictResolutionComponentProps, } from "./SettingsSubComponents/ConflictResolutionComponent"; import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent"; +import { + MaterializedViewComponent, + MaterializedViewComponentProps, +} from "./SettingsSubComponents/MaterializedViewComponent"; import { MongoIndexingPolicyComponent, MongoIndexingPolicyComponentProps, @@ -162,6 +166,7 @@ export class SettingsComponent extends React.Component, + }); + } + const pivotProps: IPivotProps = { onLinkClick: this.onPivotChange, selectedKey: SettingsV2TabTypes[this.state.selectedTab], diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/MaterializedViewComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/MaterializedViewComponent.tsx new file mode 100644 index 000000000..73523fdc0 --- /dev/null +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/MaterializedViewComponent.tsx @@ -0,0 +1,31 @@ +import { FontIcon, Link, Stack, Text } from "@fluentui/react"; +import React from "react"; +import * as ViewModels from "../../../../Contracts/ViewModels"; +import { MaterializedViewSourceComponent } from "./MaterializedViewSourceComponent"; +import { MaterializedViewTargetComponent } from "./MaterializedViewTargetComponent"; + +export interface MaterializedViewComponentProps { + collection: ViewModels.Collection; +} + +export const MaterializedViewComponent: React.FC = ({ collection }) => { + const isTargetContainer = !!collection?.materializedViewDefinition(); + const isSourceContainer = !!collection?.materializedViews(); + + return ( + + + This container has the following views defined for it. + + + Learn more + + {" "} + about how to define materialized views and how to use them. + + + {isSourceContainer && } + {isTargetContainer && } + + ); +}; diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/MaterializedViewSourceComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/MaterializedViewSourceComponent.tsx new file mode 100644 index 000000000..dafbd930c --- /dev/null +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/MaterializedViewSourceComponent.tsx @@ -0,0 +1,96 @@ +import { PrimaryButton } from "@fluentui/react"; +import { loadMonaco } from "Explorer/LazyMonaco"; +import { useDatabases } from "Explorer/useDatabases"; +import * as monaco from "monaco-editor"; +import React, { useEffect, useRef } from "react"; +import * as ViewModels from "../../../../Contracts/ViewModels"; + +export interface MaterializedViewSourceComponentProps { + collection: ViewModels.Collection; +} + +export const MaterializedViewSourceComponent: React.FC = ({ collection }) => { + const editorContainerRef = useRef(null); + const editorRef = useRef(null); + + const materializedViews = collection?.materializedViews() ?? []; + + // Helper function to fetch the definition and partition key of targetContainer by traversing through all collections and matching id from MaterializedViews[] with collection id. + const getViewDetails = (viewId: string): { definition: string; partitionKey: string[] } => { + let definition = ""; + let partitionKey: string[] = []; + + useDatabases.getState().databases.find((database) => { + const collection = database.collections().find((collection) => collection.id() === viewId); + if (collection) { + const materializedViewDefinition = collection.materializedViewDefinition(); + materializedViewDefinition && (definition = materializedViewDefinition.definition); + collection.partitionKey?.paths && (partitionKey = collection.partitionKey.paths); + } + }); + + return { definition, partitionKey }; + }; + + //JSON value for the editor using the fetched id and definitions. + const jsonValue = JSON.stringify( + materializedViews.map((view) => { + const { definition, partitionKey } = getViewDetails(view.id); + return { + name: view.id, + partitionKey: partitionKey.join(", "), + definition, + }; + }), + null, + 2, + ); + + // Initialize Monaco editor with the computed JSON value. + useEffect(() => { + let disposed = false; + const initMonaco = async () => { + const monacoInstance = await loadMonaco(); + if (disposed || !editorContainerRef.current) return; + + editorRef.current = monacoInstance.editor.create(editorContainerRef.current, { + value: jsonValue, + language: "json", + ariaLabel: "Materialized Views JSON", + readOnly: true, + }); + }; + + initMonaco(); + return () => { + disposed = true; + editorRef.current?.dispose(); + }; + }, [jsonValue]); + + // Update the editor when the jsonValue changes. + useEffect(() => { + if (editorRef.current) { + editorRef.current.setValue(jsonValue); + } + }, [jsonValue]); + + return ( +
+
+ console.log("Add view clicked")} + /> +
+ ); +}; diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/MaterializedViewTargetComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/MaterializedViewTargetComponent.tsx new file mode 100644 index 000000000..371da00ed --- /dev/null +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/MaterializedViewTargetComponent.tsx @@ -0,0 +1,43 @@ +import { Stack, Text } from "@fluentui/react"; +import * as React from "react"; +import * as ViewModels from "../../../../Contracts/ViewModels"; + +export interface MaterializedViewTargetComponentProps { + collection: ViewModels.Collection; +} + +export const MaterializedViewTargetComponent: React.FC = ({ collection }) => { + const materializedViewDefinition = collection?.materializedViewDefinition(); + + const textHeadingStyle = { + root: { fontWeight: "600", fontSize: 16 }, + }; + + const valueBoxStyle = { + root: { + backgroundColor: "#f3f3f3", + padding: "5px 10px", + borderRadius: "4px", + }, + }; + + return ( + + Materialized View Settings + + + Source container + + {materializedViewDefinition?.sourceCollectionId} + + + + + Materialized view definition + + {materializedViewDefinition?.definition} + + + + ); +}; diff --git a/src/Explorer/Controls/Settings/SettingsUtils.tsx b/src/Explorer/Controls/Settings/SettingsUtils.tsx index 900ad6ab0..da3785622 100644 --- a/src/Explorer/Controls/Settings/SettingsUtils.tsx +++ b/src/Explorer/Controls/Settings/SettingsUtils.tsx @@ -57,6 +57,7 @@ export enum SettingsV2TabTypes { ComputedPropertiesTab, ContainerVectorPolicyTab, ThroughputBucketsTab, + MaterializedViewTab, } export enum ContainerPolicyTabTypes { @@ -171,6 +172,8 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => { return "Container Policies"; case SettingsV2TabTypes.ThroughputBucketsTab: return "Throughput Buckets"; + case SettingsV2TabTypes.MaterializedViewTab: + return "Materialized Views (Preview)"; default: throw new Error(`Unknown tab ${tab}`); } diff --git a/src/Explorer/Tree/treeNodeUtil.tsx b/src/Explorer/Tree/treeNodeUtil.tsx index 8e6c94559..533008ab3 100644 --- a/src/Explorer/Tree/treeNodeUtil.tsx +++ b/src/Explorer/Tree/treeNodeUtil.tsx @@ -1,4 +1,4 @@ -import { DatabaseRegular, DocumentMultipleRegular, SettingsRegular } from "@fluentui/react-icons"; +import { DatabaseRegular, DocumentMultipleRegular, EyeRegular, SettingsRegular } from "@fluentui/react-icons"; import { TreeNode } from "Explorer/Controls/TreeComponent/TreeNodeComponent"; import { collectionWasOpened } from "Explorer/MostRecentActivity/MostRecentActivity"; import TabsBase from "Explorer/Tabs/TabsBase"; @@ -30,6 +30,7 @@ export const shouldShowScriptNodes = (): boolean => { const TreeDatabaseIcon = ; const TreeSettingsIcon = ; const TreeCollectionIcon = ; +const MaterializedViewCollectionIcon = ; //check icon export const createSampleDataTreeNodes = (sampleDataResourceTokenCollection: ViewModels.CollectionBase): TreeNode[] => { const updatedSampleTree: TreeNode = { @@ -81,7 +82,7 @@ export const createSampleDataTreeNodes = (sampleDataResourceTokenCollection: Vie return [updatedSampleTree]; }; -export const createResourceTokenTreeNodes = (collection: ViewModels.CollectionBase): TreeNode[] => { +export const createResourceTokenTreeNodes = (collection: ViewModels.Collection): TreeNode[] => { if (!collection) { return [ { @@ -111,7 +112,7 @@ export const createResourceTokenTreeNodes = (collection: ViewModels.CollectionBa isExpanded: true, children, className: "collectionNode", - iconSrc: TreeCollectionIcon, + iconSrc: collection.materializedViewDefinition() ? MaterializedViewCollectionIcon : TreeCollectionIcon, onClick: () => { // Rewritten version of expandCollapseCollection useSelectedNode.getState().setSelectedNode(collection); @@ -229,7 +230,7 @@ export const buildCollectionNode = ( children: children, className: "collectionNode", contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(container, collection), - iconSrc: TreeCollectionIcon, + iconSrc: collection.materializedViewDefinition() ? MaterializedViewCollectionIcon : TreeCollectionIcon, onClick: () => { useSelectedNode.getState().setSelectedNode(collection); collection.openTab();