Compare commits

...

7 Commits

Author SHA1 Message Date
asier-isayas
54373cc48d Enable Full Text Search on all NoSQL accounts (#2157)
* Enable Full Text Search on all NoSQL accounts

* format

* fix tests

* run tests

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2025-05-20 10:52:03 -04:00
JustinKol
d2294593d9 Missing vs code button and image (#2155)
* Reverting back to popup by default as too many factors are present using a timeout

* Added telemetry constant for VSCode

* VSCode image and button
2025-05-15 17:55:55 -04:00
Nishtha Ahuja
3bcf3a4af5 Connect with VScode on Quickstart Page (#2153)
Co-authored-by: nishthaAhujaa <nishtha17354@iiittd.ac.in>
2025-05-15 19:45:55 +05:30
asier-isayas
03890b8a4c Default New Global Secondary Index Panel to be sharded (#2138) (#2152)
Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2025-05-15 10:03:16 -04:00
sunghyunkang1111
6be46bff28 Throughput bucketing build release (#2149)
* Added AFEC check in userContext (#2140)

* Added AFEC check in userContext

* Update unit tests and fix Group to Bucket

* Hide throughput bucket settings for nonshared, nondedicated throughput container (#2146)
2025-05-14 12:26:30 -05:00
JustinKol
831c17726f Adding Opening VS Code via Data Explorer (#2150)
* Reverting back to popup by default as too many factors are present using a timeout

* Added telemetry constant for VSCode

* Reverting back to popup by default as too many factors are present using a timeout
2025-05-14 10:40:32 -04:00
Nishtha Ahuja
3cc26df6a1 bug fix for manual pricing (#2144)
Co-authored-by: nishthaAhujaa <nishtha17354@iiittd.ac.in>
2025-05-13 20:52:11 +05:30
20 changed files with 235 additions and 55 deletions

8
images/VisualStudio.svg Normal file

File diff suppressed because one or more lines are too long

1
images/vscode.svg Normal file
View File

@@ -0,0 +1 @@
<svg width="15" height="15" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" overflow="hidden"><defs><clipPath id="clip0"><rect x="479" y="279" width="15" height="15"/></clipPath><clipPath id="clip1"><rect x="-0.287396" y="-0.171573" width="152381" height="152381"/></clipPath><image width="35" height="35" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAMAAAApB0NrAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAH4UExURQAAAASExCGs7CC//wB9vQB5uxSU1yaz8ySy9AB8uwB8vAB5uxOR1Say8yax8iaw8gB6vAB6uwB7uwB4uROQ1Cax8ySy8QCAvwB3ugB5ugB2uROP1CWw8yav8wCT2QCQ1QCP1wB2uQB1uBOO0yWv8yat8wCP1QCP1QCP1QCO1QCQ1wB1tQB2uAB3uAB1tx+k6SSt8ySt8QCN0wCO1ACM0wBzuQBztAB1twB0tgB0tiSr8SSs8gCM1ACM1ACEywBwtQBxtAB0tgBztQB0sySp8SSr8gCL0wCK0gCL0wCHzwByuABusgBztQBttiOq8gB6wQCI0QCJ0gB7wySn8SOo8gBxtABtsABwtgCFzwCI0gCG0QCJ0SKn8SKn8iKl8QBvsABvsgBvsQBtsABssgCCzACG0QCF0ACDzyKm8QBtrwBusABsrgCAzQCE0ACF0ACF0ACE0CKj8SKl8QBssQBtrwBtrwBsrgBsrwCK1QCBzwCD0ACA0B2d7CGj8QBsrABrrQBwrwCA3wCC0ACCzwCBzwB+zhGM3iGi8SKh8QCAzQCAzgCAzwB9zRCK3iCh8SGh8SCf8QB+zAB/zgB8zRCJ3SCg8SCf7wB+zQB6zBCI3CCe8CCf8CCe8CCf8SCf/wB7zAB4yxGJ3h6c8CCd7yCf7wmA0RqX60C//5CaUeMAAACodFJOUwA8XAho+//ncID/////53iM//////9wCKv/////gCiYILf///+AMPP/70wYw/+3lP+AXP+MKNv/+3uA/3z/+////+NAgP9c9/////+7HP+A///MgP9Y+////7scgP+AeP/////7/+NA/2D/iyTb//t4gP808//vUBjD/7eU/yifIAi3//////+Ar////////4CI/////3B//////+9/CGj7/+dwEDhYBCm1XqwAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAFpSURBVDhPY2AYBaiAkYkZXQgdsLCysXPgV8XJxc3Dy8vHj0eVgKCQsIioqKioGLoMDIhLSEpKSknLyMjIyKLLyckrgJUoCgsLCyspq6ioqKiiKVFT19DUYmDQ1tEFAT19AwMDA0M0NUbGxsbGJqZm5ubm5haWDFbW1tbWVmhqGGxsbW1t7ewdHB2dnBkYXFxdXV1d0NUwuLl7eHh4enn7+DIwMLj4+fn5Yaph8A8IDAwMDBIHsYNDQkJCgtFVMISGhUdERkZGRkUzMDDExMbGxsahK4lPSExKTklNTU1NS2dgiMvIyMhAV5OZlZWVlZ2Tm5eXl5dfwFBYVFRUVIimpriktKycgaGisgoEqmtqa2tr0dUw1NU3gKjGpuaWlpbWtvb29vYOdDUw0NjZ1dXd09vX39c3AV0OASZOmjR5ytSpU6dOQ5dBAtN7ZsycNXvO3HnoEshg/oKFixYvQRdFA0uXLUcXGqEAAH4FV0z+qQbjAAAAAElFTkSuQmCC" preserveAspectRatio="none" id="img2"></image><clipPath id="clip3"><path d="M44291.4 46947.4 187148 46947.4 187148 188823 44291.4 188823Z" fill-rule="evenodd" clip-rule="evenodd"/></clipPath></defs><g clip-path="url(#clip0)" transform="translate(-479 -279)"><g clip-path="url(#clip1)" transform="matrix(0.000105 0 0 0.000105 479 279)"><g clip-path="url(#clip3)" transform="matrix(1 0 0 1.00692 -44291.4 -47272.4)"><use width="100%" height="100%" xlink:href="#img2" transform="scale(6709.45 6709.45)"></use></g></g></g></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -1,7 +1,6 @@
import { AuthType } from "AuthType"; import { AuthType } from "AuthType";
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import ko from "knockout"; import ko from "knockout";
import { Features } from "Platform/Hosted/extractFeatures";
import React from "react"; import React from "react";
import { updateCollection } from "../../../Common/dataAccess/updateCollection"; import { updateCollection } from "../../../Common/dataAccess/updateCollection";
import { updateOffer } from "../../../Common/dataAccess/updateOffer"; import { updateOffer } from "../../../Common/dataAccess/updateOffer";
@@ -253,7 +252,7 @@ describe("SettingsComponent", () => {
it("should save throughput bucket changes when Save button is clicked", async () => { it("should save throughput bucket changes when Save button is clicked", async () => {
updateUserContext({ updateUserContext({
apiType: "SQL", apiType: "SQL",
features: { enableThroughputBuckets: true } as Features, throughputBucketsEnabled: true,
authType: AuthType.AAD, authType: AuthType.AAD,
}); });

View File

@@ -13,7 +13,7 @@ import {
} from "Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent"; } from "Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
import { isFabricNative } from "Platform/Fabric/FabricUtil"; import { isFabricNative } from "Platform/Fabric/FabricUtil";
import { isFullTextSearchEnabled, isVectorSearchEnabled } from "Utils/CapabilityUtils"; import { isVectorSearchEnabled } from "Utils/CapabilityUtils";
import { isRunningOnPublicCloud } from "Utils/CloudUtils"; import { isRunningOnPublicCloud } from "Utils/CloudUtils";
import * as React from "react"; import * as React from "react";
import DiscardIcon from "../../../../images/discard.svg"; import DiscardIcon from "../../../../images/discard.svg";
@@ -188,13 +188,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.isGlobalSecondaryIndex = this.isGlobalSecondaryIndex =
!!this.collection?.materializedViewDefinition() || !!this.collection?.materializedViews(); !!this.collection?.materializedViewDefinition() || !!this.collection?.materializedViews();
this.isVectorSearchEnabled = isVectorSearchEnabled() && !hasDatabaseSharedThroughput(this.collection); this.isVectorSearchEnabled = isVectorSearchEnabled() && !hasDatabaseSharedThroughput(this.collection);
this.isFullTextSearchEnabled = isFullTextSearchEnabled() && !hasDatabaseSharedThroughput(this.collection); this.isFullTextSearchEnabled = userContext.apiType === "SQL";
this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy; this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy;
this.throughputBucketsEnabled = this.throughputBucketsEnabled = userContext.throughputBucketsEnabled;
userContext.apiType === "SQL" &&
userContext.features.enableThroughputBuckets &&
userContext.authType === AuthType.AAD;
// Mongo container with system partition key still treat as "Fixed" // Mongo container with system partition key still treat as "Fixed"
this.isFixedContainer = this.isFixedContainer =
@@ -1074,11 +1071,11 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
databaseId: this.collection.databaseId, databaseId: this.collection.databaseId,
collectionId: this.collection.id(), collectionId: this.collection.id(),
currentOffer: this.collection.offer(), currentOffer: this.collection.offer(),
autopilotThroughput: this.collection.offer().autoscaleMaxThroughput autopilotThroughput: this.collection.offer?.()?.autoscaleMaxThroughput
? this.collection.offer().autoscaleMaxThroughput ? this.collection.offer?.()?.autoscaleMaxThroughput
: undefined, : undefined,
manualThroughput: this.collection.offer().manualThroughput manualThroughput: this.collection.offer?.()?.manualThroughput
? this.collection.offer().manualThroughput ? this.collection.offer?.()?.manualThroughput
: undefined, : undefined,
throughputBuckets: this.state.throughputBuckets, throughputBuckets: this.state.throughputBuckets,
}); });
@@ -1344,7 +1341,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
}); });
} }
if (this.throughputBucketsEnabled) { if (this.throughputBucketsEnabled && !hasDatabaseSharedThroughput(this.collection) && this.offer) {
tabs.push({ tabs.push({
tab: SettingsV2TabTypes.ThroughputBucketsTab, tab: SettingsV2TabTypes.ThroughputBucketsTab,
content: <ThroughputBucketsComponent {...throughputBucketsComponentProps} />, content: <ThroughputBucketsComponent {...throughputBucketsComponentProps} />,

View File

@@ -26,7 +26,7 @@ describe("ThroughputBucketsComponent", () => {
it("renders the correct number of buckets", () => { it("renders the correct number of buckets", () => {
render(<ThroughputBucketsComponent {...defaultProps} />); render(<ThroughputBucketsComponent {...defaultProps} />);
expect(screen.getAllByText(/Group \d+/)).toHaveLength(5); expect(screen.getAllByText(/Bucket \d+/)).toHaveLength(5);
}); });
it("renders buckets in the correct order even if input is unordered", () => { it("renders buckets in the correct order even if input is unordered", () => {
@@ -36,8 +36,14 @@ describe("ThroughputBucketsComponent", () => {
]; ];
render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={unorderedBuckets} />); render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={unorderedBuckets} />);
const bucketLabels = screen.getAllByText(/Group \d+/).map((el) => el.textContent); const bucketLabels = screen.getAllByText(/Bucket \d+/).map((el) => el.textContent);
expect(bucketLabels).toEqual(["Group 1 (Data Explorer Query Bucket)", "Group 2", "Group 3", "Group 4", "Group 5"]); expect(bucketLabels).toEqual([
"Bucket 1 (Data Explorer Query Bucket)",
"Bucket 2",
"Bucket 3",
"Bucket 4",
"Bucket 5",
]);
}); });
it("renders all provided buckets even if they exceed the max default bucket count", () => { it("renders all provided buckets even if they exceed the max default bucket count", () => {
@@ -53,7 +59,7 @@ describe("ThroughputBucketsComponent", () => {
render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={oversizedBuckets} />); render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={oversizedBuckets} />);
expect(screen.getAllByText(/Group \d+/)).toHaveLength(7); expect(screen.getAllByText(/Bucket \d+/)).toHaveLength(7);
expect(screen.getByDisplayValue("50")).toBeInTheDocument(); expect(screen.getByDisplayValue("50")).toBeInTheDocument();
expect(screen.getByDisplayValue("60")).toBeInTheDocument(); expect(screen.getByDisplayValue("60")).toBeInTheDocument();
@@ -171,7 +177,7 @@ describe("ThroughputBucketsComponent", () => {
it("ensures default buckets are used when no buckets are provided", () => { it("ensures default buckets are used when no buckets are provided", () => {
render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={[]} />); render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={[]} />);
expect(screen.getAllByText(/Group \d+/)).toHaveLength(5); expect(screen.getAllByText(/Bucket \d+/)).toHaveLength(5);
expect(screen.getAllByDisplayValue("100")).toHaveLength(5); expect(screen.getAllByDisplayValue("100")).toHaveLength(5);
}); });
}); });

View File

@@ -76,7 +76,7 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
value={bucket.maxThroughputPercentage} value={bucket.maxThroughputPercentage}
onChange={(newValue) => handleBucketChange(bucket.id, newValue)} onChange={(newValue) => handleBucketChange(bucket.id, newValue)}
showValue={false} showValue={false}
label={`Group ${bucket.id}${bucket.id === 1 ? " (Data Explorer Query Bucket)" : ""}`} label={`Bucket ${bucket.id}${bucket.id === 1 ? " (Data Explorer Query Bucket)" : ""}`}
styles={{ root: { flex: 2, maxWidth: 400 } }} styles={{ root: { flex: 2, maxWidth: 400 } }}
disabled={bucket.maxThroughputPercentage === 100} disabled={bucket.maxThroughputPercentage === 100}
/> />

View File

@@ -285,7 +285,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
serverId, serverId,
numberOfRegions, numberOfRegions,
isMultimaster, isMultimaster,
true, false,
); );
return ( return (
<div> <div>

View File

@@ -178,6 +178,32 @@ exports[`SettingsComponent renders 1`] = `
timeToLiveSecondsBaseline={5} timeToLiveSecondsBaseline={5}
/> />
</PivotItem> </PivotItem>
<PivotItem
headerText="Container Policies"
itemKey="ContainerVectorPolicyTab"
key="ContainerVectorPolicyTab"
style={
{
"marginTop": 20,
}
}
>
<ContainerPolicyComponent
fullTextPolicy={{}}
fullTextPolicyBaseline={{}}
isFullTextSearchEnabled={true}
isGlobalSecondaryIndex={true}
isVectorSearchEnabled={false}
onFullTextPolicyChange={[Function]}
onFullTextPolicyDirtyChange={[Function]}
onVectorEmbeddingPolicyChange={[Function]}
onVectorEmbeddingPolicyDirtyChange={[Function]}
resetShouldDiscardContainerPolicyChange={[Function]}
shouldDiscardContainerPolicies={false}
vectorEmbeddingPolicy={{}}
vectorEmbeddingPolicyBaseline={{}}
/>
</PivotItem>
<PivotItem <PivotItem
headerText="Indexing Policy" headerText="Indexing Policy"
itemKey="IndexingPolicyTab" itemKey="IndexingPolicyTab"

View File

@@ -12,6 +12,7 @@ import { isFabricMirrored, isFabricMirroredKey, scheduleRefreshFabricToken } fro
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils"; import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils"; import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
import { featureRegistered } from "Utils/FeatureRegistrationUtils";
import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts"; import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
import { useQueryCopilot } from "hooks/useQueryCopilot"; import { useQueryCopilot } from "hooks/useQueryCopilot";
import * as ko from "knockout"; import * as ko from "knockout";
@@ -282,6 +283,42 @@ export default class Explorer {
} }
} }
public openInVsCode(): void {
const activeTab = useTabs.getState().activeTab;
const resourceId = encodeURIComponent(userContext.databaseAccount.id);
const database = encodeURIComponent(activeTab?.collection?.databaseId);
const container = encodeURIComponent(activeTab?.collection?.id());
const baseUrl = `vscode://ms-azuretools.vscode-cosmosdb?resourceId=${resourceId}`;
const vscodeUrl = activeTab ? `${baseUrl}&database=${database}&container=${container}` : baseUrl;
const openVSCodeDialogProps: DialogProps = {
linkProps: {
linkText: "Download Visual Studio Code",
linkUrl: "https://code.visualstudio.com/download",
},
isModal: true,
title: `Open your Azure Cosmos DB account in Visual Studio Code`,
subText: `Please ensure Visual Studio Code is installed on your device.
If you don't have it installed, please download it from the link below.`,
primaryButtonText: "Open in VS Code",
secondaryButtonText: "Cancel",
onPrimaryButtonClick: () => {
try {
window.location.href = vscodeUrl;
TelemetryProcessor.traceStart(Action.OpenVSCode);
} catch (error) {
logConsoleError(`Failed to open VS Code: ${getErrorMessage(error)}`);
}
},
onSecondaryButtonClick: () => {
useDialog.getState().closeDialog();
TelemetryProcessor.traceCancel(Action.OpenVSCode);
},
};
useDialog.getState().openDialog(openVSCodeDialogProps);
}
public async openCESCVAFeedbackBlade(): Promise<void> { public async openCESCVAFeedbackBlade(): Promise<void> {
sendMessage({ type: MessageTypes.OpenCESCVAFeedbackBlade }); sendMessage({ type: MessageTypes.OpenCESCVAFeedbackBlade });
Logger.logInfo( Logger.logInfo(
@@ -1133,6 +1170,11 @@ export default class Explorer {
await this.initNotebooks(userContext.databaseAccount); await this.initNotebooks(userContext.databaseAccount);
} }
if (userContext.authType === AuthType.AAD && userContext.apiType === "SQL") {
const throughputBucketsEnabled = await featureRegistered(userContext.subscriptionId, "ThroughputBucketing");
updateUserContext({ throughputBucketsEnabled });
}
this.refreshSampleData(); this.refreshSampleData();
} }

View File

@@ -14,6 +14,7 @@ import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg";
import OpenInTabIcon from "../../../../images/open-in-tab.svg"; import OpenInTabIcon from "../../../../images/open-in-tab.svg";
import SettingsIcon from "../../../../images/settings_15x15.svg"; import SettingsIcon from "../../../../images/settings_15x15.svg";
import SynapseIcon from "../../../../images/synapse-link.svg"; import SynapseIcon from "../../../../images/synapse-link.svg";
import VSCodeIcon from "../../../../images/vscode.svg";
import { AuthType } from "../../../AuthType"; import { AuthType } from "../../../AuthType";
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";
import { Platform, configContext } from "../../../ConfigContext"; import { Platform, configContext } from "../../../ConfigContext";
@@ -60,6 +61,10 @@ export function createStaticCommandBarButtons(
addDivider(); addDivider();
buttons.push(addSynapseLink); buttons.push(addSynapseLink);
} }
if (userContext.apiType !== "Gremlin") {
const addVsCode = createOpenVsCodeDialogButton(container);
buttons.push(addVsCode);
}
} }
if (isDataplaneRbacSupported(userContext.apiType)) { if (isDataplaneRbacSupported(userContext.apiType)) {
@@ -268,6 +273,18 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
}; };
} }
function createOpenVsCodeDialogButton(container: Explorer): CommandButtonComponentProps {
const label = "Visual Studio Code";
return {
iconSrc: VSCodeIcon,
iconAlt: label,
onCommandClick: () => container.openInVsCode(),
commandButtonLabel: label,
hasPopup: false,
ariaLabel: label,
};
}
function createLoginForEntraIDButton(container: Explorer): CommandButtonComponentProps { function createLoginForEntraIDButton(container: Explorer): CommandButtonComponentProps {
if (configContext.platform !== Platform.Portal) { if (configContext.platform !== Platform.Portal) {
return undefined; return undefined;
@@ -501,5 +518,6 @@ export function createPostgreButtons(container: Explorer): CommandButtonComponen
export function createVCoreMongoButtons(container: Explorer): CommandButtonComponentProps[] { export function createVCoreMongoButtons(container: Explorer): CommandButtonComponentProps[] {
const openVCoreMongoTerminalButton = createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.VCoreMongo); const openVCoreMongoTerminalButton = createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.VCoreMongo);
return [openVCoreMongoTerminalButton]; const addVsCode = createOpenVsCodeDialogButton(container);
return [openVCoreMongoTerminalButton, addVsCode];
} }

View File

@@ -49,12 +49,7 @@ import { Action } from "Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
import { getCollectionName } from "Utils/APITypeUtils"; import { getCollectionName } from "Utils/APITypeUtils";
import { import { isCapabilityEnabled, isServerlessAccount, isVectorSearchEnabled } from "Utils/CapabilityUtils";
isCapabilityEnabled,
isFullTextSearchEnabled,
isServerlessAccount,
isVectorSearchEnabled,
} from "Utils/CapabilityUtils";
import { getUpsellMessage } from "Utils/PricingUtils"; import { getUpsellMessage } from "Utils/PricingUtils";
import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils"; import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils";
import { CollapsibleSectionComponent } from "../../Controls/CollapsiblePanel/CollapsibleSectionComponent"; import { CollapsibleSectionComponent } from "../../Controls/CollapsiblePanel/CollapsibleSectionComponent";
@@ -110,6 +105,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
private collectionThroughput: number; private collectionThroughput: number;
private isCollectionAutoscale: boolean; private isCollectionAutoscale: boolean;
private isCostAcknowledged: boolean; private isCostAcknowledged: boolean;
private showFullTextSearch: boolean;
constructor(props: AddCollectionPanelProps) { constructor(props: AddCollectionPanelProps) {
super(props); super(props);
@@ -144,6 +140,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
fullTextIndexes: [], fullTextIndexes: [],
fullTextPolicyValidated: true, fullTextPolicyValidated: true,
}; };
this.showFullTextSearch = userContext.apiType === "SQL";
} }
componentDidMount(): void { componentDidMount(): void {
@@ -869,7 +867,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</CollapsibleSectionComponent> </CollapsibleSectionComponent>
</Stack> </Stack>
)} )}
{this.shouldShowFullTextSearchParameters() && ( {this.showFullTextSearch && (
<Stack> <Stack>
<CollapsibleSectionComponent <CollapsibleSectionComponent
title="Container Full Text Search Policy" title="Container Full Text Search Policy"
@@ -1160,10 +1158,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
return isVectorSearchEnabled() && (isServerlessAccount() || this.shouldShowCollectionThroughputInput()); return isVectorSearchEnabled() && (isServerlessAccount() || this.shouldShowCollectionThroughputInput());
} }
private shouldShowFullTextSearchParameters() {
return isFullTextSearchEnabled() && (isServerlessAccount() || this.shouldShowCollectionThroughputInput());
}
private parseUniqueKeys(): DataModels.UniqueKeyPolicy { private parseUniqueKeys(): DataModels.UniqueKeyPolicy {
if (this.state.uniqueKeys?.length === 0) { if (this.state.uniqueKeys?.length === 0) {
return undefined; return undefined;
@@ -1316,7 +1310,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
}; };
} }
if (this.shouldShowFullTextSearchParameters()) { if (this.showFullTextSearch) {
indexingPolicy.fullTextIndexes = this.state.fullTextIndexes; indexingPolicy.fullTextIndexes = this.state.fullTextIndexes;
} }

View File

@@ -411,6 +411,44 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
/> />
</Stack> </Stack>
</Stack> </Stack>
<Stack>
<CollapsibleSectionComponent
isExpandedByDefault={false}
onExpand={[Function]}
title="Container Full Text Search Policy"
>
<Stack
id="collapsibleFullTextPolicySectionContent"
styles={
{
"root": {
"position": "relative",
},
}
}
>
<Stack
styles={
{
"root": {
"paddingLeft": 40,
},
}
}
>
<FullTextPoliciesComponent
fullTextPolicy={
{
"defaultLanguage": "en-US",
"fullTextPaths": [],
}
}
onFullTextPathChange={[Function]}
/>
</Stack>
</Stack>
</CollapsibleSectionComponent>
</Stack>
<CollapsibleSectionComponent <CollapsibleSectionComponent
isExpandedByDefault={false} isExpandedByDefault={false}
onExpand={[Function]} onExpand={[Function]}

View File

@@ -40,12 +40,12 @@ import { PanelInfoErrorComponent } from "Explorer/Panes/PanelInfoErrorComponent"
import { PanelLoadingScreen } from "Explorer/Panes/PanelLoadingScreen"; import { PanelLoadingScreen } from "Explorer/Panes/PanelLoadingScreen";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
import { useSidePanel } from "hooks/useSidePanel"; import { useSidePanel } from "hooks/useSidePanel";
import React, { useEffect, useState } from "react"; import React, { MutableRefObject, useEffect, useRef, useState } from "react";
import { CollectionCreation } from "Shared/Constants"; import { CollectionCreation } from "Shared/Constants";
import { Action } from "Shared/Telemetry/TelemetryConstants"; import { Action } from "Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
import { isFullTextSearchEnabled, isServerlessAccount, isVectorSearchEnabled } from "Utils/CapabilityUtils"; import { isServerlessAccount, isVectorSearchEnabled } from "Utils/CapabilityUtils";
import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils"; import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils";
export interface AddGlobalSecondaryIndexPanelProps { export interface AddGlobalSecondaryIndexPanelProps {
@@ -75,6 +75,8 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
const [showErrorDetails, setShowErrorDetails] = useState<boolean>(); const [showErrorDetails, setShowErrorDetails] = useState<boolean>();
const [isExecuting, setIsExecuting] = useState<boolean>(); const [isExecuting, setIsExecuting] = useState<boolean>();
const showFullTextSearch: MutableRefObject<boolean> = useRef<boolean>(userContext.apiType === "SQL");
useEffect(() => { useEffect(() => {
const sourceContainerOptions: IDropdownOption[] = []; const sourceContainerOptions: IDropdownOption[] = [];
useDatabases.getState().databases.forEach((database: Database) => { useDatabases.getState().databases.forEach((database: Database) => {
@@ -140,10 +142,6 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
return isVectorSearchEnabled() && (isServerlessAccount() || showCollectionThroughputInput()); return isVectorSearchEnabled() && (isServerlessAccount() || showCollectionThroughputInput());
}; };
const showFullTextSearchParameters = (): boolean => {
return isFullTextSearchEnabled() && (isServerlessAccount() || showCollectionThroughputInput());
};
const getAnalyticalStorageTtl = (): number => { const getAnalyticalStorageTtl = (): number => {
if (!isSynapseLinkEnabled()) { if (!isSynapseLinkEnabled()) {
return undefined; return undefined;
@@ -175,11 +173,6 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
return false; return false;
} }
if (globalSecondaryIndexThroughput > CollectionCreation.MaxRUPerPartition) {
setErrorMessage("Unsharded collections support up to 10,000 RUs");
return false;
}
if (showVectorSearchParameters()) { if (showVectorSearchParameters()) {
if (!vectorPolicyValidated) { if (!vectorPolicyValidated) {
setErrorMessage("Please fix errors in container vector policy"); setErrorMessage("Please fix errors in container vector policy");
@@ -233,7 +226,7 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
}; };
} }
if (showFullTextSearchParameters()) { if (showFullTextSearch) {
indexingPolicy.fullTextIndexes = fullTextIndexes; indexingPolicy.fullTextIndexes = fullTextIndexes;
} }
@@ -392,7 +385,7 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
}} }}
/> />
)} )}
{showFullTextSearchParameters() && ( {showFullTextSearch && (
<FullTextSearchComponent <FullTextSearchComponent
{...{ fullTextPolicy, setFullTextPolicy, setFullTextIndexes, setFullTextPolicyValidated }} {...{ fullTextPolicy, setFullTextPolicy, setFullTextIndexes, setFullTextPolicyValidated }}
/> />

View File

@@ -47,7 +47,7 @@ export const ThroughputComponent = (props: ThroughputComponentProps): JSX.Elemen
<ThroughputInput <ThroughputInput
showFreeTierExceedThroughputTooltip={isFreeTierAccount() && !useDatabases.getState().isFirstResourceCreated()} showFreeTierExceedThroughputTooltip={isFreeTierAccount() && !useDatabases.getState().isFirstResourceCreated()}
isDatabase={false} isDatabase={false}
isSharded={false} isSharded={true}
isFreeTier={isFreeTierAccount()} isFreeTier={isFreeTierAccount()}
isQuickstart={false} isQuickstart={false}
isGlobalSecondaryIndex={true} isGlobalSecondaryIndex={true}

View File

@@ -172,6 +172,17 @@ exports[`AddGlobalSecondaryIndexPanel render default panel 1`] = `
} }
setEnableAnalyticalStore={[Function]} setEnableAnalyticalStore={[Function]}
/> />
<FullTextSearchComponent
fullTextPolicy={
{
"defaultLanguage": "en-US",
"fullTextPaths": [],
}
}
setFullTextIndexes={[Function]}
setFullTextPolicy={[Function]}
setFullTextPolicyValidated={[Function]}
/>
<AdvancedComponent <AdvancedComponent
setSubPartitionKeys={[Function]} setSubPartitionKeys={[Function]}
setUseHashV1={[Function]} setUseHashV1={[Function]}

View File

@@ -28,6 +28,7 @@ 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";
import QuickStartIcon from "../../../images/Quickstart_Lightning.svg"; import QuickStartIcon from "../../../images/Quickstart_Lightning.svg";
import VisualStudioIcon from "../../../images/VisualStudio.svg";
import NotebookIcon from "../../../images/notebook/Notebook-resource.svg"; import NotebookIcon from "../../../images/notebook/Notebook-resource.svg";
import CollectionIcon from "../../../images/tree-collection.svg"; import CollectionIcon from "../../../images/tree-collection.svg";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
@@ -458,10 +459,10 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
} }
if (userContext.apiType === "VCoreMongo") { if (userContext.apiType === "VCoreMongo") {
icon = ContainersIcon; icon = VisualStudioIcon;
title = "Connect with Studio 3T"; title = "Connect with VS Code";
description = "Prefer Studio 3T? Find your connection strings here"; description = "Query and Manage your MongoDB cluster in Visual Studio Code";
onClick = () => useTabs.getState().openAndActivateReactTab(ReactTabKind.Connect); onClick = () => this.container.openInVsCode();
} }
return { return {

View File

@@ -149,6 +149,7 @@ export enum Action {
UploadDocuments, // Used in Fabric. Please do not rename. UploadDocuments, // Used in Fabric. Please do not rename.
CloudShellUserConsent, CloudShellUserConsent,
CloudShellTerminalSession, CloudShellTerminalSession,
OpenVSCode,
} }
export const ActionModifiers = { export const ActionModifiers = {

View File

@@ -117,6 +117,7 @@ export interface UserContext {
readonly feedbackPolicies?: AdminFeedbackPolicySettings; readonly feedbackPolicies?: AdminFeedbackPolicySettings;
readonly dataPlaneRbacEnabled?: boolean; readonly dataPlaneRbacEnabled?: boolean;
readonly refreshCosmosClient?: boolean; readonly refreshCosmosClient?: boolean;
throughputBucketsEnabled?: boolean;
} }
export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres" | "VCoreMongo"; export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres" | "VCoreMongo";

View File

@@ -20,7 +20,3 @@ export const isServerlessAccount = (): boolean => {
export const isVectorSearchEnabled = (): boolean => { export const isVectorSearchEnabled = (): boolean => {
return userContext.apiType === "SQL" && isCapabilityEnabled(Constants.CapabilityNames.EnableNoSQLVectorSearch); return userContext.apiType === "SQL" && isCapabilityEnabled(Constants.CapabilityNames.EnableNoSQLVectorSearch);
}; };
export const isFullTextSearchEnabled = (): boolean => {
return userContext.apiType === "SQL" && isCapabilityEnabled(Constants.CapabilityNames.EnableNoSQLFullTextSearch);
};

View File

@@ -0,0 +1,48 @@
import { configContext } from "ConfigContext";
import { FeatureRegistration } from "Contracts/DataModels";
import { AuthorizationTokenHeaderMetadata } from "Contracts/ViewModels";
import { getAuthorizationHeader } from "Utils/AuthorizationUtils";
export const featureRegistered = async (subscriptionId: string, feature: string) => {
const api_version = "2021-07-01";
const url = `${configContext.ARM_ENDPOINT}/subscriptions/${subscriptionId}/providers/Microsoft.Features/featureProviders/Microsoft.DocumentDB/subscriptionFeatureRegistrations/${feature}?api-version=${api_version}`;
const authorizationHeader: AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
const headers = { [authorizationHeader.header]: authorizationHeader.token };
let response;
try {
response = await _fetchWithTimeout(url, headers);
} catch (error) {
return false;
}
if (!response?.ok) {
return false;
}
const featureRegistration = (await response?.json()) as FeatureRegistration;
return featureRegistration?.properties?.state === "Registered";
};
async function _fetchWithTimeout(
url: string,
headers: {
[x: string]: string;
},
) {
const timeout = 10000;
const options = { timeout };
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
const response = await window.fetch(url, {
headers,
...options,
signal: controller.signal,
});
clearTimeout(id);
return response;
}