From 7b7a2817b6311ac56f2f7f3f098ae584892adc09 Mon Sep 17 00:00:00 2001
From: Nishtha Ahuja <45535788+nishthaAhujaa@users.noreply.github.com>
Date: Thu, 6 Mar 2025 19:01:58 +0530
Subject: [PATCH] All views associated with a container (#2063) and
Materialized View Target Container (#2065)
Identified Source container and Target container
Created tabs in Scale and Settings respectively
Changed the Icon of target container
---
.../Controls/Settings/SettingsComponent.tsx | 18 ++++
.../MaterializedViewComponent.tsx | 31 ++++++
.../MaterializedViewSourceComponent.tsx | 96 +++++++++++++++++++
.../MaterializedViewTargetComponent.tsx | 43 +++++++++
.../Controls/Settings/SettingsUtils.tsx | 3 +
src/Explorer/Tree/treeNodeUtil.tsx | 9 +-
6 files changed, 196 insertions(+), 4 deletions(-)
create mode 100644 src/Explorer/Controls/Settings/SettingsSubComponents/MaterializedViewComponent.tsx
create mode 100644 src/Explorer/Controls/Settings/SettingsSubComponents/MaterializedViewSourceComponent.tsx
create mode 100644 src/Explorer/Controls/Settings/SettingsSubComponents/MaterializedViewTargetComponent.tsx
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();