Compare commits

..

66 Commits

Author SHA1 Message Date
Senthamil Sindhu
a6473adf40 Add recent changes 2024-11-19 20:56:54 -08:00
Senthamil Sindhu
451316cad4 Merge branch 'users/sindhuba/listKeys' into users/sindhuba/refresh-token 2024-10-27 20:59:04 -07:00
Senthamil Sindhu
b456e53b2f Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-10-10 00:16:31 -07:00
sindhuba
ac2e2a6f8e Add tenantId info in Data Explorer while opening from Portal (#1987)
* Fix API endpoint for CassandraProxy query API

* activate Mongo Proxy and Cassandra Proxy in Prod

* Add CP Prod endpoint

* Run npm format and tests

* Revert code

* fix bug that blocked local mongo proxy and cassandra proxy development

* Add prod endpoint

* fix pr check tests

* Remove prod

* Remove prod endpoint

* Remove dev endpoint

* Support data plane RBAC

* Support data plane RBAC

* Add additional changes for Portal RBAC functionality

* Remove unnecessary code

* Remove unnecessary code

* Add code to fix VCoreMongo/PG bug

* Address feedback

* Add more logs for RBAC feature

* Add more logs for RBAC features

* Add AAD endpoints for all environments

* Add AAD endpoints

* Run npm format

* Support multi-tenant switching for Data plane RBAC

* Run npm format

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-10-09 15:41:58 -07:00
Laurent Nguyen
3138580eae Move column selection out of mpac (#1980) 2024-10-09 14:23:31 +02:00
Senthamil Sindhu
de5ba041e9 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-10-08 14:40:04 -07:00
SATYA SB
aa88815c6e [Keyboard Navigation - Azure Cosmos DB - Data Explorer]: Keyboard focus is not retaining back to 'more' button after closing 'Delete container' dialog. (#1978)
* [accessibility-2262594]: [Keyboard Navigation - Azure Cosmos DB - Data Explorer]: Keyboard focus is not retaining back to 'more' button after closing 'Delete container' dialog.

* Optimize closeSidePanel: add timeout cleanup to prevent memory leaks and ensure proper focus behavior

---------

Co-authored-by: Satyapriya Bai <v-satybai@microsoft.com>
2024-10-07 09:09:23 +05:30
Senthamil Sindhu
ae7184f7ea Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-10-04 15:50:19 -07:00
Vsevolod Kukol
5a2f78b51e Improve Entra ID token acquisition logic (#1940)
* Add a silent parameter to acquireTokenWithMsal

If true, the function won't retry to sign in using a Popup if silent token acquisition fails.

* Improve Login for Entra ID RBAC button logic

Try to reuse an existing signed-in MSAL account to get the AAD token
and fall back to full sign-in otherwise.

Also move the logic to AuthorizationUtils

* Try to acquire an Entra ID token silently on startup.

When running in Portal MSAL should be able to reuse the
MSAL account from Portal and allow us to silently get
the RBAC token. If it fails we'll show the Login for Entry ID RBAC
button as usual.

* Small code improvements

* Remove the RBAC notice from settings pane
and try to acquire RBAC token silently after enabling RBAC.

* Use msal.ssoSilent with an optional login hint
to avoid more sign-in popups.
msal.loginPopup will be used as a backup option if ssoSilent fails.
Ideally the parent environment (Portal/Fabric) should send
a loginHint with the username of the currently signed in user that
can be passed to the token acquisition flow.

* Improve RBAC error wording, clarifying where to find the Login button.
2024-10-04 08:45:29 +02:00
Senthamil Sindhu
3a6769280b t Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-10-02 19:56:51 -07:00
bogercraig
fbc2e1335b Pull Additional Allowed Cassandra and Mongo Proxy Endpoints from Deployed Config (#1984)
* Updating to take default cassandra proxy endpoints from external config.json.

* Updating allow list for mongo proxy endpoints.
2024-10-02 14:05:21 -07:00
Senthamil Sindhu
4768ba3642 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-10-01 19:55:09 -07:00
SATYA SB
eb0d7b71b3 [accessibility-3100029]:[Screen Reader - Azure Cosmos DB - Add Table Row]: Descriptive Label is not provided for 'Value' edit fields under 'Add Table Row' pane. (#1970)
Co-authored-by: Satyapriya Bai <v-satybai@microsoft.com>
2024-10-01 09:15:25 +05:30
Asier Isayas
261289b031 Remove legacy backend references in tests and local dev (#1983)
* remove legacy backend references in tests and local dev

* fix unit tests

* fixed bulk delete

* fix tests

* fix cosmosclient

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-09-30 14:34:37 -04:00
Asier Isayas
fae4589427 Bulk Delete API fix (#1977)
* Bulk Delete API fix

* Bulk Delete API fix

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-09-30 11:40:48 -04:00
jawelton74
cbcb7e6240 Switch E2E tests to use new accounts. (#1982) 2024-09-30 07:29:24 -07:00
jawelton74
e0b773d920 Set shared throughput default to false for New Databases (#1981)
* Introduce common function for shared throughput default and set to
false.

* Add new file.

* Adjust E2E tests to not set throughput for database create.
2024-09-27 09:59:41 -07:00
Ashley Stanton-Nurse
9ec2cea95c Ensure the "Ctrl+Alt+["/"Ctrl+Alt+]" shortcuts don't get triggered on "AltGr+8"/"AltGr+9" (#1979)
* Remove the "Ctrl+Alt+[" and "Ctrl+Alt+]" shortcuts, as they conflict on non-US keyboard layouts

* Use "BracketLeft" and "BracketRight" to re-enable shortcut for US keyboards
2024-09-25 09:15:54 -07:00
Ashley Stanton-Nurse
1a4f713a79 Clarifying copy-edit to delete database panel (#1974)
* change 'Database id' to 'Database name' in Delete Database confirm prompt

* put 'name' in a parenthetical instead of replacing 'id'

* update test snapshots
2024-09-23 11:34:49 -07:00
Senthamil Sindhu
bd564c665b Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-09-17 16:12:01 -07:00
Senthamil Sindhu
7e5c6420ad Run npm format 2024-08-21 13:36:46 -07:00
Senthamil Sindhu
89a3a040d8 Add AAD endpoints 2024-08-21 13:31:02 -07:00
Senthamil Sindhu
ec3afa0526 Add AAD endpoints for all environments 2024-08-21 10:11:35 -07:00
Senthamil Sindhu
4176a8a9a9 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-08-21 10:11:04 -07:00
Senthamil Sindhu
311cf9aa5a Add AAD endpoints for all environments 2024-08-21 10:08:55 -07:00
Senthamil Sindhu
bc8094f44f Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-08-05 10:02:29 -07:00
Senthamil Sindhu
9203276a24 Add common code for ARM token refresh 2024-08-05 09:57:54 -07:00
Senthamil Sindhu
d7825f4f78 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-07-30 08:23:50 -07:00
Senthamil Sindhu
e51c28c634 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-07-24 12:32:54 -07:00
Senthamil Sindhu
3b86a9477f Add code for arm token refresh 2024-07-23 11:14:19 -07:00
Senthamil Sindhu
521ff39eb0 Resolve conflicts 2024-07-19 07:48:12 -07:00
Senthamil Sindhu
2e2db3c2a9 Merge branch 'users/sindhuba/fix-listKeys' into users/sindhuba/listKeys 2024-07-19 07:37:14 -07:00
Senthamil Sindhu
2b84af60f4 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-07-19 07:31:11 -07:00
Senthamil Sindhu
40283ff7f1 Add readOnlyKeys call for accounts with Reader role 2024-07-19 07:28:39 -07:00
Senthamil Sindhu
29a1a819c3 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-07-16 14:28:50 -07:00
Senthamil Sindhu
5a16eec29d Add more logs for RBAC features 2024-07-10 07:38:37 -07:00
Senthamil Sindhu
2b11e0e52b Add more logs for RBAC feature 2024-07-10 07:30:34 -07:00
Senthamil Sindhu
89374a16ba Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-07-09 21:13:41 -07:00
Senthamil Sindhu
dc289ece75 Address feedback 2024-07-09 07:31:09 -07:00
Senthamil Sindhu
1ddd372c6d Add code to fix VCoreMongo/PG bug 2024-07-08 14:46:11 -07:00
Senthamil Sindhu
2740657b4a Remove unnecessary code 2024-07-08 14:39:49 -07:00
Senthamil Sindhu
8c888a751c Remove unnecessary code 2024-07-08 14:26:04 -07:00
Senthamil Sindhu
8140f0edb1 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-07-08 13:45:54 -07:00
Senthamil Sindhu
ab5239df09 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-07-08 13:27:24 -07:00
Senthamil Sindhu
3e48393fbb Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-07-03 00:07:17 -07:00
Senthamil Sindhu
0079a9147f Resolved merge conflict 2024-07-01 16:22:04 -07:00
Senthamil Sindhu
912688dc14 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-06-27 11:00:31 -07:00
Senthamil Sindhu
8849526fab Merge branch 'add-dp-rbac' of https://github.com/Azure/cosmos-explorer 2024-06-19 15:20:20 -07:00
Senthamil Sindhu
24af64a66d Add additional changes for Portal RBAC functionality 2024-06-19 15:05:14 -07:00
Senthamil Sindhu
be871737ad Support data plane RBAC 2024-06-14 12:45:21 -07:00
Senthamil Sindhu
4d8bb5c3ea Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-06-14 12:18:14 -07:00
Senthamil Sindhu
10a8505b9a Support data plane RBAC 2024-06-14 12:12:30 -07:00
Senthamil Sindhu
ef7c2fe2f7 Remove dev endpoint 2024-04-10 11:59:57 -07:00
Senthamil Sindhu
4c7aca95e1 Merge branch 'users/aisayas/mp-cp-activate-prod' of https://github.com/Azure/cosmos-explorer into users/sindhuba/activate-prod 2024-04-09 12:27:51 -07:00
Senthamil Sindhu
2243ad895a Remove prod endpoint 2024-04-09 12:16:13 -07:00
Senthamil Sindhu
b2d5f91fe1 Remove prod 2024-04-09 11:22:17 -07:00
Asier Isayas
a712193477 fix pr check tests 2024-04-09 11:43:24 -04:00
Senthamil Sindhu
5ee411693c Add prod endpoint 2024-04-09 08:41:47 -07:00
Asier Isayas
16c7b2567b fix bug that blocked local mongo proxy and cassandra proxy development 2024-04-09 11:39:11 -04:00
Senthamil Sindhu
78d9a0cd8d Revert code 2024-04-08 16:20:40 -07:00
Senthamil Sindhu
c6ad538559 Run npm format and tests 2024-04-08 15:58:10 -07:00
Senthamil Sindhu
2bc09a6efe Add CP Prod endpoint 2024-04-08 15:37:19 -07:00
Senthamil Sindhu
d3a3033b25 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-04-08 15:32:50 -07:00
Asier Isayas
6bdc714e11 activate Mongo Proxy and Cassandra Proxy in Prod 2024-04-08 16:52:09 -04:00
Senthamil Sindhu
5042f28229 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-03-25 15:11:53 -07:00
Senthamil Sindhu
e1430fd06f Fix API endpoint for CassandraProxy query API 2024-03-18 10:25:17 -07:00
74 changed files with 1005 additions and 605 deletions

View File

@@ -18,7 +18,7 @@ Run `npm start` to start the development server and automatically rebuild on cha
### Hosted Development (https://cosmos.azure.com) ### Hosted Development (https://cosmos.azure.com)
- Visit: `https://localhost:1234/hostedExplorer.html` - Visit: `https://localhost:1234/hostedExplorer.html`
- The default webpack dev server configuration will proxy requests to the production portal backend: `https://main.documentdb.ext.azure.com`. This will allow you to use production connection strings on your local machine. - The default webpack dev server configuration will proxy requests to the production portal backend: `https://cdb-ms-mpac-pbe.cosmos.azure.com`. This will allow you to use production connection strings on your local machine.
### Emulator Development ### Emulator Development

View File

@@ -82,7 +82,7 @@
</a> </a>
<ul> <ul>
<li>Visit: <code>https://localhost:1234/hostedExplorer.html</code></li> <li>Visit: <code>https://localhost:1234/hostedExplorer.html</code></li>
<li>The default webpack dev server configuration will proxy requests to the production portal backend: <code>https://main.documentdb.ext.azure.com</code>. This will allow you to use production connection strings on your local machine.</li> <li>The default webpack dev server configuration will proxy requests to the production portal backend: <code>https://cdb-ms-mpac-pbe.cosmos.azure.com</code>. This will allow you to use production connection strings on your local machine.</li>
</ul> </ul>
<a href="#emulator-development" id="emulator-development" style="color: inherit; text-decoration: none;"> <a href="#emulator-development" id="emulator-development" style="color: inherit; text-decoration: none;">
<h3>Emulator Development</h3> <h3>Emulator Development</h3>

View File

@@ -2618,7 +2618,6 @@ a:link {
.tabPanesContainer { .tabPanesContainer {
display: flex; display: flex;
flex-grow: 1; flex-grow: 1;
flex-direction: column;
overflow: hidden; overflow: hidden;
} }

View File

@@ -4,7 +4,7 @@ const port = process.env.PORT || 3000;
const fetch = require("node-fetch"); const fetch = require("node-fetch");
const api = createProxyMiddleware("/api", { const api = createProxyMiddleware("/api", {
target: "https://main.documentdb.ext.azure.com", target: "https://cdb-ms-mpac-pbe.cosmos.azure.com",
changeOrigin: true, changeOrigin: true,
logLevel: "debug", logLevel: "debug",
bypass: (req, res) => { bypass: (req, res) => {
@@ -16,7 +16,7 @@ const api = createProxyMiddleware("/api", {
}); });
const proxy = createProxyMiddleware("/proxy", { const proxy = createProxyMiddleware("/proxy", {
target: "https://main.documentdb.ext.azure.com", target: "https://cdb-ms-mpac-pbe.cosmos.azure.com",
changeOrigin: true, changeOrigin: true,
secure: false, secure: false,
logLevel: "debug", logLevel: "debug",

View File

@@ -155,6 +155,18 @@ export class MongoProxyEndpoints {
public static readonly Mooncake: string = "https://cdb-mc-prod-mp.cosmos.azure.cn"; public static readonly Mooncake: string = "https://cdb-mc-prod-mp.cosmos.azure.cn";
} }
export class MongoProxyApi {
public static readonly ResourceList: string = "ResourceList";
public static readonly QueryDocuments: string = "QueryDocuments";
public static readonly CreateDocument: string = "CreateDocumen";
public static readonly ReadDocument: string = "ReadDocument";
public static readonly UpdateDocument: string = "UpdateDocument";
public static readonly DeleteDocument: string = "DeleteDocument";
public static readonly CreateCollectionWithProxy: string = "CreateCollectionWithProxy";
public static readonly LegacyMongoShell: string = "LegacyMongoShell";
public static readonly BulkDelete: string = "BulkDelete";
}
export class CassandraProxyEndpoints { export class CassandraProxyEndpoints {
public static readonly Development: string = "https://localhost:7240"; public static readonly Development: string = "https://localhost:7240";
public static readonly Mpac: string = "https://cdb-ms-mpac-cp.cosmos.azure.com"; public static readonly Mpac: string = "https://cdb-ms-mpac-cp.cosmos.azure.com";

View File

@@ -1,4 +1,5 @@
import { Platform, resetConfigContext, updateConfigContext } from "../ConfigContext"; import { PortalBackendEndpoints } from "Common/Constants";
import { configContext, Platform, resetConfigContext, updateConfigContext } from "../ConfigContext";
import { updateUserContext } from "../UserContext"; import { updateUserContext } from "../UserContext";
import { endpoint, getTokenFromAuthService, requestPlugin } from "./CosmosClient"; import { endpoint, getTokenFromAuthService, requestPlugin } from "./CosmosClient";
@@ -20,22 +21,22 @@ describe("getTokenFromAuthService", () => {
it("builds the correct URL in production", () => { it("builds the correct URL in production", () => {
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com", PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod,
}); });
getTokenFromAuthService("GET", "dbs", "foo"); getTokenFromAuthService("GET", "dbs", "foo");
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
"https://main.documentdb.ext.azure.com/api/guest/runtimeproxy/authorizationTokens", `${configContext.PORTAL_BACKEND_ENDPOINT}/api/connectionstring/runtimeproxy/authorizationtokens`,
expect.any(Object), expect.any(Object),
); );
}); });
it("builds the correct URL in dev", () => { it("builds the correct URL in dev", () => {
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: "https://localhost:1234", PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Development,
}); });
getTokenFromAuthService("GET", "dbs", "foo"); getTokenFromAuthService("GET", "dbs", "foo");
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
"https://localhost:1234/api/guest/runtimeproxy/authorizationTokens", `${configContext.PORTAL_BACKEND_ENDPOINT}/api/connectionstring/runtimeproxy/authorizationtokens`,
expect.any(Object), expect.any(Object),
); );
}); });
@@ -78,7 +79,7 @@ describe("requestPlugin", () => {
const next = jest.fn(); const next = jest.fn();
updateConfigContext({ updateConfigContext({
platform: Platform.Hosted, platform: Platform.Hosted,
BACKEND_ENDPOINT: "https://localhost:1234", PORTAL_BACKEND_ENDPOINT: "https://localhost:1234",
PROXY_PATH: "/proxy", PROXY_PATH: "/proxy",
}); });
const headers = {}; const headers = {};

View File

@@ -8,11 +8,13 @@ import { AuthType } from "../AuthType";
import { BackendApi, PriorityLevel } from "../Common/Constants"; import { BackendApi, PriorityLevel } from "../Common/Constants";
import * as Logger from "../Common/Logger"; import * as Logger from "../Common/Logger";
import { Platform, configContext } from "../ConfigContext"; import { Platform, configContext } from "../ConfigContext";
import { userContext } from "../UserContext"; import { updateUserContext, userContext } from "../UserContext";
import { logConsoleError } from "../Utils/NotificationConsoleUtils"; import { logConsoleError } from "../Utils/NotificationConsoleUtils";
import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils"; import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils";
import { EmulatorMasterKey, HttpHeaders } from "./Constants"; import { EmulatorMasterKey, HttpHeaders } from "./Constants";
import { getErrorMessage } from "./ErrorHandlingUtils"; import { getErrorMessage } from "./ErrorHandlingUtils";
import { runCommand } from "hooks/useDatabaseAccounts";
import { acquireTokenWithMsal, getMsalInstance } from "Utils/AuthorizationUtils";
const _global = typeof self === "undefined" ? window : self; const _global = typeof self === "undefined" ? window : self;
@@ -27,12 +29,47 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
); );
if (!userContext.aadToken) { if (!userContext.aadToken) {
logConsoleError( logConsoleError(
`AAD token does not exist. Please click on "Login for Entra ID" button prior to performing Entra ID RBAC operations`, `AAD token does not exist. Please use the "Login for Entra ID" button in the Toolbar prior to performing Entra ID RBAC operations`,
); );
return null; return null;
} }
const AUTH_PREFIX = `type=aad&ver=1.0&sig=`; const AUTH_PREFIX = `type=aad&ver=1.0&sig=`;
const authorizationToken = `${AUTH_PREFIX}${userContext.aadToken}`; let authorizationToken;
try {
authorizationToken = `${AUTH_PREFIX}${userContext.aadToken}`;
} catch (error) {
if (error.code === "ExpiredAuthenticationToken") {
// Renew the AAD token using runCommand
const newToken = await runCommand(async () => {
// Implement the logic to acquire a new AAD token
const msalInstance = await getMsalInstance();
const cachedAccount = msalInstance.getAllAccounts()?.[0];
const cachedTenantId = localStorage.getItem("cachedTenantId");
msalInstance.setActiveAccount(cachedAccount);
const newAccessToken = await acquireTokenWithMsal(msalInstance, {
authority: `${configContext.AAD_ENDPOINT}${cachedTenantId}`,
scopes: [`${configContext.ARM_ENDPOINT}/.default`],
});
// Update user context with the new token
updateUserContext({ aadToken: newAccessToken });
authorizationToken = `${AUTH_PREFIX}${userContext.aadToken}`;
return newAccessToken;
});
// Retry getting the token after renewing
const retryResult = await getTokenFromAuthService(verb, resourceType, resourceId);
headers[HttpHeaders.msDate] = retryResult.XDate;
return decodeURIComponent(retryResult.PrimaryReadWriteToken);
} else {
console.error('An error occurred:', error.message);
throw error;
}
}
return authorizationToken; return authorizationToken;
} }

View File

@@ -0,0 +1,3 @@
export function getNewDatabaseSharedThroughputDefault(): boolean {
return false;
}

View File

@@ -10,6 +10,7 @@ export interface TableEntityProps {
isEntityValueDisable?: boolean; isEntityValueDisable?: boolean;
entityTimeValue: string; entityTimeValue: string;
entityValueType: string; entityValueType: string;
entityProperty: string;
onEntityValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void; onEntityValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
onSelectDate: (date: Date | null | undefined) => void; onSelectDate: (date: Date | null | undefined) => void;
onEntityTimeValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void; onEntityTimeValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
@@ -26,6 +27,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
onSelectDate, onSelectDate,
isEntityValueDisable, isEntityValueDisable,
onEntityTimeValueChange, onEntityTimeValueChange,
entityProperty,
}: TableEntityProps): JSX.Element => { }: TableEntityProps): JSX.Element => {
if (isEntityTypeDate) { if (isEntityTypeDate) {
return ( return (
@@ -51,15 +53,20 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
} }
return ( return (
<TextField <>
label={entityValueLabel && entityValueLabel} <span id={entityProperty} className="screenReaderOnly">
className="addEntityTextField" Edit Property {entityProperty} {attributeValueLabel}
disabled={isEntityValueDisable} </span>
type={entityValueType} <TextField
placeholder={entityValuePlaceholder} label={entityValueLabel && entityValueLabel}
value={typeof entityValue === "string" ? entityValue : ""} className="addEntityTextField"
onChange={onEntityValueChange} disabled={isEntityValueDisable}
ariaLabel={attributeValueLabel} type={entityValueType}
/> placeholder={entityValuePlaceholder}
value={typeof entityValue === "string" ? entityValue : ""}
onChange={onEntityValueChange}
aria-labelledby={entityProperty}
/>
</>
); );
}; };

View File

@@ -1,5 +1,6 @@
import { MongoProxyEndpoints } from "Common/Constants";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
import { resetConfigContext, updateConfigContext } from "../ConfigContext"; import { configContext, resetConfigContext, updateConfigContext } from "../ConfigContext";
import { DatabaseAccount } from "../Contracts/DataModels"; import { DatabaseAccount } from "../Contracts/DataModels";
import { Collection } from "../Contracts/ViewModels"; import { Collection } from "../Contracts/ViewModels";
import DocumentId from "../Explorer/Tree/DocumentId"; import DocumentId from "../Explorer/Tree/DocumentId";
@@ -71,7 +72,7 @@ describe("MongoProxyClient", () => {
databaseAccount, databaseAccount,
}); });
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com", MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
}); });
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@@ -82,16 +83,16 @@ describe("MongoProxyClient", () => {
it("builds the correct URL", () => { it("builds the correct URL", () => {
queryDocuments(databaseId, collection, true, "{}"); queryDocuments(databaseId, collection, true, "{}");
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
"https://main.documentdb.ext.azure.com/api/mongo/explorer/resourcelist?db=testDB&coll=testCollection&resourceUrl=bardbs%2FtestDB%2Fcolls%2FtestCollection%2Fdocs%2F&rid=testCollectionrid&rtype=docs&sid=&rg=&dba=foo&pk=pk", `${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer/resourcelist`,
expect.any(Object), expect.any(Object),
); );
}); });
it("builds the correct proxy URL in development", () => { it("builds the correct proxy URL in development", () => {
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" }); updateConfigContext({ MONGO_PROXY_ENDPOINT: "https://localhost:1234" });
queryDocuments(databaseId, collection, true, "{}"); queryDocuments(databaseId, collection, true, "{}");
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
"https://localhost:1234/api/mongo/explorer/resourcelist?db=testDB&coll=testCollection&resourceUrl=bardbs%2FtestDB%2Fcolls%2FtestCollection%2Fdocs%2F&rid=testCollectionrid&rtype=docs&sid=&rg=&dba=foo&pk=pk", `${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer/resourcelist`,
expect.any(Object), expect.any(Object),
); );
}); });
@@ -103,7 +104,7 @@ describe("MongoProxyClient", () => {
databaseAccount, databaseAccount,
}); });
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com", MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
}); });
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@@ -114,16 +115,16 @@ describe("MongoProxyClient", () => {
it("builds the correct URL", () => { it("builds the correct URL", () => {
readDocument(databaseId, collection, documentId); readDocument(databaseId, collection, documentId);
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
"https://main.documentdb.ext.azure.com/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk", `${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`,
expect.any(Object), expect.any(Object),
); );
}); });
it("builds the correct proxy URL in development", () => { it("builds the correct proxy URL in development", () => {
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" }); updateConfigContext({ MONGO_PROXY_ENDPOINT: "https://localhost:1234" });
readDocument(databaseId, collection, documentId); readDocument(databaseId, collection, documentId);
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
"https://localhost:1234/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk", `${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`,
expect.any(Object), expect.any(Object),
); );
}); });
@@ -135,7 +136,7 @@ describe("MongoProxyClient", () => {
databaseAccount, databaseAccount,
}); });
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com", MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
}); });
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@@ -146,16 +147,16 @@ describe("MongoProxyClient", () => {
it("builds the correct URL", () => { it("builds the correct URL", () => {
readDocument(databaseId, collection, documentId); readDocument(databaseId, collection, documentId);
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
"https://main.documentdb.ext.azure.com/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk", `${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`,
expect.any(Object), expect.any(Object),
); );
}); });
it("builds the correct proxy URL in development", () => { it("builds the correct proxy URL in development", () => {
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" }); updateConfigContext({ MONGO_PROXY_ENDPOINT: "https://localhost:1234" });
readDocument(databaseId, collection, documentId); readDocument(databaseId, collection, documentId);
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
"https://localhost:1234/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk", `${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`,
expect.any(Object), expect.any(Object),
); );
}); });
@@ -167,7 +168,7 @@ describe("MongoProxyClient", () => {
databaseAccount, databaseAccount,
}); });
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com", MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
}); });
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@@ -178,7 +179,7 @@ describe("MongoProxyClient", () => {
it("builds the correct URL", () => { it("builds the correct URL", () => {
updateDocument(databaseId, collection, documentId, "{}"); updateDocument(databaseId, collection, documentId, "{}");
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
"https://main.documentdb.ext.azure.com/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2Fdocs%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk", `${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`,
expect.any(Object), expect.any(Object),
); );
}); });
@@ -187,7 +188,7 @@ describe("MongoProxyClient", () => {
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" }); updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" });
updateDocument(databaseId, collection, documentId, "{}"); updateDocument(databaseId, collection, documentId, "{}");
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
"https://localhost:1234/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2Fdocs%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk", `${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`,
expect.any(Object), expect.any(Object),
); );
}); });
@@ -199,7 +200,7 @@ describe("MongoProxyClient", () => {
databaseAccount, databaseAccount,
}); });
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com", MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
}); });
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@@ -210,16 +211,16 @@ describe("MongoProxyClient", () => {
it("builds the correct URL", () => { it("builds the correct URL", () => {
deleteDocument(databaseId, collection, documentId); deleteDocument(databaseId, collection, documentId);
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
"https://main.documentdb.ext.azure.com/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2Fdocs%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk", `${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`,
expect.any(Object), expect.any(Object),
); );
}); });
it("builds the correct proxy URL in development", () => { it("builds the correct proxy URL in development", () => {
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" }); updateConfigContext({ MONGO_PROXY_ENDPOINT: "https://localhost:1234" });
deleteDocument(databaseId, collection, documentId); deleteDocument(databaseId, collection, documentId);
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
"https://localhost:1234/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2Fdocs%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk", `${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`,
expect.any(Object), expect.any(Object),
); );
}); });
@@ -231,13 +232,13 @@ describe("MongoProxyClient", () => {
databaseAccount, databaseAccount,
}); });
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com", MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
}); });
}); });
it("returns a production endpoint", () => { it("returns a production endpoint", () => {
const endpoint = getEndpoint("https://main.documentdb.ext.azure.com"); const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/mongo/explorer"); expect(endpoint).toEqual(`${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`);
}); });
it("returns a development endpoint", () => { it("returns a development endpoint", () => {
@@ -249,18 +250,19 @@ describe("MongoProxyClient", () => {
updateUserContext({ updateUserContext({
authType: AuthType.EncryptedToken, authType: AuthType.EncryptedToken,
}); });
const endpoint = getEndpoint("https://main.documentdb.ext.azure.com"); const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer"); expect(endpoint).toEqual(`${configContext.MONGO_PROXY_ENDPOINT}/api/connectionstring/mongo/explorer`);
}); });
}); });
describe("getFeatureEndpointOrDefault", () => { describe("getFeatureEndpointOrDefault", () => {
beforeEach(() => { beforeEach(() => {
resetConfigContext(); resetConfigContext();
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com", MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
}); });
const params = new URLSearchParams({ const params = new URLSearchParams({
"feature.mongoProxyEndpoint": "https://localhost:12901", "feature.mongoProxyEndpoint": MongoProxyEndpoints.Prod,
"feature.mongoProxyAPIs": "readDocument|createDocument", "feature.mongoProxyAPIs": "readDocument|createDocument",
}); });
const features = extractFeatures(params); const features = extractFeatures(params);
@@ -272,12 +274,12 @@ describe("MongoProxyClient", () => {
it("returns a local endpoint", () => { it("returns a local endpoint", () => {
const endpoint = getFeatureEndpointOrDefault("readDocument"); const endpoint = getFeatureEndpointOrDefault("readDocument");
expect(endpoint).toEqual("https://localhost:12901/api/mongo/explorer"); expect(endpoint).toEqual(`${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`);
}); });
it("returns a production endpoint", () => { it("returns a production endpoint", () => {
const endpoint = getFeatureEndpointOrDefault("deleteDocument"); const endpoint = getFeatureEndpointOrDefault("DeleteDocument");
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/mongo/explorer"); expect(endpoint).toEqual(`${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`);
}); });
}); });
}); });

View File

@@ -1,7 +1,7 @@
import { Constants as CosmosSDKConstants } from "@azure/cosmos"; import { Constants as CosmosSDKConstants } from "@azure/cosmos";
import { import {
allowedMongoProxyEndpoints,
allowedMongoProxyEndpoints_ToBeDeprecated, allowedMongoProxyEndpoints_ToBeDeprecated,
defaultAllowedMongoProxyEndpoints,
validateEndpoint, validateEndpoint,
} from "Utils/EndpointUtils"; } from "Utils/EndpointUtils";
import queryString from "querystring"; import queryString from "querystring";
@@ -14,7 +14,7 @@ import DocumentId from "../Explorer/Tree/DocumentId";
import { hasFlag } from "../Platform/Hosted/extractFeatures"; import { hasFlag } from "../Platform/Hosted/extractFeatures";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
import { logConsoleError } from "../Utils/NotificationConsoleUtils"; import { logConsoleError } from "../Utils/NotificationConsoleUtils";
import { ApiType, ContentType, HttpHeaders, HttpStatusCodes, MongoProxyEndpoints } from "./Constants"; import { ApiType, ContentType, HttpHeaders, HttpStatusCodes, MongoProxyApi, MongoProxyEndpoints } from "./Constants";
import { MinimalQueryIterator } from "./IteratorUtilities"; import { MinimalQueryIterator } from "./IteratorUtilities";
import { sendMessage } from "./MessageHandler"; import { sendMessage } from "./MessageHandler";
@@ -67,7 +67,7 @@ export function queryDocuments(
query: string, query: string,
continuationToken?: string, continuationToken?: string,
): Promise<QueryResponse> { ): Promise<QueryResponse> {
if (!useMongoProxyEndpoint("resourcelist") || !useMongoProxyEndpoint("queryDocuments")) { if (!useMongoProxyEndpoint(MongoProxyApi.ResourceList) || !useMongoProxyEndpoint(MongoProxyApi.QueryDocuments)) {
return queryDocuments_ToBeDeprecated(databaseId, collection, isResourceList, query, continuationToken); return queryDocuments_ToBeDeprecated(databaseId, collection, isResourceList, query, continuationToken);
} }
@@ -89,7 +89,7 @@ export function queryDocuments(
query, query,
}; };
const endpoint = getFeatureEndpointOrDefault("resourcelist") || ""; const endpoint = getFeatureEndpointOrDefault(MongoProxyApi.ResourceList) || "";
const headers = { const headers = {
...defaultHeaders, ...defaultHeaders,
@@ -194,7 +194,7 @@ export function readDocument(
collection: Collection, collection: Collection,
documentId: DocumentId, documentId: DocumentId,
): Promise<DataModels.DocumentId> { ): Promise<DataModels.DocumentId> {
if (!useMongoProxyEndpoint("readDocument")) { if (!useMongoProxyEndpoint(MongoProxyApi.ReadDocument)) {
return readDocument_ToBeDeprecated(databaseId, collection, documentId); return readDocument_ToBeDeprecated(databaseId, collection, documentId);
} }
const { databaseAccount } = userContext; const { databaseAccount } = userContext;
@@ -217,7 +217,7 @@ export function readDocument(
: "", : "",
}; };
const endpoint = getFeatureEndpointOrDefault("readDocument"); const endpoint = getFeatureEndpointOrDefault(MongoProxyApi.ReadDocument);
return window return window
.fetch(endpoint, { .fetch(endpoint, {
@@ -289,7 +289,7 @@ export function createDocument(
partitionKeyProperty: string, partitionKeyProperty: string,
documentContent: unknown, documentContent: unknown,
): Promise<DataModels.DocumentId> { ): Promise<DataModels.DocumentId> {
if (!useMongoProxyEndpoint("createDocument")) { if (!useMongoProxyEndpoint(MongoProxyApi.CreateDocument)) {
return createDocument_ToBeDeprecated(databaseId, collection, partitionKeyProperty, documentContent); return createDocument_ToBeDeprecated(databaseId, collection, partitionKeyProperty, documentContent);
} }
const { databaseAccount } = userContext; const { databaseAccount } = userContext;
@@ -308,7 +308,7 @@ export function createDocument(
documentContent: JSON.stringify(documentContent), documentContent: JSON.stringify(documentContent),
}; };
const endpoint = getFeatureEndpointOrDefault("createDocument"); const endpoint = getFeatureEndpointOrDefault(MongoProxyApi.CreateDocument);
return window return window
.fetch(`${endpoint}/createDocument`, { .fetch(`${endpoint}/createDocument`, {
@@ -373,7 +373,7 @@ export function updateDocument(
documentId: DocumentId, documentId: DocumentId,
documentContent: string, documentContent: string,
): Promise<DataModels.DocumentId> { ): Promise<DataModels.DocumentId> {
if (!useMongoProxyEndpoint("updateDocument")) { if (!useMongoProxyEndpoint(MongoProxyApi.UpdateDocument)) {
return updateDocument_ToBeDeprecated(databaseId, collection, documentId, documentContent); return updateDocument_ToBeDeprecated(databaseId, collection, documentId, documentContent);
} }
const { databaseAccount } = userContext; const { databaseAccount } = userContext;
@@ -396,7 +396,7 @@ export function updateDocument(
: "", : "",
documentContent, documentContent,
}; };
const endpoint = getFeatureEndpointOrDefault("updateDocument"); const endpoint = getFeatureEndpointOrDefault(MongoProxyApi.UpdateDocument);
return window return window
.fetch(endpoint, { .fetch(endpoint, {
@@ -464,7 +464,7 @@ export function updateDocument_ToBeDeprecated(
} }
export function deleteDocument(databaseId: string, collection: Collection, documentId: DocumentId): Promise<void> { export function deleteDocument(databaseId: string, collection: Collection, documentId: DocumentId): Promise<void> {
if (!useMongoProxyEndpoint("deleteDocument")) { if (!useMongoProxyEndpoint(MongoProxyApi.DeleteDocument)) {
return deleteDocument_ToBeDeprecated(databaseId, collection, documentId); return deleteDocument_ToBeDeprecated(databaseId, collection, documentId);
} }
const { databaseAccount } = userContext; const { databaseAccount } = userContext;
@@ -486,7 +486,7 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
? documentId.partitionKeyProperties?.[0] ? documentId.partitionKeyProperties?.[0]
: "", : "",
}; };
const endpoint = getFeatureEndpointOrDefault("deleteDocument"); const endpoint = getFeatureEndpointOrDefault(MongoProxyApi.DeleteDocument);
return window return window
.fetch(endpoint, { .fetch(endpoint, {
@@ -561,7 +561,10 @@ export function deleteDocuments(
const { databaseAccount } = userContext; const { databaseAccount } = userContext;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const rids = documentIds.map((documentId) => documentId.id()); const rids: string[] = documentIds.map((documentId) => {
const idComponents = documentId.self.split("/");
return idComponents[5];
});
const params = { const params = {
databaseID: databaseId, databaseID: databaseId,
@@ -572,7 +575,7 @@ export function deleteDocuments(
resourceGroup: userContext.resourceGroup, resourceGroup: userContext.resourceGroup,
databaseAccountName: databaseAccount.name, databaseAccountName: databaseAccount.name,
}; };
const endpoint = getFeatureEndpointOrDefault("bulkdelete"); const endpoint = getFeatureEndpointOrDefault(MongoProxyApi.BulkDelete);
return window return window
.fetch(`${endpoint}/bulkdelete`, { .fetch(`${endpoint}/bulkdelete`, {
@@ -596,7 +599,7 @@ export function deleteDocuments(
export function createMongoCollectionWithProxy( export function createMongoCollectionWithProxy(
params: DataModels.CreateCollectionParams, params: DataModels.CreateCollectionParams,
): Promise<DataModels.Collection> { ): Promise<DataModels.Collection> {
if (!useMongoProxyEndpoint("createCollectionWithProxy")) { if (!useMongoProxyEndpoint(MongoProxyApi.CreateCollectionWithProxy)) {
return createMongoCollectionWithProxy_ToBeDeprecated(params); return createMongoCollectionWithProxy_ToBeDeprecated(params);
} }
const { databaseAccount } = userContext; const { databaseAccount } = userContext;
@@ -619,7 +622,7 @@ export function createMongoCollectionWithProxy(
isSharded: !!shardKey, isSharded: !!shardKey,
}; };
const endpoint = getFeatureEndpointOrDefault("createCollectionWithProxy"); const endpoint = getFeatureEndpointOrDefault(MongoProxyApi.CreateCollectionWithProxy);
return window return window
.fetch(`${endpoint}/createCollection`, { .fetch(`${endpoint}/createCollection`, {
@@ -686,15 +689,16 @@ export function createMongoCollectionWithProxy_ToBeDeprecated(
} }
export function getFeatureEndpointOrDefault(feature: string): string { export function getFeatureEndpointOrDefault(feature: string): string {
let endpoint; let endpoint;
const allowedMongoProxyEndpoints = configContext.allowedMongoProxyEndpoints || [
...defaultAllowedMongoProxyEndpoints,
...allowedMongoProxyEndpoints_ToBeDeprecated,
];
if (useMongoProxyEndpoint(feature)) { if (useMongoProxyEndpoint(feature)) {
endpoint = configContext.MONGO_PROXY_ENDPOINT; endpoint = configContext.MONGO_PROXY_ENDPOINT;
} else { } else {
endpoint = endpoint =
hasFlag(userContext.features.mongoProxyAPIs, feature) && hasFlag(userContext.features.mongoProxyAPIs, feature) &&
validateEndpoint(userContext.features.mongoProxyEndpoint, [ validateEndpoint(userContext.features.mongoProxyEndpoint, allowedMongoProxyEndpoints)
...allowedMongoProxyEndpoints,
...allowedMongoProxyEndpoints_ToBeDeprecated,
])
? userContext.features.mongoProxyEndpoint ? userContext.features.mongoProxyEndpoint
: configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT; : configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT;
} }
@@ -715,19 +719,78 @@ export function getEndpoint(endpoint: string): string {
return url; return url;
} }
export function useMongoProxyEndpoint(api: string): boolean { export function useMongoProxyEndpoint(mongoProxyApi: string): boolean {
const activeMongoProxyEndpoints: string[] = [ const mongoProxyEnvironmentMap: { [key: string]: string[] } = {
MongoProxyEndpoints.Local, [MongoProxyApi.ResourceList]: [
MongoProxyEndpoints.Mpac, MongoProxyEndpoints.Local,
MongoProxyEndpoints.Prod, MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Fairfax, MongoProxyEndpoints.Prod,
MongoProxyEndpoints.Mooncake, MongoProxyEndpoints.Fairfax,
]; MongoProxyEndpoints.Mooncake,
],
[MongoProxyApi.QueryDocuments]: [
MongoProxyEndpoints.Local,
MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod,
MongoProxyEndpoints.Fairfax,
MongoProxyEndpoints.Mooncake,
],
[MongoProxyApi.CreateDocument]: [
MongoProxyEndpoints.Local,
MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod,
MongoProxyEndpoints.Fairfax,
MongoProxyEndpoints.Mooncake,
],
[MongoProxyApi.ReadDocument]: [
MongoProxyEndpoints.Local,
MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod,
MongoProxyEndpoints.Fairfax,
MongoProxyEndpoints.Mooncake,
],
[MongoProxyApi.UpdateDocument]: [
MongoProxyEndpoints.Local,
MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod,
MongoProxyEndpoints.Fairfax,
MongoProxyEndpoints.Mooncake,
],
[MongoProxyApi.DeleteDocument]: [
MongoProxyEndpoints.Local,
MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod,
MongoProxyEndpoints.Fairfax,
MongoProxyEndpoints.Mooncake,
],
[MongoProxyApi.CreateCollectionWithProxy]: [
MongoProxyEndpoints.Local,
MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod,
MongoProxyEndpoints.Fairfax,
MongoProxyEndpoints.Mooncake,
],
[MongoProxyApi.LegacyMongoShell]: [
MongoProxyEndpoints.Local,
MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod,
MongoProxyEndpoints.Fairfax,
MongoProxyEndpoints.Mooncake,
],
[MongoProxyApi.BulkDelete]: [
MongoProxyEndpoints.Local,
MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod,
MongoProxyEndpoints.Fairfax,
MongoProxyEndpoints.Mooncake,
],
};
return ( if (!mongoProxyEnvironmentMap[mongoProxyApi] || !configContext.MONGO_PROXY_ENDPOINT) {
configContext.NEW_MONGO_APIS?.includes(api) && return false;
activeMongoProxyEndpoints.includes(configContext.MONGO_PROXY_ENDPOINT) }
);
return mongoProxyEnvironmentMap[mongoProxyApi].includes(configContext.MONGO_PROXY_ENDPOINT);
} }
export class ThrottlingError extends Error { export class ThrottlingError extends Error {

View File

@@ -135,6 +135,7 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
onEntityValueChange={onEntityValueChange} onEntityValueChange={onEntityValueChange}
onSelectDate={onSelectDate} onSelectDate={onSelectDate}
onEntityTimeValueChange={onEntityTimeValueChange} onEntityTimeValueChange={onEntityTimeValueChange}
entityProperty={entityProperty}
/> />
{!isEntityValueDisable && ( {!isEntityValueDisable && (
<TooltipHost content="Edit property" id="editTooltip"> <TooltipHost content="Edit property" id="editTooltip">

View File

@@ -5,19 +5,20 @@ import {
MongoProxyEndpoints, MongoProxyEndpoints,
PortalBackendEndpoints, PortalBackendEndpoints,
} from "Common/Constants"; } from "Common/Constants";
import { userContext } from "UserContext";
import { import {
allowedAadEndpoints, allowedAadEndpoints,
allowedArcadiaEndpoints, allowedArcadiaEndpoints,
allowedCassandraProxyEndpoints,
allowedEmulatorEndpoints, allowedEmulatorEndpoints,
allowedGraphEndpoints, allowedGraphEndpoints,
allowedHostedExplorerEndpoints, allowedHostedExplorerEndpoints,
allowedJunoOrigins, allowedJunoOrigins,
allowedMongoBackendEndpoints, allowedMongoBackendEndpoints,
allowedMongoProxyEndpoints,
allowedMsalRedirectEndpoints, allowedMsalRedirectEndpoints,
defaultAllowedArmEndpoints, defaultAllowedArmEndpoints,
defaultAllowedBackendEndpoints, defaultAllowedBackendEndpoints,
defaultAllowedCassandraProxyEndpoints,
defaultAllowedMongoProxyEndpoints,
validateEndpoint, validateEndpoint,
} from "Utils/EndpointUtils"; } from "Utils/EndpointUtils";
@@ -32,10 +33,13 @@ export interface ConfigContext {
platform: Platform; platform: Platform;
allowedArmEndpoints: ReadonlyArray<string>; allowedArmEndpoints: ReadonlyArray<string>;
allowedBackendEndpoints: ReadonlyArray<string>; allowedBackendEndpoints: ReadonlyArray<string>;
allowedCassandraProxyEndpoints: ReadonlyArray<string>;
allowedMongoProxyEndpoints: ReadonlyArray<string>;
allowedParentFrameOrigins: ReadonlyArray<string>; allowedParentFrameOrigins: ReadonlyArray<string>;
gitSha?: string; gitSha?: string;
proxyPath?: string; proxyPath?: string;
AAD_ENDPOINT: string; AAD_ENDPOINT: string;
ENVIRONMENT: string;
ARM_AUTH_AREA: string; ARM_AUTH_AREA: string;
ARM_ENDPOINT: string; ARM_ENDPOINT: string;
EMULATOR_ENDPOINT?: string; EMULATOR_ENDPOINT?: string;
@@ -53,7 +57,6 @@ export interface ConfigContext {
NEW_BACKEND_APIS?: BackendApi[]; NEW_BACKEND_APIS?: BackendApi[];
MONGO_BACKEND_ENDPOINT?: string; MONGO_BACKEND_ENDPOINT?: string;
MONGO_PROXY_ENDPOINT: string; MONGO_PROXY_ENDPOINT: string;
NEW_MONGO_APIS?: string[];
CASSANDRA_PROXY_ENDPOINT: string; CASSANDRA_PROXY_ENDPOINT: string;
NEW_CASSANDRA_APIS?: string[]; NEW_CASSANDRA_APIS?: string[];
PROXY_PATH?: string; PROXY_PATH?: string;
@@ -73,9 +76,12 @@ let configContext: Readonly<ConfigContext> = {
platform: Platform.Portal, platform: Platform.Portal,
allowedArmEndpoints: defaultAllowedArmEndpoints, allowedArmEndpoints: defaultAllowedArmEndpoints,
allowedBackendEndpoints: defaultAllowedBackendEndpoints, allowedBackendEndpoints: defaultAllowedBackendEndpoints,
allowedCassandraProxyEndpoints: defaultAllowedCassandraProxyEndpoints,
allowedMongoProxyEndpoints: defaultAllowedMongoProxyEndpoints,
allowedParentFrameOrigins: [ allowedParentFrameOrigins: [
`^https:\\/\\/cosmos\\.azure\\.(com|cn|us)$`, `^https:\\/\\/cosmos\\.azure\\.(com|cn|us)$`,
`^https:\\/\\/[\\.\\w]*portal\\.azure\\.(com|cn|us)$`, `^https:\\/\\/[\\.\\w]*portal\\.azure\\.(com|cn|us)$`,
`^https:\\/\\/cdb-(ms|ff|mc)-prod-pbe\\.cosmos\\.azure\\.(com|us|cn)$`,
`^https:\\/\\/[\\.\\w]*portal\\.microsoftazure\\.de$`, `^https:\\/\\/[\\.\\w]*portal\\.microsoftazure\\.de$`,
`^https:\\/\\/[\\.\\w]*ext\\.azure\\.(com|cn|us)$`, `^https:\\/\\/[\\.\\w]*ext\\.azure\\.(com|cn|us)$`,
`^https:\\/\\/[\\.\\w]*\\.ext\\.microsoftazure\\.de$`, `^https:\\/\\/[\\.\\w]*\\.ext\\.microsoftazure\\.de$`,
@@ -89,7 +95,7 @@ let configContext: Readonly<ConfigContext> = {
], // Webpack injects this at build time ], // Webpack injects this at build time
gitSha: process.env.GIT_SHA, gitSha: process.env.GIT_SHA,
hostedExplorerURL: "https://cosmos.azure.com/", hostedExplorerURL: "https://cosmos.azure.com/",
AAD_ENDPOINT: "https://login.microsoftonline.com/", AAD_ENDPOINT: "",
ARM_AUTH_AREA: "https://management.azure.com/", ARM_AUTH_AREA: "https://management.azure.com/",
ARM_ENDPOINT: "https://management.azure.com/", ARM_ENDPOINT: "https://management.azure.com/",
ARM_API_VERSION: "2016-06-01", ARM_API_VERSION: "2016-06-01",
@@ -106,17 +112,6 @@ let configContext: Readonly<ConfigContext> = {
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com", BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod, PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod,
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod, MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
NEW_MONGO_APIS: [
"resourcelist",
"queryDocuments",
"createDocument",
"readDocument",
"updateDocument",
"deleteDocument",
"createCollectionWithProxy",
"legacyMongoShell",
// "bulkdelete",
],
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod, CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod,
NEW_CASSANDRA_APIS: ["postQuery", "createOrDelete", "getKeys", "getSchema"], NEW_CASSANDRA_APIS: ["postQuery", "createOrDelete", "getKeys", "getSchema"],
isTerminalEnabled: false, isTerminalEnabled: false,
@@ -164,7 +159,12 @@ export function updateConfigContext(newContext: Partial<ConfigContext>): void {
delete newContext.BACKEND_ENDPOINT; delete newContext.BACKEND_ENDPOINT;
} }
if (!validateEndpoint(newContext.MONGO_PROXY_ENDPOINT, allowedMongoProxyEndpoints)) { if (
!validateEndpoint(
newContext.MONGO_PROXY_ENDPOINT,
configContext.allowedMongoProxyEndpoints || defaultAllowedMongoProxyEndpoints,
)
) {
delete newContext.MONGO_PROXY_ENDPOINT; delete newContext.MONGO_PROXY_ENDPOINT;
} }
@@ -172,7 +172,12 @@ export function updateConfigContext(newContext: Partial<ConfigContext>): void {
delete newContext.MONGO_BACKEND_ENDPOINT; delete newContext.MONGO_BACKEND_ENDPOINT;
} }
if (!validateEndpoint(newContext.CASSANDRA_PROXY_ENDPOINT, allowedCassandraProxyEndpoints)) { if (
!validateEndpoint(
newContext.CASSANDRA_PROXY_ENDPOINT,
configContext.allowedCassandraProxyEndpoints || defaultAllowedCassandraProxyEndpoints,
)
) {
delete newContext.CASSANDRA_PROXY_ENDPOINT; delete newContext.CASSANDRA_PROXY_ENDPOINT;
} }

View File

@@ -381,6 +381,7 @@ export enum TerminalKind {
export interface DataExplorerInputsFrame { export interface DataExplorerInputsFrame {
databaseAccount: any; databaseAccount: any;
subscriptionId?: string; subscriptionId?: string;
tenantId?: string;
resourceGroup?: string; resourceGroup?: string;
masterKey?: string; masterKey?: string;
hasWriteAccess?: boolean; hasWriteAccess?: boolean;

View File

@@ -56,13 +56,15 @@ export const createDatabaseContextMenu = (container: Explorer, databaseId: strin
if (userContext.apiType !== "Tables" || userContext.features.enableSDKoperations) { if (userContext.apiType !== "Tables" || userContext.features.enableSDKoperations) {
items.push({ items.push({
iconSrc: DeleteDatabaseIcon, iconSrc: DeleteDatabaseIcon,
onClick: () => onClick: (lastFocusedElement?: React.RefObject<HTMLElement>) => {
useSidePanel (useSidePanel.getState().getRef = lastFocusedElement),
.getState() useSidePanel
.openSidePanel( .getState()
"Delete " + getDatabaseName(), .openSidePanel(
<DeleteDatabaseConfirmationPanel refreshDatabases={() => container.refreshAllDatabases()} />, "Delete " + getDatabaseName(),
), <DeleteDatabaseConfirmationPanel refreshDatabases={() => container.refreshAllDatabases()} />,
);
},
label: `Delete ${getDatabaseName()}`, label: `Delete ${getDatabaseName()}`,
styleClass: "deleteDatabaseMenuItem", styleClass: "deleteDatabaseMenuItem",
}); });
@@ -146,14 +148,15 @@ export const createCollectionContextMenuButton = (
if (configContext.platform !== Platform.Fabric) { if (configContext.platform !== Platform.Fabric) {
items.push({ items.push({
iconSrc: DeleteCollectionIcon, iconSrc: DeleteCollectionIcon,
onClick: () => { onClick: (lastFocusedElement?: React.RefObject<HTMLElement>) => {
useSelectedNode.getState().setSelectedNode(selectedCollection); useSelectedNode.getState().setSelectedNode(selectedCollection);
useSidePanel (useSidePanel.getState().getRef = lastFocusedElement),
.getState() useSidePanel
.openSidePanel( .getState()
"Delete " + getCollectionName(), .openSidePanel(
<DeleteCollectionConfirmationPane refreshDatabases={() => container.refreshAllDatabases()} />, "Delete " + getCollectionName(),
); <DeleteCollectionConfirmationPane refreshDatabases={() => container.refreshAllDatabases()} />,
);
}, },
label: `Delete ${getCollectionName()}`, label: `Delete ${getCollectionName()}`,
styleClass: "deleteCollectionMenuItem", styleClass: "deleteCollectionMenuItem",

View File

@@ -1,14 +1,23 @@
/** /**
* React component for Command button component. * React component for Command button component.
*/ */
import Explorer from "Explorer/Explorer";
import { KeyboardAction } from "KeyboardShortcuts"; import { KeyboardAction } from "KeyboardShortcuts";
import * as React from "react"; import * as React from "react";
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
import { KeyCodes } from "../../../Common/Constants";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import * as StringUtils from "../../../Utils/StringUtils";
/** /**
* Options for this component * Options for this component
*/ */
export interface CommandButtonComponentProps { export interface CommandButtonComponentProps {
/**
* font icon name for the button
*/
iconName?: string;
/** /**
* image source for the button icon * image source for the button icon
*/ */
@@ -22,7 +31,7 @@ export interface CommandButtonComponentProps {
/** /**
* Click handler for command button click * Click handler for command button click
*/ */
onCommandClick?: (e: React.SyntheticEvent | KeyboardEvent, container: Explorer) => void; onCommandClick?: (e: React.SyntheticEvent | KeyboardEvent) => void;
/** /**
* Label for the button * Label for the button
@@ -111,3 +120,157 @@ export interface CommandButtonComponentProps {
*/ */
keyboardAction?: KeyboardAction; keyboardAction?: KeyboardAction;
} }
export class CommandButtonComponent extends React.Component<CommandButtonComponentProps> {
private dropdownElt: HTMLElement;
private expandButtonElt: HTMLElement;
public componentDidUpdate(): void {
if (!this.dropdownElt || !this.expandButtonElt) {
return;
}
$(this.dropdownElt).offset({ left: $(this.expandButtonElt).offset().left });
}
private onKeyPress(event: React.KeyboardEvent): boolean {
if (event.keyCode === KeyCodes.Space || event.keyCode === KeyCodes.Enter) {
this.commandClickCallback && this.commandClickCallback(event);
event.stopPropagation();
return false;
}
return true;
}
private onLauncherKeyDown(event: React.KeyboardEvent<HTMLDivElement>): boolean {
if (event.keyCode === KeyCodes.DownArrow) {
$(this.dropdownElt).hide();
$(this.dropdownElt).show().focus();
event.stopPropagation();
return false;
}
if (event.keyCode === KeyCodes.UpArrow) {
$(this.dropdownElt).hide();
event.stopPropagation();
return false;
}
return true;
}
private getCommandButtonId(): string {
if (this.props.id) {
return this.props.id;
} else {
return `commandButton-${StringUtils.stripSpacesFromString(this.props.commandButtonLabel)}`;
}
}
public static renderButton(options: CommandButtonComponentProps, key?: string): JSX.Element {
return <CommandButtonComponent key={key} {...options} />;
}
private commandClickCallback(e: React.SyntheticEvent): void {
if (this.props.disabled) {
return;
}
// TODO Query component's parent, not document
const el = document.querySelector(".commandDropdownContainer") as HTMLElement;
if (el) {
el.style.display = "none";
}
this.props.onCommandClick(e);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
commandButtonClicked: this.props.commandButtonLabel,
});
}
private renderChildren(): JSX.Element {
if (!this.props.children || this.props.children.length < 1) {
return <React.Fragment />;
}
return (
<div
className="commandExpand"
tabIndex={0}
ref={(ref: HTMLElement) => {
this.expandButtonElt = ref;
}}
onKeyDown={(e: React.KeyboardEvent<HTMLDivElement>) => this.onLauncherKeyDown(e)}
>
<div className="commandDropdownLauncher">
<span className="partialSplitter" />
<span className="expandDropdown">
<img src={CollapseChevronDownIcon} />
</span>
</div>
<div
className="commandDropdownContainer"
ref={(ref: HTMLElement) => {
this.dropdownElt = ref;
}}
>
<div className="commandDropdown">
{this.props.children.map((c: CommandButtonComponentProps, index: number): JSX.Element => {
return CommandButtonComponent.renderButton(c, `${index}`);
})}
</div>
</div>
</div>
);
}
public static renderLabel(
props: CommandButtonComponentProps,
key?: string,
refct?: (input: HTMLElement) => void,
): JSX.Element {
if (!props.commandButtonLabel) {
return <React.Fragment />;
}
return (
<span className="commandLabel" key={key} ref={refct}>
{props.commandButtonLabel}
</span>
);
}
public render(): JSX.Element {
let mainClassName = "commandButtonComponent";
if (this.props.disabled) {
mainClassName += " commandDisabled";
}
if (this.props.isSelected) {
mainClassName += " selectedButton";
}
let contentClassName = "commandContent";
if (this.props.children && this.props.children.length > 0) {
contentClassName += " hasHiddenItems";
}
return (
<div className="commandButtonReact">
<span
className={mainClassName}
role="menuitem"
tabIndex={this.props.tabIndex}
onKeyPress={(e: React.KeyboardEvent<HTMLSpanElement>) => this.onKeyPress(e)}
title={this.props.tooltipText}
id={this.getCommandButtonId()}
aria-disabled={this.props.disabled}
aria-haspopup={this.props.hasPopup}
aria-label={this.props.ariaLabel}
onClick={(e: React.MouseEvent<HTMLSpanElement>) => this.commandClickCallback(e)}
>
<div className={contentClassName}>
<img className="commandIcon" src={this.props.iconSrc} alt={this.props.iconAlt} />
{CommandButtonComponent.renderLabel(this.props)}
</div>
</span>
{this.props.children && this.renderChildren()}
</div>
);
}
}

View File

@@ -7,7 +7,6 @@ import {
ContainerVectorPolicyComponent, ContainerVectorPolicyComponent,
ContainerVectorPolicyComponentProps, ContainerVectorPolicyComponentProps,
} from "Explorer/Controls/Settings/SettingsSubComponents/ContainerVectorPolicyComponent"; } from "Explorer/Controls/Settings/SettingsSubComponents/ContainerVectorPolicyComponent";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
import { isVectorSearchEnabled } from "Utils/CapabilityUtils"; import { isVectorSearchEnabled } from "Utils/CapabilityUtils";
import { isRunningOnPublicCloud } from "Utils/CloudUtils"; import { isRunningOnPublicCloud } from "Utils/CloudUtils";
@@ -33,6 +32,7 @@ import {
PartitionKeyComponent, PartitionKeyComponent,
PartitionKeyComponentProps, PartitionKeyComponentProps,
} from "../../Controls/Settings/SettingsSubComponents/PartitionKeyComponent"; } from "../../Controls/Settings/SettingsSubComponents/PartitionKeyComponent";
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2"; import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
import "./SettingsComponent.less"; import "./SettingsComponent.less";
import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils"; import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";

View File

@@ -23,7 +23,7 @@ import { useCallback } from "react";
export interface TreeNodeMenuItem { export interface TreeNodeMenuItem {
label: string; label: string;
onClick: () => void; onClick: (value?: React.RefObject<HTMLElement>) => void;
iconSrc?: string; iconSrc?: string;
isDisabled?: boolean; isDisabled?: boolean;
styleClass?: string; styleClass?: string;
@@ -74,6 +74,7 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
openItems, openItems,
}: TreeNodeComponentProps): JSX.Element => { }: TreeNodeComponentProps): JSX.Element => {
const [isLoading, setIsLoading] = React.useState<boolean>(false); const [isLoading, setIsLoading] = React.useState<boolean>(false);
const contextMenuRef = React.useRef<HTMLButtonElement>(null);
const treeStyles = useTreeStyles(); const treeStyles = useTreeStyles();
const getSortedChildren = (treeNode: TreeNode): TreeNode[] => { const getSortedChildren = (treeNode: TreeNode): TreeNode[] => {
@@ -107,7 +108,7 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
const onOpenChange = useCallback( const onOpenChange = useCallback(
(_: TreeOpenChangeEvent, data: TreeOpenChangeData) => { (_: TreeOpenChangeEvent, data: TreeOpenChangeData) => {
if (data.type === "Click" && node.onClick) { if (data.type === "Click" && !isBranch && node.onClick) {
node.onClick(); node.onClick();
} }
if (!node.isExpanded && data.open && node.onExpanded) { if (!node.isExpanded && data.open && node.onExpanded) {
@@ -119,7 +120,7 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
node.onCollapsed?.(); node.onCollapsed?.();
} }
}, },
[node, setIsLoading], [isBranch, node, setIsLoading],
); );
const onMenuOpenChange = useCallback( const onMenuOpenChange = useCallback(
@@ -141,7 +142,7 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
data-test={`TreeNode/ContextMenuItem:${menuItem.label}`} data-test={`TreeNode/ContextMenuItem:${menuItem.label}`}
disabled={menuItem.isDisabled} disabled={menuItem.isDisabled}
key={menuItem.label} key={menuItem.label}
onClick={menuItem.onClick} onClick={() => menuItem.onClick(contextMenuRef)}
> >
{menuItem.label} {menuItem.label}
</MenuItem> </MenuItem>
@@ -190,6 +191,7 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
className={mergeClasses(treeStyles.actionsButton, shouldShowAsSelected && treeStyles.selectedItem)} className={mergeClasses(treeStyles.actionsButton, shouldShowAsSelected && treeStyles.selectedItem)}
data-test="TreeNode/ContextMenuTrigger" data-test="TreeNode/ContextMenuTrigger"
appearance="subtle" appearance="subtle"
ref={contextMenuRef}
icon={<MoreHorizontal20Regular />} icon={<MoreHorizontal20Regular />}
/> />
</MenuTrigger> </MenuTrigger>

View File

@@ -1478,14 +1478,14 @@ exports[`TreeNodeComponent renders a node with a menu 1`] = `
<MenuList> <MenuList>
<MenuItem <MenuItem
data-test="TreeNode/ContextMenuItem:enabledItem" data-test="TreeNode/ContextMenuItem:enabledItem"
onClick={[MockFunction enabledItemClick]} onClick={[Function]}
> >
enabledItem enabledItem
</MenuItem> </MenuItem>
<MenuItem <MenuItem
data-test="TreeNode/ContextMenuItem:disabledItem" data-test="TreeNode/ContextMenuItem:disabledItem"
disabled={true} disabled={true}
onClick={[MockFunction disabledItemClick]} onClick={[Function]}
> >
disabledItem disabledItem
</MenuItem> </MenuItem>
@@ -1518,7 +1518,7 @@ exports[`TreeNodeComponent renders a node with a menu 1`] = `
<MenuItem <MenuItem
data-test="TreeNode/ContextMenuItem:enabledItem" data-test="TreeNode/ContextMenuItem:enabledItem"
key="enabledItem" key="enabledItem"
onClick={[MockFunction enabledItemClick]} onClick={[Function]}
> >
enabledItem enabledItem
</MenuItem> </MenuItem>
@@ -1526,7 +1526,7 @@ exports[`TreeNodeComponent renders a node with a menu 1`] = `
data-test="TreeNode/ContextMenuItem:disabledItem" data-test="TreeNode/ContextMenuItem:disabledItem"
disabled={true} disabled={true}
key="disabledItem" key="disabledItem"
onClick={[MockFunction disabledItemClick]} onClick={[Function]}
> >
disabledItem disabledItem
</MenuItem> </MenuItem>

View File

@@ -5,13 +5,12 @@ import { Environment, getEnvironment } from "Common/EnvironmentUtility";
import { sendMessage } from "Common/MessageHandler"; import { sendMessage } from "Common/MessageHandler";
import { Platform, configContext } from "ConfigContext"; import { Platform, configContext } from "ConfigContext";
import { MessageTypes } from "Contracts/ExplorerContracts"; import { MessageTypes } from "Contracts/ExplorerContracts";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane"; import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient"; import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
import { IGalleryItem } from "Juno/JunoClient"; import { IGalleryItem } from "Juno/JunoClient";
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil"; import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { acquireTokenWithMsal, getMsalInstance } from "Utils/AuthorizationUtils"; import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils"; import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts"; import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
import { useQueryCopilot } from "hooks/useQueryCopilot"; import { useQueryCopilot } from "hooks/useQueryCopilot";
@@ -48,6 +47,7 @@ import { useTabs } from "../hooks/useTabs";
import "./ComponentRegisterer"; import "./ComponentRegisterer";
import { DialogProps, useDialog } from "./Controls/Dialog"; import { DialogProps, useDialog } from "./Controls/Dialog";
import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/GalleryViewerComponent"; import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/GalleryViewerComponent";
import { useCommandBar } from "./Menus/CommandBar/CommandBarComponentAdapter";
import * as FileSystemUtil from "./Notebook/FileSystemUtil"; import * as FileSystemUtil from "./Notebook/FileSystemUtil";
import { SnapshotRequest } from "./Notebook/NotebookComponent/types"; import { SnapshotRequest } from "./Notebook/NotebookComponent/types";
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem"; import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";
@@ -259,25 +259,8 @@ export default class Explorer {
public async openLoginForEntraIDPopUp(): Promise<void> { public async openLoginForEntraIDPopUp(): Promise<void> {
if (userContext.databaseAccount.properties?.documentEndpoint) { if (userContext.databaseAccount.properties?.documentEndpoint) {
const hrefEndpoint = new URL(userContext.databaseAccount.properties.documentEndpoint).href.replace(
/\/$/,
"/.default",
);
const msalInstance = await getMsalInstance();
try { try {
const response = await msalInstance.loginPopup({ const aadToken = await acquireMsalTokenForAccount(userContext.databaseAccount, false);
redirectUri: configContext.msalRedirectURI,
scopes: [],
});
localStorage.setItem("cachedTenantId", response.tenantId);
const cachedAccount = msalInstance.getAllAccounts()?.[0];
msalInstance.setActiveAccount(cachedAccount);
const aadToken = await acquireTokenWithMsal(msalInstance, {
forceRefresh: true,
scopes: [hrefEndpoint],
authority: `${configContext.AAD_ENDPOINT}${localStorage.getItem("cachedTenantId")}`,
});
updateUserContext({ aadToken: aadToken }); updateUserContext({ aadToken: aadToken });
useDataPlaneRbac.setState({ aadTokenUpdated: true }); useDataPlaneRbac.setState({ aadTokenUpdated: true });
} catch (error) { } catch (error) {

View File

@@ -4,51 +4,83 @@
* and update any knockout observables passed from the parent. * and update any knockout observables passed from the parent.
*/ */
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react"; import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
import {
createPlatformButtons,
createStaticCommandBarButtons,
} from "Explorer/Menus/CommandBar/CommandBarComponentButtonFactory";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { useNotebook } from "Explorer/Notebook/useNotebook"; import { useNotebook } from "Explorer/Notebook/useNotebook";
import { KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts"; import { KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
import { userContext } from "UserContext";
import * as React from "react"; import * as React from "react";
import create, { UseStore } from "zustand";
import { ConnectionStatusType, PoolIdType } from "../../../Common/Constants"; import { ConnectionStatusType, PoolIdType } from "../../../Common/Constants";
import { StyleConstants } from "../../../Common/StyleConstants"; import { StyleConstants } from "../../../Common/StyleConstants";
import { Platform, configContext } from "../../../ConfigContext"; import { Platform, configContext } from "../../../ConfigContext";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { useSelectedNode } from "../../useSelectedNode"; import { useSelectedNode } from "../../useSelectedNode";
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
import * as CommandBarUtil from "./CommandBarUtil"; import * as CommandBarUtil from "./CommandBarUtil";
interface Props { interface Props {
container: Explorer; container: Explorer;
} }
export interface CommandBarStore {
contextButtons: CommandButtonComponentProps[];
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => void;
isHidden: boolean;
setIsHidden: (isHidden: boolean) => void;
}
export const useCommandBar: UseStore<CommandBarStore> = create((set) => ({
contextButtons: [],
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => set((state) => ({ ...state, contextButtons })),
isHidden: false,
setIsHidden: (isHidden: boolean) => set((state) => ({ ...state, isHidden })),
}));
export const CommandBar: React.FC<Props> = ({ container }: Props) => { export const CommandBar: React.FC<Props> = ({ container }: Props) => {
const selectedNodeState = useSelectedNode(); const selectedNodeState = useSelectedNode();
const contextButtons = useCommandBar((state) => state.contextButtons); const buttons = useCommandBar((state) => state.contextButtons);
const isHidden = useCommandBar((state) => state.isHidden); const isHidden = useCommandBar((state) => state.isHidden);
const backgroundColor = StyleConstants.BaseLight; const backgroundColor = StyleConstants.BaseLight;
const setKeyboardHandlers = useKeyboardActionGroup(KeyboardActionGroup.COMMAND_BAR); const setKeyboardHandlers = useKeyboardActionGroup(KeyboardActionGroup.COMMAND_BAR);
const staticButtons = createStaticCommandBarButtons(selectedNodeState);
const platformButtons = createPlatformButtons();
const uiFabricStaticButtons = CommandBarUtil.convertButton(staticButtons, backgroundColor, container); if (userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo") {
if (contextButtons?.length > 0) { const buttons =
userContext.apiType === "Postgres"
? CommandBarComponentButtonFactory.createPostgreButtons(container)
: CommandBarComponentButtonFactory.createVCoreMongoButtons(container);
return (
<div className="commandBarContainer" style={{ display: isHidden ? "none" : "initial" }}>
<FluentCommandBar
ariaLabel="Use left and right arrow keys to navigate between commands"
items={CommandBarUtil.convertButton(buttons, backgroundColor)}
styles={{
root: { backgroundColor: backgroundColor },
}}
overflowButtonProps={{ ariaLabel: "More commands" }}
/>
</div>
);
}
const staticButtons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(container, selectedNodeState);
const contextButtons = (buttons || []).concat(
CommandBarComponentButtonFactory.createContextCommandBarButtons(container, selectedNodeState),
);
const controlButtons = CommandBarComponentButtonFactory.createControlCommandBarButtons(container);
const uiFabricStaticButtons = CommandBarUtil.convertButton(staticButtons, backgroundColor);
if (buttons && buttons.length > 0) {
uiFabricStaticButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true)); uiFabricStaticButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
} }
const uiFabricTabsButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton( const uiFabricTabsButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton(contextButtons, backgroundColor);
contextButtons || [],
backgroundColor,
container,
);
if (uiFabricTabsButtons.length > 0) { if (uiFabricTabsButtons.length > 0) {
uiFabricStaticButtons.push(CommandBarUtil.createDivider("commandBarDivider")); uiFabricStaticButtons.push(CommandBarUtil.createDivider("commandBarDivider"));
} }
const uiFabricPlatformButtons = CommandBarUtil.convertButton(platformButtons || [], backgroundColor, container); const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
uiFabricPlatformButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true)); uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
const connectionInfo = useNotebook((state) => state.connectionInfo); const connectionInfo = useNotebook((state) => state.connectionInfo);
@@ -56,7 +88,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
(useNotebook.getState().isPhoenixNotebooks || useNotebook.getState().isPhoenixFeatures) && (useNotebook.getState().isPhoenixNotebooks || useNotebook.getState().isPhoenixFeatures) &&
connectionInfo?.status !== ConnectionStatusType.Connect connectionInfo?.status !== ConnectionStatusType.Connect
) { ) {
uiFabricPlatformButtons.unshift( uiFabricControlButtons.unshift(
CommandBarUtil.createConnectionStatus(container, PoolIdType.DefaultPoolId, "connectionStatus"), CommandBarUtil.createConnectionStatus(container, PoolIdType.DefaultPoolId, "connectionStatus"),
); );
} }
@@ -75,8 +107,8 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
}, },
}; };
const allButtons = staticButtons.concat(contextButtons).concat(platformButtons); const allButtons = staticButtons.concat(contextButtons).concat(controlButtons);
const keyboardHandlers = CommandBarUtil.createKeyboardHandlers(allButtons, container); const keyboardHandlers = CommandBarUtil.createKeyboardHandlers(allButtons);
setKeyboardHandlers(keyboardHandlers); setKeyboardHandlers(keyboardHandlers);
return ( return (
@@ -84,7 +116,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
<FluentCommandBar <FluentCommandBar
ariaLabel="Use left and right arrow keys to navigate between commands" ariaLabel="Use left and right arrow keys to navigate between commands"
items={uiFabricStaticButtons.concat(uiFabricTabsButtons)} items={uiFabricStaticButtons.concat(uiFabricTabsButtons)}
farItems={uiFabricPlatformButtons} farItems={uiFabricControlButtons}
styles={rootStyle} styles={rootStyle}
overflowButtonProps={{ ariaLabel: "More commands" }} overflowButtonProps={{ ariaLabel: "More commands" }}
/> />

View File

@@ -3,12 +3,15 @@ import { AuthType } from "../../../AuthType";
import { DatabaseAccount } from "../../../Contracts/DataModels"; import { DatabaseAccount } from "../../../Contracts/DataModels";
import { CollectionBase } from "../../../Contracts/ViewModels"; import { CollectionBase } from "../../../Contracts/ViewModels";
import { updateUserContext } from "../../../UserContext"; import { updateUserContext } from "../../../UserContext";
import Explorer from "../../Explorer";
import { useNotebook } from "../../Notebook/useNotebook"; import { useNotebook } from "../../Notebook/useNotebook";
import { useDatabases } from "../../useDatabases"; import { useDatabases } from "../../useDatabases";
import { useSelectedNode } from "../../useSelectedNode"; import { useSelectedNode } from "../../useSelectedNode";
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory"; import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
describe("CommandBarComponentButtonFactory tests", () => { describe("CommandBarComponentButtonFactory tests", () => {
let mockExplorer: Explorer;
afterEach(() => useSelectedNode.getState().setSelectedNode(undefined)); afterEach(() => useSelectedNode.getState().setSelectedNode(undefined));
describe("Enable Azure Synapse Link Button", () => { describe("Enable Azure Synapse Link Button", () => {
@@ -16,6 +19,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
const selectedNodeState = useSelectedNode.getState(); const selectedNodeState = useSelectedNode.getState();
beforeAll(() => { beforeAll(() => {
mockExplorer = {} as Explorer;
updateUserContext({ updateUserContext({
databaseAccount: { databaseAccount: {
properties: { properties: {
@@ -26,7 +30,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
}); });
it("Button should be visible", () => { it("Button should be visible", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableAzureSynapseLinkBtn = buttons.find( const enableAzureSynapseLinkBtn = buttons.find(
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel, (button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel,
); );
@@ -42,7 +46,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
} as DatabaseAccount, } as DatabaseAccount,
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableAzureSynapseLinkBtn = buttons.find( const enableAzureSynapseLinkBtn = buttons.find(
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel, (button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel,
); );
@@ -58,7 +62,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
} as DatabaseAccount, } as DatabaseAccount,
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableAzureSynapseLinkBtn = buttons.find( const enableAzureSynapseLinkBtn = buttons.find(
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel, (button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel,
); );
@@ -71,6 +75,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
const selectedNodeState = useSelectedNode.getState(); const selectedNodeState = useSelectedNode.getState();
beforeAll(() => { beforeAll(() => {
mockExplorer = {} as Explorer;
updateUserContext({ updateUserContext({
databaseAccount: { databaseAccount: {
properties: { properties: {
@@ -103,7 +108,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
}, },
} as DatabaseAccount, } as DatabaseAccount,
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined(); expect(openCassandraShellBtn).toBeUndefined();
}); });
@@ -113,13 +118,13 @@ describe("CommandBarComponentButtonFactory tests", () => {
portalEnv: "mooncake", portalEnv: "mooncake",
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined(); expect(openCassandraShellBtn).toBeUndefined();
}); });
it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => { it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined(); expect(openCassandraShellBtn).toBeUndefined();
}); });
@@ -129,8 +134,12 @@ describe("CommandBarComponentButtonFactory tests", () => {
const openPostgresShellButtonLabel = "Open PSQL shell"; const openPostgresShellButtonLabel = "Open PSQL shell";
const openVCoreMongoShellButtonLabel = "Open MongoDB (vCore) shell"; const openVCoreMongoShellButtonLabel = "Open MongoDB (vCore) shell";
beforeAll(() => {
mockExplorer = {} as Explorer;
});
it("creates Postgres shell button", () => { it("creates Postgres shell button", () => {
const buttons = CommandBarComponentButtonFactory.createPostgreButtons(); const buttons = CommandBarComponentButtonFactory.createPostgreButtons(mockExplorer);
const openPostgresShellButton = buttons.find( const openPostgresShellButton = buttons.find(
(button) => button.commandButtonLabel === openPostgresShellButtonLabel, (button) => button.commandButtonLabel === openPostgresShellButtonLabel,
); );
@@ -138,7 +147,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
}); });
it("creates vCore Mongo shell button", () => { it("creates vCore Mongo shell button", () => {
const buttons = CommandBarComponentButtonFactory.createVCoreMongoButtons(); const buttons = CommandBarComponentButtonFactory.createVCoreMongoButtons(mockExplorer);
const openVCoreMongoShellButton = buttons.find( const openVCoreMongoShellButton = buttons.find(
(button) => button.commandButtonLabel === openVCoreMongoShellButtonLabel, (button) => button.commandButtonLabel === openVCoreMongoShellButtonLabel,
); );
@@ -153,6 +162,8 @@ describe("CommandBarComponentButtonFactory tests", () => {
const selectedNodeState = useSelectedNode.getState(); const selectedNodeState = useSelectedNode.getState();
beforeAll(() => { beforeAll(() => {
mockExplorer = {} as Explorer;
updateUserContext({ updateUserContext({
authType: AuthType.ResourceToken, authType: AuthType.ResourceToken,
}); });
@@ -164,7 +175,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
kind: "DocumentDB", kind: "DocumentDB",
} as DatabaseAccount, } as DatabaseAccount,
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
expect(buttons.length).toBe(2); expect(buttons.length).toBe(2);
expect(buttons[0].commandButtonLabel).toBe("New SQL Query"); expect(buttons[0].commandButtonLabel).toBe("New SQL Query");
expect(buttons[0].disabled).toBe(false); expect(buttons[0].disabled).toBe(false);

View File

@@ -21,6 +21,7 @@ import { userContext } from "../../../UserContext";
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils"; import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
import { useSidePanel } from "../../../hooks/useSidePanel"; import { useSidePanel } from "../../../hooks/useSidePanel";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer";
import { useNotebook } from "../../Notebook/useNotebook"; import { useNotebook } from "../../Notebook/useNotebook";
import { OpenFullScreen } from "../../OpenFullScreen"; import { OpenFullScreen } from "../../OpenFullScreen";
import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane"; import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane";
@@ -31,20 +32,19 @@ import { SelectedNodeState, useSelectedNode } from "../../useSelectedNode";
let counter = 0; let counter = 0;
export function createStaticCommandBarButtons(selectedNodeState: SelectedNodeState): CommandButtonComponentProps[] { export function createStaticCommandBarButtons(
if (userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo") { container: Explorer,
return userContext.apiType === "Postgres" ? createPostgreButtons() : createVCoreMongoButtons(); selectedNodeState: SelectedNodeState,
} ): CommandButtonComponentProps[] {
if (userContext.authType === AuthType.ResourceToken) { if (userContext.authType === AuthType.ResourceToken) {
return createStaticCommandBarButtonsForResourceToken(selectedNodeState); return createStaticCommandBarButtonsForResourceToken(container, selectedNodeState);
} }
const buttons: CommandButtonComponentProps[] = []; const buttons: CommandButtonComponentProps[] = [];
// Avoid starting with a divider // Avoid starting with a divider
const addDivider = () => { const addDivider = () => {
if (buttons.length > 0 && !buttons[buttons.length - 1].isDivider) { if (buttons.length > 0) {
buttons.push(createDivider()); buttons.push(createDivider());
} }
}; };
@@ -54,7 +54,7 @@ export function createStaticCommandBarButtons(selectedNodeState: SelectedNodeSta
userContext.apiType !== "Tables" && userContext.apiType !== "Tables" &&
userContext.apiType !== "Cassandra" userContext.apiType !== "Cassandra"
) { ) {
const addSynapseLink = createOpenSynapseLinkDialogButton(); const addSynapseLink = createOpenSynapseLinkDialogButton(container);
if (addSynapseLink) { if (addSynapseLink) {
addDivider(); addDivider();
buttons.push(addSynapseLink); buttons.push(addSynapseLink);
@@ -67,9 +67,9 @@ export function createStaticCommandBarButtons(selectedNodeState: SelectedNodeSta
const aadTokenUpdated = useDataPlaneRbac((state) => state.aadTokenUpdated); const aadTokenUpdated = useDataPlaneRbac((state) => state.aadTokenUpdated);
useEffect(() => { useEffect(() => {
const buttonProps = createLoginForEntraIDButton(); const buttonProps = createLoginForEntraIDButton(container);
setLoginButtonProps(buttonProps); setLoginButtonProps(buttonProps);
}, [dataPlaneRbacEnabled, aadTokenUpdated]); }, [dataPlaneRbacEnabled, aadTokenUpdated, container]);
if (loginButtonProps) { if (loginButtonProps) {
addDivider(); addDivider();
@@ -87,8 +87,8 @@ export function createStaticCommandBarButtons(selectedNodeState: SelectedNodeSta
} }
if (isQuerySupported && selectedNodeState.findSelectedCollection() && configContext.platform !== Platform.Fabric) { if (isQuerySupported && selectedNodeState.findSelectedCollection() && configContext.platform !== Platform.Fabric) {
const openQueryBtn = createOpenQueryButton(); const openQueryBtn = createOpenQueryButton(container);
openQueryBtn.children = [createOpenQueryButton(), createOpenQueryFromDiskButton()]; openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton()];
buttons.push(openQueryBtn); buttons.push(openQueryBtn);
} }
@@ -103,7 +103,6 @@ export function createStaticCommandBarButtons(selectedNodeState: SelectedNodeSta
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection); selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection);
}, },
commandButtonLabel: label, commandButtonLabel: label,
tooltipText: userContext.features.commandBarV2 ? "New..." : label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
disabled: disabled:
@@ -116,12 +115,21 @@ export function createStaticCommandBarButtons(selectedNodeState: SelectedNodeSta
} }
} }
return buttons;
}
export function createContextCommandBarButtons(
container: Explorer,
selectedNodeState: SelectedNodeState,
): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = [];
if (!selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") { if (!selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") {
const label = useNotebook.getState().isShellEnabled ? "Open Mongo Shell" : "New Shell"; const label = useNotebook.getState().isShellEnabled ? "Open Mongo Shell" : "New Shell";
const newMongoShellBtn: CommandButtonComponentProps = { const newMongoShellBtn: CommandButtonComponentProps = {
iconSrc: HostedTerminalIcon, iconSrc: HostedTerminalIcon,
iconAlt: label, iconAlt: label,
onCommandClick: (_, container) => { onCommandClick: () => {
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection(); const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
if (useNotebook.getState().isShellEnabled) { if (useNotebook.getState().isShellEnabled) {
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo); container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
@@ -133,7 +141,6 @@ export function createStaticCommandBarButtons(selectedNodeState: SelectedNodeSta
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
}; };
addDivider();
buttons.push(newMongoShellBtn); buttons.push(newMongoShellBtn);
} }
@@ -146,27 +153,25 @@ export function createStaticCommandBarButtons(selectedNodeState: SelectedNodeSta
const newCassandraShellButton: CommandButtonComponentProps = { const newCassandraShellButton: CommandButtonComponentProps = {
iconSrc: HostedTerminalIcon, iconSrc: HostedTerminalIcon,
iconAlt: label, iconAlt: label,
onCommandClick: (_, container) => { onCommandClick: () => {
container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra); container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra);
}, },
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
}; };
addDivider();
buttons.push(newCassandraShellButton); buttons.push(newCassandraShellButton);
} }
return buttons; return buttons;
} }
export function createPlatformButtons(): CommandButtonComponentProps[] { export function createControlCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = [ const buttons: CommandButtonComponentProps[] = [
{ {
iconSrc: SettingsIcon, iconSrc: SettingsIcon,
iconAlt: "Settings", iconAlt: "Settings",
onCommandClick: (_, container) => onCommandClick: () => useSidePanel.getState().openSidePanel("Settings", <SettingsPane explorer={container} />),
useSidePanel.getState().openSidePanel("Settings", <SettingsPane explorer={container} />),
commandButtonLabel: undefined, commandButtonLabel: undefined,
ariaLabel: "Settings", ariaLabel: "Settings",
tooltipText: "Settings", tooltipText: "Settings",
@@ -202,7 +207,7 @@ export function createPlatformButtons(): CommandButtonComponentProps[] {
const feedbackButtonOptions: CommandButtonComponentProps = { const feedbackButtonOptions: CommandButtonComponentProps = {
iconSrc: FeedbackIcon, iconSrc: FeedbackIcon,
iconAlt: label, iconAlt: label,
onCommandClick: (_, container) => container.openCESCVAFeedbackBlade(), onCommandClick: () => container.openCESCVAFeedbackBlade(),
commandButtonLabel: undefined, commandButtonLabel: undefined,
ariaLabel: label, ariaLabel: label,
tooltipText: label, tooltipText: label,
@@ -234,7 +239,7 @@ function areScriptsSupported(): boolean {
); );
} }
function createOpenSynapseLinkDialogButton(): CommandButtonComponentProps { function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonComponentProps {
if (configContext.platform === Platform.Emulator) { if (configContext.platform === Platform.Emulator) {
return undefined; return undefined;
} }
@@ -252,7 +257,7 @@ function createOpenSynapseLinkDialogButton(): CommandButtonComponentProps {
return { return {
iconSrc: SynapseIcon, iconSrc: SynapseIcon,
iconAlt: label, iconAlt: label,
onCommandClick: (_, container) => container.openEnableSynapseLinkDialog(), onCommandClick: () => container.openEnableSynapseLinkDialog(),
commandButtonLabel: label, commandButtonLabel: label,
hasPopup: false, hasPopup: false,
disabled: disabled:
@@ -261,12 +266,12 @@ function createOpenSynapseLinkDialogButton(): CommandButtonComponentProps {
}; };
} }
function createLoginForEntraIDButton(): CommandButtonComponentProps { function createLoginForEntraIDButton(container: Explorer): CommandButtonComponentProps {
if (configContext.platform !== Platform.Portal) { if (configContext.platform !== Platform.Portal) {
return undefined; return undefined;
} }
const handleCommandClick: CommandButtonComponentProps["onCommandClick"] = async (_, container) => { const handleCommandClick = async () => {
await container.openLoginForEntraIDPopUp(); await container.openLoginForEntraIDPopUp();
useDataPlaneRbac.setState({ dataPlaneRbacEnabled: true }); useDataPlaneRbac.setState({ dataPlaneRbacEnabled: true });
}; };
@@ -393,14 +398,13 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState)
return buttons; return buttons;
} }
function createOpenQueryButton(): CommandButtonComponentProps { function createOpenQueryButton(container: Explorer): CommandButtonComponentProps {
const label = "Open Query"; const label = "Open Query";
return { return {
iconSrc: BrowseQueriesIcon, iconSrc: BrowseQueriesIcon,
iconAlt: label, iconAlt: label,
tooltipText: userContext.features.commandBarV2 ? "Open Query..." : "Open Query",
keyboardAction: KeyboardAction.OPEN_QUERY, keyboardAction: KeyboardAction.OPEN_QUERY,
onCommandClick: (_, container) => onCommandClick: () =>
useSidePanel.getState().openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={container} />), useSidePanel.getState().openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={container} />),
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
@@ -423,7 +427,10 @@ function createOpenQueryFromDiskButton(): CommandButtonComponentProps {
}; };
} }
function createOpenTerminalButtonByKind(terminalKind: ViewModels.TerminalKind): CommandButtonComponentProps { function createOpenTerminalButtonByKind(
container: Explorer,
terminalKind: ViewModels.TerminalKind,
): CommandButtonComponentProps {
const terminalFriendlyName = (): string => { const terminalFriendlyName = (): string => {
switch (terminalKind) { switch (terminalKind) {
case ViewModels.TerminalKind.Cassandra: case ViewModels.TerminalKind.Cassandra:
@@ -446,7 +453,7 @@ function createOpenTerminalButtonByKind(terminalKind: ViewModels.TerminalKind):
return { return {
iconSrc: HostedTerminalIcon, iconSrc: HostedTerminalIcon,
iconAlt: label, iconAlt: label,
onCommandClick: (_, container) => { onCommandClick: () => {
if (useNotebook.getState().isNotebookEnabled) { if (useNotebook.getState().isNotebookEnabled) {
container.openNotebookTerminal(terminalKind); container.openNotebookTerminal(terminalKind);
} }
@@ -460,10 +467,11 @@ function createOpenTerminalButtonByKind(terminalKind: ViewModels.TerminalKind):
} }
function createStaticCommandBarButtonsForResourceToken( function createStaticCommandBarButtonsForResourceToken(
container: Explorer,
selectedNodeState: SelectedNodeState, selectedNodeState: SelectedNodeState,
): CommandButtonComponentProps[] { ): CommandButtonComponentProps[] {
const newSqlQueryBtn = createNewSQLQueryButton(selectedNodeState); const newSqlQueryBtn = createNewSQLQueryButton(selectedNodeState);
const openQueryBtn = createOpenQueryButton(); const openQueryBtn = createOpenQueryButton(container);
const resourceTokenCollection: ViewModels.CollectionBase = useDatabases.getState().resourceTokenCollection; const resourceTokenCollection: ViewModels.CollectionBase = useDatabases.getState().resourceTokenCollection;
const isResourceTokenCollectionNodeSelected: boolean = const isResourceTokenCollectionNodeSelected: boolean =
@@ -476,20 +484,20 @@ function createStaticCommandBarButtonsForResourceToken(
openQueryBtn.disabled = !isResourceTokenCollectionNodeSelected; openQueryBtn.disabled = !isResourceTokenCollectionNodeSelected;
if (!openQueryBtn.disabled) { if (!openQueryBtn.disabled) {
openQueryBtn.children = [createOpenQueryButton(), createOpenQueryFromDiskButton()]; openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton()];
} }
return [newSqlQueryBtn, openQueryBtn]; return [newSqlQueryBtn, openQueryBtn];
} }
export function createPostgreButtons(): CommandButtonComponentProps[] { export function createPostgreButtons(container: Explorer): CommandButtonComponentProps[] {
const openPostgreShellBtn = createOpenTerminalButtonByKind(ViewModels.TerminalKind.Postgres); const openPostgreShellBtn = createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.Postgres);
return [openPostgreShellBtn]; return [openPostgreShellBtn];
} }
export function createVCoreMongoButtons(): CommandButtonComponentProps[] { export function createVCoreMongoButtons(container: Explorer): CommandButtonComponentProps[] {
const openVCoreMongoTerminalButton = createOpenTerminalButtonByKind(ViewModels.TerminalKind.VCoreMongo); const openVCoreMongoTerminalButton = createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.VCoreMongo);
return [openVCoreMongoTerminalButton]; return [openVCoreMongoTerminalButton];
} }

View File

@@ -1,10 +1,8 @@
import { ICommandBarItemProps } from "@fluentui/react"; import { ICommandBarItemProps } from "@fluentui/react";
import Explorer from "Explorer/Explorer";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import * as CommandBarUtil from "./CommandBarUtil"; import * as CommandBarUtil from "./CommandBarUtil";
describe("CommandBarUtil tests", () => { describe("CommandBarUtil tests", () => {
const mockExplorer = {} as Explorer;
const createButton = (): CommandButtonComponentProps => { const createButton = (): CommandButtonComponentProps => {
return { return {
iconSrc: "icon", iconSrc: "icon",
@@ -24,7 +22,7 @@ describe("CommandBarUtil tests", () => {
const btn = createButton(); const btn = createButton();
const backgroundColor = "backgroundColor"; const backgroundColor = "backgroundColor";
const converteds = CommandBarUtil.convertButton([btn], backgroundColor, mockExplorer); const converteds = CommandBarUtil.convertButton([btn], backgroundColor);
expect(converteds.length).toBe(1); expect(converteds.length).toBe(1);
const converted = converteds[0]; const converted = converteds[0];
expect(converted.split).toBe(undefined); expect(converted.split).toBe(undefined);
@@ -48,7 +46,7 @@ describe("CommandBarUtil tests", () => {
btn.children.push(child); btn.children.push(child);
} }
const converteds = CommandBarUtil.convertButton([btn], "backgroundColor", mockExplorer); const converteds = CommandBarUtil.convertButton([btn], "backgroundColor");
expect(converteds.length).toBe(1); expect(converteds.length).toBe(1);
const converted = converteds[0]; const converted = converteds[0];
expect(converted.split).toBe(true); expect(converted.split).toBe(true);
@@ -64,7 +62,7 @@ describe("CommandBarUtil tests", () => {
btns.push(createButton()); btns.push(createButton());
} }
const converteds = CommandBarUtil.convertButton(btns, "backgroundColor", mockExplorer); const converteds = CommandBarUtil.convertButton(btns, "backgroundColor");
const uniqueKeys = converteds const uniqueKeys = converteds
.map((btn: ICommandBarItemProps) => btn.key) .map((btn: ICommandBarItemProps) => btn.key)
.filter((value: string, index: number, self: string[]) => self.indexOf(value) === index); .filter((value: string, index: number, self: string[]) => self.indexOf(value) === index);
@@ -76,10 +74,10 @@ describe("CommandBarUtil tests", () => {
const backgroundColor = "backgroundColor"; const backgroundColor = "backgroundColor";
btn.commandButtonLabel = undefined; btn.commandButtonLabel = undefined;
let converted = CommandBarUtil.convertButton([btn], backgroundColor, mockExplorer)[0]; let converted = CommandBarUtil.convertButton([btn], backgroundColor)[0];
expect(converted.text).toEqual(btn.tooltipText); expect(converted.text).toEqual(btn.tooltipText);
converted = CommandBarUtil.convertButton([btn], backgroundColor, mockExplorer)[0]; converted = CommandBarUtil.convertButton([btn], backgroundColor)[0];
delete btn.commandButtonLabel; delete btn.commandButtonLabel;
expect(converted.text).toEqual(btn.tooltipText); expect(converted.text).toEqual(btn.tooltipText);
}); });

View File

@@ -25,11 +25,7 @@ import { MemoryTracker } from "./MemoryTrackerComponent";
* Convert our NavbarButtonConfig to UI Fabric buttons * Convert our NavbarButtonConfig to UI Fabric buttons
* @param btns * @param btns
*/ */
export const convertButton = ( export const convertButton = (btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] => {
btns: CommandButtonComponentProps[],
backgroundColor: string,
container: Explorer,
): ICommandBarItemProps[] => {
const buttonHeightPx = const buttonHeightPx =
configContext.platform == Platform.Fabric configContext.platform == Platform.Fabric
? StyleConstants.FabricCommandBarButtonHeight ? StyleConstants.FabricCommandBarButtonHeight
@@ -58,14 +54,15 @@ export const convertButton = (
iconProps: { iconProps: {
style: { style: {
width: StyleConstants.CommandBarIconWidth, // 16 width: StyleConstants.CommandBarIconWidth, // 16
alignSelf: undefined, alignSelf: btn.iconName ? "baseline" : undefined,
filter: getFilter(btn.disabled), filter: getFilter(btn.disabled),
}, },
imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined, imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined,
iconName: btn.iconName,
}, },
onClick: btn.onCommandClick onClick: btn.onCommandClick
? (ev?: React.MouseEvent<HTMLElement, MouseEvent> | React.KeyboardEvent<HTMLElement>) => { ? (ev?: React.MouseEvent<HTMLElement, MouseEvent> | React.KeyboardEvent<HTMLElement>) => {
btn.onCommandClick(ev, container); btn.onCommandClick(ev);
let copilotEnabled = false; let copilotEnabled = false;
if (useQueryCopilot.getState().copilotEnabled && useQueryCopilot.getState().copilotUserDBEnabled) { if (useQueryCopilot.getState().copilotEnabled && useQueryCopilot.getState().copilotUserDBEnabled) {
copilotEnabled = useQueryCopilot.getState().copilotEnabledforExecution; copilotEnabled = useQueryCopilot.getState().copilotEnabledforExecution;
@@ -138,7 +135,7 @@ export const convertButton = (
result.split = true; result.split = true;
result.subMenuProps = { result.subMenuProps = {
items: convertButton(btn.children, backgroundColor, container), items: convertButton(btn.children, backgroundColor),
styles: { styles: {
list: { list: {
// TODO Figure out how to do it the proper way with subComponentStyles. // TODO Figure out how to do it the proper way with subComponentStyles.
@@ -189,7 +186,7 @@ export const convertButton = (
option?: IDropdownOption, option?: IDropdownOption,
index?: number, index?: number,
): void => { ): void => {
btn.children[index].onCommandClick(event, container); btn.children[index].onCommandClick(event);
TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label: option.text }); TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label: option.text });
}; };
@@ -240,17 +237,14 @@ export const createConnectionStatus = (container: Explorer, poolId: PoolIdType,
}; };
}; };
export function createKeyboardHandlers( export function createKeyboardHandlers(allButtons: CommandButtonComponentProps[]): KeyboardHandlerMap {
allButtons: CommandButtonComponentProps[],
container: Explorer,
): KeyboardHandlerMap {
const handlers: KeyboardHandlerMap = {}; const handlers: KeyboardHandlerMap = {};
function createHandlers(buttons: CommandButtonComponentProps[]) { function createHandlers(buttons: CommandButtonComponentProps[]) {
buttons.forEach((button) => { buttons.forEach((button) => {
if (!button.disabled && button.keyboardAction) { if (!button.disabled && button.keyboardAction) {
handlers[button.keyboardAction] = (e) => { handlers[button.keyboardAction] = (e) => {
button.onCommandClick(e, container); button.onCommandClick(e);
// If the handler is bound, it means the button is visible and enabled, so we should prevent the default action // If the handler is bound, it means the button is visible and enabled, so we should prevent the default action
return true; return true;

View File

@@ -1,16 +0,0 @@
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
import create, { UseStore } from "zustand";
export interface CommandBarStore {
contextButtons: CommandButtonComponentProps[];
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => void;
isHidden: boolean;
setIsHidden: (isHidden: boolean) => void;
}
export const useCommandBar: UseStore<CommandBarStore> = create((set) => ({
contextButtons: [],
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => set((state) => ({ ...state, contextButtons })),
isHidden: false,
setIsHidden: (isHidden: boolean) => set((state) => ({ ...state, isHidden })),
}));

View File

@@ -1,159 +0,0 @@
import {
makeStyles,
Menu,
MenuButton,
MenuItem,
MenuList,
MenuPopover,
MenuTrigger,
Toolbar,
ToolbarButton,
ToolbarDivider,
ToolbarGroup,
Tooltip,
} from "@fluentui/react-components";
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
import Explorer from "Explorer/Explorer";
import {
createPlatformButtons,
createStaticCommandBarButtons,
} from "Explorer/Menus/CommandBar/CommandBarComponentButtonFactory";
import { createKeyboardHandlers } from "Explorer/Menus/CommandBar/CommandBarUtil";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { CosmosFluentProvider, cosmosShorthands, tokens } from "Explorer/Theme/ThemeUtil";
import { useSelectedNode } from "Explorer/useSelectedNode";
import { KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
import React, { MouseEventHandler } from "react";
const useToolbarStyles = makeStyles({
toolbar: {
height: tokens.layoutRowHeight,
justifyContent: "space-between", // Ensures that the two toolbar groups are at opposite ends of the toolbar
...cosmosShorthands.borderBottom(),
},
toolbarGroup: {
display: "flex",
},
});
export interface CommandBarV2Props {
explorer: Explorer;
}
export const CommandBarV2: React.FC<CommandBarV2Props> = ({ explorer }: CommandBarV2Props) => {
const styles = useToolbarStyles();
const selectedNodeState = useSelectedNode();
const contextButtons = useCommandBar((state) => state.contextButtons);
const isHidden = useCommandBar((state) => state.isHidden);
const setKeyboardHandlers = useKeyboardActionGroup(KeyboardActionGroup.COMMAND_BAR);
const staticButtons = createStaticCommandBarButtons(selectedNodeState);
const platformButtons = createPlatformButtons();
if (isHidden) {
setKeyboardHandlers({});
return null;
}
const allButtons = staticButtons.concat(contextButtons).concat(platformButtons);
const keyboardHandlers = createKeyboardHandlers(allButtons, explorer);
setKeyboardHandlers(keyboardHandlers);
return (
<CosmosFluentProvider>
<Toolbar className={styles.toolbar}>
<ToolbarGroup role="presentation" className={styles.toolbarGroup}>
{staticButtons.map((button, index) =>
renderButton(explorer, button, `static-${index}`, contextButtons?.length > 0),
)}
{staticButtons.length > 0 && contextButtons?.length > 0 && <ToolbarDivider />}
{contextButtons.map((button, index) => renderButton(explorer, button, `context-${index}`, false))}
</ToolbarGroup>
<ToolbarGroup role="presentation">
{platformButtons.map((button, index) => renderButton(explorer, button, `platform-${index}`, true))}
</ToolbarGroup>
</Toolbar>
</CosmosFluentProvider>
);
};
// This allows us to migrate individual buttons over to using a JSX.Element for the icon, without requiring us to change them all at once.
function renderIcon(iconSrcOrElement: string | JSX.Element, alt?: string): JSX.Element {
if (typeof iconSrcOrElement === "string") {
return <img src={iconSrcOrElement} alt={alt} />;
}
return iconSrcOrElement;
}
function renderButton(
explorer: Explorer,
btn: CommandButtonComponentProps,
key: string,
iconOnly: boolean,
): JSX.Element {
if (btn.isDivider) {
return <ToolbarDivider key={key} />;
}
const hasChildren = !!btn.children && btn.children.length > 0;
const label = btn.commandButtonLabel || btn.tooltipText;
const tooltip = btn.tooltipText || (iconOnly ? label : undefined);
const onClick: MouseEventHandler | undefined =
btn.onCommandClick && !hasChildren ? (e) => btn.onCommandClick(e, explorer) : undefined;
// We don't know which element will be the top-level element, so just slap a key on all of 'em
let button = hasChildren ? (
<MenuButton key={key} appearance="subtle" aria-label={btn.ariaLabel} icon={renderIcon(btn.iconSrc, btn.iconAlt)}>
{!iconOnly && label}
</MenuButton>
) : (
<ToolbarButton key={key} aria-label={btn.ariaLabel} onClick={onClick} icon={renderIcon(btn.iconSrc, btn.iconAlt)}>
{!iconOnly && label}
</ToolbarButton>
);
if (tooltip) {
button = (
<Tooltip key={key} content={tooltip} relationship="description" withArrow>
{button}
</Tooltip>
);
}
if (hasChildren) {
button = (
<Menu key={key}>
<MenuTrigger disableButtonEnhancement>{button}</MenuTrigger>
<MenuPopover>
<MenuList>{btn.children.map((child, index) => renderMenuItem(explorer, child, index.toString()))}</MenuList>
</MenuPopover>
</Menu>
);
}
return button;
}
function renderMenuItem(explorer: Explorer, btn: CommandButtonComponentProps, key: string): JSX.Element {
const hasChildren = !!btn.children && btn.children.length > 0;
const onClick: MouseEventHandler | undefined = btn.onCommandClick
? (e) => btn.onCommandClick(e, explorer)
: undefined;
const item = (
<MenuItem key={key} onClick={onClick} icon={renderIcon(btn.iconSrc, btn.iconAlt)}>
{btn.commandButtonLabel || btn.tooltipText}
</MenuItem>
);
if (hasChildren) {
return (
<Menu>
<MenuTrigger disableButtonEnhancement>{item}</MenuTrigger>
<MenuPopover>
<MenuList>{btn.children.map((child, index) => renderMenuItem(explorer, child, index.toString()))}</MenuList>
</MenuPopover>
</Menu>
);
}
return item;
}

View File

@@ -17,10 +17,10 @@ import {
} from "@fluentui/react"; } from "@fluentui/react";
import * as Constants from "Common/Constants"; import * as Constants from "Common/Constants";
import { createCollection } from "Common/dataAccess/createCollection"; import { createCollection } from "Common/dataAccess/createCollection";
import { getNewDatabaseSharedThroughputDefault } from "Common/DatabaseUtility";
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils"; import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
import { configContext, Platform } from "ConfigContext"; import { configContext, Platform } from "ConfigContext";
import * as DataModels from "Contracts/DataModels"; import * as DataModels from "Contracts/DataModels";
import { SubscriptionType } from "Contracts/SubscriptionType";
import { AddVectorEmbeddingPolicyForm } from "Explorer/Panes/VectorSearchPanel/AddVectorEmbeddingPolicyForm"; import { AddVectorEmbeddingPolicyForm } from "Explorer/Panes/VectorSearchPanel/AddVectorEmbeddingPolicyForm";
import { useSidePanel } from "hooks/useSidePanel"; import { useSidePanel } from "hooks/useSidePanel";
import { useTeachingBubble } from "hooks/useTeachingBubble"; import { useTeachingBubble } from "hooks/useTeachingBubble";
@@ -125,7 +125,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
createNewDatabase: createNewDatabase:
userContext.apiType !== "Tables" && configContext.platform !== Platform.Fabric && !this.props.databaseId, userContext.apiType !== "Tables" && configContext.platform !== Platform.Fabric && !this.props.databaseId,
newDatabaseId: props.isQuickstart ? this.getSampleDBName() : "", newDatabaseId: props.isQuickstart ? this.getSampleDBName() : "",
isSharedThroughputChecked: this.getSharedThroughputDefault(), isSharedThroughputChecked: getNewDatabaseSharedThroughputDefault(),
selectedDatabaseId: selectedDatabaseId:
userContext.apiType === "Tables" ? CollectionCreation.TablesAPIDefaultDatabase : this.props.databaseId, userContext.apiType === "Tables" ? CollectionCreation.TablesAPIDefaultDatabase : this.props.databaseId,
collectionId: props.isQuickstart ? `Sample${getCollectionName()}` : "", collectionId: props.isQuickstart ? `Sample${getCollectionName()}` : "",
@@ -1138,10 +1138,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
return userContext.databaseAccount?.properties?.enableFreeTier; return userContext.databaseAccount?.properties?.enableFreeTier;
} }
private getSharedThroughputDefault(): boolean {
return userContext.subscriptionType !== SubscriptionType.EA && !isServerlessAccount();
}
private getFreeTierIndexingText(): string { private getFreeTierIndexingText(): string {
return this.state.enableIndexing return this.state.enableIndexing
? "All properties in your documents will be indexed by default for flexible and efficient queries." ? "All properties in your documents will be indexed by default for flexible and efficient queries."

View File

@@ -1,4 +1,5 @@
import { Checkbox, Stack, Text, TextField } from "@fluentui/react"; import { Checkbox, Stack, Text, TextField } from "@fluentui/react";
import { getNewDatabaseSharedThroughputDefault } from "Common/DatabaseUtility";
import React, { FunctionComponent, useEffect, useState } from "react"; import React, { FunctionComponent, useEffect, useState } from "react";
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils"; import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
@@ -48,7 +49,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
const databaseLevelThroughputTooltipText = `Provisioned throughput at the ${databaseLabel} level will be shared across all ${collectionsLabel} within the ${databaseLabel}.`; const databaseLevelThroughputTooltipText = `Provisioned throughput at the ${databaseLabel} level will be shared across all ${collectionsLabel} within the ${databaseLabel}.`;
const [databaseCreateNewShared, setDatabaseCreateNewShared] = useState<boolean>( const [databaseCreateNewShared, setDatabaseCreateNewShared] = useState<boolean>(
subscriptionType !== SubscriptionType.EA && !isServerlessAccount(), getNewDatabaseSharedThroughputDefault(),
); );
const [formErrors, setFormErrors] = useState<string>(""); const [formErrors, setFormErrors] = useState<string>("");
const [isExecuting, setIsExecuting] = useState<boolean>(false); const [isExecuting, setIsExecuting] = useState<boolean>(false);

View File

@@ -65,7 +65,7 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
horizontal={true} horizontal={true}
> >
<StyledCheckboxBase <StyledCheckboxBase
checked={true} checked={false}
label="Provision throughput" label="Provision throughput"
onChange={[Function]} onChange={[Function]}
styles={ styles={
@@ -90,14 +90,6 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
</InfoTooltip> </InfoTooltip>
</Stack> </Stack>
</Stack> </Stack>
<ThroughputInput
isDatabase={true}
isSharded={true}
onCostAcknowledgeChange={[Function]}
setIsAutoscale={[Function]}
setIsThroughputCapExceeded={[Function]}
setThroughputValue={[Function]}
/>
</div> </div>
</RightPaneForm> </RightPaneForm>
`; `;

View File

@@ -124,7 +124,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
message: message:
"Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.", "Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.",
}; };
const confirmDatabase = `Confirm by typing the ${getDatabaseName()} id`; const confirmDatabase = `Confirm by typing the ${getDatabaseName()} id (name)`;
const reasonInfo = `Help us improve Azure Cosmos DB! What is the reason why you are deleting this ${getDatabaseName()}?`; const reasonInfo = `Help us improve Azure Cosmos DB! What is the reason why you are deleting this ${getDatabaseName()}?`;
return ( return (
<RightPaneForm {...props}> <RightPaneForm {...props}>
@@ -132,7 +132,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
<div className="panelMainContent"> <div className="panelMainContent">
<div className="confirmDeleteInput"> <div className="confirmDeleteInput">
<span className="mandatoryStar">* </span> <span className="mandatoryStar">* </span>
<Text variant="small">Confirm by typing the {getDatabaseName()} id</Text> <Text variant="small">{confirmDatabase}</Text>
<TextField <TextField
id="confirmDatabaseId" id="confirmDatabaseId"
data-test="Input:confirmDatabaseId" data-test="Input:confirmDatabaseId"

View File

@@ -1,3 +1,7 @@
import {
AuthError as msalAuthError,
BrowserAuthErrorMessage as msalBrowserAuthErrorMessage,
} from "@azure/msal-browser";
import { import {
Checkbox, Checkbox,
ChoiceGroup, ChoiceGroup,
@@ -5,8 +9,6 @@ import {
IChoiceGroupOption, IChoiceGroupOption,
ISpinButtonStyles, ISpinButtonStyles,
IToggleStyles, IToggleStyles,
MessageBar,
MessageBarType,
Position, Position,
SpinButton, SpinButton,
Toggle, Toggle,
@@ -30,6 +32,7 @@ import {
} from "Shared/StorageUtility"; } from "Shared/StorageUtility";
import * as StringUtility from "Shared/StringUtility"; import * as StringUtility from "Shared/StringUtility";
import { updateUserContext, userContext } from "UserContext"; import { updateUserContext, userContext } from "UserContext";
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
import { logConsoleError, logConsoleInfo } from "Utils/NotificationConsoleUtils"; import { logConsoleError, logConsoleInfo } from "Utils/NotificationConsoleUtils";
import * as PriorityBasedExecutionUtils from "Utils/PriorityBasedExecutionUtils"; import * as PriorityBasedExecutionUtils from "Utils/PriorityBasedExecutionUtils";
import { getReadOnlyKeys, listKeys } from "Utils/arm/generatedClients/cosmos/databaseAccounts"; import { getReadOnlyKeys, listKeys } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
@@ -108,7 +111,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
? LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled) ? LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled)
: Constants.RBACOptions.setAutomaticRBACOption, : Constants.RBACOptions.setAutomaticRBACOption,
); );
const [showDataPlaneRBACWarning, setShowDataPlaneRBACWarning] = useState<boolean>(false);
const [ruThresholdEnabled, setRUThresholdEnabled] = useState<boolean>(isRUThresholdEnabled()); const [ruThresholdEnabled, setRUThresholdEnabled] = useState<boolean>(isRUThresholdEnabled());
const [ruThreshold, setRUThreshold] = useState<number>(getRUThreshold()); const [ruThreshold, setRUThreshold] = useState<number>(getRUThreshold());
@@ -203,6 +205,24 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
hasDataPlaneRbacSettingChanged: true, hasDataPlaneRbacSettingChanged: true,
}); });
useDataPlaneRbac.setState({ dataPlaneRbacEnabled: true }); useDataPlaneRbac.setState({ dataPlaneRbacEnabled: true });
try {
const aadToken = await acquireMsalTokenForAccount(userContext.databaseAccount, true);
updateUserContext({ aadToken: aadToken });
useDataPlaneRbac.setState({ aadTokenUpdated: true });
} catch (authError) {
if (
authError instanceof msalAuthError &&
authError.errorCode === msalBrowserAuthErrorMessage.popUpWindowError.code
) {
logConsoleError(
`We were unable to establish authorization for this account, due to pop-ups being disabled in the browser.\nPlease enable pop-ups for this site and click on "Login for Entra ID" button`,
);
} else {
logConsoleError(
`"Failed to acquire authorization token automatically. Please click on "Login for Entra ID" button to enable Entra ID RBAC operations`,
);
}
}
} else { } else {
updateUserContext({ updateUserContext({
dataPlaneRbacEnabled: false, dataPlaneRbacEnabled: false,
@@ -347,13 +367,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
option: IChoiceGroupOption, option: IChoiceGroupOption,
): void => { ): void => {
setEnableDataPlaneRBACOption(option.key); setEnableDataPlaneRBACOption(option.key);
const shouldShowWarning =
(option.key === Constants.RBACOptions.setTrueRBACOption ||
(option.key === Constants.RBACOptions.setAutomaticRBACOption &&
userContext.databaseAccount.properties.disableLocalAuth === true)) &&
!useDataPlaneRbac.getState().aadTokenUpdated;
setShowDataPlaneRBACWarning(shouldShowWarning);
}; };
const handleOnRUThresholdToggleChange = (ev: React.MouseEvent<HTMLElement>, checked?: boolean): void => { const handleOnRUThresholdToggleChange = (ev: React.MouseEvent<HTMLElement>, checked?: boolean): void => {
@@ -528,17 +541,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
</AccordionHeader> </AccordionHeader>
<AccordionPanel> <AccordionPanel>
<div className={styles.settingsSectionContainer}> <div className={styles.settingsSectionContainer}>
{showDataPlaneRBACWarning && configContext.platform === Platform.Portal && (
<MessageBar
messageBarType={MessageBarType.warning}
isMultiline={true}
onDismiss={() => setShowDataPlaneRBACWarning(false)}
dismissButtonAriaLabel="Close"
>
Please click on &quot;Login for Entra ID RBAC&quot; button prior to performing Entra ID RBAC
operations
</MessageBar>
)}
<div className={styles.settingsSectionDescription}> <div className={styles.settingsSectionDescription}>
Choose Automatic to enable Entra ID RBAC automatically. True/False to force enable/disable Entra Choose Automatic to enable Entra ID RBAC automatically. True/False to force enable/disable Entra
ID RBAC. ID RBAC.

View File

@@ -106,7 +106,7 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
horizontal={true} horizontal={true}
> >
<StyledCheckboxBase <StyledCheckboxBase
checked={true} checked={false}
label="Share throughput across containers" label="Share throughput across containers"
onChange={[Function]} onChange={[Function]}
styles={ styles={
@@ -137,14 +137,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
/> />
</StyledTooltipHostBase> </StyledTooltipHostBase>
</Stack> </Stack>
<ThroughputInput
isDatabase={true}
isSharded={true}
onCostAcknowledgeChange={[Function]}
setIsAutoscale={[Function]}
setIsThroughputCapExceeded={[Function]}
setThroughputValue={[Function]}
/>
</Stack> </Stack>
<Separator <Separator
className="panelSeparator" className="panelSeparator"
@@ -263,6 +255,14 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
</CustomizedDefaultButton> </CustomizedDefaultButton>
</Stack> </Stack>
</Stack> </Stack>
<ThroughputInput
isDatabase={false}
isSharded={true}
onCostAcknowledgeChange={[Function]}
setIsAutoscale={[Function]}
setIsThroughputCapExceeded={[Function]}
setThroughputValue={[Function]}
/>
<Stack> <Stack>
<Stack <Stack
horizontal={true} horizontal={true}

View File

@@ -361,13 +361,11 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
<span <span
className="css-113" className="css-113"
> >
Confirm by typing the Confirm by typing the Database id (name)
Database
id
</span> </span>
</Text> </Text>
<StyledTextFieldBase <StyledTextFieldBase
ariaLabel="Confirm by typing the Database id" ariaLabel="Confirm by typing the Database id (name)"
autoFocus={true} autoFocus={true}
data-test="Input:confirmDatabaseId" data-test="Input:confirmDatabaseId"
id="confirmDatabaseId" id="confirmDatabaseId"
@@ -382,7 +380,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
} }
> >
<TextFieldBase <TextFieldBase
ariaLabel="Confirm by typing the Database id" ariaLabel="Confirm by typing the Database id (name)"
autoFocus={true} autoFocus={true}
data-test="Input:confirmDatabaseId" data-test="Input:confirmDatabaseId"
deferredValidationTime={200} deferredValidationTime={200}
@@ -677,7 +675,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
> >
<input <input
aria-invalid={false} aria-invalid={false}
aria-label="Confirm by typing the Database id" aria-label="Confirm by typing the Database id (name)"
autoFocus={true} autoFocus={true}
className="ms-TextField-field field-117" className="ms-TextField-field field-117"
data-test="Input:confirmDatabaseId" data-test="Input:confirmDatabaseId"

View File

@@ -3,7 +3,7 @@ import { Stack } from "@fluentui/react";
import { QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants"; import { QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants";
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
import { EditorReact } from "Explorer/Controls/Editor/EditorReact"; import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar"; import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
import { SaveQueryPane } from "Explorer/Panes/SaveQueryPane/SaveQueryPane"; import { SaveQueryPane } from "Explorer/Panes/SaveQueryPane/SaveQueryPane";
import { QueryCopilotPromptbar } from "Explorer/QueryCopilot/QueryCopilotPromptbar"; import { QueryCopilotPromptbar } from "Explorer/QueryCopilot/QueryCopilotPromptbar";
import { readCopilotToggleStatus, saveCopilotToggleStatus } from "Explorer/QueryCopilot/QueryCopilotUtilities"; import { readCopilotToggleStatus, saveCopilotToggleStatus } from "Explorer/QueryCopilot/QueryCopilotUtilities";

View File

@@ -5,7 +5,7 @@ import { Platform, updateConfigContext } from "ConfigContext";
import { useDialog } from "Explorer/Controls/Dialog"; import { useDialog } from "Explorer/Controls/Dialog";
import { EditorReactProps } from "Explorer/Controls/Editor/EditorReact"; import { EditorReactProps } from "Explorer/Controls/Editor/EditorReact";
import { ProgressModalDialog } from "Explorer/Controls/ProgressModalDialog"; import { ProgressModalDialog } from "Explorer/Controls/ProgressModalDialog";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar"; import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
import { import {
ButtonsDependencies, ButtonsDependencies,
DELETE_BUTTON_ID, DELETE_BUTTON_ID,
@@ -461,7 +461,7 @@ describe("Documents tab (noSql API)", () => {
useCommandBar useCommandBar
.getState() .getState()
.contextButtons.find((button) => button.id === NEW_DOCUMENT_BUTTON_ID) .contextButtons.find((button) => button.id === NEW_DOCUMENT_BUTTON_ID)
.onCommandClick(undefined, undefined); .onCommandClick(undefined);
}); });
expect(wrapper.findWhere((node) => node.text().includes("replace_with_new_document_id")).exists()).toBeTruthy(); expect(wrapper.findWhere((node) => node.text().includes("replace_with_new_document_id")).exists()).toBeTruthy();
}); });
@@ -471,7 +471,7 @@ describe("Documents tab (noSql API)", () => {
useCommandBar useCommandBar
.getState() .getState()
.contextButtons.find((button) => button.id === NEW_DOCUMENT_BUTTON_ID) .contextButtons.find((button) => button.id === NEW_DOCUMENT_BUTTON_ID)
.onCommandClick(undefined, undefined); .onCommandClick(undefined);
}); });
expect(useCommandBar.getState().contextButtons.find((button) => button.id === SAVE_BUTTON_ID)).toBeDefined(); expect(useCommandBar.getState().contextButtons.find((button) => button.id === SAVE_BUTTON_ID)).toBeDefined();
@@ -483,7 +483,7 @@ describe("Documents tab (noSql API)", () => {
await useCommandBar await useCommandBar
.getState() .getState()
.contextButtons.find((button) => button.id === DELETE_BUTTON_ID) .contextButtons.find((button) => button.id === DELETE_BUTTON_ID)
.onCommandClick(undefined, undefined); .onCommandClick(undefined);
}); });
expect(useDialog.getState().showOkCancelModalDialog).toHaveBeenCalled(); expect(useDialog.getState().showOkCancelModalDialog).toHaveBeenCalled();
@@ -494,7 +494,7 @@ describe("Documents tab (noSql API)", () => {
useCommandBar useCommandBar
.getState() .getState()
.contextButtons.find((button) => button.id === DELETE_BUTTON_ID) .contextButtons.find((button) => button.id === DELETE_BUTTON_ID)
.onCommandClick(undefined, undefined); .onCommandClick(undefined);
}); });
expect(ProgressModalDialog).toHaveBeenCalled(); expect(ProgressModalDialog).toHaveBeenCalled();
@@ -508,7 +508,7 @@ describe("Documents tab (noSql API)", () => {
useCommandBar useCommandBar
.getState() .getState()
.contextButtons.find((button) => button.id === DELETE_BUTTON_ID) .contextButtons.find((button) => button.id === DELETE_BUTTON_ID)
.onCommandClick(undefined, undefined); .onCommandClick(undefined);
}); });
// The implementation uses setTimeout, so wait for it to finish // The implementation uses setTimeout, so wait for it to finish

View File

@@ -28,7 +28,7 @@ import { useDialog } from "Explorer/Controls/Dialog";
import { EditorReact } from "Explorer/Controls/Editor/EditorReact"; import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
import { ProgressModalDialog } from "Explorer/Controls/ProgressModalDialog"; import { ProgressModalDialog } from "Explorer/Controls/ProgressModalDialog";
import Explorer from "Explorer/Explorer"; import Explorer from "Explorer/Explorer";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar"; import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
import { querySampleDocuments, readSampleDocument } from "Explorer/QueryCopilot/QueryCopilotUtilities"; import { querySampleDocuments, readSampleDocument } from "Explorer/QueryCopilot/QueryCopilotUtilities";
import { import {
ColumnsSelection, ColumnsSelection,
@@ -2115,7 +2115,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
// TODO: remove isMongoBulkDeleteDisabled when new mongo proxy is enabled for all users // TODO: remove isMongoBulkDeleteDisabled when new mongo proxy is enabled for all users
// TODO: remove partitionKey.systemKey when JS SDK bug is fixed // TODO: remove partitionKey.systemKey when JS SDK bug is fixed
const isMongoBulkDeleteDisabled = !MongoProxyClient.useMongoProxyEndpoint("bulkdelete"); const isMongoBulkDeleteDisabled = !MongoProxyClient.useMongoProxyEndpoint(Constants.MongoProxyApi.BulkDelete);
const isBulkDeleteDisabled = const isBulkDeleteDisabled =
(partitionKey.systemKey && !isPreferredApiMongoDB) || (isPreferredApiMongoDB && isMongoBulkDeleteDisabled); (partitionKey.systemKey && !isPreferredApiMongoDB) || (isPreferredApiMongoDB && isMongoBulkDeleteDisabled);
// ------------------------------------------------------- // -------------------------------------------------------

View File

@@ -1,7 +1,7 @@
import { deleteDocuments } from "Common/MongoProxyClient"; import { deleteDocuments } from "Common/MongoProxyClient";
import { Platform, updateConfigContext } from "ConfigContext"; import { Platform, updateConfigContext } from "ConfigContext";
import { EditorReactProps } from "Explorer/Controls/Editor/EditorReact"; import { EditorReactProps } from "Explorer/Controls/Editor/EditorReact";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar"; import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
import { import {
DELETE_BUTTON_ID, DELETE_BUTTON_ID,
DISCARD_BUTTON_ID, DISCARD_BUTTON_ID,
@@ -163,7 +163,7 @@ describe("Documents tab (Mongo API)", () => {
useCommandBar useCommandBar
.getState() .getState()
.contextButtons.find((button) => button.id === NEW_DOCUMENT_BUTTON_ID) .contextButtons.find((button) => button.id === NEW_DOCUMENT_BUTTON_ID)
.onCommandClick(undefined, undefined); .onCommandClick(undefined);
}); });
expect(wrapper.findWhere((node) => node.text().includes("replace_with_new_document_id")).exists()).toBeTruthy(); expect(wrapper.findWhere((node) => node.text().includes("replace_with_new_document_id")).exists()).toBeTruthy();
}); });
@@ -173,7 +173,7 @@ describe("Documents tab (Mongo API)", () => {
useCommandBar useCommandBar
.getState() .getState()
.contextButtons.find((button) => button.id === NEW_DOCUMENT_BUTTON_ID) .contextButtons.find((button) => button.id === NEW_DOCUMENT_BUTTON_ID)
.onCommandClick(undefined, undefined); .onCommandClick(undefined);
}); });
expect(useCommandBar.getState().contextButtons.find((button) => button.id === SAVE_BUTTON_ID)).toBeDefined(); expect(useCommandBar.getState().contextButtons.find((button) => button.id === SAVE_BUTTON_ID)).toBeDefined();
@@ -188,7 +188,7 @@ describe("Documents tab (Mongo API)", () => {
useCommandBar useCommandBar
.getState() .getState()
.contextButtons.find((button) => button.id === DELETE_BUTTON_ID) .contextButtons.find((button) => button.id === DELETE_BUTTON_ID)
.onCommandClick(undefined, undefined); .onCommandClick(undefined);
}); });
expect(mockDeleteDocuments).toHaveBeenCalled(); expect(mockDeleteDocuments).toHaveBeenCalled();

View File

@@ -38,7 +38,6 @@ import {
TextSortDescendingRegular, TextSortDescendingRegular,
} from "@fluentui/react-icons"; } from "@fluentui/react-icons";
import { NormalizedEventKey } from "Common/Constants"; import { NormalizedEventKey } from "Common/Constants";
import { Environment, getEnvironment } from "Common/EnvironmentUtility";
import { TableColumnSelectionPane } from "Explorer/Panes/TableColumnSelectionPane/TableColumnSelectionPane"; import { TableColumnSelectionPane } from "Explorer/Panes/TableColumnSelectionPane/TableColumnSelectionPane";
import { import {
ColumnSizesMap, ColumnSizesMap,
@@ -228,31 +227,29 @@ export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> =
<MenuItem key="refresh" icon={<ArrowClockwise16Regular />} onClick={onRefreshTable}> <MenuItem key="refresh" icon={<ArrowClockwise16Regular />} onClick={onRefreshTable}>
Refresh Refresh
</MenuItem> </MenuItem>
{[Environment.Development, Environment.Mpac].includes(getEnvironment()) && ( <>
<> <MenuItem
<MenuItem icon={<TextSortAscendingRegular />}
icon={<TextSortAscendingRegular />} onClick={(e) => onSortClick(e, column.id, "ascending")}
onClick={(e) => onSortClick(e, column.id, "ascending")} >
> Sort ascending
Sort ascending </MenuItem>
<MenuItem
icon={<TextSortDescendingRegular />}
onClick={(e) => onSortClick(e, column.id, "descending")}
>
Sort descending
</MenuItem>
<MenuItem icon={<ArrowResetRegular />} onClick={(e) => onSortClick(e, undefined, undefined)}>
Reset sorting
</MenuItem>
{!isColumnSelectionDisabled && (
<MenuItem key="editcolumns" icon={<EditRegular />} onClick={openColumnSelectionPane}>
Edit columns
</MenuItem> </MenuItem>
<MenuItem )}
icon={<TextSortDescendingRegular />} <MenuDivider />
onClick={(e) => onSortClick(e, column.id, "descending")} </>
>
Sort descending
</MenuItem>
<MenuItem icon={<ArrowResetRegular />} onClick={(e) => onSortClick(e, undefined, undefined)}>
Reset sorting
</MenuItem>
{!isColumnSelectionDisabled && (
<MenuItem key="editcolumns" icon={<EditRegular />} onClick={openColumnSelectionPane}>
Edit columns
</MenuItem>
)}
<MenuDivider />
</>
)}
<MenuItem <MenuItem
key="keyboardresize" key="keyboardresize"
icon={<TableResizeColumnRegular />} icon={<TableResizeColumnRegular />}
@@ -260,25 +257,24 @@ export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> =
> >
Resize with left/right arrow keys Resize with left/right arrow keys
</MenuItem> </MenuItem>
{[Environment.Development, Environment.Mpac].includes(getEnvironment()) && {!isColumnSelectionDisabled && (
!isColumnSelectionDisabled && ( <MenuItem
<MenuItem key="remove"
key="remove" icon={<DeleteRegular />}
icon={<DeleteRegular />} onClick={() => {
onClick={() => { // Remove column id from selectedColumnIds
// Remove column id from selectedColumnIds const index = selectedColumnIds.indexOf(column.id);
const index = selectedColumnIds.indexOf(column.id); if (index === -1) {
if (index === -1) { return;
return; }
} const newSelectedColumnIds = [...selectedColumnIds];
const newSelectedColumnIds = [...selectedColumnIds]; newSelectedColumnIds.splice(index, 1);
newSelectedColumnIds.splice(index, 1); onColumnSelectionChange(newSelectedColumnIds);
onColumnSelectionChange(newSelectedColumnIds); }}
}} >
> Remove column
Remove column </MenuItem>
</MenuItem> )}
)}
</MenuList> </MenuList>
</MenuPopover> </MenuPopover>
</Menu> </Menu>

View File

@@ -55,7 +55,7 @@ export default class MongoShellTabComponent extends Component<
constructor(props: IMongoShellTabComponentProps) { constructor(props: IMongoShellTabComponentProps) {
super(props); super(props);
this._logTraces = new Map(); this._logTraces = new Map();
this._useMongoProxyEndpoint = useMongoProxyEndpoint("legacyMongoShell"); this._useMongoProxyEndpoint = useMongoProxyEndpoint(Constants.MongoProxyApi.LegacyMongoShell);
this.state = { this.state = {
url: getMongoShellUrl(this._useMongoProxyEndpoint), url: getMongoShellUrl(this._useMongoProxyEndpoint),

View File

@@ -152,6 +152,7 @@ export default class NotebookTabV2 extends NotebookTabBase {
ariaLabel: saveLabel, ariaLabel: saveLabel,
children: saveButtonChildren.length && [ children: saveButtonChildren.length && [
{ {
iconName: "Save",
onCommandClick: () => this.notebookComponentAdapter.notebookSave(), onCommandClick: () => this.notebookComponentAdapter.notebookSave(),
commandButtonLabel: saveLabel, commandButtonLabel: saveLabel,
hasPopup: false, hasPopup: false,

View File

@@ -6,7 +6,6 @@ import { SplitterDirection } from "Common/Splitter";
import { Platform, configContext } from "ConfigContext"; import { Platform, configContext } from "ConfigContext";
import { useDialog } from "Explorer/Controls/Dialog"; import { useDialog } from "Explorer/Controls/Dialog";
import { monaco } from "Explorer/LazyMonaco"; import { monaco } from "Explorer/LazyMonaco";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal"; import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal";
import { useCopilotStore } from "Explorer/QueryCopilot/QueryCopilotContext"; import { useCopilotStore } from "Explorer/QueryCopilot/QueryCopilotContext";
import { QueryCopilotPromptbar } from "Explorer/QueryCopilot/QueryCopilotPromptbar"; import { QueryCopilotPromptbar } from "Explorer/QueryCopilot/QueryCopilotPromptbar";
@@ -55,6 +54,7 @@ import { useSidePanel } from "../../../hooks/useSidePanel";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import { EditorReact } from "../../Controls/Editor/EditorReact"; import { EditorReact } from "../../Controls/Editor/EditorReact";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane"; import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane";
import { SaveQueryPane } from "../../Panes/SaveQueryPane/SaveQueryPane"; import { SaveQueryPane } from "../../Panes/SaveQueryPane/SaveQueryPane";
import TabsBase from "../TabsBase"; import TabsBase from "../TabsBase";

View File

@@ -1,6 +1,5 @@
import { Resource, StoredProcedureDefinition } from "@azure/cosmos"; import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { Pivot, PivotItem } from "@fluentui/react"; import { Pivot, PivotItem } from "@fluentui/react";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { KeyboardAction } from "KeyboardShortcuts"; import { KeyboardAction } from "KeyboardShortcuts";
import React from "react"; import React from "react";
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg"; import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
@@ -16,6 +15,7 @@ import { useTabs } from "../../../hooks/useTabs";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import { EditorReact } from "../../Controls/Editor/EditorReact"; import { EditorReact } from "../../Controls/Editor/EditorReact";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
import StoredProcedure from "../../Tree/StoredProcedure"; import StoredProcedure from "../../Tree/StoredProcedure";
import { useSelectedNode } from "../../useSelectedNode"; import { useSelectedNode } from "../../useSelectedNode";
import ScriptTabBase from "../ScriptTabBase"; import ScriptTabBase from "../ScriptTabBase";

View File

@@ -6,8 +6,7 @@ import { IpRule } from "Contracts/DataModels";
import { MessageTypes } from "Contracts/ExplorerContracts"; import { MessageTypes } from "Contracts/ExplorerContracts";
import { CollectionTabKind } from "Contracts/ViewModels"; import { CollectionTabKind } from "Contracts/ViewModels";
import Explorer from "Explorer/Explorer"; import Explorer from "Explorer/Explorer";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar"; import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
import { CommandBarV2 } from "Explorer/Menus/CommandBarV2/CommandBarV2";
import { QueryCopilotTab } from "Explorer/QueryCopilot/QueryCopilotTab"; import { QueryCopilotTab } from "Explorer/QueryCopilot/QueryCopilotTab";
import { SplashScreen } from "Explorer/SplashScreen/SplashScreen"; import { SplashScreen } from "Explorer/SplashScreen/SplashScreen";
import { ConnectTab } from "Explorer/Tabs/ConnectTab"; import { ConnectTab } from "Explorer/Tabs/ConnectTab";
@@ -107,7 +106,6 @@ export const Tabs = ({ explorer }: TabsProps): JSX.Element => {
</ul> </ul>
</div> </div>
<div className="tabPanesContainer"> <div className="tabPanesContainer">
{userContext.features.commandBarV2 && <CommandBarV2 explorer={explorer} />}
{activeReactTab !== undefined && getReactTabContent(activeReactTab, explorer)} {activeReactTab !== undefined && getReactTabContent(activeReactTab, explorer)}
{openedTabs.map((tab) => ( {openedTabs.map((tab) => (
<TabPane key={tab.tabId} tab={tab} active={activeTab === tab} /> <TabPane key={tab.tabId} tab={tab} active={activeTab === tab} />

View File

@@ -1,4 +1,3 @@
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { KeyboardActionGroup, clearKeyboardActionGroup } from "KeyboardShortcuts"; import { KeyboardActionGroup, clearKeyboardActionGroup } from "KeyboardShortcuts";
import * as ko from "knockout"; import * as ko from "knockout";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
@@ -10,6 +9,7 @@ import { useNotificationConsole } from "../../hooks/useNotificationConsole";
import { useTabs } from "../../hooks/useTabs"; import { useTabs } from "../../hooks/useTabs";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel"; import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
import { useSelectedNode } from "../useSelectedNode"; import { useSelectedNode } from "../useSelectedNode";
// TODO: Use specific actions for logging telemetry data // TODO: Use specific actions for logging telemetry data

View File

@@ -1,6 +1,5 @@
import { TriggerDefinition } from "@azure/cosmos"; import { TriggerDefinition } from "@azure/cosmos";
import { Dropdown, IDropdownOption, Label, TextField } from "@fluentui/react"; import { Dropdown, IDropdownOption, Label, TextField } from "@fluentui/react";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { KeyboardAction } from "KeyboardShortcuts"; import { KeyboardAction } from "KeyboardShortcuts";
import React, { Component } from "react"; import React, { Component } from "react";
import DiscardIcon from "../../../images/discard.svg"; import DiscardIcon from "../../../images/discard.svg";
@@ -15,6 +14,7 @@ import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { SqlTriggerResource } from "../../Utils/arm/generatedClients/cosmos/types"; import { SqlTriggerResource } from "../../Utils/arm/generatedClients/cosmos/types";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import { EditorReact } from "../Controls/Editor/EditorReact"; import { EditorReact } from "../Controls/Editor/EditorReact";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import TriggerTab from "./TriggerTab"; import TriggerTab from "./TriggerTab";
const triggerTypeOptions: IDropdownOption[] = [ const triggerTypeOptions: IDropdownOption[] = [

View File

@@ -1,6 +1,5 @@
import { UserDefinedFunctionDefinition } from "@azure/cosmos"; import { UserDefinedFunctionDefinition } from "@azure/cosmos";
import { Label, TextField } from "@fluentui/react"; import { Label, TextField } from "@fluentui/react";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { KeyboardAction } from "KeyboardShortcuts"; import { KeyboardAction } from "KeyboardShortcuts";
import React, { Component } from "react"; import React, { Component } from "react";
import DiscardIcon from "../../../images/discard.svg"; import DiscardIcon from "../../../images/discard.svg";
@@ -14,6 +13,7 @@ import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import { EditorReact } from "../Controls/Editor/EditorReact"; import { EditorReact } from "../Controls/Editor/EditorReact";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import UserDefinedFunctionTab from "./UserDefinedFunctionTab"; import UserDefinedFunctionTab from "./UserDefinedFunctionTab";
interface IUserDefinedFunctionTabContentState { interface IUserDefinedFunctionTabContentState {

View File

@@ -1,5 +1,4 @@
import { Resource, StoredProcedureDefinition, TriggerDefinition, UserDefinedFunctionDefinition } from "@azure/cosmos"; import { Resource, StoredProcedureDefinition, TriggerDefinition, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { useNotebook } from "Explorer/Notebook/useNotebook"; import { useNotebook } from "Explorer/Notebook/useNotebook";
import { DocumentsTabV2 } from "Explorer/Tabs/DocumentsTabV2/DocumentsTabV2"; import { DocumentsTabV2 } from "Explorer/Tabs/DocumentsTabV2/DocumentsTabV2";
import * as ko from "knockout"; import * as ko from "knockout";
@@ -24,6 +23,7 @@ import { logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import { SqlTriggerResource } from "../../Utils/arm/generatedClients/cosmos/types"; import { SqlTriggerResource } from "../../Utils/arm/generatedClients/cosmos/types";
import { useTabs } from "../../hooks/useTabs"; import { useTabs } from "../../hooks/useTabs";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import { CassandraAPIDataClient, CassandraTableKey, CassandraTableKeys } from "../Tables/TableDataClient"; import { CassandraAPIDataClient, CassandraTableKey, CassandraTableKeys } from "../Tables/TableDataClient";
import ConflictsTab from "../Tabs/ConflictsTab"; import ConflictsTab from "../Tabs/ConflictsTab";
import GraphTab from "../Tabs/GraphTab"; import GraphTab from "../Tabs/GraphTab";

View File

@@ -1,5 +1,4 @@
import { TreeNodeMenuItem } from "Explorer/Controls/TreeComponent/TreeNodeComponent"; import { TreeNodeMenuItem } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { collectionWasOpened } from "Explorer/MostRecentActivity/MostRecentActivity"; import { collectionWasOpened } from "Explorer/MostRecentActivity/MostRecentActivity";
import { shouldShowScriptNodes } from "Explorer/Tree/treeNodeUtil"; import { shouldShowScriptNodes } from "Explorer/Tree/treeNodeUtil";
import { getItemName } from "Utils/APITypeUtils"; import { getItemName } from "Utils/APITypeUtils";
@@ -29,6 +28,7 @@ import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFacto
import { useDialog } from "../Controls/Dialog"; import { useDialog } from "../Controls/Dialog";
import { LegacyTreeComponent, LegacyTreeNode } from "../Controls/TreeComponent/LegacyTreeComponent"; import { LegacyTreeComponent, LegacyTreeNode } from "../Controls/TreeComponent/LegacyTreeComponent";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem"; import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem";
import { NotebookUtil } from "../Notebook/NotebookUtil"; import { NotebookUtil } from "../Notebook/NotebookUtil";
import { useNotebook } from "../Notebook/useNotebook"; import { useNotebook } from "../Notebook/useNotebook";

View File

@@ -1,4 +1,4 @@
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar"; import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
import TabsBase from "Explorer/Tabs/TabsBase"; import TabsBase from "Explorer/Tabs/TabsBase";
import { useSelectedNode } from "Explorer/useSelectedNode"; import { useSelectedNode } from "Explorer/useSelectedNode";
import { useTabs } from "hooks/useTabs"; import { useTabs } from "hooks/useTabs";

View File

@@ -2,7 +2,7 @@ import { CapabilityNames } from "Common/Constants";
import { Platform, updateConfigContext } from "ConfigContext"; import { Platform, updateConfigContext } from "ConfigContext";
import { TreeNode } from "Explorer/Controls/TreeComponent/TreeNodeComponent"; import { TreeNode } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
import Explorer from "Explorer/Explorer"; import Explorer from "Explorer/Explorer";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar"; import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
import { useNotebook } from "Explorer/Notebook/useNotebook"; import { useNotebook } from "Explorer/Notebook/useNotebook";
import { DeleteDatabaseConfirmationPanel } from "Explorer/Panes/DeleteDatabaseConfirmationPanel"; import { DeleteDatabaseConfirmationPanel } from "Explorer/Panes/DeleteDatabaseConfirmationPanel";
import TabsBase from "Explorer/Tabs/TabsBase"; import TabsBase from "Explorer/Tabs/TabsBase";

View File

@@ -1,6 +1,5 @@
import { DatabaseRegular, DocumentMultipleRegular, SettingsRegular } from "@fluentui/react-icons"; import { DatabaseRegular, DocumentMultipleRegular, SettingsRegular } from "@fluentui/react-icons";
import { TreeNode } from "Explorer/Controls/TreeComponent/TreeNodeComponent"; import { TreeNode } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { collectionWasOpened } from "Explorer/MostRecentActivity/MostRecentActivity"; import { collectionWasOpened } from "Explorer/MostRecentActivity/MostRecentActivity";
import TabsBase from "Explorer/Tabs/TabsBase"; import TabsBase from "Explorer/Tabs/TabsBase";
import StoredProcedure from "Explorer/Tree/StoredProcedure"; import StoredProcedure from "Explorer/Tree/StoredProcedure";
@@ -18,6 +17,7 @@ import * as ViewModels from "../../Contracts/ViewModels";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFactory"; import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFactory";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import { useNotebook } from "../Notebook/useNotebook"; import { useNotebook } from "../Notebook/useNotebook";
import { useSelectedNode } from "../useSelectedNode"; import { useSelectedNode } from "../useSelectedNode";

View File

@@ -83,8 +83,8 @@ const bindings: Record<KeyboardAction, string[]> = {
[KeyboardAction.NEW_ITEM]: ["Alt+N I"], [KeyboardAction.NEW_ITEM]: ["Alt+N I"],
[KeyboardAction.DELETE_ITEM]: ["Alt+D"], [KeyboardAction.DELETE_ITEM]: ["Alt+D"],
[KeyboardAction.TOGGLE_COPILOT]: ["$mod+P"], [KeyboardAction.TOGGLE_COPILOT]: ["$mod+P"],
[KeyboardAction.SELECT_LEFT_TAB]: ["$mod+Alt+[", "$mod+Shift+F6"], [KeyboardAction.SELECT_LEFT_TAB]: ["$mod+Alt+BracketLeft", "$mod+Shift+F6"],
[KeyboardAction.SELECT_RIGHT_TAB]: ["$mod+Alt+]", "$mod+F6"], [KeyboardAction.SELECT_RIGHT_TAB]: ["$mod+Alt+BracketRight", "$mod+F6"],
[KeyboardAction.CLOSE_TAB]: ["$mod+Alt+W"], [KeyboardAction.CLOSE_TAB]: ["$mod+Alt+W"],
[KeyboardAction.SEARCH]: ["$mod+Shift+F"], [KeyboardAction.SEARCH]: ["$mod+Shift+F"],
[KeyboardAction.CLEAR_SEARCH]: ["$mod+Shift+C"], [KeyboardAction.CLEAR_SEARCH]: ["$mod+Shift+C"],

View File

@@ -20,11 +20,9 @@ import "../externals/jquery.typeahead.min.css";
import "../externals/jquery.typeahead.min.js"; import "../externals/jquery.typeahead.min.js";
// Image Dependencies // Image Dependencies
import { Platform } from "ConfigContext"; import { Platform } from "ConfigContext";
import { CommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
import { QueryCopilotCarousel } from "Explorer/QueryCopilot/CopilotCarousel"; import { QueryCopilotCarousel } from "Explorer/QueryCopilot/CopilotCarousel";
import { SidebarContainer } from "Explorer/Sidebar"; import { SidebarContainer } from "Explorer/Sidebar";
import { KeyboardShortcutRoot } from "KeyboardShortcuts"; import { KeyboardShortcutRoot } from "KeyboardShortcuts";
import { userContext } from "UserContext";
import "allotment/dist/style.css"; import "allotment/dist/style.css";
import "../images/CosmosDB_rgb_ui_lighttheme.ico"; import "../images/CosmosDB_rgb_ui_lighttheme.ico";
import hdeConnectImage from "../images/HdeConnectCosmosDB.svg"; import hdeConnectImage from "../images/HdeConnectCosmosDB.svg";
@@ -50,6 +48,7 @@ import "./Explorer/Controls/Notebook/NotebookTerminalComponent.less";
import "./Explorer/Controls/TreeComponent/treeComponent.less"; import "./Explorer/Controls/TreeComponent/treeComponent.less";
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less"; import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
import "./Explorer/Menus/CommandBar/CommandBarComponent.less"; import "./Explorer/Menus/CommandBar/CommandBarComponent.less";
import { CommandBar } from "./Explorer/Menus/CommandBar/CommandBarComponentAdapter";
import "./Explorer/Menus/CommandBar/ConnectionStatusComponent.less"; import "./Explorer/Menus/CommandBar/ConnectionStatusComponent.less";
import "./Explorer/Menus/CommandBar/MemoryTrackerComponent.less"; import "./Explorer/Menus/CommandBar/MemoryTrackerComponent.less";
import "./Explorer/Menus/NotificationConsole/NotificationConsole.less"; import "./Explorer/Menus/NotificationConsole/NotificationConsole.less";
@@ -87,7 +86,7 @@ const App: React.FunctionComponent = () => {
<div id="divExplorer" className="flexContainer hideOverflows"> <div id="divExplorer" className="flexContainer hideOverflows">
<div id="freeTierTeachingBubble"> </div> <div id="freeTierTeachingBubble"> </div>
{/* Main Command Bar - Start */} {/* Main Command Bar - Start */}
{!userContext.features.commandBarV2 && <CommandBar container={explorer} />} <CommandBar container={explorer} />
{/* Collections Tree and Tabs - Begin */} {/* Collections Tree and Tabs - Begin */}
<SidebarContainer explorer={explorer} /> <SidebarContainer explorer={explorer} />
{/* Collections Tree and Tabs - End */} {/* Collections Tree and Tabs - End */}

View File

@@ -1,18 +1,22 @@
import * as React from "react"; import * as React from "react";
import { CommandButtonComponent } from "../../../Explorer/Controls/CommandButton/CommandButtonComponent";
import FeedbackIcon from "../../../../images/Feedback.svg"; import FeedbackIcon from "../../../../images/Feedback.svg";
const onClick = () => {
window.open("https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20Hosted%20Data%20Explorer%20Feedback");
};
export const FeedbackCommandButton: React.FunctionComponent = () => { export const FeedbackCommandButton: React.FunctionComponent = () => {
return ( return (
<div className="feedbackConnectSettingIcons"> <div className="feedbackConnectSettingIcons">
<div className="commandButtonReact"> <CommandButtonComponent
<a href="#" title="Send feedback" aria-haspopup="dialog" onClick={onClick}> id="commandbutton-feedback"
<img src={FeedbackIcon} alt="Send feedback" /> iconSrc={FeedbackIcon}
</a> iconAlt="feeback button"
</div> onCommandClick={() =>
window.open("https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20Hosted%20Data%20Explorer%20Feedback")
}
ariaLabel="feeback button"
tooltipText="Send feedback"
hasPopup={true}
disabled={false}
/>
</div> </div>
); );
}; };

View File

@@ -38,7 +38,6 @@ export type Features = {
readonly copilotChatFixedMonacoEditorHeight: boolean; readonly copilotChatFixedMonacoEditorHeight: boolean;
readonly enablePriorityBasedExecution: boolean; readonly enablePriorityBasedExecution: boolean;
readonly disableConnectionStringLogin: boolean; readonly disableConnectionStringLogin: boolean;
readonly commandBarV2: boolean;
// can be set via both flight and feature flag // can be set via both flight and feature flag
autoscaleDefault: boolean; autoscaleDefault: boolean;
@@ -109,7 +108,6 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
copilotChatFixedMonacoEditorHeight: "true" === get("copilotchatfixedmonacoeditorheight"), copilotChatFixedMonacoEditorHeight: "true" === get("copilotchatfixedmonacoeditorheight"),
enablePriorityBasedExecution: "true" === get("enableprioritybasedexecution"), enablePriorityBasedExecution: "true" === get("enableprioritybasedexecution"),
disableConnectionStringLogin: "true" === get("disableconnectionstringlogin"), disableConnectionStringLogin: "true" === get("disableconnectionstringlogin"),
commandBarV2: "true" === get("commandbarv2"),
}; };
} }

View File

@@ -74,11 +74,13 @@ export interface UserContext {
readonly authType?: AuthType; readonly authType?: AuthType;
readonly masterKey?: string; readonly masterKey?: string;
readonly subscriptionId?: string; readonly subscriptionId?: string;
readonly tenantId?: string;
readonly resourceGroup?: string; readonly resourceGroup?: string;
readonly databaseAccount?: DatabaseAccount; readonly databaseAccount?: DatabaseAccount;
readonly endpoint?: string; readonly endpoint?: string;
readonly aadToken?: string; readonly aadToken?: string;
readonly accessToken?: string; readonly accessToken?: string;
readonly armToken?: string;
readonly authorizationToken?: string; readonly authorizationToken?: string;
readonly resourceToken?: string; readonly resourceToken?: string;
readonly subscriptionType?: SubscriptionType; readonly subscriptionType?: SubscriptionType;

View File

@@ -1,11 +1,12 @@
import * as msal from "@azure/msal-browser"; import * as msal from "@azure/msal-browser";
import { Action } from "Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
import * as Constants from "../Common/Constants"; import * as Constants from "../Common/Constants";
import * as Logger from "../Common/Logger"; import * as Logger from "../Common/Logger";
import { configContext } from "../ConfigContext"; import { configContext } from "../ConfigContext";
import { DatabaseAccount } from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { traceFailure } from "../Shared/Telemetry/TelemetryProcessor"; import { trace, traceFailure } from "../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
export function getAuthorizationHeader(): ViewModels.AuthorizationTokenHeaderMetadata { export function getAuthorizationHeader(): ViewModels.AuthorizationTokenHeaderMetadata {
@@ -64,7 +65,83 @@ export async function getMsalInstance() {
return msalInstance; return msalInstance;
} }
export async function acquireTokenWithMsal(msalInstance: msal.IPublicClientApplication, request: msal.SilentRequest) { export async function acquireMsalTokenForAccount(
account: DatabaseAccount,
silent: boolean = false,
user_hint?: string,
) {
if (userContext.databaseAccount.properties?.documentEndpoint === undefined) {
throw new Error("Database account has no document endpoint defined");
}
const hrefEndpoint = new URL(userContext.databaseAccount.properties.documentEndpoint).href.replace(
/\/+$/,
"/.default",
);
const msalInstance = await getMsalInstance();
const knownAccounts = msalInstance.getAllAccounts();
// If user_hint is provided, we will try to use it to find the account.
// If no account is found, we will use the current active account or first account in the list.
const msalAccount =
knownAccounts?.filter((account) => account.username === user_hint)[0] ??
msalInstance.getActiveAccount() ??
knownAccounts?.[0];
if (!msalAccount) {
// If no account was found, we need to sign in.
// This will eventually throw InteractionRequiredAuthError if silent is true, we won't handle it here.
const loginRequest = {
scopes: [hrefEndpoint],
loginHint: user_hint,
};
try {
if (silent) {
// We can try to use SSO between different apps to avoid showing a popup.
// With a hint provided, this should work in most cases.
// See https://learn.microsoft.com/en-us/entra/identity-platform/msal-js-sso#sso-between-different-apps
try {
const loginResponse = await msalInstance.ssoSilent(loginRequest);
return loginResponse.accessToken;
} catch (silentError) {
trace(Action.SignInAad, ActionModifiers.Mark, {
request: JSON.stringify(loginRequest),
acquireTokenType: silent ? "silent" : "interactive",
errorMessage: JSON.stringify(silentError),
});
}
}
// If silent acquisition failed, we need to show a popup.
// Passing prompt: "none" will still show a popup but not perform a full sign-in.
// This will only work if the user has already signed in and the session is still valid.
// See https://learn.microsoft.com/en-us/entra/identity-platform/msal-js-prompt-behavior#interactive-requests-with-promptnone
// The hint will be used to pre-fill the username field in the popup if silent is false.
const loginResponse = await msalInstance.loginPopup({ prompt: silent ? "none" : "login", ...loginRequest });
return loginResponse.accessToken;
} catch (error) {
traceFailure(Action.SignInAad, {
request: JSON.stringify(loginRequest),
acquireTokenType: silent ? "silent" : "interactive",
errorMessage: JSON.stringify(error),
});
throw error;
}
} else {
msalInstance.setActiveAccount(msalAccount);
}
const tokenRequest = {
account: msalAccount || null,
forceRefresh: true,
scopes: [hrefEndpoint],
authority: `${configContext.AAD_ENDPOINT}${msalAccount.tenantId}`,
};
return acquireTokenWithMsal(msalInstance, tokenRequest, silent);
}
export async function acquireTokenWithMsal(
msalInstance: msal.IPublicClientApplication,
request: msal.SilentRequest,
silent: boolean = false,
) {
const tokenRequest = { const tokenRequest = {
account: msalInstance.getActiveAccount() || null, account: msalInstance.getActiveAccount() || null,
...request, ...request,
@@ -74,7 +151,7 @@ export async function acquireTokenWithMsal(msalInstance: msal.IPublicClientAppli
// attempt silent acquisition first // attempt silent acquisition first
return (await msalInstance.acquireTokenSilent(tokenRequest)).accessToken; return (await msalInstance.acquireTokenSilent(tokenRequest)).accessToken;
} catch (silentError) { } catch (silentError) {
if (silentError instanceof msal.InteractionRequiredAuthError) { if (silentError instanceof msal.InteractionRequiredAuthError && silent === false) {
try { try {
// The error indicates that we need to acquire the token interactively. // 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 // This will display a pop-up to re-establish authorization. If user does not

View File

@@ -92,7 +92,7 @@ export const MongoProxyOutboundIPs: { [key: string]: string[] } = {
[MongoProxyEndpoints.Mooncake]: ["52.131.240.99", "143.64.61.130"], [MongoProxyEndpoints.Mooncake]: ["52.131.240.99", "143.64.61.130"],
}; };
export const allowedMongoProxyEndpoints: ReadonlyArray<string> = [ export const defaultAllowedMongoProxyEndpoints: ReadonlyArray<string> = [
MongoProxyEndpoints.Local, MongoProxyEndpoints.Local,
MongoProxyEndpoints.Mpac, MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod, MongoProxyEndpoints.Prod,
@@ -108,7 +108,7 @@ export const allowedMongoProxyEndpoints_ToBeDeprecated: ReadonlyArray<string> =
"https://localhost:12901", "https://localhost:12901",
]; ];
export const allowedCassandraProxyEndpoints: ReadonlyArray<string> = [ export const defaultAllowedCassandraProxyEndpoints: ReadonlyArray<string> = [
CassandraProxyEndpoints.Development, CassandraProxyEndpoints.Development,
CassandraProxyEndpoints.Mpac, CassandraProxyEndpoints.Mpac,
CassandraProxyEndpoints.Prod, CassandraProxyEndpoints.Prod,

View File

@@ -13,9 +13,9 @@ describe("isInvalidParentFrameOrigin", () => {
${"https://subdomain.portal.azure.com"} | ${false} ${"https://subdomain.portal.azure.com"} | ${false}
${"https://subdomain.portal.azure.us"} | ${false} ${"https://subdomain.portal.azure.us"} | ${false}
${"https://subdomain.portal.azure.cn"} | ${false} ${"https://subdomain.portal.azure.cn"} | ${false}
${"https://main.documentdb.ext.azure.com"} | ${false} ${"https://cdb-ms-prod-pbe.cosmos.azure.com"} | ${false}
${"https://main.documentdb.ext.azure.us"} | ${false} ${"https://cdb-ff-prod-pbe.cosmos.azure.us"} | ${false}
${"https://main.documentdb.ext.azure.cn"} | ${false} ${"https://cdb-mc-prod-pbe.cosmos.azure.cn"} | ${false}
${"https://cosmos-db-dataexplorer-germanycentral.azurewebsites.de"} | ${false} ${"https://cosmos-db-dataexplorer-germanycentral.azurewebsites.de"} | ${false}
${"https://main.documentdb.ext.microsoftazure.de"} | ${false} ${"https://main.documentdb.ext.microsoftazure.de"} | ${false}
${"https://random.domain"} | ${true} ${"https://random.domain"} | ${true}

View File

@@ -2,12 +2,11 @@ import { MongoProxyEndpoints, PortalBackendEndpoints } from "Common/Constants";
import { resetConfigContext, updateConfigContext } from "ConfigContext"; import { resetConfigContext, updateConfigContext } from "ConfigContext";
import { DatabaseAccount, IpRule } from "Contracts/DataModels"; import { DatabaseAccount, IpRule } from "Contracts/DataModels";
import { updateUserContext } from "UserContext"; import { updateUserContext } from "UserContext";
import { MongoProxyOutboundIPs, PortalBackendIPs, PortalBackendOutboundIPs } from "Utils/EndpointUtils"; import { MongoProxyOutboundIPs, PortalBackendOutboundIPs } from "Utils/EndpointUtils";
import { getNetworkSettingsWarningMessage } from "./NetworkUtility"; import { getNetworkSettingsWarningMessage } from "./NetworkUtility";
describe("NetworkUtility tests", () => { describe("NetworkUtility tests", () => {
describe("getNetworkSettingsWarningMessage", () => { describe("getNetworkSettingsWarningMessage", () => {
const legacyBackendEndpoint: string = "https://main.documentdb.ext.azure.com";
const publicAccessMessagePart = "Please enable public access to proceed"; const publicAccessMessagePart = "Please enable public access to proceed";
const accessMessagePart = "Please allow access from Azure Portal to proceed"; const accessMessagePart = "Please allow access from Azure Portal to proceed";
let warningMessageResult: string; let warningMessageResult: string;
@@ -48,25 +47,23 @@ describe("NetworkUtility tests", () => {
}); });
it(`should return no message when the appropriate ip rules are added to mongo/cassandra account per endpoint`, async () => { it(`should return no message when the appropriate ip rules are added to mongo/cassandra account per endpoint`, async () => {
const portalBackendOutboundIPsWithLegacyIPs: string[] = [ const portalBackendOutboundIPs: string[] = [
...PortalBackendOutboundIPs[PortalBackendEndpoints.Mpac], ...PortalBackendOutboundIPs[PortalBackendEndpoints.Mpac],
...PortalBackendOutboundIPs[PortalBackendEndpoints.Prod], ...PortalBackendOutboundIPs[PortalBackendEndpoints.Prod],
...MongoProxyOutboundIPs[MongoProxyEndpoints.Mpac], ...MongoProxyOutboundIPs[MongoProxyEndpoints.Mpac],
...MongoProxyOutboundIPs[MongoProxyEndpoints.Prod], ...MongoProxyOutboundIPs[MongoProxyEndpoints.Prod],
...PortalBackendIPs["https://main.documentdb.ext.azure.com"],
]; ];
updateUserContext({ updateUserContext({
databaseAccount: { databaseAccount: {
kind: "MongoDB", kind: "MongoDB",
properties: { properties: {
ipRules: portalBackendOutboundIPsWithLegacyIPs.map((ip: string) => ({ ipAddressOrRange: ip }) as IpRule), ipRules: portalBackendOutboundIPs.map((ip: string) => ({ ipAddressOrRange: ip }) as IpRule),
publicNetworkAccess: "Enabled", publicNetworkAccess: "Enabled",
}, },
} as DatabaseAccount, } as DatabaseAccount,
}); });
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: legacyBackendEndpoint,
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Mpac, PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Mpac,
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Mpac, MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Mpac,
}); });
@@ -90,7 +87,6 @@ describe("NetworkUtility tests", () => {
}); });
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: legacyBackendEndpoint,
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Mpac, PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Mpac,
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Mpac, MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Mpac,
}); });

View File

@@ -2,12 +2,7 @@ import { CassandraProxyEndpoints, MongoProxyEndpoints, PortalBackendEndpoints }
import { configContext } from "ConfigContext"; import { configContext } from "ConfigContext";
import { checkFirewallRules } from "Explorer/Tabs/Shared/CheckFirewallRules"; import { checkFirewallRules } from "Explorer/Tabs/Shared/CheckFirewallRules";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
import { import { CassandraProxyOutboundIPs, MongoProxyOutboundIPs, PortalBackendOutboundIPs } from "Utils/EndpointUtils";
CassandraProxyOutboundIPs,
MongoProxyOutboundIPs,
PortalBackendIPs,
PortalBackendOutboundIPs,
} from "Utils/EndpointUtils";
export const getNetworkSettingsWarningMessage = async ( export const getNetworkSettingsWarningMessage = async (
setStateFunc: (warningMessage: string) => void, setStateFunc: (warningMessage: string) => void,
@@ -61,7 +56,7 @@ export const getNetworkSettingsWarningMessage = async (
...PortalBackendOutboundIPs[PortalBackendEndpoints.Prod], ...PortalBackendOutboundIPs[PortalBackendEndpoints.Prod],
] ]
: PortalBackendOutboundIPs[configContext.PORTAL_BACKEND_ENDPOINT]; : PortalBackendOutboundIPs[configContext.PORTAL_BACKEND_ENDPOINT];
let portalIPs: string[] = [...portalBackendOutboundIPs, ...PortalBackendIPs[configContext.BACKEND_ENDPOINT]]; let portalIPs: string[] = [...portalBackendOutboundIPs];
if (userContext.apiType === "Mongo") { if (userContext.apiType === "Mongo") {
const isProdOrMpacMongoProxyEndpoint: boolean = [MongoProxyEndpoints.Mpac, MongoProxyEndpoints.Prod].includes( const isProdOrMpacMongoProxyEndpoint: boolean = [MongoProxyEndpoints.Mpac, MongoProxyEndpoints.Prod].includes(

View File

@@ -3,6 +3,7 @@ import { useBoolean } from "@fluentui/react-hooks";
import * as React from "react"; import * as React from "react";
import { configContext } from "../ConfigContext"; import { configContext } from "../ConfigContext";
import { acquireTokenWithMsal, getMsalInstance } from "../Utils/AuthorizationUtils"; import { acquireTokenWithMsal, getMsalInstance } from "../Utils/AuthorizationUtils";
import { updateUserContext } from "UserContext";
const msalInstance = await getMsalInstance(); const msalInstance = await getMsalInstance();
@@ -79,7 +80,7 @@ export function useAADAuth(): ReturnType {
authority: `${configContext.AAD_ENDPOINT}${tenantId}`, authority: `${configContext.AAD_ENDPOINT}${tenantId}`,
scopes: [`${configContext.ARM_ENDPOINT}/.default`], scopes: [`${configContext.ARM_ENDPOINT}/.default`],
}); });
updateUserContext({ armToken: armToken});
setArmToken(armToken); setArmToken(armToken);
setAuthFailure(null); setAuthFailure(null);
} catch (error) { } catch (error) {

View File

@@ -1,8 +1,8 @@
import { HttpHeaders } from "Common/Constants"; import { HttpHeaders } from "Common/Constants";
import { QueryRequestOptions, QueryResponse } from "Contracts/AzureResourceGraph";
import useSWR from "swr"; import useSWR from "swr";
import { configContext } from "../ConfigContext"; import { acquireTokenWithMsal, getMsalInstance } from "Utils/AuthorizationUtils";
import { DatabaseAccount } from "../Contracts/DataModels"; import React from "react";
import { updateUserContext, userContext } from "UserContext";
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
interface AccountListResult { interface AccountListResult {
@@ -34,11 +34,10 @@ export async function fetchDatabaseAccounts(subscriptionId: string, accessToken:
} }
export async function fetchDatabaseAccountsFromGraph( export async function fetchDatabaseAccountsFromGraph(
subscriptionId: string, subscriptionId: string
accessToken: string,
): Promise<DatabaseAccount[]> { ): Promise<DatabaseAccount[]> {
const headers = new Headers(); const headers = new Headers();
const bearer = `Bearer ${accessToken}`; const bearer = `Bearer ${userContext.armToken}`;
headers.append("Authorization", bearer); headers.append("Authorization", bearer);
headers.append(HttpHeaders.contentType, "application/json"); headers.append(HttpHeaders.contentType, "application/json");
@@ -46,8 +45,9 @@ export async function fetchDatabaseAccountsFromGraph(
const apiVersion = "2021-03-01"; const apiVersion = "2021-03-01";
const managementResourceGraphAPIURL = `${configContext.ARM_ENDPOINT}providers/Microsoft.ResourceGraph/resources?api-version=${apiVersion}`; const managementResourceGraphAPIURL = `${configContext.ARM_ENDPOINT}providers/Microsoft.ResourceGraph/resources?api-version=${apiVersion}`;
const databaseAccounts: DatabaseAccount[] = []; let databaseAccounts: DatabaseAccount[] = [];
let skipToken: string; let skipToken: string;
console.log("Old ARM Token", userContext.armToken);
do { do {
const body = { const body = {
query: databaseAccountsQuery, query: databaseAccountsQuery,
@@ -75,20 +75,165 @@ export async function fetchDatabaseAccountsFromGraph(
throw new Error(await response.text()); throw new Error(await response.text());
} }
const queryResponse: QueryResponse = (await response.json()) as QueryResponse; const queryResponse: QueryResponse = (await response.json()) as QueryResponse;
skipToken = queryResponse.$skipToken; skipToken = queryResponse.$skipToken;
queryResponse.data?.map((databaseAccount: any) => { queryResponse.data?.map((databaseAccount: any) => {
databaseAccounts.push(databaseAccount as DatabaseAccount); databaseAccounts.push(databaseAccount as DatabaseAccount);
}); });
} while (skipToken);
// else {
// try{
// console.log("Token expired");
// databaseAccounts = await acquireNewTokenAndRetry(body);
// }
// catch (error) {
// throw new Error(error);
// }
//}
} while (skipToken);
return databaseAccounts.sort((a, b) => a.name.localeCompare(b.name)); return databaseAccounts.sort((a, b) => a.name.localeCompare(b.name));
} }
export function useDatabaseAccounts(subscriptionId: string, armToken: string): DatabaseAccount[] | undefined { export function useDatabaseAccounts(subscriptionId: string): DatabaseAccount[] | undefined {
const { data } = useSWR( const { data } = useSWR(
() => (armToken && subscriptionId ? ["databaseAccounts", subscriptionId, armToken] : undefined), () => ( subscriptionId ? ["databaseAccounts", subscriptionId] : undefined),
(_, subscriptionId, armToken) => fetchDatabaseAccountsFromGraph(subscriptionId, armToken), (_, subscriptionId) => runCommand(fetchDatabaseAccountsFromGraph, subscriptionId),
); );
return data; return data;
} }
// Define the types for your responses
interface DatabaseAccount {
name: string;
id: string;
// Add other relevant fields as per your use case
}
interface Subscription {
displayName: string;
subscriptionId: string;
state: string;
}
interface QueryRequestOptions {
$top?: number;
$skipToken?: string;
$allowPartialScopes?: boolean;
}
// Define the configuration context and headers if not already defined
const configContext = {
ARM_ENDPOINT: 'https://management.azure.com/',
AAD_ENDPOINT: 'https://login.microsoftonline.com/'
};
interface QueryResponse {
data?: any[];
$skipToken?: string;
}
export async function runCommand<T>(
fn: (...args: any[]) => Promise<T>,
...args: any[]
): Promise<T> {
try {
// Attempt to execute the function passed as an argument
const result = await fn(...args);
console.log('Successfully executed function:', result);
return result;
} catch (error) {
// Handle any error that is thrown during the execution of the function
//(error.code === "ExpiredAuthenticationToken")
if(error) {
console.log('Creating new token');
const msalInstance = await getMsalInstance();
const cachedAccount = msalInstance.getAllAccounts()?.[0];
const cachedTenantId = localStorage.getItem("cachedTenantId");
msalInstance.setActiveAccount(cachedAccount);
const newAccessToken = await acquireTokenWithMsal(msalInstance, {
authority: `${configContext.AAD_ENDPOINT}${cachedTenantId}`,
scopes: [`${configContext.ARM_ENDPOINT}/.default`],
});
console.log("Latest ARM Token", userContext.armToken);
updateUserContext({armToken: newAccessToken});
const result = await fn(...args);
return result;
}
else {
console.error('An error occurred:', error.message);
throw new error;
}
}
}
// Running the functions using runCommand
const accessToken = 'your-access-token';
const subscriptionId = 'your-subscription-id';
//runCommand(fetchDatabaseAccountsFromGraph, subscriptionId, accessToken);
//runCommand(fetchSubscriptionsFromGraph, accessToken);
async function acquireNewTokenAndRetry(body: any) : Promise<DatabaseAccount[]> {
try {
const msalInstance = await getMsalInstance();
const cachedAccount = msalInstance.getAllAccounts()?.[0];
const cachedTenantId = localStorage.getItem("cachedTenantId");
// const [tenantId, setTenantId] = React.useState<string>(cachedTenantId);
msalInstance.setActiveAccount(cachedAccount);
const newAccessToken = await acquireTokenWithMsal(msalInstance, {
authority: `${configContext.AAD_ENDPOINT}${cachedTenantId}`,
scopes: [`${configContext.ARM_ENDPOINT}/.default`],
});
console.log("New ARM Token", newAccessToken);
const newBearer = `Bearer ${newAccessToken}`;
const newHeaders = new Headers();
newHeaders.append("Authorization", newBearer);
newHeaders.append(HttpHeaders.contentType, "application/json");
const apiVersion = "2021-03-01";
const managementResourceGraphAPIURL = `${configContext.ARM_ENDPOINT}providers/Microsoft.ResourceGraph/resources?api-version=${apiVersion}`;
const databaseAccounts: DatabaseAccount[] = [];
let skipToken: string;
// Retry the request with the new token
const response = await fetch(managementResourceGraphAPIURL, {
method: "POST",
headers: newHeaders,
body: JSON.stringify(body),
});
if (response.ok) {
// Handle successful response with new token
const queryResponse: QueryResponse = await response.json();
skipToken = queryResponse.$skipToken;
queryResponse.data?.forEach((databaseAccount: any) => {
databaseAccounts.push(databaseAccount as DatabaseAccount);
});
return databaseAccounts;
} else {
throw new Error(`Failed to fetch data after acquiring new token. Status: ${response.status}, ${await response.text()}`);
}
} catch (error) {
console.error("Error acquiring new token and retrying:", error);
throw error;
}
}

View File

@@ -5,6 +5,7 @@ import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
import { FABRIC_RPC_VERSION, FabricMessageV2 } from "Contracts/FabricMessagesContract"; import { FABRIC_RPC_VERSION, FabricMessageV2 } from "Contracts/FabricMessagesContract";
import Explorer from "Explorer/Explorer"; import Explorer from "Explorer/Explorer";
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane"; import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
import { useSelectedNode } from "Explorer/useSelectedNode"; import { useSelectedNode } from "Explorer/useSelectedNode";
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil"; import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
@@ -18,6 +19,7 @@ import { AuthType } from "../AuthType";
import { AccountKind, Flights } from "../Common/Constants"; import { AccountKind, Flights } from "../Common/Constants";
import { normalizeArmEndpoint } from "../Common/EnvironmentUtility"; import { normalizeArmEndpoint } from "../Common/EnvironmentUtility";
import * as Logger from "../Common/Logger"; import * as Logger from "../Common/Logger";
import * as Logger from "../Common/Logger";
import { handleCachedDataMessage, sendMessage, sendReadyMessage } from "../Common/MessageHandler"; import { handleCachedDataMessage, sendMessage, sendReadyMessage } from "../Common/MessageHandler";
import { Platform, configContext, updateConfigContext } from "../ConfigContext"; import { Platform, configContext, updateConfigContext } from "../ConfigContext";
import { ActionType, DataExplorerAction, TabKind } from "../Contracts/ActionContracts"; import { ActionType, DataExplorerAction, TabKind } from "../Contracts/ActionContracts";
@@ -41,7 +43,12 @@ import {
import { extractFeatures } from "../Platform/Hosted/extractFeatures"; import { extractFeatures } from "../Platform/Hosted/extractFeatures";
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility"; import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
import { Node, PortalEnv, updateUserContext, userContext } from "../UserContext"; import { Node, PortalEnv, updateUserContext, userContext } from "../UserContext";
import { acquireTokenWithMsal, getAuthorizationHeader, getMsalInstance } from "../Utils/AuthorizationUtils"; import {
acquireMsalTokenForAccount,
acquireTokenWithMsal,
getAuthorizationHeader,
getMsalInstance,
} from "../Utils/AuthorizationUtils";
import { isInvalidParentFrameOrigin, shouldProcessMessage } from "../Utils/MessageValidation"; import { isInvalidParentFrameOrigin, shouldProcessMessage } from "../Utils/MessageValidation";
import { getReadOnlyKeys, listKeys } from "../Utils/arm/generatedClients/cosmos/databaseAccounts"; import { getReadOnlyKeys, listKeys } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
import { applyExplorerBindings } from "../applyExplorerBindings"; import { applyExplorerBindings } from "../applyExplorerBindings";
@@ -459,6 +466,7 @@ export async function fetchAndUpdateKeys(subscriptionId: string, resourceGroup:
Logger.logInfo(`Fetching keys for ${userContext.apiType} account ${account}`, "Explorer/fetchAndUpdateKeys"); Logger.logInfo(`Fetching keys for ${userContext.apiType} account ${account}`, "Explorer/fetchAndUpdateKeys");
let keys; let keys;
try { try {
keys = await listKeys(subscriptionId, resourceGroup, account);
keys = await listKeys(subscriptionId, resourceGroup, account); keys = await listKeys(subscriptionId, resourceGroup, account);
Logger.logInfo(`Keys fetched for ${userContext.apiType} account ${account}`, "Explorer/fetchAndUpdateKeys"); Logger.logInfo(`Keys fetched for ${userContext.apiType} account ${account}`, "Explorer/fetchAndUpdateKeys");
updateUserContext({ updateUserContext({
@@ -482,6 +490,23 @@ export async function fetchAndUpdateKeys(subscriptionId: string, resourceGroup:
); );
throw error; throw error;
} }
if (error.code === "AuthorizationFailed") {
keys = await getReadOnlyKeys(subscriptionId, resourceGroup, account);
Logger.logInfo(
`Read only Keys fetched for ${userContext.apiType} account ${account}`,
"Explorer/fetchAndUpdateKeys",
);
updateUserContext({
masterKey: keys.primaryReadonlyMasterKey,
});
} else {
logConsoleError(`Error occurred fetching keys for the account." ${error.message}`);
Logger.logError(
`Error during fetching keys or updating user context: ${error} for ${userContext.apiType} account ${account}`,
"Explorer/fetchAndUpdateKeys",
);
throw error;
}
} }
} }
@@ -575,6 +600,22 @@ async function configurePortal(): Promise<Explorer> {
"Explorer/configurePortal", "Explorer/configurePortal",
); );
await fetchAndUpdateKeys(subscriptionId, resourceGroup, account.name); await fetchAndUpdateKeys(subscriptionId, resourceGroup, account.name);
} else {
Logger.logInfo(
`Trying to silently acquire MSAL token for ${userContext.apiType} account ${account.name}`,
"Explorer/configurePortal",
);
try {
const aadToken = await acquireMsalTokenForAccount(userContext.databaseAccount, true);
updateUserContext({ aadToken: aadToken });
useDataPlaneRbac.setState({ aadTokenUpdated: true });
} catch (authError) {
Logger.logWarning(
`Failed to silently acquire authorization token from MSAL: ${authError} for ${userContext.apiType} account ${account}`,
"Explorer/configurePortal",
);
logConsoleError("Failed to silently acquire authorization token: " + authError);
}
} }
updateUserContext({ dataPlaneRbacEnabled }); updateUserContext({ dataPlaneRbacEnabled });
@@ -672,6 +713,7 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
databaseAccount, databaseAccount,
resourceGroup: inputs.resourceGroup, resourceGroup: inputs.resourceGroup,
subscriptionId: inputs.subscriptionId, subscriptionId: inputs.subscriptionId,
tenantId: inputs.tenantId,
subscriptionType: inputs.subscriptionType, subscriptionType: inputs.subscriptionType,
quotaId: inputs.quotaId, quotaId: inputs.quotaId,
portalEnv: inputs.serverId as PortalEnv, portalEnv: inputs.serverId as PortalEnv,

View File

@@ -7,12 +7,20 @@ export interface SidePanelState {
headerText?: string; headerText?: string;
openSidePanel: (headerText: string, panelContent: JSX.Element, panelWidth?: string, onClose?: () => void) => void; openSidePanel: (headerText: string, panelContent: JSX.Element, panelWidth?: string, onClose?: () => void) => void;
closeSidePanel: () => void; closeSidePanel: () => void;
getRef?: React.RefObject<HTMLElement>; // Optional ref for focusing the last element.
} }
export const useSidePanel: UseStore<SidePanelState> = create((set) => ({ export const useSidePanel: UseStore<SidePanelState> = create((set) => ({
isOpen: false, isOpen: false,
panelWidth: "440px", panelWidth: "440px",
openSidePanel: (headerText, panelContent, panelWidth = "440px") => openSidePanel: (headerText, panelContent, panelWidth = "440px") =>
set((state) => ({ ...state, headerText, panelContent, panelWidth, isOpen: true })), set((state) => ({ ...state, headerText, panelContent, panelWidth, isOpen: true })),
closeSidePanel: () => set((state) => ({ ...state, isOpen: false })), closeSidePanel: () => {
const lastFocusedElement = useSidePanel.getState().getRef;
set((state) => ({ ...state, isOpen: false }));
const timeoutId = setTimeout(() => {
lastFocusedElement?.current?.focus();
set({ getRef: undefined });
}, 300);
return () => clearTimeout(timeoutId);
},
})); }));

View File

@@ -3,6 +3,7 @@ import { QueryRequestOptions, QueryResponse } from "Contracts/AzureResourceGraph
import useSWR from "swr"; import useSWR from "swr";
import { configContext } from "../ConfigContext"; import { configContext } from "../ConfigContext";
import { Subscription } from "../Contracts/DataModels"; import { Subscription } from "../Contracts/DataModels";
import { acquireTokenWithMsal, getMsalInstance } from "Utils/AuthorizationUtils";
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
interface SubscriptionListResult { interface SubscriptionListResult {
@@ -92,3 +93,5 @@ export function useSubscriptions(armToken: string): Subscription[] | undefined {
); );
return data; return data;
} }

View File

@@ -14,7 +14,6 @@ test("Cassandra keyspace and table CRUD", async ({ page }) => {
async (panel, okButton) => { async (panel, okButton) => {
await panel.getByPlaceholder("Type a new keyspace id").fill(keyspaceId); await panel.getByPlaceholder("Type a new keyspace id").fill(keyspaceId);
await panel.getByPlaceholder("Enter table Id").fill(tableId); await panel.getByPlaceholder("Enter table Id").fill(tableId);
await panel.getByLabel("Table max RU/s").fill("1000");
await okButton.click(); await okButton.click();
}, },
{ closeTimeout: 5 * 60 * 1000 }, { closeTimeout: 5 * 60 * 1000 },

View File

@@ -40,15 +40,15 @@ export enum TestAccount {
} }
export const defaultAccounts: Record<TestAccount, string> = { export const defaultAccounts: Record<TestAccount, string> = {
[TestAccount.Tables]: "portal-tables-runner", [TestAccount.Tables]: "github-e2etests-tables",
[TestAccount.Cassandra]: "portal-cassandra-runner", [TestAccount.Cassandra]: "github-e2etests-cassandra",
[TestAccount.Gremlin]: "portal-gremlin-runner", [TestAccount.Gremlin]: "github-e2etests-gremlin",
[TestAccount.Mongo]: "portal-mongo-runner", [TestAccount.Mongo]: "github-e2etests-mongo",
[TestAccount.Mongo32]: "portal-mongo32-runner", [TestAccount.Mongo32]: "github-e2etests-mongo32",
[TestAccount.SQL]: "portal-sql-runner-west-us", [TestAccount.SQL]: "github-e2etests-sql",
}; };
export const resourceGroupName = process.env.DE_TEST_RESOURCE_GROUP ?? "runners"; export const resourceGroupName = process.env.DE_TEST_RESOURCE_GROUP ?? "de-e2e-tests";
export const subscriptionId = process.env.DE_TEST_SUBSCRIPTION_ID ?? "69e02f2d-f059-4409-9eac-97e8a276ae2c"; export const subscriptionId = process.env.DE_TEST_SUBSCRIPTION_ID ?? "69e02f2d-f059-4409-9eac-97e8a276ae2c";
function tryGetStandardName(accountType: TestAccount) { function tryGetStandardName(accountType: TestAccount) {

View File

@@ -16,7 +16,6 @@ test("Gremlin graph CRUD", async ({ page }) => {
await panel.getByPlaceholder("Type a new database id").fill(databaseId); await panel.getByPlaceholder("Type a new database id").fill(databaseId);
await panel.getByRole("textbox", { name: "Graph id, Example Graph1" }).fill(graphId); await panel.getByRole("textbox", { name: "Graph id, Example Graph1" }).fill(graphId);
await panel.getByRole("textbox", { name: "Partition key" }).fill("/pk"); await panel.getByRole("textbox", { name: "Partition key" }).fill("/pk");
await panel.getByLabel("Database max RU/s").fill("1000");
await okButton.click(); await okButton.click();
}, },
{ closeTimeout: 5 * 60 * 1000 }, { closeTimeout: 5 * 60 * 1000 },

View File

@@ -21,7 +21,6 @@ import { DataExplorer, TestAccount, generateUniqueName } from "../fx";
await panel.getByPlaceholder("Type a new database id").fill(databaseId); await panel.getByPlaceholder("Type a new database id").fill(databaseId);
await panel.getByRole("textbox", { name: "Collection id, Example Collection1" }).fill(collectionId); await panel.getByRole("textbox", { name: "Collection id, Example Collection1" }).fill(collectionId);
await panel.getByRole("textbox", { name: "Shard key" }).fill("pk"); await panel.getByRole("textbox", { name: "Shard key" }).fill("pk");
await panel.getByLabel("Database max RU/s").fill("1000");
await okButton.click(); await okButton.click();
}, },
{ closeTimeout: 5 * 60 * 1000 }, { closeTimeout: 5 * 60 * 1000 },

View File

@@ -15,7 +15,6 @@ test("SQL database and container CRUD", async ({ page }) => {
await panel.getByPlaceholder("Type a new database id").fill(databaseId); await panel.getByPlaceholder("Type a new database id").fill(databaseId);
await panel.getByRole("textbox", { name: "Container id, Example Container1" }).fill(containerId); await panel.getByRole("textbox", { name: "Container id, Example Container1" }).fill(containerId);
await panel.getByRole("textbox", { name: "Partition key" }).fill("/pk"); await panel.getByRole("textbox", { name: "Partition key" }).fill("/pk");
await panel.getByLabel("Database max RU/s").fill("1000");
await okButton.click(); await okButton.click();
}, },
{ closeTimeout: 5 * 60 * 1000 }, { closeTimeout: 5 * 60 * 1000 },

View File

@@ -3,7 +3,7 @@ const { CosmosDBManagementClient } = require("@azure/arm-cosmosdb");
const ms = require("ms"); const ms = require("ms");
const subscriptionId = process.env["AZURE_SUBSCRIPTION_ID"]; const subscriptionId = process.env["AZURE_SUBSCRIPTION_ID"];
const resourceGroupName = "runners"; const resourceGroupName = "de-e2e-tests";
const thirtyMinutesAgo = new Date(Date.now() - 1000 * 60 * 30).getTime(); const thirtyMinutesAgo = new Date(Date.now() - 1000 * 60 * 30).getTime();

View File

@@ -23,7 +23,7 @@ const gitSha = childProcess.execSync("git rev-parse HEAD").toString("utf8");
const AZURE_CLIENT_ID = "fd8753b0-0707-4e32-84e9-2532af865fb4"; const AZURE_CLIENT_ID = "fd8753b0-0707-4e32-84e9-2532af865fb4";
const AZURE_TENANT_ID = "72f988bf-86f1-41af-91ab-2d7cd011db47"; const AZURE_TENANT_ID = "72f988bf-86f1-41af-91ab-2d7cd011db47";
const SUBSCRIPTION_ID = "69e02f2d-f059-4409-9eac-97e8a276ae2c"; const SUBSCRIPTION_ID = "69e02f2d-f059-4409-9eac-97e8a276ae2c";
const RESOURCE_GROUP = "runners"; const RESOURCE_GROUP = "de-e2e-tests";
const AZURE_CLIENT_SECRET = process.env.AZURE_CLIENT_SECRET || process.env.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET; // TODO Remove. Exists for backwards compat with old .env files. Prefer AZURE_CLIENT_SECRET const AZURE_CLIENT_SECRET = process.env.AZURE_CLIENT_SECRET || process.env.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET; // TODO Remove. Exists for backwards compat with old .env files. Prefer AZURE_CLIENT_SECRET
if (!AZURE_CLIENT_SECRET) { if (!AZURE_CLIENT_SECRET) {
@@ -301,7 +301,7 @@ module.exports = function (_env = {}, argv = {}) {
}, },
proxy: { proxy: {
"/api": { "/api": {
target: "https://main.documentdb.ext.azure.com", target: "https://cdb-ms-mpac-pbe.cosmos.azure.com",
changeOrigin: true, changeOrigin: true,
logLevel: "debug", logLevel: "debug",
bypass: (req, res) => { bypass: (req, res) => {
@@ -312,7 +312,7 @@ module.exports = function (_env = {}, argv = {}) {
}, },
}, },
"/proxy": { "/proxy": {
target: "https://main.documentdb.ext.azure.com", target: "https://cdb-ms-mpac-pbe.cosmos.azure.com",
changeOrigin: true, changeOrigin: true,
secure: false, secure: false,
logLevel: "debug", logLevel: "debug",