mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-05-03 06:53:49 +01:00
Merge branch 'feature/materialized-views' of https://github.com/Azure/cosmos-explorer into feature/materialized-views
This commit is contained in:
commit
176bb47cb5
@ -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<SettingsComponentProps, S
|
||||
private shouldShowComputedPropertiesEditor: boolean;
|
||||
private shouldShowIndexingPolicyEditor: boolean;
|
||||
private shouldShowPartitionKeyEditor: boolean;
|
||||
private isMaterializedView: boolean;
|
||||
private isVectorSearchEnabled: boolean;
|
||||
private isFullTextSearchEnabled: boolean;
|
||||
private totalThroughputUsed: number;
|
||||
@ -179,6 +184,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
this.shouldShowComputedPropertiesEditor = userContext.apiType === "SQL";
|
||||
this.shouldShowIndexingPolicyEditor = userContext.apiType !== "Cassandra" && userContext.apiType !== "Mongo";
|
||||
this.shouldShowPartitionKeyEditor = userContext.apiType === "SQL" && isRunningOnPublicCloud();
|
||||
this.isMaterializedView =
|
||||
!!this.collection?.materializedViewDefinition() || !!this.collection?.materializedViews();
|
||||
this.isVectorSearchEnabled = isVectorSearchEnabled() && !hasDatabaseSharedThroughput(this.collection);
|
||||
this.isFullTextSearchEnabled = isFullTextSearchEnabled() && !hasDatabaseSharedThroughput(this.collection);
|
||||
|
||||
@ -1272,6 +1279,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
explorer: this.props.settingsTab.getContainer(),
|
||||
};
|
||||
|
||||
const materializedViewComponentProps: MaterializedViewComponentProps = {
|
||||
collection: this.collection,
|
||||
};
|
||||
|
||||
const tabs: SettingsV2TabInfo[] = [];
|
||||
if (!hasDatabaseSharedThroughput(this.collection) && this.offer) {
|
||||
tabs.push({
|
||||
@ -1335,6 +1346,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
});
|
||||
}
|
||||
|
||||
if (this.isMaterializedView) {
|
||||
tabs.push({
|
||||
tab: SettingsV2TabTypes.MaterializedViewTab,
|
||||
content: <MaterializedViewComponent {...materializedViewComponentProps} />,
|
||||
});
|
||||
}
|
||||
|
||||
const pivotProps: IPivotProps = {
|
||||
onLinkClick: this.onPivotChange,
|
||||
selectedKey: SettingsV2TabTypes[this.state.selectedTab],
|
||||
|
@ -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<MaterializedViewComponentProps> = ({ collection }) => {
|
||||
const isTargetContainer = !!collection?.materializedViewDefinition();
|
||||
const isSourceContainer = !!collection?.materializedViews();
|
||||
|
||||
return (
|
||||
<Stack tokens={{ childrenGap: 8 }} styles={{ root: { maxWidth: 600 } }}>
|
||||
<Stack horizontal verticalAlign="center" wrap tokens={{ childrenGap: 8 }}>
|
||||
<Text styles={{ root: { fontWeight: 600 } }}>This container has the following views defined for it.</Text>
|
||||
<Text>
|
||||
<Link href="https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/materialized-views#defining-materialized-views">
|
||||
Learn more
|
||||
<FontIcon iconName="NavigateExternalInline" style={{ marginLeft: "4px" }} />
|
||||
</Link>{" "}
|
||||
about how to define materialized views and how to use them.
|
||||
</Text>
|
||||
</Stack>
|
||||
{isSourceContainer && <MaterializedViewSourceComponent collection={collection} />}
|
||||
{isTargetContainer && <MaterializedViewTargetComponent collection={collection} />}
|
||||
</Stack>
|
||||
);
|
||||
};
|
@ -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<MaterializedViewSourceComponentProps> = ({ collection }) => {
|
||||
const editorContainerRef = useRef<HTMLDivElement>(null);
|
||||
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>(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 (
|
||||
<div>
|
||||
<div
|
||||
ref={editorContainerRef}
|
||||
style={{
|
||||
height: 250,
|
||||
border: "1px solid #ccc",
|
||||
borderRadius: 4,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
/>
|
||||
<PrimaryButton
|
||||
text="Add view"
|
||||
styles={{ root: { width: "fit-content", marginTop: 12 } }}
|
||||
onClick={() => console.log("Add view clicked")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -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<MaterializedViewTargetComponentProps> = ({ collection }) => {
|
||||
const materializedViewDefinition = collection?.materializedViewDefinition();
|
||||
|
||||
const textHeadingStyle = {
|
||||
root: { fontWeight: "600", fontSize: 16 },
|
||||
};
|
||||
|
||||
const valueBoxStyle = {
|
||||
root: {
|
||||
backgroundColor: "#f3f3f3",
|
||||
padding: "5px 10px",
|
||||
borderRadius: "4px",
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack tokens={{ childrenGap: 15 }} styles={{ root: { maxWidth: 600 } }}>
|
||||
<Text styles={textHeadingStyle}>Materialized View Settings</Text>
|
||||
|
||||
<Stack tokens={{ childrenGap: 5 }}>
|
||||
<Text styles={{ root: { fontWeight: "600" } }}>Source container</Text>
|
||||
<Stack styles={valueBoxStyle}>
|
||||
<Text>{materializedViewDefinition?.sourceCollectionId}</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Stack tokens={{ childrenGap: 5 }}>
|
||||
<Text styles={{ root: { fontWeight: "600" } }}>Materialized view definition</Text>
|
||||
<Stack styles={valueBoxStyle}>
|
||||
<Text>{materializedViewDefinition?.definition}</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
@ -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}`);
|
||||
}
|
||||
|
@ -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 = <DatabaseRegular fontSize={16} />;
|
||||
const TreeSettingsIcon = <SettingsRegular fontSize={16} />;
|
||||
const TreeCollectionIcon = <DocumentMultipleRegular fontSize={16} />;
|
||||
const MaterializedViewCollectionIcon = <EyeRegular fontSize={16} />; //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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user