mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-26 12:21:23 +00:00
Compare commits
5 Commits
cloudshell
...
users/lang
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da33f42a81 | ||
|
|
922ca5c523 | ||
|
|
bafe002fa3 | ||
|
|
0817acf404 | ||
|
|
8e2c46301d |
@@ -765,3 +765,10 @@ export const ShortenedQueryCopilotSampleContainerSchema = {
|
|||||||
|
|
||||||
userPrompt: "find all products",
|
userPrompt: "find all products",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum MongoGuidRepresentation {
|
||||||
|
Standard = "Standard",
|
||||||
|
CSharpLegacy = "CSharpLegacy",
|
||||||
|
JavaLegacy = "JavaLegacy",
|
||||||
|
PythonLegacy = "PythonLegacy",
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
|
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
|
||||||
|
import { getMongoGuidRepresentation } from "Shared/StorageUtility";
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import { configContext } from "../ConfigContext";
|
import { configContext } from "../ConfigContext";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
@@ -139,6 +140,9 @@ export function readDocument(
|
|||||||
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
|
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
|
||||||
? documentId.partitionKeyProperties?.[0]
|
? documentId.partitionKeyProperties?.[0]
|
||||||
: "",
|
: "",
|
||||||
|
clientSettings: {
|
||||||
|
guidRepresentation: getMongoGuidRepresentation(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
||||||
@@ -181,6 +185,9 @@ export function createDocument(
|
|||||||
partitionKey:
|
partitionKey:
|
||||||
collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "",
|
collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "",
|
||||||
documentContent: JSON.stringify(documentContent),
|
documentContent: JSON.stringify(documentContent),
|
||||||
|
clientSettings: {
|
||||||
|
guidRepresentation: getMongoGuidRepresentation(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
||||||
@@ -228,6 +235,9 @@ export function updateDocument(
|
|||||||
? documentId.partitionKeyProperties?.[0]
|
? documentId.partitionKeyProperties?.[0]
|
||||||
: "",
|
: "",
|
||||||
documentContent,
|
documentContent,
|
||||||
|
clientSettings: {
|
||||||
|
guidRepresentation: getMongoGuidRepresentation(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
||||||
|
|
||||||
@@ -274,6 +284,9 @@ export function deleteDocuments(
|
|||||||
subscriptionID: userContext.subscriptionId,
|
subscriptionID: userContext.subscriptionId,
|
||||||
resourceGroup: userContext.resourceGroup,
|
resourceGroup: userContext.resourceGroup,
|
||||||
databaseAccountName: databaseAccount.name,
|
databaseAccountName: databaseAccount.name,
|
||||||
|
clientSettings: {
|
||||||
|
guidRepresentation: getMongoGuidRepresentation(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export enum FabricMessageTypes {
|
|||||||
GetAccessToken = "GetAccessToken",
|
GetAccessToken = "GetAccessToken",
|
||||||
Ready = "Ready",
|
Ready = "Ready",
|
||||||
OpenSettings = "OpenSettings",
|
OpenSettings = "OpenSettings",
|
||||||
|
NewShortcut = "NewShortcut",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthorizationToken {
|
export interface AuthorizationToken {
|
||||||
|
|||||||
@@ -84,9 +84,11 @@ export type FabricMessageV3 =
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "refreshResourceTree";
|
type: "refreshResourceTree";
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "shortcutCreated";
|
||||||
message: {
|
message: {
|
||||||
id: string;
|
shortcutId: string;
|
||||||
error: string | undefined;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Environment, getEnvironment } from "Common/EnvironmentUtility";
|
|||||||
import { sendMessage } from "Common/MessageHandler";
|
import { sendMessage } from "Common/MessageHandler";
|
||||||
import { Platform, configContext } from "ConfigContext";
|
import { Platform, configContext } from "ConfigContext";
|
||||||
import { MessageTypes } from "Contracts/ExplorerContracts";
|
import { MessageTypes } from "Contracts/ExplorerContracts";
|
||||||
|
import { AddFabricShortcutPanel } from "Explorer/Panes/AddCollectionPanel/AddFabricShortcutPanel";
|
||||||
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
|
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
|
||||||
import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||||
import { IGalleryItem } from "Juno/JunoClient";
|
import { IGalleryItem } from "Juno/JunoClient";
|
||||||
@@ -1112,6 +1113,14 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async onNewFabricShortcutClicked(param: {
|
||||||
|
shortcutId: string;
|
||||||
|
}): Promise<void> {
|
||||||
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel("Configure Shortcut", <AddFabricShortcutPanel explorer={this} shortcutId={param.shortcutId} />);
|
||||||
|
}
|
||||||
|
|
||||||
private refreshCommandBarButtons(): void {
|
private refreshCommandBarButtons(): void {
|
||||||
const activeTab = useTabs.getState().activeTab;
|
const activeTab = useTabs.getState().activeTab;
|
||||||
if (activeTab) {
|
if (activeTab) {
|
||||||
|
|||||||
800
src/Explorer/Panes/AddCollectionPanel/AddFabricShortcutPanel.tsx
Normal file
800
src/Explorer/Panes/AddCollectionPanel/AddFabricShortcutPanel.tsx
Normal file
@@ -0,0 +1,800 @@
|
|||||||
|
import {
|
||||||
|
ChoiceGroup,
|
||||||
|
DefaultButton,
|
||||||
|
DirectionalHint,
|
||||||
|
Icon,
|
||||||
|
IconButton,
|
||||||
|
IDropdownOption,
|
||||||
|
Link,
|
||||||
|
ProgressIndicator,
|
||||||
|
Separator,
|
||||||
|
Stack,
|
||||||
|
TeachingBubble,
|
||||||
|
Text,
|
||||||
|
TooltipHost
|
||||||
|
} from "@fluentui/react";
|
||||||
|
import * as Constants from "Common/Constants";
|
||||||
|
import { createCollection } from "Common/dataAccess/createCollection";
|
||||||
|
import { getNewDatabaseSharedThroughputDefault } from "Common/DatabaseUtility";
|
||||||
|
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
|
||||||
|
import { configContext, Platform } from "ConfigContext";
|
||||||
|
import * as DataModels from "Contracts/DataModels";
|
||||||
|
import { VectorEmbeddingPoliciesComponent } from "Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent";
|
||||||
|
import {
|
||||||
|
AllPropertiesIndexed,
|
||||||
|
ContainerVectorPolicyTooltipContent,
|
||||||
|
FullTextPolicyDefault,
|
||||||
|
getPartitionKey,
|
||||||
|
getPartitionKeyName,
|
||||||
|
getPartitionKeyPlaceHolder,
|
||||||
|
getPartitionKeyTooltipText,
|
||||||
|
isFreeTierAccount,
|
||||||
|
isSynapseLinkEnabled,
|
||||||
|
parseUniqueKeys,
|
||||||
|
scrollToSection,
|
||||||
|
SharedDatabaseDefault,
|
||||||
|
shouldShowAnalyticalStoreOptions
|
||||||
|
} from "Explorer/Panes/AddCollectionPanel/AddCollectionPanelUtility";
|
||||||
|
import { useSidePanel } from "hooks/useSidePanel";
|
||||||
|
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
||||||
|
import { isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||||
|
import React from "react";
|
||||||
|
import { CollectionCreation } from "Shared/Constants";
|
||||||
|
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||||
|
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import { userContext } from "UserContext";
|
||||||
|
import { getCollectionName } from "Utils/APITypeUtils";
|
||||||
|
import { isCapabilityEnabled, isServerlessAccount, isVectorSearchEnabled } from "Utils/CapabilityUtils";
|
||||||
|
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||||
|
import { CollapsibleSectionComponent } from "../../Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
||||||
|
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";
|
||||||
|
|
||||||
|
interface AddFabricShortcutPanelProps {
|
||||||
|
explorer: Explorer;
|
||||||
|
shortcutId: string;
|
||||||
|
|
||||||
|
|
||||||
|
databaseId?: string;
|
||||||
|
isQuickstart?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DefaultVectorEmbeddingPolicy: DataModels.VectorEmbeddingPolicy = {
|
||||||
|
vectorEmbeddings: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface AddCollectionPanelState {
|
||||||
|
createNewDatabase: boolean;
|
||||||
|
newDatabaseId: string;
|
||||||
|
isSharedThroughputChecked: boolean;
|
||||||
|
selectedDatabaseId: string;
|
||||||
|
collectionId: string;
|
||||||
|
enableIndexing: boolean;
|
||||||
|
isSharded: boolean;
|
||||||
|
partitionKey: string;
|
||||||
|
subPartitionKeys: string[];
|
||||||
|
enableDedicatedThroughput: boolean;
|
||||||
|
createMongoWildCardIndex: boolean;
|
||||||
|
useHashV1: boolean;
|
||||||
|
enableAnalyticalStore: boolean;
|
||||||
|
uniqueKeys: string[];
|
||||||
|
errorMessage: string;
|
||||||
|
showErrorDetails: boolean;
|
||||||
|
isExecuting: boolean;
|
||||||
|
isThroughputCapExceeded: boolean;
|
||||||
|
teachingBubbleStep: number;
|
||||||
|
vectorIndexingPolicy: DataModels.VectorIndex[];
|
||||||
|
vectorEmbeddingPolicy: DataModels.VectorEmbedding[];
|
||||||
|
vectorPolicyValidated: boolean;
|
||||||
|
fullTextPolicy: DataModels.FullTextPolicy;
|
||||||
|
fullTextIndexes: DataModels.FullTextIndex[];
|
||||||
|
fullTextPolicyValidated: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AddFabricShortcutPanel extends React.Component<AddFabricShortcutPanelProps, AddCollectionPanelState> {
|
||||||
|
private newDatabaseThroughput: number;
|
||||||
|
private isNewDatabaseAutoscale: boolean;
|
||||||
|
private collectionThroughput: number;
|
||||||
|
private isCollectionAutoscale: boolean;
|
||||||
|
private isCostAcknowledged: boolean;
|
||||||
|
private showFullTextSearch: boolean;
|
||||||
|
|
||||||
|
constructor(props: AddFabricShortcutPanelProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
createNewDatabase:
|
||||||
|
userContext.apiType !== "Tables" && configContext.platform !== Platform.Fabric && !this.props.databaseId,
|
||||||
|
newDatabaseId: props.isQuickstart ? this.getSampleDBName() : "",
|
||||||
|
isSharedThroughputChecked: getNewDatabaseSharedThroughputDefault(),
|
||||||
|
selectedDatabaseId:
|
||||||
|
userContext.apiType === "Tables" ? CollectionCreation.TablesAPIDefaultDatabase : this.props.databaseId,
|
||||||
|
collectionId: props.isQuickstart ? `Sample${getCollectionName()}` : "",
|
||||||
|
enableIndexing: true,
|
||||||
|
isSharded: userContext.apiType !== "Tables",
|
||||||
|
partitionKey: getPartitionKey(props.isQuickstart),
|
||||||
|
subPartitionKeys: [],
|
||||||
|
enableDedicatedThroughput: isFabricNative(), // Dedicated throughput is only enabled in Fabric Native by default
|
||||||
|
createMongoWildCardIndex:
|
||||||
|
isCapabilityEnabled("EnableMongo") && !isCapabilityEnabled("EnableMongo16MBDocumentSupport"),
|
||||||
|
useHashV1: false,
|
||||||
|
enableAnalyticalStore: false,
|
||||||
|
uniqueKeys: [],
|
||||||
|
errorMessage: "",
|
||||||
|
showErrorDetails: false,
|
||||||
|
isExecuting: false,
|
||||||
|
isThroughputCapExceeded: false,
|
||||||
|
teachingBubbleStep: 0,
|
||||||
|
vectorEmbeddingPolicy: [],
|
||||||
|
vectorIndexingPolicy: [],
|
||||||
|
vectorPolicyValidated: true,
|
||||||
|
fullTextPolicy: FullTextPolicyDefault,
|
||||||
|
fullTextIndexes: [],
|
||||||
|
fullTextPolicyValidated: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.showFullTextSearch = userContext.apiType === "SQL";
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
if (this.state.teachingBubbleStep === 0 && this.props.isQuickstart) {
|
||||||
|
this.setState({ teachingBubbleStep: 1 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(_prevProps: AddFabricShortcutPanelProps, prevState: AddCollectionPanelState): void {
|
||||||
|
if (this.state.errorMessage && this.state.errorMessage !== prevState.errorMessage) {
|
||||||
|
scrollToSection("panelContainer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): JSX.Element {
|
||||||
|
const options: IChoiceGroupOption[] = [
|
||||||
|
{ key: 'continuous', text: 'Continuous' },
|
||||||
|
{ key: 'manual', text: 'Manual' },
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className="panelFormWrapper" onSubmit={this.submit.bind(this)} id="panelContainer">
|
||||||
|
{this.state.errorMessage && (
|
||||||
|
<PanelInfoErrorComponent
|
||||||
|
message={this.state.errorMessage}
|
||||||
|
messageType="error"
|
||||||
|
showErrorDetails={this.state.showErrorDetails}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="panelMainContent">
|
||||||
|
<Stack>
|
||||||
|
<Stack horizontal style={{ marginTop: -5, marginBottom: -4 }}>
|
||||||
|
<Text className="panelTextBold" variant="small">Container id</Text>
|
||||||
|
<TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={getPartitionKeyTooltipText()}>
|
||||||
|
<Icon
|
||||||
|
iconName="Info"
|
||||||
|
className="panelInfoIcon"
|
||||||
|
tabIndex={0}
|
||||||
|
ariaLabel={getPartitionKeyTooltipText()}
|
||||||
|
/>
|
||||||
|
</TooltipHost>
|
||||||
|
</Stack>
|
||||||
|
<div>{this.props.shortcutId}</div>
|
||||||
|
<div>publicHolidays</div>
|
||||||
|
<Separator className="panelSeparator" style={{ marginTop: 2, marginBottom: -4 }} />
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
|
||||||
|
<Stack>
|
||||||
|
<Stack horizontal style={{ marginTop: -5, marginBottom: -4 }}>
|
||||||
|
<span className="mandatoryStar">* </span>
|
||||||
|
<Text className="panelTextBold" variant="small">
|
||||||
|
{getPartitionKeyName()}
|
||||||
|
</Text>
|
||||||
|
<TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={getPartitionKeyTooltipText()}>
|
||||||
|
<Icon
|
||||||
|
iconName="Info"
|
||||||
|
className="panelInfoIcon"
|
||||||
|
tabIndex={0}
|
||||||
|
ariaLabel={getPartitionKeyTooltipText()}
|
||||||
|
/>
|
||||||
|
</TooltipHost>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Text variant="small">{this.getPartitionKeySubtext()}</Text>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="addCollection-partitionKeyValue"
|
||||||
|
aria-required
|
||||||
|
required
|
||||||
|
size={40}
|
||||||
|
className="panelTextField"
|
||||||
|
placeholder={getPartitionKeyPlaceHolder()}
|
||||||
|
aria-label={getPartitionKeyName()}
|
||||||
|
pattern={userContext.apiType === "Gremlin" ? "^/[^/]*" : ".*"}
|
||||||
|
title={userContext.apiType === "Gremlin" ? "May not use composite partition key" : ""}
|
||||||
|
value={this.state.partitionKey}
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (
|
||||||
|
userContext.apiType !== "Mongo" &&
|
||||||
|
!this.state.partitionKey &&
|
||||||
|
!event.target.value.startsWith("/")
|
||||||
|
) {
|
||||||
|
this.setState({ partitionKey: "/" + event.target.value });
|
||||||
|
} else {
|
||||||
|
this.setState({ partitionKey: event.target.value });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{userContext.apiType === "SQL" &&
|
||||||
|
this.state.subPartitionKeys.map((subPartitionKey: string, index: number) => {
|
||||||
|
return (
|
||||||
|
<Stack style={{ marginBottom: 2, marginTop: -5 }} key={`uniqueKey${index}`} horizontal>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: "20px",
|
||||||
|
border: "solid",
|
||||||
|
borderWidth: "0px 0px 1px 1px",
|
||||||
|
marginRight: "5px",
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="addCollection-partitionKeyValue"
|
||||||
|
key={`addCollection-partitionKeyValue_${index}`}
|
||||||
|
aria-required
|
||||||
|
required
|
||||||
|
size={40}
|
||||||
|
tabIndex={index > 0 ? 1 : 0}
|
||||||
|
className="panelTextField"
|
||||||
|
autoComplete="off"
|
||||||
|
placeholder={getPartitionKeyPlaceHolder(index)}
|
||||||
|
aria-label={getPartitionKeyName()}
|
||||||
|
pattern={".*"}
|
||||||
|
title={""}
|
||||||
|
value={subPartitionKey}
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const subPartitionKeys = [...this.state.subPartitionKeys];
|
||||||
|
if (!this.state.subPartitionKeys[index] && !event.target.value.startsWith("/")) {
|
||||||
|
subPartitionKeys[index] = "/" + event.target.value.trim();
|
||||||
|
this.setState({ subPartitionKeys });
|
||||||
|
} else {
|
||||||
|
subPartitionKeys[index] = event.target.value.trim();
|
||||||
|
this.setState({ subPartitionKeys });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
iconProps={{ iconName: "Delete" }}
|
||||||
|
style={{ height: 27 }}
|
||||||
|
onClick={() => {
|
||||||
|
const subPartitionKeys = this.state.subPartitionKeys.filter((uniqueKey, j) => index !== j);
|
||||||
|
this.setState({ subPartitionKeys });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{userContext.apiType === "SQL" && (
|
||||||
|
<Stack className="panelGroupSpacing">
|
||||||
|
<DefaultButton
|
||||||
|
styles={{ root: { padding: 0, width: 200, height: 30 }, label: { fontSize: 12 } }}
|
||||||
|
hidden={this.state.useHashV1}
|
||||||
|
disabled={this.state.subPartitionKeys.length >= Constants.BackendDefaults.maxNumMultiHashPartition}
|
||||||
|
onClick={() => this.setState({ subPartitionKeys: [...this.state.subPartitionKeys, ""] })}
|
||||||
|
>
|
||||||
|
Add hierarchical partition key
|
||||||
|
</DefaultButton>
|
||||||
|
{this.state.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>
|
||||||
|
)}
|
||||||
|
<Separator className="panelSeparator" style={{ marginTop: 2, marginBottom: -4 }} />
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
|
||||||
|
<Stack>
|
||||||
|
<CollapsibleSectionComponent
|
||||||
|
title="Container Vector Policy"
|
||||||
|
isExpandedByDefault={false}
|
||||||
|
onExpand={() => {
|
||||||
|
scrollToSection("collapsibleVectorPolicySectionContent");
|
||||||
|
}}
|
||||||
|
tooltipContent={ContainerVectorPolicyTooltipContent()}
|
||||||
|
>
|
||||||
|
<Stack id="collapsibleVectorPolicySectionContent" styles={{ root: { position: "relative" } }}>
|
||||||
|
<Stack styles={{ root: { paddingLeft: 40 } }}>
|
||||||
|
<VectorEmbeddingPoliciesComponent
|
||||||
|
vectorEmbeddings={this.state.vectorEmbeddingPolicy}
|
||||||
|
vectorIndexes={this.state.vectorIndexingPolicy}
|
||||||
|
onVectorEmbeddingChange={(
|
||||||
|
vectorEmbeddingPolicy: DataModels.VectorEmbedding[],
|
||||||
|
vectorIndexingPolicy: DataModels.VectorIndex[],
|
||||||
|
vectorPolicyValidated: boolean,
|
||||||
|
) => {
|
||||||
|
this.setState({ vectorEmbeddingPolicy, vectorIndexingPolicy, vectorPolicyValidated });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</CollapsibleSectionComponent>
|
||||||
|
<Separator className="panelSeparator" style={{ marginTop: 2, marginBottom: -4 }} />
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack>
|
||||||
|
<CollapsibleSectionComponent
|
||||||
|
title="ETL Frequency"
|
||||||
|
isExpandedByDefault={false}
|
||||||
|
onExpand={() => {
|
||||||
|
scrollToSection("collapsibleVectorPolicySectionContent");
|
||||||
|
}}
|
||||||
|
tooltipContent={ContainerVectorPolicyTooltipContent()}
|
||||||
|
>
|
||||||
|
<ChoiceGroup defaultSelectedKey="continuous" options={options} onChange={() => { }} required={true} />
|
||||||
|
</CollapsibleSectionComponent>
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PanelFooterComponent buttonLabel="OK" isButtonDisabled={this.state.isThroughputCapExceeded} />
|
||||||
|
|
||||||
|
{this.state.isExecuting && (
|
||||||
|
<div>
|
||||||
|
<PanelLoadingScreen />
|
||||||
|
{this.state.teachingBubbleStep === 5 && (
|
||||||
|
<TeachingBubble
|
||||||
|
headline="Creating sample container"
|
||||||
|
target={"#loadingScreen"}
|
||||||
|
onDismiss={() => this.setState({ teachingBubbleStep: 0 })}
|
||||||
|
styles={{ footer: { width: "100%" } }}
|
||||||
|
>
|
||||||
|
A sample container is now being created and we are adding sample data for you. It should take about 1
|
||||||
|
minute.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
Once the sample container is created, review your sample dataset and follow next steps
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<ProgressIndicator
|
||||||
|
styles={{
|
||||||
|
itemName: { color: "white" },
|
||||||
|
progressTrack: { backgroundColor: "#A6A6A6" },
|
||||||
|
progressBar: { background: "white" },
|
||||||
|
}}
|
||||||
|
label="Adding sample data set"
|
||||||
|
/>
|
||||||
|
</TeachingBubble>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDatabaseOptions(): IDropdownOption[] {
|
||||||
|
return useDatabases.getState().databases?.map((database) => ({
|
||||||
|
key: database.id(),
|
||||||
|
text: database.id(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private onCreateNewDatabaseRadioBtnChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||||
|
if (event.target.checked && !this.state.createNewDatabase) {
|
||||||
|
this.setState({
|
||||||
|
createNewDatabase: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onUseExistingDatabaseRadioBtnChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||||
|
if (event.target.checked && this.state.createNewDatabase) {
|
||||||
|
this.setState({
|
||||||
|
createNewDatabase: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onUnshardedRadioBtnChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||||
|
if (event.target.checked && this.state.isSharded) {
|
||||||
|
this.setState({
|
||||||
|
isSharded: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onShardedRadioBtnChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||||
|
if (event.target.checked && !this.state.isSharded) {
|
||||||
|
this.setState({
|
||||||
|
isSharded: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onEnableAnalyticalStoreRadioBtnChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||||
|
if (event.target.checked && !this.state.enableAnalyticalStore) {
|
||||||
|
this.setState({
|
||||||
|
enableAnalyticalStore: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onDisableAnalyticalStoreRadioBtnChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||||
|
if (event.target.checked && this.state.enableAnalyticalStore) {
|
||||||
|
this.setState({
|
||||||
|
enableAnalyticalStore: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onTurnOnIndexing(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||||
|
if (event.target.checked && !this.state.enableIndexing) {
|
||||||
|
this.setState({
|
||||||
|
enableIndexing: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onTurnOffIndexing(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||||
|
if (event.target.checked && this.state.enableIndexing) {
|
||||||
|
this.setState({
|
||||||
|
enableIndexing: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setVectorEmbeddingPolicy(vectorEmbeddingPolicy: DataModels.VectorEmbedding[]): void {
|
||||||
|
this.setState({
|
||||||
|
vectorEmbeddingPolicy,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private setVectorIndexingPolicy(vectorIndexingPolicy: DataModels.VectorIndex[]): void {
|
||||||
|
this.setState({
|
||||||
|
vectorIndexingPolicy,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private isSelectedDatabaseSharedThroughput(): boolean {
|
||||||
|
if (!this.state.selectedDatabaseId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedDatabase = useDatabases
|
||||||
|
.getState()
|
||||||
|
.databases?.find((database) => database.id() === this.state.selectedDatabaseId);
|
||||||
|
return !!selectedDatabase?.offer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private getFreeTierIndexingText(): string {
|
||||||
|
return this.state.enableIndexing
|
||||||
|
? "All properties in your documents will be indexed by default for flexible and efficient queries."
|
||||||
|
: "Indexing will be turned off. Recommended if you don't need to run queries or only have key value operations.";
|
||||||
|
}
|
||||||
|
|
||||||
|
private getPartitionKeySubtext(): string {
|
||||||
|
if (
|
||||||
|
userContext.features.partitionKeyDefault &&
|
||||||
|
(userContext.apiType === "SQL" || userContext.apiType === "Mongo")
|
||||||
|
) {
|
||||||
|
const subtext = "For small workloads, the item ID is a suitable choice for the partition key.";
|
||||||
|
return subtext;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: uncomment when learn more text becomes available
|
||||||
|
// private getContainerFullTextPolicyTooltipContent(): JSX.Element {
|
||||||
|
// return (
|
||||||
|
// <Text variant="small">
|
||||||
|
// Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||||
|
// magna aliqua.{" "}
|
||||||
|
// <Link target="_blank" href="https://aka.ms/CosmosFullTextSearch">
|
||||||
|
// Learn more
|
||||||
|
// </Link>
|
||||||
|
// </Text>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
private shouldShowCollectionThroughputInput(): boolean {
|
||||||
|
if (isServerlessAccount()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.createNewDatabase) {
|
||||||
|
return !this.state.isSharedThroughputChecked;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.enableDedicatedThroughput) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.state.selectedDatabaseId && !this.isSelectedDatabaseSharedThroughput();
|
||||||
|
}
|
||||||
|
|
||||||
|
private shouldShowIndexingOptionsForFreeTierAccount(): boolean {
|
||||||
|
if (!isFreeTierAccount()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.state.createNewDatabase
|
||||||
|
? this.state.isSharedThroughputChecked
|
||||||
|
: this.isSelectedDatabaseSharedThroughput();
|
||||||
|
}
|
||||||
|
|
||||||
|
private shouldShowVectorSearchParameters() {
|
||||||
|
return isVectorSearchEnabled() && (isServerlessAccount() || this.shouldShowCollectionThroughputInput());
|
||||||
|
}
|
||||||
|
|
||||||
|
private shouldShowFullTextSearchParameters() {
|
||||||
|
return !isFabricNative() && this.showFullTextSearch;
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseUniqueKeys(): DataModels.UniqueKeyPolicy {
|
||||||
|
if (this.state.uniqueKeys?.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueKeyPolicy: DataModels.UniqueKeyPolicy = { uniqueKeys: [] };
|
||||||
|
this.state.uniqueKeys.forEach((uniqueKey) => {
|
||||||
|
if (uniqueKey) {
|
||||||
|
const validPaths: string[] = uniqueKey.split(",")?.filter((path) => path?.length > 0);
|
||||||
|
const trimmedPaths: string[] = validPaths?.map((path) => path.trim());
|
||||||
|
if (trimmedPaths?.length > 0) {
|
||||||
|
if (userContext.apiType === "Mongo") {
|
||||||
|
trimmedPaths.map((path) => {
|
||||||
|
const transformedPath = path.split(".").join("/");
|
||||||
|
if (transformedPath[0] !== "/") {
|
||||||
|
return "/" + transformedPath;
|
||||||
|
}
|
||||||
|
return transformedPath;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
uniqueKeyPolicy.uniqueKeys.push({ paths: trimmedPaths });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return uniqueKeyPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
private validateInputs(): boolean {
|
||||||
|
if (!this.state.createNewDatabase && !this.state.selectedDatabaseId) {
|
||||||
|
this.setState({ errorMessage: "Please select an existing database" });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const throughput = this.state.createNewDatabase ? this.newDatabaseThroughput : this.collectionThroughput;
|
||||||
|
if (throughput > CollectionCreation.DefaultCollectionRUs100K && !this.isCostAcknowledged) {
|
||||||
|
const errorMessage = this.isNewDatabaseAutoscale
|
||||||
|
? "Please acknowledge the estimated monthly spend."
|
||||||
|
: "Please acknowledge the estimated daily spend.";
|
||||||
|
this.setState({ errorMessage });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (throughput > CollectionCreation.MaxRUPerPartition && !this.state.isSharded) {
|
||||||
|
this.setState({ errorMessage: "Unsharded collections support up to 10,000 RUs" });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
userContext.apiType === "Gremlin" &&
|
||||||
|
(this.state.partitionKey === "/id" || this.state.partitionKey === "/label")
|
||||||
|
) {
|
||||||
|
this.setState({ errorMessage: "/id and /label as partition keys are not allowed for graph." });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.shouldShowVectorSearchParameters()) {
|
||||||
|
if (!this.state.vectorPolicyValidated) {
|
||||||
|
this.setState({ errorMessage: "Please fix errors in container vector policy" });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.state.fullTextPolicyValidated) {
|
||||||
|
this.setState({ errorMessage: "Please fix errors in container full text search polilcy" });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAnalyticalStorageTtl(): number {
|
||||||
|
if (!isSynapseLinkEnabled()) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldShowAnalyticalStoreOptions()) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.enableAnalyticalStore) {
|
||||||
|
// TODO: always default to 90 days once the backend hotfix is deployed
|
||||||
|
return userContext.features.ttl90Days
|
||||||
|
? Constants.AnalyticalStorageTtl.Days90
|
||||||
|
: Constants.AnalyticalStorageTtl.Infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Constants.AnalyticalStorageTtl.Disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSampleDBName(): string {
|
||||||
|
const existingSampleDBs = useDatabases
|
||||||
|
.getState()
|
||||||
|
.databases?.filter((database) => database.id().startsWith("SampleDB"));
|
||||||
|
const existingSampleDBNames = existingSampleDBs?.map((database) => database.id());
|
||||||
|
if (!existingSampleDBNames || existingSampleDBNames.length === 0) {
|
||||||
|
return "SampleDB";
|
||||||
|
}
|
||||||
|
|
||||||
|
let i = 1;
|
||||||
|
while (existingSampleDBNames.indexOf(`SampleDB${i}`) !== -1) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `SampleDB${i}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async submit(event?: React.FormEvent<HTMLFormElement>): Promise<void> {
|
||||||
|
event?.preventDefault();
|
||||||
|
|
||||||
|
if (!this.validateInputs()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const collectionId: string = this.state.collectionId.trim();
|
||||||
|
let databaseId = this.state.createNewDatabase ? this.state.newDatabaseId.trim() : this.state.selectedDatabaseId;
|
||||||
|
let partitionKeyString = this.state.isSharded ? this.state.partitionKey.trim() : undefined;
|
||||||
|
|
||||||
|
if (userContext.apiType === "Tables") {
|
||||||
|
// Table require fixed Database: TablesDB, and fixed Partition Key: /'$pk'
|
||||||
|
databaseId = CollectionCreation.TablesAPIDefaultDatabase;
|
||||||
|
partitionKeyString = "/'$pk'";
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueKeyPolicy: DataModels.UniqueKeyPolicy = parseUniqueKeys(this.state.uniqueKeys);
|
||||||
|
const partitionKeyVersion = this.state.useHashV1 ? undefined : 2;
|
||||||
|
const partitionKey: DataModels.PartitionKey = partitionKeyString
|
||||||
|
? {
|
||||||
|
paths: [
|
||||||
|
partitionKeyString,
|
||||||
|
...(userContext.apiType === "SQL" && this.state.subPartitionKeys.length > 0
|
||||||
|
? this.state.subPartitionKeys
|
||||||
|
: []),
|
||||||
|
],
|
||||||
|
kind: userContext.apiType === "SQL" && this.state.subPartitionKeys.length > 0 ? "MultiHash" : "Hash",
|
||||||
|
version: partitionKeyVersion,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const indexingPolicy: DataModels.IndexingPolicy = this.state.enableIndexing
|
||||||
|
? AllPropertiesIndexed
|
||||||
|
: SharedDatabaseDefault;
|
||||||
|
|
||||||
|
let vectorEmbeddingPolicy: DataModels.VectorEmbeddingPolicy;
|
||||||
|
|
||||||
|
if (this.shouldShowVectorSearchParameters()) {
|
||||||
|
indexingPolicy.vectorIndexes = this.state.vectorIndexingPolicy;
|
||||||
|
vectorEmbeddingPolicy = {
|
||||||
|
vectorEmbeddings: this.state.vectorEmbeddingPolicy,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.showFullTextSearch) {
|
||||||
|
indexingPolicy.fullTextIndexes = this.state.fullTextIndexes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const telemetryData = {
|
||||||
|
database: {
|
||||||
|
id: databaseId,
|
||||||
|
new: this.state.createNewDatabase,
|
||||||
|
shared: this.state.createNewDatabase
|
||||||
|
? this.state.isSharedThroughputChecked
|
||||||
|
: this.isSelectedDatabaseSharedThroughput(),
|
||||||
|
},
|
||||||
|
collection: {
|
||||||
|
id: this.state.collectionId,
|
||||||
|
throughput: this.collectionThroughput,
|
||||||
|
isAutoscale: this.isCollectionAutoscale,
|
||||||
|
partitionKey,
|
||||||
|
uniqueKeyPolicy,
|
||||||
|
collectionWithDedicatedThroughput: this.state.enableDedicatedThroughput,
|
||||||
|
},
|
||||||
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||||
|
useIndexingForSharedThroughput: this.state.enableIndexing,
|
||||||
|
isQuickstart: !!this.props.isQuickstart,
|
||||||
|
};
|
||||||
|
const startKey: number = TelemetryProcessor.traceStart(Action.CreateCollection, telemetryData);
|
||||||
|
|
||||||
|
const databaseLevelThroughput: boolean = this.state.createNewDatabase
|
||||||
|
? this.state.isSharedThroughputChecked
|
||||||
|
: this.isSelectedDatabaseSharedThroughput() && !this.state.enableDedicatedThroughput;
|
||||||
|
|
||||||
|
let offerThroughput: number;
|
||||||
|
let autoPilotMaxThroughput: number;
|
||||||
|
|
||||||
|
// Throughput
|
||||||
|
if (isFabricNative()) {
|
||||||
|
// Fabric Native accounts are always autoscale and have a fixed throughput of 5K
|
||||||
|
autoPilotMaxThroughput = AutoPilotUtils.autoPilotThroughput5K;
|
||||||
|
offerThroughput = undefined;
|
||||||
|
} else if (databaseLevelThroughput) {
|
||||||
|
if (this.state.createNewDatabase) {
|
||||||
|
if (this.isNewDatabaseAutoscale) {
|
||||||
|
autoPilotMaxThroughput = this.newDatabaseThroughput;
|
||||||
|
} else {
|
||||||
|
offerThroughput = this.newDatabaseThroughput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.isCollectionAutoscale) {
|
||||||
|
autoPilotMaxThroughput = this.collectionThroughput;
|
||||||
|
} else {
|
||||||
|
offerThroughput = this.collectionThroughput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createCollectionParams: DataModels.CreateCollectionParams = {
|
||||||
|
createNewDatabase: this.state.createNewDatabase,
|
||||||
|
collectionId,
|
||||||
|
databaseId,
|
||||||
|
databaseLevelThroughput,
|
||||||
|
offerThroughput,
|
||||||
|
autoPilotMaxThroughput,
|
||||||
|
analyticalStorageTtl: this.getAnalyticalStorageTtl(),
|
||||||
|
indexingPolicy,
|
||||||
|
partitionKey,
|
||||||
|
uniqueKeyPolicy,
|
||||||
|
createMongoWildcardIndex: this.state.createMongoWildCardIndex,
|
||||||
|
vectorEmbeddingPolicy,
|
||||||
|
fullTextPolicy: this.state.fullTextPolicy,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setState({ isExecuting: true });
|
||||||
|
|
||||||
|
try {
|
||||||
|
await createCollection(createCollectionParams);
|
||||||
|
await this.props.explorer.refreshAllDatabases();
|
||||||
|
if (this.props.isQuickstart) {
|
||||||
|
const database = useDatabases.getState().findDatabaseWithId(databaseId);
|
||||||
|
if (database) {
|
||||||
|
database.isSampleDB = true;
|
||||||
|
// populate sample container with sample data
|
||||||
|
await database.loadCollections();
|
||||||
|
const collection = database.findCollectionWithId(collectionId);
|
||||||
|
collection.isSampleCollection = true;
|
||||||
|
useTeachingBubble.getState().setSampleCollection(collection);
|
||||||
|
const sampleGenerator = await ContainerSampleGenerator.createSampleGeneratorAsync(this.props.explorer);
|
||||||
|
await sampleGenerator.populateContainerAsync(collection, partitionKeyString);
|
||||||
|
// auto-expand sample database + container and show teaching bubble
|
||||||
|
await database.expandDatabase();
|
||||||
|
collection.expandCollection();
|
||||||
|
useDatabases.getState().updateDatabase(database);
|
||||||
|
useTeachingBubble.getState().setIsSampleDBExpanded(true);
|
||||||
|
TelemetryProcessor.traceOpen(Action.LaunchUITour);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setState({ isExecuting: false });
|
||||||
|
TelemetryProcessor.traceSuccess(Action.CreateCollection, telemetryData, startKey);
|
||||||
|
useSidePanel.getState().closeSidePanel();
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage: string = getErrorMessage(error);
|
||||||
|
this.setState({ isExecuting: false, errorMessage, showErrorDetails: true });
|
||||||
|
const failureTelemetryData = { ...telemetryData, error: errorMessage, errorStack: getErrorStack(error) };
|
||||||
|
TelemetryProcessor.traceFailure(Action.CreateCollection, failureTelemetryData, startKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -199,6 +199,12 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
LocalStorageUtility.getEntryString(StorageKey.CopilotSampleDBEnabled) === "true",
|
LocalStorageUtility.getEntryString(StorageKey.CopilotSampleDBEnabled) === "true",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [mongoGuidRepresentation, setMongoGuidRepresentation] = useState<Constants.MongoGuidRepresentation>(
|
||||||
|
LocalStorageUtility.hasItem(StorageKey.MongoGuidRepresentation)
|
||||||
|
? (LocalStorageUtility.getEntryString(StorageKey.MongoGuidRepresentation) as Constants.MongoGuidRepresentation)
|
||||||
|
: Constants.MongoGuidRepresentation.CSharpLegacy,
|
||||||
|
);
|
||||||
|
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
|
||||||
const explorerVersion = configContext.gitSha;
|
const explorerVersion = configContext.gitSha;
|
||||||
@@ -261,6 +267,8 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
useDatabases.getState().sampleDataResourceTokenCollection &&
|
useDatabases.getState().sampleDataResourceTokenCollection &&
|
||||||
!isEmulator;
|
!isEmulator;
|
||||||
|
|
||||||
|
const shouldShowMongoGuidRepresentationOption = userContext.apiType === "Mongo";
|
||||||
|
|
||||||
const handlerOnSubmit = async () => {
|
const handlerOnSubmit = async () => {
|
||||||
setIsExecuting(true);
|
setIsExecuting(true);
|
||||||
|
|
||||||
@@ -412,6 +420,10 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldShowMongoGuidRepresentationOption) {
|
||||||
|
LocalStorageUtility.setEntryString(StorageKey.MongoGuidRepresentation, mongoGuidRepresentation);
|
||||||
|
}
|
||||||
|
|
||||||
setIsExecuting(false);
|
setIsExecuting(false);
|
||||||
logConsoleInfo(
|
logConsoleInfo(
|
||||||
`Updated items per page setting to ${LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage)}`,
|
`Updated items per page setting to ${LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage)}`,
|
||||||
@@ -433,6 +445,14 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldShowMongoGuidRepresentationOption) {
|
||||||
|
logConsoleInfo(
|
||||||
|
`Updated Mongo Guid Representation to ${LocalStorageUtility.getEntryString(
|
||||||
|
StorageKey.MongoGuidRepresentation,
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
refreshExplorer && (await explorer.refreshExplorer());
|
refreshExplorer && (await explorer.refreshExplorer());
|
||||||
closeSidePanel();
|
closeSidePanel();
|
||||||
};
|
};
|
||||||
@@ -477,6 +497,13 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
{ key: SplitterDirection.Horizontal, text: "Horizontal" },
|
{ key: SplitterDirection.Horizontal, text: "Horizontal" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const mongoGuidRepresentationDropdownOptions: IDropdownOption[] = [
|
||||||
|
{ key: Constants.MongoGuidRepresentation.CSharpLegacy, text: Constants.MongoGuidRepresentation.CSharpLegacy },
|
||||||
|
{ key: Constants.MongoGuidRepresentation.JavaLegacy, text: Constants.MongoGuidRepresentation.JavaLegacy },
|
||||||
|
{ key: Constants.MongoGuidRepresentation.PythonLegacy, text: Constants.MongoGuidRepresentation.PythonLegacy },
|
||||||
|
{ key: Constants.MongoGuidRepresentation.Standard, text: Constants.MongoGuidRepresentation.Standard },
|
||||||
|
];
|
||||||
|
|
||||||
const handleOnPriorityLevelOptionChange = (
|
const handleOnPriorityLevelOptionChange = (
|
||||||
ev: React.FormEvent<HTMLInputElement>,
|
ev: React.FormEvent<HTMLInputElement>,
|
||||||
option: IChoiceGroupOption,
|
option: IChoiceGroupOption,
|
||||||
@@ -559,6 +586,13 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
setRefreshExplorer(false);
|
setRefreshExplorer(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOnMongoGuidRepresentationOptionChange = (
|
||||||
|
ev: React.FormEvent<HTMLInputElement>,
|
||||||
|
option: IDropdownOption,
|
||||||
|
): void => {
|
||||||
|
setMongoGuidRepresentation(option.key as Constants.MongoGuidRepresentation);
|
||||||
|
};
|
||||||
|
|
||||||
const choiceButtonStyles = {
|
const choiceButtonStyles = {
|
||||||
root: {
|
root: {
|
||||||
clear: "both",
|
clear: "both",
|
||||||
@@ -1065,15 +1099,15 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
<div className={styles.settingsSectionContainer}>
|
<div className={styles.settingsSectionContainer}>
|
||||||
<div className={styles.settingsSectionDescription}>
|
<div className={styles.settingsSectionDescription}>
|
||||||
This is a sample database and collection with synthetic product data you can use to explore using
|
This is a sample database and collection with synthetic product data you can use to explore using
|
||||||
NoSQL queries and Query Advisor. This will appear as another database in the Data Explorer UI, and
|
NoSQL queries. This will appear as another database in the Data Explorer UI, and is created by,
|
||||||
is created by, and maintained by Microsoft at no cost to you.
|
and maintained by Microsoft at no cost to you.
|
||||||
</div>
|
</div>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
styles={{
|
styles={{
|
||||||
label: { padding: 0 },
|
label: { padding: 0 },
|
||||||
}}
|
}}
|
||||||
className="padding"
|
className="padding"
|
||||||
ariaLabel="Enable sample db for Query Advisor"
|
ariaLabel="Enable sample db for query exploration"
|
||||||
checked={copilotSampleDBEnabled}
|
checked={copilotSampleDBEnabled}
|
||||||
onChange={handleSampleDatabaseChange}
|
onChange={handleSampleDatabaseChange}
|
||||||
label="Enable sample database"
|
label="Enable sample database"
|
||||||
@@ -1082,6 +1116,27 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
</AccordionPanel>
|
</AccordionPanel>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
)}
|
)}
|
||||||
|
{shouldShowMongoGuidRepresentationOption && (
|
||||||
|
<AccordionItem value="14">
|
||||||
|
<AccordionHeader>
|
||||||
|
<div className={styles.header}>Guid Representation</div>
|
||||||
|
</AccordionHeader>
|
||||||
|
<AccordionPanel>
|
||||||
|
<div className={styles.settingsSectionContainer}>
|
||||||
|
<div className={styles.settingsSectionDescription}>
|
||||||
|
GuidRepresentation in MongoDB refers to how Globally Unique Identifiers (GUIDs) are serialized and
|
||||||
|
deserialized when stored in BSON documents. This will apply to all document operations.
|
||||||
|
</div>
|
||||||
|
<Dropdown
|
||||||
|
aria-labelledby="mongoGuidRepresentation"
|
||||||
|
selectedKey={mongoGuidRepresentation}
|
||||||
|
options={mongoGuidRepresentationDropdownOptions}
|
||||||
|
onChange={handleOnMongoGuidRepresentationOptionChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
|
)}
|
||||||
</Accordion>
|
</Accordion>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
import { Stack } from "@fluentui/react";
|
import { Stack } from "@fluentui/react";
|
||||||
import { QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants";
|
|
||||||
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
|
||||||
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
||||||
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import { SaveQueryPane } from "Explorer/Panes/SaveQueryPane/SaveQueryPane";
|
import { SaveQueryPane } from "Explorer/Panes/SaveQueryPane/SaveQueryPane";
|
||||||
import { QueryCopilotPromptbar } from "Explorer/QueryCopilot/QueryCopilotPromptbar";
|
|
||||||
import { readCopilotToggleStatus, saveCopilotToggleStatus } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
import { readCopilotToggleStatus, saveCopilotToggleStatus } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||||
import { OnExecuteQueryClick } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
import { OnExecuteQueryClick } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||||
import { QueryCopilotProps } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
import { QueryCopilotProps } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
||||||
@@ -13,7 +11,6 @@ import { QueryCopilotResults } from "Explorer/QueryCopilot/Shared/QueryCopilotRe
|
|||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import { useSidePanel } from "hooks/useSidePanel";
|
import { useSidePanel } from "hooks/useSidePanel";
|
||||||
import { ReactTabKind, TabsState, useTabs } from "hooks/useTabs";
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import SplitterLayout from "react-splitter-layout";
|
import SplitterLayout from "react-splitter-layout";
|
||||||
import QueryCommandIcon from "../../../images/CopilotCommand.svg";
|
import QueryCommandIcon from "../../../images/CopilotCommand.svg";
|
||||||
@@ -26,7 +23,8 @@ export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: Query
|
|||||||
const [copilotActive, setCopilotActive] = useState<boolean>(() =>
|
const [copilotActive, setCopilotActive] = useState<boolean>(() =>
|
||||||
readCopilotToggleStatus(userContext.databaseAccount),
|
readCopilotToggleStatus(userContext.databaseAccount),
|
||||||
);
|
);
|
||||||
const [tabActive, setTabActive] = useState<boolean>(true);
|
//TODO: Uncomment this useState when query copilot is reinstated in DE
|
||||||
|
// const [tabActive, setTabActive] = useState<boolean>(true);
|
||||||
|
|
||||||
const getCommandbarButtons = (): CommandButtonComponentProps[] => {
|
const getCommandbarButtons = (): CommandButtonComponentProps[] => {
|
||||||
const executeQueryBtnLabel = selectedQuery ? "Execute Selection" : "Execute Query";
|
const executeQueryBtnLabel = selectedQuery ? "Execute Selection" : "Execute Query";
|
||||||
@@ -70,17 +68,18 @@ export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: Query
|
|||||||
useCommandBar.getState().setContextButtons(getCommandbarButtons());
|
useCommandBar.getState().setContextButtons(getCommandbarButtons());
|
||||||
}, [query, selectedQuery, copilotActive]);
|
}, [query, selectedQuery, copilotActive]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
//TODO: Uncomment this effect when query copilot is reinstated in DE
|
||||||
return () => {
|
// React.useEffect(() => {
|
||||||
useTabs.subscribe((state: TabsState) => {
|
// return () => {
|
||||||
if (state.activeReactTab === ReactTabKind.QueryCopilot) {
|
// useTabs.subscribe((state: TabsState) => {
|
||||||
setTabActive(true);
|
// if (state.activeReactTab === ReactTabKind.QueryCopilot) {
|
||||||
} else {
|
// setTabActive(true);
|
||||||
setTabActive(false);
|
// } else {
|
||||||
}
|
// setTabActive(false);
|
||||||
});
|
// }
|
||||||
};
|
// });
|
||||||
}, []);
|
// };
|
||||||
|
// }, []);
|
||||||
|
|
||||||
const toggleCopilot = (toggle: boolean) => {
|
const toggleCopilot = (toggle: boolean) => {
|
||||||
setCopilotActive(toggle);
|
setCopilotActive(toggle);
|
||||||
@@ -90,6 +89,7 @@ export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: Query
|
|||||||
return (
|
return (
|
||||||
<Stack className="tab-pane" style={{ width: "100%" }}>
|
<Stack className="tab-pane" style={{ width: "100%" }}>
|
||||||
<div style={isGeneratingQuery ? { height: "100%" } : { overflowY: "auto", height: "100%" }}>
|
<div style={isGeneratingQuery ? { height: "100%" } : { overflowY: "auto", height: "100%" }}>
|
||||||
|
{/*TODO: Uncomment this section when query copilot is reinstated in DE
|
||||||
{tabActive && copilotActive && (
|
{tabActive && copilotActive && (
|
||||||
<QueryCopilotPromptbar
|
<QueryCopilotPromptbar
|
||||||
explorer={explorer}
|
explorer={explorer}
|
||||||
@@ -97,7 +97,7 @@ export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: Query
|
|||||||
databaseId={QueryCopilotSampleDatabaseId}
|
databaseId={QueryCopilotSampleDatabaseId}
|
||||||
containerId={QueryCopilotSampleContainerId}
|
containerId={QueryCopilotSampleContainerId}
|
||||||
></QueryCopilotPromptbar>
|
></QueryCopilotPromptbar>
|
||||||
)}
|
)} */}
|
||||||
<Stack className="tabPaneContentContainer">
|
<Stack className="tabPaneContentContainer">
|
||||||
<SplitterLayout percentage={true} vertical={true} primaryIndex={0} primaryMinSize={30} secondaryMinSize={70}>
|
<SplitterLayout percentage={true} vertical={true} primaryIndex={0} primaryMinSize={30} secondaryMinSize={70}>
|
||||||
<EditorReact
|
<EditorReact
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ import {
|
|||||||
import { Add16Regular, ArrowSync12Regular, ChevronLeft12Regular, ChevronRight12Regular } from "@fluentui/react-icons";
|
import { Add16Regular, ArrowSync12Regular, ChevronLeft12Regular, ChevronRight12Regular } from "@fluentui/react-icons";
|
||||||
import { GlobalSecondaryIndexLabels } from "Common/Constants";
|
import { GlobalSecondaryIndexLabels } from "Common/Constants";
|
||||||
import { isGlobalSecondaryIndexEnabled } from "Common/DatabaseAccountUtility";
|
import { isGlobalSecondaryIndexEnabled } from "Common/DatabaseAccountUtility";
|
||||||
|
import { sendMessage } from "Common/MessageHandler";
|
||||||
import { configContext, Platform } from "ConfigContext";
|
import { configContext, Platform } from "ConfigContext";
|
||||||
|
import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
|
||||||
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 {
|
import {
|
||||||
@@ -171,6 +173,19 @@ const GlobalCommands: React.FC<GlobalCommandsProps> = ({ explorer }) => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (isFabricNative()) {
|
||||||
|
actions.push({
|
||||||
|
id: "new_fabric_shortcut",
|
||||||
|
label: "New Shortcut",
|
||||||
|
icon: <Add16Regular />,
|
||||||
|
onClick: () => {
|
||||||
|
// explorer.onNewFabricShortcutClicked({ shortcutId: "blah" });
|
||||||
|
// TODO: add telemetry
|
||||||
|
sendMessage({ type: FabricMessageTypes.NewShortcut, });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (configContext.platform !== Platform.Fabric && userContext.apiType !== "Tables") {
|
if (configContext.platform !== Platform.Fabric && userContext.apiType !== "Tables") {
|
||||||
actions.push({
|
actions.push({
|
||||||
id: "new_database",
|
id: "new_database",
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* Accordion top class
|
* Accordion top class
|
||||||
*/
|
*/
|
||||||
import { makeStyles, tokens } from "@fluentui/react-components";
|
import { Link, makeStyles, tokens } from "@fluentui/react-components";
|
||||||
import { DocumentAddRegular, LinkMultipleRegular } from "@fluentui/react-icons";
|
import { DocumentAddRegular, LinkMultipleRegular, OpenRegular } from "@fluentui/react-icons";
|
||||||
import { SampleDataImportDialog } from "Explorer/SplashScreen/SampleDataImportDialog";
|
import { SampleDataImportDialog } from "Explorer/SplashScreen/SampleDataImportDialog";
|
||||||
import { CosmosFluentProvider } from "Explorer/Theme/ThemeUtil";
|
import { CosmosFluentProvider } from "Explorer/Theme/ThemeUtil";
|
||||||
import { isFabricNative, isFabricNativeReadOnly } from "Platform/Fabric/FabricUtil";
|
import { isFabricNative, isFabricNativeReadOnly } from "Platform/Fabric/FabricUtil";
|
||||||
@@ -119,7 +119,7 @@ const FabricHomeScreenButton: React.FC<FabricHomeScreenButtonProps & { className
|
|||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
return (
|
return (
|
||||||
<div role="button" className={`${styles.buttonContainer} ${className}`} onClick={onClick}>
|
<div role="button" className={`${styles.buttonContainer} ${className}`} onClick={onClick} tabIndex={0}>
|
||||||
<div className={styles.buttonUpperPart}>{icon}</div>
|
<div className={styles.buttonUpperPart}>{icon}</div>
|
||||||
<div aria-label={title} className={styles.buttonLowerPart}>
|
<div aria-label={title} className={styles.buttonLowerPart}>
|
||||||
<div>{title}</div>
|
<div>{title}</div>
|
||||||
@@ -147,7 +147,7 @@ export const FabricHomeScreen: React.FC<SplashScreenProps> = (props: SplashScree
|
|||||||
{
|
{
|
||||||
title: "Sample data",
|
title: "Sample data",
|
||||||
description: "Automatically load sample data in your database",
|
description: "Automatically load sample data in your database",
|
||||||
icon: <img src={CosmosDbBlackIcon} />,
|
icon: <img src={CosmosDbBlackIcon} alt={"Azure Cosmos DB icon"} aria-hidden="true" />,
|
||||||
onClick: () => setOpenSampleDataImportDialog(true),
|
onClick: () => setOpenSampleDataImportDialog(true),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -181,16 +181,18 @@ export const FabricHomeScreen: React.FC<SplashScreenProps> = (props: SplashScree
|
|||||||
explorer={props.explorer}
|
explorer={props.explorer}
|
||||||
databaseName={userContext.fabricContext?.databaseName}
|
databaseName={userContext.fabricContext?.databaseName}
|
||||||
/>
|
/>
|
||||||
<div className={styles.title} role="heading" aria-label={title}>
|
<div className={styles.title} role="heading" aria-label={title} aria-level={1}>
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
{getSplashScreenButtons()}
|
{getSplashScreenButtons()}
|
||||||
{/* <div className={styles.footer}>
|
{
|
||||||
Need help?{" "}
|
<div className={styles.footer}>
|
||||||
<Link href="https://aka.ms/cosmosdbfabricdocs" target="_blank">
|
Need help?{" "}
|
||||||
Learn more <img src={LinkIcon} alt="Learn more" />
|
<Link href="https://learn.microsoft.com/fabric/database/cosmos-db/overview" target="_blank">
|
||||||
</Link>
|
Learn more <OpenRegular />
|
||||||
</div> */}
|
</Link>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</CosmosFluentProvider>
|
</CosmosFluentProvider>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { ReactTabKind, useTabs } from "hooks/useTabs";
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import ConnectIcon from "../../../images/Connect_color.svg";
|
import ConnectIcon from "../../../images/Connect_color.svg";
|
||||||
import ContainersIcon from "../../../images/Containers.svg";
|
import ContainersIcon from "../../../images/Containers.svg";
|
||||||
|
import CosmosDBIcon from "../../../images/CosmosDB-logo.svg";
|
||||||
import LinkIcon from "../../../images/Link_blue.svg";
|
import LinkIcon from "../../../images/Link_blue.svg";
|
||||||
import PowerShellIcon from "../../../images/PowerShell.svg";
|
import PowerShellIcon from "../../../images/PowerShell.svg";
|
||||||
import CopilotIcon from "../../../images/QueryCopilotNewLogo.svg";
|
import CopilotIcon from "../../../images/QueryCopilotNewLogo.svg";
|
||||||
@@ -120,11 +121,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private getSplashScreenButtons = (): JSX.Element => {
|
private getSplashScreenButtons = (): JSX.Element => {
|
||||||
if (
|
if (userContext.apiType === "SQL") {
|
||||||
userContext.apiType === "SQL" &&
|
|
||||||
useQueryCopilot.getState().copilotEnabled &&
|
|
||||||
useDatabases.getState().sampleDataResourceTokenCollection
|
|
||||||
) {
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
className="splashStackContainer"
|
className="splashStackContainer"
|
||||||
@@ -152,25 +149,18 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack className="splashStackRow" horizontal>
|
<Stack className="splashStackRow" horizontal>
|
||||||
{useQueryCopilot.getState().copilotEnabled && (
|
<SplashScreenButton
|
||||||
<SplashScreenButton
|
imgSrc={CosmosDBIcon}
|
||||||
imgSrc={CopilotIcon}
|
imgSize={35}
|
||||||
title={"Query faster with Query Advisor"}
|
title={"Azure Cosmos DB Samples Gallery"}
|
||||||
description={
|
description={
|
||||||
"Query Advisor is your AI buddy that helps you write Azure Cosmos DB queries like a pro. Try it using our sample data set now!"
|
"Discover samples that showcase scalable, intelligent app patterns. Try one now to see how fast you can go from concept to code with Cosmos DB"
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const copilotVersion = userContext.features.copilotVersion;
|
window.open("https://azurecosmosdb.github.io/gallery/?tags=example", "_blank");
|
||||||
if (copilotVersion === "v1.0") {
|
traceOpen(Action.LearningResourcesClicked, { apiType: userContext.apiType });
|
||||||
useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot);
|
}}
|
||||||
} else if (copilotVersion === "v2.0") {
|
/>
|
||||||
const sampleCollection = useDatabases.getState().sampleDataResourceTokenCollection;
|
|
||||||
sampleCollection.onNewQueryClick(sampleCollection, undefined);
|
|
||||||
}
|
|
||||||
traceOpen(Action.OpenQueryCopilotFromSplashScreen, { apiType: userContext.apiType });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<SplashScreenButton
|
<SplashScreenButton
|
||||||
imgSrc={ConnectIcon}
|
imgSrc={ConnectIcon}
|
||||||
title={"Connect"}
|
title={"Connect"}
|
||||||
@@ -212,6 +202,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
sample data, query.
|
sample data, query.
|
||||||
</TeachingBubble>
|
</TeachingBubble>
|
||||||
)}
|
)}
|
||||||
|
{/*TODO: convert below to use SplashScreenButton */}
|
||||||
{mainItems.map((item) => (
|
{mainItems.map((item) => (
|
||||||
<Stack
|
<Stack
|
||||||
id={`mainButton-${item.id}`}
|
id={`mainButton-${item.id}`}
|
||||||
@@ -477,6 +468,34 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: Re-enable lint rule when query copilot is reinstated in DE
|
||||||
|
/* eslint-disable-next-line no-unused-vars */
|
||||||
|
private getQueryCopilotCard = (): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{useQueryCopilot.getState().copilotEnabled && (
|
||||||
|
<SplashScreenButton
|
||||||
|
imgSrc={CopilotIcon}
|
||||||
|
title={"Query faster with Query Advisor"}
|
||||||
|
description={
|
||||||
|
"Query Advisor is your AI buddy that helps you write Azure Cosmos DB queries like a pro. Try it using our sample data set now!"
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
const copilotVersion = userContext.features.copilotVersion;
|
||||||
|
if (copilotVersion === "v1.0") {
|
||||||
|
useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot);
|
||||||
|
} else if (copilotVersion === "v2.0") {
|
||||||
|
const sampleCollection = useDatabases.getState().sampleDataResourceTokenCollection;
|
||||||
|
sampleCollection.onNewQueryClick(sampleCollection, undefined);
|
||||||
|
}
|
||||||
|
traceOpen(Action.OpenQueryCopilotFromSplashScreen, { apiType: userContext.apiType });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
private decorateOpenCollectionActivity({ databaseId, collectionId }: MostRecentActivity.OpenCollectionItem) {
|
private decorateOpenCollectionActivity({ databaseId, collectionId }: MostRecentActivity.OpenCollectionItem) {
|
||||||
return {
|
return {
|
||||||
iconSrc: CollectionIcon,
|
iconSrc: CollectionIcon,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ interface SplashScreenButtonProps {
|
|||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
|
imgSize?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SplashScreenButton: React.FC<SplashScreenButtonProps> = ({
|
export const SplashScreenButton: React.FC<SplashScreenButtonProps> = ({
|
||||||
@@ -14,6 +15,7 @@ export const SplashScreenButton: React.FC<SplashScreenButtonProps> = ({
|
|||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
onClick,
|
onClick,
|
||||||
|
imgSize,
|
||||||
}: SplashScreenButtonProps): JSX.Element => {
|
}: SplashScreenButtonProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
@@ -39,7 +41,7 @@ export const SplashScreenButton: React.FC<SplashScreenButtonProps> = ({
|
|||||||
role="button"
|
role="button"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<img src={imgSrc} alt={title} aria-hidden="true" />
|
<img src={imgSrc} alt={title} aria-hidden="true" {...(imgSize ? { height: imgSize, width: imgSize } : {})} />
|
||||||
</div>
|
</div>
|
||||||
<Stack style={{ marginLeft: 16 }}>
|
<Stack style={{ marginLeft: 16 }}>
|
||||||
<Text style={{ fontSize: 18, fontWeight: 600 }}>{title}</Text>
|
<Text style={{ fontSize: 18, fontWeight: 600 }}>{title}</Text>
|
||||||
|
|||||||
@@ -106,6 +106,6 @@ describe("QueryTabComponent", () => {
|
|||||||
<QueryTabCopilotComponent {...propsMock} />
|
<QueryTabCopilotComponent {...propsMock} />
|
||||||
</CopilotProvider>,
|
</CopilotProvider>,
|
||||||
);
|
);
|
||||||
expect(container.find(QueryCopilotPromptbar).exists()).toBe(true);
|
expect(container.find(QueryCopilotPromptbar).exists()).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { useDialog } from "Explorer/Controls/Dialog";
|
|||||||
import { monaco } from "Explorer/LazyMonaco";
|
import { monaco } from "Explorer/LazyMonaco";
|
||||||
import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal";
|
import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal";
|
||||||
import { useCopilotStore } from "Explorer/QueryCopilot/QueryCopilotContext";
|
import { useCopilotStore } from "Explorer/QueryCopilot/QueryCopilotContext";
|
||||||
import { QueryCopilotPromptbar } from "Explorer/QueryCopilot/QueryCopilotPromptbar";
|
|
||||||
import { readCopilotToggleStatus, saveCopilotToggleStatus } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
import { readCopilotToggleStatus, saveCopilotToggleStatus } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||||
import { OnExecuteQueryClick, QueryDocumentsPerPage } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
import { OnExecuteQueryClick, QueryDocumentsPerPage } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||||
import { QueryCopilotSidebar } from "Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar";
|
import { QueryCopilotSidebar } from "Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar";
|
||||||
@@ -28,8 +27,9 @@ import { TabsState, useTabs } from "hooks/useTabs";
|
|||||||
import React, { Fragment, createRef } from "react";
|
import React, { Fragment, createRef } from "react";
|
||||||
import "react-splitter-layout/lib/index.css";
|
import "react-splitter-layout/lib/index.css";
|
||||||
import { format } from "react-string-format";
|
import { format } from "react-string-format";
|
||||||
import QueryCommandIcon from "../../../../images/CopilotCommand.svg";
|
//TODO: Uncomment next two lines when query copilot is reinstated in DE
|
||||||
import LaunchCopilot from "../../../../images/CopilotTabIcon.svg";
|
// import QueryCommandIcon from "../../../../images/CopilotCommand.svg";
|
||||||
|
// import LaunchCopilot from "../../../../images/CopilotTabIcon.svg";
|
||||||
import DownloadQueryIcon from "../../../../images/DownloadQuery.svg";
|
import DownloadQueryIcon from "../../../../images/DownloadQuery.svg";
|
||||||
import CancelQueryIcon from "../../../../images/Entity_cancel.svg";
|
import CancelQueryIcon from "../../../../images/Entity_cancel.svg";
|
||||||
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
|
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
|
||||||
@@ -494,53 +494,55 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.launchCopilotButton.visible && this.isCopilotTabActive) {
|
//TODO: Uncomment next section when query copilot is reinstated in DE
|
||||||
const mainButtonLabel = "Launch Copilot";
|
// if (this.launchCopilotButton.visible && this.isCopilotTabActive) {
|
||||||
const chatPaneLabel = "Open Copilot in chat pane (ALT+C)";
|
// const mainButtonLabel = "Launch Copilot";
|
||||||
const copilotSettingLabel = "Copilot settings";
|
// const chatPaneLabel = "Open Copilot in chat pane (ALT+C)";
|
||||||
|
// const copilotSettingLabel = "Copilot settings";
|
||||||
|
|
||||||
const openCopilotChatButton: CommandButtonComponentProps = {
|
// const openCopilotChatButton: CommandButtonComponentProps = {
|
||||||
iconAlt: chatPaneLabel,
|
// iconAlt: chatPaneLabel,
|
||||||
onCommandClick: this.launchQueryCopilotChat,
|
// onCommandClick: this.launchQueryCopilotChat,
|
||||||
commandButtonLabel: chatPaneLabel,
|
// commandButtonLabel: chatPaneLabel,
|
||||||
ariaLabel: chatPaneLabel,
|
// ariaLabel: chatPaneLabel,
|
||||||
hasPopup: false,
|
// hasPopup: false,
|
||||||
};
|
// };
|
||||||
|
|
||||||
const copilotSettingsButton: CommandButtonComponentProps = {
|
// const copilotSettingsButton: CommandButtonComponentProps = {
|
||||||
iconAlt: copilotSettingLabel,
|
// iconAlt: copilotSettingLabel,
|
||||||
onCommandClick: () => undefined,
|
// onCommandClick: () => undefined,
|
||||||
commandButtonLabel: copilotSettingLabel,
|
// commandButtonLabel: copilotSettingLabel,
|
||||||
ariaLabel: copilotSettingLabel,
|
// ariaLabel: copilotSettingLabel,
|
||||||
hasPopup: false,
|
// hasPopup: false,
|
||||||
};
|
// };
|
||||||
|
|
||||||
const launchCopilotButton: CommandButtonComponentProps = {
|
// const launchCopilotButton: CommandButtonComponentProps = {
|
||||||
iconSrc: LaunchCopilot,
|
// iconSrc: LaunchCopilot,
|
||||||
iconAlt: mainButtonLabel,
|
// iconAlt: mainButtonLabel,
|
||||||
onCommandClick: this.launchQueryCopilotChat,
|
// onCommandClick: this.launchQueryCopilotChat,
|
||||||
commandButtonLabel: mainButtonLabel,
|
// commandButtonLabel: mainButtonLabel,
|
||||||
ariaLabel: mainButtonLabel,
|
// ariaLabel: mainButtonLabel,
|
||||||
hasPopup: false,
|
// hasPopup: false,
|
||||||
children: [openCopilotChatButton, copilotSettingsButton],
|
// children: [openCopilotChatButton, copilotSettingsButton],
|
||||||
};
|
// };
|
||||||
buttons.push(launchCopilotButton);
|
// buttons.push(launchCopilotButton);
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (this.props.copilotEnabled) {
|
//TODO: Uncomment next section when query copilot is reinstated in DE
|
||||||
const toggleCopilotButton: CommandButtonComponentProps = {
|
// if (this.props.copilotEnabled) {
|
||||||
iconSrc: QueryCommandIcon,
|
// const toggleCopilotButton: CommandButtonComponentProps = {
|
||||||
iconAlt: "Query Advisor",
|
// iconSrc: QueryCommandIcon,
|
||||||
keyboardAction: KeyboardAction.TOGGLE_COPILOT,
|
// iconAlt: "Query Advisor",
|
||||||
onCommandClick: () => {
|
// keyboardAction: KeyboardAction.TOGGLE_COPILOT,
|
||||||
this._toggleCopilot(!this.state.copilotActive);
|
// onCommandClick: () => {
|
||||||
},
|
// this._toggleCopilot(!this.state.copilotActive);
|
||||||
commandButtonLabel: this.state.copilotActive ? "Disable Query Advisor" : "Enable Query Advisor",
|
// },
|
||||||
ariaLabel: this.state.copilotActive ? "Disable Query Advisor" : "Enable Query Advisor",
|
// commandButtonLabel: this.state.copilotActive ? "Disable Query Advisor" : "Enable Query Advisor",
|
||||||
hasPopup: false,
|
// ariaLabel: this.state.copilotActive ? "Disable Query Advisor" : "Enable Query Advisor",
|
||||||
};
|
// hasPopup: false,
|
||||||
buttons.push(toggleCopilotButton);
|
// };
|
||||||
}
|
// buttons.push(toggleCopilotButton);
|
||||||
|
// }
|
||||||
|
|
||||||
if (!this.props.isPreferredApiMongoDB && this.state.isExecuting) {
|
if (!this.props.isPreferredApiMongoDB && this.state.isExecuting) {
|
||||||
const label = "Cancel query";
|
const label = "Cancel query";
|
||||||
@@ -725,6 +727,7 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<CosmosFluentProvider id={this.props.tabId} className={this.props.styles.queryTab} role="tabpanel">
|
<CosmosFluentProvider id={this.props.tabId} className={this.props.styles.queryTab} role="tabpanel">
|
||||||
|
{/*TODO: Uncomment this section when query copilot is reinstated in DE
|
||||||
{this.props.copilotEnabled && this.state.currentTabActive && this.state.copilotActive && (
|
{this.props.copilotEnabled && this.state.currentTabActive && this.state.copilotActive && (
|
||||||
<QueryCopilotPromptbar
|
<QueryCopilotPromptbar
|
||||||
explorer={this.props.collection.container}
|
explorer={this.props.collection.container}
|
||||||
@@ -732,7 +735,7 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
databaseId={this.props.collection.databaseId}
|
databaseId={this.props.collection.databaseId}
|
||||||
containerId={this.props.collection.id()}
|
containerId={this.props.collection.id()}
|
||||||
></QueryCopilotPromptbar>
|
></QueryCopilotPromptbar>
|
||||||
)}
|
)} */}
|
||||||
{/* Set 'key' to the value of vertical to force re-rendering when vertical changes, to work around https://github.com/johnwalley/allotment/issues/457 */}
|
{/* Set 'key' to the value of vertical to force re-rendering when vertical changes, to work around https://github.com/johnwalley/allotment/issues/457 */}
|
||||||
<Allotment
|
<Allotment
|
||||||
key={vertical.toString()}
|
key={vertical.toString()}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { MongoGuidRepresentation } from "Common/Constants";
|
||||||
import { SplitterDirection } from "Common/Splitter";
|
import { SplitterDirection } from "Common/Splitter";
|
||||||
import * as LocalStorageUtility from "./LocalStorageUtility";
|
import * as LocalStorageUtility from "./LocalStorageUtility";
|
||||||
import * as SessionStorageUtility from "./SessionStorageUtility";
|
import * as SessionStorageUtility from "./SessionStorageUtility";
|
||||||
@@ -33,6 +34,7 @@ export enum StorageKey {
|
|||||||
DocumentsTabPrefs,
|
DocumentsTabPrefs,
|
||||||
DefaultQueryResultsView,
|
DefaultQueryResultsView,
|
||||||
AppState,
|
AppState,
|
||||||
|
MongoGuidRepresentation,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const hasRUThresholdBeenConfigured = (): boolean => {
|
export const hasRUThresholdBeenConfigured = (): boolean => {
|
||||||
@@ -65,4 +67,13 @@ export const getDefaultQueryResultsView = (): SplitterDirection => {
|
|||||||
return SplitterDirection.Horizontal;
|
return SplitterDirection.Horizontal;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getMongoGuidRepresentation = (): MongoGuidRepresentation => {
|
||||||
|
const mongoGuidRepresentation: string | null = LocalStorageUtility.getEntryString(StorageKey.MongoGuidRepresentation);
|
||||||
|
if (mongoGuidRepresentation) {
|
||||||
|
return mongoGuidRepresentation as MongoGuidRepresentation;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MongoGuidRepresentation.CSharpLegacy;
|
||||||
|
};
|
||||||
|
|
||||||
export const DefaultRUThreshold = 5000;
|
export const DefaultRUThreshold = 5000;
|
||||||
|
|||||||
@@ -220,6 +220,10 @@ async function configureFabric(): Promise<Explorer> {
|
|||||||
explorer.onRefreshResourcesClick();
|
explorer.onRefreshResourcesClick();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "shortcutCreated": {
|
||||||
|
explorer.onNewFabricShortcutClicked({ shortcutId: data.message.shortcutId });
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
console.error(`Unknown Fabric message type: ${JSON.stringify(data)}`);
|
console.error(`Unknown Fabric message type: ${JSON.stringify(data)}`);
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user