Compare commits

..

16 Commits

Author SHA1 Message Date
Ashley Stanton-Nurse
2089d8ca9e a system of contributors? 2024-04-15 15:45:22 -07:00
Ashley Stanton-Nurse
e441b75325 initial mousetrap keyboard shortcuts 2024-04-15 15:02:10 -07:00
JustinKol
d35e2a325e Cassandra API create table error messages swallowed by the Portal and shown as "undefined". (#1790)
* changed error message variable

* changed other error messages

* Added check in case responseJSON is missing

* created error const
2024-04-15 15:47:58 -04:00
sunghyunkang1111
00a816c488 set the value in the editor for results (#1799) 2024-04-13 15:19:56 -05:00
jawelton74
953bef404b Set backend endpoints in testExplorer to use MPAC. (#1797) 2024-04-11 15:43:46 -07:00
Asier Isayas
dfcb771939 Activate Mongo Proxy and Cassandra Proxy in Prod (#1794)
* activate Mongo Proxy and Cassandra Proxy in Prod

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

* fix pr check tests

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-04-09 13:45:36 -07:00
jawelton74
6925fa8e4e Replace Entra app client secret auth with OpenID Connect in E2E tests. (#1792)
* Use Az login with OpenID connection to get test credentials.

* Set subscription id environment variable.

* Update testExplorer and cleanup job.

* Retrieve access token in test case and pass to testExplorer.

* Add debug tracing for tests.

* Set up other mongo test to use Az CLI creds.

* Revert subscription id retrieval.

* Add CLI credentials retrieval to rest of tests.

* Fix missing imports.

* Clean up redundant code.

* Remove commented import statement.
2024-04-09 10:55:08 -07:00
jawelton74
7f6338b68b Change copilot settings call to use new backend endpoint. (#1781)
* Change copilot settings call to use new backend endpoint.

* Refactor EndpointUtils function for new backend enablement.
2024-04-04 10:18:50 -07:00
Ashley Stanton-Nurse
db50f42832 [Task #3061771] Correct render order issues on undo (#1785)
* fix #3061771 by correcting render order issues on undo

* clarifying comment

* fix lints

* push an undo stop before executing edits

* tidy up some unnecessary comments
2024-04-04 09:17:09 -07:00
Ashley Stanton-Nurse
f533eeb0fc add support for react dev tools in the cosmos explorer (#1788) 2024-04-04 09:16:23 -07:00
sunghyunkang1111
3c5d899e47 add the new message to the bottom to avoid contract breaking (#1786) 2024-04-02 17:54:53 -05:00
Ashley Stanton-Nurse
b44778b00a fix #3061738 by unclobbering some Monaco styles we clobber (#1784) 2024-04-02 10:51:19 -07:00
sunghyunkang1111
1464745659 Add activate/close tab contracts and add to queryTab (#1783) 2024-04-02 11:49:33 -05:00
Asier Isayas
18cc2a4195 Activate Mongo and Cassandra Proxies in MPAC (#1776)
* Fix API endpoint for CassandraProxy query API

* activated mongo proxy

* added mpac

* Activate CassandraProxy API endpoints for MPAC

* Run npm format

* Set CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED when we detect new
Cassandra Proxy endpoints in IP rules.

* query documents API fix

* simplify ip check

---------

Co-authored-by: Senthamil Sindhu <sindhuba@microsoft.com>
Co-authored-by: Asier Isayas <aisayas@microsoft.com>
Co-authored-by: Jade Welton <jawelton@microsoft.com>
2024-04-02 09:34:58 -07:00
sindhuba
86f2bc171f Remove notebooks UI (#1779)
* Fix API endpoint for CassandraProxy query API

* Remove notebooks UI components

* Fix tests

* Address comments

* Fix unit tests

* Remove commented code
2024-04-01 08:44:42 -07:00
jawelton74
cabedf4a29 Enable new backend endpoint to be passed to Data Explorer via message. (#1782) 2024-04-01 07:54:04 -07:00
75 changed files with 549 additions and 848 deletions

View File

@@ -8,6 +8,9 @@ on:
pull_request:
branches:
- master
permissions:
id-token: write
contents: read
jobs:
codemetrics:
runs-on: ubuntu-latest
@@ -134,7 +137,7 @@ jobs:
runs-on: ubuntu-latest
env:
NODE_TLS_REJECT_UNAUTHORIZED: 0
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
strategy:
fail-fast: false
matrix:
@@ -145,11 +148,18 @@ jobs:
- ./test/mongo/container.spec.ts
- ./test/mongo/container32.spec.ts
- ./test/selfServe/selfServeExample.spec.ts
# - ./test/notebooks/upload.spec.ts // TEMP disabled since notebooks service is off
- ./test/sql/resourceToken.spec.ts
- ./test/tables/container.spec.ts
steps:
- uses: actions/checkout@v4
- name: "Az CLI login"
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Use Node.js 18.x
uses: actions/setup-node@v4
with:

View File

@@ -9,6 +9,10 @@ on:
# Once every hour
- cron: "0 15 * * *"
permissions:
id-token: write
contents: read
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
@@ -16,10 +20,17 @@ jobs:
name: "Cleanup Test Database Accounts"
runs-on: ubuntu-latest
env:
NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }}
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
steps:
- uses: actions/checkout@v2
- name: "Az CLI login"
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Use Node.js 18.x
uses: actions/setup-node@v1
with:

3
.gitignore vendored
View File

@@ -16,5 +16,4 @@ Contracts/*
.env
failure.png
screenshots/*
GettingStarted-ignore*.ipynb
.vscode/
GettingStarted-ignore*.ipynb

View File

@@ -2296,6 +2296,17 @@ a:link {
display: none !important;
}
.monaco-editor .quick-input-list-label {
/* Restore some of Monaco's default styles that are clobbered by our global styles */
padding: 0;
line-height: 22px;
}
.monaco-editor .quick-input-list .highlight {
/* Padding in highlighted text within the quick input list breaks the flow of the text */
padding: 0;
}
td a {
color: #393939;
}

13
package-lock.json generated
View File

@@ -82,6 +82,7 @@
"knockout": "3.5.1",
"mkdirp": "1.0.4",
"monaco-editor": "0.44.0",
"mousetrap": "1.6.5",
"ms": "2.1.3",
"p-retry": "4.6.2",
"patch-package": "8.0.0",
@@ -128,6 +129,7 @@
"@types/hasher": "0.0.31",
"@types/jest": "26.0.20",
"@types/jquery": "3.5.29",
"@types/mousetrap": "1.6.15",
"@types/node": "12.11.1",
"@types/post-robot": "10.0.1",
"@types/q": "1.5.1",
@@ -12792,6 +12794,12 @@
"@types/node": "*"
}
},
"node_modules/@types/mousetrap": {
"version": "1.6.15",
"resolved": "https://registry.npmjs.org/@types/mousetrap/-/mousetrap-1.6.15.tgz",
"integrity": "sha512-qL0hyIMNPow317QWW/63RvL1x5MVMV+Ru3NaY9f/CuEpCqrmb7WeuK2071ZY5hczOnm38qExWM2i2WtkXLSqFw==",
"dev": true
},
"node_modules/@types/node": {
"version": "12.11.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.11.1.tgz",
@@ -31631,6 +31639,11 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/mousetrap": {
"version": "1.6.5",
"resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.5.tgz",
"integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA=="
},
"node_modules/mrmime": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz",

View File

@@ -77,6 +77,7 @@
"knockout": "3.5.1",
"mkdirp": "1.0.4",
"monaco-editor": "0.44.0",
"mousetrap": "1.6.5",
"ms": "2.1.3",
"p-retry": "4.6.2",
"patch-package": "8.0.0",
@@ -123,6 +124,7 @@
"@types/hasher": "0.0.31",
"@types/jest": "26.0.20",
"@types/jquery": "3.5.29",
"@types/mousetrap": "1.6.15",
"@types/node": "12.11.1",
"@types/post-robot": "10.0.1",
"@types/q": "1.5.1",

View File

@@ -124,8 +124,9 @@ export enum MongoBackendEndpointType {
remote,
}
export enum BackendApi {
GenerateToken,
export class BackendApi {
public static readonly GenerateToken: string = "GenerateToken";
public static readonly PortalSettings: string = "PortalSettings";
}
export class PortalBackendEndpoints {

View File

@@ -85,8 +85,6 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
return decodeURIComponent(result.PrimaryReadWriteToken);
};
// TODO
// Need to create separate plugins or change endpoint logic to return the correct write or read endpoint.
export const requestPlugin: Cosmos.Plugin<any> = async (requestContext, diagnosticNode, next) => {
requestContext.endpoint = new URL(configContext.PROXY_PATH, window.location.href).href;
requestContext.headers["x-ms-proxy-target"] = endpoint();
@@ -136,32 +134,9 @@ enum SDKSupportedCapabilities {
PartitionMerge = 1 << 0,
}
// Client Management
let _client: Cosmos.CosmosClient;
let _readClients: Map<string, Cosmos.CosmosClient> = new Map();
export enum ClientOperationType {
READ,
WRITE,
}
export function client(clientOperationType: ClientOperationType): Cosmos.CosmosClient {
switch (clientOperationType) {
case ClientOperationType.READ:
return readClients();
case ClientOperationType.WRITE:
return writeClient();
default:
throw new Error("Invalid operation type");
}
}
export function writeClient(): Cosmos.CosmosClient {
console.log(`Called primary client`);
const currentUserContextDocumentEndpoint = userContext?.databaseAccount?.properties?.documentEndpoint;
console.log(`Current selected endpoint in userContext: ${currentUserContextDocumentEndpoint}`);
export function client(): Cosmos.CosmosClient {
if (_client) return _client;
let _defaultHeaders: Cosmos.CosmosHeaders = {};
@@ -208,61 +183,3 @@ export function writeClient(): Cosmos.CosmosClient {
_client = new Cosmos.CosmosClient(options);
return _client;
}
export function readClients(): Cosmos.CosmosClient {
console.log(`Called read only client`);
const currentUserContextDocumentEndpoint = userContext?.databaseAccount?.properties?.documentEndpoint;
console.log(`Current selected read endpoint in userContext: ${currentUserContextDocumentEndpoint}`);
3;
const selectedEndpoint = endpoint() || "https://cosmos.azure.com";
if (_readClients.has(selectedEndpoint)) {
return _readClients.get(selectedEndpoint);
}
let _defaultHeaders: Cosmos.CosmosHeaders = {};
_defaultHeaders["x-ms-cosmos-sdk-supportedcapabilities"] =
SDKSupportedCapabilities.None | SDKSupportedCapabilities.PartitionMerge;
if (
userContext.authType === AuthType.ConnectionString ||
userContext.authType === AuthType.EncryptedToken ||
userContext.authType === AuthType.ResourceToken
) {
// Default to low priority. Needed for non-AAD-auth scenarios
// where we cannot use RP API, and thus, cannot detect whether priority
// based execution is enabled.
// The header will be ignored if priority based execution is disabled on the account.
_defaultHeaders["x-ms-cosmos-priority-level"] = PriorityLevel.Default;
}
const options: Cosmos.CosmosClientOptions = {
endpoint: selectedEndpoint, // CosmosClient gets upset if we pass a bad URL. This should never actually get called
key: userContext.masterKey,
tokenProvider,
userAgentSuffix: "Azure Portal",
defaultHeaders: _defaultHeaders,
connectionPolicy: {
retryOptions: {
maxRetryAttemptCount: LocalStorageUtility.getEntryNumber(StorageKey.RetryAttempts),
fixedRetryIntervalInMilliseconds: LocalStorageUtility.getEntryNumber(StorageKey.RetryInterval),
maxWaitTimeInSeconds: LocalStorageUtility.getEntryNumber(StorageKey.MaxWaitTimeInSeconds),
},
},
};
if (configContext.PROXY_PATH !== undefined) {
(options as any).plugins = [{ on: "request", plugin: requestPlugin }];
}
if (PriorityBasedExecutionUtils.isFeatureEnabled()) {
const plugins = (options as any).plugins || [];
plugins.push({ on: "request", plugin: PriorityBasedExecutionUtils.requestPlugin });
(options as any).plugins = plugins;
}
_readClients.set(selectedEndpoint, new Cosmos.CosmosClient(options));
return _readClients.get(selectedEndpoint);
}

View File

@@ -67,7 +67,7 @@ export function queryDocuments(
query: string,
continuationToken?: string,
): Promise<QueryResponse> {
if (!useMongoProxyEndpoint("resourcelist")) {
if (!useMongoProxyEndpoint("resourcelist") || !useMongoProxyEndpoint("queryDocuments")) {
return queryDocuments_ToBeDeprecated(databaseId, collection, isResourceList, query, continuationToken);
}
@@ -106,7 +106,7 @@ export function queryDocuments(
headers[CosmosSDKConstants.HttpHeaders.Continuation] = continuationToken;
}
const path = isResourceList ? "/resourcelist" : "";
const path = isResourceList ? "/resourcelist" : "/queryDocuments";
return window
.fetch(`${endpoint}${path}`, {
@@ -690,9 +690,16 @@ export function getARMCreateCollectionEndpoint(params: DataModels.MongoParameter
}
function useMongoProxyEndpoint(api: string): boolean {
const activeMongoProxyEndpoints: string[] = [MongoProxyEndpoints.Development];
const activeMongoProxyEndpoints: string[] = [
MongoProxyEndpoints.Development,
MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod,
];
let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
if (userContext.databaseAccount.properties.ipRules?.length > 0) {
if (
configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Development &&
userContext.databaseAccount.properties.ipRules?.length > 0
) {
canAccessMongoProxy = canAccessMongoProxy && configContext.MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED;
}

View File

@@ -1,7 +1,7 @@
import { JSONObject, OperationResponse } from "@azure/cosmos";
import { CollectionBase } from "../../Contracts/ViewModels";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { ClientOperationType, client } from "../CosmosClient";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export const bulkCreateDocument = async (
@@ -13,7 +13,7 @@ export const bulkCreateDocument = async (
);
try {
const response = await client(ClientOperationType.WRITE)
const response = await client()
.database(collection.databaseId)
.container(collection.id())
.items.bulk(

View File

@@ -6,14 +6,14 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import { getCollectionName } from "../../Utils/APITypeUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { createUpdateCassandraTable } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
import { createUpdateGremlinGraph } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
import { createUpdateMongoDBCollection } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
import { createUpdateSqlContainer } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
import { createUpdateTable } from "../../Utils/arm/generatedClients/cosmos/tableResources";
import * as ARMTypes from "../../Utils/arm/generatedClients/cosmos/types";
import { ClientOperationType, client } from "../CosmosClient";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { createMongoCollectionWithProxy } from "../MongoProxyClient";
import { createDatabase } from "./createDatabase";
@@ -284,9 +284,7 @@ const createCollectionWithSDK = async (params: DataModels.CreateCollectionParams
}
}
const databaseResponse: DatabaseResponse = await client(ClientOperationType.WRITE).databases.createIfNotExists(
createDatabaseBody,
);
const databaseResponse: DatabaseResponse = await client().databases.createIfNotExists(createDatabaseBody);
const collectionResponse: ContainerResponse = await databaseResponse?.database.containers.create(
createCollectionBody,
collectionOptions,

View File

@@ -4,7 +4,6 @@ import * as DataModels from "../../Contracts/DataModels";
import { useDatabases } from "../../Explorer/useDatabases";
import { userContext } from "../../UserContext";
import { getDatabaseName } from "../../Utils/APITypeUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { createUpdateCassandraKeyspace } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
import { createUpdateGremlinDatabase } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
import { createUpdateMongoDBDatabase } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
@@ -16,7 +15,8 @@ import {
MongoDBDatabaseCreateUpdateParameters,
SqlDatabaseCreateUpdateParameters,
} from "../../Utils/arm/generatedClients/cosmos/types";
import { ClientOperationType, client } from "../CosmosClient";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function createDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
@@ -153,7 +153,7 @@ async function createDatabaseWithSDK(params: DataModels.CreateDatabaseParams): P
}
}
const response: DatabaseResponse = await client(ClientOperationType.WRITE).databases.create(createBody);
const response: DatabaseResponse = await client().databases.create(createBody);
return response.resource;
}

View File

@@ -1,6 +1,6 @@
import { CollectionBase } from "../../Contracts/ViewModels";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { ClientOperationType, client } from "../CosmosClient";
import { client } from "../CosmosClient";
import { getEntityName } from "../DocumentUtility";
import { handleError } from "../ErrorHandlingUtils";
@@ -9,7 +9,7 @@ export const createDocument = async (collection: CollectionBase, newDocument: un
const clearMessage = logConsoleProgress(`Creating new ${entityName} for container ${collection.id()}`);
try {
const response = await client(ClientOperationType.WRITE)
const response = await client()
.database(collection.databaseId)
.container(collection.id())
.items.create(newDocument);

View File

@@ -1,7 +1,6 @@
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import {
createUpdateSqlStoredProcedure,
getSqlStoredProcedure,
@@ -10,7 +9,8 @@ import {
SqlStoredProcedureCreateUpdateParameters,
SqlStoredProcedureResource,
} from "../../Utils/arm/generatedClients/cosmos/types";
import { ClientOperationType, client } from "../CosmosClient";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function createStoredProcedure(
@@ -63,7 +63,7 @@ export async function createStoredProcedure(
return rpResponse && (rpResponse.properties?.resource as StoredProcedureDefinition & Resource);
}
const response = await client(ClientOperationType.WRITE)
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.storedProcedures.create(storedProcedure);

View File

@@ -1,10 +1,10 @@
import { TriggerDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
import { SqlTriggerCreateUpdateParameters, SqlTriggerResource } from "../../Utils/arm/generatedClients/cosmos/types";
import { ClientOperationType, client } from "../CosmosClient";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function createTrigger(
@@ -55,7 +55,7 @@ export async function createTrigger(
return rpResponse && rpResponse.properties?.resource;
}
const response = await client(ClientOperationType.WRITE)
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.triggers.create(trigger as unknown as TriggerDefinition); // TODO: TypeScript does not like the SQL SDK trigger type

View File

@@ -1,7 +1,6 @@
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import {
createUpdateSqlUserDefinedFunction,
getSqlUserDefinedFunction,
@@ -10,7 +9,8 @@ import {
SqlUserDefinedFunctionCreateUpdateParameters,
SqlUserDefinedFunctionResource,
} from "../../Utils/arm/generatedClients/cosmos/types";
import { ClientOperationType, client } from "../CosmosClient";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function createUserDefinedFunction(
@@ -63,7 +63,7 @@ export async function createUserDefinedFunction(
return rpResponse && (rpResponse.properties?.resource as UserDefinedFunctionDefinition & Resource);
}
const response = await client(ClientOperationType.WRITE)
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.userDefinedFunctions.create(userDefinedFunction);

View File

@@ -6,7 +6,7 @@ import { deleteMongoDBCollection } from "../../Utils/arm/generatedClients/cosmos
import { deleteSqlContainer } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
import { deleteTable } from "../../Utils/arm/generatedClients/cosmos/tableResources";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client, ClientOperationType } from "../CosmosClient";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
@@ -15,7 +15,7 @@ export async function deleteCollection(databaseId: string, collectionId: string)
if (userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations) {
await deleteCollectionWithARM(databaseId, collectionId);
} else {
await client(ClientOperationType.WRITE).database(databaseId).container(collectionId).delete();
await client().database(databaseId).container(collectionId).delete();
}
logConsoleInfo(`Successfully deleted container ${collectionId}`);
} catch (error) {

View File

@@ -1,9 +1,9 @@
import { RequestOptions } from "@azure/cosmos";
import { CollectionBase } from "../../Contracts/ViewModels";
import ConflictId from "../../Explorer/Tree/ConflictId";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { ClientOperationType, client } from "../CosmosClient";
import { CollectionBase } from "../../Contracts/ViewModels";
import { RequestOptions } from "@azure/cosmos";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
export const deleteConflict = async (collection: CollectionBase, conflictId: ConflictId): Promise<void> => {
const clearMessage = logConsoleProgress(`Deleting conflict ${conflictId.id()}`);
@@ -13,7 +13,7 @@ export const deleteConflict = async (collection: CollectionBase, conflictId: Con
partitionKey: getPartitionKeyHeaderForConflict(conflictId),
};
await client(ClientOperationType.WRITE)
await client()
.database(collection.databaseId)
.container(collection.id())
.conflict(conflictId.id())

View File

@@ -5,7 +5,7 @@ import { deleteGremlinDatabase } from "../../Utils/arm/generatedClients/cosmos/g
import { deleteMongoDBDatabase } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
import { deleteSqlDatabase } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client, ClientOperationType } from "../CosmosClient";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function deleteDatabase(databaseId: string): Promise<void> {
@@ -15,7 +15,7 @@ export async function deleteDatabase(databaseId: string): Promise<void> {
if (userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations) {
await deleteDatabaseWithARM(databaseId);
} else {
await client(ClientOperationType.WRITE).database(databaseId).delete();
await client().database(databaseId).delete();
}
logConsoleInfo(`Successfully deleted database ${databaseId}`);
} catch (error) {

View File

@@ -1,7 +1,7 @@
import { CollectionBase } from "../../Contracts/ViewModels";
import DocumentId from "../../Explorer/Tree/DocumentId";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { ClientOperationType, client } from "../CosmosClient";
import { client } from "../CosmosClient";
import { getEntityName } from "../DocumentUtility";
import { handleError } from "../ErrorHandlingUtils";
import { getPartitionKeyValue } from "./getPartitionKeyValue";
@@ -11,7 +11,7 @@ export const deleteDocument = async (collection: CollectionBase, documentId: Doc
const clearMessage = logConsoleProgress(`Deleting ${entityName} ${documentId.id()}`);
try {
await client(ClientOperationType.WRITE)
await client()
.database(collection.databaseId)
.container(collection.id())
.item(documentId.id(), getPartitionKeyValue(documentId))

View File

@@ -2,7 +2,7 @@ import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { deleteSqlStoredProcedure } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client, ClientOperationType } from "../CosmosClient";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function deleteStoredProcedure(
@@ -26,11 +26,7 @@ export async function deleteStoredProcedure(
storedProcedureId,
);
} else {
await client(ClientOperationType.WRITE)
.database(databaseId)
.container(collectionId)
.scripts.storedProcedure(storedProcedureId)
.delete();
await client().database(databaseId).container(collectionId).scripts.storedProcedure(storedProcedureId).delete();
}
} catch (error) {
handleError(error, "DeleteStoredProcedure", `Error while deleting stored procedure ${storedProcedureId}`);

View File

@@ -2,7 +2,7 @@ import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { deleteSqlTrigger } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client, ClientOperationType } from "../CosmosClient";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function deleteTrigger(databaseId: string, collectionId: string, triggerId: string): Promise<void> {
@@ -22,11 +22,7 @@ export async function deleteTrigger(databaseId: string, collectionId: string, tr
triggerId,
);
} else {
await client(ClientOperationType.WRITE)
.database(databaseId)
.container(collectionId)
.scripts.trigger(triggerId)
.delete();
await client().database(databaseId).container(collectionId).scripts.trigger(triggerId).delete();
}
} catch (error) {
handleError(error, "DeleteTrigger", `Error while deleting trigger ${triggerId}`);

View File

@@ -2,7 +2,7 @@ import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { deleteSqlUserDefinedFunction } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client, ClientOperationType } from "../CosmosClient";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function deleteUserDefinedFunction(databaseId: string, collectionId: string, id: string): Promise<void> {
@@ -22,11 +22,7 @@ export async function deleteUserDefinedFunction(databaseId: string, collectionId
id,
);
} else {
await client(ClientOperationType.WRITE)
.database(databaseId)
.container(collectionId)
.scripts.userDefinedFunction(id)
.delete();
await client().database(databaseId).container(collectionId).scripts.userDefinedFunction(id).delete();
}
} catch (error) {
handleError(error, "DeleteUserDefinedFunction", `Error while deleting user defined function ${id}`);

View File

@@ -1,9 +1,9 @@
import { Collection } from "../../Contracts/ViewModels";
import StoredProcedure from "../../Explorer/Tree/StoredProcedure";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { ClientDefaults, HttpHeaders } from "../Constants";
import { ClientOperationType, client } from "../CosmosClient";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import StoredProcedure from "../../Explorer/Tree/StoredProcedure";
export interface ExecuteSprocResult {
result: StoredProcedure;
@@ -22,7 +22,7 @@ export const executeStoredProcedure = async (
}, ClientDefaults.requestTimeoutMs);
try {
const response = await client(ClientOperationType.WRITE)
const response = await client()
.database(collection.databaseId)
.container(collection.id())
.scripts.storedProcedure(storedProcedure.id())

View File

@@ -1,9 +1,9 @@
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import * as Constants from "../Constants";
import { ClientOperationType, client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
export async function getIndexTransformationProgress(databaseId: string, collectionId: string): Promise<number> {
if (userContext.authType !== AuthType.AAD) {
@@ -12,10 +12,7 @@ export async function getIndexTransformationProgress(databaseId: string, collect
let indexTransformationPercentage: number;
const clearMessage = logConsoleProgress(`Reading container ${collectionId}`);
try {
const response = await client(ClientOperationType.READ)
.database(databaseId)
.container(collectionId)
.read({ populateQuotaInfo: true });
const response = await client().database(databaseId).container(collectionId).read({ populateQuotaInfo: true });
indexTransformationPercentage = parseInt(
response.headers[Constants.HttpHeaders.collectionIndexTransformationProgress] as string,

View File

@@ -1,5 +1,5 @@
import { ConflictDefinition, FeedOptions, QueryIterator, Resource } from "@azure/cosmos";
import { ClientOperationType, client } from "../CosmosClient";
import { client } from "../CosmosClient";
export const queryConflicts = (
databaseId: string,
@@ -7,5 +7,5 @@ export const queryConflicts = (
query: string,
options: FeedOptions,
): QueryIterator<ConflictDefinition & Resource> => {
return client(ClientOperationType.READ).database(databaseId).container(containerId).conflicts.query(query, options);
return client().database(databaseId).container(containerId).conflicts.query(query, options);
};

View File

@@ -1,7 +1,7 @@
import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
import { Queries } from "../Constants";
import { ClientOperationType, client } from "../CosmosClient";
import { client } from "../CosmosClient";
export const queryDocuments = (
databaseId: string,
@@ -10,7 +10,7 @@ export const queryDocuments = (
options: FeedOptions,
): QueryIterator<ItemDefinition & Resource> => {
options = getCommonQueryOptions(options);
return client(ClientOperationType.READ).database(databaseId).container(containerId).items.query(query, options);
return client().database(databaseId).container(containerId).items.query(query, options);
};
export const getCommonQueryOptions = (options: FeedOptions): FeedOptions => {

View File

@@ -3,11 +3,11 @@ import { sampleDataClient } from "Common/SampleDataClient";
import { userContext } from "UserContext";
import * as DataModels from "../../Contracts/DataModels";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { ClientOperationType, client } from "../CosmosClient";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function readCollection(databaseId: string, collectionId: string): Promise<DataModels.Collection> {
const cosmosClient = client(ClientOperationType.READ);
const cosmosClient = client();
return await readCollectionInternal(cosmosClient, databaseId, collectionId);
}

View File

@@ -10,7 +10,7 @@ import { listGremlinGraphs } from "../../Utils/arm/generatedClients/cosmos/greml
import { listMongoDBCollections } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
import { listSqlContainers } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
import { listTables } from "../../Utils/arm/generatedClients/cosmos/tableResources";
import { ClientOperationType, client } from "../CosmosClient";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
@@ -31,7 +31,7 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
const tokenCollectionId = resourceIdObj[3];
if (tokenDatabaseId === databaseId) {
promises.push(client(ClientOperationType.READ).database(databaseId).container(tokenCollectionId).read());
promises.push(client().database(databaseId).container(tokenCollectionId).read());
}
}
@@ -61,7 +61,7 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
return await readCollectionsWithARM(databaseId);
}
const sdkResponse = await client(ClientOperationType.READ).database(databaseId).containers.readAll().fetchAll();
const sdkResponse = await client().database(databaseId).containers.readAll().fetchAll();
return sdkResponse.resources as DataModels.Collection[];
} catch (error) {
handleError(error, "ReadCollections", `Error while querying containers for database ${databaseId}`);
@@ -77,7 +77,7 @@ export async function readCollectionsWithPagination(
): Promise<DataModels.CollectionsWithPagination> {
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
try {
const sdkResponse = await client(ClientOperationType.READ)
const sdkResponse = await client()
.database(databaseId)
.containers.query(
{ query: "SELECT * FROM c" },

View File

@@ -7,7 +7,7 @@ import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/cosmos/
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
import { listSqlDatabases } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
import { ClientOperationType, client } from "../CosmosClient";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function readDatabases(): Promise<DataModels.Database[]> {
@@ -56,7 +56,7 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
) {
databases = await readDatabasesWithARM();
} else {
const sdkResponse = await client(ClientOperationType.READ).databases.readAll().fetchAll();
const sdkResponse = await client().databases.readAll().fetchAll();
databases = sdkResponse.resources as DataModels.Database[];
}
} catch (error) {

View File

@@ -3,7 +3,7 @@ import { CollectionBase } from "../../Contracts/ViewModels";
import DocumentId from "../../Explorer/Tree/DocumentId";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { HttpHeaders } from "../Constants";
import { ClientOperationType, client } from "../CosmosClient";
import { client } from "../CosmosClient";
import { getEntityName } from "../DocumentUtility";
import { handleError } from "../ErrorHandlingUtils";
import { getPartitionKeyValue } from "./getPartitionKeyValue";
@@ -19,7 +19,7 @@ export const readDocument = async (collection: CollectionBase, documentId: Docum
[HttpHeaders.partitionKey]: documentId.partitionKeyValue,
}
: {};
const response = await client(ClientOperationType.READ)
const response = await client()
.database(collection.databaseId)
.container(collection.id())
.item(documentId.id(), getPartitionKeyValue(documentId))

View File

@@ -1,7 +1,7 @@
import { RequestOptions } from "@azure/cosmos";
import { Offer } from "../../Contracts/DataModels";
import { HttpHeaders } from "../Constants";
import { ClientOperationType, client } from "../CosmosClient";
import { client } from "../CosmosClient";
import { parseSDKOfferResponse } from "../OfferUtility";
import { readOffers } from "./readOffers";
@@ -21,7 +21,7 @@ export const readOfferWithSDK = async (offerId: string, resourceId: string): Pro
[HttpHeaders.populateCollectionThroughputInfo]: true,
},
};
const response = await client(ClientOperationType.READ).offer(offerId).read(options);
const response = await client().offer(offerId).read(options);
return parseSDKOfferResponse(response);
};

View File

@@ -1,13 +1,13 @@
import { SDKOfferDefinition } from "../../Contracts/DataModels";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { ClientOperationType, client } from "../CosmosClient";
import { getErrorMessage, handleError } from "../ErrorHandlingUtils";
import { client } from "../CosmosClient";
import { handleError, getErrorMessage } from "../ErrorHandlingUtils";
export const readOffers = async (): Promise<SDKOfferDefinition[]> => {
const clearMessage = logConsoleProgress(`Querying offers`);
try {
const response = await client(ClientOperationType.READ).offers.readAll().fetchAll();
const response = await client().offers.readAll().fetchAll();
return response?.resources;
} catch (error) {
// This should be removed when we can correctly identify if an account is serverless when connected using connection string too.

View File

@@ -4,7 +4,7 @@ import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { listSqlStoredProcedures } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
import { ClientOperationType, client } from "../CosmosClient";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function readStoredProcedures(
@@ -34,7 +34,7 @@ export async function readStoredProcedures(
throw new Error(cloudError?.error?.message);
}
const response = await client(ClientOperationType.READ)
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.storedProcedures.readAll()

View File

@@ -1,10 +1,10 @@
import { TriggerDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { listSqlTriggers } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
import { SqlTriggerResource } from "../../Utils/arm/generatedClients/cosmos/types";
import { ClientOperationType, client } from "../CosmosClient";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function readTriggers(
@@ -28,11 +28,7 @@ export async function readTriggers(
return rpResponse?.value?.map((trigger) => trigger.properties?.resource);
}
const response = await client(ClientOperationType.READ)
.database(databaseId)
.container(collectionId)
.scripts.triggers.readAll()
.fetchAll();
const response = await client().database(databaseId).container(collectionId).scripts.triggers.readAll().fetchAll();
return response?.resources;
} catch (error) {
handleError(error, "ReadTriggers", `Failed to query triggers for container ${collectionId}`);

View File

@@ -1,9 +1,9 @@
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { listSqlUserDefinedFunctions } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
import { ClientOperationType, client } from "../CosmosClient";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function readUserDefinedFunctions(
@@ -24,7 +24,7 @@ export async function readUserDefinedFunctions(
return rpResponse?.value?.map((udf) => udf.properties?.resource as UserDefinedFunctionDefinition & Resource);
}
const response = await client(ClientOperationType.READ)
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.userDefinedFunctions.readAll()

View File

@@ -2,7 +2,6 @@ import { ContainerDefinition, RequestOptions } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { Collection } from "../../Contracts/DataModels";
import { userContext } from "../../UserContext";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import {
createUpdateCassandraTable,
getCassandraTable,
@@ -20,7 +19,8 @@ import {
SqlContainerCreateUpdateParameters,
SqlContainerResource,
} from "../../Utils/arm/generatedClients/cosmos/types";
import { ClientOperationType, client } from "../CosmosClient";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function updateCollection(
@@ -40,7 +40,7 @@ export async function updateCollection(
) {
collection = await updateCollectionWithARM(databaseId, collectionId, newCollection);
} else {
const sdkResponse = await client(ClientOperationType.WRITE)
const sdkResponse = await client()
.database(databaseId)
.container(collectionId)
.replace(newCollection as ContainerDefinition, options);

View File

@@ -3,7 +3,7 @@ import { HttpHeaders } from "Common/Constants";
import { CollectionBase } from "../../Contracts/ViewModels";
import DocumentId from "../../Explorer/Tree/DocumentId";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { ClientOperationType, client } from "../CosmosClient";
import { client } from "../CosmosClient";
import { getEntityName } from "../DocumentUtility";
import { handleError } from "../ErrorHandlingUtils";
import { getPartitionKeyValue } from "./getPartitionKeyValue";
@@ -23,7 +23,7 @@ export const updateDocument = async (
[HttpHeaders.partitionKey]: documentId.partitionKeyValue,
}
: {};
const response = await client(ClientOperationType.WRITE)
const response = await client()
.database(collection.databaseId)
.container(collection.id())
.item(documentId.id(), getPartitionKeyValue(documentId))

View File

@@ -2,7 +2,6 @@ import { OfferDefinition, RequestOptions } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { Offer, SDKOfferDefinition, UpdateOfferParams } from "../../Contracts/DataModels";
import { userContext } from "../../UserContext";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import {
migrateCassandraKeyspaceToAutoscale,
migrateCassandraKeyspaceToManualThroughput,
@@ -41,8 +40,9 @@ import {
updateTableThroughput,
} from "../../Utils/arm/generatedClients/cosmos/tableResources";
import { ThroughputSettingsUpdateParameters } from "../../Utils/arm/generatedClients/cosmos/types";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { HttpHeaders } from "../Constants";
import { ClientOperationType, client } from "../CosmosClient";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { parseSDKOfferResponse } from "../OfferUtility";
import { readCollectionOffer } from "./readCollectionOffer";
@@ -401,7 +401,7 @@ const updateOfferWithSDK = async (params: UpdateOfferParams): Promise<Offer> =>
newOffer.content.offerAutopilotSettings = { maxThroughput: 0 };
}
const sdkResponse = await client(ClientOperationType.WRITE)
const sdkResponse = await client()
.offer(params.currentOffer.id)
// TODO Remove casting when SDK types are fixed (https://github.com/Azure/azure-sdk-for-js/issues/10660)
.replace(newOffer as unknown as OfferDefinition, options);

View File

@@ -1,7 +1,6 @@
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import {
createUpdateSqlStoredProcedure,
getSqlStoredProcedure,
@@ -10,7 +9,8 @@ import {
SqlStoredProcedureCreateUpdateParameters,
SqlStoredProcedureResource,
} from "../../Utils/arm/generatedClients/cosmos/types";
import { ClientOperationType, client } from "../CosmosClient";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function updateStoredProcedure(
@@ -54,7 +54,7 @@ export async function updateStoredProcedure(
throw new Error(`Failed to update stored procedure: ${storedProcedure.id} does not exist.`);
}
const response = await client(ClientOperationType.WRITE)
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.storedProcedure(storedProcedure.id)

View File

@@ -1,10 +1,10 @@
import { TriggerDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
import { SqlTriggerCreateUpdateParameters, SqlTriggerResource } from "../../Utils/arm/generatedClients/cosmos/types";
import { ClientOperationType, client } from "../CosmosClient";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function updateTrigger(
@@ -47,7 +47,7 @@ export async function updateTrigger(
throw new Error(`Failed to update trigger: ${trigger.id} does not exist.`);
}
const response = await client(ClientOperationType.WRITE)
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.trigger(trigger.id)

View File

@@ -1,7 +1,6 @@
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import {
createUpdateSqlUserDefinedFunction,
getSqlUserDefinedFunction,
@@ -10,7 +9,8 @@ import {
SqlUserDefinedFunctionCreateUpdateParameters,
SqlUserDefinedFunctionResource,
} from "../../Utils/arm/generatedClients/cosmos/types";
import { ClientOperationType, client } from "../CosmosClient";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function updateUserDefinedFunction(
@@ -53,7 +53,7 @@ export async function updateUserDefinedFunction(
throw new Error(`Failed to update user defined function: ${userDefinedFunction.id} does not exist.`);
}
const response = await client(ClientOperationType.WRITE)
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.userDefinedFunction(userDefinedFunction.id)

View File

@@ -99,24 +99,19 @@ let configContext: Readonly<ConfigContext> = {
JUNO_ENDPOINT: JunoEndpoints.Prod,
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod,
NEW_BACKEND_APIS: [BackendApi.GenerateToken],
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
NEW_MONGO_APIS: [
// "resourcelist",
// "createDocument",
// "readDocument",
// "updateDocument",
// "deleteDocument",
// "createCollectionWithProxy",
"resourcelist",
"queryDocuments",
"createDocument",
"readDocument",
"updateDocument",
"deleteDocument",
"createCollectionWithProxy",
],
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod,
NEW_CASSANDRA_APIS: [
// "postQuery",
// "createOrDelete",
// "getKeys",
// "getSchema",
],
NEW_CASSANDRA_APIS: ["postQuery", "createOrDelete", "getKeys", "getSchema"],
CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
isTerminalEnabled: false,
isPhoenixEnabled: false,

View File

@@ -47,6 +47,7 @@ export enum MessageTypes {
GetAllResourceTokens, // Data Explorer -> Fabric
Ready, // Data Explorer -> Fabric
OpenCESCVAFeedbackBlade,
ActivateTab,
}
export interface AuthorizationToken {

View File

@@ -387,6 +387,7 @@ export interface DataExplorerInputsFrame {
dnsSuffix?: string;
serverId?: string;
extensionEndpoint?: string;
portalBackendEndpoint?: string;
mongoProxyEndpoint?: string;
cassandraProxyEndpoint?: string;
subscriptionType?: SubscriptionType;

View File

@@ -1,6 +1,8 @@
/**
* React component for Command button component.
*/
import { KeyboardShortcutAction } from "KeyboardShortcuts";
import { ExtendedKeyboardEvent } from "mousetrap";
import * as React from "react";
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
import { KeyCodes } from "../../../Common/Constants";
@@ -30,7 +32,7 @@ export interface CommandButtonComponentProps {
/**
* Click handler for command button click
*/
onCommandClick: (e: React.SyntheticEvent) => void;
onCommandClick: (e: React.SyntheticEvent | ExtendedKeyboardEvent) => void;
/**
* Label for the button
@@ -107,10 +109,13 @@ export interface CommandButtonComponentProps {
* Vertical bar to divide buttons
*/
isDivider?: boolean;
/**
* Aria-label for the button
*/
ariaLabel: string;
keyboardShortcut?: KeyboardShortcutAction;
}
export class CommandButtonComponent extends React.Component<CommandButtonComponentProps> {

View File

@@ -46,9 +46,25 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
}, 100);
}
public componentDidUpdate(previous: EditorReactProps) {
if (this.props.content !== previous.content) {
this.editor?.setValue(this.props.content);
public componentDidUpdate() {
if (!this.editor) {
return;
}
const existingContent = this.editor.getModel().getValue();
if (this.props.content !== existingContent) {
if (this.props.isReadOnly) {
this.editor.setValue(this.props.content);
} else {
this.editor.pushUndoStop();
this.editor.executeEdits("", [
{
range: this.editor.getModel().getFullModelRange(),
text: this.props.content,
},
]);
}
}
}
@@ -71,9 +87,14 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
this.editor = editor;
const queryEditorModel = this.editor.getModel();
if (!this.props.isReadOnly && this.props.onContentChanged) {
queryEditorModel.onDidChangeContent(() => {
// Hooking the model's onDidChangeContent event because of some event ordering issues.
// If a single user input causes BOTH the editor content to change AND the cursor selection to change (which is likely),
// then there are some inconsistencies as to which event fires first.
// But the editor.onDidChangeModelContent event seems to always fire before the cursor selection event.
// (This is NOT true for the model's onDidChangeContent event, which sometimes fires after the cursor selection event.)
// If the cursor selection event fires first, then the calling component may re-render the component with old content, so we want to ensure the model content changed event always fires first.
this.editor.onDidChangeModelContent(() => {
const queryEditorModel = this.editor.getModel();
this.props.onContentChanged(queryEditorModel.getValue());
});

View File

@@ -5,6 +5,7 @@
*/
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
import { useNotebook } from "Explorer/Notebook/useNotebook";
import { KeyboardShortcutAction, KeyboardShortcutContributor, KeyboardShortcutHandler, useKeyboardShortcutContributor } from "KeyboardShortcuts";
import { userContext } from "UserContext";
import * as React from "react";
import create, { UseStore } from "zustand";
@@ -40,6 +41,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
const buttons = useCommandBar((state) => state.contextButtons);
const isHidden = useCommandBar((state) => state.isHidden);
const backgroundColor = StyleConstants.BaseLight;
const setKeyboardShortcutHandlers = useKeyboardShortcutContributor(KeyboardShortcutContributor.COMMAND_BAR);
if (userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo") {
const buttons =
@@ -105,6 +107,18 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
},
};
const allButtons = staticButtons.concat(contextButtons).concat(controlButtons);
const handlers: Partial<Record<KeyboardShortcutAction, KeyboardShortcutHandler>> = {};
allButtons.forEach((button) => {
if(button.keyboardShortcut) {
handlers[button.keyboardShortcut] = (e) => {
button.onCommandClick(e);
return false;
}
}
});
setKeyboardShortcutHandlers(handlers);
return (
<div className="commandBarContainer" style={{ display: isHidden ? "none" : "initial" }}>
<FluentCommandBar

View File

@@ -2,10 +2,8 @@ import * as ko from "knockout";
import { AuthType } from "../../../AuthType";
import { DatabaseAccount } from "../../../Contracts/DataModels";
import { CollectionBase } from "../../../Contracts/ViewModels";
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
import { updateUserContext } from "../../../UserContext";
import Explorer from "../../Explorer";
import NotebookManager from "../../Notebook/NotebookManager";
import { useNotebook } from "../../Notebook/useNotebook";
import { useDatabases } from "../../useDatabases";
import { useSelectedNode } from "../../useSelectedNode";
@@ -72,181 +70,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
});
});
describe("Enable notebook button", () => {
const enableNotebookBtnLabel = "Enable Notebooks (Preview)";
const selectedNodeState = useSelectedNode.getState();
beforeAll(() => {
mockExplorer = {} as Explorer;
updateUserContext({
portalEnv: "prod",
databaseAccount: {
properties: {
capabilities: [{ name: "EnableTable" }],
},
} as DatabaseAccount,
});
});
afterEach(() => {
updateUserContext({
portalEnv: "prod",
});
useNotebook.getState().setIsNotebookEnabled(false);
useNotebook.getState().setIsNotebooksEnabledForAccount(false);
});
it("Notebooks is already enabled - button should be hidden", () => {
useNotebook.getState().setIsNotebookEnabled(true);
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
expect(enableNotebookBtn).toBeUndefined();
});
it("Account is running on one of the national clouds - button should be hidden", () => {
updateUserContext({
portalEnv: "mooncake",
});
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
expect(enableNotebookBtn).toBeUndefined();
});
it("Notebooks is not enabled but is available - button should be shown and enabled", () => {
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
//TODO: modify once notebooks are available
expect(enableNotebookBtn).toBeUndefined();
//expect(enableNotebookBtn).toBeDefined();
//expect(enableNotebookBtn.disabled).toBe(false);
//expect(enableNotebookBtn.tooltipText).toBe("");
});
it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
//TODO: modify once notebooks are available
expect(enableNotebookBtn).toBeUndefined();
//expect(enableNotebookBtn).toBeDefined();
//expect(enableNotebookBtn.disabled).toBe(true);
//expect(enableNotebookBtn.tooltipText).toBe(
// "Notebooks are not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."
//);
});
});
describe("Open Mongo shell button", () => {
const openMongoShellBtnLabel = "Open Mongo shell";
const selectedNodeState = useSelectedNode.getState();
beforeAll(() => {
mockExplorer = {} as Explorer;
updateUserContext({
databaseAccount: {
properties: {
capabilities: [{ name: "EnableTable" }],
},
} as DatabaseAccount,
});
});
afterAll(() => {
updateUserContext({
apiType: "SQL",
});
useNotebook.getState().setIsShellEnabled(false);
});
beforeEach(() => {
updateUserContext({
apiType: "Mongo",
});
useNotebook.getState().setIsShellEnabled(true);
});
afterEach(() => {
useNotebook.getState().setIsNotebookEnabled(false);
useNotebook.getState().setIsNotebooksEnabledForAccount(false);
});
it("Mongo Api not available - button should be hidden", () => {
updateUserContext({
apiType: "SQL",
});
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeUndefined();
});
it("Running on a national cloud - button should be hidden", () => {
updateUserContext({
portalEnv: "mooncake",
});
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeUndefined();
});
it("Notebooks is not enabled and is unavailable - button should be hidden", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeUndefined();
});
it("Notebooks is not enabled and is available - button should be hidden", () => {
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeUndefined();
});
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
useNotebook.getState().setIsNotebookEnabled(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeDefined();
//TODO: modify once notebooks are available
expect(openMongoShellBtn.disabled).toBe(true);
//expect(openMongoShellBtn.disabled).toBe(false);
//expect(openMongoShellBtn.tooltipText).toBe("");
});
it("Notebooks is enabled and is available - button should be shown and enabled", () => {
useNotebook.getState().setIsNotebookEnabled(true);
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeDefined();
//TODO: modify once notebooks are available
expect(openMongoShellBtn.disabled).toBe(true);
//expect(openMongoShellBtn.disabled).toBe(false);
//expect(openMongoShellBtn.tooltipText).toBe("");
});
it("Notebooks is enabled and is available, terminal is unavailable due to ipRules - button should be hidden", () => {
useNotebook.getState().setIsNotebookEnabled(true);
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
useNotebook.getState().setIsShellEnabled(false);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeUndefined();
});
});
describe("Open Cassandra shell button", () => {
const openCassandraShellBtnLabel = "Open Cassandra shell";
const selectedNodeState = useSelectedNode.getState();
@@ -305,42 +128,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined();
});
it("Notebooks is not enabled and is available - button should be shown and enabled", () => {
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined();
});
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
useNotebook.getState().setIsNotebookEnabled(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeDefined();
//TODO: modify once notebooks are available
expect(openCassandraShellBtn.disabled).toBe(true);
//expect(openCassandraShellBtn.disabled).toBe(false);
//expect(openCassandraShellBtn.tooltipText).toBe("");
});
it("Notebooks is enabled and is available - button should be shown and enabled", () => {
useNotebook.getState().setIsNotebookEnabled(true);
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeDefined();
//TODO: modify once notebooks are available
expect(openCassandraShellBtn.disabled).toBe(true);
//expect(openCassandraShellBtn.disabled).toBe(false);
//expect(openCassandraShellBtn.tooltipText).toBe("");
});
});
describe("Open Postgres and vCore Mongo buttons", () => {
@@ -368,62 +155,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
});
});
describe("GitHub buttons", () => {
const connectToGitHubBtnLabel = "Connect to GitHub";
const manageGitHubSettingsBtnLabel = "Manage GitHub settings";
const selectedNodeState = useSelectedNode.getState();
beforeAll(() => {
mockExplorer = {} as Explorer;
updateUserContext({
databaseAccount: {
properties: {
capabilities: [{ name: "EnableTable" }],
},
} as DatabaseAccount,
});
mockExplorer.notebookManager = new NotebookManager();
mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined);
});
afterEach(() => {
jest.resetAllMocks();
useNotebook.getState().setIsNotebookEnabled(false);
});
it("Notebooks is enabled and GitHubOAuthService is not logged in - connect to github button should be visible", () => {
useNotebook.getState().setIsNotebookEnabled(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel);
expect(connectToGitHubBtn).toBeDefined();
});
it("Notebooks is enabled and GitHubOAuthService is logged in - manage github settings button should be visible", () => {
useNotebook.getState().setIsNotebookEnabled(true);
mockExplorer.notebookManager.gitHubOAuthService.isLoggedIn = jest.fn().mockReturnValue(true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const manageGitHubSettingsBtn = buttons.find(
(button) => button.commandButtonLabel === manageGitHubSettingsBtnLabel,
);
expect(manageGitHubSettingsBtn).toBeDefined();
});
it("Notebooks is not enabled - connect to github and manage github settings buttons should be hidden", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel);
expect(connectToGitHubBtn).toBeUndefined();
const manageGitHubSettingsBtn = buttons.find(
(button) => button.commandButtonLabel === manageGitHubSettingsBtnLabel,
);
expect(manageGitHubSettingsBtn).toBeUndefined();
});
});
describe("Resource token", () => {
const mockCollection = { id: ko.observable("test") } as CollectionBase;
useSelectedNode.getState().setSelectedNode(mockCollection);

View File

@@ -1,3 +1,4 @@
import { KeyboardShortcutAction } from "KeyboardShortcuts";
import { ReactTabKind, useTabs } from "hooks/useTabs";
import * as React from "react";
import AddCollectionIcon from "../../../../images/AddCollection.svg";
@@ -7,14 +8,10 @@ import AddStoredProcedureIcon from "../../../../images/AddStoredProcedure.svg";
import AddTriggerIcon from "../../../../images/AddTrigger.svg";
import AddUdfIcon from "../../../../images/AddUdf.svg";
import BrowseQueriesIcon from "../../../../images/BrowseQuery.svg";
import CosmosTerminalIcon from "../../../../images/Cosmos-Terminal.svg";
import FeedbackIcon from "../../../../images/Feedback-Command.svg";
import HomeIcon from "../../../../images/Home_16.svg";
import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg";
import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg";
import GitHubIcon from "../../../../images/github.svg";
import NewNotebookIcon from "../../../../images/notebook/Notebook-new.svg";
import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-workspace.svg";
import OpenInTabIcon from "../../../../images/open-in-tab.svg";
import SettingsIcon from "../../../../images/settings_15x15.svg";
import SynapseIcon from "../../../../images/synapse-link.svg";
@@ -22,8 +19,7 @@ import { AuthType } from "../../../AuthType";
import * as Constants from "../../../Common/Constants";
import { Platform, configContext } from "../../../ConfigContext";
import * as ViewModels from "../../../Contracts/ViewModels";
import { JunoClient } from "../../../Juno/JunoClient";
import { updateUserContext, userContext } from "../../../UserContext";
import { userContext } from "../../../UserContext";
import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils";
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
import { useSidePanel } from "../../../hooks/useSidePanel";
@@ -33,7 +29,6 @@ import { useNotebook } from "../../Notebook/useNotebook";
import { OpenFullScreen } from "../../OpenFullScreen";
import { AddDatabasePanel } from "../../Panes/AddDatabasePanel/AddDatabasePanel";
import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane";
import { GitHubReposPanel } from "../../Panes/GitHubReposPanel/GitHubReposPanel";
import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane";
import { SettingsPane } from "../../Panes/SettingsPane/SettingsPane";
import { useDatabases } from "../../useDatabases";
@@ -80,57 +75,6 @@ export function createStaticCommandBarButtons(
}
}
if (useNotebook.getState().isNotebookEnabled) {
addDivider();
const notebookButtons: CommandButtonComponentProps[] = [];
const newNotebookButton = createNewNotebookButton(container);
newNotebookButton.children = [createNewNotebookButton(container), createuploadNotebookButton(container)];
notebookButtons.push(newNotebookButton);
if (container.notebookManager?.gitHubOAuthService) {
notebookButtons.push(createManageGitHubAccountButton(container));
}
if (useNotebook.getState().isPhoenixFeatures && configContext.isTerminalEnabled) {
notebookButtons.push(createOpenTerminalButton(container));
}
if (useNotebook.getState().isPhoenixNotebooks && selectedNodeState.isConnectedToContainer()) {
notebookButtons.push(createNotebookWorkspaceResetButton(container));
}
if (
(userContext.apiType === "Mongo" &&
useNotebook.getState().isShellEnabled &&
selectedNodeState.isDatabaseNodeOrNoneSelected()) ||
userContext.apiType === "Cassandra"
) {
notebookButtons.push(createDivider());
if (userContext.apiType === "Cassandra") {
notebookButtons.push(createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.Cassandra));
} else {
notebookButtons.push(createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.Mongo));
}
}
notebookButtons.forEach((btn) => {
if (btn.commandButtonLabel.indexOf("Cassandra") !== -1) {
if (!useNotebook.getState().isPhoenixFeatures) {
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.cassandraShellTemporarilyDownMsg);
}
} else if (btn.commandButtonLabel.indexOf("Mongo") !== -1) {
if (!useNotebook.getState().isPhoenixFeatures) {
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.mongoShellTemporarilyDownMsg);
}
} else if (btn.commandButtonLabel.indexOf("Open Terminal") !== -1) {
if (!useNotebook.getState().isPhoenixFeatures) {
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg);
}
} else if (!useNotebook.getState().isPhoenixNotebooks) {
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg);
}
buttons.push(btn);
});
}
if (!selectedNodeState.isDatabaseNodeOrNoneSelected()) {
const isQuerySupported = userContext.apiType === "SQL" || userContext.apiType === "Gremlin";
@@ -168,14 +112,6 @@ export function createStaticCommandBarButtons(
}
}
// Attempting to add region selection button here.
addDivider();
const [selectedRegion, setSelectedRegion] = React.useState(
userContext.databaseAccount.properties.locations[0].locationName,
);
const newReadRegionSelectionBtn = createReadRegionSelectionGroup(container, selectedRegion, setSelectedRegion);
buttons.push(newReadRegionSelectionBtn);
return buttons;
}
@@ -362,6 +298,7 @@ function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandB
id: "newQueryBtn",
iconSrc: AddSqlQueryIcon,
iconAlt: label,
keyboardShortcut: KeyboardShortcutAction.NEW_QUERY,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection);
@@ -377,6 +314,7 @@ function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandB
id: "newQueryBtn",
iconSrc: AddSqlQueryIcon,
iconAlt: label,
keyboardShortcut: KeyboardShortcutAction.NEW_QUERY,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection);
@@ -457,40 +395,6 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState)
return buttons;
}
function applyNotebooksTemporarilyDownStyle(buttonProps: CommandButtonComponentProps, tooltip: string): void {
if (!buttonProps.isDivider) {
buttonProps.disabled = true;
buttonProps.tooltipText = tooltip;
}
}
function createNewNotebookButton(container: Explorer): CommandButtonComponentProps {
const label = "New Notebook";
return {
id: "newNotebookBtn",
iconSrc: NewNotebookIcon,
iconAlt: label,
onCommandClick: () => container.onNewNotebookClicked(),
commandButtonLabel: label,
hasPopup: false,
disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(),
ariaLabel: label,
};
}
function createuploadNotebookButton(container: Explorer): CommandButtonComponentProps {
const label = "Upload to Notebook Server";
return {
iconSrc: NewNotebookIcon,
iconAlt: label,
onCommandClick: () => container.openUploadFilePanel(),
commandButtonLabel: label,
hasPopup: false,
disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(),
ariaLabel: label,
};
}
function createOpenQueryButton(container: Explorer): CommandButtonComponentProps {
const label = "Open Query";
return {
@@ -518,19 +422,6 @@ function createOpenQueryFromDiskButton(): CommandButtonComponentProps {
};
}
function createOpenTerminalButton(container: Explorer): CommandButtonComponentProps {
const label = "Open Terminal";
return {
iconSrc: CosmosTerminalIcon,
iconAlt: label,
onCommandClick: () => container.openNotebookTerminal(ViewModels.TerminalKind.Default),
commandButtonLabel: label,
hasPopup: false,
disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(),
ariaLabel: label,
};
}
function createOpenTerminalButtonByKind(
container: Explorer,
terminalKind: ViewModels.TerminalKind,
@@ -570,142 +461,6 @@ function createOpenTerminalButtonByKind(
};
}
function createNotebookWorkspaceResetButton(container: Explorer): CommandButtonComponentProps {
const label = "Reset Workspace";
return {
iconSrc: ResetWorkspaceIcon,
iconAlt: label,
onCommandClick: () => container.resetNotebookWorkspace(),
commandButtonLabel: label,
hasPopup: false,
disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(),
ariaLabel: label,
};
}
function createManageGitHubAccountButton(container: Explorer): CommandButtonComponentProps {
const connectedToGitHub: boolean = container.notebookManager?.gitHubOAuthService.isLoggedIn();
const label = connectedToGitHub ? "Manage GitHub settings" : "Connect to GitHub";
const junoClient = new JunoClient();
return {
iconSrc: GitHubIcon,
iconAlt: label,
onCommandClick: () => {
useSidePanel
.getState()
.openSidePanel(
label,
<GitHubReposPanel
explorer={container}
gitHubClientProp={container.notebookManager.gitHubClient}
junoClientProp={junoClient}
/>,
);
},
commandButtonLabel: label,
hasPopup: false,
disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(),
ariaLabel: label,
};
}
// function createReadRegionSelectionGroup(container: Explorer): CommandButtonComponentProps {
// const label = "Select a Read Region";
// return {
// iconAlt: label,
// commandButtonLabel: label,
// hasPopup: false,
// disabled: false,
// ariaLabel: label,
// onCommandClick: async () => {
// console.log(
// `CURRENT DOCUMENT ENDPOINT: ${JSON.stringify(userContext.databaseAccount.properties.documentEndpoint)}`,
// );
// userContext.databaseAccount.properties.readLocations.forEach((readLocation) => {
// console.log(`CURRENT READ ENDPOINT(S): ${JSON.stringify(readLocation)}`);
// });
// const updatedDatabaseAccount = {
// ...userContext.databaseAccount,
// properties: {
// ...userContext.databaseAccount.properties,
// documentEndpoint: "https://test-craig-nosql-periodic-eastus.documents.azure.com:443/",
// },
// };
// updateUserContext({
// databaseAccount: updatedDatabaseAccount,
// });
// },
// };
// }
function createReadRegionSelectionGroup(
container: Explorer,
selectedRegion: string,
setSelectedRegion: (region: string) => void,
): CommandButtonComponentProps {
const children = userContext.databaseAccount.properties.readLocations.map((readLocation) => ({
commandButtonLabel: readLocation.locationName,
onCommandClick: async () => {
const updatedDatabaseAccount = {
...userContext.databaseAccount,
properties: {
...userContext.databaseAccount.properties,
documentEndpoint: readLocation.documentEndpoint,
},
};
updateUserContext({
databaseAccount: updatedDatabaseAccount,
});
setSelectedRegion(readLocation.locationName);
},
hasPopup: false,
ariaLabel: `Select ${readLocation.locationName}`,
}));
const label = selectedRegion || "Select a Read Region";
return {
iconAlt: label,
commandButtonLabel: label,
onCommandClick: () => {},
hasPopup: true,
ariaLabel: label,
isDropdown: true,
children,
dropdownWidth: 100,
};
}
// function createAccountRegionSelectionButton(container: Explorer): JSX.Element {
// const [selectedEndpoint, setSelectedEndpoint] = useState(userContext.databaseAccount.properties.documentEndpoint);
// const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => {
// const endpoint = event.target.value as string;
// setSelectedEndpoint(endpoint);
// const updatedDatabaseAccount = {
// ...userContext.databaseAccount,
// properties: {
// ...userContext.databaseAccount.properties,
// documentEndpoint: endpoint,
// },
// };
// updateUserContext({
// databaseAccount: updatedDatabaseAccount,
// });
// };
// return (
// <Select
// value={selectedEndpoint}
// onChange={handleChange}
// displayEmpty
// >
// {userContext.databaseAccount.properties.readLocations.map((readLocation) => (
// <MenuItem value={readLocation}>{readLocation}</MenuItem>
// ))}
// </Select>
// );
// }
function createStaticCommandBarButtonsForResourceToken(
container: Explorer,
selectedNodeState: SelectedNodeState,

View File

@@ -1,6 +1,7 @@
import { FeedOptions } from "@azure/cosmos";
import {
Areas,
BackendApi,
ConnectionStatusType,
ContainerStatusType,
HttpStatusCodes,
@@ -30,6 +31,7 @@ import { Action } from "Shared/Telemetry/TelemetryConstants";
import { traceFailure, traceStart, traceSuccess } from "Shared/Telemetry/TelemetryProcessor";
import { userContext } from "UserContext";
import { getAuthorizationHeader } from "Utils/AuthorizationUtils";
import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils";
import { queryPagesUntilContentPresent } from "Utils/QueryUtils";
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
import { useTabs } from "hooks/useTabs";
@@ -80,7 +82,11 @@ export const isCopilotFeatureRegistered = async (subscriptionId: string): Promis
};
export const getCopilotEnabled = async (): Promise<boolean> => {
const url = `${configContext.BACKEND_ENDPOINT}/api/portalsettings/querycopilot`;
const backendEndpoint: string = useNewPortalBackendEndpoint(BackendApi.PortalSettings)
? configContext.PORTAL_BACKEND_ENDPOINT
: configContext.BACKEND_ENDPOINT;
const url = `${backendEndpoint}/api/portalsettings/querycopilot`;
const authorizationHeader: AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
const headers = { [authorizationHeader.header]: authorizationHeader.token };

View File

@@ -3,6 +3,7 @@ import * as ko from "knockout";
import Q from "q";
import { AuthType } from "../../AuthType";
import * as Constants from "../../Common/Constants";
import { CassandraProxyAPIs, CassandraProxyEndpoints } from "../../Common/Constants";
import { handleError } from "../../Common/ErrorHandlingUtils";
import * as HeadersUtility from "../../Common/HeadersUtility";
import { createDocument } from "../../Common/dataAccess/createDocument";
@@ -19,7 +20,6 @@ import Explorer from "../Explorer";
import * as TableConstants from "./Constants";
import * as Entities from "./Entities";
import * as TableEntityProcessor from "./TableEntityProcessor";
import { CassandraProxyAPIs } from "../../Common/Constants";
export interface CassandraTableKeys {
partitionKeys: CassandraTableKey[];
@@ -172,8 +172,9 @@ export class CassandraAPIDataClient extends TableDataClient {
deferred.resolve(entity);
},
(error) => {
handleError(error, "AddRowCassandra", `Error while adding new row to table ${collection.id()}`);
deferred.reject(error);
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
handleError(errorText, "AddRowCassandra", `Error while adding new row to table ${collection.id()}`);
deferred.reject(errorText);
},
)
.finally(clearInProgressMessage);
@@ -406,12 +407,13 @@ export class CassandraAPIDataClient extends TableDataClient {
deferred.resolve();
},
(error) => {
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
handleError(
error,
errorText,
"CreateKeyspaceCassandra",
`Error while creating a keyspace with query ${createKeyspaceQuery}`,
);
deferred.reject(error);
deferred.reject(errorText);
},
)
.finally(clearInProgressMessage);
@@ -444,8 +446,13 @@ export class CassandraAPIDataClient extends TableDataClient {
deferred.resolve();
},
(error) => {
handleError(error, "CreateTableCassandra", `Error while creating a table with query ${createTableQuery}`);
deferred.reject(error);
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
handleError(
errorText,
"CreateTableCassandra",
`Error while creating a table with query ${createTableQuery}`,
);
deferred.reject(errorText);
},
)
.finally(clearInProgressMessage);
@@ -458,7 +465,7 @@ export class CassandraAPIDataClient extends TableDataClient {
}
public getTableKeys(collection: ViewModels.Collection): Q.Promise<CassandraTableKeys> {
if (!this.useCassandraProxyEndpoint("getTableKeys")) {
if (!this.useCassandraProxyEndpoint("getKeys")) {
return this.getTableKeys_ToBeDeprecated(collection);
}
@@ -493,8 +500,9 @@ export class CassandraAPIDataClient extends TableDataClient {
deferred.resolve(data);
},
(error: any) => {
handleError(error, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
deferred.reject(error);
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
handleError(errorText, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
deferred.reject(errorText);
},
)
.done(clearInProgressMessage);
@@ -533,8 +541,9 @@ export class CassandraAPIDataClient extends TableDataClient {
deferred.resolve(data);
},
(error: any) => {
handleError(error, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
deferred.reject(error);
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
handleError(errorText, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
deferred.reject(errorText);
},
)
.done(clearInProgressMessage);
@@ -578,8 +587,9 @@ export class CassandraAPIDataClient extends TableDataClient {
deferred.resolve(data.columns);
},
(error: any) => {
handleError(error, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
deferred.reject(error);
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
handleError(errorText, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
deferred.reject(errorText);
},
)
.done(clearInProgressMessage);
@@ -618,8 +628,9 @@ export class CassandraAPIDataClient extends TableDataClient {
deferred.resolve(data.columns);
},
(error: any) => {
handleError(error, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
deferred.reject(error);
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
handleError(errorText, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
deferred.reject(errorText);
},
)
.done(clearInProgressMessage);
@@ -732,17 +743,23 @@ export class CassandraAPIDataClient extends TableDataClient {
}
private useCassandraProxyEndpoint(api: string): boolean {
const activeCassandraProxyEndpoints: string[] = [
CassandraProxyEndpoints.Development,
CassandraProxyEndpoints.Mpac,
CassandraProxyEndpoints.Prod,
];
let canAccessCassandraProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
if (userContext.databaseAccount.properties.ipRules?.length > 0) {
if (
configContext.CASSANDRA_PROXY_ENDPOINT !== CassandraProxyEndpoints.Development &&
userContext.databaseAccount.properties.ipRules?.length > 0
) {
canAccessCassandraProxy = canAccessCassandraProxy && configContext.CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED;
}
return (
canAccessCassandraProxy &&
configContext.NEW_CASSANDRA_APIS?.includes(api) &&
[Constants.CassandraProxyEndpoints.Development, Constants.CassandraProxyEndpoints.Mpac].includes(
configContext.CASSANDRA_PROXY_ENDPOINT,
)
activeCassandraProxyEndpoints.includes(configContext.CASSANDRA_PROXY_ENDPOINT)
);
}
}

View File

@@ -1,3 +1,5 @@
import { sendMessage } from "Common/MessageHandler";
import { MessageTypes } from "Contracts/MessageTypes";
import { CopilotProvider } from "Explorer/QueryCopilot/QueryCopilotContext";
import { userContext } from "UserContext";
import React from "react";
@@ -54,6 +56,11 @@ export class NewQueryTab extends TabsBase {
);
}
public onActivate(): void {
this.propagateTabInformation(MessageTypes.ActivateTab);
super.onActivate();
}
public onTabClick(): void {
useTabs.getState().activateTab(this);
this.iTabAccessor.onTabClickEvent();
@@ -61,6 +68,7 @@ export class NewQueryTab extends TabsBase {
public onCloseTabButtonClick(): void {
useTabs.getState().closeTab(this);
this.propagateTabInformation(MessageTypes.CloseTab);
if (this.iTabAccessor) {
this.iTabAccessor.onCloseClickEvent(true);
}
@@ -69,4 +77,15 @@ export class NewQueryTab extends TabsBase {
public getContainer(): Explorer {
return this.props.container;
}
private propagateTabInformation(type: MessageTypes): void {
sendMessage({
type,
data: {
kind: this.tabKind,
databaseId: this.collection?.databaseId,
collectionId: this.collection?.id?.(),
},
});
}
}

View File

@@ -10,6 +10,7 @@ import { OnExecuteQueryClick, QueryDocumentsPerPage } from "Explorer/QueryCopilo
import { QueryCopilotSidebar } from "Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar";
import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection";
import { useSelectedNode } from "Explorer/useSelectedNode";
import { KeyboardShortcutAction } from "KeyboardShortcuts";
import { QueryConstants } from "Shared/Constants";
import { LocalStorageUtility, StorageKey, getRUThreshold, ruThresholdEnabled } from "Shared/StorageUtility";
import { Action } from "Shared/Telemetry/TelemetryConstants";
@@ -134,7 +135,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
this.state = {
toggleState: ToggleState.Result,
sqlQueryEditorContent: props.queryText || "SELECT * FROM c",
sqlQueryEditorContent: props.isPreferredApiMongoDB ? "{}" : props.queryText || "SELECT * FROM c",
selectedContent: "",
queryResults: undefined,
error: "",
@@ -393,6 +394,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
buttons.push({
iconSrc: ExecuteQueryIcon,
iconAlt: label,
keyboardShortcut: KeyboardShortcutAction.EXECUTE_ITEM,
onCommandClick: this.props.isSampleCopilotActive
? () => OnExecuteQueryClick(this.props.copilotStore)
: this.onExecuteQueryClick,
@@ -496,13 +498,16 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
};
public onChangeContent(newContent: string): void {
// The copilot store's active query takes precedence over the local state,
// and we can't update both states in a single operation.
// So, we update the copilot store's state first, then update the local state.
if (this.state.copilotActive) {
this.props.copilotStore?.setQuery(newContent);
}
this.setState({
sqlQueryEditorContent: newContent,
queryCopilotGeneratedQuery: "",
});
if (this.state.copilotActive) {
this.props.copilotStore?.setQuery(newContent);
}
if (this.isPreferredApiMongoDB) {
if (newContent.length > 0) {
this.executeQueryButton = {
@@ -544,7 +549,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
useCommandBar.getState().setContextButtons(this.getTabsButtons());
}
public setEditorContent(): string {
public getEditorContent(): string {
if (this.isCopilotTabActive && this.state.queryCopilotGeneratedQuery) {
return this.state.queryCopilotGeneratedQuery;
}
@@ -601,7 +606,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
<div className="queryEditor" style={{ height: "100%" }}>
<EditorReact
language={"sql"}
content={this.setEditorContent()}
content={this.getEditorContent()}
isReadOnly={false}
wordWrap={"on"}
ariaLabel={"Editing Query"}

View File

@@ -324,13 +324,17 @@ const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): J
const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => {
const ipRules: IpRule[] = userContext.databaseAccount?.properties?.ipRules;
if ((userContext.apiType === "Mongo" || userContext.apiType === "Cassandra") && ipRules?.length) {
if (
((userContext.apiType === "Mongo" && configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Development) ||
(userContext.apiType === "Cassandra" &&
configContext.CASSANDRA_PROXY_ENDPOINT !== CassandraProxyEndpoints.Development)) &&
ipRules?.length
) {
const legacyPortalBackendIPs: string[] = PortalBackendIPs[configContext.BACKEND_ENDPOINT];
const ipAddressesFromIPRules: string[] = ipRules.map((ipRule) => ipRule.ipAddressOrRange);
const ipRulesIncludeLegacyPortalBackend: boolean =
ipAddressesFromIPRules.filter((ipAddressFromIPRule) => legacyPortalBackendIPs.includes(ipAddressFromIPRule))
?.length === legacyPortalBackendIPs.length;
const ipRulesIncludeLegacyPortalBackend: boolean = legacyPortalBackendIPs.every((legacyPortalBackendIP: string) =>
ipAddressesFromIPRules.includes(legacyPortalBackendIP),
);
if (!ipRulesIncludeLegacyPortalBackend) {
return false;
}
@@ -344,9 +348,9 @@ const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => {
? [...MongoProxyOutboundIPs[MongoProxyEndpoints.Mpac], ...MongoProxyOutboundIPs[MongoProxyEndpoints.Prod]]
: MongoProxyOutboundIPs[configContext.MONGO_PROXY_ENDPOINT];
const ipRulesIncludeMongoProxy: boolean =
ipAddressesFromIPRules.filter((ipAddressFromIPRule) => mongoProxyOutboundIPs.includes(ipAddressFromIPRule))
?.length === mongoProxyOutboundIPs.length;
const ipRulesIncludeMongoProxy: boolean = mongoProxyOutboundIPs.every((mongoProxyOutboundIP: string) =>
ipAddressesFromIPRules.includes(mongoProxyOutboundIP),
);
if (ipRulesIncludeMongoProxy) {
updateConfigContext({
@@ -368,9 +372,15 @@ const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => {
]
: CassandraProxyOutboundIPs[configContext.CASSANDRA_PROXY_ENDPOINT];
const ipRulesIncludeCassandraProxy: boolean =
ipAddressesFromIPRules.filter((ipAddressFromIPRule) => cassandraProxyOutboundIPs.includes(ipAddressFromIPRule))
?.length === cassandraProxyOutboundIPs.length;
const ipRulesIncludeCassandraProxy: boolean = cassandraProxyOutboundIPs.every(
(cassandraProxyOutboundIP: string) => ipAddressesFromIPRules.includes(cassandraProxyOutboundIP),
);
if (ipRulesIncludeCassandraProxy) {
updateConfigContext({
CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: true,
});
}
return !ipRulesIncludeCassandraProxy;
}

View File

@@ -373,11 +373,6 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
iconSrc: NewNotebookIcon,
onClick: () => container.onCreateDirectory(item, isGithubTree),
},
{
label: "New Notebook",
iconSrc: NewNotebookIcon,
onClick: () => container.onNewNotebookClicked(item, isGithubTree),
},
{
label: "Upload File",
iconSrc: NewNotebookIcon,
@@ -786,9 +781,6 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
<AccordionItemComponent title={"DATA"} isExpanded={!gitHubNotebooksContentRoot}>
<TreeComponent className="dataResourceTree" rootNode={dataRootNode} />
</AccordionItemComponent>
<AccordionItemComponent title={"NOTEBOOKS"}>
<TreeComponent className="notebookResourceTree" rootNode={buildNotebooksTree()} />
</AccordionItemComponent>
</AccordionComponent>
{/* {buildGalleryCallout()} */}

View File

@@ -800,11 +800,6 @@ export class ResourceTreeAdapter implements ReactAdapter {
iconSrc: NewNotebookIcon,
onClick: () => this.container.onCreateDirectory(item),
},
{
label: "New Notebook",
iconSrc: NewNotebookIcon,
onClick: () => this.container.onNewNotebookClicked(item),
},
{
label: "Upload File",
iconSrc: NewNotebookIcon,

150
src/KeyboardShortcuts.tsx Normal file
View File

@@ -0,0 +1,150 @@
import Mousetrap, { ExtendedKeyboardEvent } from "mousetrap";
import * as React from "react";
import create, { UseStore } from "zustand";
// Provides a system of Keyboard Shortcuts that can be contributed to by different parts of the application.
//
// The goals of this system are:
// * Shortcuts can be contributed from different parts of the application (e.g. the command bar, specified editor tabs, etc.)
// * Contributors may only provide some of their shortcuts, others may be out-of-scope for the current context.
// * Contributors don't have to add/remove handlers individually, they can use a declarative pattern to set all their handlers at once.
//
// So, in order to do that, we store handlers in a two-level hierarchy:
// 1. We store a mapping of contributors to their Contributions.
// 2. Each Contribution is a mapping of actions to their handlers.
//
// Thus, a Contributor sets all its handlers at once, replacing all handlers previously contributed by that Contributor.
// The system then merges all Contributions into a single set of handlers, with duplicate handlers being handled in the order that the Contributors are processed.
export type KeyboardShortcutHandler = (e: ExtendedKeyboardEvent, combo: string) => boolean | void;
/**
* Lists all the possible contributors to keyboard shortcut handlers.
*
* A "Contributor" is a part of the application that can contribute keyboard shortcut handlers.
* The contributor must set all it's keyboard shortcut handlers at once.
* This allows the contributor to easily replace all it's keyboard shortcuts at once.
*
* For example, the command bar adds/removes keyboard shortcut handlers based on the current context, using the existing logic that determines which buttons are shown.
* Isolating contributors like this allow the command bar to easily replace all it's keyboard shortcuts when the context changes without breaking keyboard shortcuts contributed by other parts of the application.
*/
export enum KeyboardShortcutContributor {
COMMAND_BAR = "COMMAND_BAR",
}
/**
* The order in which contributors are processed.
* This is important because the last contributor to set a handler for a given action will be the one that is used.
*/
const contributorOrder: KeyboardShortcutContributor[] = [
KeyboardShortcutContributor.COMMAND_BAR,
];
/**
* The possible actions that can be triggered by keyboard shortcuts.
*/
export enum KeyboardShortcutAction {
NEW_QUERY = "NEW_QUERY",
EXECUTE_ITEM = "EXECUTE_ITEM",
}
/**
* The default keyboard shortcuts for the application.
* This record maps each action to the keyboard shortcuts that trigger the action.
* Even if an action is specified here though, it will not be triggered unless a handler is set for it.
*/
const bindings: Record<KeyboardShortcutAction, string[]> = {
[KeyboardShortcutAction.NEW_QUERY]: ["ctrl+j"],
[KeyboardShortcutAction.EXECUTE_ITEM]: ["shift+enter"],
};
/**
* Represents all the handlers provided by a contributor.
*/
export type KeyboardShortcutContribution = Partial<Record<KeyboardShortcutAction, KeyboardShortcutHandler>>;
interface KeyboardShortcutState {
/**
* Collects all the contributions from different contributors.
*/
contributions: Partial<Record<KeyboardShortcutContributor, KeyboardShortcutContribution>>;
/**
* A merged set of all the handlers from all contributors.
*/
allHandlers: KeyboardShortcutContribution;
/**
* Sets the keyboard shortcut handlers for a given contributor.
*/
setHandlers: (contributor: KeyboardShortcutContributor, handlers: Partial<Record<KeyboardShortcutAction, KeyboardShortcutHandler>>) => void;
}
/**
* Gets the setHandlers function for a given contributor.
* @param contributor The contributor to get the setHandlers function for.
* @returns A function that sets the keyboard shortcut handlers for the given contributor.
*/
export const useKeyboardShortcutContributor = (contributor: KeyboardShortcutContributor) => {
const setHandlers = useKeyboardShortcutHandlers.getState().setHandlers;
return (handlers: Partial<Record<KeyboardShortcutAction, KeyboardShortcutHandler>>) => {
setHandlers(contributor, handlers);
};
}
const useKeyboardShortcutHandlers: UseStore<KeyboardShortcutState> = create((set, get) => ({
contributions: {},
allHandlers: {},
setHandlers: (contributor: KeyboardShortcutContributor, handlers: Partial<Record<KeyboardShortcutAction, KeyboardShortcutHandler>>) => {
const current = get();
// Update the list of contributions.
const newContributions = { ...current.contributions, [contributor]: handlers };
// Merge all the contributions into a single set of handlers.
const allHandlers: KeyboardShortcutContribution = {};
contributorOrder.forEach((contributor) => {
const contribution = newContributions[contributor];
if (contribution) {
(Object.keys(contribution) as KeyboardShortcutAction[]).forEach((action) => {
allHandlers[action] = contribution[action];
});
}
});
set({ contributions: newContributions, allHandlers })
}
}));
function createHandler(action: KeyboardShortcutAction): KeyboardShortcutHandler {
return (e, combo) => {
const handlers = useKeyboardShortcutHandlers.getState().allHandlers;
const handler = handlers[action];
if (handler) {
return handler(e, combo);
}
};
}
export function KeyboardShortcutRoot(props: React.HTMLProps<HTMLDivElement>) {
const ref = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
const m = new Mousetrap(ref.current);
const existingStopCallback = m.stopCallback;
m.stopCallback = (e, element, combo) => {
// Don't block mousetrap callback in the Monaco editor.
if (element.matches(".monaco-editor textarea")) {
return false;
}
return existingStopCallback(e, element, combo);
};
(Object.keys(bindings) as KeyboardShortcutAction[]).forEach((action) => {
m.bind(bindings[action], createHandler(action));
});
}, [ref]); // We only need to re-render the component when the ref changes.
return <div ref={ref} {...props}>
</div>;
}

View File

@@ -1,3 +1,6 @@
// Import this first, to ensure that the dev tools hook is copied before React is loaded.
import "./ReactDevTools";
// CSS Dependencies
import { initializeIcons, loadTheme } from "@fluentui/react";
import { QuickstartCarousel } from "Explorer/Quickstart/QuickstartCarousel";
@@ -18,6 +21,7 @@ import "../externals/jquery.typeahead.min.js";
// Image Dependencies
import { Platform } from "ConfigContext";
import { QueryCopilotCarousel } from "Explorer/QueryCopilot/CopilotCarousel";
import { KeyboardShortcutRoot } from "KeyboardShortcuts";
import "../images/CosmosDB_rgb_ui_lighttheme.ico";
import hdeConnectImage from "../images/HdeConnectCosmosDB.svg";
import "../images/favicon.ico";
@@ -88,7 +92,7 @@ const App: React.FunctionComponent = () => {
}
return (
<div className="flexContainer" aria-hidden="false">
<KeyboardShortcutRoot className="flexContainer" aria-hidden="false">
<div id="divExplorer" className="flexContainer hideOverflows">
<div id="freeTierTeachingBubble"> </div>
{/* Main Command Bar - Start */}
@@ -133,7 +137,7 @@ const App: React.FunctionComponent = () => {
{<SQLQuickstartTutorial />}
{<MongoQuickstartTutorial />}
{<QueryCopilotCarousel isOpen={isCopilotCarouselOpen} explorer={explorer} />}
</div>
</KeyboardShortcutRoot>
);
};

View File

@@ -1,6 +1,6 @@
import { useBoolean } from "@fluentui/react-hooks";
import { userContext } from "UserContext";
import { usePortalBackendEndpoint } from "Utils/EndpointUtils";
import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils";
import * as React from "react";
import ConnectImage from "../../../../images/HdeConnectCosmosDB.svg";
import ErrorImage from "../../../../images/error.svg";
@@ -19,7 +19,7 @@ interface Props {
}
export const fetchEncryptedToken = async (connectionString: string): Promise<string> => {
if (!usePortalBackendEndpoint(BackendApi.GenerateToken)) {
if (!useNewPortalBackendEndpoint(BackendApi.GenerateToken)) {
return await fetchEncryptedToken_ToBeDeprecated(connectionString);
}

View File

@@ -1,3 +1,7 @@
if (window.parent !== window) {
(window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__ = (window.parent as any).__REACT_DEVTOOLS_GLOBAL_HOOK__;
try {
(window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__ = (window.parent as any).__REACT_DEVTOOLS_GLOBAL_HOOK__;
} catch {
// No-op. We can throw here if the parent is not the same origin (such as in the Azure portal).
}
}

View File

@@ -145,8 +145,22 @@ export const allowedJunoOrigins: ReadonlyArray<string> = [
export const allowedNotebookServerUrls: ReadonlyArray<string> = [];
export function usePortalBackendEndpoint(backendApi: BackendApi): boolean {
const activePortalBackendEndpoints: string[] = [PortalBackendEndpoints.Development];
const activeBackendApi: boolean = configContext.NEW_BACKEND_APIS?.includes(backendApi) || false;
return activeBackendApi && activePortalBackendEndpoints.includes(configContext.PORTAL_BACKEND_ENDPOINT as string);
//
// Temporary function to determine if a portal backend API is supported by the
// new backend in this environment.
//
// TODO: Remove this function once new backend migration is completed for all environments.
//
export function useNewPortalBackendEndpoint(backendApi: string): boolean {
// This maps backend APIs to the environments supported by the new backend.
const newBackendApiEnvironmentMap: { [key: string]: string[] } = {
[BackendApi.GenerateToken]: [PortalBackendEndpoints.Development],
[BackendApi.PortalSettings]: [PortalBackendEndpoints.Development, PortalBackendEndpoints.Mpac],
};
if (!newBackendApiEnvironmentMap[backendApi] || !configContext.PORTAL_BACKEND_ENDPOINT) {
return false;
}
return newBackendApiEnvironmentMap[backendApi].includes(configContext.PORTAL_BACKEND_ENDPOINT);
}

View File

@@ -497,6 +497,7 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT),
MONGO_PROXY_ENDPOINT: inputs.mongoProxyEndpoint,
CASSANDRA_PROXY_ENDPOINT: inputs.cassandraProxyEndpoint,
PORTAL_BACKEND_ENDPOINT: inputs.portalBackendEndpoint,
});
updateUserContext({

View File

@@ -1,15 +1,18 @@
import { jest } from "@jest/globals";
import "expect-playwright";
import { generateUniqueName } from "../utils/shared";
import { generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
import { waitForExplorer } from "../utils/waitForExplorer";
jest.setTimeout(120000);
test("Cassandra keyspace and table CRUD", async () => {
const keyspaceId = generateUniqueName("keyspace");
const tableId = generateUniqueName("table");
// We can't retrieve AZ CLI credentials from the browser so we get them here.
const token = await getAzureCLICredentialsToken();
page.setDefaultTimeout(50000);
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-cassandra-runner");
await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-cassandra-runner&token=${token}`);
await page.waitForSelector("iframe");
const explorer = await waitForExplorer();

View File

@@ -1,15 +1,18 @@
import { jest } from "@jest/globals";
import "expect-playwright";
import { generateDatabaseNameWithTimestamp, generateUniqueName } from "../utils/shared";
import { generateDatabaseNameWithTimestamp, generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
import { waitForExplorer } from "../utils/waitForExplorer";
jest.setTimeout(240000);
test("Graph CRUD", async () => {
const databaseId = generateDatabaseNameWithTimestamp();
const containerId = generateUniqueName("container");
// We can't retrieve AZ CLI credentials from the browser so we get them here.
const token = await getAzureCLICredentialsToken();
page.setDefaultTimeout(50000);
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-gremlin-runner");
await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-gremlin-runner&token=${token}`);
const explorer = await waitForExplorer();
// Create new database and graph

View File

@@ -1,15 +1,18 @@
import { jest } from "@jest/globals";
import "expect-playwright";
import { generateDatabaseNameWithTimestamp, generateUniqueName } from "../utils/shared";
import { generateDatabaseNameWithTimestamp, generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
import { waitForExplorer } from "../utils/waitForExplorer";
jest.setTimeout(240000);
test("Mongo CRUD", async () => {
const databaseId = generateDatabaseNameWithTimestamp();
const containerId = generateUniqueName("container");
// We can't retrieve AZ CLI credentials from the browser so we get them here.
const token = await getAzureCLICredentialsToken();
page.setDefaultTimeout(50000);
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-mongo-runner");
await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-mongo-runner&token=${token}`);
const explorer = await waitForExplorer();
// Create new database and collection

View File

@@ -1,15 +1,18 @@
import { jest } from "@jest/globals";
import "expect-playwright";
import { generateDatabaseNameWithTimestamp, generateUniqueName } from "../utils/shared";
import { generateDatabaseNameWithTimestamp, generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
import { waitForExplorer } from "../utils/waitForExplorer";
jest.setTimeout(240000);
test("Mongo CRUD", async () => {
const databaseId = generateDatabaseNameWithTimestamp();
const containerId = generateUniqueName("container");
// We can't retrieve AZ CLI credentials from the browser so we get them here.
const token = await getAzureCLICredentialsToken();
page.setDefaultTimeout(50000);
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-mongo32-runner");
await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-mongo32-runner&token=${token}`);
const explorer = await waitForExplorer();
// Create new database and collection

View File

@@ -1,5 +1,10 @@
import { getAzureCLICredentialsToken } from "../utils/shared";
test("Self Serve", async () => {
await page.goto("https://localhost:1234/testExplorer.html?iframeSrc=selfServe.html");
// We can't retrieve AZ CLI credentials from the browser so we get them here.
const token = await getAzureCLICredentialsToken();
await page.goto(`https://localhost:1234/testExplorer.html?iframeSrc=selfServe.html&token=${token}`);
const handle = await page.waitForSelector("iframe");
const frame = await handle.contentFrame();

View File

@@ -1,15 +1,18 @@
import { jest } from "@jest/globals";
import "expect-playwright";
import { generateUniqueName } from "../utils/shared";
import { generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
import { waitForExplorer } from "../utils/waitForExplorer";
jest.setTimeout(120000);
test("SQL CRUD", async () => {
const databaseId = generateUniqueName("db");
const containerId = generateUniqueName("container");
// We can't retrieve AZ CLI credentials from the browser so we get them here.
const token = await getAzureCLICredentialsToken();
page.setDefaultTimeout(50000);
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-sql-runner-west-us");
await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-sql-runner-west-us&token=${token}`);
const explorer = await waitForExplorer();
await explorer.click('[data-test="New Container"]');

View File

@@ -1,19 +1,15 @@
import { CosmosDBManagementClient } from "@azure/arm-cosmosdb";
import { CosmosClient, PermissionMode } from "@azure/cosmos";
import * as msRestNodeAuth from "@azure/ms-rest-nodeauth";
import { jest } from "@jest/globals";
import "expect-playwright";
import { generateUniqueName } from "../utils/shared";
import { generateUniqueName, getAzureCLICredentials } from "../utils/shared";
jest.setTimeout(120000);
const clientId = "fd8753b0-0707-4e32-84e9-2532af865fb4";
const secret = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET"];
const tenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
const subscriptionId = "69e02f2d-f059-4409-9eac-97e8a276ae2c";
const subscriptionId = process.env["AZURE_SUBSCRIPTION_ID"] ?? "";
const resourceGroupName = "runners";
test("Resource token", async () => {
const credentials = await msRestNodeAuth.loginWithServicePrincipalSecret(clientId, secret, tenantId);
const credentials = await getAzureCLICredentials();
const armClient = new CosmosDBManagementClient(credentials, subscriptionId);
const account = await armClient.databaseAccounts.get(resourceGroupName, "portal-sql-runner-west-us");
const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, "portal-sql-runner-west-us");

View File

@@ -1,15 +1,17 @@
import { jest } from "@jest/globals";
import "expect-playwright";
import { generateUniqueName } from "../utils/shared";
import { generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
import { waitForExplorer } from "../utils/waitForExplorer";
jest.setTimeout(120000);
test("Tables CRUD", async () => {
const tableId = generateUniqueName("table");
// We can't retrieve AZ CLI credentials from the browser so we get them here.
const token = await getAzureCLICredentialsToken();
page.setDefaultTimeout(50000);
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-tables-runner");
await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-tables-runner&token=${token}`);
const explorer = await waitForExplorer();
await page.waitForSelector('text="Querying databases"', { state: "detached" });

View File

@@ -1,5 +1,4 @@
/* eslint-disable no-console */
import { ClientSecretCredential } from "@azure/identity";
import "../../less/hostedexplorer.less";
import { DataExplorerInputsFrame } from "../../src/Contracts/ViewModels";
import { updateUserContext } from "../../src/UserContext";
@@ -11,29 +10,13 @@ const urlSearchParams = new URLSearchParams(window.location.search);
const accountName = urlSearchParams.get("accountName") || "portal-sql-runner-west-us";
const selfServeType = urlSearchParams.get("selfServeType") || "example";
const iframeSrc = urlSearchParams.get("iframeSrc") || "explorer.html?platform=Portal&disablePortalInitCache";
if (!process.env.AZURE_CLIENT_SECRET) {
throw new Error(
"process.env.AZURE_CLIENT_SECRET was not set! Set it in your .env file and restart webpack dev server",
);
}
// Azure SDK clients accept the credential as a parameter
const credentials = new ClientSecretCredential(
process.env.AZURE_TENANT_ID,
process.env.AZURE_CLIENT_ID,
process.env.AZURE_CLIENT_SECRET,
{
authorityHost: "https://localhost:1234",
},
);
const token = urlSearchParams.get("token");
console.log("Resource Group:", resourceGroup);
console.log("Subcription: ", subscriptionId);
console.log("Account Name: ", accountName);
const initTestExplorer = async (): Promise<void> => {
const { token } = await credentials.getToken("https://management.azure.com//.default");
updateUserContext({
authorizationToken: `bearer ${token}`,
});
@@ -52,6 +35,9 @@ const initTestExplorer = async (): Promise<void> => {
dnsSuffix: "documents.azure.com",
serverId: "prod1",
extensionEndpoint: "/proxy",
portalBackendEndpoint: "https://cdb-ms-mpac-pbe.cosmos.azure.com",
mongoProxyEndpoint: "https://cdb-ms-mpac-mp.cosmos.azure.com",
cassandraProxyEndpoint: "https://cdb-ms-mpac-cp.cosmos.azure.com",
subscriptionType: 3,
quotaId: "Internal_2014-09-01",
isTryCosmosDBSubscription: false,

View File

@@ -1,3 +1,4 @@
import { AzureCliCredentials } from "@azure/ms-rest-nodeauth";
import crypto from "crypto";
export function generateUniqueName(baseName = "", length = 4): string {
@@ -7,3 +8,13 @@ export function generateUniqueName(baseName = "", length = 4): string {
export function generateDatabaseNameWithTimestamp(baseName = "db", length = 1): string {
return `${baseName}${crypto.randomBytes(length).toString("hex")}-${Date.now()}`;
}
export async function getAzureCLICredentials(): Promise<AzureCliCredentials> {
return await AzureCliCredentials.create();
}
export async function getAzureCLICredentialsToken(): Promise<string> {
const credentials = await getAzureCLICredentials();
const token = (await credentials.getToken()).accessToken;
return token;
}

View File

@@ -2,10 +2,7 @@ const msRestNodeAuth = require("@azure/ms-rest-nodeauth");
const { CosmosDBManagementClient } = require("@azure/arm-cosmosdb");
const ms = require("ms");
const clientId = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_ID"];
const secret = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET"];
const tenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
const subscriptionId = "69e02f2d-f059-4409-9eac-97e8a276ae2c";
const subscriptionId = process.env["AZURE_SUBSCRIPTION_ID"];
const resourceGroupName = "runners";
const thirtyMinutesAgo = new Date(Date.now() - 1000 * 60 * 30).getTime();
@@ -19,7 +16,7 @@ function friendlyTime(date) {
}
async function main() {
const credentials = await msRestNodeAuth.loginWithServicePrincipalSecret(clientId, secret, tenantId);
const credentials = await msRestNodeAuth.AzureCliCredentials.create();
const client = new CosmosDBManagementClient(credentials, subscriptionId);
const accounts = await client.databaseAccounts.list(resourceGroupName);
for (const account of accounts) {
@@ -38,7 +35,7 @@ async function main() {
} else if (account.capabilities.find((c) => c.name === "EnableCassandra")) {
const cassandraDatabases = await client.cassandraResources.listCassandraKeyspaces(
resourceGroupName,
account.name
account.name,
);
for (const database of cassandraDatabases) {
const timestamp = Number(database.resource._ts) * 1000;

View File

@@ -302,7 +302,6 @@ module.exports = function (_env = {}, argv = {}) {
pathRewrite: { "^/proxy": "" },
router: (req) => {
let newTarget = req.headers["x-ms-proxy-target"];
console.log(`Proxy path used. New target is: ${newTarget}`);
return newTarget;
},
},