@@ -54,6 +77,39 @@ export const Tabs = ({ explorer }: TabsProps): JSX.Element => {
{networkSettingsWarning}
)}
+ {showRUThresholdMessageBar && (
+
+ )}
+ {showMongoAndCassandraProxiesNetworkSettingsWarningState && (
+
@@ -138,11 +194,9 @@ function TabNav({ tab, active, tabKind }: { tab?: Tab; active: boolean; tabKind?
)}
{useObservable(tab?.tabTitle || getReactTabTitle())}
- {tabKind !== ReactTabKind.Home && (
-
-
-
- )}
+
+
+
@@ -255,6 +309,9 @@ const isQueryErrorThrown = (tab?: Tab, tabKind?: ReactTabKind): boolean => {
};
const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): JSX.Element => {
+ // React tabs have no context buttons.
+ useCommandBar.getState().setContextButtons([]);
+
// eslint-disable-next-line no-console
switch (activeReactTab) {
case ReactTabKind.Connect:
@@ -279,3 +336,69 @@ const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): J
throw Error(`Unsupported tab kind ${ReactTabKind[activeReactTab]}`);
}
};
+
+const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => {
+ const ipRules: IpRule[] = userContext.databaseAccount?.properties?.ipRules;
+ if (
+ ((userContext.apiType === "Mongo" && configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Local) ||
+ (userContext.apiType === "Cassandra" &&
+ configContext.CASSANDRA_PROXY_ENDPOINT !== CassandraProxyEndpoints.Development)) &&
+ ipRules?.length
+ ) {
+ const legacyPortalBackendIPs: string[] = PortalBackendIPs[configContext.BACKEND_ENDPOINT];
+ const ipAddressesFromIPRules: string[] = ipRules.map((ipRule) => ipRule.ipAddressOrRange);
+ const ipRulesIncludeLegacyPortalBackend: boolean = legacyPortalBackendIPs.every((legacyPortalBackendIP: string) =>
+ ipAddressesFromIPRules.includes(legacyPortalBackendIP),
+ );
+ if (!ipRulesIncludeLegacyPortalBackend) {
+ return false;
+ }
+
+ if (userContext.apiType === "Mongo") {
+ const isProdOrMpacMongoProxyEndpoint: boolean = [MongoProxyEndpoints.Mpac, MongoProxyEndpoints.Prod].includes(
+ configContext.MONGO_PROXY_ENDPOINT,
+ );
+
+ const mongoProxyOutboundIPs: string[] = isProdOrMpacMongoProxyEndpoint
+ ? [...MongoProxyOutboundIPs[MongoProxyEndpoints.Mpac], ...MongoProxyOutboundIPs[MongoProxyEndpoints.Prod]]
+ : MongoProxyOutboundIPs[configContext.MONGO_PROXY_ENDPOINT];
+
+ const ipRulesIncludeMongoProxy: boolean = mongoProxyOutboundIPs.every((mongoProxyOutboundIP: string) =>
+ ipAddressesFromIPRules.includes(mongoProxyOutboundIP),
+ );
+
+ if (ipRulesIncludeMongoProxy) {
+ updateConfigContext({
+ MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED: true,
+ });
+ }
+
+ return !ipRulesIncludeMongoProxy;
+ } else if (userContext.apiType === "Cassandra") {
+ const isProdOrMpacCassandraProxyEndpoint: boolean = [
+ CassandraProxyEndpoints.Mpac,
+ CassandraProxyEndpoints.Prod,
+ ].includes(configContext.CASSANDRA_PROXY_ENDPOINT);
+
+ const cassandraProxyOutboundIPs: string[] = isProdOrMpacCassandraProxyEndpoint
+ ? [
+ ...CassandraProxyOutboundIPs[CassandraProxyEndpoints.Mpac],
+ ...CassandraProxyOutboundIPs[CassandraProxyEndpoints.Prod],
+ ]
+ : CassandraProxyOutboundIPs[configContext.CASSANDRA_PROXY_ENDPOINT];
+
+ const ipRulesIncludeCassandraProxy: boolean = cassandraProxyOutboundIPs.every(
+ (cassandraProxyOutboundIP: string) => ipAddressesFromIPRules.includes(cassandraProxyOutboundIP),
+ );
+
+ if (ipRulesIncludeCassandraProxy) {
+ updateConfigContext({
+ CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: true,
+ });
+ }
+
+ return !ipRulesIncludeCassandraProxy;
+ }
+ }
+ return false;
+};
diff --git a/src/Explorer/Tabs/TabsBase.ts b/src/Explorer/Tabs/TabsBase.ts
index a6f3a45dd..8b017f6bc 100644
--- a/src/Explorer/Tabs/TabsBase.ts
+++ b/src/Explorer/Tabs/TabsBase.ts
@@ -1,3 +1,4 @@
+import { KeyboardActionGroup, clearKeyboardActionGroup } from "KeyboardShortcuts";
import * as ko from "knockout";
import * as Constants from "../../Common/Constants";
import * as ThemeUtility from "../../Common/ThemeUtility";
@@ -40,11 +41,10 @@ export default class TabsBase extends WaitsForTemplateViewModel {
this.database = options.database;
this.rid = options.rid || (this.collection && this.collection.rid) || "";
this.tabKind = options.tabKind;
- this.tabTitle = ko.observable
(options.title);
+ this.tabTitle = ko.observable(this.getTitle(options));
this.tabPath =
- ko.observable(options.tabPath ?? "") ||
- (this.collection &&
- ko.observable(`${this.collection.databaseId}>${this.collection.id()}>${this.tabTitle()}`));
+ this.collection &&
+ ko.observable(`${this.collection.databaseId}>${this.collection.id()}>${options.title}`);
this.pendingNotification = ko.observable(undefined);
this.onLoadStartKey = options.onLoadStartKey;
this.closeTabButton = {
@@ -108,6 +108,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
}
public onActivate(): void {
+ clearKeyboardActionGroup(KeyboardActionGroup.ACTIVE_TAB);
this.updateSelectedNode();
this.collection?.selectedSubnodeKind(this.tabKind);
this.database?.selectedSubnodeKind(this.tabKind);
@@ -143,6 +144,26 @@ export default class TabsBase extends WaitsForTemplateViewModel {
return (this.collection && this.collection.container) || (this.database && this.database.container);
}
+ public getTitle(options: ViewModels.TabOptions): string {
+ const coll = this.collection?.id();
+ const db = this.database?.id();
+ if (coll) {
+ if (coll.length > 8) {
+ return coll.slice(0, 5) + "…" + options.title;
+ } else {
+ return coll + "." + options.title;
+ }
+ } else if (db) {
+ if (db.length > 8) {
+ return db.slice(0, 5) + "…" + options.title;
+ } else {
+ return db + "." + options.title;
+ }
+ } else {
+ return options.title;
+ }
+ }
+
/** Renders a Javascript object to be displayed inside Monaco Editor */
public renderObjectForEditor(value: any, replacer: any, space: string | number): string {
return JSON.stringify(value, replacer, space);
diff --git a/src/Explorer/Tabs/TerminalTab.tsx b/src/Explorer/Tabs/TerminalTab.tsx
index 538c596c5..986d4ef11 100644
--- a/src/Explorer/Tabs/TerminalTab.tsx
+++ b/src/Explorer/Tabs/TerminalTab.tsx
@@ -34,6 +34,7 @@ class NotebookTerminalComponentAdapter implements ReactAdapter {
private getTabId: () => string,
private getUsername: () => string,
private isAllPublicIPAddressesEnabled: ko.Observable,
+ private kind: ViewModels.TerminalKind,
) {}
public renderComponent(): JSX.Element {
@@ -42,7 +43,7 @@ class NotebookTerminalComponentAdapter implements ReactAdapter {
);
}
@@ -58,6 +59,18 @@ class NotebookTerminalComponentAdapter implements ReactAdapter {
);
}
+
+ private getShellNameForDisplay(terminalKind: ViewModels.TerminalKind): string {
+ switch (terminalKind) {
+ case ViewModels.TerminalKind.Postgres:
+ return "PostgreSQL";
+ case ViewModels.TerminalKind.Mongo:
+ case ViewModels.TerminalKind.VCoreMongo:
+ return "MongoDB";
+ default:
+ return "";
+ }
+ }
}
export default class TerminalTab extends TabsBase {
@@ -76,6 +89,7 @@ export default class TerminalTab extends TabsBase {
() => this.tabId,
() => this.getUsername(),
this.isAllPublicIPAddressesEnabled,
+ options.kind,
);
this.notebookTerminalComponentAdapter.parameters = ko.computed(() => {
if (
diff --git a/src/Explorer/Tabs/TriggerTabContent.tsx b/src/Explorer/Tabs/TriggerTabContent.tsx
index 315984554..5fd28502a 100644
--- a/src/Explorer/Tabs/TriggerTabContent.tsx
+++ b/src/Explorer/Tabs/TriggerTabContent.tsx
@@ -1,12 +1,13 @@
import { TriggerDefinition } from "@azure/cosmos";
import { Dropdown, IDropdownOption, Label, TextField } from "@fluentui/react";
+import { KeyboardAction } from "KeyboardShortcuts";
import React, { Component } from "react";
import DiscardIcon from "../../../images/discard.svg";
import SaveIcon from "../../../images/save-cosmos.svg";
import * as Constants from "../../Common/Constants";
+import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import { createTrigger } from "../../Common/dataAccess/createTrigger";
import { updateTrigger } from "../../Common/dataAccess/updateTrigger";
-import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import * as ViewModels from "../../Contracts/ViewModels";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
@@ -218,6 +219,18 @@ export class TriggerTabContent extends Component
diff --git a/src/Explorer/Tabs/UserDefinedFunctionTabContent.tsx b/src/Explorer/Tabs/UserDefinedFunctionTabContent.tsx
index c0d78ff9a..98b90c045 100644
--- a/src/Explorer/Tabs/UserDefinedFunctionTabContent.tsx
+++ b/src/Explorer/Tabs/UserDefinedFunctionTabContent.tsx
@@ -1,12 +1,13 @@
import { UserDefinedFunctionDefinition } from "@azure/cosmos";
import { Label, TextField } from "@fluentui/react";
+import { KeyboardAction } from "KeyboardShortcuts";
import React, { Component } from "react";
import DiscardIcon from "../../../images/discard.svg";
import SaveIcon from "../../../images/save-cosmos.svg";
import * as Constants from "../../Common/Constants";
+import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import { createUserDefinedFunction } from "../../Common/dataAccess/createUserDefinedFunction";
import { updateUserDefinedFunction } from "../../Common/dataAccess/updateUserDefinedFunction";
-import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import * as ViewModels from "../../Contracts/ViewModels";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
@@ -80,6 +81,7 @@ export default class UserDefinedFunctionTabContent extends Component<
setState: this.setState,
iconSrc: SaveIcon,
iconAlt: label,
+ keyboardAction: KeyboardAction.SAVE_ITEM,
onCommandClick: this.onSaveClick,
commandButtonLabel: label,
ariaLabel: label,
@@ -94,6 +96,7 @@ export default class UserDefinedFunctionTabContent extends Component<
...this,
iconSrc: SaveIcon,
iconAlt: label,
+ keyboardAction: KeyboardAction.SAVE_ITEM,
onCommandClick: this.onUpdateClick,
commandButtonLabel: label,
ariaLabel: label,
@@ -109,6 +112,7 @@ export default class UserDefinedFunctionTabContent extends Component<
...this,
iconSrc: DiscardIcon,
iconAlt: label,
+ keyboardAction: KeyboardAction.CANCEL_OR_DISCARD,
onCommandClick: this.onDiscard,
commandButtonLabel: label,
ariaLabel: label,
diff --git a/src/Explorer/Tree/Collection.ts b/src/Explorer/Tree/Collection.ts
index 4b5411d09..d7c673620 100644
--- a/src/Explorer/Tree/Collection.ts
+++ b/src/Explorer/Tree/Collection.ts
@@ -58,6 +58,7 @@ export default class Collection implements ViewModels.Collection {
public indexingPolicy: ko.Observable;
public uniqueKeyPolicy: DataModels.UniqueKeyPolicy;
public usageSizeInKB: ko.Observable;
+ public computedProperties: ko.Observable;
public offer: ko.Observable;
public conflictResolutionPolicy: ko.Observable;
@@ -121,6 +122,7 @@ export default class Collection implements ViewModels.Collection {
this.schema = data.schema;
this.requestSchema = data.requestSchema;
this.geospatialConfig = ko.observable(data.geospatialConfig);
+ this.computedProperties = ko.observable(data.computedProperties);
this.partitionKeyPropertyHeaders = this.partitionKey?.paths;
this.partitionKeyProperties = this.partitionKeyPropertyHeaders?.map((partitionKeyPropertyHeader, i) => {
@@ -306,7 +308,7 @@ export default class Collection implements ViewModels.Collection {
collectionName: this.id(),
dataExplorerArea: Constants.Areas.Tab,
- tabTitle: this.rawDataModel.id + " - Items",
+ tabTitle: "Items",
});
this.documentIds([]);
@@ -314,7 +316,7 @@ export default class Collection implements ViewModels.Collection {
partitionKey: this.partitionKey,
documentIds: ko.observableArray([]),
tabKind: ViewModels.CollectionTabKind.Documents,
- title: this.rawDataModel.id + " - Items",
+ title: "Items",
collection: this,
node: this,
tabPath: `${this.databaseId}>${this.id()}>Documents`,
diff --git a/src/Explorer/Tree/ResourceTree.tsx b/src/Explorer/Tree/ResourceTree.tsx
index b245f327f..a14c202e8 100644
--- a/src/Explorer/Tree/ResourceTree.tsx
+++ b/src/Explorer/Tree/ResourceTree.tsx
@@ -1,11 +1,9 @@
-import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react";
import { SampleDataTree } from "Explorer/Tree/SampleDataTree";
import { getItemName } from "Utils/APITypeUtils";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import * as React from "react";
import shallow from "zustand/shallow";
import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg";
-import GalleryIcon from "../../../images/GalleryIcon.svg";
import DeleteIcon from "../../../images/delete.svg";
import CopyIcon from "../../../images/notebook/Notebook-copy.svg";
import NewNotebookIcon from "../../../images/notebook/Notebook-new.svg";
@@ -14,17 +12,14 @@ import FileIcon from "../../../images/notebook/file-cosmos.svg";
import PublishIcon from "../../../images/notebook/publish_content.svg";
import RefreshIcon from "../../../images/refresh-cosmos.svg";
import CollectionIcon from "../../../images/tree-collection.svg";
-import { Areas, ConnectionStatusType, Notebook } from "../../Common/Constants";
import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
-import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import { isServerlessAccount } from "../../Utils/CapabilityUtils";
import * as GitHubUtils from "../../Utils/GitHubUtils";
-import { useSidePanel } from "../../hooks/useSidePanel";
import { useTabs } from "../../hooks/useTabs";
import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFactory";
import { AccordionComponent, AccordionItemComponent } from "../Controls/Accordion/AccordionComponent";
@@ -36,7 +31,6 @@ import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity";
import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem";
import { NotebookUtil } from "../Notebook/NotebookUtil";
import { useNotebook } from "../Notebook/useNotebook";
-import { GitHubReposPanel } from "../Panes/GitHubReposPanel/GitHubReposPanel";
import TabsBase from "../Tabs/TabsBase";
import { useDatabases } from "../useDatabases";
import { useSelectedNode } from "../useSelectedNode";
@@ -75,152 +69,6 @@ export const ResourceTree: React.FC = ({ container }: Resourc
configContext.platform !== Platform.Fabric && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin");
const pseudoDirPath = "PsuedoDir";
- const buildGalleryCallout = (): JSX.Element => {
- if (
- LocalStorageUtility.hasItem(StorageKey.GalleryCalloutDismissed) &&
- LocalStorageUtility.getEntryBoolean(StorageKey.GalleryCalloutDismissed)
- ) {
- return undefined;
- }
-
- const calloutProps: ICalloutProps = {
- calloutMaxWidth: 350,
- ariaLabel: "New gallery",
- role: "alertdialog",
- gapSpace: 0,
- target: ".galleryHeader",
- directionalHint: DirectionalHint.leftTopEdge,
- onDismiss: () => {
- LocalStorageUtility.setEntryBoolean(StorageKey.GalleryCalloutDismissed, true);
- },
- setInitialFocus: true,
- };
-
- const openGalleryProps: ILinkProps = {
- onClick: () => {
- LocalStorageUtility.setEntryBoolean(StorageKey.GalleryCalloutDismissed, true);
- container.openGallery();
- },
- };
-
- return (
-
-
-
- New gallery
-
-
- Sample notebooks are now combined in gallery. View and try out samples provided by Microsoft and other
- contributors.
-
- Open gallery
-
-
- );
- };
-
- const buildNotebooksTree = (): TreeNode => {
- const notebooksTree: TreeNode = {
- label: undefined,
- isExpanded: true,
- children: [],
- };
-
- if (!useNotebook.getState().isPhoenixNotebooks) {
- notebooksTree.children.push(buildNotebooksTemporarilyDownTree());
- } else {
- if (galleryContentRoot) {
- notebooksTree.children.push(buildGalleryNotebooksTree());
- }
-
- if (
- myNotebooksContentRoot &&
- useNotebook.getState().isPhoenixNotebooks &&
- useNotebook.getState().connectionInfo.status === ConnectionStatusType.Connected
- ) {
- notebooksTree.children.push(buildMyNotebooksTree());
- }
- if (container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
- // collapse all other notebook nodes
- notebooksTree.children.forEach((node) => (node.isExpanded = false));
- notebooksTree.children.push(buildGitHubNotebooksTree(true));
- }
- }
- return notebooksTree;
- };
-
- const buildNotebooksTemporarilyDownTree = (): TreeNode => {
- return {
- label: Notebook.temporarilyDownMsg,
- className: "clickDisabled",
- };
- };
-
- const buildGalleryNotebooksTree = (): TreeNode => {
- return {
- label: "Gallery",
- iconSrc: GalleryIcon,
- className: "notebookHeader galleryHeader",
- onClick: () => container.openGallery(),
- isSelected: () => activeTab?.tabKind === ViewModels.CollectionTabKind.Gallery,
- };
- };
-
- const buildMyNotebooksTree = (): TreeNode => {
- const myNotebooksTree: TreeNode = buildNotebookDirectoryNode(
- myNotebooksContentRoot,
- (item: NotebookContentItem) => {
- container.openNotebook(item);
- },
- );
-
- myNotebooksTree.isExpanded = true;
- myNotebooksTree.isAlphaSorted = true;
- // Remove "Delete" menu item from context menu
- myNotebooksTree.contextMenu = myNotebooksTree.contextMenu.filter((menuItem) => menuItem.label !== "Delete");
- return myNotebooksTree;
- };
-
- const buildGitHubNotebooksTree = (isConnected: boolean): TreeNode => {
- const gitHubNotebooksTree: TreeNode = buildNotebookDirectoryNode(
- gitHubNotebooksContentRoot,
- (item: NotebookContentItem) => {
- container.openNotebook(item);
- },
- true,
- );
- const manageGitContextMenu: TreeNodeMenuItem[] = [
- {
- label: "Manage GitHub settings",
- onClick: () =>
- useSidePanel
- .getState()
- .openSidePanel(
- "Manage GitHub settings",
- ,
- ),
- },
- {
- label: "Disconnect from GitHub",
- onClick: () => {
- TelemetryProcessor.trace(Action.NotebooksGitHubDisconnect, ActionModifiers.Mark, {
- dataExplorerArea: Areas.Notebook,
- });
- container.notebookManager?.gitHubOAuthService.logout();
- },
- },
- ];
- gitHubNotebooksTree.contextMenu = manageGitContextMenu;
- gitHubNotebooksTree.isExpanded = true;
- gitHubNotebooksTree.isAlphaSorted = true;
-
- return gitHubNotebooksTree;
- };
-
const buildChildNodes = (
item: NotebookContentItem,
onFileClick: (item: NotebookContentItem) => void,
@@ -373,16 +221,6 @@ export const ResourceTree: React.FC = ({ container }: Resourc
iconSrc: NewNotebookIcon,
onClick: () => container.onCreateDirectory(item, isGithubTree),
},
- {
- label: "New Notebook",
- iconSrc: NewNotebookIcon,
- onClick: () => container.onNewNotebookClicked(item, isGithubTree),
- },
- {
- label: "Upload File",
- iconSrc: NewNotebookIcon,
- onClick: () => container.openUploadFilePanel(item),
- },
];
//disallow renaming of temporary notebook workspace
@@ -786,12 +624,7 @@ export const ResourceTree: React.FC = ({ container }: Resourc
-
-
-
-
- {/* {buildGalleryCallout()} */}
>
)}
{!isNotebookEnabled && isSampleDataEnabled && (
@@ -804,8 +637,6 @@ export const ResourceTree: React.FC = ({ container }: Resourc
-
- {/* {buildGalleryCallout()} */}
>
)}
{isNotebookEnabled && isSampleDataEnabled && (
@@ -817,12 +648,7 @@ export const ResourceTree: React.FC = ({ container }: Resourc
-
-
-
-
- {/* {buildGalleryCallout()} */}
>
)}
>
diff --git a/src/Explorer/Tree/ResourceTreeAdapter.tsx b/src/Explorer/Tree/ResourceTreeAdapter.tsx
index abe05e85f..2fd8d6bd1 100644
--- a/src/Explorer/Tree/ResourceTreeAdapter.tsx
+++ b/src/Explorer/Tree/ResourceTreeAdapter.tsx
@@ -1,9 +1,7 @@
-import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react";
import { getItemName } from "Utils/APITypeUtils";
import * as ko from "knockout";
import * as React from "react";
import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg";
-import GalleryIcon from "../../../images/GalleryIcon.svg";
import DeleteIcon from "../../../images/delete.svg";
import CopyIcon from "../../../images/notebook/Notebook-copy.svg";
import NewNotebookIcon from "../../../images/notebook/Notebook-new.svg";
@@ -13,21 +11,17 @@ import PublishIcon from "../../../images/notebook/publish_content.svg";
import RefreshIcon from "../../../images/refresh-cosmos.svg";
import CollectionIcon from "../../../images/tree-collection.svg";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
-import { Areas } from "../../Common/Constants";
import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import { IPinnedRepo } from "../../Juno/JunoClient";
-import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import { isServerlessAccount } from "../../Utils/CapabilityUtils";
import * as GitHubUtils from "../../Utils/GitHubUtils";
-import { useSidePanel } from "../../hooks/useSidePanel";
import { useTabs } from "../../hooks/useTabs";
import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFactory";
-import { AccordionComponent, AccordionItemComponent } from "../Controls/Accordion/AccordionComponent";
import { useDialog } from "../Controls/Dialog";
import { TreeComponent, TreeNode, TreeNodeMenuItem } from "../Controls/TreeComponent/TreeComponent";
import Explorer from "../Explorer";
@@ -36,7 +30,6 @@ import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity";
import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem";
import { NotebookUtil } from "../Notebook/NotebookUtil";
import { useNotebook } from "../Notebook/useNotebook";
-import { GitHubReposPanel } from "../Panes/GitHubReposPanel/GitHubReposPanel";
import TabsBase from "../Tabs/TabsBase";
import { useDatabases } from "../useDatabases";
import { useSelectedNode } from "../useSelectedNode";
@@ -102,26 +95,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
public renderComponent(): JSX.Element {
const dataRootNode = this.buildDataTree();
- const notebooksRootNode = this.buildNotebooksTrees();
-
- if (useNotebook.getState().isNotebookEnabled) {
- return (
- <>
-
-
-
-
-
-
-
-
-
- {/* {this.galleryContentRoot && this.buildGalleryCallout()} */}
- >
- );
- } else {
- return ;
- }
+ return ;
}
public async initialize(): Promise {
@@ -504,156 +478,6 @@ export class ResourceTreeAdapter implements ReactAdapter {
return traverse(schema);
}
- private buildNotebooksTrees(): TreeNode {
- let notebooksTree: TreeNode = {
- label: undefined,
- isExpanded: true,
- children: [],
- };
-
- if (this.galleryContentRoot) {
- notebooksTree.children.push(this.buildGalleryNotebooksTree());
- }
-
- if (this.myNotebooksContentRoot) {
- notebooksTree.children.push(this.buildMyNotebooksTree());
- }
-
- if (this.gitHubNotebooksContentRoot) {
- // collapse all other notebook nodes
- notebooksTree.children.forEach((node) => (node.isExpanded = false));
- notebooksTree.children.push(this.buildGitHubNotebooksTree());
- }
-
- return notebooksTree;
- }
-
- private buildGalleryCallout(): JSX.Element {
- if (
- LocalStorageUtility.hasItem(StorageKey.GalleryCalloutDismissed) &&
- LocalStorageUtility.getEntryBoolean(StorageKey.GalleryCalloutDismissed)
- ) {
- return undefined;
- }
-
- const calloutProps: ICalloutProps = {
- calloutMaxWidth: 350,
- ariaLabel: "New gallery",
- role: "alertdialog",
- gapSpace: 0,
- target: ".galleryHeader",
- directionalHint: DirectionalHint.leftTopEdge,
- onDismiss: () => {
- LocalStorageUtility.setEntryBoolean(StorageKey.GalleryCalloutDismissed, true);
- this.triggerRender();
- },
- setInitialFocus: true,
- };
-
- const openGalleryProps: ILinkProps = {
- onClick: () => {
- LocalStorageUtility.setEntryBoolean(StorageKey.GalleryCalloutDismissed, true);
- this.container.openGallery();
- this.triggerRender();
- },
- };
-
- return (
-
-
-
- New gallery
-
-
- Sample notebooks are now combined in gallery. View and try out samples provided by Microsoft and other
- contributors.
-
- Open gallery
-
-
- );
- }
-
- private buildGalleryNotebooksTree(): TreeNode {
- return {
- label: "Gallery",
- iconSrc: GalleryIcon,
- className: "notebookHeader galleryHeader",
- onClick: () => this.container.openGallery(),
- isSelected: () => {
- const activeTab = useTabs.getState().activeTab;
- return activeTab && activeTab.tabKind === ViewModels.CollectionTabKind.Gallery;
- },
- };
- }
-
- private buildMyNotebooksTree(): TreeNode {
- const myNotebooksTree: TreeNode = this.buildNotebookDirectoryNode(
- this.myNotebooksContentRoot,
- (item: NotebookContentItem) => {
- this.container.openNotebook(item).then((hasOpened) => {
- if (hasOpened) {
- mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item);
- }
- });
- },
- true,
- true,
- );
-
- myNotebooksTree.isExpanded = true;
- myNotebooksTree.isAlphaSorted = true;
- // Remove "Delete" menu item from context menu
- myNotebooksTree.contextMenu = myNotebooksTree.contextMenu.filter((menuItem) => menuItem.label !== "Delete");
- return myNotebooksTree;
- }
-
- private buildGitHubNotebooksTree(): TreeNode {
- const gitHubNotebooksTree: TreeNode = this.buildNotebookDirectoryNode(
- this.gitHubNotebooksContentRoot,
- (item: NotebookContentItem) => {
- this.container.openNotebook(item).then((hasOpened) => {
- if (hasOpened) {
- mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item);
- }
- });
- },
- true,
- true,
- );
-
- gitHubNotebooksTree.contextMenu = [
- {
- label: "Manage GitHub settings",
- onClick: () =>
- useSidePanel
- .getState()
- .openSidePanel(
- "Manage GitHub settings",
- ,
- ),
- },
- {
- label: "Disconnect from GitHub",
- onClick: () => {
- TelemetryProcessor.trace(Action.NotebooksGitHubDisconnect, ActionModifiers.Mark, {
- dataExplorerArea: Areas.Notebook,
- });
- this.container.notebookManager?.gitHubOAuthService.logout();
- },
- },
- ];
-
- gitHubNotebooksTree.isExpanded = true;
- gitHubNotebooksTree.isAlphaSorted = true;
-
- return gitHubNotebooksTree;
- }
-
private buildChildNodes(
item: NotebookContentItem,
onFileClick: (item: NotebookContentItem) => void,
@@ -800,16 +624,6 @@ export class ResourceTreeAdapter implements ReactAdapter {
iconSrc: NewNotebookIcon,
onClick: () => this.container.onCreateDirectory(item),
},
- {
- label: "New Notebook",
- iconSrc: NewNotebookIcon,
- onClick: () => this.container.onNewNotebookClicked(item),
- },
- {
- label: "Upload File",
- iconSrc: NewNotebookIcon,
- onClick: () => this.container.openUploadFilePanel(item),
- },
];
//disallow renaming of temporary notebook workspace
diff --git a/src/Explorer/useDatabases.ts b/src/Explorer/useDatabases.ts
index 7701f1360..4d1155692 100644
--- a/src/Explorer/useDatabases.ts
+++ b/src/Explorer/useDatabases.ts
@@ -68,7 +68,9 @@ export const useDatabases: UseStore = create((set, get) => ({
return true;
},
findDatabaseWithId: (databaseId: string, isSampleDatabase?: boolean) => {
- return get().databases.find((db) => databaseId === db.id() && db.isSampleDB === isSampleDatabase);
+ return isSampleDatabase === undefined
+ ? get().databases.find((db) => databaseId === db.id())
+ : get().databases.find((db) => databaseId === db.id() && db.isSampleDB === isSampleDatabase);
},
isLastNonEmptyDatabase: () => {
const databases = get().databases;
diff --git a/src/HostedExplorer.tsx b/src/HostedExplorer.tsx
index 1eb2f5711..6f9c62866 100644
--- a/src/HostedExplorer.tsx
+++ b/src/HostedExplorer.tsx
@@ -1,5 +1,6 @@
import { initializeIcons } from "@fluentui/react";
import { useBoolean } from "@fluentui/react-hooks";
+import { AadAuthorizationFailure } from "Platform/Hosted/Components/AadAuthorizationFailure";
import * as React from "react";
import { render } from "react-dom";
import ChevronRight from "../images/chevron-right.svg";
@@ -32,7 +33,8 @@ const App: React.FunctionComponent = () => {
// For showing/hiding panel
const [isOpen, { setTrue: openPanel, setFalse: dismissPanel }] = useBoolean(false);
const config = useConfig();
- const { isLoggedIn, armToken, graphToken, account, tenantId, logout, login, switchTenant } = useAADAuth();
+ const { isLoggedIn, armToken, graphToken, account, tenantId, logout, login, switchTenant, authFailure } =
+ useAADAuth();
const [databaseAccount, setDatabaseAccount] = React.useState();
const [authType, setAuthType] = React.useState(encryptedToken ? AuthType.EncryptedToken : undefined);
const [connectionString, setConnectionString] = React.useState();
@@ -136,7 +138,10 @@ const App: React.FunctionComponent = () => {
{!isLoggedIn && !encryptedTokenMetadata && (
)}
- {isLoggedIn && }
+ {isLoggedIn && authFailure && }
+ {isLoggedIn && !authFailure && (
+
+ )}
>
);
};
diff --git a/src/Juno/JunoClient.ts b/src/Juno/JunoClient.ts
index 7cef27da1..0b0618e72 100644
--- a/src/Juno/JunoClient.ts
+++ b/src/Juno/JunoClient.ts
@@ -1,6 +1,6 @@
-import ko from "knockout";
-import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointValidation";
+import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointUtils";
import { GetGithubClientId } from "Utils/GitHubUtils";
+import ko from "knockout";
import { HttpHeaders, HttpStatusCodes } from "../Common/Constants";
import { configContext } from "../ConfigContext";
import * as DataModels from "../Contracts/DataModels";
diff --git a/src/KeyboardShortcuts.tsx b/src/KeyboardShortcuts.tsx
new file mode 100644
index 000000000..2041662ee
--- /dev/null
+++ b/src/KeyboardShortcuts.tsx
@@ -0,0 +1,183 @@
+import * as React from "react";
+import { PropsWithChildren, useEffect } from "react";
+import { KeyBindingMap, tinykeys } from "tinykeys";
+import create, { UseStore } from "zustand";
+
+/**
+ * Represents a keyboard shortcut handler.
+ * Return `true` to prevent the default action of the keyboard shortcut.
+ * Any other return value will allow the default action to proceed.
+ */
+export type KeyboardActionHandler = (e: KeyboardEvent) => boolean | void;
+
+export type KeyboardHandlerMap = Partial>;
+
+/**
+ * The groups of keyboard actions that can be managed by the application.
+ * Each group can be updated separately, but, when updated, must be completely replaced.
+ */
+export enum KeyboardActionGroup {
+ /** Keyboard actions related to tab navigation. */
+ TABS = "TABS",
+
+ /** Keyboard actions managed by the global command bar. */
+ COMMAND_BAR = "COMMAND_BAR",
+
+ /**
+ * Keyboard actions specific to the active tab.
+ * This group is automatically cleared when the active tab changes.
+ */
+ ACTIVE_TAB = "ACTIVE_TAB",
+}
+
+/**
+ * The possible actions that can be triggered by keyboard shortcuts.
+ */
+export enum KeyboardAction {
+ NEW_QUERY = "NEW_QUERY",
+ EXECUTE_ITEM = "EXECUTE_ITEM",
+ CANCEL_OR_DISCARD = "CANCEL_OR_DISCARD",
+ SAVE_ITEM = "SAVE_ITEM",
+ DOWNLOAD_ITEM = "DOWNLOAD_ITEM",
+ OPEN_QUERY = "OPEN_QUERY",
+ OPEN_QUERY_FROM_DISK = "OPEN_QUERY_FROM_DISK",
+ NEW_SPROC = "NEW_SPROC",
+ NEW_UDF = "NEW_UDF",
+ NEW_TRIGGER = "NEW_TRIGGER",
+ NEW_DATABASE = "NEW_DATABASE",
+ NEW_COLLECTION = "NEW_CONTAINER",
+ NEW_ITEM = "NEW_ITEM",
+ DELETE_ITEM = "DELETE_ITEM",
+ TOGGLE_COPILOT = "TOGGLE_COPILOT",
+ SELECT_LEFT_TAB = "SELECT_LEFT_TAB",
+ SELECT_RIGHT_TAB = "SELECT_RIGHT_TAB",
+ CLOSE_TAB = "CLOSE_TAB",
+ SEARCH = "SEARCH",
+ CLEAR_SEARCH = "CLEAR_SEARCH",
+}
+
+/**
+ * The keyboard shortcuts for the application.
+ * This record maps each action to the keyboard shortcuts that trigger the action.
+ * Even if an action is specified here, it will not be triggered unless a handler is set for it.
+ */
+const bindings: Record = {
+ // NOTE: The "$mod" special value is used to represent the "Control" key on Windows/Linux and the "Command" key on macOS.
+ // See https://www.npmjs.com/package/tinykeys#commonly-used-keys-and-codes for more information on the expected values for keyboard shortcuts.
+
+ [KeyboardAction.NEW_QUERY]: ["$mod+J", "Alt+N Q"],
+ [KeyboardAction.EXECUTE_ITEM]: ["Shift+Enter", "F5"],
+ [KeyboardAction.CANCEL_OR_DISCARD]: ["Escape"],
+ [KeyboardAction.SAVE_ITEM]: ["$mod+S"],
+ [KeyboardAction.DOWNLOAD_ITEM]: ["$mod+Shift+S"],
+ [KeyboardAction.OPEN_QUERY]: ["$mod+O"],
+ [KeyboardAction.OPEN_QUERY_FROM_DISK]: ["$mod+Shift+O"],
+ [KeyboardAction.NEW_SPROC]: ["Alt+N P"],
+ [KeyboardAction.NEW_UDF]: ["Alt+N F"],
+ [KeyboardAction.NEW_TRIGGER]: ["Alt+N T"],
+ [KeyboardAction.NEW_DATABASE]: ["Alt+N D"],
+ [KeyboardAction.NEW_COLLECTION]: ["Alt+N C"],
+ [KeyboardAction.NEW_ITEM]: ["Alt+N I"],
+ [KeyboardAction.DELETE_ITEM]: ["Alt+D"],
+ [KeyboardAction.TOGGLE_COPILOT]: ["$mod+P"],
+ [KeyboardAction.SELECT_LEFT_TAB]: ["$mod+Alt+[", "$mod+Shift+F6"],
+ [KeyboardAction.SELECT_RIGHT_TAB]: ["$mod+Alt+]", "$mod+F6"],
+ [KeyboardAction.CLOSE_TAB]: ["$mod+Alt+W"],
+ [KeyboardAction.SEARCH]: ["$mod+Shift+F"],
+ [KeyboardAction.CLEAR_SEARCH]: ["$mod+Shift+C"],
+};
+
+interface KeyboardShortcutState {
+ /**
+ * A set of all the keyboard shortcuts handlers.
+ */
+ allHandlers: KeyboardHandlerMap;
+
+ /**
+ * A set of all the groups of keyboard shortcuts handlers.
+ */
+ groups: Partial>;
+
+ /**
+ * Sets the keyboard shortcut handlers for the given group.
+ */
+ setHandlers: (group: KeyboardActionGroup, handlers: KeyboardHandlerMap) => void;
+}
+
+export type KeyboardHandlerSetter = (handlers: KeyboardHandlerMap) => void;
+
+/**
+ * Defines the calling component as the manager of the keyboard actions for the given group.
+ * @param group The group of keyboard actions to manage.
+ * @returns A function that can be used to set the keyboard action handlers for the given group.
+ */
+export const useKeyboardActionGroup: (group: KeyboardActionGroup) => KeyboardHandlerSetter =
+ (group: KeyboardActionGroup) => (handlers: KeyboardHandlerMap) =>
+ useKeyboardActionHandlers.getState().setHandlers(group, handlers);
+
+/**
+ * Clears the keyboard action handlers for the given group.
+ * @param group The group of keyboard actions to clear.
+ */
+export const clearKeyboardActionGroup = (group: KeyboardActionGroup) => {
+ useKeyboardActionHandlers.getState().setHandlers(group, {});
+};
+
+const useKeyboardActionHandlers: UseStore = create((set, get) => ({
+ allHandlers: {},
+ groups: {},
+ setHandlers: (group: KeyboardActionGroup, handlers: KeyboardHandlerMap) => {
+ const state = get();
+ const groups = { ...state.groups, [group]: handlers };
+
+ // Combine all the handlers from all the groups in the correct order.
+ const allHandlers: KeyboardHandlerMap = {};
+ eachKey(groups).forEach((group) => {
+ const groupHandlers = groups[group];
+ if (groupHandlers) {
+ eachKey(groupHandlers).forEach((action) => {
+ // Check for duplicate handlers in development mode.
+ // We don't want to raise an error here in production, but having duplicate handlers is a mistake.
+ if (process.env.NODE_ENV === "development" && allHandlers[action]) {
+ throw new Error(`Duplicate handler for Keyboard Action "${action}".`);
+ }
+ allHandlers[action] = groupHandlers[action];
+ });
+ }
+ });
+ set({ groups, allHandlers });
+ },
+}));
+
+function createHandler(action: KeyboardAction): KeyboardActionHandler {
+ return (e) => {
+ const state = useKeyboardActionHandlers.getState();
+ const handler = state.allHandlers[action];
+ if (handler && handler(e)) {
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ };
+}
+
+const allHandlers: KeyBindingMap = {};
+eachKey(bindings).forEach((action) => {
+ const shortcuts = bindings[action];
+ shortcuts.forEach((shortcut) => {
+ allHandlers[shortcut] = createHandler(action);
+ });
+});
+
+export function KeyboardShortcutRoot({ children }: PropsWithChildren) {
+ useEffect(() => {
+ // We bind to the body because Fluent UI components sometimes shift focus to the body, which is above the root React component.
+ tinykeys(document.body, allHandlers);
+ }, []);
+
+ return <>{children}>;
+}
+
+/** A _typed_ version of `Object.keys` that preserves the original key type */
+function eachKey(record: Partial>): K[] {
+ return Object.keys(record) as K[];
+}
diff --git a/src/Main.tsx b/src/Main.tsx
index d62f9b45e..f79ee29f1 100644
--- a/src/Main.tsx
+++ b/src/Main.tsx
@@ -1,3 +1,6 @@
+// Import this first, to ensure that the dev tools hook is copied before React is loaded.
+import "./ReactDevTools";
+
// CSS Dependencies
import { initializeIcons, loadTheme } from "@fluentui/react";
import { QuickstartCarousel } from "Explorer/Quickstart/QuickstartCarousel";
@@ -18,6 +21,7 @@ import "../externals/jquery.typeahead.min.js";
// Image Dependencies
import { Platform } from "ConfigContext";
import { QueryCopilotCarousel } from "Explorer/QueryCopilot/CopilotCarousel";
+import { KeyboardShortcutRoot } from "KeyboardShortcuts";
import "../images/CosmosDB_rgb_ui_lighttheme.ico";
import hdeConnectImage from "../images/HdeConnectCosmosDB.svg";
import "../images/favicon.ico";
@@ -88,52 +92,54 @@ const App: React.FunctionComponent = () => {
}
return (
-
-
-
- {/* Main Command Bar - Start */}
-
- {/* Collections Tree and Tabs - Begin */}
-
- {/* Collections Tree - Start */}
- {userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo" && (
-
-
- {/* Collections Tree Expanded - Start */}
-
- {/* Collections Tree Expanded - End */}
- {/* Collections Tree Collapsed - Start */}
-
- {/* Collections Tree Collapsed - End */}
+
+
+
+
+ {/* Main Command Bar - Start */}
+
+ {/* Collections Tree and Tabs - Begin */}
+
+ {/* Collections Tree - Start */}
+ {userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo" && (
+
+
+ {/* Collections Tree Expanded - Start */}
+
+ {/* Collections Tree Expanded - End */}
+ {/* Collections Tree Collapsed - Start */}
+
+ {/* Collections Tree Collapsed - End */}
+
-
- )}
-
-
- {/* Collections Tree and Tabs - End */}
-
-
+ )}
+
+
+ {/* Collections Tree and Tabs - End */}
+
+
+
+
+
+ { }
+ { }
+ { }
+ { }
-
-
- {
}
- {
}
- {
}
- {
}
-
+
);
};
diff --git a/src/Phoenix/PhoenixClient.ts b/src/Phoenix/PhoenixClient.ts
index 685bbbbb2..d5e304b8e 100644
--- a/src/Phoenix/PhoenixClient.ts
+++ b/src/Phoenix/PhoenixClient.ts
@@ -2,7 +2,7 @@ import { configContext } from "ConfigContext";
import { useDialog } from "Explorer/Controls/Dialog";
import { Action } from "Shared/Telemetry/TelemetryConstants";
import { userContext } from "UserContext";
-import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointValidation";
+import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointUtils";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import promiseRetry, { AbortError } from "p-retry";
import {
diff --git a/src/Platform/Fabric/FabricUtil.ts b/src/Platform/Fabric/FabricUtil.ts
index 53c38f988..26ba859ff 100644
--- a/src/Platform/Fabric/FabricUtil.ts
+++ b/src/Platform/Fabric/FabricUtil.ts
@@ -29,7 +29,11 @@ const requestDatabaseResourceTokens = async (): Promise
=> {
}
updateUserContext({
- fabricContext: { ...userContext.fabricContext, databaseConnectionInfo: fabricDatabaseConnectionInfo },
+ fabricContext: {
+ ...userContext.fabricContext,
+ databaseConnectionInfo: fabricDatabaseConnectionInfo,
+ isReadOnly: true,
+ },
databaseAccount: { ...userContext.databaseAccount },
});
scheduleRefreshDatabaseResourceToken();
diff --git a/src/Platform/Hosted/AadAuthorizationFailure.less b/src/Platform/Hosted/AadAuthorizationFailure.less
new file mode 100644
index 000000000..696a8337b
--- /dev/null
+++ b/src/Platform/Hosted/AadAuthorizationFailure.less
@@ -0,0 +1,52 @@
+.aadAuthFailureContainer {
+ height: 100%;
+ width: 100%;
+}
+.aadAuthFailureContainer .aadAuthFailureFormContainer {
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: -ms-flex;
+ display: flex;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ height: 100%;
+ width: 100%;
+}
+.aadAuthFailureContainer .aadAuthFailure {
+ text-align: center;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: -ms-flex;
+ display: flex;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ justify-content: center;
+ height: 100%;
+ margin-bottom: 60px;
+}
+.aadAuthFailureContainer .aadAuthFailure .authFailureTitle {
+ font-size: 16px;
+ font-weight: 500;
+ color: #d12d2d;
+ margin: 16px 8px 8px 8px;
+}
+.aadAuthFailureContainer .aadAuthFailure .authFailureMessage {
+ font-size: 14px;
+ color: #393939;
+ margin: 16px 16px 16px 16px;
+ word-wrap: break-word;
+ white-space: pre-wrap;
+}
+.aadAuthFailureContainer .aadAuthFailure .authFailureLink {
+ margin: 8px;
+ font-size: 14px;
+ color: #0058ad;
+ cursor: pointer;
+}
+
+.aadAuthFailureContainer .aadAuthFailure .aadAuthFailureContent {
+ margin: 8px;
+ color: #393939;
+}
diff --git a/src/Platform/Hosted/Components/AadAuthorizationFailure.tsx b/src/Platform/Hosted/Components/AadAuthorizationFailure.tsx
new file mode 100644
index 000000000..00d360a2e
--- /dev/null
+++ b/src/Platform/Hosted/Components/AadAuthorizationFailure.tsx
@@ -0,0 +1,29 @@
+import { AadAuthFailure } from "hooks/useAADAuth";
+import * as React from "react";
+import ConnectImage from "../../../../images/HdeConnectCosmosDB.svg";
+import "../AadAuthorizationFailure.less";
+
+interface Props {
+ authFailure: AadAuthFailure;
+}
+
+export const AadAuthorizationFailure: React.FunctionComponent = ({ authFailure }: Props) => {
+ return (
+
+
+
+
+
+
+
Authorization Failure
+
{authFailure.failureMessage}
+ {authFailure.failureLinkTitle && (
+
+ {authFailure.failureLinkTitle}
+
+ )}
+
+
+
+ );
+};
diff --git a/src/Platform/Hosted/Components/ConnectExplorer.tsx b/src/Platform/Hosted/Components/ConnectExplorer.tsx
index dd291f279..749f248ba 100644
--- a/src/Platform/Hosted/Components/ConnectExplorer.tsx
+++ b/src/Platform/Hosted/Components/ConnectExplorer.tsx
@@ -1,10 +1,11 @@
import { useBoolean } from "@fluentui/react-hooks";
import { userContext } from "UserContext";
+import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils";
import * as React from "react";
import ConnectImage from "../../../../images/HdeConnectCosmosDB.svg";
import ErrorImage from "../../../../images/error.svg";
import { AuthType } from "../../../AuthType";
-import { HttpHeaders } from "../../../Common/Constants";
+import { BackendApi, HttpHeaders } from "../../../Common/Constants";
import { configContext } from "../../../ConfigContext";
import { GenerateTokenResponse } from "../../../Contracts/DataModels";
import { isResourceTokenConnectionString } from "../Helpers/ResourceTokenUtils";
@@ -18,6 +19,23 @@ interface Props {
}
export const fetchEncryptedToken = async (connectionString: string): Promise => {
+ if (!useNewPortalBackendEndpoint(BackendApi.GenerateToken)) {
+ return await fetchEncryptedToken_ToBeDeprecated(connectionString);
+ }
+
+ const headers = new Headers();
+ headers.append(HttpHeaders.connectionString, connectionString);
+ const url = configContext.PORTAL_BACKEND_ENDPOINT + "/api/connectionstring/token/generatetoken";
+ const response = await fetch(url, { headers, method: "POST" });
+ if (!response.ok) {
+ throw response;
+ }
+
+ const encryptedTokenResponse: string = await response.json();
+ return decodeURIComponent(encryptedTokenResponse);
+};
+
+export const fetchEncryptedToken_ToBeDeprecated = async (connectionString: string): Promise => {
const headers = new Headers();
headers.append(HttpHeaders.connectionString, connectionString);
const url = configContext.BACKEND_ENDPOINT + "/api/guest/tokens/generateToken";
diff --git a/src/Platform/Hosted/extractFeatures.ts b/src/Platform/Hosted/extractFeatures.ts
index 7bf3c8a3f..5bd84516e 100644
--- a/src/Platform/Hosted/extractFeatures.ts
+++ b/src/Platform/Hosted/extractFeatures.ts
@@ -31,11 +31,6 @@ export type Features = {
readonly mongoProxyAPIs?: string;
readonly enableThroughputCap: boolean;
readonly enableHierarchicalKeys: boolean;
- readonly enableLegacyMongoShellV1: boolean;
- readonly enableLegacyMongoShellV1Debug: boolean;
- readonly enableLegacyMongoShellV2: boolean;
- readonly enableLegacyMongoShellV2Debug: boolean;
- readonly loadLegacyMongoShellFromBE: boolean;
readonly enableCopilot: boolean;
readonly copilotVersion?: string;
readonly disableCopilotPhoenixGateaway: boolean;
@@ -106,11 +101,6 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
notebooksDownBanner: "true" === get("notebooksDownBanner"),
enableThroughputCap: "true" === get("enablethroughputcap"),
enableHierarchicalKeys: "true" === get("enablehierarchicalkeys"),
- enableLegacyMongoShellV1: "true" === get("enablelegacymongoshellv1"),
- enableLegacyMongoShellV1Debug: "true" === get("enablelegacymongoshellv1debug"),
- enableLegacyMongoShellV2: "true" === get("enablelegacymongoshellv2"),
- enableLegacyMongoShellV2Debug: "true" === get("enablelegacymongoshellv2debug"),
- loadLegacyMongoShellFromBE: "true" === get("loadlegacymongoshellfrombe"),
enableCopilot: "true" === get("enablecopilot", "true"),
copilotVersion: get("copilotversion") ?? "v2.0",
disableCopilotPhoenixGateaway: "true" === get("disablecopilotphoenixgateaway"),
diff --git a/src/ReactDevTools.ts b/src/ReactDevTools.ts
index 09947f934..2a12d81d5 100644
--- a/src/ReactDevTools.ts
+++ b/src/ReactDevTools.ts
@@ -1,3 +1,7 @@
if (window.parent !== window) {
- (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__ = (window.parent as any).__REACT_DEVTOOLS_GLOBAL_HOOK__;
+ try {
+ (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__ = (window.parent as any).__REACT_DEVTOOLS_GLOBAL_HOOK__;
+ } catch {
+ // No-op. We can throw here if the parent is not the same origin (such as in the Azure portal).
+ }
}
diff --git a/src/Shared/Constants.ts b/src/Shared/Constants.ts
index 1c2e91cfb..d26a5700c 100644
--- a/src/Shared/Constants.ts
+++ b/src/Shared/Constants.ts
@@ -172,7 +172,6 @@ export class CollectionCreation {
public static readonly DefaultCollectionRUs100K: number = 100000;
public static readonly DefaultCollectionRUs1Million: number = 1000000;
- public static readonly DefaultAddCollectionDefaultFlight: string = "0";
public static readonly DefaultSubscriptionType: SubscriptionType = SubscriptionType.Free;
public static readonly TablesAPIDefaultDatabase: string = "TablesDB";
diff --git a/src/Shared/StorageUtility.ts b/src/Shared/StorageUtility.ts
index ca7bc81af..b229ac7db 100644
--- a/src/Shared/StorageUtility.ts
+++ b/src/Shared/StorageUtility.ts
@@ -1,9 +1,12 @@
import * as LocalStorageUtility from "./LocalStorageUtility";
import * as SessionStorageUtility from "./SessionStorageUtility";
+import * as StringUtility from "./StringUtility";
export { LocalStorageUtility, SessionStorageUtility };
export enum StorageKey {
ActualItemPerPage,
+ RUThresholdEnabled,
+ RUThreshold,
QueryTimeoutEnabled,
QueryTimeout,
RetryAttempts,
@@ -25,3 +28,27 @@ export enum StorageKey {
VisitedAccounts,
PriorityLevel,
}
+
+export const hasRUThresholdBeenConfigured = (): boolean => {
+ const ruThresholdEnabledLocalStorageRaw: string | null = LocalStorageUtility.getEntryString(
+ StorageKey.RUThresholdEnabled,
+ );
+ return ruThresholdEnabledLocalStorageRaw === "true" || ruThresholdEnabledLocalStorageRaw === "false";
+};
+
+export const ruThresholdEnabled = (): boolean => {
+ const ruThresholdEnabledLocalStorageRaw: string | null = LocalStorageUtility.getEntryString(
+ StorageKey.RUThresholdEnabled,
+ );
+ return ruThresholdEnabledLocalStorageRaw === null || StringUtility.toBoolean(ruThresholdEnabledLocalStorageRaw);
+};
+
+export const getRUThreshold = (): number => {
+ const ruThresholdRaw = LocalStorageUtility.getEntryNumber(StorageKey.RUThreshold);
+ if (ruThresholdRaw !== 0) {
+ return ruThresholdRaw;
+ }
+ return DefaultRUThreshold;
+};
+
+export const DefaultRUThreshold = 5000;
diff --git a/src/UserContext.ts b/src/UserContext.ts
index d4b64f422..2fa1bb946 100644
--- a/src/UserContext.ts
+++ b/src/UserContext.ts
@@ -50,8 +50,25 @@ export interface VCoreMongoConnectionParams {
interface FabricContext {
connectionId: string;
databaseConnectionInfo: FabricDatabaseConnectionInfo | undefined;
+ isReadOnly: boolean;
+ isVisible: boolean;
}
+export type AdminFeedbackControlPolicy =
+ | "connectedExperiences"
+ | "policyAllowFeedback"
+ | "policyAllowSurvey"
+ | "policyAllowScreenshot"
+ | "policyAllowContact"
+ | "policyAllowContent"
+ | "policyEmailCollectionDefault"
+ | "policyScreenshotDefault"
+ | "policyContentSamplesDefault";
+
+export type AdminFeedbackPolicySettings = {
+ [key in AdminFeedbackControlPolicy]: boolean;
+};
+
interface UserContext {
readonly fabricContext?: FabricContext;
readonly authType?: AuthType;
@@ -72,7 +89,6 @@ interface UserContext {
readonly isTryCosmosDBSubscription?: boolean;
readonly portalEnv?: PortalEnv;
readonly features: Features;
- readonly addCollectionFlight: string;
readonly hasWriteAccess: boolean;
readonly parsedResourceToken?: {
databaseId: string;
@@ -84,10 +100,11 @@ interface UserContext {
collectionCreationDefaults: CollectionCreationDefaults;
sampleDataConnectionInfo?: ParsedResourceTokenConnectionString;
readonly vcoreMongoConnectionParams?: VCoreMongoConnectionParams;
+ readonly feedbackPolicies?: AdminFeedbackPolicySettings;
}
export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres" | "VCoreMongo";
-export type PortalEnv = "localhost" | "blackforest" | "fairfax" | "mooncake" | "prod" | "dev";
+export type PortalEnv = "localhost" | "blackforest" | "fairfax" | "mooncake" | "prod1" | "rx" | "ex" | "prod" | "dev";
const ONE_WEEK_IN_MS = 604800000;
@@ -99,7 +116,6 @@ const userContext: UserContext = {
isTryCosmosDBSubscription: false,
portalEnv: "prod",
features,
- addCollectionFlight: CollectionCreation.DefaultAddCollectionDefaultFlight,
subscriptionType: CollectionCreation.DefaultSubscriptionType,
collectionCreationDefaults: CollectionCreationDefaults,
};
diff --git a/src/Utils/AuthorizationUtils.ts b/src/Utils/AuthorizationUtils.ts
index 7fe1709c0..35fd4ed39 100644
--- a/src/Utils/AuthorizationUtils.ts
+++ b/src/Utils/AuthorizationUtils.ts
@@ -1,9 +1,11 @@
import * as msal from "@azure/msal-browser";
+import { Action } from "Shared/Telemetry/TelemetryConstants";
import { AuthType } from "../AuthType";
import * as Constants from "../Common/Constants";
import * as Logger from "../Common/Logger";
import { configContext } from "../ConfigContext";
import * as ViewModels from "../Contracts/ViewModels";
+import { traceFailure } from "../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../UserContext";
export function getAuthorizationHeader(): ViewModels.AuthorizationTokenHeaderMetadata {
@@ -43,8 +45,8 @@ export function decryptJWTToken(token: string) {
return JSON.parse(tokenPayload);
}
-export function getMsalInstance() {
- const config: msal.Configuration = {
+export async function getMsalInstance() {
+ const msalConfig: msal.Configuration = {
cache: {
cacheLocation: "localStorage",
},
@@ -55,8 +57,46 @@ export function getMsalInstance() {
};
if (process.env.NODE_ENV === "development") {
- config.auth.redirectUri = "https://dataexplorer-dev.azurewebsites.net";
+ msalConfig.auth.redirectUri = "https://dataexplorer-dev.azurewebsites.net";
}
- const msalInstance = new msal.PublicClientApplication(config);
+
+ const msalInstance = new msal.PublicClientApplication(msalConfig);
return msalInstance;
}
+
+export async function acquireTokenWithMsal(msalInstance: msal.IPublicClientApplication, request: msal.SilentRequest) {
+ const tokenRequest = {
+ account: msalInstance.getActiveAccount() || null,
+ ...request,
+ };
+
+ try {
+ // attempt silent acquisition first
+ return (await msalInstance.acquireTokenSilent(tokenRequest)).accessToken;
+ } catch (silentError) {
+ if (silentError instanceof msal.InteractionRequiredAuthError) {
+ try {
+ // The error indicates that we need to acquire the token interactively.
+ // This will display a pop-up to re-establish authorization. If user does not
+ // have pop-ups enabled in their browser, this will fail.
+ return (await msalInstance.acquireTokenPopup(tokenRequest)).accessToken;
+ } catch (interactiveError) {
+ traceFailure(Action.SignInAad, {
+ request: JSON.stringify(tokenRequest),
+ acquireTokenType: "interactive",
+ errorMessage: JSON.stringify(interactiveError),
+ });
+
+ throw interactiveError;
+ }
+ } else {
+ traceFailure(Action.SignInAad, {
+ request: JSON.stringify(tokenRequest),
+ acquireTokenType: "silent",
+ errorMessage: JSON.stringify(silentError),
+ });
+
+ throw silentError;
+ }
+ }
+}
diff --git a/src/Utils/CapabilityUtils.ts b/src/Utils/CapabilityUtils.ts
index fc2de8149..8b6976666 100644
--- a/src/Utils/CapabilityUtils.ts
+++ b/src/Utils/CapabilityUtils.ts
@@ -9,4 +9,14 @@ export const isCapabilityEnabled = (capabilityName: string): boolean => {
return false;
};
-export const isServerlessAccount = (): boolean => isCapabilityEnabled(Constants.CapabilityNames.EnableServerless);
+export const isServerlessAccount = (): boolean => {
+ const { databaseAccount } = userContext;
+ return (
+ databaseAccount?.properties?.capacityMode === Constants.CapacityMode.Serverless ||
+ isCapabilityEnabled(Constants.CapabilityNames.EnableServerless)
+ );
+};
+
+export const isVectorSearchEnabled = (): boolean => {
+ return userContext.apiType === "SQL" && isCapabilityEnabled(Constants.CapabilityNames.EnableNoSQLVectorSearch);
+};
diff --git a/src/Utils/CloudUtils.ts b/src/Utils/CloudUtils.ts
index 089bbf0b6..2593aa4dc 100644
--- a/src/Utils/CloudUtils.ts
+++ b/src/Utils/CloudUtils.ts
@@ -1,9 +1,9 @@
import { userContext } from "../UserContext";
export function isRunningOnNationalCloud(): boolean {
- return (
- userContext.portalEnv === "blackforest" ||
- userContext.portalEnv === "fairfax" ||
- userContext.portalEnv === "mooncake"
- );
+ return !isRunningOnPublicCloud();
+}
+
+export function isRunningOnPublicCloud(): boolean {
+ return userContext?.portalEnv === "prod1" || userContext?.portalEnv === "prod";
}
diff --git a/src/Utils/EndpointValidation.ts b/src/Utils/EndpointUtils.ts
similarity index 56%
rename from src/Utils/EndpointValidation.ts
rename to src/Utils/EndpointUtils.ts
index 0f9eb44ef..97e733b98 100644
--- a/src/Utils/EndpointValidation.ts
+++ b/src/Utils/EndpointUtils.ts
@@ -1,4 +1,11 @@
-import { JunoEndpoints } from "Common/Constants";
+import {
+ BackendApi,
+ CassandraProxyEndpoints,
+ JunoEndpoints,
+ MongoProxyEndpoints,
+ PortalBackendEndpoints,
+} from "Common/Constants";
+import { configContext } from "ConfigContext";
import * as Logger from "../Common/Logger";
export function validateEndpoint(
@@ -67,7 +74,22 @@ export const PortalBackendIPs: { [key: string]: string[] } = {
//usnat: ["7.28.202.68"],
};
+export const MongoProxyOutboundIPs: { [key: string]: string[] } = {
+ [MongoProxyEndpoints.Mpac]: ["20.245.81.54", "40.118.23.126"],
+ [MongoProxyEndpoints.Prod]: ["40.80.152.199", "13.95.130.121"],
+ [MongoProxyEndpoints.Fairfax]: ["52.244.176.112", "52.247.148.42"],
+ [MongoProxyEndpoints.Mooncake]: ["52.131.240.99", "143.64.61.130"],
+};
+
export const allowedMongoProxyEndpoints: ReadonlyArray = [
+ MongoProxyEndpoints.Local,
+ MongoProxyEndpoints.Mpac,
+ MongoProxyEndpoints.Prod,
+ MongoProxyEndpoints.Fairfax,
+ MongoProxyEndpoints.Mooncake,
+];
+
+export const allowedMongoProxyEndpoints_ToBeDeprecated: ReadonlyArray = [
"https://main.documentdb.ext.azure.com",
"https://main.documentdb.ext.azure.cn",
"https://main.documentdb.ext.azure.us",
@@ -75,6 +97,29 @@ export const allowedMongoProxyEndpoints: ReadonlyArray = [
"https://localhost:12901",
];
+export const allowedCassandraProxyEndpoints: ReadonlyArray = [
+ CassandraProxyEndpoints.Development,
+ CassandraProxyEndpoints.Mpac,
+ CassandraProxyEndpoints.Prod,
+ CassandraProxyEndpoints.Fairfax,
+ CassandraProxyEndpoints.Mooncake,
+];
+
+export const allowedCassandraProxyEndpoints_ToBeDeprecated: ReadonlyArray = [
+ "https://main.documentdb.ext.azure.com",
+ "https://main.documentdb.ext.azure.cn",
+ "https://main.documentdb.ext.azure.us",
+ "https://main.cosmos.ext.azure",
+ "https://localhost:12901",
+];
+
+export const CassandraProxyOutboundIPs: { [key: string]: string[] } = {
+ [CassandraProxyEndpoints.Mpac]: ["40.113.96.14", "104.42.11.145"],
+ [CassandraProxyEndpoints.Prod]: ["137.117.230.240", "168.61.72.237"],
+ [CassandraProxyEndpoints.Fairfax]: ["52.244.50.101", "52.227.165.24"],
+ [CassandraProxyEndpoints.Mooncake]: ["40.73.99.146", "143.64.62.47"],
+};
+
export const allowedEmulatorEndpoints: ReadonlyArray = ["https://localhost:8081"];
export const allowedMongoBackendEndpoints: ReadonlyArray = ["https://localhost:1234"];
@@ -99,3 +144,31 @@ export const allowedJunoOrigins: ReadonlyArray = [
];
export const allowedNotebookServerUrls: ReadonlyArray = [];
+
+//
+// Temporary function to determine if a portal backend API is supported by the
+// new backend in this environment.
+//
+// TODO: Remove this function once new backend migration is completed for all environments.
+//
+export function useNewPortalBackendEndpoint(backendApi: string): boolean {
+ // This maps backend APIs to the environments supported by the new backend.
+ const newBackendApiEnvironmentMap: { [key: string]: string[] } = {
+ [BackendApi.GenerateToken]: [
+ PortalBackendEndpoints.Development,
+ PortalBackendEndpoints.Mpac,
+ PortalBackendEndpoints.Prod,
+ ],
+ [BackendApi.PortalSettings]: [
+ PortalBackendEndpoints.Development,
+ PortalBackendEndpoints.Mpac,
+ PortalBackendEndpoints.Prod,
+ ],
+ };
+
+ if (!newBackendApiEnvironmentMap[backendApi] || !configContext.PORTAL_BACKEND_ENDPOINT) {
+ return false;
+ }
+
+ return newBackendApiEnvironmentMap[backendApi].includes(configContext.PORTAL_BACKEND_ENDPOINT);
+}
diff --git a/src/Utils/NetworkUtility.test.ts b/src/Utils/NetworkUtility.test.ts
index ed22b502e..4ee4b5cd2 100644
--- a/src/Utils/NetworkUtility.test.ts
+++ b/src/Utils/NetworkUtility.test.ts
@@ -1,7 +1,7 @@
import { resetConfigContext, updateConfigContext } from "ConfigContext";
import { DatabaseAccount, IpRule } from "Contracts/DataModels";
import { updateUserContext } from "UserContext";
-import { PortalBackendIPs } from "Utils/EndpointValidation";
+import { PortalBackendIPs } from "Utils/EndpointUtils";
import { getNetworkSettingsWarningMessage } from "./NetworkUtility";
describe("NetworkUtility tests", () => {
diff --git a/src/Utils/NetworkUtility.ts b/src/Utils/NetworkUtility.ts
index 8f4624297..96f3ae124 100644
--- a/src/Utils/NetworkUtility.ts
+++ b/src/Utils/NetworkUtility.ts
@@ -1,7 +1,7 @@
import { configContext } from "ConfigContext";
import { checkFirewallRules } from "Explorer/Tabs/Shared/CheckFirewallRules";
import { userContext } from "UserContext";
-import { PortalBackendIPs } from "Utils/EndpointValidation";
+import { PortalBackendIPs } from "Utils/EndpointUtils";
export const getNetworkSettingsWarningMessage = async (
setStateFunc: (warningMessage: string) => void,
diff --git a/src/Utils/arm/generatedClients/cosmos/cassandraResources.ts b/src/Utils/arm/generatedClients/cosmos/cassandraResources.ts
index 2a7762328..461e516bf 100644
--- a/src/Utils/arm/generatedClients/cosmos/cassandraResources.ts
+++ b/src/Utils/arm/generatedClients/cosmos/cassandraResources.ts
@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
- Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/
+import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request";
import * as Types from "./types";
-import { configContext } from "../../../../ConfigContext";
-const apiVersion = "2023-09-15-preview";
+const apiVersion = "2024-02-15-preview";
/* Lists the Cassandra keyspaces under an existing Azure Cosmos DB database account. */
export async function listCassandraKeyspaces(
diff --git a/src/Utils/arm/generatedClients/cosmos/collection.ts b/src/Utils/arm/generatedClients/cosmos/collection.ts
index 86b116f01..4a9a9c198 100644
--- a/src/Utils/arm/generatedClients/cosmos/collection.ts
+++ b/src/Utils/arm/generatedClients/cosmos/collection.ts
@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
- Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/
+import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request";
import * as Types from "./types";
-import { configContext } from "../../../../ConfigContext";
-const apiVersion = "2023-09-15-preview";
+const apiVersion = "2024-02-15-preview";
/* Retrieves the metrics determined by the given filter for the given database account and collection. */
export async function listMetrics(
diff --git a/src/Utils/arm/generatedClients/cosmos/collectionPartition.ts b/src/Utils/arm/generatedClients/cosmos/collectionPartition.ts
index 8b3f407da..6fed487b6 100644
--- a/src/Utils/arm/generatedClients/cosmos/collectionPartition.ts
+++ b/src/Utils/arm/generatedClients/cosmos/collectionPartition.ts
@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
- Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/
+import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request";
import * as Types from "./types";
-import { configContext } from "../../../../ConfigContext";
-const apiVersion = "2023-09-15-preview";
+const apiVersion = "2024-02-15-preview";
/* Retrieves the metrics determined by the given filter for the given collection, split by partition. */
export async function listMetrics(
diff --git a/src/Utils/arm/generatedClients/cosmos/collectionPartitionRegion.ts b/src/Utils/arm/generatedClients/cosmos/collectionPartitionRegion.ts
index ec0d5e0ea..b33c904d9 100644
--- a/src/Utils/arm/generatedClients/cosmos/collectionPartitionRegion.ts
+++ b/src/Utils/arm/generatedClients/cosmos/collectionPartitionRegion.ts
@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
- Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/
+import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request";
import * as Types from "./types";
-import { configContext } from "../../../../ConfigContext";
-const apiVersion = "2023-09-15-preview";
+const apiVersion = "2024-02-15-preview";
/* Retrieves the metrics determined by the given filter for the given collection and region, split by partition. */
export async function listMetrics(
diff --git a/src/Utils/arm/generatedClients/cosmos/collectionRegion.ts b/src/Utils/arm/generatedClients/cosmos/collectionRegion.ts
index 281d98289..984cb146a 100644
--- a/src/Utils/arm/generatedClients/cosmos/collectionRegion.ts
+++ b/src/Utils/arm/generatedClients/cosmos/collectionRegion.ts
@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
- Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/
+import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request";
import * as Types from "./types";
-import { configContext } from "../../../../ConfigContext";
-const apiVersion = "2023-09-15-preview";
+const apiVersion = "2024-02-15-preview";
/* Retrieves the metrics determined by the given filter for the given database account, collection and region. */
export async function listMetrics(
diff --git a/src/Utils/arm/generatedClients/cosmos/database.ts b/src/Utils/arm/generatedClients/cosmos/database.ts
index 1d01fcbd7..7b286c4ec 100644
--- a/src/Utils/arm/generatedClients/cosmos/database.ts
+++ b/src/Utils/arm/generatedClients/cosmos/database.ts
@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
- Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/
+import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request";
import * as Types from "./types";
-import { configContext } from "../../../../ConfigContext";
-const apiVersion = "2023-09-15-preview";
+const apiVersion = "2024-02-15-preview";
/* Retrieves the metrics determined by the given filter for the given database account and database. */
export async function listMetrics(
diff --git a/src/Utils/arm/generatedClients/cosmos/databaseAccountRegion.ts b/src/Utils/arm/generatedClients/cosmos/databaseAccountRegion.ts
index 38fdbd89e..09f17c35b 100644
--- a/src/Utils/arm/generatedClients/cosmos/databaseAccountRegion.ts
+++ b/src/Utils/arm/generatedClients/cosmos/databaseAccountRegion.ts
@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
- Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/
+import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request";
import * as Types from "./types";
-import { configContext } from "../../../../ConfigContext";
-const apiVersion = "2023-09-15-preview";
+const apiVersion = "2024-02-15-preview";
/* Retrieves the metrics determined by the given filter for the given database account and region. */
export async function listMetrics(
diff --git a/src/Utils/arm/generatedClients/cosmos/databaseAccounts.ts b/src/Utils/arm/generatedClients/cosmos/databaseAccounts.ts
index 32aeb05eb..4b52b631b 100644
--- a/src/Utils/arm/generatedClients/cosmos/databaseAccounts.ts
+++ b/src/Utils/arm/generatedClients/cosmos/databaseAccounts.ts
@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
- Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/
+import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request";
import * as Types from "./types";
-import { configContext } from "../../../../ConfigContext";
-const apiVersion = "2023-09-15-preview";
+const apiVersion = "2024-02-15-preview";
/* Retrieves the properties of an existing Azure Cosmos DB database account. */
export async function get(
diff --git a/src/Utils/arm/generatedClients/cosmos/graphResources.ts b/src/Utils/arm/generatedClients/cosmos/graphResources.ts
index 807380575..d51d44d77 100644
--- a/src/Utils/arm/generatedClients/cosmos/graphResources.ts
+++ b/src/Utils/arm/generatedClients/cosmos/graphResources.ts
@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
- Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/
+import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request";
import * as Types from "./types";
-import { configContext } from "../../../../ConfigContext";
-const apiVersion = "2023-09-15-preview";
+const apiVersion = "2024-02-15-preview";
/* Lists the graphs under an existing Azure Cosmos DB database account. */
export async function listGraphs(
diff --git a/src/Utils/arm/generatedClients/cosmos/gremlinResources.ts b/src/Utils/arm/generatedClients/cosmos/gremlinResources.ts
index ffad0a38a..6e8656400 100644
--- a/src/Utils/arm/generatedClients/cosmos/gremlinResources.ts
+++ b/src/Utils/arm/generatedClients/cosmos/gremlinResources.ts
@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
- Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/
+import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request";
import * as Types from "./types";
-import { configContext } from "../../../../ConfigContext";
-const apiVersion = "2023-09-15-preview";
+const apiVersion = "2024-02-15-preview";
/* Lists the Gremlin databases under an existing Azure Cosmos DB database account. */
export async function listGremlinDatabases(
diff --git a/src/Utils/arm/generatedClients/cosmos/locations.ts b/src/Utils/arm/generatedClients/cosmos/locations.ts
index 5c28d60f0..6ec4cdc62 100644
--- a/src/Utils/arm/generatedClients/cosmos/locations.ts
+++ b/src/Utils/arm/generatedClients/cosmos/locations.ts
@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
- Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/
+import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request";
import * as Types from "./types";
-import { configContext } from "../../../../ConfigContext";
-const apiVersion = "2023-09-15-preview";
+const apiVersion = "2024-02-15-preview";
/* List Cosmos DB locations and their properties */
export async function list(subscriptionId: string): Promise {
diff --git a/src/Utils/arm/generatedClients/cosmos/mongoDBResources.ts b/src/Utils/arm/generatedClients/cosmos/mongoDBResources.ts
index b99584e1c..22b316904 100644
--- a/src/Utils/arm/generatedClients/cosmos/mongoDBResources.ts
+++ b/src/Utils/arm/generatedClients/cosmos/mongoDBResources.ts
@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
- Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/
+import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request";
import * as Types from "./types";
-import { configContext } from "../../../../ConfigContext";
-const apiVersion = "2023-09-15-preview";
+const apiVersion = "2024-02-15-preview";
/* Lists the MongoDB databases under an existing Azure Cosmos DB database account. */
export async function listMongoDBDatabases(
diff --git a/src/Utils/arm/generatedClients/cosmos/operations.ts b/src/Utils/arm/generatedClients/cosmos/operations.ts
index 5a01456ce..ae87fbb48 100644
--- a/src/Utils/arm/generatedClients/cosmos/operations.ts
+++ b/src/Utils/arm/generatedClients/cosmos/operations.ts
@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
- Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/
+import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request";
import * as Types from "./types";
-import { configContext } from "../../../../ConfigContext";
-const apiVersion = "2023-09-15-preview";
+const apiVersion = "2024-02-15-preview";
/* Lists all of the available Cosmos DB Resource Provider operations. */
export async function list(): Promise {
diff --git a/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeId.ts b/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeId.ts
index a66e6294c..d9b5d6dfa 100644
--- a/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeId.ts
+++ b/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeId.ts
@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
- Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/
+import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request";
import * as Types from "./types";
-import { configContext } from "../../../../ConfigContext";
-const apiVersion = "2023-09-15-preview";
+const apiVersion = "2024-02-15-preview";
/* Retrieves the metrics determined by the given filter for the given partition key range id. */
export async function listMetrics(
diff --git a/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeIdRegion.ts b/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeIdRegion.ts
index 3625b0859..6ec2ba50a 100644
--- a/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeIdRegion.ts
+++ b/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeIdRegion.ts
@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
- Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/
+import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request";
import * as Types from "./types";
-import { configContext } from "../../../../ConfigContext";
-const apiVersion = "2023-09-15-preview";
+const apiVersion = "2024-02-15-preview";
/* Retrieves the metrics determined by the given filter for the given partition key range id and region. */
export async function listMetrics(
diff --git a/src/Utils/arm/generatedClients/cosmos/percentile.ts b/src/Utils/arm/generatedClients/cosmos/percentile.ts
index b7a8d2841..cbbc751dc 100644
--- a/src/Utils/arm/generatedClients/cosmos/percentile.ts
+++ b/src/Utils/arm/generatedClients/cosmos/percentile.ts
@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
- Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/
+import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request";
import * as Types from "./types";
-import { configContext } from "../../../../ConfigContext";
-const apiVersion = "2023-09-15-preview";
+const apiVersion = "2024-02-15-preview";
/* Retrieves the metrics determined by the given filter for the given database account. This url is only for PBS and Replication Latency data */
export async function listMetrics(
diff --git a/src/Utils/arm/generatedClients/cosmos/percentileSourceTarget.ts b/src/Utils/arm/generatedClients/cosmos/percentileSourceTarget.ts
index aa9432e8f..88c05dd11 100644
--- a/src/Utils/arm/generatedClients/cosmos/percentileSourceTarget.ts
+++ b/src/Utils/arm/generatedClients/cosmos/percentileSourceTarget.ts
@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
- Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/
+import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request";
import * as Types from "./types";
-import { configContext } from "../../../../ConfigContext";
-const apiVersion = "2023-09-15-preview";
+const apiVersion = "2024-02-15-preview";
/* Retrieves the metrics determined by the given filter for the given account, source and target region. This url is only for PBS and Replication Latency data */
export async function listMetrics(
diff --git a/src/Utils/arm/generatedClients/cosmos/percentileTarget.ts b/src/Utils/arm/generatedClients/cosmos/percentileTarget.ts
index 43f102890..87359e9f0 100644
--- a/src/Utils/arm/generatedClients/cosmos/percentileTarget.ts
+++ b/src/Utils/arm/generatedClients/cosmos/percentileTarget.ts
@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
- Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/
+import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request";
import * as Types from "./types";
-import { configContext } from "../../../../ConfigContext";
-const apiVersion = "2023-09-15-preview";
+const apiVersion = "2024-02-15-preview";
/* Retrieves the metrics determined by the given filter for the given account target region. This url is only for PBS and Replication Latency data */
export async function listMetrics(
diff --git a/src/Utils/arm/generatedClients/cosmos/sqlResources.ts b/src/Utils/arm/generatedClients/cosmos/sqlResources.ts
index 321d68639..049e265e9 100644
--- a/src/Utils/arm/generatedClients/cosmos/sqlResources.ts
+++ b/src/Utils/arm/generatedClients/cosmos/sqlResources.ts
@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
- Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/
+import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request";
import * as Types from "./types";
-import { configContext } from "../../../../ConfigContext";
-const apiVersion = "2023-09-15-preview";
+const apiVersion = "2024-05-15-preview";
/* Lists the SQL databases under an existing Azure Cosmos DB database account. */
export async function listSqlDatabases(
diff --git a/src/Utils/arm/generatedClients/cosmos/tableResources.ts b/src/Utils/arm/generatedClients/cosmos/tableResources.ts
index 3bc16cf0e..0da78793e 100644
--- a/src/Utils/arm/generatedClients/cosmos/tableResources.ts
+++ b/src/Utils/arm/generatedClients/cosmos/tableResources.ts
@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
- Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/
+import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request";
import * as Types from "./types";
-import { configContext } from "../../../../ConfigContext";
-const apiVersion = "2023-09-15-preview";
+const apiVersion = "2024-02-15-preview";
/* Lists the Tables under an existing Azure Cosmos DB database account. */
export async function listTables(
diff --git a/src/Utils/arm/generatedClients/cosmos/types.ts b/src/Utils/arm/generatedClients/cosmos/types.ts
index 3252b38fe..5272f215b 100644
--- a/src/Utils/arm/generatedClients/cosmos/types.ts
+++ b/src/Utils/arm/generatedClients/cosmos/types.ts
@@ -3,7 +3,7 @@
Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
- Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/
/* The List operation response, that contains the client encryption keys and their properties. */
@@ -566,12 +566,14 @@ export interface DatabaseAccountGetProperties {
minimalTlsVersion?: MinimalTlsVersion;
/* Indicates the status of the Customer Managed Key feature on the account. In case there are errors, the property provides troubleshooting guidance. */
- customerManagedKeyStatus?: CustomerManagedKeyStatus;
-
+ customerManagedKeyStatus?: string;
/* Flag to indicate enabling/disabling of Priority Based Execution Preview feature on the account */
enablePriorityBasedExecution?: boolean;
/* Enum to indicate default Priority Level of request for Priority Based Execution. */
defaultPriorityLevel?: DefaultPriorityLevel;
+
+ /* Flag to indicate enabling/disabling of Per-Region Per-partition autoscale Preview feature on the account */
+ enablePerRegionPerPartitionAutoscale?: boolean;
}
/* Properties to create and update Azure Cosmos DB database accounts. */
@@ -663,12 +665,14 @@ export interface DatabaseAccountCreateUpdateProperties {
minimalTlsVersion?: MinimalTlsVersion;
/* Indicates the status of the Customer Managed Key feature on the account. In case there are errors, the property provides troubleshooting guidance. */
- customerManagedKeyStatus?: CustomerManagedKeyStatus;
-
+ customerManagedKeyStatus?: string;
/* Flag to indicate enabling/disabling of Priority Based Execution Preview feature on the account */
enablePriorityBasedExecution?: boolean;
/* Enum to indicate default Priority Level of request for Priority Based Execution. */
defaultPriorityLevel?: DefaultPriorityLevel;
+
+ /* Flag to indicate enabling/disabling of Per-Region Per-partition autoscale Preview feature on the account */
+ enablePerRegionPerPartitionAutoscale?: boolean;
}
/* Parameters to create and update Cosmos DB database accounts. */
@@ -763,12 +767,14 @@ export interface DatabaseAccountUpdateProperties {
minimalTlsVersion?: MinimalTlsVersion;
/* Indicates the status of the Customer Managed Key feature on the account. In case there are errors, the property provides troubleshooting guidance. */
- customerManagedKeyStatus?: CustomerManagedKeyStatus;
-
+ customerManagedKeyStatus?: string;
/* Flag to indicate enabling/disabling of Priority Based Execution Preview feature on the account */
enablePriorityBasedExecution?: boolean;
/* Enum to indicate default Priority Level of request for Priority Based Execution. */
defaultPriorityLevel?: DefaultPriorityLevel;
+
+ /* Flag to indicate enabling/disabling of Per-Region Per-partition autoscale Preview feature on the account */
+ enablePerRegionPerPartitionAutoscale?: boolean;
}
/* Parameters for patching Azure Cosmos DB database account properties. */
@@ -1229,6 +1235,9 @@ export interface SqlDatabaseResource {
export interface SqlContainerResource {
/* Name of the Cosmos DB SQL container */
id: string;
+
+ vectorEmbeddingPolicy?: VectorEmbeddingPolicy;
+
/* The configuration of the indexing policy. By default, the indexing is automatic for all document paths within the container */
indexingPolicy?: IndexingPolicy;
@@ -1256,6 +1265,20 @@ export interface SqlContainerResource {
/* The configuration for defining Materialized Views. This must be specified only for creating a Materialized View container. */
materializedViewDefinition?: MaterializedViewDefinition;
+
+ /* List of computed properties */
+ computedProperties?: ComputedProperty[];
+}
+
+export interface VectorEmbeddingPolicy {
+ vectorEmbeddings: VectorEmbedding[];
+}
+
+export interface VectorEmbedding {
+ path?: string;
+ dataType?: string;
+ dimensions?: number;
+ distanceFunction?: string;
}
/* Cosmos DB indexing policy */
@@ -1276,6 +1299,13 @@ export interface IndexingPolicy {
/* List of spatial specifics */
spatialIndexes?: SpatialSpec[];
+
+ vectorIndexes?: VectorIndex[];
+}
+
+export interface VectorIndex {
+ path?: string;
+ type?: string;
}
/* undocumented */
@@ -1325,6 +1355,14 @@ export interface SpatialSpec {
/* Indicates the spatial type of index. */
export type SpatialType = "Point" | "LineString" | "Polygon" | "MultiPolygon";
+/* The definition of a computed property */
+export interface ComputedProperty {
+ /* The name of a computed property, for example - "cp_lowerName" */
+ name?: string;
+ /* The query that evaluates the value for computed property, for example - "SELECT VALUE LOWER(c.name) FROM c" */
+ query?: string;
+}
+
/* The configuration of the partition key to be used for partitioning data into multiple partitions */
export interface ContainerPartitionKey {
/* List of paths using which data within the container can be partitioned */
@@ -1929,6 +1967,8 @@ export interface RestoreParametersBase {
restoreSource?: string;
/* Time to which the account has to be restored (ISO-8601 format). */
restoreTimestampInUtc?: string;
+ /* Specifies whether the restored account will have Time-To-Live disabled upon the successful restore. */
+ restoreWithTtlDisabled?: boolean;
}
/* Parameters to indicate the information about the restore. */
@@ -2072,19 +2112,5 @@ export type ContinuousTier = "Continuous7Days" | "Continuous30Days";
/* Indicates the minimum allowed Tls version. The default is Tls 1.0, except for Cassandra and Mongo API's, which only work with Tls 1.2. */
export type MinimalTlsVersion = "Tls" | "Tls11" | "Tls12";
-/* Indicates the status of the Customer Managed Key feature on the account. In case there are errors, the property provides troubleshooting guidance. */
-export type CustomerManagedKeyStatus =
- | "Access to your account is currently revoked because the Azure Cosmos DB service is unable to obtain the AAD authentication token for the account's default identity; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#azure-active-directory-token-acquisition-error (4000)."
- | "Access to your account is currently revoked because the Azure Cosmos DB account's key vault key URI does not follow the expected format; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#improper-syntax-detected-on-the-key-vault-uri-property (4006)."
- | "Access to your account is currently revoked because the current default identity no longer has permission to the associated Key Vault key; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#default-identity-is-unauthorized-to-access-the-azure-key-vault-key (4002)."
- | "Access to your account is currently revoked because the Azure Key Vault DNS name specified by the account's keyvaultkeyuri property could not be resolved; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#unable-to-resolve-the-key-vaults-dns (4009)."
- | "Access to your account is currently revoked because the correspondent key is not found on the specified Key Vault; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#azure-key-vault-resource-not-found (4003)."
- | "Access to your account is currently revoked because the Azure Cosmos DB service is unable to wrap or unwrap the key; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#internal-unwrapping-procedure-error (4005)."
- | "Access to your account is currently revoked because the Azure Cosmos DB account has an undefined default identity; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#invalid-azure-cosmos-db-default-identity (4015)."
- | "Access to your account is currently revoked because the access rules are blocking outbound requests to the Azure Key Vault service; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide (4016)."
- | "Access to your account is currently revoked because the correspondent Azure Key Vault was not found; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#azure-key-vault-resource-not-found (4017)."
- | "Access to your account is currently revoked; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide"
- | "Access to the configured customer managed key confirmed.";
-
/* Enum to indicate default priorityLevel of requests */
export type DefaultPriorityLevel = "High" | "Low";
diff --git a/src/Utils/arm/generatedClients/dataTransferService/dataTransferJobs.ts b/src/Utils/arm/generatedClients/dataTransferService/dataTransferJobs.ts
new file mode 100644
index 000000000..0c2b7c916
--- /dev/null
+++ b/src/Utils/arm/generatedClients/dataTransferService/dataTransferJobs.ts
@@ -0,0 +1,78 @@
+/*
+ AUTOGENERATED FILE
+ Run "npm run generateARMClients" to regenerate
+ Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
+
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-11-15-preview/dataTransferService.json
+*/
+
+import { configContext } from "../../../../ConfigContext";
+import { armRequest } from "../../request";
+import * as Types from "./types";
+const apiVersion = "2023-11-15-preview";
+
+/* Creates a Data Transfer Job. */
+export async function create(
+ subscriptionId: string,
+ resourceGroupName: string,
+ accountName: string,
+ jobName: string,
+ body: Types.CreateJobRequest,
+): Promise {
+ const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/dataTransferJobs/${jobName}`;
+ return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
+}
+
+/* Get a Data Transfer Job. */
+export async function get(
+ subscriptionId: string,
+ resourceGroupName: string,
+ accountName: string,
+ jobName: string,
+): Promise {
+ const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/dataTransferJobs/${jobName}`;
+ return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "GET", apiVersion });
+}
+
+/* Pause a Data Transfer Job. */
+export async function pause(
+ subscriptionId: string,
+ resourceGroupName: string,
+ accountName: string,
+ jobName: string,
+): Promise {
+ const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/dataTransferJobs/${jobName}/pause`;
+ return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "POST", apiVersion });
+}
+
+/* Resumes a Data Transfer Job. */
+export async function resume(
+ subscriptionId: string,
+ resourceGroupName: string,
+ accountName: string,
+ jobName: string,
+): Promise {
+ const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/dataTransferJobs/${jobName}/resume`;
+ return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "POST", apiVersion });
+}
+
+/* Cancels a Data Transfer Job. */
+export async function cancel(
+ subscriptionId: string,
+ resourceGroupName: string,
+ accountName: string,
+ jobName: string,
+): Promise {
+ const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/dataTransferJobs/${jobName}/cancel`;
+ return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "POST", apiVersion });
+}
+
+/* Get a list of Data Transfer jobs. */
+export async function listByDatabaseAccount(
+ subscriptionId: string,
+ resourceGroupName: string,
+ accountName: string,
+): Promise {
+ const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/dataTransferJobs`;
+ return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "GET", apiVersion });
+}
diff --git a/src/Utils/arm/generatedClients/dataTransferService/types.ts b/src/Utils/arm/generatedClients/dataTransferService/types.ts
new file mode 100644
index 000000000..27c3db709
--- /dev/null
+++ b/src/Utils/arm/generatedClients/dataTransferService/types.ts
@@ -0,0 +1,101 @@
+/*
+ AUTOGENERATED FILE
+ Run "npm run generateARMClients" to regenerate
+ Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
+
+ Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-11-15-preview/dataTransferService.json
+*/
+
+/* Base class for all DataTransfer source/sink */
+export interface DataTransferDataSourceSink {
+ /* undocumented */
+ component: "CosmosDBCassandra" | "CosmosDBMongo" | "CosmosDBSql" | "AzureBlobStorage";
+}
+
+/* A base CosmosDB data source/sink */
+export type BaseCosmosDataTransferDataSourceSink = DataTransferDataSourceSink & {
+ /* undocumented */
+ remoteAccountName?: string;
+};
+
+/* A CosmosDB Cassandra API data source/sink */
+export type CosmosCassandraDataTransferDataSourceSink = BaseCosmosDataTransferDataSourceSink & {
+ /* undocumented */
+ keyspaceName: string;
+ /* undocumented */
+ tableName: string;
+};
+
+/* A CosmosDB Mongo API data source/sink */
+export type CosmosMongoDataTransferDataSourceSink = BaseCosmosDataTransferDataSourceSink & {
+ /* undocumented */
+ databaseName: string;
+ /* undocumented */
+ collectionName: string;
+};
+
+/* A CosmosDB No Sql API data source/sink */
+export type CosmosSqlDataTransferDataSourceSink = BaseCosmosDataTransferDataSourceSink & {
+ /* undocumented */
+ databaseName: string;
+ /* undocumented */
+ containerName: string;
+};
+
+/* An Azure Blob Storage data source/sink */
+export type AzureBlobDataTransferDataSourceSink = DataTransferDataSourceSink & {
+ /* undocumented */
+ containerName: string;
+ /* undocumented */
+ endpointUrl?: string;
+};
+
+/* The properties of a DataTransfer Job */
+export interface DataTransferJobProperties {
+ /* Job Name */
+ readonly jobName?: string;
+ /* Source DataStore details */
+ source: DataTransferDataSourceSink;
+
+ /* Destination DataStore details */
+ destination: DataTransferDataSourceSink;
+
+ /* Job Status */
+ readonly status?: string;
+ /* Processed Count. */
+ readonly processedCount?: number;
+ /* Total Count. */
+ readonly totalCount?: number;
+ /* Last Updated Time (ISO-8601 format). */
+ readonly lastUpdatedUtcTime?: string;
+ /* Worker count */
+ workerCount?: number;
+ /* Error response for Faulted job */
+ readonly error?: unknown;
+
+ /* Total Duration of Job */
+ readonly duration?: string;
+ /* Mode of job execution */
+ mode?: "Offline" | "Online";
+}
+
+/* Parameters to create Data Transfer Job */
+export type CreateJobRequest = unknown & {
+ /* Data Transfer Create Job Properties */
+ properties: DataTransferJobProperties;
+};
+
+/* A Cosmos DB Data Transfer Job */
+export type DataTransferJobGetResults = unknown & {
+ /* undocumented */
+ properties?: DataTransferJobProperties;
+};
+
+/* The List operation response, that contains the Data Transfer jobs and their properties. */
+export interface DataTransferJobFeedResults {
+ /* List of Data Transfer jobs and their properties. */
+ readonly value?: DataTransferJobGetResults[];
+
+ /* URL to get the next set of Data Transfer job list results if there are any. */
+ readonly nextLink?: string;
+}
diff --git a/src/hooks/useAADAuth.ts b/src/hooks/useAADAuth.ts
index 749a39632..c20f953f7 100644
--- a/src/hooks/useAADAuth.ts
+++ b/src/hooks/useAADAuth.ts
@@ -2,9 +2,9 @@ import * as msal from "@azure/msal-browser";
import { useBoolean } from "@fluentui/react-hooks";
import * as React from "react";
import { configContext } from "../ConfigContext";
-import { getMsalInstance } from "../Utils/AuthorizationUtils";
+import { acquireTokenWithMsal, getMsalInstance } from "../Utils/AuthorizationUtils";
-const msalInstance = getMsalInstance();
+const msalInstance = await getMsalInstance();
const cachedAccount = msalInstance.getAllAccounts()?.[0];
const cachedTenantId = localStorage.getItem("cachedTenantId");
@@ -18,6 +18,13 @@ interface ReturnType {
tenantId: string;
account: msal.AccountInfo;
switchTenant: (tenantId: string) => void;
+ authFailure: AadAuthFailure;
+}
+
+export interface AadAuthFailure {
+ failureMessage: string;
+ failureLinkTitle?: string;
+ failureLinkAction?: () => void;
}
export function useAADAuth(): ReturnType {
@@ -28,6 +35,7 @@ export function useAADAuth(): ReturnType {
const [tenantId, setTenantId] = React.useState(cachedTenantId);
const [graphToken, setGraphToken] = React.useState();
const [armToken, setArmToken] = React.useState();
+ const [authFailure, setAuthFailure] = React.useState(undefined);
msalInstance.setActiveAccount(account);
const login = React.useCallback(async () => {
@@ -61,24 +69,60 @@ export function useAADAuth(): ReturnType {
[account, tenantId],
);
- React.useEffect(() => {
- if (account && tenantId) {
- Promise.all([
- msalInstance.acquireTokenSilent({
- authority: `${configContext.AAD_ENDPOINT}${tenantId}`,
- scopes: [`${configContext.GRAPH_ENDPOINT}/.default`],
- }),
- msalInstance.acquireTokenSilent({
- authority: `${configContext.AAD_ENDPOINT}${tenantId}`,
- scopes: [`${configContext.ARM_ENDPOINT}/.default`],
- }),
- ]).then(([graphTokenResponse, armTokenResponse]) => {
- setGraphToken(graphTokenResponse.accessToken);
- setArmToken(armTokenResponse.accessToken);
+ const acquireTokens = React.useCallback(async () => {
+ if (!(account && tenantId)) {
+ return;
+ }
+
+ try {
+ const armToken = await acquireTokenWithMsal(msalInstance, {
+ authority: `${configContext.AAD_ENDPOINT}${tenantId}`,
+ scopes: [`${configContext.ARM_ENDPOINT}/.default`],
});
+
+ setArmToken(armToken);
+ setAuthFailure(null);
+ } catch (error) {
+ if (error instanceof msal.AuthError && error.errorCode === msal.BrowserAuthErrorMessage.popUpWindowError.code) {
+ // This error can occur when acquireTokenWithMsal() has attempted to acquire token interactively
+ // and user has popups disabled in browser. This fails as the popup is not the result of a explicit user
+ // action. In this case, we display the failure and a link to repeat the operation. Clicking on the
+ // link is a user action so it will work even if popups have been disabled.
+ // See: https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/76#issuecomment-324787539
+ setAuthFailure({
+ failureMessage:
+ "We were unable to establish authorization for this account, due to pop-ups being disabled in the browser.\nPlease click below to retry authorization without requiring popups being enabled.",
+ failureLinkTitle: "Retry Authorization",
+ failureLinkAction: acquireTokens,
+ });
+ } else {
+ const errorJson = JSON.stringify(error);
+ setAuthFailure({
+ failureMessage: `We were unable to establish authorization for this account, due to the following error: \n${errorJson}`,
+ });
+ }
+ }
+
+ try {
+ const graphToken = await acquireTokenWithMsal(msalInstance, {
+ authority: `${configContext.AAD_ENDPOINT}${tenantId}`,
+ scopes: [`${configContext.GRAPH_ENDPOINT}/.default`],
+ });
+
+ setGraphToken(graphToken);
+ } catch (error) {
+ // Graph token is used only for retrieving user photo at the moment, so
+ // it's not critical if this fails.
+ console.warn("Error acquiring graph token: " + error);
}
}, [account, tenantId]);
+ React.useEffect(() => {
+ if (account && tenantId && !authFailure) {
+ acquireTokens();
+ }
+ }, [account, tenantId, acquireTokens, authFailure]);
+
return {
account,
tenantId,
@@ -88,5 +132,6 @@ export function useAADAuth(): ReturnType {
login,
logout,
switchTenant,
+ authFailure,
};
}
diff --git a/src/hooks/useDataTransferJobs.tsx b/src/hooks/useDataTransferJobs.tsx
new file mode 100644
index 000000000..5414dae28
--- /dev/null
+++ b/src/hooks/useDataTransferJobs.tsx
@@ -0,0 +1,60 @@
+import { getDataTransferJobs } from "Common/dataAccess/dataTransfers";
+import { DataTransferJobGetResults } from "Utils/arm/generatedClients/dataTransferService/types";
+import create, { UseStore } from "zustand";
+
+export interface DataTransferJobsState {
+ dataTransferJobs: DataTransferJobGetResults[];
+ pollingDataTransferJobs: Set;
+ setDataTransferJobs: (dataTransferJobs: DataTransferJobGetResults[]) => void;
+ setPollingDataTransferJobs: (pollingDataTransferJobs: Set) => void;
+}
+
+type DataTransferJobStore = UseStore;
+
+export const useDataTransferJobs: DataTransferJobStore = create((set) => ({
+ dataTransferJobs: [],
+ pollingDataTransferJobs: new Set(),
+ setDataTransferJobs: (dataTransferJobs: DataTransferJobGetResults[]) => set({ dataTransferJobs }),
+ setPollingDataTransferJobs: (pollingDataTransferJobs: Set) => set({ pollingDataTransferJobs }),
+}));
+
+export const refreshDataTransferJobs = async (
+ subscriptionId: string,
+ resourceGroup: string,
+ accountName: string,
+): Promise => {
+ const dataTransferJobs: DataTransferJobGetResults[] = await getDataTransferJobs(
+ subscriptionId,
+ resourceGroup,
+ accountName,
+ );
+ const jobRegex = /^Portal_(.+)_(\d{10,})$/;
+ const sortedJobs: DataTransferJobGetResults[] = dataTransferJobs?.sort(
+ (a, b) =>
+ new Date(b?.properties?.lastUpdatedUtcTime).getTime() - new Date(a?.properties?.lastUpdatedUtcTime).getTime(),
+ );
+ const filteredJobs = sortedJobs.filter((job) => jobRegex.test(job?.properties?.jobName));
+ useDataTransferJobs.getState().setDataTransferJobs(filteredJobs);
+ return filteredJobs;
+};
+
+export const updateDataTransferJob = (updateJob: DataTransferJobGetResults) => {
+ const updatedDataTransferJobs = useDataTransferJobs
+ .getState()
+ .dataTransferJobs.map((job: DataTransferJobGetResults) =>
+ job?.properties?.jobName === updateJob?.properties?.jobName ? updateJob : job,
+ );
+ useDataTransferJobs.getState().setDataTransferJobs(updatedDataTransferJobs);
+};
+
+export const addToPolling = (addJob: string) => {
+ const pollingJobs = useDataTransferJobs.getState().pollingDataTransferJobs;
+ pollingJobs.add(addJob);
+ useDataTransferJobs.getState().setPollingDataTransferJobs(pollingJobs);
+};
+
+export const removeFromPolling = (removeJob: string) => {
+ const pollingJobs = useDataTransferJobs.getState().pollingDataTransferJobs;
+ pollingJobs.delete(removeJob);
+ useDataTransferJobs.getState().setPollingDataTransferJobs(pollingJobs);
+};
diff --git a/src/hooks/useKnockoutExplorer.ts b/src/hooks/useKnockoutExplorer.ts
index 6bbe37583..3a6061acd 100644
--- a/src/hooks/useKnockoutExplorer.ts
+++ b/src/hooks/useKnockoutExplorer.ts
@@ -3,10 +3,10 @@ import { DATA_EXPLORER_RPC_VERSION } from "Contracts/DataExplorerMessagesContrac
import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
import { FABRIC_RPC_VERSION, FabricMessageV2 } from "Contracts/FabricMessagesContract";
import Explorer from "Explorer/Explorer";
-import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
import { useSelectedNode } from "Explorer/useSelectedNode";
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
+import { logConsoleError } from "Utils/NotificationConsoleUtils";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import { ReactTabKind, useTabs } from "hooks/useTabs";
import { useEffect, useState } from "react";
@@ -34,10 +34,9 @@ import {
getDatabaseAccountPropertiesFromMetadata,
} from "../Platform/Hosted/HostedUtils";
import { extractFeatures } from "../Platform/Hosted/extractFeatures";
-import { CollectionCreation } from "../Shared/Constants";
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
import { Node, PortalEnv, updateUserContext, userContext } from "../UserContext";
-import { getAuthorizationHeader, getMsalInstance } from "../Utils/AuthorizationUtils";
+import { acquireTokenWithMsal, getAuthorizationHeader, getMsalInstance } from "../Utils/AuthorizationUtils";
import { isInvalidParentFrameOrigin, shouldProcessMessage } from "../Utils/MessageValidation";
import { listKeys } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
import { DatabaseAccountListKeysResult } from "../Utils/arm/generatedClients/cosmos/types";
@@ -91,6 +90,7 @@ async function configureFabric(): Promise {
// These are the versions of Fabric that Data Explorer supports.
const SUPPORTED_FABRIC_VERSIONS = [FABRIC_RPC_VERSION];
+ let firstContainerOpened = false;
let explorer: Explorer;
return new Promise((resolve) => {
window.addEventListener(
@@ -122,7 +122,10 @@ async function configureFabric(): Promise {
await scheduleRefreshDatabaseResourceToken(true);
resolve(explorer);
await explorer.refreshAllDatabases();
- openFirstContainer(explorer, userContext.fabricContext.databaseConnectionInfo.databaseId);
+ if (userContext.fabricContext.isVisible && !firstContainerOpened) {
+ firstContainerOpened = true;
+ openFirstContainer(explorer, userContext.fabricContext.databaseConnectionInfo.databaseId);
+ }
break;
}
case "newContainer":
@@ -133,8 +136,16 @@ async function configureFabric(): Promise {
handleCachedDataMessage(data);
break;
}
- case "setToolbarStatus": {
- useCommandBar.getState().setIsHidden(data.message.visible === false);
+ case "explorerVisible": {
+ userContext.fabricContext.isVisible = data.message.visible;
+ if (
+ userContext.fabricContext.isVisible &&
+ !firstContainerOpened &&
+ userContext?.fabricContext?.databaseConnectionInfo?.databaseId !== undefined
+ ) {
+ firstContainerOpened = true;
+ openFirstContainer(explorer, userContext.fabricContext.databaseConnectionInfo.databaseId);
+ }
break;
}
default:
@@ -245,16 +256,19 @@ async function configureHostedWithAAD(config: AAD): Promise {
let keys: DatabaseAccountListKeysResult = {};
if (account.properties?.documentEndpoint) {
const hrefEndpoint = new URL(account.properties.documentEndpoint).href.replace(/\/$/, "/.default");
- const msalInstance = getMsalInstance();
+ const msalInstance = await getMsalInstance();
const cachedAccount = msalInstance.getAllAccounts()?.[0];
msalInstance.setActiveAccount(cachedAccount);
const cachedTenantId = localStorage.getItem("cachedTenantId");
- const aadTokenResponse = await msalInstance.acquireTokenSilent({
- forceRefresh: true,
- scopes: [hrefEndpoint],
- authority: `${configContext.AAD_ENDPOINT}${cachedTenantId}`,
- });
- aadToken = aadTokenResponse.accessToken;
+ try {
+ aadToken = await acquireTokenWithMsal(msalInstance, {
+ forceRefresh: true,
+ scopes: [hrefEndpoint],
+ authority: `${configContext.AAD_ENDPOINT}${cachedTenantId}`,
+ });
+ } catch (authError) {
+ logConsoleError("Failed to acquire authorization token: " + authError);
+ }
}
try {
if (!account.properties.disableLocalAuth) {
@@ -325,11 +339,13 @@ function configureHostedWithResourceToken(config: ResourceToken): Explorer {
return explorer;
}
-function createExplorerFabric(params: { connectionId: string }): Explorer {
+function createExplorerFabric(params: { connectionId: string; isVisible: boolean }): Explorer {
updateUserContext({
fabricContext: {
connectionId: params.connectionId,
databaseConnectionInfo: undefined,
+ isReadOnly: true,
+ isVisible: params.isVisible ?? true,
},
authType: AuthType.ConnectionString,
databaseAccount: {
@@ -480,6 +496,9 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
updateConfigContext({
BACKEND_ENDPOINT: inputs.extensionEndpoint || configContext.BACKEND_ENDPOINT,
ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT),
+ MONGO_PROXY_ENDPOINT: inputs.mongoProxyEndpoint,
+ CASSANDRA_PROXY_ENDPOINT: inputs.cassandraProxyEndpoint,
+ PORTAL_BACKEND_ENDPOINT: inputs.portalBackendEndpoint,
});
updateUserContext({
@@ -492,9 +511,9 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
quotaId: inputs.quotaId,
portalEnv: inputs.serverId as PortalEnv,
hasWriteAccess: inputs.hasWriteAccess ?? true,
- addCollectionFlight: inputs.addCollectionDefaultFlight || CollectionCreation.DefaultAddCollectionDefaultFlight,
collectionCreationDefaults: inputs.defaultCollectionThroughput,
isTryCosmosDBSubscription: inputs.isTryCosmosDBSubscription,
+ feedbackPolicies: inputs.feedbackPolicies,
});
if (inputs.isPostgresAccount) {
diff --git a/src/hooks/useQueryCopilot.ts b/src/hooks/useQueryCopilot.ts
index 1d9685b6f..23e5a0aa6 100644
--- a/src/hooks/useQueryCopilot.ts
+++ b/src/hooks/useQueryCopilot.ts
@@ -29,6 +29,7 @@ export interface QueryCopilotState {
queryResults: QueryResults | undefined;
errorMessage: string;
isSamplePromptsOpen: boolean;
+ showPromptTeachingBubble: boolean;
showDeletePopup: boolean;
showFeedbackBar: boolean;
showCopyPopup: boolean;
@@ -71,6 +72,7 @@ export interface QueryCopilotState {
setQueryResults: (queryResults: QueryResults | undefined) => void;
setErrorMessage: (errorMessage: string) => void;
setIsSamplePromptsOpen: (isSamplePromptsOpen: boolean) => void;
+ setShowPromptTeachingBubble: (showPromptTeachingBubble: boolean) => void;
setShowDeletePopup: (showDeletePopup: boolean) => void;
setShowFeedbackBar: (showFeedbackBar: boolean) => void;
setshowCopyPopup: (showCopyPopup: boolean) => void;
@@ -93,7 +95,7 @@ export interface QueryCopilotState {
resetQueryCopilotStates: () => void;
}
-type QueryCopilotStore = UseStore;
+type QueryCopilotStore = UseStore>;
export const useQueryCopilot: QueryCopilotStore = create((set) => ({
copilotEnabled: false,
diff --git a/src/hooks/useTabs.ts b/src/hooks/useTabs.ts
index 136e0ce2c..982768afa 100644
--- a/src/hooks/useTabs.ts
+++ b/src/hooks/useTabs.ts
@@ -1,3 +1,4 @@
+import { clamp } from "@fluentui/react";
import create, { UseStore } from "zustand";
import * as ViewModels from "../Contracts/ViewModels";
import { CollectionTabKind } from "../Contracts/ViewModels";
@@ -29,6 +30,11 @@ export interface TabsState {
setQueryCopilotTabInitialInput: (input: string) => void;
setIsTabExecuting: (state: boolean) => void;
setIsQueryErrorThrown: (state: boolean) => void;
+ getCurrentTabIndex: () => number;
+ selectTabByIndex: (index: number) => void;
+ selectLeftTab: () => void;
+ selectRightTab: () => void;
+ closeActiveTab: () => void;
}
export enum ReactTabKind {
@@ -105,7 +111,7 @@ export const useTabs: UseStore = create((set, get) => ({
return true;
});
if (updatedTabs.length === 0 && configContext.platform !== Platform.Fabric) {
- set({ activeTab: undefined, activeReactTab: ReactTabKind.Home });
+ set({ activeTab: undefined, activeReactTab: undefined });
}
if (tab.tabId === activeTab.tabId && tabIndex !== -1) {
@@ -143,7 +149,7 @@ export const useTabs: UseStore = create((set, get) => ({
});
if (get().openedTabs.length === 0 && configContext.platform !== Platform.Fabric) {
- set({ activeTab: undefined, activeReactTab: ReactTabKind.Home });
+ set({ activeTab: undefined, activeReactTab: undefined });
}
}
},
@@ -175,4 +181,44 @@ export const useTabs: UseStore = create((set, get) => ({
setIsQueryErrorThrown: (state: boolean) => {
set({ isQueryErrorThrown: state });
},
+ getCurrentTabIndex: () => {
+ const state = get();
+ if (state.activeReactTab !== undefined) {
+ return state.openedReactTabs.indexOf(state.activeReactTab);
+ } else if (state.activeTab !== undefined) {
+ const nonReactTabIndex = state.openedTabs.indexOf(state.activeTab);
+ if (nonReactTabIndex !== -1) {
+ return state.openedReactTabs.length + nonReactTabIndex;
+ }
+ }
+
+ return -1;
+ },
+ selectTabByIndex: (index: number) => {
+ const state = get();
+ const totalTabCount = state.openedReactTabs.length + state.openedTabs.length;
+ const clampedIndex = clamp(index, totalTabCount - 1, 0);
+
+ if (clampedIndex < state.openedReactTabs.length) {
+ set({ activeTab: undefined, activeReactTab: state.openedReactTabs[clampedIndex] });
+ } else {
+ set({ activeTab: state.openedTabs[clampedIndex - state.openedReactTabs.length], activeReactTab: undefined });
+ }
+ },
+ selectLeftTab: () => {
+ const state = get();
+ state.selectTabByIndex(state.getCurrentTabIndex() - 1);
+ },
+ selectRightTab: () => {
+ const state = get();
+ state.selectTabByIndex(state.getCurrentTabIndex() + 1);
+ },
+ closeActiveTab: () => {
+ const state = get();
+ if (state.activeReactTab !== undefined) {
+ state.closeReactTab(state.activeReactTab);
+ } else if (state.activeTab !== undefined) {
+ state.closeTab(state.activeTab);
+ }
+ },
}));
diff --git a/test/cassandra/container.spec.ts b/test/cassandra/container.spec.ts
index 209c34eec..80d5df41d 100644
--- a/test/cassandra/container.spec.ts
+++ b/test/cassandra/container.spec.ts
@@ -1,15 +1,18 @@
import { jest } from "@jest/globals";
import "expect-playwright";
-import { generateUniqueName } from "../utils/shared";
+import { generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
import { waitForExplorer } from "../utils/waitForExplorer";
jest.setTimeout(120000);
test("Cassandra keyspace and table CRUD", async () => {
const keyspaceId = generateUniqueName("keyspace");
const tableId = generateUniqueName("table");
+
+ // We can't retrieve AZ CLI credentials from the browser so we get them here.
+ const token = await getAzureCLICredentialsToken();
page.setDefaultTimeout(50000);
- await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-cassandra-runner");
+ await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-cassandra-runner&token=${token}`);
await page.waitForSelector("iframe");
const explorer = await waitForExplorer();
@@ -20,11 +23,11 @@ test("Cassandra keyspace and table CRUD", async () => {
await explorer.fill('[aria-label="addCollection-table Id Create table"]', tableId);
await explorer.click("#sidePanelOkButton");
await explorer.click(`.nodeItem >> text=${keyspaceId}`);
- await explorer.click(`[data-test="${tableId}"] [aria-label="More"]`);
+ await explorer.click(`[data-test="${tableId}"] [aria-label="More options"]`);
await explorer.click('button[role="menuitem"]:has-text("Delete Table")');
await explorer.fill('text=* Confirm by typing the table id >> input[type="text"]', tableId);
await explorer.click('[aria-label="OK"]');
- await explorer.click(`[data-test="${keyspaceId}"] [aria-label="More"]`);
+ await explorer.click(`[data-test="${keyspaceId}"] [aria-label="More options"]`);
await explorer.click('button[role="menuitem"]:has-text("Delete Keyspace")');
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
await explorer.fill('text=* Confirm by typing the database id >> input[type="text"]', keyspaceId);
diff --git a/test/graph/container.spec.ts b/test/graph/container.spec.ts
index 342c12d4b..e7f288da5 100644
--- a/test/graph/container.spec.ts
+++ b/test/graph/container.spec.ts
@@ -1,15 +1,18 @@
import { jest } from "@jest/globals";
import "expect-playwright";
-import { generateDatabaseNameWithTimestamp, generateUniqueName } from "../utils/shared";
+import { generateDatabaseNameWithTimestamp, generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
import { waitForExplorer } from "../utils/waitForExplorer";
jest.setTimeout(240000);
test("Graph CRUD", async () => {
const databaseId = generateDatabaseNameWithTimestamp();
const containerId = generateUniqueName("container");
+
+ // We can't retrieve AZ CLI credentials from the browser so we get them here.
+ const token = await getAzureCLICredentialsToken();
page.setDefaultTimeout(50000);
- await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-gremlin-runner");
+ await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-gremlin-runner&token=${token}`);
const explorer = await waitForExplorer();
// Create new database and graph
@@ -21,11 +24,11 @@ test("Graph CRUD", async () => {
await explorer.click(`.nodeItem >> text=${databaseId}`);
await explorer.click(`.nodeItem >> text=${containerId}`);
// Delete database and graph
- await explorer.click(`[data-test="${containerId}"] [aria-label="More"]`);
+ await explorer.click(`[data-test="${containerId}"] [aria-label="More options"]`);
await explorer.click('button[role="menuitem"]:has-text("Delete Graph")');
await explorer.fill('text=* Confirm by typing the graph id >> input[type="text"]', containerId);
await explorer.click('[aria-label="OK"]');
- await explorer.click(`[data-test="${databaseId}"] [aria-label="More"]`);
+ await explorer.click(`[data-test="${databaseId}"] [aria-label="More options"]`);
await explorer.click('button[role="menuitem"]:has-text("Delete Database")');
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
await explorer.fill('text=* Confirm by typing the database id >> input[type="text"]', databaseId);
diff --git a/test/mongo/container.spec.ts b/test/mongo/container.spec.ts
index d6a9271ed..baafefbbe 100644
--- a/test/mongo/container.spec.ts
+++ b/test/mongo/container.spec.ts
@@ -1,15 +1,18 @@
import { jest } from "@jest/globals";
import "expect-playwright";
-import { generateDatabaseNameWithTimestamp, generateUniqueName } from "../utils/shared";
+import { generateDatabaseNameWithTimestamp, generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
import { waitForExplorer } from "../utils/waitForExplorer";
jest.setTimeout(240000);
test("Mongo CRUD", async () => {
const databaseId = generateDatabaseNameWithTimestamp();
const containerId = generateUniqueName("container");
+
+ // We can't retrieve AZ CLI credentials from the browser so we get them here.
+ const token = await getAzureCLICredentialsToken();
page.setDefaultTimeout(50000);
- await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-mongo-runner");
+ await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-mongo-runner&token=${token}`);
const explorer = await waitForExplorer();
// Create new database and collection
@@ -32,11 +35,11 @@ test("Mongo CRUD", async () => {
await explorer.click('[aria-label="Delete index Button"]');
await explorer.click('[data-test="Save"]');
// Delete database and collection
- await explorer.click(`[data-test="${containerId}"] [aria-label="More"]`);
+ await explorer.click(`[data-test="${containerId}"] [aria-label="More options"]`);
await explorer.click('button[role="menuitem"]:has-text("Delete Collection")');
await explorer.fill('text=* Confirm by typing the collection id >> input[type="text"]', containerId);
await explorer.click('[aria-label="OK"]');
- await explorer.click(`[data-test="${databaseId}"] [aria-label="More"]`);
+ await explorer.click(`[data-test="${databaseId}"] [aria-label="More options"]`);
await explorer.click('button[role="menuitem"]:has-text("Delete Database")');
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
await explorer.fill('text=* Confirm by typing the database id >> input[type="text"]', databaseId);
diff --git a/test/mongo/container32.spec.ts b/test/mongo/container32.spec.ts
index ac6dbc39c..c71f9d0cc 100644
--- a/test/mongo/container32.spec.ts
+++ b/test/mongo/container32.spec.ts
@@ -1,15 +1,18 @@
import { jest } from "@jest/globals";
import "expect-playwright";
-import { generateDatabaseNameWithTimestamp, generateUniqueName } from "../utils/shared";
+import { generateDatabaseNameWithTimestamp, generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
import { waitForExplorer } from "../utils/waitForExplorer";
jest.setTimeout(240000);
test("Mongo CRUD", async () => {
const databaseId = generateDatabaseNameWithTimestamp();
const containerId = generateUniqueName("container");
+
+ // We can't retrieve AZ CLI credentials from the browser so we get them here.
+ const token = await getAzureCLICredentialsToken();
page.setDefaultTimeout(50000);
- await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-mongo32-runner");
+ await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-mongo32-runner&token=${token}`);
const explorer = await waitForExplorer();
// Create new database and collection
@@ -21,11 +24,11 @@ test("Mongo CRUD", async () => {
explorer.click(`.nodeItem >> text=${databaseId}`);
explorer.click(`.nodeItem >> text=${containerId}`);
// Delete database and collection
- explorer.click(`[data-test="${containerId}"] [aria-label="More"]`);
+ explorer.click(`[data-test="${containerId}"] [aria-label="More options"]`);
explorer.click('button[role="menuitem"]:has-text("Delete Collection")');
await explorer.fill('text=* Confirm by typing the collection id >> input[type="text"]', containerId);
await explorer.click('[aria-label="OK"]');
- await explorer.click(`[data-test="${databaseId}"] [aria-label="More"]`);
+ await explorer.click(`[data-test="${databaseId}"] [aria-label="More options"]`);
await explorer.click('button[role="menuitem"]:has-text("Delete Database")');
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
await explorer.fill('text=* Confirm by typing the database id >> input[type="text"]', databaseId);
diff --git a/test/selfServe/selfServeExample.spec.ts b/test/selfServe/selfServeExample.spec.ts
index 7e10c1ce2..3678f5b35 100644
--- a/test/selfServe/selfServeExample.spec.ts
+++ b/test/selfServe/selfServeExample.spec.ts
@@ -1,5 +1,10 @@
+import { getAzureCLICredentialsToken } from "../utils/shared";
+
test("Self Serve", async () => {
- await page.goto("https://localhost:1234/testExplorer.html?iframeSrc=selfServe.html");
+ // We can't retrieve AZ CLI credentials from the browser so we get them here.
+ const token = await getAzureCLICredentialsToken();
+
+ await page.goto(`https://localhost:1234/testExplorer.html?iframeSrc=selfServe.html&token=${token}`);
const handle = await page.waitForSelector("iframe");
const frame = await handle.contentFrame();
diff --git a/test/sql/container.spec.ts b/test/sql/container.spec.ts
index 975687aaa..a1aacfd42 100644
--- a/test/sql/container.spec.ts
+++ b/test/sql/container.spec.ts
@@ -1,15 +1,18 @@
import { jest } from "@jest/globals";
import "expect-playwright";
-import { generateUniqueName } from "../utils/shared";
+import { generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
import { waitForExplorer } from "../utils/waitForExplorer";
jest.setTimeout(120000);
test("SQL CRUD", async () => {
const databaseId = generateUniqueName("db");
const containerId = generateUniqueName("container");
+
+ // We can't retrieve AZ CLI credentials from the browser so we get them here.
+ const token = await getAzureCLICredentialsToken();
page.setDefaultTimeout(50000);
- await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-sql-runner-west-us");
+ await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-sql-runner-west-us&token=${token}`);
const explorer = await waitForExplorer();
await explorer.click('[data-test="New Container"]');
@@ -18,11 +21,11 @@ test("SQL CRUD", async () => {
await explorer.fill('[aria-label="Partition key"]', "/pk");
await explorer.click("#sidePanelOkButton");
await explorer.click(`.nodeItem >> text=${databaseId}`);
- await explorer.click(`[data-test="${containerId}"] [aria-label="More"]`);
+ await explorer.click(`[data-test="${containerId}"] [aria-label="More options"]`);
await explorer.click('button[role="menuitem"]:has-text("Delete Container")');
await explorer.fill('text=* Confirm by typing the container id >> input[type="text"]', containerId);
await explorer.click('[aria-label="OK"]');
- await explorer.click(`[data-test="${databaseId}"] [aria-label="More"]`);
+ await explorer.click(`[data-test="${databaseId}"] [aria-label="More options"]`);
await explorer.click('button[role="menuitem"]:has-text("Delete Database")');
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
await explorer.fill('text=* Confirm by typing the database id >> input[type="text"]', databaseId);
diff --git a/test/sql/resourceToken.spec.ts b/test/sql/resourceToken.spec.ts
index f2e9d94ef..18228c7ed 100644
--- a/test/sql/resourceToken.spec.ts
+++ b/test/sql/resourceToken.spec.ts
@@ -1,19 +1,15 @@
import { CosmosDBManagementClient } from "@azure/arm-cosmosdb";
import { CosmosClient, PermissionMode } from "@azure/cosmos";
-import * as msRestNodeAuth from "@azure/ms-rest-nodeauth";
import { jest } from "@jest/globals";
import "expect-playwright";
-import { generateUniqueName } from "../utils/shared";
+import { generateUniqueName, getAzureCLICredentials } from "../utils/shared";
jest.setTimeout(120000);
-const clientId = "fd8753b0-0707-4e32-84e9-2532af865fb4";
-const secret = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET"];
-const tenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
-const subscriptionId = "69e02f2d-f059-4409-9eac-97e8a276ae2c";
+const subscriptionId = process.env["AZURE_SUBSCRIPTION_ID"] ?? "";
const resourceGroupName = "runners";
test("Resource token", async () => {
- const credentials = await msRestNodeAuth.loginWithServicePrincipalSecret(clientId, secret, tenantId);
+ const credentials = await getAzureCLICredentials();
const armClient = new CosmosDBManagementClient(credentials, subscriptionId);
const account = await armClient.databaseAccounts.get(resourceGroupName, "portal-sql-runner-west-us");
const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, "portal-sql-runner-west-us");
diff --git a/test/tables/container.spec.ts b/test/tables/container.spec.ts
index 932127bb1..98687d60f 100644
--- a/test/tables/container.spec.ts
+++ b/test/tables/container.spec.ts
@@ -1,15 +1,17 @@
import { jest } from "@jest/globals";
import "expect-playwright";
-import { generateUniqueName } from "../utils/shared";
+import { generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
import { waitForExplorer } from "../utils/waitForExplorer";
jest.setTimeout(120000);
test("Tables CRUD", async () => {
const tableId = generateUniqueName("table");
+ // We can't retrieve AZ CLI credentials from the browser so we get them here.
+ const token = await getAzureCLICredentialsToken();
page.setDefaultTimeout(50000);
- await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-tables-runner");
+ await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-tables-runner&token=${token}`);
const explorer = await waitForExplorer();
await page.waitForSelector('text="Querying databases"', { state: "detached" });
@@ -17,7 +19,7 @@ test("Tables CRUD", async () => {
await explorer.fill('[aria-label="Table id, Example Table1"]', tableId);
await explorer.click("#sidePanelOkButton");
await explorer.click(`[data-test="TablesDB"]`);
- await explorer.click(`[data-test="${tableId}"] [aria-label="More"]`);
+ await explorer.click(`[data-test="${tableId}"] [aria-label="More options"]`);
await explorer.click('button[role="menuitem"]:has-text("Delete Table")');
await explorer.fill('text=* Confirm by typing the table id >> input[type="text"]', tableId);
await explorer.click('[aria-label="OK"]');
diff --git a/test/testExplorer/TestExplorer.ts b/test/testExplorer/TestExplorer.ts
index 540de9768..4dbeb86b6 100644
--- a/test/testExplorer/TestExplorer.ts
+++ b/test/testExplorer/TestExplorer.ts
@@ -1,5 +1,4 @@
/* eslint-disable no-console */
-import { ClientSecretCredential } from "@azure/identity";
import "../../less/hostedexplorer.less";
import { DataExplorerInputsFrame } from "../../src/Contracts/ViewModels";
import { updateUserContext } from "../../src/UserContext";
@@ -11,29 +10,13 @@ const urlSearchParams = new URLSearchParams(window.location.search);
const accountName = urlSearchParams.get("accountName") || "portal-sql-runner-west-us";
const selfServeType = urlSearchParams.get("selfServeType") || "example";
const iframeSrc = urlSearchParams.get("iframeSrc") || "explorer.html?platform=Portal&disablePortalInitCache";
-
-if (!process.env.AZURE_CLIENT_SECRET) {
- throw new Error(
- "process.env.AZURE_CLIENT_SECRET was not set! Set it in your .env file and restart webpack dev server",
- );
-}
-
-// Azure SDK clients accept the credential as a parameter
-const credentials = new ClientSecretCredential(
- process.env.AZURE_TENANT_ID,
- process.env.AZURE_CLIENT_ID,
- process.env.AZURE_CLIENT_SECRET,
- {
- authorityHost: "https://localhost:1234",
- },
-);
+const token = urlSearchParams.get("token");
console.log("Resource Group:", resourceGroup);
console.log("Subcription: ", subscriptionId);
console.log("Account Name: ", accountName);
const initTestExplorer = async (): Promise => {
- const { token } = await credentials.getToken("https://management.azure.com//.default");
updateUserContext({
authorizationToken: `bearer ${token}`,
});
@@ -52,9 +35,11 @@ const initTestExplorer = async (): Promise => {
dnsSuffix: "documents.azure.com",
serverId: "prod1",
extensionEndpoint: "/proxy",
+ portalBackendEndpoint: "https://cdb-ms-mpac-pbe.cosmos.azure.com",
+ mongoProxyEndpoint: "https://cdb-ms-mpac-mp.cosmos.azure.com",
+ cassandraProxyEndpoint: "https://cdb-ms-mpac-cp.cosmos.azure.com",
subscriptionType: 3,
quotaId: "Internal_2014-09-01",
- addCollectionDefaultFlight: "2",
isTryCosmosDBSubscription: false,
masterKey: keys.primaryMasterKey,
loadDatabaseAccountTimestamp: 1604663109836,
diff --git a/test/utils/shared.ts b/test/utils/shared.ts
index 118736129..59ef0994c 100644
--- a/test/utils/shared.ts
+++ b/test/utils/shared.ts
@@ -1,3 +1,4 @@
+import { AzureCliCredentials } from "@azure/ms-rest-nodeauth";
import crypto from "crypto";
export function generateUniqueName(baseName = "", length = 4): string {
@@ -7,3 +8,13 @@ export function generateUniqueName(baseName = "", length = 4): string {
export function generateDatabaseNameWithTimestamp(baseName = "db", length = 1): string {
return `${baseName}${crypto.randomBytes(length).toString("hex")}-${Date.now()}`;
}
+
+export async function getAzureCLICredentials(): Promise {
+ return await AzureCliCredentials.create();
+}
+
+export async function getAzureCLICredentialsToken(): Promise {
+ const credentials = await getAzureCLICredentials();
+ const token = (await credentials.getToken()).accessToken;
+ return token;
+}
diff --git a/tsconfig.strict.json b/tsconfig.strict.json
index be0195655..969c39d01 100644
--- a/tsconfig.strict.json
+++ b/tsconfig.strict.json
@@ -112,6 +112,7 @@
"./src/Utils/BlobUtils.ts",
"./src/Utils/CapabilityUtils.ts",
"./src/Utils/CloudUtils.ts",
+ "./src/Utils/EndpointUtils.ts",
"./src/Utils/GitHubUtils.test.ts",
"./src/Utils/GitHubUtils.ts",
"./src/Utils/MessageValidation.test.ts",
diff --git a/utils/armClientGenerator/generator.ts b/utils/armClientGenerator/generator.ts
index 01a7e2e72..1a50e9082 100644
--- a/utils/armClientGenerator/generator.ts
+++ b/utils/armClientGenerator/generator.ts
@@ -16,13 +16,13 @@ Results of this file should be checked into the repo.
*/
// CHANGE THESE VALUES TO GENERATE NEW CLIENTS
-const version = "2023-09-15-preview";
+const version = "2024-02-15-preview";
/* The following are legal options for resourceName but you generally will only use cosmos-db:
"cosmos-db" | "managedCassandra" | "mongorbac" | "notebook" | "privateEndpointConnection" | "privateLinkResources" |
-"rbac" | "restorable" | "services"
+"rbac" | "restorable" | "services" | "dataTransferService"
*/
const githubResourceName = "cosmos-db";
-const deResourceName = "cosmos";
+const deResourceName = "cosmos-db";
const schemaURL = `https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/${version}/${githubResourceName}.json`;
const outputDir = path.join(__dirname, `../../src/Utils/arm/generatedClients/${deResourceName}`);
@@ -117,9 +117,9 @@ const propertyToType = (property: Property, prop: string, required: boolean) =>
if (property.allOf) {
outputTypes.push(`
/* ${property.description || "undocumented"} */
- ${property.readOnly ? "readonly " : ""}${prop}${
- required ? "" : "?"
- }: ${property.allOf.map((allof: { $ref: string }) => refToType(allof.$ref)).join(" & ")}`);
+ ${property.readOnly ? "readonly " : ""}${prop}${required ? "" : "?"}: ${property.allOf
+ .map((allof: { $ref: string }) => refToType(allof.$ref))
+ .join(" & ")}`);
} else if (property.$ref) {
const type = refToType(property.$ref);
outputTypes.push(`
@@ -142,8 +142,8 @@ const propertyToType = (property: Property, prop: string, required: boolean) =>
outputTypes.push(`
/* ${property.description || "undocumented"} */
${property.readOnly ? "readonly " : ""}${prop}${required ? "" : "?"}: ${property.enum
- .map((v: string) => `"${v}"`)
- .join(" | ")}
+ .map((v: string) => `"${v}"`)
+ .join(" | ")}
`);
} else {
if (property.type === undefined) {
@@ -153,8 +153,8 @@ const propertyToType = (property: Property, prop: string, required: boolean) =>
outputTypes.push(`
/* ${property.description || "undocumented"} */
${property.readOnly ? "readonly " : ""}${prop}${required ? "" : "?"}: ${
- propertyMap[property.type] ? propertyMap[property.type] : property.type
- }`);
+ propertyMap[property.type] ? propertyMap[property.type] : property.type
+ }`);
}
}
};
@@ -247,7 +247,7 @@ async function main() {
const operation = schema.paths[path][method];
const [, methodName] = operation.operationId.split("_");
const bodyParameter = operation.parameters.find(
- (parameter: { in: string; required: boolean }) => parameter.in === "body" && parameter.required === true
+ (parameter: { in: string; required: boolean }) => parameter.in === "body" && parameter.required === true,
);
outputClient.push(`
/* ${operation.description || "undocumented"} */
@@ -259,8 +259,8 @@ async function main() {
) : Promise<${responseType(operation, "Types")}> {
const path = \`${path.replace(/{/g, "${")}\`
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "${method.toLocaleUpperCase()}", apiVersion, ${
- bodyParameter ? "body" : ""
- } })
+ bodyParameter ? "body" : ""
+ } })
}
`);
}
diff --git a/utils/cleanupDBs.js b/utils/cleanupDBs.js
index 72fcfbafd..b2bbf0be8 100644
--- a/utils/cleanupDBs.js
+++ b/utils/cleanupDBs.js
@@ -2,10 +2,7 @@ const msRestNodeAuth = require("@azure/ms-rest-nodeauth");
const { CosmosDBManagementClient } = require("@azure/arm-cosmosdb");
const ms = require("ms");
-const clientId = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_ID"];
-const secret = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET"];
-const tenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
-const subscriptionId = "69e02f2d-f059-4409-9eac-97e8a276ae2c";
+const subscriptionId = process.env["AZURE_SUBSCRIPTION_ID"];
const resourceGroupName = "runners";
const thirtyMinutesAgo = new Date(Date.now() - 1000 * 60 * 30).getTime();
@@ -19,7 +16,7 @@ function friendlyTime(date) {
}
async function main() {
- const credentials = await msRestNodeAuth.loginWithServicePrincipalSecret(clientId, secret, tenantId);
+ const credentials = await msRestNodeAuth.AzureCliCredentials.create();
const client = new CosmosDBManagementClient(credentials, subscriptionId);
const accounts = await client.databaseAccounts.list(resourceGroupName);
for (const account of accounts) {
@@ -38,7 +35,7 @@ async function main() {
} else if (account.capabilities.find((c) => c.name === "EnableCassandra")) {
const cassandraDatabases = await client.cassandraResources.listCassandraKeyspaces(
resourceGroupName,
- account.name
+ account.name,
);
for (const database of cassandraDatabases) {
const timestamp = Number(database.resource._ts) * 1000;
diff --git a/web.config b/web.config
index 9d9ff2619..4a967e52a 100644
--- a/web.config
+++ b/web.config
@@ -30,7 +30,7 @@
-
+