mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-26 20:31:33 +00:00
Compare commits
3 Commits
missing_pk
...
3556812
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a569dcf32d | ||
|
|
20151c155b | ||
|
|
80775464cd |
@@ -248,10 +248,6 @@
|
|||||||
outline: 1px dashed @FocusColor;
|
outline: 1px dashed @FocusColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.focusedBorder() {
|
|
||||||
border: 1px dashed @FocusColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/************************************************************************************************
|
/************************************************************************************************
|
||||||
Common Toggle Switch
|
Common Toggle Switch
|
||||||
*************************************************************************************************/
|
*************************************************************************************************/
|
||||||
|
|||||||
@@ -1914,20 +1914,13 @@ input::-webkit-calendar-picker-indicator::after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-tabs-margin {
|
.nav-tabs-margin {
|
||||||
|
height: 32px;
|
||||||
background-color: #f2f2f2;
|
background-color: #f2f2f2;
|
||||||
|
|
||||||
.nav-tabs {
|
.nav-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-bottom: -0.5px;
|
|
||||||
|
|
||||||
li {
|
|
||||||
// Override the bootstrap defaults here to align with our layout constants.
|
|
||||||
margin-bottom: 0px;
|
|
||||||
height: 32px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
51
package-lock.json
generated
51
package-lock.json
generated
@@ -86,7 +86,7 @@
|
|||||||
"mkdirp": "1.0.4",
|
"mkdirp": "1.0.4",
|
||||||
"monaco-editor": "0.44.0",
|
"monaco-editor": "0.44.0",
|
||||||
"ms": "2.1.3",
|
"ms": "2.1.3",
|
||||||
"p-retry": "6.2.1",
|
"p-retry": "4.6.2",
|
||||||
"patch-package": "8.0.0",
|
"patch-package": "8.0.0",
|
||||||
"plotly.js-cartesian-dist-min": "1.52.3",
|
"plotly.js-cartesian-dist-min": "1.52.3",
|
||||||
"post-robot": "10.0.42",
|
"post-robot": "10.0.42",
|
||||||
@@ -12662,9 +12662,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/retry": {
|
"node_modules/@types/retry": {
|
||||||
"version": "0.12.2",
|
"version": "0.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz",
|
|
||||||
"integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==",
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/sanitize-html": {
|
"node_modules/@types/sanitize-html": {
|
||||||
@@ -21801,18 +21799,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/is-network-error": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/is-number": {
|
"node_modules/is-number": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -30257,20 +30243,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/p-retry": {
|
"node_modules/p-retry": {
|
||||||
"version": "6.2.1",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz",
|
|
||||||
"integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==",
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/retry": "0.12.2",
|
"@types/retry": "0.12.0",
|
||||||
"is-network-error": "^1.0.0",
|
|
||||||
"retry": "^0.13.1"
|
"retry": "^0.13.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.17"
|
"node": ">=8"
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/p-try": {
|
"node_modules/p-try": {
|
||||||
@@ -36017,13 +35997,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webpack-dev-server/node_modules/@types/retry": {
|
|
||||||
"version": "0.12.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
|
|
||||||
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/webpack-dev-server/node_modules/ajv": {
|
"node_modules/webpack-dev-server/node_modules/ajv": {
|
||||||
"version": "8.12.0",
|
"version": "8.12.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -36071,20 +36044,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webpack-dev-server/node_modules/p-retry": {
|
|
||||||
"version": "4.6.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
|
|
||||||
"integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/retry": "0.12.0",
|
|
||||||
"retry": "^0.13.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/webpack-dev-server/node_modules/rimraf": {
|
"node_modules/webpack-dev-server/node_modules/rimraf": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
|||||||
@@ -81,7 +81,7 @@
|
|||||||
"mkdirp": "1.0.4",
|
"mkdirp": "1.0.4",
|
||||||
"monaco-editor": "0.44.0",
|
"monaco-editor": "0.44.0",
|
||||||
"ms": "2.1.3",
|
"ms": "2.1.3",
|
||||||
"p-retry": "6.2.1",
|
"p-retry": "4.6.2",
|
||||||
"patch-package": "8.0.0",
|
"patch-package": "8.0.0",
|
||||||
"plotly.js-cartesian-dist-min": "1.52.3",
|
"plotly.js-cartesian-dist-min": "1.52.3",
|
||||||
"post-robot": "10.0.42",
|
"post-robot": "10.0.42",
|
||||||
|
|||||||
37051
preview/package-lock.json
generated
37051
preview/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -530,10 +530,6 @@ export class ariaLabelForLearnMoreLink {
|
|||||||
public static readonly AzureSynapseLink = "Learn more about Azure Synapse Link.";
|
public static readonly AzureSynapseLink = "Learn more about Azure Synapse Link.";
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FeedbackLabels {
|
|
||||||
public static readonly provideFeedback: string = "Provide feedback";
|
|
||||||
}
|
|
||||||
|
|
||||||
export const QueryCopilotSampleDatabaseId = "CopilotSampleDB";
|
export const QueryCopilotSampleDatabaseId = "CopilotSampleDB";
|
||||||
export const QueryCopilotSampleContainerId = "SampleContainer";
|
export const QueryCopilotSampleContainerId = "SampleContainer";
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import * as Cosmos from "@azure/cosmos";
|
import * as Cosmos from "@azure/cosmos";
|
||||||
import { getAuthorizationTokenUsingResourceTokens } from "Common/getAuthorizationTokenUsingResourceTokens";
|
import { getAuthorizationTokenUsingResourceTokens } from "Common/getAuthorizationTokenUsingResourceTokens";
|
||||||
import { CosmosDbArtifactType } from "Contracts/FabricMessagesContract";
|
|
||||||
import { AuthorizationToken } from "Contracts/FabricMessageTypes";
|
import { AuthorizationToken } from "Contracts/FabricMessageTypes";
|
||||||
import { checkDatabaseResourceTokensValidity, isFabricMirroredKey } from "Platform/Fabric/FabricUtil";
|
import { checkDatabaseResourceTokensValidity } from "Platform/Fabric/FabricUtil";
|
||||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import { PriorityLevel } from "../Common/Constants";
|
import { PriorityLevel } from "../Common/Constants";
|
||||||
import * as Logger from "../Common/Logger";
|
import * as Logger from "../Common/Logger";
|
||||||
import { Platform, configContext } from "../ConfigContext";
|
import { Platform, configContext } from "../ConfigContext";
|
||||||
import { FabricArtifactInfo, updateUserContext, userContext } from "../UserContext";
|
import { updateUserContext, userContext } from "../UserContext";
|
||||||
import { isDataplaneRbacSupported } from "../Utils/APITypeUtils";
|
|
||||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||||
import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils";
|
import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils";
|
||||||
import { EmulatorMasterKey, HttpHeaders } from "./Constants";
|
import { EmulatorMasterKey, HttpHeaders } from "./Constants";
|
||||||
@@ -20,7 +18,7 @@ const _global = typeof self === "undefined" ? window : self;
|
|||||||
export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
||||||
const { verb, resourceId, resourceType, headers } = requestInfo;
|
const { verb, resourceId, resourceType, headers } = requestInfo;
|
||||||
|
|
||||||
const dataPlaneRBACOptionEnabled = userContext.dataPlaneRbacEnabled && isDataplaneRbacSupported(userContext.apiType);
|
const dataPlaneRBACOptionEnabled = userContext.dataPlaneRbacEnabled && userContext.apiType === "SQL";
|
||||||
if (userContext.features.enableAadDataPlane || dataPlaneRBACOptionEnabled) {
|
if (userContext.features.enableAadDataPlane || dataPlaneRBACOptionEnabled) {
|
||||||
Logger.logInfo(
|
Logger.logInfo(
|
||||||
`AAD Data Plane Feature flag set to ${userContext.features.enableAadDataPlane} for account with disable local auth ${userContext.databaseAccount.properties.disableLocalAuth} `,
|
`AAD Data Plane Feature flag set to ${userContext.features.enableAadDataPlane} for account with disable local auth ${userContext.databaseAccount.properties.disableLocalAuth} `,
|
||||||
@@ -43,7 +41,7 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
|||||||
return decodeURIComponent(headers.authorization);
|
return decodeURIComponent(headers.authorization);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFabricMirroredKey()) {
|
if (configContext.platform === Platform.Fabric) {
|
||||||
switch (requestInfo.resourceType) {
|
switch (requestInfo.resourceType) {
|
||||||
case Cosmos.ResourceType.conflicts:
|
case Cosmos.ResourceType.conflicts:
|
||||||
case Cosmos.ResourceType.container:
|
case Cosmos.ResourceType.container:
|
||||||
@@ -55,13 +53,8 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
|||||||
// User resource tokens
|
// User resource tokens
|
||||||
// TODO userContext.fabricContext.databaseConnectionInfo can be undefined
|
// TODO userContext.fabricContext.databaseConnectionInfo can be undefined
|
||||||
headers[HttpHeaders.msDate] = new Date().toUTCString();
|
headers[HttpHeaders.msDate] = new Date().toUTCString();
|
||||||
const resourceTokens = (
|
const resourceTokens = userContext.fabricContext.databaseConnectionInfo.resourceTokens;
|
||||||
userContext.fabricContext.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY]
|
checkDatabaseResourceTokensValidity(userContext.fabricContext.databaseConnectionInfo.resourceTokensTimestamp);
|
||||||
).resourceTokenInfo.resourceTokens;
|
|
||||||
checkDatabaseResourceTokensValidity(
|
|
||||||
(userContext.fabricContext.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY])
|
|
||||||
.resourceTokenInfo.resourceTokensTimestamp,
|
|
||||||
);
|
|
||||||
return getAuthorizationTokenUsingResourceTokens(resourceTokens, requestInfo.path, requestInfo.resourceId);
|
return getAuthorizationTokenUsingResourceTokens(resourceTokens, requestInfo.path, requestInfo.resourceId);
|
||||||
|
|
||||||
case Cosmos.ResourceType.none:
|
case Cosmos.ResourceType.none:
|
||||||
@@ -72,9 +65,7 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
|||||||
// For now, these operations aren't used, so fetching the authorization token is commented out.
|
// For now, these operations aren't used, so fetching the authorization token is commented out.
|
||||||
// This provider must return a real token to pass validation by the client, so we return the cached resource token
|
// This provider must return a real token to pass validation by the client, so we return the cached resource token
|
||||||
// (which is a valid token, but won't work for these operations).
|
// (which is a valid token, but won't work for these operations).
|
||||||
const resourceTokens2 = (
|
const resourceTokens2 = userContext.fabricContext.databaseConnectionInfo.resourceTokens;
|
||||||
userContext.fabricContext.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY]
|
|
||||||
).resourceTokenInfo.resourceTokens;
|
|
||||||
return getAuthorizationTokenUsingResourceTokens(resourceTokens2, requestInfo.path, requestInfo.resourceId);
|
return getAuthorizationTokenUsingResourceTokens(resourceTokens2, requestInfo.path, requestInfo.resourceId);
|
||||||
|
|
||||||
/* ************** TODO: Uncomment this code if we need to support these operations **************
|
/* ************** TODO: Uncomment this code if we need to support these operations **************
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { isFabric } from "Platform/Fabric/FabricUtil";
|
import { Platform, configContext } from "../ConfigContext";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
export const StyleConstants = require("less-vars-loader!../../less/Common/Constants.less");
|
export const StyleConstants = require("less-vars-loader!../../less/Common/Constants.less");
|
||||||
|
|
||||||
export function updateStyles(): void {
|
export function updateStyles(): void {
|
||||||
if (isFabric()) {
|
if (configContext.platform === Platform.Fabric) {
|
||||||
StyleConstants.AccentMediumHigh = StyleConstants.FabricAccentMediumHigh;
|
StyleConstants.AccentMediumHigh = StyleConstants.FabricAccentMediumHigh;
|
||||||
StyleConstants.AccentMedium = StyleConstants.FabricAccentMedium;
|
StyleConstants.AccentMedium = StyleConstants.FabricAccentMedium;
|
||||||
StyleConstants.AccentLight = StyleConstants.FabricAccentLight;
|
StyleConstants.AccentLight = StyleConstants.FabricAccentLight;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { ContainerRequest, ContainerResponse, DatabaseRequest, DatabaseResponse, RequestOptions } from "@azure/cosmos";
|
import { ContainerRequest, ContainerResponse, DatabaseRequest, DatabaseResponse, RequestOptions } from "@azure/cosmos";
|
||||||
import { isFabricNative } from "Platform/Fabric/FabricUtil";
|
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { useDatabases } from "../../Explorer/useDatabases";
|
import { useDatabases } from "../../Explorer/useDatabases";
|
||||||
@@ -25,7 +24,7 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
|
|||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
let collection: DataModels.Collection;
|
let collection: DataModels.Collection;
|
||||||
if (!isFabricNative() && userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations) {
|
if (userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations) {
|
||||||
if (params.createNewDatabase) {
|
if (params.createNewDatabase) {
|
||||||
const createDatabaseParams: DataModels.CreateDatabaseParams = {
|
const createDatabaseParams: DataModels.CreateDatabaseParams = {
|
||||||
autoPilotMaxThroughput: params.autoPilotMaxThroughput,
|
autoPilotMaxThroughput: params.autoPilotMaxThroughput,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { isFabric } from "Platform/Fabric/FabricUtil";
|
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { deleteCassandraTable } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
import { deleteCassandraTable } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
||||||
@@ -13,7 +12,7 @@ import { handleError } from "../ErrorHandlingUtils";
|
|||||||
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
|
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
|
||||||
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
|
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
|
||||||
try {
|
try {
|
||||||
if (userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations && !isFabric()) {
|
if (userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations) {
|
||||||
await deleteCollectionWithARM(databaseId, collectionId);
|
await deleteCollectionWithARM(databaseId, collectionId);
|
||||||
} else {
|
} else {
|
||||||
await client().database(databaseId).container(collectionId).delete();
|
await client().database(databaseId).container(collectionId).delete();
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { ContainerResponse } from "@azure/cosmos";
|
import { ContainerResponse } from "@azure/cosmos";
|
||||||
import { Queries } from "Common/Constants";
|
import { Queries } from "Common/Constants";
|
||||||
import { CosmosDbArtifactType } from "Contracts/FabricMessagesContract";
|
import { Platform, configContext } from "ConfigContext";
|
||||||
import { isFabric, isFabricMirroredKey } from "Platform/Fabric/FabricUtil";
|
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { FabricArtifactInfo, userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { listCassandraTables } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
import { listCassandraTables } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
||||||
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
|
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
|
||||||
@@ -17,13 +16,15 @@ import { handleError } from "../ErrorHandlingUtils";
|
|||||||
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
|
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
|
||||||
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
|
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
|
||||||
|
|
||||||
if (isFabricMirroredKey() && userContext.fabricContext?.databaseName === databaseId) {
|
if (
|
||||||
|
configContext.platform === Platform.Fabric &&
|
||||||
|
userContext.fabricContext &&
|
||||||
|
userContext.fabricContext.databaseConnectionInfo.databaseId === databaseId
|
||||||
|
) {
|
||||||
const collections: DataModels.Collection[] = [];
|
const collections: DataModels.Collection[] = [];
|
||||||
const promises: Promise<ContainerResponse>[] = [];
|
const promises: Promise<ContainerResponse>[] = [];
|
||||||
|
|
||||||
for (const collectionResourceId in (
|
for (const collectionResourceId in userContext.fabricContext.databaseConnectionInfo.resourceTokens) {
|
||||||
userContext.fabricContext.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY]
|
|
||||||
).resourceTokenInfo.resourceTokens) {
|
|
||||||
// Dictionary key looks like this: dbs/SampleDB/colls/Container
|
// Dictionary key looks like this: dbs/SampleDB/colls/Container
|
||||||
const resourceIdObj = collectionResourceId.split("/");
|
const resourceIdObj = collectionResourceId.split("/");
|
||||||
const tokenDatabaseId = resourceIdObj[1];
|
const tokenDatabaseId = resourceIdObj[1];
|
||||||
@@ -55,8 +56,7 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
|
|||||||
if (
|
if (
|
||||||
userContext.authType === AuthType.AAD &&
|
userContext.authType === AuthType.AAD &&
|
||||||
!userContext.features.enableSDKoperations &&
|
!userContext.features.enableSDKoperations &&
|
||||||
userContext.apiType !== "Tables" &&
|
userContext.apiType !== "Tables"
|
||||||
!isFabric()
|
|
||||||
) {
|
) {
|
||||||
return await readCollectionsWithARM(databaseId);
|
return await readCollectionsWithARM(databaseId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { isFabric, isFabricMirroredKey, isFabricNative } from "Platform/Fabric/FabricUtil";
|
import { Platform, configContext } from "ConfigContext";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { Offer, ReadDatabaseOfferParams } from "../../Contracts/DataModels";
|
import { Offer, ReadDatabaseOfferParams } from "../../Contracts/DataModels";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
@@ -11,9 +11,8 @@ import { handleError } from "../ErrorHandlingUtils";
|
|||||||
import { readOfferWithSDK } from "./readOfferWithSDK";
|
import { readOfferWithSDK } from "./readOfferWithSDK";
|
||||||
|
|
||||||
export const readDatabaseOffer = async (params: ReadDatabaseOfferParams): Promise<Offer> => {
|
export const readDatabaseOffer = async (params: ReadDatabaseOfferParams): Promise<Offer> => {
|
||||||
if (isFabricMirroredKey() || isFabricNative()) {
|
if (configContext.platform === Platform.Fabric) {
|
||||||
// For Fabric Mirroring, it is slow, because it requests the token and we don't need it.
|
// TODO This works, but is very slow, because it requests the token, so we skip for now
|
||||||
// For Fabric Native, it is not supported.
|
|
||||||
console.error("Skiping readDatabaseOffer for Fabric");
|
console.error("Skiping readDatabaseOffer for Fabric");
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -24,8 +23,7 @@ export const readDatabaseOffer = async (params: ReadDatabaseOfferParams): Promis
|
|||||||
if (
|
if (
|
||||||
userContext.authType === AuthType.AAD &&
|
userContext.authType === AuthType.AAD &&
|
||||||
!userContext.features.enableSDKoperations &&
|
!userContext.features.enableSDKoperations &&
|
||||||
userContext.apiType !== "Tables" &&
|
userContext.apiType !== "Tables"
|
||||||
!isFabric()
|
|
||||||
) {
|
) {
|
||||||
return await readDatabaseOfferWithARM(params.databaseId);
|
return await readDatabaseOfferWithARM(params.databaseId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { CosmosDbArtifactType } from "Contracts/FabricMessagesContract";
|
import { Platform, configContext } from "ConfigContext";
|
||||||
import { isFabric, isFabricMirroredKey, isFabricNative } from "Platform/Fabric/FabricUtil";
|
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { FabricArtifactInfo, userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
||||||
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
|
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
|
||||||
@@ -15,13 +14,8 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
|
|||||||
let databases: DataModels.Database[];
|
let databases: DataModels.Database[];
|
||||||
const clearMessage = logConsoleProgress(`Querying databases`);
|
const clearMessage = logConsoleProgress(`Querying databases`);
|
||||||
|
|
||||||
if (
|
if (configContext.platform === Platform.Fabric && userContext.fabricContext?.databaseConnectionInfo.resourceTokens) {
|
||||||
isFabricMirroredKey() &&
|
const tokensData = userContext.fabricContext.databaseConnectionInfo;
|
||||||
(userContext.fabricContext?.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY]).resourceTokenInfo
|
|
||||||
.resourceTokens
|
|
||||||
) {
|
|
||||||
const tokensData = (userContext.fabricContext.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY])
|
|
||||||
.resourceTokenInfo;
|
|
||||||
|
|
||||||
const databaseIdsSet = new Set<string>(); // databaseId
|
const databaseIdsSet = new Set<string>(); // databaseId
|
||||||
|
|
||||||
@@ -52,28 +46,13 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
|
|||||||
}));
|
}));
|
||||||
clearMessage();
|
clearMessage();
|
||||||
return databases;
|
return databases;
|
||||||
} else if (isFabricNative() && userContext.fabricContext?.databaseName) {
|
|
||||||
const databaseId = userContext.fabricContext.databaseName;
|
|
||||||
databases = [
|
|
||||||
{
|
|
||||||
_rid: "",
|
|
||||||
_self: "",
|
|
||||||
_etag: "",
|
|
||||||
_ts: 0,
|
|
||||||
id: databaseId,
|
|
||||||
collections: [],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
clearMessage();
|
|
||||||
return databases;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
userContext.authType === AuthType.AAD &&
|
userContext.authType === AuthType.AAD &&
|
||||||
!userContext.features.enableSDKoperations &&
|
!userContext.features.enableSDKoperations &&
|
||||||
userContext.apiType !== "Tables" &&
|
userContext.apiType !== "Tables"
|
||||||
!isFabric()
|
|
||||||
) {
|
) {
|
||||||
databases = await readDatabasesWithARM();
|
databases = await readDatabasesWithARM();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { ContainerDefinition, RequestOptions } from "@azure/cosmos";
|
import { ContainerDefinition, RequestOptions } from "@azure/cosmos";
|
||||||
import { isFabric } from "Platform/Fabric/FabricUtil";
|
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { Collection } from "../../Contracts/DataModels";
|
import { Collection } from "../../Contracts/DataModels";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
@@ -37,8 +36,7 @@ export async function updateCollection(
|
|||||||
if (
|
if (
|
||||||
userContext.authType === AuthType.AAD &&
|
userContext.authType === AuthType.AAD &&
|
||||||
!userContext.features.enableSDKoperations &&
|
!userContext.features.enableSDKoperations &&
|
||||||
userContext.apiType !== "Tables" &&
|
userContext.apiType !== "Tables"
|
||||||
!isFabric()
|
|
||||||
) {
|
) {
|
||||||
collection = await updateCollectionWithARM(databaseId, collectionId, newCollection);
|
collection = await updateCollectionWithARM(databaseId, collectionId, newCollection);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { OfferDefinition, RequestOptions } from "@azure/cosmos";
|
import { OfferDefinition, RequestOptions } from "@azure/cosmos";
|
||||||
import { isFabric } from "Platform/Fabric/FabricUtil";
|
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { Offer, SDKOfferDefinition, ThroughputBucket, UpdateOfferParams } from "../../Contracts/DataModels";
|
import { Offer, SDKOfferDefinition, ThroughputBucket, UpdateOfferParams } from "../../Contracts/DataModels";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
@@ -57,7 +56,7 @@ export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> =>
|
|||||||
const clearMessage = logConsoleProgress(`Updating offer for ${offerResourceText}`);
|
const clearMessage = logConsoleProgress(`Updating offer for ${offerResourceText}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations && !isFabric()) {
|
if (userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations) {
|
||||||
if (params.collectionId) {
|
if (params.collectionId) {
|
||||||
updatedOffer = await updateCollectionOfferWithARM(params);
|
updatedOffer = await updateCollectionOfferWithARM(params);
|
||||||
} else if (userContext.apiType === "Tables") {
|
} else if (userContext.apiType === "Tables") {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
export enum FabricMessageTypes {
|
export enum FabricMessageTypes {
|
||||||
GetAuthorizationToken = "GetAuthorizationToken",
|
GetAuthorizationToken = "GetAuthorizationToken",
|
||||||
GetAllResourceTokens = "GetAllResourceTokens",
|
GetAllResourceTokens = "GetAllResourceTokens",
|
||||||
GetAccessToken = "GetAccessToken",
|
|
||||||
Ready = "Ready",
|
Ready = "Ready",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,47 @@
|
|||||||
import { AuthorizationToken } from "./FabricMessageTypes";
|
import { AuthorizationToken } from "Contracts/FabricMessageTypes";
|
||||||
|
|
||||||
// This is the version of these messages
|
// This is the version of these messages
|
||||||
export const FABRIC_RPC_VERSION = "FabricMessageV3";
|
export const FABRIC_RPC_VERSION = "2";
|
||||||
|
|
||||||
// Fabric to Data Explorer
|
// Fabric to Data Explorer
|
||||||
|
|
||||||
|
// TODO Deprecated. Remove this section once DE is updated
|
||||||
|
export type FabricMessageV1 =
|
||||||
|
| {
|
||||||
|
type: "newContainer";
|
||||||
|
databaseName: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "initialize";
|
||||||
|
message: {
|
||||||
|
endpoint: string | undefined;
|
||||||
|
databaseId: string | undefined;
|
||||||
|
resourceTokens: unknown | undefined;
|
||||||
|
resourceTokensTimestamp: number | undefined;
|
||||||
|
error: string | undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "authorizationToken";
|
||||||
|
message: {
|
||||||
|
id: string;
|
||||||
|
error: string | undefined;
|
||||||
|
data: AuthorizationToken | undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "allResourceTokens";
|
||||||
|
message: {
|
||||||
|
id: string;
|
||||||
|
error: string | undefined;
|
||||||
|
endpoint: string | undefined;
|
||||||
|
databaseId: string | undefined;
|
||||||
|
resourceTokens: unknown | undefined;
|
||||||
|
resourceTokensTimestamp: number | undefined;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
// -----------------------------
|
||||||
|
|
||||||
export type FabricMessageV2 =
|
export type FabricMessageV2 =
|
||||||
| {
|
| {
|
||||||
type: "newContainer";
|
type: "newContainer";
|
||||||
@@ -31,7 +69,7 @@ export type FabricMessageV2 =
|
|||||||
message: {
|
message: {
|
||||||
id: string;
|
id: string;
|
||||||
error: string | undefined;
|
error: string | undefined;
|
||||||
data: ResourceTokenInfo | undefined;
|
data: FabricDatabaseConnectionInfo | undefined;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
@@ -41,81 +79,17 @@ export type FabricMessageV2 =
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FabricMessageV3 =
|
export type CosmosDBTokenResponse = {
|
||||||
| {
|
token: string;
|
||||||
type: "newContainer";
|
date: string;
|
||||||
databaseName: string;
|
};
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "initialize";
|
|
||||||
version: string;
|
|
||||||
id: string;
|
|
||||||
message: InitializeMessageV3<CosmosDbArtifactType>;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "authorizationToken";
|
|
||||||
message: {
|
|
||||||
id: string;
|
|
||||||
error: string | undefined;
|
|
||||||
data: AuthorizationToken | undefined;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "allResourceTokens_v2";
|
|
||||||
message: {
|
|
||||||
id: string;
|
|
||||||
error: string | undefined;
|
|
||||||
data: ResourceTokenInfo | undefined;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "explorerVisible";
|
|
||||||
message: {
|
|
||||||
visible: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "accessToken";
|
|
||||||
message: {
|
|
||||||
id: string;
|
|
||||||
error: string | undefined;
|
|
||||||
data: { accessToken: string };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum CosmosDbArtifactType {
|
export type CosmosDBConnectionInfoResponse = {
|
||||||
MIRRORED_KEY = "MIRRORED_KEY",
|
|
||||||
MIRRORED_AAD = "MIRRORED_AAD",
|
|
||||||
NATIVE = "NATIVE",
|
|
||||||
}
|
|
||||||
export interface ArtifactConnectionInfo {
|
|
||||||
[CosmosDbArtifactType.MIRRORED_KEY]: { connectionId: string };
|
|
||||||
[CosmosDbArtifactType.MIRRORED_AAD]: AccessTokenConnectionInfo;
|
|
||||||
[CosmosDbArtifactType.NATIVE]: AccessTokenConnectionInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AccessTokenConnectionInfo {
|
|
||||||
accessToken: string;
|
|
||||||
databaseName: string;
|
|
||||||
accountEndpoint: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface InitializeMessageV3<T extends CosmosDbArtifactType> {
|
|
||||||
connectionId: string;
|
|
||||||
isVisible: boolean;
|
|
||||||
isReadOnly: boolean;
|
|
||||||
artifactType: T;
|
|
||||||
artifactConnectionInfo: ArtifactConnectionInfo[T];
|
|
||||||
}
|
|
||||||
export interface CosmosDBConnectionInfoResponse {
|
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
databaseId: string;
|
databaseId: string;
|
||||||
resourceTokens: Record<string, string> | undefined;
|
resourceTokens: { [resourceId: string]: string };
|
||||||
accessToken: string | undefined;
|
};
|
||||||
isReadOnly: boolean;
|
|
||||||
credentialType: "Key" | "OAuth2" | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ResourceTokenInfo extends CosmosDBConnectionInfoResponse {
|
export interface FabricDatabaseConnectionInfo extends CosmosDBConnectionInfoResponse {
|
||||||
resourceTokensTimestamp: number;
|
resourceTokensTimestamp: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { configContext, Platform } from "ConfigContext";
|
|
||||||
import { TreeNodeMenuItem } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
|
import { TreeNodeMenuItem } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
import { isFabric, isFabricNative } from "Platform/Fabric/FabricUtil";
|
|
||||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||||
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
|
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
|
||||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||||
@@ -21,6 +19,7 @@ import * as ViewModels from "../Contracts/ViewModels";
|
|||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils";
|
import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils";
|
||||||
import { useSidePanel } from "../hooks/useSidePanel";
|
import { useSidePanel } from "../hooks/useSidePanel";
|
||||||
|
import { Platform, configContext } from "./../ConfigContext";
|
||||||
import Explorer from "./Explorer";
|
import Explorer from "./Explorer";
|
||||||
import { useNotebook } from "./Notebook/useNotebook";
|
import { useNotebook } from "./Notebook/useNotebook";
|
||||||
import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane";
|
import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane";
|
||||||
@@ -42,7 +41,7 @@ export interface DatabaseContextMenuButtonParams {
|
|||||||
* New resource tree (in ReactJS)
|
* New resource tree (in ReactJS)
|
||||||
*/
|
*/
|
||||||
export const createDatabaseContextMenu = (container: Explorer, databaseId: string): TreeNodeMenuItem[] => {
|
export const createDatabaseContextMenu = (container: Explorer, databaseId: string): TreeNodeMenuItem[] => {
|
||||||
if (isFabric() && userContext.fabricContext?.isReadOnly) {
|
if (configContext.platform === Platform.Fabric && userContext.fabricContext?.isReadOnly) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +53,7 @@ export const createDatabaseContextMenu = (container: Explorer, databaseId: strin
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!isFabricNative() && (userContext.apiType !== "Tables" || userContext.features.enableSDKoperations)) {
|
if (userContext.apiType !== "Tables" || userContext.features.enableSDKoperations) {
|
||||||
items.push({
|
items.push({
|
||||||
iconSrc: DeleteDatabaseIcon,
|
iconSrc: DeleteDatabaseIcon,
|
||||||
onClick: (lastFocusedElement?: React.RefObject<HTMLElement>) => {
|
onClick: (lastFocusedElement?: React.RefObject<HTMLElement>) => {
|
||||||
@@ -146,7 +145,7 @@ export const createCollectionContextMenuButton = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isFabric() || (isFabric() && !userContext.fabricContext?.isReadOnly)) {
|
if (configContext.platform !== Platform.Fabric) {
|
||||||
items.push({
|
items.push({
|
||||||
iconSrc: DeleteCollectionIcon,
|
iconSrc: DeleteCollectionIcon,
|
||||||
onClick: (lastFocusedElement?: React.RefObject<HTMLElement>) => {
|
onClick: (lastFocusedElement?: React.RefObject<HTMLElement>) => {
|
||||||
|
|||||||
@@ -35,20 +35,12 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
setIsThroughputCapExceeded,
|
setIsThroughputCapExceeded,
|
||||||
onCostAcknowledgeChange,
|
onCostAcknowledgeChange,
|
||||||
}: ThroughputInputProps) => {
|
}: ThroughputInputProps) => {
|
||||||
let defaultThroughput: number;
|
const defaultThroughput: number =
|
||||||
const workloadType: Constants.WorkloadType = getWorkloadType();
|
|
||||||
|
|
||||||
if (
|
|
||||||
isFreeTier ||
|
isFreeTier ||
|
||||||
isQuickstart ||
|
isQuickstart ||
|
||||||
[Constants.WorkloadType.Learning, Constants.WorkloadType.DevelopmentTesting].includes(workloadType)
|
[Constants.WorkloadType.Learning, Constants.WorkloadType.DevelopmentTesting].includes(getWorkloadType())
|
||||||
) {
|
? AutoPilotUtils.autoPilotThroughput1K
|
||||||
defaultThroughput = AutoPilotUtils.autoPilotThroughput1K;
|
: AutoPilotUtils.autoPilotThroughput4K;
|
||||||
} else if (workloadType === Constants.WorkloadType.Production) {
|
|
||||||
defaultThroughput = AutoPilotUtils.autoPilotThroughput10K;
|
|
||||||
} else {
|
|
||||||
defaultThroughput = AutoPilotUtils.autoPilotThroughput4K;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [isAutoscaleSelected, setIsAutoScaleSelected] = useState<boolean>(true);
|
const [isAutoscaleSelected, setIsAutoScaleSelected] = useState<boolean>(true);
|
||||||
const [throughput, setThroughput] = useState<number>(defaultThroughput);
|
const [throughput, setThroughput] = useState<number>(defaultThroughput);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { MessageTypes } from "Contracts/ExplorerContracts";
|
|||||||
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
|
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
|
||||||
import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||||
import { IGalleryItem } from "Juno/JunoClient";
|
import { IGalleryItem } from "Juno/JunoClient";
|
||||||
import { isFabricMirrored, isFabricMirroredKey, scheduleRefreshFabricToken } from "Platform/Fabric/FabricUtil";
|
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
|
||||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||||
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
|
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
|
||||||
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
|
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
|
||||||
@@ -43,7 +43,7 @@ import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
|
|||||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
|
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
|
||||||
import { useSidePanel } from "../hooks/useSidePanel";
|
import { useSidePanel } from "../hooks/useSidePanel";
|
||||||
import { ReactTabKind, useTabs } from "../hooks/useTabs";
|
import { useTabs } from "../hooks/useTabs";
|
||||||
import "./ComponentRegisterer";
|
import "./ComponentRegisterer";
|
||||||
import { DialogProps, useDialog } from "./Controls/Dialog";
|
import { DialogProps, useDialog } from "./Controls/Dialog";
|
||||||
import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/GalleryViewerComponent";
|
import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/GalleryViewerComponent";
|
||||||
@@ -187,10 +187,6 @@ export default class Explorer {
|
|||||||
useNotebook.getState().setNotebookBasePath(userContext.features.notebookBasePath);
|
useNotebook.getState().setNotebookBasePath(userContext.features.notebookBasePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFabricMirrored()) {
|
|
||||||
useTabs.getState().closeReactTab(ReactTabKind.Home);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.refreshExplorer();
|
this.refreshExplorer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,8 +347,8 @@ export default class Explorer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public onRefreshResourcesClick = async (): Promise<void> => {
|
public onRefreshResourcesClick = async (): Promise<void> => {
|
||||||
if (isFabricMirroredKey()) {
|
if (configContext.platform === Platform.Fabric) {
|
||||||
scheduleRefreshFabricToken(true).then(() => this.refreshAllDatabases());
|
scheduleRefreshDatabaseResourceToken(true).then(() => this.refreshAllDatabases());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,10 @@
|
|||||||
.flex-direction(@direction: row);
|
.flex-direction(@direction: row);
|
||||||
padding: 4px 5px;
|
padding: 4px 5px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.valueCol {
|
.valueCol {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
@@ -59,10 +63,6 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.customTrashIcon {
|
|
||||||
padding-top: 33px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightPaneTrashIconImg {
|
.rightPaneTrashIconImg {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,11 +142,10 @@ export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = (
|
|||||||
<div className="labelCol">
|
<div className="labelCol">
|
||||||
<TextField
|
<TextField
|
||||||
className="edgeInput"
|
className="edgeInput"
|
||||||
label={index === 0 && "Key"}
|
|
||||||
type="text"
|
type="text"
|
||||||
id="propertyKeyNewVertexPane"
|
id="propertyKeyNewVertexPane"
|
||||||
componentRef={input}
|
componentRef={input}
|
||||||
required
|
aria-required="true"
|
||||||
placeholder="Key"
|
placeholder="Key"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
aria-label={`Enter value for propery ${index + 1}`}
|
aria-label={`Enter value for propery ${index + 1}`}
|
||||||
@@ -154,11 +153,11 @@ export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = (
|
|||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onKeyChange(event, index)}
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onKeyChange(event, index)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<span className="mandatoryStar">* </span>
|
||||||
|
|
||||||
<div className="valueCol">
|
<div className="valueCol">
|
||||||
<TextField
|
<TextField
|
||||||
className="edgeInput"
|
className="edgeInput"
|
||||||
label={index === 0 && "Value"}
|
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Value"
|
placeholder="Value"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
@@ -170,8 +169,6 @@ export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = (
|
|||||||
<div>
|
<div>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
role="combobox"
|
role="combobox"
|
||||||
label={index === 0 && "Type"}
|
|
||||||
ariaLabel="Type"
|
|
||||||
placeholder="Select an option"
|
placeholder="Select an option"
|
||||||
defaultSelectedKey={data.values[0].type}
|
defaultSelectedKey={data.values[0].type}
|
||||||
style={{ width: 100 }}
|
style={{ width: 100 }}
|
||||||
@@ -184,7 +181,7 @@ export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = (
|
|||||||
</div>
|
</div>
|
||||||
<div className="actionCol">
|
<div className="actionCol">
|
||||||
<div
|
<div
|
||||||
className={`rightPaneTrashIcon rightPaneBtns ${index === 0 && "customTrashIcon"}`}
|
className="rightPaneTrashIcon rightPaneBtns"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role="button"
|
role="button"
|
||||||
aria-label={`Delete ${data.key}`}
|
aria-label={`Delete ${data.key}`}
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
|
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
|
||||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
||||||
import { KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
|
import { KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
|
||||||
import { isFabric } from "Platform/Fabric/FabricUtil";
|
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import create, { UseStore } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { ConnectionStatusType, PoolIdType } from "../../../Common/Constants";
|
import { ConnectionStatusType, PoolIdType } from "../../../Common/Constants";
|
||||||
import { StyleConstants } from "../../../Common/StyleConstants";
|
import { StyleConstants } from "../../../Common/StyleConstants";
|
||||||
|
import { Platform, configContext } from "../../../ConfigContext";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { useSelectedNode } from "../../useSelectedNode";
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
@@ -93,18 +93,19 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootStyle = isFabric()
|
const rootStyle =
|
||||||
? {
|
configContext.platform === Platform.Fabric
|
||||||
root: {
|
? {
|
||||||
backgroundColor: "transparent",
|
root: {
|
||||||
padding: "2px 8px 0px 8px",
|
backgroundColor: "transparent",
|
||||||
},
|
padding: "2px 8px 0px 8px",
|
||||||
}
|
},
|
||||||
: {
|
}
|
||||||
root: {
|
: {
|
||||||
backgroundColor: backgroundColor,
|
root: {
|
||||||
},
|
backgroundColor: backgroundColor,
|
||||||
};
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const allButtons = staticButtons.concat(contextButtons).concat(controlButtons);
|
const allButtons = staticButtons.concat(contextButtons).concat(controlButtons);
|
||||||
const keyboardHandlers = CommandBarUtil.createKeyboardHandlers(allButtons);
|
const keyboardHandlers = CommandBarUtil.createKeyboardHandlers(allButtons);
|
||||||
|
|||||||
@@ -37,25 +37,21 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
expect(enableAzureSynapseLinkBtn).toBeDefined();
|
expect(enableAzureSynapseLinkBtn).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Now that Tables API supports dataplane RBAC, calling createStaticCommandBarButtons will enable the
|
it("Button should not be visible for Tables API", () => {
|
||||||
// Entra ID Login button, which causes this test to fail due to "Invalid hook call.". This seems to be
|
updateUserContext({
|
||||||
// unsupported in jest and needs to be tested with react-hooks-testing-library.
|
databaseAccount: {
|
||||||
//
|
properties: {
|
||||||
// it("Button should not be visible for Tables API", () => {
|
capabilities: [{ name: "EnableTable" }],
|
||||||
// updateUserContext({
|
},
|
||||||
// databaseAccount: {
|
} as DatabaseAccount,
|
||||||
// properties: {
|
});
|
||||||
// capabilities: [{ name: "EnableTable" }],
|
|
||||||
// },
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
// } as DatabaseAccount,
|
const enableAzureSynapseLinkBtn = buttons.find(
|
||||||
// });
|
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel,
|
||||||
//
|
);
|
||||||
// const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
expect(enableAzureSynapseLinkBtn).toBeUndefined();
|
||||||
// const enableAzureSynapseLinkBtn = buttons.find(
|
});
|
||||||
// (button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel,
|
|
||||||
// );
|
|
||||||
// expect(enableAzureSynapseLinkBtn).toBeUndefined();
|
|
||||||
//});
|
|
||||||
|
|
||||||
it("Button should not be visible for Cassandra API", () => {
|
it("Button should not be visible for Cassandra API", () => {
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { KeyboardAction } from "KeyboardShortcuts";
|
import { KeyboardAction } from "KeyboardShortcuts";
|
||||||
import { isDataplaneRbacSupported } from "Utils/APITypeUtils";
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import AddSqlQueryIcon from "../../../../images/AddSqlQuery_16x16.svg";
|
import AddSqlQueryIcon from "../../../../images/AddSqlQuery_16x16.svg";
|
||||||
@@ -62,7 +61,7 @@ export function createStaticCommandBarButtons(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDataplaneRbacSupported(userContext.apiType)) {
|
if (userContext.apiType === "SQL") {
|
||||||
const [loginButtonProps, setLoginButtonProps] = useState<CommandButtonComponentProps | undefined>(undefined);
|
const [loginButtonProps, setLoginButtonProps] = useState<CommandButtonComponentProps | undefined>(undefined);
|
||||||
const dataPlaneRbacEnabled = useDataPlaneRbac((state) => state.dataPlaneRbacEnabled);
|
const dataPlaneRbacEnabled = useDataPlaneRbac((state) => state.dataPlaneRbacEnabled);
|
||||||
const aadTokenUpdated = useDataPlaneRbac((state) => state.aadTokenUpdated);
|
const aadTokenUpdated = useDataPlaneRbac((state) => state.aadTokenUpdated);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@import "../../../../less/Common/Constants";
|
@import "../../../../less/Common/Constants";
|
||||||
|
|
||||||
@ConsoleHeaderHeight: 32px;
|
// @ConsoleHeaderHeight: 32px;
|
||||||
@ConsoleContentsPaneHeight: 220px;
|
@ConsoleContentsPaneHeight: 220px;
|
||||||
@ConsoleStatusMaxWidth: 672px;
|
@ConsoleStatusMaxWidth: 672px;
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
height: @ConsoleHeaderHeight;
|
// height: @ConsoleHeaderHeight;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: @NotificationLow;
|
background-color: @NotificationLow;
|
||||||
border-top: @ButtonBorderWidth @BaseMedium solid;
|
border-top: @ButtonBorderWidth @BaseMedium solid;
|
||||||
@@ -36,12 +36,11 @@
|
|||||||
&:active {
|
&:active {
|
||||||
background-color:@NotificationHigh;
|
background-color:@NotificationHigh;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
|
||||||
.focusedBorder();
|
|
||||||
}
|
|
||||||
|
|
||||||
.statusBar {
|
.statusBar {
|
||||||
|
width: 100%;
|
||||||
|
padding: 6px 0px;
|
||||||
|
|
||||||
.dataTypeIcons {
|
.dataTypeIcons {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin: 0px @DefaultSpace 0px @MediumSpace;
|
margin: 0px @DefaultSpace 0px @MediumSpace;
|
||||||
@@ -80,11 +79,14 @@
|
|||||||
max-width: @ConsoleStatusMaxWidth;
|
max-width: @ConsoleStatusMaxWidth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.expandCollapseButton {
|
.expandCollapseButton {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding-right: 5px;
|
padding: 6px 5px 6px 0px;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: @ExpandCollapseIconSize;
|
width: @ExpandCollapseIconSize;
|
||||||
|
|||||||
@@ -81,6 +81,10 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// public setElememntRef = (element: HTMLElement): void => {
|
||||||
|
// this.consoleHeaderElement = element;
|
||||||
|
// };
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
const numInProgress = this.state.allConsoleData.filter(
|
const numInProgress = this.state.allConsoleData.filter(
|
||||||
(data: ConsoleData) => data.type === ConsoleDataType.InProgress,
|
(data: ConsoleData) => data.type === ConsoleDataType.InProgress,
|
||||||
@@ -94,17 +98,14 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="notificationConsoleContainer">
|
<div className="notificationConsoleContainer">
|
||||||
<div
|
<div className="notificationConsoleHeader" id="notificationConsoleHeader">
|
||||||
className="notificationConsoleHeader"
|
<div
|
||||||
id="notificationConsoleHeader"
|
className="statusBar"
|
||||||
role="button"
|
// ref={this.setElememntRef}
|
||||||
aria-label="Console"
|
onClick={() => this.expandCollapseConsole()}
|
||||||
aria-expanded={this.props.isConsoleExpanded}
|
onKeyDown={(event: React.KeyboardEvent<HTMLDivElement>) => this.onExpandCollapseKeyPress(event)}
|
||||||
onClick={() => this.expandCollapseConsole()}
|
tabIndex={0}
|
||||||
onKeyDown={(event: React.KeyboardEvent<HTMLDivElement>) => this.onExpandCollapseKeyPress(event)}
|
>
|
||||||
tabIndex={0}
|
|
||||||
>
|
|
||||||
<div className="statusBar">
|
|
||||||
<span className="dataTypeIcons">
|
<span className="dataTypeIcons">
|
||||||
<span className="notificationConsoleHeaderIconWithData">
|
<span className="notificationConsoleHeaderIconWithData">
|
||||||
<img src={LoadingIcon} alt="In progress items" />
|
<img src={LoadingIcon} alt="In progress items" />
|
||||||
@@ -127,7 +128,16 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="expandCollapseButton" data-test="NotificationConsole/ExpandCollapseButton">
|
<div
|
||||||
|
className="expandCollapseButton"
|
||||||
|
data-test="NotificationConsole/ExpandCollapseButton"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label="Console"
|
||||||
|
aria-expanded={this.props.isConsoleExpanded}
|
||||||
|
onClick={() => this.expandCollapseConsole()}
|
||||||
|
onKeyDown={(event: React.KeyboardEvent<HTMLDivElement>) => this.onExpandCollapseKeyPress(event)}
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src={this.props.isConsoleExpanded ? ChevronDownIcon : ChevronUpIcon}
|
src={this.props.isConsoleExpanded ? ChevronDownIcon : ChevronUpIcon}
|
||||||
alt={this.props.isConsoleExpanded ? "Collapse icon" : "Expand icon"}
|
alt={this.props.isConsoleExpanded ? "Collapse icon" : "Expand icon"}
|
||||||
@@ -250,6 +260,9 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onConsoleWasExpanded = (): void => {
|
private onConsoleWasExpanded = (): void => {
|
||||||
|
// if (this.props.isConsoleExpanded && this.consoleHeaderElement) {
|
||||||
|
// this.consoleHeaderElement.focus();
|
||||||
|
// }
|
||||||
useNotificationConsole.getState().setConsoleAnimationFinished(true);
|
useNotificationConsole.getState().setConsoleAnimationFinished(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,10 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
|||||||
className="notificationConsoleContainer"
|
className="notificationConsoleContainer"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-expanded={false}
|
|
||||||
aria-label="Console"
|
|
||||||
className="notificationConsoleHeader"
|
className="notificationConsoleHeader"
|
||||||
id="notificationConsoleHeader"
|
id="notificationConsoleHeader"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -74,8 +71,12 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
aria-expanded={false}
|
||||||
|
aria-label="Console"
|
||||||
className="expandCollapseButton"
|
className="expandCollapseButton"
|
||||||
data-test="NotificationConsole/ExpandCollapseButton"
|
data-test="NotificationConsole/ExpandCollapseButton"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Expand icon"
|
alt="Expand icon"
|
||||||
@@ -175,13 +176,10 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
|||||||
className="notificationConsoleContainer"
|
className="notificationConsoleContainer"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-expanded={false}
|
|
||||||
aria-label="Console"
|
|
||||||
className="notificationConsoleHeader"
|
className="notificationConsoleHeader"
|
||||||
id="notificationConsoleHeader"
|
id="notificationConsoleHeader"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -246,8 +244,12 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
aria-expanded={false}
|
||||||
|
aria-label="Console"
|
||||||
className="expandCollapseButton"
|
className="expandCollapseButton"
|
||||||
data-test="NotificationConsole/ExpandCollapseButton"
|
data-test="NotificationConsole/ExpandCollapseButton"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Expand icon"
|
alt="Expand icon"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Notebook container related stuff
|
* Notebook container related stuff
|
||||||
*/
|
*/
|
||||||
import { useDialog } from "Explorer/Controls/Dialog";
|
import { useDialog } from "Explorer/Controls/Dialog";
|
||||||
import promiseRetry, { AbortError, Options } from "p-retry";
|
import promiseRetry, { AbortError } from "p-retry";
|
||||||
import { PhoenixClient } from "Phoenix/PhoenixClient";
|
import { PhoenixClient } from "Phoenix/PhoenixClient";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import { ConnectionStatusType, HttpHeaders, HttpStatusCodes, Notebook, PoolIdType } from "../../Common/Constants";
|
import { ConnectionStatusType, HttpHeaders, HttpStatusCodes, Notebook, PoolIdType } from "../../Common/Constants";
|
||||||
@@ -19,7 +19,7 @@ export class NotebookContainerClient {
|
|||||||
private clearReconnectionAttemptMessage? = () => {};
|
private clearReconnectionAttemptMessage? = () => {};
|
||||||
private isResettingWorkspace: boolean;
|
private isResettingWorkspace: boolean;
|
||||||
private phoenixClient: PhoenixClient;
|
private phoenixClient: PhoenixClient;
|
||||||
private retryOptions: Options;
|
private retryOptions: promiseRetry.Options;
|
||||||
private scheduleTimerId: NodeJS.Timeout;
|
private scheduleTimerId: NodeJS.Timeout;
|
||||||
|
|
||||||
constructor(private onConnectionLost: () => void) {
|
constructor(private onConnectionLost: () => void) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// TODO convert this file to an action registry in order to have actions and their handlers be more tightly coupled.
|
// TODO convert this file to an action registry in order to have actions and their handlers be more tightly coupled.
|
||||||
|
import { configContext, Platform } from "ConfigContext";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
import { isFabricMirrored } from "Platform/Fabric/FabricUtil";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { ActionContracts } from "../../Contracts/ExplorerContracts";
|
import { ActionContracts } from "../../Contracts/ExplorerContracts";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
@@ -58,9 +58,9 @@ function openCollectionTab(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isFabricMirrored() &&
|
configContext.platform === Platform.Fabric &&
|
||||||
!(
|
!(
|
||||||
// whitelist the tab kinds that are allowed to be opened in Fabric mirrored
|
// whitelist the tab kinds that are allowed to be opened in Fabric
|
||||||
(
|
(
|
||||||
action.tabKind === ActionContracts.TabKind.SQLDocuments ||
|
action.tabKind === ActionContracts.TabKind.SQLDocuments ||
|
||||||
action.tabKind === ActionContracts.TabKind.SQLQuery
|
action.tabKind === ActionContracts.TabKind.SQLQuery
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import {
|
|||||||
import { VectorEmbeddingPoliciesComponent } from "Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent";
|
import { VectorEmbeddingPoliciesComponent } from "Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent";
|
||||||
import { useSidePanel } from "hooks/useSidePanel";
|
import { useSidePanel } from "hooks/useSidePanel";
|
||||||
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
||||||
import { isFabricNative } from "Platform/Fabric/FabricUtil";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { CollectionCreation } from "Shared/Constants";
|
import { CollectionCreation } from "Shared/Constants";
|
||||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||||
@@ -285,152 +284,150 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
{!(isFabricNative() && this.props.databaseId !== undefined) && (
|
<Stack hidden={userContext.apiType === "Tables"}>
|
||||||
<Stack hidden={userContext.apiType === "Tables"}>
|
<Stack horizontal>
|
||||||
<Stack horizontal>
|
<span className="mandatoryStar">* </span>
|
||||||
<span className="mandatoryStar">* </span>
|
<Text className="panelTextBold" variant="small">
|
||||||
<Text className="panelTextBold" variant="small">
|
Database {userContext.apiType === "Mongo" ? "name" : "id"}
|
||||||
Database {userContext.apiType === "Mongo" ? "name" : "id"}
|
</Text>
|
||||||
</Text>
|
<TooltipHost
|
||||||
<TooltipHost
|
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
content={`A database is analogous to a namespace. It is the unit of management for a set of ${getCollectionName(
|
||||||
content={`A database is analogous to a namespace. It is the unit of management for a set of ${getCollectionName(
|
true,
|
||||||
|
).toLocaleLowerCase()}.`}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
iconName="Info"
|
||||||
|
className="panelInfoIcon"
|
||||||
|
tabIndex={0}
|
||||||
|
ariaLabel={`A database is analogous to a namespace. It is the unit of management for a set of ${getCollectionName(
|
||||||
true,
|
true,
|
||||||
).toLocaleLowerCase()}.`}
|
).toLocaleLowerCase()}.`}
|
||||||
>
|
/>
|
||||||
<Icon
|
</TooltipHost>
|
||||||
iconName="Info"
|
</Stack>
|
||||||
className="panelInfoIcon"
|
|
||||||
tabIndex={0}
|
|
||||||
ariaLabel={`A database is analogous to a namespace. It is the unit of management for a set of ${getCollectionName(
|
|
||||||
true,
|
|
||||||
).toLocaleLowerCase()}.`}
|
|
||||||
/>
|
|
||||||
</TooltipHost>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
{configContext.platform !== Platform.Fabric && (
|
{configContext.platform !== Platform.Fabric && (
|
||||||
<Stack horizontal verticalAlign="center">
|
<Stack horizontal verticalAlign="center">
|
||||||
<div role="radiogroup">
|
<div role="radiogroup">
|
||||||
<input
|
|
||||||
className="panelRadioBtn"
|
|
||||||
checked={this.state.createNewDatabase}
|
|
||||||
aria-label="Create new database"
|
|
||||||
aria-checked={this.state.createNewDatabase}
|
|
||||||
name="databaseType"
|
|
||||||
type="radio"
|
|
||||||
role="radio"
|
|
||||||
id="databaseCreateNew"
|
|
||||||
tabIndex={0}
|
|
||||||
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
|
|
||||||
/>
|
|
||||||
<span className="panelRadioBtnLabel">Create new</span>
|
|
||||||
|
|
||||||
<input
|
|
||||||
className="panelRadioBtn"
|
|
||||||
checked={!this.state.createNewDatabase}
|
|
||||||
aria-label="Use existing database"
|
|
||||||
aria-checked={!this.state.createNewDatabase}
|
|
||||||
name="databaseType"
|
|
||||||
type="radio"
|
|
||||||
role="radio"
|
|
||||||
tabIndex={0}
|
|
||||||
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
|
|
||||||
/>
|
|
||||||
<span className="panelRadioBtnLabel">Use existing</span>
|
|
||||||
</div>
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{this.state.createNewDatabase && (
|
|
||||||
<Stack className="panelGroupSpacing">
|
|
||||||
<input
|
<input
|
||||||
name="newDatabaseId"
|
className="panelRadioBtn"
|
||||||
id="newDatabaseId"
|
checked={this.state.createNewDatabase}
|
||||||
aria-required
|
aria-label="Create new database"
|
||||||
required
|
aria-checked={this.state.createNewDatabase}
|
||||||
type="text"
|
name="databaseType"
|
||||||
autoComplete="off"
|
type="radio"
|
||||||
pattern="[^/?#\\]*[^/?# \\]"
|
role="radio"
|
||||||
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
id="databaseCreateNew"
|
||||||
placeholder="Type a new database id"
|
|
||||||
size={40}
|
|
||||||
className="panelTextField"
|
|
||||||
aria-label="New database id, Type a new database id"
|
|
||||||
autoFocus
|
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
value={this.state.newDatabaseId}
|
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
|
||||||
this.setState({ newDatabaseId: event.target.value })
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
<span className="panelRadioBtnLabel">Create new</span>
|
||||||
|
|
||||||
{!isServerlessAccount() && (
|
<input
|
||||||
<Stack horizontal>
|
className="panelRadioBtn"
|
||||||
<Checkbox
|
checked={!this.state.createNewDatabase}
|
||||||
label={`Share throughput across ${getCollectionName(true).toLocaleLowerCase()}`}
|
aria-label="Use existing database"
|
||||||
checked={this.state.isSharedThroughputChecked}
|
aria-checked={!this.state.createNewDatabase}
|
||||||
styles={{
|
name="databaseType"
|
||||||
text: { fontSize: 12 },
|
type="radio"
|
||||||
checkbox: { width: 12, height: 12 },
|
role="radio"
|
||||||
label: { padding: 0, alignItems: "center" },
|
tabIndex={0}
|
||||||
}}
|
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
|
||||||
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
|
/>
|
||||||
this.setState({ isSharedThroughputChecked: isChecked })
|
<span className="panelRadioBtnLabel">Use existing</span>
|
||||||
}
|
</div>
|
||||||
/>
|
</Stack>
|
||||||
<TooltipHost
|
)}
|
||||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
|
||||||
content={`Throughput configured at the database level will be shared across all ${getCollectionName(
|
{this.state.createNewDatabase && (
|
||||||
|
<Stack className="panelGroupSpacing">
|
||||||
|
<input
|
||||||
|
name="newDatabaseId"
|
||||||
|
id="newDatabaseId"
|
||||||
|
aria-required
|
||||||
|
required
|
||||||
|
type="text"
|
||||||
|
autoComplete="off"
|
||||||
|
pattern="[^/?#\\]*[^/?# \\]"
|
||||||
|
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
||||||
|
placeholder="Type a new database id"
|
||||||
|
size={40}
|
||||||
|
className="panelTextField"
|
||||||
|
aria-label="New database id, Type a new database id"
|
||||||
|
autoFocus
|
||||||
|
tabIndex={0}
|
||||||
|
value={this.state.newDatabaseId}
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
this.setState({ newDatabaseId: event.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{!isServerlessAccount() && (
|
||||||
|
<Stack horizontal>
|
||||||
|
<Checkbox
|
||||||
|
label={`Share throughput across ${getCollectionName(true).toLocaleLowerCase()}`}
|
||||||
|
checked={this.state.isSharedThroughputChecked}
|
||||||
|
styles={{
|
||||||
|
text: { fontSize: 12 },
|
||||||
|
checkbox: { width: 12, height: 12 },
|
||||||
|
label: { padding: 0, alignItems: "center" },
|
||||||
|
}}
|
||||||
|
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
|
||||||
|
this.setState({ isSharedThroughputChecked: isChecked })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TooltipHost
|
||||||
|
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||||
|
content={`Throughput configured at the database level will be shared across all ${getCollectionName(
|
||||||
|
true,
|
||||||
|
).toLocaleLowerCase()} within the database.`}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
iconName="Info"
|
||||||
|
className="panelInfoIcon"
|
||||||
|
tabIndex={0}
|
||||||
|
ariaLabel={`Throughput configured at the database level will be shared across all ${getCollectionName(
|
||||||
true,
|
true,
|
||||||
).toLocaleLowerCase()} within the database.`}
|
).toLocaleLowerCase()} within the database.`}
|
||||||
>
|
/>
|
||||||
<Icon
|
</TooltipHost>
|
||||||
iconName="Info"
|
</Stack>
|
||||||
className="panelInfoIcon"
|
)}
|
||||||
tabIndex={0}
|
|
||||||
ariaLabel={`Throughput configured at the database level will be shared across all ${getCollectionName(
|
|
||||||
true,
|
|
||||||
).toLocaleLowerCase()} within the database.`}
|
|
||||||
/>
|
|
||||||
</TooltipHost>
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isServerlessAccount() && this.state.isSharedThroughputChecked && (
|
{!isServerlessAccount() && this.state.isSharedThroughputChecked && (
|
||||||
<ThroughputInput
|
<ThroughputInput
|
||||||
showFreeTierExceedThroughputTooltip={this.isFreeTierAccount() && !isFirstResourceCreated}
|
showFreeTierExceedThroughputTooltip={this.isFreeTierAccount() && !isFirstResourceCreated}
|
||||||
isDatabase={true}
|
isDatabase={true}
|
||||||
isSharded={this.state.isSharded}
|
isSharded={this.state.isSharded}
|
||||||
isFreeTier={this.isFreeTierAccount()}
|
isFreeTier={this.isFreeTierAccount()}
|
||||||
isQuickstart={this.props.isQuickstart}
|
isQuickstart={this.props.isQuickstart}
|
||||||
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
|
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
|
setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
|
||||||
setIsThroughputCapExceeded={(isThroughputCapExceeded: boolean) =>
|
setIsThroughputCapExceeded={(isThroughputCapExceeded: boolean) =>
|
||||||
this.setState({ isThroughputCapExceeded })
|
this.setState({ isThroughputCapExceeded })
|
||||||
}
|
}
|
||||||
onCostAcknowledgeChange={(isAcknowledge: boolean) => (this.isCostAcknowledged = isAcknowledge)}
|
onCostAcknowledgeChange={(isAcknowledge: boolean) => (this.isCostAcknowledged = isAcknowledge)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
{!this.state.createNewDatabase && (
|
{!this.state.createNewDatabase && (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
ariaLabel="Choose an existing database"
|
ariaLabel="Choose an existing database"
|
||||||
styles={{ title: { height: 27, lineHeight: 27 }, dropdownItem: { fontSize: 12 } }}
|
styles={{ title: { height: 27, lineHeight: 27 }, dropdownItem: { fontSize: 12 } }}
|
||||||
style={{ width: 300, fontSize: 12 }}
|
style={{ width: 300, fontSize: 12 }}
|
||||||
placeholder="Choose an existing database"
|
placeholder="Choose an existing database"
|
||||||
options={this.getDatabaseOptions()}
|
options={this.getDatabaseOptions()}
|
||||||
onChange={(event: React.FormEvent<HTMLDivElement>, database: IDropdownOption) =>
|
onChange={(event: React.FormEvent<HTMLDivElement>, database: IDropdownOption) =>
|
||||||
this.setState({ selectedDatabaseId: database.key as string })
|
this.setState({ selectedDatabaseId: database.key as string })
|
||||||
}
|
}
|
||||||
defaultSelectedKey={this.props.databaseId}
|
defaultSelectedKey={this.props.databaseId}
|
||||||
responsiveMode={999}
|
responsiveMode={999}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Separator className="panelSeparator" />
|
<Separator className="panelSeparator" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
|
||||||
|
|
||||||
<Stack>
|
<Stack>
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
@@ -669,7 +666,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{!isFabricNative() && userContext.apiType === "SQL" && (
|
{userContext.apiType === "SQL" && (
|
||||||
<Stack className="panelGroupSpacing">
|
<Stack className="panelGroupSpacing">
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
styles={{ root: { padding: 0, width: 200, height: 30 }, label: { fontSize: 12 } }}
|
styles={{ root: { padding: 0, width: 200, height: 30 }, label: { fontSize: 12 } }}
|
||||||
@@ -750,7 +747,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isFabricNative() && userContext.apiType === "SQL" && (
|
{userContext.apiType === "SQL" && (
|
||||||
<Stack>
|
<Stack>
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<Text className="panelTextBold" variant="small">
|
<Text className="panelTextBold" variant="small">
|
||||||
@@ -940,7 +937,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
</CollapsibleSectionComponent>
|
</CollapsibleSectionComponent>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
{!isFabricNative() && userContext.apiType !== "Tables" && (
|
{userContext.apiType !== "Tables" && (
|
||||||
<CollapsibleSectionComponent
|
<CollapsibleSectionComponent
|
||||||
title="Advanced"
|
title="Advanced"
|
||||||
isExpandedByDefault={false}
|
isExpandedByDefault={false}
|
||||||
@@ -1263,7 +1260,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
private shouldShowCollectionThroughputInput(): boolean {
|
private shouldShowCollectionThroughputInput(): boolean {
|
||||||
if (isFabricNative() || isServerlessAccount()) {
|
if (isServerlessAccount()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1289,7 +1286,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private shouldShowAnalyticalStoreOptions(): boolean {
|
private shouldShowAnalyticalStoreOptions(): boolean {
|
||||||
if (isFabricNative() || configContext.platform === Platform.Emulator) {
|
if (configContext.platform === Platform.Emulator) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import {
|
|||||||
} from "Shared/StorageUtility";
|
} from "Shared/StorageUtility";
|
||||||
import * as StringUtility from "Shared/StringUtility";
|
import * as StringUtility from "Shared/StringUtility";
|
||||||
import { updateUserContext, userContext } from "UserContext";
|
import { updateUserContext, userContext } from "UserContext";
|
||||||
import { isDataplaneRbacSupported } from "Utils/APITypeUtils";
|
|
||||||
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
|
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
|
||||||
import { logConsoleError, logConsoleInfo } from "Utils/NotificationConsoleUtils";
|
import { logConsoleError, logConsoleInfo } from "Utils/NotificationConsoleUtils";
|
||||||
import * as PriorityBasedExecutionUtils from "Utils/PriorityBasedExecutionUtils";
|
import * as PriorityBasedExecutionUtils from "Utils/PriorityBasedExecutionUtils";
|
||||||
@@ -184,7 +183,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
const shouldShowCrossPartitionOption = userContext.apiType !== "Gremlin" && !isEmulator;
|
const shouldShowCrossPartitionOption = userContext.apiType !== "Gremlin" && !isEmulator;
|
||||||
const shouldShowParallelismOption = userContext.apiType !== "Gremlin" && !isEmulator;
|
const shouldShowParallelismOption = userContext.apiType !== "Gremlin" && !isEmulator;
|
||||||
const showEnableEntraIdRbac =
|
const showEnableEntraIdRbac =
|
||||||
isDataplaneRbacSupported(userContext.apiType) &&
|
userContext.apiType === "SQL" &&
|
||||||
userContext.authType === AuthType.AAD &&
|
userContext.authType === AuthType.AAD &&
|
||||||
configContext.platform !== Platform.Fabric &&
|
configContext.platform !== Platform.Fabric &&
|
||||||
!isEmulator;
|
!isEmulator;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
TextField,
|
TextField,
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
import { FeedbackLabels, HttpStatusCodes, NormalizedEventKey } from "Common/Constants";
|
import { HttpStatusCodes, NormalizedEventKey } from "Common/Constants";
|
||||||
import { handleError } from "Common/ErrorHandlingUtils";
|
import { handleError } from "Common/ErrorHandlingUtils";
|
||||||
import QueryError, { QueryErrorSeverity } from "Common/QueryError";
|
import QueryError, { QueryErrorSeverity } from "Common/QueryError";
|
||||||
import { createUri } from "Common/UrlUtility";
|
import { createUri } from "Common/UrlUtility";
|
||||||
@@ -393,7 +393,8 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
disabled={isGeneratingQuery}
|
disabled={isGeneratingQuery}
|
||||||
autoComplete="off"
|
autoComplete="list"
|
||||||
|
aria-expanded={showSamplePrompts}
|
||||||
placeholder="Ask a question in natural language and we’ll generate the query for you."
|
placeholder="Ask a question in natural language and we’ll generate the query for you."
|
||||||
aria-labelledby="copilot-textfield-label"
|
aria-labelledby="copilot-textfield-label"
|
||||||
onRenderSuffix={() => {
|
onRenderSuffix={() => {
|
||||||
@@ -579,7 +580,7 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
<Stack horizontal verticalAlign="center" style={{ maxHeight: 20 }}>
|
<Stack horizontal verticalAlign="center" style={{ maxHeight: 20 }}>
|
||||||
{userContext.feedbackPolicies?.policyAllowFeedback && (
|
{userContext.feedbackPolicies?.policyAllowFeedback && (
|
||||||
<Stack horizontal verticalAlign="center">
|
<Stack horizontal verticalAlign="center">
|
||||||
<Text style={{ fontSize: 12 }}>{FeedbackLabels.provideFeedback}</Text>
|
<Text style={{ fontSize: 12 }}>Provide feedback</Text>
|
||||||
{showCallout && !hideFeedbackModalForLikedQueries && (
|
{showCallout && !hideFeedbackModalForLikedQueries && (
|
||||||
<Callout
|
<Callout
|
||||||
role="status"
|
role="status"
|
||||||
@@ -629,9 +630,8 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
<IconButton
|
<IconButton
|
||||||
id="likeBtn"
|
id="likeBtn"
|
||||||
style={{ marginLeft: 10 }}
|
style={{ marginLeft: 10 }}
|
||||||
aria-label={FeedbackLabels.provideFeedback}
|
aria-label="Like"
|
||||||
role="button"
|
role="toggle"
|
||||||
title="Like"
|
|
||||||
iconProps={{ iconName: likeQuery === true ? "LikeSolid" : "Like" }}
|
iconProps={{ iconName: likeQuery === true ? "LikeSolid" : "Like" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowCallout(!likeQuery);
|
setShowCallout(!likeQuery);
|
||||||
@@ -649,9 +649,8 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
style={{ margin: "0 4px" }}
|
style={{ margin: "0 4px" }}
|
||||||
role="button"
|
role="toggle"
|
||||||
aria-label={FeedbackLabels.provideFeedback}
|
aria-label="Dislike"
|
||||||
title="Dislike"
|
|
||||||
iconProps={{ iconName: dislikeQuery === true ? "DislikeSolid" : "Dislike" }}
|
iconProps={{ iconName: dislikeQuery === true ? "DislikeSolid" : "Dislike" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
let toggleStatusValue = "Unpressed";
|
let toggleStatusValue = "Unpressed";
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
makeStyles,
|
|
||||||
Menu,
|
Menu,
|
||||||
MenuButton,
|
MenuButton,
|
||||||
MenuButtonProps,
|
MenuButtonProps,
|
||||||
@@ -8,12 +7,13 @@ import {
|
|||||||
MenuList,
|
MenuList,
|
||||||
MenuPopover,
|
MenuPopover,
|
||||||
MenuTrigger,
|
MenuTrigger,
|
||||||
|
SplitButton,
|
||||||
|
makeStyles,
|
||||||
mergeClasses,
|
mergeClasses,
|
||||||
shorthands,
|
shorthands,
|
||||||
SplitButton,
|
|
||||||
} from "@fluentui/react-components";
|
} from "@fluentui/react-components";
|
||||||
import { Add16Regular, ArrowSync12Regular, ChevronLeft12Regular, ChevronRight12Regular } from "@fluentui/react-icons";
|
import { Add16Regular, ArrowSync12Regular, ChevronLeft12Regular, ChevronRight12Regular } from "@fluentui/react-icons";
|
||||||
import { configContext, Platform } from "ConfigContext";
|
import { Platform, configContext } from "ConfigContext";
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { AddDatabasePanel } from "Explorer/Panes/AddDatabasePanel/AddDatabasePanel";
|
import { AddDatabasePanel } from "Explorer/Panes/AddDatabasePanel/AddDatabasePanel";
|
||||||
import { Tabs } from "Explorer/Tabs/Tabs";
|
import { Tabs } from "Explorer/Tabs/Tabs";
|
||||||
@@ -21,7 +21,6 @@ import { CosmosFluentProvider, cosmosShorthands, tokens } from "Explorer/Theme/T
|
|||||||
import { ResourceTree } from "Explorer/Tree/ResourceTree";
|
import { ResourceTree } from "Explorer/Tree/ResourceTree";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
import { KeyboardAction, KeyboardActionGroup, KeyboardActionHandler, useKeyboardActionGroup } from "KeyboardShortcuts";
|
import { KeyboardAction, KeyboardActionGroup, KeyboardActionHandler, useKeyboardActionGroup } from "KeyboardShortcuts";
|
||||||
import { isFabric, isFabricMirrored, isFabricNative } from "Platform/Fabric/FabricUtil";
|
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { getCollectionName, getDatabaseName } from "Utils/APITypeUtils";
|
import { getCollectionName, getDatabaseName } from "Utils/APITypeUtils";
|
||||||
import { Allotment, AllotmentHandle } from "allotment";
|
import { Allotment, AllotmentHandle } from "allotment";
|
||||||
@@ -124,7 +123,7 @@ const GlobalCommands: React.FC<GlobalCommandsProps> = ({ explorer }) => {
|
|||||||
|
|
||||||
const actions = useMemo<GlobalCommand[]>(() => {
|
const actions = useMemo<GlobalCommand[]>(() => {
|
||||||
if (
|
if (
|
||||||
(isFabric() && userContext.fabricContext?.isReadOnly) ||
|
configContext.platform === Platform.Fabric ||
|
||||||
userContext.apiType === "Postgres" ||
|
userContext.apiType === "Postgres" ||
|
||||||
userContext.apiType === "VCoreMongo"
|
userContext.apiType === "VCoreMongo"
|
||||||
) {
|
) {
|
||||||
@@ -138,15 +137,12 @@ const GlobalCommands: React.FC<GlobalCommandsProps> = ({ explorer }) => {
|
|||||||
id: "new_collection",
|
id: "new_collection",
|
||||||
label: `New ${getCollectionName()}`,
|
label: `New ${getCollectionName()}`,
|
||||||
icon: <Add16Regular />,
|
icon: <Add16Regular />,
|
||||||
onClick: () => {
|
onClick: () => explorer.onNewCollectionClicked(),
|
||||||
const databaseId = isFabricNative() ? userContext.fabricContext?.databaseName : undefined;
|
|
||||||
explorer.onNewCollectionClicked({ databaseId });
|
|
||||||
},
|
|
||||||
keyboardAction: KeyboardAction.NEW_COLLECTION,
|
keyboardAction: KeyboardAction.NEW_COLLECTION,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (configContext.platform !== Platform.Fabric && userContext.apiType !== "Tables") {
|
if (userContext.apiType !== "Tables") {
|
||||||
actions.push({
|
actions.push({
|
||||||
id: "new_database",
|
id: "new_database",
|
||||||
label: `New ${getDatabaseName()}`,
|
label: `New ${getDatabaseName()}`,
|
||||||
@@ -292,7 +288,7 @@ export const SidebarContainer: React.FC<SidebarProps> = ({ explorer }) => {
|
|||||||
}, [setLoading]);
|
}, [setLoading]);
|
||||||
|
|
||||||
const hasGlobalCommands = !(
|
const hasGlobalCommands = !(
|
||||||
isFabricMirrored() ||
|
configContext.platform === Platform.Fabric ||
|
||||||
userContext.apiType === "Postgres" ||
|
userContext.apiType === "Postgres" ||
|
||||||
userContext.apiType === "VCoreMongo"
|
userContext.apiType === "VCoreMongo"
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,173 +0,0 @@
|
|||||||
/**
|
|
||||||
* Accordion top class
|
|
||||||
*/
|
|
||||||
import { Link, makeStyles, tokens } from "@fluentui/react-components";
|
|
||||||
import { DocumentAddRegular, LinkMultipleRegular } from "@fluentui/react-icons";
|
|
||||||
import { isFabricNative } from "Platform/Fabric/FabricUtil";
|
|
||||||
import * as React from "react";
|
|
||||||
import { userContext } from "UserContext";
|
|
||||||
import CosmosDbBlackIcon from "../../../images/CosmosDB_black.svg";
|
|
||||||
import LinkIcon from "../../../images/Link_blue.svg";
|
|
||||||
import Explorer from "../Explorer";
|
|
||||||
|
|
||||||
export interface SplashScreenProps {
|
|
||||||
explorer: Explorer;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
|
||||||
homeContainer: {
|
|
||||||
width: "100%",
|
|
||||||
alignContent: "center",
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
textAlign: "center",
|
|
||||||
fontSize: "20px",
|
|
||||||
fontWeight: "bold",
|
|
||||||
},
|
|
||||||
buttonsContainer: {
|
|
||||||
width: "584px",
|
|
||||||
margin: "auto",
|
|
||||||
display: "grid",
|
|
||||||
padding: "16px",
|
|
||||||
gridTemplateColumns: "repeat(3, 1fr)",
|
|
||||||
gap: "10px",
|
|
||||||
gridAutoRows: "minmax(184px, auto)",
|
|
||||||
},
|
|
||||||
one: {
|
|
||||||
gridColumn: "1 / 3",
|
|
||||||
gridRow: "1 / 3",
|
|
||||||
"& svg": {
|
|
||||||
width: "48px",
|
|
||||||
height: "48px",
|
|
||||||
margin: "auto",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
two: {
|
|
||||||
gridColumn: "3",
|
|
||||||
gridRow: "1",
|
|
||||||
"& img": {
|
|
||||||
width: "32px",
|
|
||||||
height: "32px",
|
|
||||||
margin: "auto",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
three: {
|
|
||||||
gridColumn: "3",
|
|
||||||
gridRow: "2",
|
|
||||||
"& svg": {
|
|
||||||
width: "32px",
|
|
||||||
height: "32px",
|
|
||||||
margin: "auto",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
buttonContainer: {
|
|
||||||
height: "100%",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
border: "1px solid #e0e0e0",
|
|
||||||
cursor: "pointer",
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: tokens.colorNeutralBackground1Hover,
|
|
||||||
"border-color": tokens.colorNeutralStroke1Hover,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
buttonUpperPart: {
|
|
||||||
textAlign: "center",
|
|
||||||
flexGrow: 1,
|
|
||||||
display: "flex",
|
|
||||||
backgroundColor: "#e3f7ef",
|
|
||||||
},
|
|
||||||
buttonLowerPart: {
|
|
||||||
borderTop: "1px solid #e0e0e0",
|
|
||||||
height: "76px",
|
|
||||||
padding: "8px",
|
|
||||||
"> div:nth-child(1)": {
|
|
||||||
fontWeight: "bold",
|
|
||||||
},
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: "center",
|
|
||||||
},
|
|
||||||
footer: {
|
|
||||||
textAlign: "center",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
interface FabricHomeScreenButtonProps {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
icon: JSX.Element;
|
|
||||||
onClick?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FabricHomeScreenButton: React.FC<FabricHomeScreenButtonProps & { className: string }> = ({
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
icon,
|
|
||||||
className,
|
|
||||||
onClick,
|
|
||||||
}) => {
|
|
||||||
const styles = useStyles();
|
|
||||||
|
|
||||||
// TODO Make this a11y copmliant: aria-label for icon
|
|
||||||
return (
|
|
||||||
<div role="button" className={`${styles.buttonContainer} ${className}`} onClick={onClick}>
|
|
||||||
<div className={styles.buttonUpperPart}>{icon}</div>
|
|
||||||
<div className={styles.buttonLowerPart}>
|
|
||||||
<div>{title}</div>
|
|
||||||
<div>{description}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FabricHomeScreen: React.FC<SplashScreenProps> = (props: SplashScreenProps) => {
|
|
||||||
const styles = useStyles();
|
|
||||||
const getSplashScreenButtons = (): JSX.Element => {
|
|
||||||
const buttons: FabricHomeScreenButtonProps[] = [
|
|
||||||
{
|
|
||||||
title: "New container",
|
|
||||||
description: "Create a destination container to store your data",
|
|
||||||
icon: <DocumentAddRegular />,
|
|
||||||
onClick: () => {
|
|
||||||
const databaseId = isFabricNative() ? userContext.fabricContext?.databaseName : undefined;
|
|
||||||
props.explorer.onNewCollectionClicked({ databaseId });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Sample data",
|
|
||||||
description: "Automatically load sample data in your database",
|
|
||||||
icon: <img src={CosmosDbBlackIcon} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "App development",
|
|
||||||
description: "Start here to use an SDK to build your apps",
|
|
||||||
icon: <LinkMultipleRegular />,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.buttonsContainer}>
|
|
||||||
<FabricHomeScreenButton className={styles.one} {...buttons[0]} />
|
|
||||||
<FabricHomeScreenButton className={styles.two} {...buttons[1]} />
|
|
||||||
<FabricHomeScreenButton className={styles.three} {...buttons[2]} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const title = "Build your database";
|
|
||||||
return (
|
|
||||||
<div className={styles.homeContainer}>
|
|
||||||
<div className={styles.title} role="heading" aria-label={title}>
|
|
||||||
{title}
|
|
||||||
</div>
|
|
||||||
{getSplashScreenButtons()}
|
|
||||||
<div className={styles.footer}>
|
|
||||||
Need help?{" "}
|
|
||||||
<Link href="https://cosmos.azure.com/docs" target="_blank">
|
|
||||||
Learn more <img src={LinkIcon} alt="Learn more" />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -2,7 +2,6 @@ import { FeedResponse, ItemDefinition, Resource } from "@azure/cosmos";
|
|||||||
import { waitFor } from "@testing-library/react";
|
import { waitFor } from "@testing-library/react";
|
||||||
import { deleteDocuments } from "Common/dataAccess/deleteDocument";
|
import { deleteDocuments } from "Common/dataAccess/deleteDocument";
|
||||||
import { Platform, updateConfigContext } from "ConfigContext";
|
import { Platform, updateConfigContext } from "ConfigContext";
|
||||||
import { CosmosDbArtifactType } from "Contracts/FabricMessagesContract";
|
|
||||||
import { useDialog } from "Explorer/Controls/Dialog";
|
import { useDialog } from "Explorer/Controls/Dialog";
|
||||||
import { EditorReactProps } from "Explorer/Controls/Editor/EditorReact";
|
import { EditorReactProps } from "Explorer/Controls/Editor/EditorReact";
|
||||||
import { ProgressModalDialog } from "Explorer/Controls/ProgressModalDialog";
|
import { ProgressModalDialog } from "Explorer/Controls/ProgressModalDialog";
|
||||||
@@ -342,15 +341,10 @@ describe("Documents tab (noSql API)", () => {
|
|||||||
updateConfigContext({ platform: Platform.Fabric });
|
updateConfigContext({ platform: Platform.Fabric });
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
fabricContext: {
|
fabricContext: {
|
||||||
databaseName: "database",
|
connectionId: "test",
|
||||||
artifactInfo: {
|
databaseConnectionInfo: undefined,
|
||||||
connectionId: "test",
|
|
||||||
resourceTokenInfo: undefined,
|
|
||||||
},
|
|
||||||
artifactType: CosmosDbArtifactType.MIRRORED_KEY,
|
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
fabricClientRpcVersion: "rpcVersion",
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
import { queryDocuments } from "Common/dataAccess/queryDocuments";
|
import { queryDocuments } from "Common/dataAccess/queryDocuments";
|
||||||
import { readDocument } from "Common/dataAccess/readDocument";
|
import { readDocument } from "Common/dataAccess/readDocument";
|
||||||
import { updateDocument } from "Common/dataAccess/updateDocument";
|
import { updateDocument } from "Common/dataAccess/updateDocument";
|
||||||
|
import { Platform, configContext } from "ConfigContext";
|
||||||
import { ActionType, OpenCollectionTab, TabKind } from "Contracts/ActionContracts";
|
import { ActionType, OpenCollectionTab, TabKind } from "Contracts/ActionContracts";
|
||||||
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
|
||||||
import { useDialog } from "Explorer/Controls/Dialog";
|
import { useDialog } from "Explorer/Controls/Dialog";
|
||||||
@@ -42,7 +43,6 @@ import { usePrevious } from "Explorer/Tabs/DocumentsTabV2/SelectionHelper";
|
|||||||
import { CosmosFluentProvider, LayoutConstants, cosmosShorthands, tokens } from "Explorer/Theme/ThemeUtil";
|
import { CosmosFluentProvider, LayoutConstants, cosmosShorthands, tokens } from "Explorer/Theme/ThemeUtil";
|
||||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
import { KeyboardAction, KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
|
import { KeyboardAction, KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
|
||||||
import { isFabric } from "Platform/Fabric/FabricUtil";
|
|
||||||
import { QueryConstants } from "Shared/Constants";
|
import { QueryConstants } from "Shared/Constants";
|
||||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||||
@@ -344,7 +344,7 @@ export const getTabsButtons = ({
|
|||||||
onRevertExistingDocumentClick,
|
onRevertExistingDocumentClick,
|
||||||
onDeleteExistingDocumentsClick,
|
onDeleteExistingDocumentsClick,
|
||||||
}: ButtonsDependencies): CommandButtonComponentProps[] => {
|
}: ButtonsDependencies): CommandButtonComponentProps[] => {
|
||||||
if (isFabric() && userContext.fabricContext?.isReadOnly) {
|
if (configContext.platform === Platform.Fabric && userContext.fabricContext?.isReadOnly) {
|
||||||
// All the following buttons require write access
|
// All the following buttons require write access
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -1028,7 +1028,6 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
);
|
);
|
||||||
|
|
||||||
const selectedDocumentId = documentIds[clickedRowIndex as number];
|
const selectedDocumentId = documentIds[clickedRowIndex as number];
|
||||||
const originalPartitionKeyValue = selectedDocumentId.partitionKeyValue;
|
|
||||||
selectedDocumentId.partitionKeyValue = partitionKeyValueArray;
|
selectedDocumentId.partitionKeyValue = partitionKeyValueArray;
|
||||||
|
|
||||||
onExecutionErrorChange(false);
|
onExecutionErrorChange(false);
|
||||||
@@ -1064,14 +1063,9 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
setColumnDefinitionsFromDocument(documentContent);
|
setColumnDefinitionsFromDocument(documentContent);
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
// in case of any kind of failures of accidently changing partition key, restore the original
|
|
||||||
// so that when user navigates away from current document and comes back,
|
|
||||||
// it doesnt fail to load due to using the invalid partition keys
|
|
||||||
selectedDocumentId.partitionKeyValue = originalPartitionKeyValue;
|
|
||||||
onExecutionErrorChange(true);
|
onExecutionErrorChange(true);
|
||||||
const errorMessage = getErrorMessage(error);
|
const errorMessage = getErrorMessage(error);
|
||||||
useDialog.getState().showOkModalDialog("Update document failed", errorMessage);
|
useDialog.getState().showOkModalDialog("Update document failed", errorMessage);
|
||||||
|
|
||||||
TelemetryProcessor.traceFailure(
|
TelemetryProcessor.traceFailure(
|
||||||
Action.UpdateDocument,
|
Action.UpdateDocument,
|
||||||
{
|
{
|
||||||
@@ -2142,7 +2136,8 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
selectedColumnIds={selectedColumnIds}
|
selectedColumnIds={selectedColumnIds}
|
||||||
columnDefinitions={columnDefinitions}
|
columnDefinitions={columnDefinitions}
|
||||||
isRowSelectionDisabled={
|
isRowSelectionDisabled={
|
||||||
isBulkDeleteDisabled || (isFabric() && userContext.fabricContext?.isReadOnly)
|
isBulkDeleteDisabled ||
|
||||||
|
(configContext.platform === Platform.Fabric && userContext.fabricContext?.isReadOnly)
|
||||||
}
|
}
|
||||||
onColumnSelectionChange={onColumnSelectionChange}
|
onColumnSelectionChange={onColumnSelectionChange}
|
||||||
defaultColumnSelection={getInitialColumnSelection()}
|
defaultColumnSelection={getInitialColumnSelection()}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { CollectionTabKind } from "Contracts/ViewModels";
|
|||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import { QueryCopilotTab } from "Explorer/QueryCopilot/QueryCopilotTab";
|
import { QueryCopilotTab } from "Explorer/QueryCopilot/QueryCopilotTab";
|
||||||
import { FabricHomeScreen } from "Explorer/SplashScreen/FabricHome";
|
|
||||||
import { SplashScreen } from "Explorer/SplashScreen/SplashScreen";
|
import { SplashScreen } from "Explorer/SplashScreen/SplashScreen";
|
||||||
import { ConnectTab } from "Explorer/Tabs/ConnectTab";
|
import { ConnectTab } from "Explorer/Tabs/ConnectTab";
|
||||||
import { PostgresConnectTab } from "Explorer/Tabs/PostgresConnectTab";
|
import { PostgresConnectTab } from "Explorer/Tabs/PostgresConnectTab";
|
||||||
@@ -10,7 +9,6 @@ import { QuickstartTab } from "Explorer/Tabs/QuickstartTab";
|
|||||||
import { VcoreMongoConnectTab } from "Explorer/Tabs/VCoreMongoConnectTab";
|
import { VcoreMongoConnectTab } from "Explorer/Tabs/VCoreMongoConnectTab";
|
||||||
import { VcoreMongoQuickstartTab } from "Explorer/Tabs/VCoreMongoQuickstartTab";
|
import { VcoreMongoQuickstartTab } from "Explorer/Tabs/VCoreMongoQuickstartTab";
|
||||||
import { KeyboardAction, KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
|
import { KeyboardAction, KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
|
||||||
import { isFabricNative } from "Platform/Fabric/FabricUtil";
|
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
@@ -273,11 +271,7 @@ const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): J
|
|||||||
<ConnectTab />
|
<ConnectTab />
|
||||||
);
|
);
|
||||||
case ReactTabKind.Home:
|
case ReactTabKind.Home:
|
||||||
if (isFabricNative()) {
|
return <SplashScreen explorer={explorer} />;
|
||||||
return <FabricHomeScreen explorer={explorer} />;
|
|
||||||
} else {
|
|
||||||
return <SplashScreen explorer={explorer} />;
|
|
||||||
}
|
|
||||||
case ReactTabKind.Quickstart:
|
case ReactTabKind.Quickstart:
|
||||||
return userContext.apiType === "VCoreMongo" ? (
|
return userContext.apiType === "VCoreMongo" ? (
|
||||||
<VcoreMongoQuickstartTab explorer={explorer} />
|
<VcoreMongoQuickstartTab explorer={explorer} />
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { checkFirewallRules } from "Explorer/Tabs/Shared/CheckFirewallRules";
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import FirewallRuleScreenshot from "../../../images/firewallRule.png";
|
import FirewallRuleScreenshot from "../../../images/firewallRule.png";
|
||||||
import VcoreFirewallRuleScreenshot from "../../../images/vcoreMongoFirewallRule.png";
|
|
||||||
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
@@ -43,11 +42,7 @@ class NotebookTerminalComponentAdapter implements ReactAdapter {
|
|||||||
return (
|
return (
|
||||||
<QuickstartFirewallNotification
|
<QuickstartFirewallNotification
|
||||||
messageType={MessageTypes.OpenPostgresNetworkingBlade}
|
messageType={MessageTypes.OpenPostgresNetworkingBlade}
|
||||||
screenshot={
|
screenshot={FirewallRuleScreenshot}
|
||||||
this.kind === ViewModels.TerminalKind.Mongo || this.kind === ViewModels.TerminalKind.VCoreMongo
|
|
||||||
? VcoreFirewallRuleScreenshot
|
|
||||||
: FirewallRuleScreenshot
|
|
||||||
}
|
|
||||||
shellName={this.getShellNameForDisplay(this.kind)}
|
shellName={this.getShellNameForDisplay(this.kind)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Resource, StoredProcedureDefinition, TriggerDefinition, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
import { Resource, StoredProcedureDefinition, TriggerDefinition, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
||||||
import { DocumentsTabV2 } from "Explorer/Tabs/DocumentsTabV2/DocumentsTabV2";
|
import { DocumentsTabV2 } from "Explorer/Tabs/DocumentsTabV2/DocumentsTabV2";
|
||||||
import { isFabricMirrored } from "Platform/Fabric/FabricUtil";
|
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
@@ -35,6 +34,7 @@ import QueryTablesTab from "../Tabs/QueryTablesTab";
|
|||||||
import { CollectionSettingsTabV2 } from "../Tabs/SettingsTabV2";
|
import { CollectionSettingsTabV2 } from "../Tabs/SettingsTabV2";
|
||||||
import { useDatabases } from "../useDatabases";
|
import { useDatabases } from "../useDatabases";
|
||||||
import { useSelectedNode } from "../useSelectedNode";
|
import { useSelectedNode } from "../useSelectedNode";
|
||||||
|
import { Platform, configContext } from "./../../ConfigContext";
|
||||||
import ConflictId from "./ConflictId";
|
import ConflictId from "./ConflictId";
|
||||||
import DocumentId from "./DocumentId";
|
import DocumentId from "./DocumentId";
|
||||||
import StoredProcedure from "./StoredProcedure";
|
import StoredProcedure from "./StoredProcedure";
|
||||||
@@ -210,7 +210,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const showScriptsMenus: boolean =
|
const showScriptsMenus: boolean =
|
||||||
!isFabricMirrored() && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin");
|
configContext.platform != Platform.Fabric && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin");
|
||||||
this.showStoredProcedures = ko.observable<boolean>(showScriptsMenus);
|
this.showStoredProcedures = ko.observable<boolean>(showScriptsMenus);
|
||||||
this.showTriggers = ko.observable<boolean>(showScriptsMenus);
|
this.showTriggers = ko.observable<boolean>(showScriptsMenus);
|
||||||
this.showUserDefinedFunctions = ko.observable<boolean>(showScriptsMenus);
|
this.showUserDefinedFunctions = ko.observable<boolean>(showScriptsMenus);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Tree, TreeItemValue, TreeOpenChangeData, TreeOpenChangeEvent } from "@fluentui/react-components";
|
import { Tree, TreeItemValue, TreeOpenChangeData, TreeOpenChangeEvent } from "@fluentui/react-components";
|
||||||
import { Home16Regular } from "@fluentui/react-icons";
|
import { Home16Regular } from "@fluentui/react-icons";
|
||||||
import { AuthType } from "AuthType";
|
import { AuthType } from "AuthType";
|
||||||
|
import { Platform, configContext } from "ConfigContext";
|
||||||
import { useTreeStyles } from "Explorer/Controls/TreeComponent/Styles";
|
import { useTreeStyles } from "Explorer/Controls/TreeComponent/Styles";
|
||||||
import { TreeNode, TreeNodeComponent } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
|
import { TreeNode, TreeNodeComponent } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
|
||||||
import {
|
import {
|
||||||
@@ -10,7 +11,6 @@ import {
|
|||||||
} from "Explorer/Tree/treeNodeUtil";
|
} from "Explorer/Tree/treeNodeUtil";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
import { isFabricMirrored } from "Platform/Fabric/FabricUtil";
|
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||||
@@ -76,22 +76,23 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ explorer }: Resource
|
|||||||
: [];
|
: [];
|
||||||
}, [isSampleDataEnabled, sampleDataResourceTokenCollection]);
|
}, [isSampleDataEnabled, sampleDataResourceTokenCollection]);
|
||||||
|
|
||||||
const headerNodes: TreeNode[] = isFabricMirrored()
|
const headerNodes: TreeNode[] =
|
||||||
? []
|
configContext.platform === Platform.Fabric
|
||||||
: [
|
? []
|
||||||
{
|
: [
|
||||||
id: "home",
|
{
|
||||||
iconSrc: <Home16Regular />,
|
id: "home",
|
||||||
label: "Home",
|
iconSrc: <Home16Regular />,
|
||||||
isSelected: () =>
|
label: "Home",
|
||||||
useSelectedNode.getState().selectedNode === undefined &&
|
isSelected: () =>
|
||||||
useTabs.getState().activeReactTab === ReactTabKind.Home,
|
useSelectedNode.getState().selectedNode === undefined &&
|
||||||
onClick: () => {
|
useTabs.getState().activeReactTab === ReactTabKind.Home,
|
||||||
useSelectedNode.getState().setSelectedNode(undefined);
|
onClick: () => {
|
||||||
useTabs.getState().openAndActivateReactTab(ReactTabKind.Home);
|
useSelectedNode.getState().setSelectedNode(undefined);
|
||||||
|
useTabs.getState().openAndActivateReactTab(ReactTabKind.Home);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
];
|
||||||
];
|
|
||||||
|
|
||||||
const rootNodes: TreeNode[] = useMemo(() => {
|
const rootNodes: TreeNode[] = useMemo(() => {
|
||||||
if (sampleDataNodes.length > 0) {
|
if (sampleDataNodes.length > 0) {
|
||||||
|
|||||||
@@ -740,7 +740,7 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Mo
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`createDatabaseTreeNodes generates the correct tree structure for the SQL API, on Fabric non read-only 1`] = `
|
exports[`createDatabaseTreeNodes generates the correct tree structure for the SQL API, on Fabric 1`] = `
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"children": [
|
"children": [
|
||||||
@@ -753,12 +753,6 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
|
|||||||
"label": "New SQL Query",
|
"label": "New SQL Query",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"iconSrc": {},
|
|
||||||
"label": "Delete Container",
|
|
||||||
"onClick": [Function],
|
|
||||||
"styleClass": "deleteCollectionMenuItem",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
"iconSrc": <DocumentMultipleRegular
|
"iconSrc": <DocumentMultipleRegular
|
||||||
fontSize={16}
|
fontSize={16}
|
||||||
@@ -780,12 +774,6 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
|
|||||||
"label": "New SQL Query",
|
"label": "New SQL Query",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"iconSrc": {},
|
|
||||||
"label": "Delete Container",
|
|
||||||
"onClick": [Function],
|
|
||||||
"styleClass": "deleteCollectionMenuItem",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
"iconSrc": <DocumentMultipleRegular
|
"iconSrc": <DocumentMultipleRegular
|
||||||
fontSize={16}
|
fontSize={16}
|
||||||
@@ -834,12 +822,6 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
|
|||||||
"label": "New SQL Query",
|
"label": "New SQL Query",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"iconSrc": {},
|
|
||||||
"label": "Delete Container",
|
|
||||||
"onClick": [Function],
|
|
||||||
"styleClass": "deleteCollectionMenuItem",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
"iconSrc": <DocumentMultipleRegular
|
"iconSrc": <DocumentMultipleRegular
|
||||||
fontSize={16}
|
fontSize={16}
|
||||||
@@ -888,12 +870,6 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
|
|||||||
"label": "New SQL Query",
|
"label": "New SQL Query",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"iconSrc": {},
|
|
||||||
"label": "Delete Container",
|
|
||||||
"onClick": [Function],
|
|
||||||
"styleClass": "deleteCollectionMenuItem",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
"iconSrc": <DocumentMultipleRegular
|
"iconSrc": <DocumentMultipleRegular
|
||||||
fontSize={16}
|
fontSize={16}
|
||||||
@@ -939,145 +915,6 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`createDatabaseTreeNodes generates the correct tree structure for the SQL API, on Fabric read-only 1`] = `
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"children": undefined,
|
|
||||||
"className": "collectionNode",
|
|
||||||
"contextMenu": [
|
|
||||||
{
|
|
||||||
"iconSrc": {},
|
|
||||||
"label": "New SQL Query",
|
|
||||||
"onClick": [Function],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"iconSrc": <DocumentMultipleRegular
|
|
||||||
fontSize={16}
|
|
||||||
/>,
|
|
||||||
"isExpanded": true,
|
|
||||||
"isSelected": [Function],
|
|
||||||
"label": "standardCollection",
|
|
||||||
"onClick": [Function],
|
|
||||||
"onCollapsed": [Function],
|
|
||||||
"onContextMenuOpen": [Function],
|
|
||||||
"onExpanded": [Function],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"children": undefined,
|
|
||||||
"className": "collectionNode",
|
|
||||||
"contextMenu": [
|
|
||||||
{
|
|
||||||
"iconSrc": {},
|
|
||||||
"label": "New SQL Query",
|
|
||||||
"onClick": [Function],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"iconSrc": <DocumentMultipleRegular
|
|
||||||
fontSize={16}
|
|
||||||
/>,
|
|
||||||
"isExpanded": true,
|
|
||||||
"isSelected": [Function],
|
|
||||||
"label": "conflictsCollection",
|
|
||||||
"onClick": [Function],
|
|
||||||
"onCollapsed": [Function],
|
|
||||||
"onContextMenuOpen": [Function],
|
|
||||||
"onExpanded": [Function],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"className": "databaseNode",
|
|
||||||
"contextMenu": undefined,
|
|
||||||
"iconSrc": <DatabaseRegular
|
|
||||||
fontSize={16}
|
|
||||||
/>,
|
|
||||||
"isExpanded": true,
|
|
||||||
"isSelected": [Function],
|
|
||||||
"label": "standardDb",
|
|
||||||
"onCollapsed": [Function],
|
|
||||||
"onContextMenuOpen": [Function],
|
|
||||||
"onExpanded": [Function],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"children": undefined,
|
|
||||||
"className": "collectionNode",
|
|
||||||
"contextMenu": [
|
|
||||||
{
|
|
||||||
"iconSrc": {},
|
|
||||||
"label": "New SQL Query",
|
|
||||||
"onClick": [Function],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"iconSrc": <DocumentMultipleRegular
|
|
||||||
fontSize={16}
|
|
||||||
/>,
|
|
||||||
"isExpanded": true,
|
|
||||||
"isSelected": [Function],
|
|
||||||
"label": "sampleItemsCollection",
|
|
||||||
"onClick": [Function],
|
|
||||||
"onCollapsed": [Function],
|
|
||||||
"onContextMenuOpen": [Function],
|
|
||||||
"onExpanded": [Function],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"className": "databaseNode",
|
|
||||||
"contextMenu": undefined,
|
|
||||||
"iconSrc": <DatabaseRegular
|
|
||||||
fontSize={16}
|
|
||||||
/>,
|
|
||||||
"isExpanded": true,
|
|
||||||
"isSelected": [Function],
|
|
||||||
"label": "sharedDatabase",
|
|
||||||
"onCollapsed": [Function],
|
|
||||||
"onContextMenuOpen": [Function],
|
|
||||||
"onExpanded": [Function],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"children": undefined,
|
|
||||||
"className": "collectionNode",
|
|
||||||
"contextMenu": [
|
|
||||||
{
|
|
||||||
"iconSrc": {},
|
|
||||||
"label": "New SQL Query",
|
|
||||||
"onClick": [Function],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"iconSrc": <DocumentMultipleRegular
|
|
||||||
fontSize={16}
|
|
||||||
/>,
|
|
||||||
"isExpanded": true,
|
|
||||||
"isSelected": [Function],
|
|
||||||
"label": "schemaCollection",
|
|
||||||
"onClick": [Function],
|
|
||||||
"onCollapsed": [Function],
|
|
||||||
"onContextMenuOpen": [Function],
|
|
||||||
"onExpanded": [Function],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"className": "loadMoreNode",
|
|
||||||
"label": "load more",
|
|
||||||
"onClick": [Function],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"className": "databaseNode",
|
|
||||||
"contextMenu": undefined,
|
|
||||||
"iconSrc": <DatabaseRegular
|
|
||||||
fontSize={16}
|
|
||||||
/>,
|
|
||||||
"isExpanded": true,
|
|
||||||
"isSelected": [Function],
|
|
||||||
"label": "giganticDatabase",
|
|
||||||
"onCollapsed": [Function],
|
|
||||||
"onContextMenuOpen": [Function],
|
|
||||||
"onExpanded": [Function],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`createDatabaseTreeNodes generates the correct tree structure for the SQL API, on Portal 1`] = `
|
exports[`createDatabaseTreeNodes generates the correct tree structure for the SQL API, on Portal 1`] = `
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@@ -1135,7 +972,7 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
"isSelected": [Function],
|
"isSelected": [Function],
|
||||||
"label": "mockSproc4",
|
"label": "mockSproc3",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1153,7 +990,7 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
"isSelected": [Function],
|
"isSelected": [Function],
|
||||||
"label": "mockUdf4",
|
"label": "mockUdf3",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1171,7 +1008,7 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
"isSelected": [Function],
|
"isSelected": [Function],
|
||||||
"label": "mockTrigger4",
|
"label": "mockTrigger3",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { CapabilityNames } from "Common/Constants";
|
import { CapabilityNames } from "Common/Constants";
|
||||||
import { Platform, updateConfigContext } from "ConfigContext";
|
import { Platform, updateConfigContext } from "ConfigContext";
|
||||||
import { CosmosDbArtifactType } from "Contracts/FabricMessagesContract";
|
|
||||||
import { TreeNode } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
|
import { TreeNode } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
@@ -17,7 +16,7 @@ import {
|
|||||||
} from "Explorer/Tree/treeNodeUtil";
|
} from "Explorer/Tree/treeNodeUtil";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
import { FabricContext, updateUserContext, UserContext } from "UserContext";
|
import { updateUserContext } from "UserContext";
|
||||||
import PromiseSource from "Utils/PromiseSource";
|
import PromiseSource from "Utils/PromiseSource";
|
||||||
import { useSidePanel } from "hooks/useSidePanel";
|
import { useSidePanel } from "hooks/useSidePanel";
|
||||||
import { useTabs } from "hooks/useTabs";
|
import { useTabs } from "hooks/useTabs";
|
||||||
@@ -361,30 +360,9 @@ describe("createDatabaseTreeNodes", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each<[string, Platform, boolean, Partial<DataModels.DatabaseAccountExtendedProperties>, Partial<UserContext>]>([
|
it.each<[string, Platform, boolean, Partial<DataModels.DatabaseAccountExtendedProperties>]>([
|
||||||
[
|
["the SQL API, on Fabric", Platform.Fabric, false, { capabilities: [], enableMultipleWriteLocations: true }],
|
||||||
"the SQL API, on Fabric read-only",
|
["the SQL API, on Portal", Platform.Portal, false, { capabilities: [], enableMultipleWriteLocations: true }],
|
||||||
Platform.Fabric,
|
|
||||||
false,
|
|
||||||
{ capabilities: [], enableMultipleWriteLocations: true },
|
|
||||||
{ fabricContext: { isReadOnly: true } as FabricContext<CosmosDbArtifactType> },
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"the SQL API, on Fabric non read-only",
|
|
||||||
Platform.Fabric,
|
|
||||||
false,
|
|
||||||
{ capabilities: [], enableMultipleWriteLocations: true },
|
|
||||||
{ fabricContext: { isReadOnly: false } as FabricContext<CosmosDbArtifactType> },
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"the SQL API, on Portal",
|
|
||||||
Platform.Portal,
|
|
||||||
false,
|
|
||||||
{ capabilities: [], enableMultipleWriteLocations: true },
|
|
||||||
{
|
|
||||||
fabricContext: undefined,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
"the Cassandra API, serverless, on Hosted",
|
"the Cassandra API, serverless, on Hosted",
|
||||||
Platform.Hosted,
|
Platform.Hosted,
|
||||||
@@ -395,7 +373,6 @@ describe("createDatabaseTreeNodes", () => {
|
|||||||
{ name: CapabilityNames.EnableServerless, description: "" },
|
{ name: CapabilityNames.EnableServerless, description: "" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ fabricContext: undefined },
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"the Mongo API, with Notebooks and Phoenix features, on Emulator",
|
"the Mongo API, with Notebooks and Phoenix features, on Emulator",
|
||||||
@@ -404,31 +381,26 @@ describe("createDatabaseTreeNodes", () => {
|
|||||||
{
|
{
|
||||||
capabilities: [{ name: CapabilityNames.EnableMongo, description: "" }],
|
capabilities: [{ name: CapabilityNames.EnableMongo, description: "" }],
|
||||||
},
|
},
|
||||||
{ fabricContext: undefined },
|
|
||||||
],
|
],
|
||||||
])(
|
])("generates the correct tree structure for %s", (_, platform, isNotebookEnabled, dbAccountProperties) => {
|
||||||
"generates the correct tree structure for %s",
|
useNotebook.setState({ isPhoenixFeatures: isNotebookEnabled });
|
||||||
(_, platform, isNotebookEnabled, dbAccountProperties, userContext) => {
|
updateConfigContext({ platform });
|
||||||
useNotebook.setState({ isPhoenixFeatures: isNotebookEnabled });
|
updateUserContext({
|
||||||
updateConfigContext({ platform });
|
databaseAccount: {
|
||||||
updateUserContext({
|
properties: {
|
||||||
...userContext,
|
enableMultipleWriteLocations: true,
|
||||||
databaseAccount: {
|
...dbAccountProperties,
|
||||||
properties: {
|
},
|
||||||
enableMultipleWriteLocations: true,
|
} as unknown as DataModels.DatabaseAccount,
|
||||||
...dbAccountProperties,
|
});
|
||||||
},
|
const nodes = createDatabaseTreeNodes(
|
||||||
} as unknown as DataModels.DatabaseAccount,
|
explorer,
|
||||||
});
|
isNotebookEnabled,
|
||||||
const nodes = createDatabaseTreeNodes(
|
useDatabases.getState().databases,
|
||||||
explorer,
|
refreshActiveTab,
|
||||||
isNotebookEnabled,
|
);
|
||||||
useDatabases.getState().databases,
|
expect(nodes).toMatchSnapshot();
|
||||||
refreshActiveTab,
|
});
|
||||||
);
|
|
||||||
expect(nodes).toMatchSnapshot();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// The above tests focused on the tree structure. The below tests focus on some core behaviors of the nodes.
|
// The above tests focused on the tree structure. The below tests focus on some core behaviors of the nodes.
|
||||||
// They are not exhaustive, because exhaustive tests here require a lot of mocking and can become very brittle.
|
// They are not exhaustive, because exhaustive tests here require a lot of mocking and can become very brittle.
|
||||||
@@ -579,18 +551,7 @@ describe("createDatabaseTreeNodes", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[
|
["in Fabric", () => updateConfigContext({ platform: Platform.Fabric })],
|
||||||
"in Fabric",
|
|
||||||
() => {
|
|
||||||
updateConfigContext({ platform: Platform.Fabric });
|
|
||||||
updateUserContext({
|
|
||||||
fabricContext: {
|
|
||||||
artifactType: CosmosDbArtifactType.MIRRORED_KEY,
|
|
||||||
isReadOnly: true,
|
|
||||||
} as FabricContext<CosmosDbArtifactType>,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
"for Cassandra API",
|
"for Cassandra API",
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import StoredProcedure from "Explorer/Tree/StoredProcedure";
|
|||||||
import Trigger from "Explorer/Tree/Trigger";
|
import Trigger from "Explorer/Tree/Trigger";
|
||||||
import UserDefinedFunction from "Explorer/Tree/UserDefinedFunction";
|
import UserDefinedFunction from "Explorer/Tree/UserDefinedFunction";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
import { isFabricMirrored } from "Platform/Fabric/FabricUtil";
|
|
||||||
import { getItemName } from "Utils/APITypeUtils";
|
import { getItemName } from "Utils/APITypeUtils";
|
||||||
import { isServerlessAccount } from "Utils/CapabilityUtils";
|
import { isServerlessAccount } from "Utils/CapabilityUtils";
|
||||||
import { useTabs } from "hooks/useTabs";
|
import { useTabs } from "hooks/useTabs";
|
||||||
@@ -23,7 +22,9 @@ import { useNotebook } from "../Notebook/useNotebook";
|
|||||||
import { useSelectedNode } from "../useSelectedNode";
|
import { useSelectedNode } from "../useSelectedNode";
|
||||||
|
|
||||||
export const shouldShowScriptNodes = (): boolean => {
|
export const shouldShowScriptNodes = (): boolean => {
|
||||||
return !isFabricMirrored() && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin");
|
return (
|
||||||
|
configContext.platform !== Platform.Fabric && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin")
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const TreeDatabaseIcon = <DatabaseRegular fontSize={16} />;
|
const TreeDatabaseIcon = <DatabaseRegular fontSize={16} />;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Action } from "Shared/Telemetry/TelemetryConstants";
|
|||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointUtils";
|
import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointUtils";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import promiseRetry, { AbortError, Options } from "p-retry";
|
import promiseRetry, { AbortError } from "p-retry";
|
||||||
import {
|
import {
|
||||||
Areas,
|
Areas,
|
||||||
ConnectionStatusType,
|
ConnectionStatusType,
|
||||||
@@ -35,26 +35,21 @@ import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
|||||||
export class PhoenixClient {
|
export class PhoenixClient {
|
||||||
private armResourceId: string;
|
private armResourceId: string;
|
||||||
private containerHealthHandler: NodeJS.Timeout;
|
private containerHealthHandler: NodeJS.Timeout;
|
||||||
private retryOptions: Options = {
|
private retryOptions: promiseRetry.Options = {
|
||||||
retries: Notebook.retryAttempts,
|
retries: Notebook.retryAttempts,
|
||||||
maxTimeout: Notebook.retryAttemptDelayMs,
|
maxTimeout: Notebook.retryAttemptDelayMs,
|
||||||
minTimeout: Notebook.retryAttemptDelayMs,
|
minTimeout: Notebook.retryAttemptDelayMs,
|
||||||
};
|
};
|
||||||
private abortController: AbortController;
|
|
||||||
private abortSignal: AbortSignal;
|
|
||||||
|
|
||||||
constructor(armResourceId: string) {
|
constructor(armResourceId: string) {
|
||||||
this.armResourceId = armResourceId;
|
this.armResourceId = armResourceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async allocateContainer(provisionData: IProvisionData): Promise<IResponse<IPhoenixServiceInfo>> {
|
public async allocateContainer(provisionData: IProvisionData): Promise<IResponse<IPhoenixServiceInfo>> {
|
||||||
this.initializeCancelEventListener();
|
|
||||||
|
|
||||||
return promiseRetry(() => this.executeContainerAssignmentOperation(provisionData, "allocate"), {
|
return promiseRetry(() => this.executeContainerAssignmentOperation(provisionData, "allocate"), {
|
||||||
retries: 4,
|
retries: 4,
|
||||||
maxTimeout: 20000,
|
maxTimeout: 20000,
|
||||||
minTimeout: 20000,
|
minTimeout: 20000,
|
||||||
signal: this.abortSignal,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,17 +270,6 @@ export class PhoenixClient {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeCancelEventListener(): void {
|
|
||||||
this.abortController = new AbortController();
|
|
||||||
this.abortSignal = this.abortController.signal;
|
|
||||||
|
|
||||||
document.addEventListener("keydown", (event: KeyboardEvent) => {
|
|
||||||
if (event.ctrlKey && (event.key === "c" || event.key === "z")) {
|
|
||||||
this.abortController.abort(new AbortError("Request canceled"));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConvertToForbiddenErrorString(jsonData: IPhoenixError): string {
|
public ConvertToForbiddenErrorString(jsonData: IPhoenixError): string {
|
||||||
const errInfo = jsonData;
|
const errInfo = jsonData;
|
||||||
switch (errInfo?.type) {
|
switch (errInfo?.type) {
|
||||||
|
|||||||
@@ -1,112 +1,56 @@
|
|||||||
import { sendCachedDataMessage } from "Common/MessageHandler";
|
import { sendCachedDataMessage } from "Common/MessageHandler";
|
||||||
import { configContext, Platform } from "ConfigContext";
|
|
||||||
import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
|
import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
|
||||||
import { CosmosDbArtifactType, ResourceTokenInfo } from "Contracts/FabricMessagesContract";
|
import { FabricDatabaseConnectionInfo } from "Contracts/FabricMessagesContract";
|
||||||
import { FabricArtifactInfo, updateUserContext, userContext } from "UserContext";
|
import { updateUserContext, userContext } from "UserContext";
|
||||||
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
||||||
|
|
||||||
const TOKEN_VALIDITY_MS = (3600 - 600) * 1000; // 1 hour minus 10 minutes to be safe
|
const TOKEN_VALIDITY_MS = (3600 - 600) * 1000; // 1 hour minus 10 minutes to be safe
|
||||||
const DEBOUNCE_DELAY_MS = 1000 * 20; // 20 second
|
const DEBOUNCE_DELAY_MS = 1000 * 20; // 20 second
|
||||||
let timeoutId: NodeJS.Timeout | undefined;
|
let timeoutId: NodeJS.Timeout;
|
||||||
|
|
||||||
// Prevents multiple parallel requests during DEBOUNCE_DELAY_MS
|
// Prevents multiple parallel requests during DEBOUNCE_DELAY_MS
|
||||||
let lastRequestTimestamp: number | undefined = undefined;
|
let lastRequestTimestamp: number = undefined;
|
||||||
|
|
||||||
/**
|
const requestDatabaseResourceTokens = async (): Promise<void> => {
|
||||||
* Request fabric token:
|
|
||||||
* - Mirrored key and AAD: Database Resource Tokens
|
|
||||||
* - Native: AAD token
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const requestFabricToken = async (): Promise<void> => {
|
|
||||||
if (lastRequestTimestamp !== undefined && lastRequestTimestamp + DEBOUNCE_DELAY_MS > Date.now()) {
|
if (lastRequestTimestamp !== undefined && lastRequestTimestamp + DEBOUNCE_DELAY_MS > Date.now()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastRequestTimestamp = Date.now();
|
lastRequestTimestamp = Date.now();
|
||||||
try {
|
try {
|
||||||
if (isFabricMirrored()) {
|
const fabricDatabaseConnectionInfo = await sendCachedDataMessage<FabricDatabaseConnectionInfo>(
|
||||||
await requestAndStoreDatabaseResourceTokens();
|
FabricMessageTypes.GetAllResourceTokens,
|
||||||
} else if (isFabricNative()) {
|
[],
|
||||||
await requestAndStoreAccessToken();
|
userContext.fabricContext.connectionId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!userContext.databaseAccount.properties.documentEndpoint) {
|
||||||
|
userContext.databaseAccount.properties.documentEndpoint = fabricDatabaseConnectionInfo.endpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduleRefreshFabricToken();
|
updateUserContext({
|
||||||
|
fabricContext: {
|
||||||
|
...userContext.fabricContext,
|
||||||
|
databaseConnectionInfo: fabricDatabaseConnectionInfo,
|
||||||
|
isReadOnly: true,
|
||||||
|
},
|
||||||
|
databaseAccount: { ...userContext.databaseAccount },
|
||||||
|
});
|
||||||
|
scheduleRefreshDatabaseResourceToken();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(error as string);
|
logConsoleError(error);
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
lastRequestTimestamp = undefined;
|
lastRequestTimestamp = undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestAndStoreDatabaseResourceTokens = async (): Promise<void> => {
|
|
||||||
if (!userContext.fabricContext || !userContext.databaseAccount) {
|
|
||||||
// This should not happen
|
|
||||||
logConsoleError("Fabric context or database account is missing: cannot request tokens");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const resourceTokenInfo = await sendCachedDataMessage<ResourceTokenInfo>(
|
|
||||||
FabricMessageTypes.GetAllResourceTokens,
|
|
||||||
[],
|
|
||||||
userContext.fabricContext.artifactInfo?.connectionId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!userContext.databaseAccount.properties.documentEndpoint) {
|
|
||||||
userContext.databaseAccount.properties.documentEndpoint = resourceTokenInfo.endpoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resourceTokenInfo.credentialType === "OAuth2") {
|
|
||||||
// Mirrored AAD
|
|
||||||
updateUserContext({
|
|
||||||
fabricContext: {
|
|
||||||
...userContext.fabricContext,
|
|
||||||
databaseName: resourceTokenInfo.databaseId,
|
|
||||||
artifactInfo: undefined,
|
|
||||||
isReadOnly: resourceTokenInfo.isReadOnly ?? userContext.fabricContext.isReadOnly,
|
|
||||||
},
|
|
||||||
databaseAccount: { ...userContext.databaseAccount },
|
|
||||||
aadToken: resourceTokenInfo.accessToken,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// TODO: In Fabric contract V2, credentialType is undefined. For V3, it is "Key". Check for "Key" when V3 is supported for Fabric Mirroring Key
|
|
||||||
// Mirrored key
|
|
||||||
updateUserContext({
|
|
||||||
fabricContext: {
|
|
||||||
...userContext.fabricContext,
|
|
||||||
databaseName: resourceTokenInfo.databaseId,
|
|
||||||
artifactInfo: {
|
|
||||||
...(userContext.fabricContext.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY]),
|
|
||||||
resourceTokenInfo,
|
|
||||||
},
|
|
||||||
isReadOnly: resourceTokenInfo.isReadOnly ?? userContext.fabricContext.isReadOnly,
|
|
||||||
},
|
|
||||||
databaseAccount: { ...userContext.databaseAccount },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const requestAndStoreAccessToken = async (): Promise<void> => {
|
|
||||||
if (!userContext.fabricContext || !userContext.databaseAccount) {
|
|
||||||
// This should not happen
|
|
||||||
logConsoleError("Fabric context or database account is missing: cannot request tokens");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const accessTokenInfo = await sendCachedDataMessage<{ accessToken: string }>(FabricMessageTypes.GetAccessToken, []);
|
|
||||||
|
|
||||||
updateUserContext({
|
|
||||||
aadToken: accessTokenInfo.accessToken,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check token validity and schedule a refresh if necessary
|
* Check token validity and schedule a refresh if necessary
|
||||||
* @param tokenTimestamp
|
* @param tokenTimestamp
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const scheduleRefreshFabricToken = (refreshNow?: boolean): Promise<void> => {
|
export const scheduleRefreshDatabaseResourceToken = (refreshNow?: boolean): Promise<void> => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (timeoutId !== undefined) {
|
if (timeoutId !== undefined) {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
@@ -115,7 +59,7 @@ export const scheduleRefreshFabricToken = (refreshNow?: boolean): Promise<void>
|
|||||||
|
|
||||||
timeoutId = setTimeout(
|
timeoutId = setTimeout(
|
||||||
() => {
|
() => {
|
||||||
requestFabricToken().then(resolve);
|
requestDatabaseResourceTokens().then(resolve);
|
||||||
},
|
},
|
||||||
refreshNow ? 0 : TOKEN_VALIDITY_MS,
|
refreshNow ? 0 : TOKEN_VALIDITY_MS,
|
||||||
);
|
);
|
||||||
@@ -124,15 +68,6 @@ export const scheduleRefreshFabricToken = (refreshNow?: boolean): Promise<void>
|
|||||||
|
|
||||||
export const checkDatabaseResourceTokensValidity = (tokenTimestamp: number): void => {
|
export const checkDatabaseResourceTokensValidity = (tokenTimestamp: number): void => {
|
||||||
if (tokenTimestamp + TOKEN_VALIDITY_MS < Date.now()) {
|
if (tokenTimestamp + TOKEN_VALIDITY_MS < Date.now()) {
|
||||||
scheduleRefreshFabricToken(true);
|
scheduleRefreshDatabaseResourceToken(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isFabric = (): boolean => configContext.platform === Platform.Fabric;
|
|
||||||
export const isFabricMirroredKey = (): boolean =>
|
|
||||||
isFabric() && userContext.fabricContext?.artifactType === CosmosDbArtifactType.MIRRORED_KEY;
|
|
||||||
export const isFabricMirroredAAD = (): boolean =>
|
|
||||||
isFabric() && userContext.fabricContext?.artifactType === CosmosDbArtifactType.MIRRORED_AAD;
|
|
||||||
export const isFabricMirrored = (): boolean => isFabricMirroredKey() || isFabricMirroredAAD();
|
|
||||||
export const isFabricNative = (): boolean =>
|
|
||||||
isFabric() && userContext.fabricContext?.artifactType === CosmosDbArtifactType.NATIVE;
|
|
||||||
|
|||||||
@@ -41,13 +41,13 @@ const getDescriptor = async (selfServeType: SelfServeType): Promise<SelfServeDes
|
|||||||
case SelfServeType.example: {
|
case SelfServeType.example: {
|
||||||
const SelfServeExample = await import(/* webpackChunkName: "SelfServeExample" */ "./Example/SelfServeExample");
|
const SelfServeExample = await import(/* webpackChunkName: "SelfServeExample" */ "./Example/SelfServeExample");
|
||||||
const selfServeExample = new SelfServeExample.default();
|
const selfServeExample = new SelfServeExample.default();
|
||||||
await loadTranslations(selfServeType);
|
await loadTranslations(selfServeExample.constructor.name);
|
||||||
return selfServeExample.toSelfServeDescriptor();
|
return selfServeExample.toSelfServeDescriptor();
|
||||||
}
|
}
|
||||||
case SelfServeType.sqlx: {
|
case SelfServeType.sqlx: {
|
||||||
const SqlX = await import(/* webpackChunkName: "SqlX" */ "./SqlX/SqlX");
|
const SqlX = await import(/* webpackChunkName: "SqlX" */ "./SqlX/SqlX");
|
||||||
const sqlX = new SqlX.default();
|
const sqlX = new SqlX.default();
|
||||||
await loadTranslations(selfServeType);
|
await loadTranslations(sqlX.constructor.name);
|
||||||
return sqlX.toSelfServeDescriptor();
|
return sqlX.toSelfServeDescriptor();
|
||||||
}
|
}
|
||||||
case SelfServeType.graphapicompute: {
|
case SelfServeType.graphapicompute: {
|
||||||
@@ -55,7 +55,7 @@ const getDescriptor = async (selfServeType: SelfServeType): Promise<SelfServeDes
|
|||||||
/* webpackChunkName: "GraphAPICompute" */ "./GraphAPICompute/GraphAPICompute"
|
/* webpackChunkName: "GraphAPICompute" */ "./GraphAPICompute/GraphAPICompute"
|
||||||
);
|
);
|
||||||
const graphAPICompute = new GraphAPICompute.default();
|
const graphAPICompute = new GraphAPICompute.default();
|
||||||
await loadTranslations(selfServeType);
|
await loadTranslations(graphAPICompute.constructor.name);
|
||||||
return graphAPICompute.toSelfServeDescriptor();
|
return graphAPICompute.toSelfServeDescriptor();
|
||||||
}
|
}
|
||||||
case SelfServeType.materializedviewsbuilder: {
|
case SelfServeType.materializedviewsbuilder: {
|
||||||
@@ -63,7 +63,7 @@ const getDescriptor = async (selfServeType: SelfServeType): Promise<SelfServeDes
|
|||||||
/* webpackChunkName: "MaterializedViewsBuilder" */ "./MaterializedViewsBuilder/MaterializedViewsBuilder"
|
/* webpackChunkName: "MaterializedViewsBuilder" */ "./MaterializedViewsBuilder/MaterializedViewsBuilder"
|
||||||
);
|
);
|
||||||
const materializedViewsBuilder = new MaterializedViewsBuilder.default();
|
const materializedViewsBuilder = new MaterializedViewsBuilder.default();
|
||||||
await loadTranslations(selfServeType);
|
await loadTranslations(materializedViewsBuilder.constructor.name);
|
||||||
return materializedViewsBuilder.toSelfServeDescriptor();
|
return materializedViewsBuilder.toSelfServeDescriptor();
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -103,7 +103,7 @@ const handleMessage = async (event: MessageEvent): Promise<void> => {
|
|||||||
|
|
||||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||||
const selfServeTypeText = urlSearchParams.get("selfServeType") || inputs.selfServeType;
|
const selfServeTypeText = urlSearchParams.get("selfServeType") || inputs.selfServeType;
|
||||||
const selfServeType = SelfServeType[selfServeTypeText.toLocaleLowerCase() as keyof typeof SelfServeType];
|
const selfServeType = SelfServeType[selfServeTypeText?.toLowerCase() as keyof typeof SelfServeType];
|
||||||
if (
|
if (
|
||||||
!inputs.subscriptionId ||
|
!inputs.subscriptionId ||
|
||||||
!inputs.resourceGroup ||
|
!inputs.resourceGroup ||
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
import { TFunction } from "i18next";
|
import { TFunction } from "i18next";
|
||||||
import promiseRetry, { AbortError, Options } from "p-retry";
|
import promiseRetry, { AbortError } from "p-retry";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { WithTranslation } from "react-i18next";
|
import { WithTranslation } from "react-i18next";
|
||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
@@ -80,7 +80,7 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
|
|||||||
private static readonly defaultRetryIntervalInMs = 30000;
|
private static readonly defaultRetryIntervalInMs = 30000;
|
||||||
private smartUiGeneratorClassName: string;
|
private smartUiGeneratorClassName: string;
|
||||||
private retryIntervalInMs: number;
|
private retryIntervalInMs: number;
|
||||||
private retryOptions: Options;
|
private retryOptions: promiseRetry.Options;
|
||||||
private translationFunction: TFunction;
|
private translationFunction: TFunction;
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
|
|||||||
@@ -29,11 +29,10 @@ export enum SelfServeType {
|
|||||||
// Unsupported self serve type passed as feature flag
|
// Unsupported self serve type passed as feature flag
|
||||||
invalid = "invalid",
|
invalid = "invalid",
|
||||||
// Add your self serve types here
|
// Add your self serve types here
|
||||||
// NOTE: text and casing of the enum's value must match the corresponding file in Localization\en\
|
example = "example",
|
||||||
example = "SelfServeExample",
|
sqlx = "sqlx",
|
||||||
sqlx = "SqlX",
|
graphapicompute = "graphapicompute",
|
||||||
graphapicompute = "GraphAPICompute",
|
materializedviewsbuilder = "materializedviewsbuilder",
|
||||||
materializedviewsbuilder = "MaterializedViewsBuilder",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -197,11 +197,6 @@ export const getPriceMapAndCurrencyCode = async (map: OfferingIdMap): Promise<Pr
|
|||||||
const priceMap = new Map<string, Map<string, number>>();
|
const priceMap = new Map<string, Map<string, number>>();
|
||||||
let billingCurrency;
|
let billingCurrency;
|
||||||
for (const region of map.keys()) {
|
for (const region of map.keys()) {
|
||||||
// if no offering id is found for that region, skipping calling price API
|
|
||||||
const subMap = map.get(region);
|
|
||||||
if (!subMap || subMap.size === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const regionPriceMap = new Map<string, number>();
|
const regionPriceMap = new Map<string, number>();
|
||||||
const regionShortName = await getRegionShortName(region);
|
const regionShortName = await getRegionShortName(region);
|
||||||
const requestBody: OfferingIdRequest = {
|
const requestBody: OfferingIdRequest = {
|
||||||
@@ -242,7 +237,7 @@ export const getPriceMapAndCurrencyCode = async (map: OfferingIdMap): Promise<Pr
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
const failureTelemetry = { err, selfServeClassName: SqlX.name };
|
const failureTelemetry = { err, selfServeClassName: SqlX.name };
|
||||||
selfServeTraceFailure(failureTelemetry, getPriceMapAndCurrencyCodeTimestamp);
|
selfServeTraceFailure(failureTelemetry, getPriceMapAndCurrencyCodeTimestamp);
|
||||||
return { priceMap: new Map<string, Map<string, number>>(), billingCurrency: undefined };
|
return { priceMap: undefined, billingCurrency: undefined };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -291,6 +286,6 @@ export const getOfferingIds = async (regions: Array<RegionItem>): Promise<Offeri
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
const failureTelemetry = { err, selfServeClassName: SqlX.name };
|
const failureTelemetry = { err, selfServeClassName: SqlX.name };
|
||||||
selfServeTraceFailure(failureTelemetry, getOfferingIdsCodeTimestamp);
|
selfServeTraceFailure(failureTelemetry, getOfferingIdsCodeTimestamp);
|
||||||
return new Map<string, Map<string, string>>();
|
return undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -227,13 +227,11 @@ const calculateCost = (skuName: string, instanceCount: number): Description => {
|
|||||||
let costPerHour = 0;
|
let costPerHour = 0;
|
||||||
let costBreakdown = "";
|
let costBreakdown = "";
|
||||||
for (const regionItem of regions) {
|
for (const regionItem of regions) {
|
||||||
const incrementalCost = priceMap?.get(regionItem.locationName)?.get(skuName.replace("Cosmos.", ""));
|
const incrementalCost = priceMap.get(regionItem.locationName).get(skuName.replace("Cosmos.", ""));
|
||||||
if (incrementalCost === undefined) {
|
if (incrementalCost === undefined) {
|
||||||
throw new Error(`${regionItem.locationName} not found in price map.`);
|
throw new Error(`${regionItem.locationName} not found in price map.`);
|
||||||
} else if (incrementalCost === 0) {
|
} else if (incrementalCost === 0) {
|
||||||
throw new Error(`${regionItem.locationName} cost per hour = 0`);
|
throw new Error(`${regionItem.locationName} cost per hour = 0`);
|
||||||
} else if (currencyCode === undefined) {
|
|
||||||
throw new Error(`Currency code not found in price map.`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let regionalInstanceCount = instanceCount;
|
let regionalInstanceCount = instanceCount;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export class JupyterLabAppFactory {
|
|||||||
if (userContext.apiType === "VCoreMongo" && content?.includes("MongoServerError: Invalid key")) {
|
if (userContext.apiType === "VCoreMongo" && content?.includes("MongoServerError: Invalid key")) {
|
||||||
this.restartShell = true;
|
this.restartShell = true;
|
||||||
}
|
}
|
||||||
return content?.includes("cosmosshelluser@");
|
return content?.includes("cosmosuser@");
|
||||||
}
|
}
|
||||||
|
|
||||||
private isMongoShellStarted(content: string | undefined) {
|
private isMongoShellStarted(content: string | undefined) {
|
||||||
@@ -68,6 +68,7 @@ export class JupyterLabAppFactory {
|
|||||||
const session = await manager.startNew();
|
const session = await manager.startNew();
|
||||||
session.messageReceived.connect(async (_, message: IMessage) => {
|
session.messageReceived.connect(async (_, message: IMessage) => {
|
||||||
const content = message.content && message.content[0]?.toString();
|
const content = message.content && message.content[0]?.toString();
|
||||||
|
|
||||||
if (this.checkShellStarted && message.type == "stdout") {
|
if (this.checkShellStarted && message.type == "stdout") {
|
||||||
//Close the terminal tab once the shell closed messages are received
|
//Close the terminal tab once the shell closed messages are received
|
||||||
if (!this.isShellStarted) {
|
if (!this.isShellStarted) {
|
||||||
@@ -113,13 +114,6 @@ export class JupyterLabAppFactory {
|
|||||||
panel.dispose();
|
panel.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close terminal when Ctrl key is pressed
|
|
||||||
term.node.addEventListener("keydown", (event: KeyboardEvent) => {
|
|
||||||
if (event.ctrlKey) {
|
|
||||||
this.onShellExited(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CosmosDbArtifactType, ResourceTokenInfo } from "Contracts/FabricMessagesContract";
|
import { FabricDatabaseConnectionInfo } from "Contracts/FabricMessagesContract";
|
||||||
import { ParsedResourceTokenConnectionString } from "Platform/Hosted/Helpers/ResourceTokenUtils";
|
import { ParsedResourceTokenConnectionString } from "Platform/Hosted/Helpers/ResourceTokenUtils";
|
||||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||||
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
|
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
|
||||||
@@ -47,21 +47,11 @@ export interface VCoreMongoConnectionParams {
|
|||||||
connectionString: string;
|
connectionString: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FabricArtifactInfo {
|
interface FabricContext {
|
||||||
[CosmosDbArtifactType.MIRRORED_KEY]: {
|
connectionId: string;
|
||||||
connectionId: string;
|
databaseConnectionInfo: FabricDatabaseConnectionInfo | undefined;
|
||||||
resourceTokenInfo: ResourceTokenInfo | undefined;
|
|
||||||
};
|
|
||||||
[CosmosDbArtifactType.MIRRORED_AAD]: undefined;
|
|
||||||
[CosmosDbArtifactType.NATIVE]: undefined;
|
|
||||||
}
|
|
||||||
export interface FabricContext<T extends CosmosDbArtifactType> {
|
|
||||||
fabricClientRpcVersion: string;
|
|
||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
databaseName: string;
|
|
||||||
artifactType: CosmosDbArtifactType;
|
|
||||||
artifactInfo: FabricArtifactInfo[T];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AdminFeedbackControlPolicy =
|
export type AdminFeedbackControlPolicy =
|
||||||
@@ -80,7 +70,7 @@ export type AdminFeedbackPolicySettings = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface UserContext {
|
export interface UserContext {
|
||||||
readonly fabricContext?: FabricContext<CosmosDbArtifactType>;
|
readonly fabricContext?: FabricContext;
|
||||||
readonly authType?: AuthType;
|
readonly authType?: AuthType;
|
||||||
readonly masterKey?: string;
|
readonly masterKey?: string;
|
||||||
readonly subscriptionId?: string;
|
readonly subscriptionId?: string;
|
||||||
|
|||||||
@@ -89,7 +89,3 @@ export const getItemName = (): string => {
|
|||||||
return "Items";
|
return "Items";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isDataplaneRbacSupported = (apiType: string): boolean => {
|
|
||||||
return apiType === "SQL" || apiType === "Tables";
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
export const autoPilotThroughput1K = 1000;
|
export const autoPilotThroughput1K = 1000;
|
||||||
export const autoPilotIncrementStep = 1000;
|
export const autoPilotIncrementStep = 1000;
|
||||||
export const autoPilotThroughput4K = 4000;
|
export const autoPilotThroughput4K = 4000;
|
||||||
export const autoPilotThroughput10K = 10000;
|
|
||||||
|
|
||||||
export function isValidAutoPilotThroughput(maxThroughput: number): boolean {
|
export function isValidAutoPilotThroughput(maxThroughput: number): boolean {
|
||||||
if (!maxThroughput) {
|
if (!maxThroughput) {
|
||||||
|
|||||||
@@ -35,13 +35,6 @@ describe("Query Utils", () => {
|
|||||||
version: 2,
|
version: 2,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
const generatePartitionKeysForPaths = (paths: string[]): DataModels.PartitionKey => {
|
|
||||||
return {
|
|
||||||
paths: paths,
|
|
||||||
kind: "Hash",
|
|
||||||
version: 2,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("buildDocumentsQueryPartitionProjections()", () => {
|
describe("buildDocumentsQueryPartitionProjections()", () => {
|
||||||
it("should return empty string if partition key is undefined", () => {
|
it("should return empty string if partition key is undefined", () => {
|
||||||
@@ -96,18 +89,6 @@ describe("Query Utils", () => {
|
|||||||
|
|
||||||
expect(query).toContain("c.id");
|
expect(query).toContain("c.id");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should always include {} for any missing partition keys", () => {
|
|
||||||
const query = QueryUtils.buildDocumentsQuery(
|
|
||||||
"",
|
|
||||||
["a", "b", "c"],
|
|
||||||
generatePartitionKeysForPaths(["/a", "/b", "/c"]),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
expect(query).toContain('IIF(IS_DEFINED(c["a"]), c["a"], {})');
|
|
||||||
expect(query).toContain('IIF(IS_DEFINED(c["b"]), c["b"], {})');
|
|
||||||
expect(query).toContain('IIF(IS_DEFINED(c["c"]), c["c"], {})');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("queryPagesUntilContentPresent()", () => {
|
describe("queryPagesUntilContentPresent()", () => {
|
||||||
@@ -220,6 +201,18 @@ describe("Query Utils", () => {
|
|||||||
expect(expectedPartitionKeyValues).toContain(documentContent["Category"]);
|
expect(expectedPartitionKeyValues).toContain(documentContent["Category"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should extract no partition key values in the case nested partition key", () => {
|
||||||
|
const singlePartitionKeyDefinition: PartitionKeyDefinition = {
|
||||||
|
kind: PartitionKeyKind.Hash,
|
||||||
|
paths: ["/Location.type"],
|
||||||
|
};
|
||||||
|
const partitionKeyValues: PartitionKey[] = extractPartitionKeyValues(
|
||||||
|
documentContent,
|
||||||
|
singlePartitionKeyDefinition,
|
||||||
|
);
|
||||||
|
expect(partitionKeyValues.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
it("should extract all partition key values for hierarchical and nested partition keys", () => {
|
it("should extract all partition key values for hierarchical and nested partition keys", () => {
|
||||||
const mixedPartitionKeyDefinition: PartitionKeyDefinition = {
|
const mixedPartitionKeyDefinition: PartitionKeyDefinition = {
|
||||||
kind: PartitionKeyKind.MultiHash,
|
kind: PartitionKeyKind.MultiHash,
|
||||||
@@ -232,52 +225,5 @@ describe("Query Utils", () => {
|
|||||||
expect(partitionKeyValues.length).toBe(2);
|
expect(partitionKeyValues.length).toBe(2);
|
||||||
expect(partitionKeyValues).toEqual(["United States", "Point"]);
|
expect(partitionKeyValues).toEqual(["United States", "Point"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("if any partition key is null or empty string, the partitionKeyValues shall match", () => {
|
|
||||||
const newDocumentContent = {
|
|
||||||
...documentContent,
|
|
||||||
...{
|
|
||||||
Country: null,
|
|
||||||
Location: {
|
|
||||||
type: "",
|
|
||||||
coordinates: [-121.49, 46.206],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const mixedPartitionKeyDefinition: PartitionKeyDefinition = {
|
|
||||||
kind: PartitionKeyKind.MultiHash,
|
|
||||||
paths: ["/Country", "/Location/type"],
|
|
||||||
};
|
|
||||||
const partitionKeyValues: PartitionKey[] = extractPartitionKeyValues(
|
|
||||||
newDocumentContent,
|
|
||||||
mixedPartitionKeyDefinition,
|
|
||||||
);
|
|
||||||
expect(partitionKeyValues.length).toBe(2);
|
|
||||||
expect(partitionKeyValues).toEqual([null, ""]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("if any partition key doesn't exist, it should still set partitionkey value as {}", () => {
|
|
||||||
const newDocumentContent = {
|
|
||||||
...documentContent,
|
|
||||||
...{
|
|
||||||
Country: null,
|
|
||||||
Location: {
|
|
||||||
coordinates: [-121.49, 46.206],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const mixedPartitionKeyDefinition: PartitionKeyDefinition = {
|
|
||||||
kind: PartitionKeyKind.MultiHash,
|
|
||||||
paths: ["/Country", "/Location/type"],
|
|
||||||
};
|
|
||||||
const partitionKeyValues: PartitionKey[] = extractPartitionKeyValues(
|
|
||||||
newDocumentContent,
|
|
||||||
mixedPartitionKeyDefinition,
|
|
||||||
);
|
|
||||||
expect(partitionKeyValues.length).toBe(2);
|
|
||||||
expect(partitionKeyValues).toEqual([null, {}]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -61,9 +61,8 @@ export function buildDocumentsQueryPartitionProjections(
|
|||||||
projectedProperty += `[${projection}]`;
|
projectedProperty += `[${projection}]`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const fullAccess = `${collectionAlias}${projectedProperty}`;
|
|
||||||
const wrappedProjection = `IIF(IS_DEFINED(${fullAccess}), ${fullAccess}, {})`;
|
projections.push(`${collectionAlias}${projectedProperty}`);
|
||||||
projections.push(wrappedProjection);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return projections.join(",");
|
return projections.join(",");
|
||||||
@@ -131,8 +130,6 @@ export const extractPartitionKeyValues = (
|
|||||||
|
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
partitionKeyValues.push(value);
|
partitionKeyValues.push(value);
|
||||||
} else {
|
|
||||||
partitionKeyValues.push({});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { isFabric } from "Platform/Fabric/FabricUtil";
|
|
||||||
import { Platform, configContext } from "./../ConfigContext";
|
import { Platform, configContext } from "./../ConfigContext";
|
||||||
|
|
||||||
export const getDataExplorerWindow = (currentWindow: Window): Window | undefined => {
|
export const getDataExplorerWindow = (currentWindow: Window): Window | undefined => {
|
||||||
@@ -8,7 +7,7 @@ export const getDataExplorerWindow = (currentWindow: Window): Window | undefined
|
|||||||
if (currentWindow.parent === currentWindow) {
|
if (currentWindow.parent === currentWindow) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (isFabric() && currentWindow.parent.parent === currentWindow.top) {
|
if (configContext.platform === Platform.Fabric && currentWindow.parent.parent === currentWindow.top) {
|
||||||
// in Fabric data explorer is inside an extension iframe, so we have two parent iframes
|
// in Fabric data explorer is inside an extension iframe, so we have two parent iframes
|
||||||
return currentWindow;
|
return currentWindow;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,26 +2,17 @@ import * as Constants from "Common/Constants";
|
|||||||
import { createUri } from "Common/UrlUtility";
|
import { createUri } from "Common/UrlUtility";
|
||||||
import { DATA_EXPLORER_RPC_VERSION } from "Contracts/DataExplorerMessagesContract";
|
import { DATA_EXPLORER_RPC_VERSION } from "Contracts/DataExplorerMessagesContract";
|
||||||
import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
|
import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
|
||||||
import {
|
import { FABRIC_RPC_VERSION, FabricMessageV2 } from "Contracts/FabricMessagesContract";
|
||||||
ArtifactConnectionInfo,
|
|
||||||
CosmosDbArtifactType,
|
|
||||||
FABRIC_RPC_VERSION,
|
|
||||||
FabricMessageV2,
|
|
||||||
FabricMessageV3,
|
|
||||||
InitializeMessageV3,
|
|
||||||
} from "Contracts/FabricMessagesContract";
|
|
||||||
import { useDialog } from "Explorer/Controls/Dialog";
|
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
|
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
|
||||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
import { isFabricMirroredKey, scheduleRefreshFabricToken } from "Platform/Fabric/FabricUtil";
|
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
|
||||||
import {
|
import {
|
||||||
AppStateComponentNames,
|
AppStateComponentNames,
|
||||||
OPEN_TABS_SUBCOMPONENT_NAME,
|
OPEN_TABS_SUBCOMPONENT_NAME,
|
||||||
readSubComponentState,
|
readSubComponentState,
|
||||||
} from "Shared/AppStatePersistenceUtility";
|
} from "Shared/AppStatePersistenceUtility";
|
||||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||||
import { isDataplaneRbacSupported } from "Utils/APITypeUtils";
|
|
||||||
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||||
@@ -31,7 +22,7 @@ import { AccountKind, Flights } from "../Common/Constants";
|
|||||||
import { normalizeArmEndpoint } from "../Common/EnvironmentUtility";
|
import { normalizeArmEndpoint } from "../Common/EnvironmentUtility";
|
||||||
import * as Logger from "../Common/Logger";
|
import * as Logger from "../Common/Logger";
|
||||||
import { handleCachedDataMessage, sendMessage, sendReadyMessage } from "../Common/MessageHandler";
|
import { handleCachedDataMessage, sendMessage, sendReadyMessage } from "../Common/MessageHandler";
|
||||||
import { configContext, Platform, updateConfigContext } from "../ConfigContext";
|
import { Platform, configContext, updateConfigContext } from "../ConfigContext";
|
||||||
import { ActionType, DataExplorerAction, TabKind } from "../Contracts/ActionContracts";
|
import { ActionType, DataExplorerAction, TabKind } from "../Contracts/ActionContracts";
|
||||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
import { DataExplorerInputsFrame } from "../Contracts/ViewModels";
|
import { DataExplorerInputsFrame } from "../Contracts/ViewModels";
|
||||||
@@ -52,7 +43,7 @@ import {
|
|||||||
} from "../Platform/Hosted/HostedUtils";
|
} from "../Platform/Hosted/HostedUtils";
|
||||||
import { extractFeatures } from "../Platform/Hosted/extractFeatures";
|
import { extractFeatures } from "../Platform/Hosted/extractFeatures";
|
||||||
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
|
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
|
||||||
import { FabricArtifactInfo, Node, PortalEnv, updateUserContext, userContext } from "../UserContext";
|
import { Node, PortalEnv, updateUserContext, userContext } from "../UserContext";
|
||||||
import {
|
import {
|
||||||
acquireMsalTokenForAccount,
|
acquireMsalTokenForAccount,
|
||||||
acquireTokenWithMsal,
|
acquireTokenWithMsal,
|
||||||
@@ -112,7 +103,7 @@ export function useKnockoutExplorer(platform: Platform): Explorer {
|
|||||||
|
|
||||||
async function configureFabric(): Promise<Explorer> {
|
async function configureFabric(): Promise<Explorer> {
|
||||||
// These are the versions of Fabric that Data Explorer supports.
|
// These are the versions of Fabric that Data Explorer supports.
|
||||||
const SUPPORTED_FABRIC_VERSIONS = ["2", FABRIC_RPC_VERSION];
|
const SUPPORTED_FABRIC_VERSIONS = [FABRIC_RPC_VERSION];
|
||||||
|
|
||||||
let firstContainerOpened = false;
|
let firstContainerOpened = false;
|
||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
@@ -128,7 +119,7 @@ async function configureFabric(): Promise<Explorer> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: FabricMessageV2 | FabricMessageV3 = event.data?.data;
|
const data: FabricMessageV2 = event.data?.data;
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -137,77 +128,38 @@ async function configureFabric(): Promise<Explorer> {
|
|||||||
case "initialize": {
|
case "initialize": {
|
||||||
const fabricVersion = data.version;
|
const fabricVersion = data.version;
|
||||||
if (!SUPPORTED_FABRIC_VERSIONS.includes(fabricVersion)) {
|
if (!SUPPORTED_FABRIC_VERSIONS.includes(fabricVersion)) {
|
||||||
// TODO Surface error to user and log to telemetry
|
// TODO Surface error to user
|
||||||
useDialog
|
|
||||||
.getState()
|
|
||||||
.showOkModalDialog("Unsupported Fabric version", `Unsupported Fabric version: ${fabricVersion}`);
|
|
||||||
Logger.logError(`Unsupported Fabric version: ${fabricVersion}`, "Explorer/configureFabric");
|
|
||||||
console.error(`Unsupported Fabric version: ${fabricVersion}`);
|
console.error(`Unsupported Fabric version: ${fabricVersion}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fabricVersion === "2") {
|
explorer = createExplorerFabric(data.message);
|
||||||
// ----------------- TODO: Remove this when FabricMessageV2 is deprecated -----------------
|
await scheduleRefreshDatabaseResourceToken(true);
|
||||||
const initializationMessage = data.message as {
|
resolve(explorer);
|
||||||
connectionId: string;
|
await explorer.refreshAllDatabases();
|
||||||
isVisible: boolean;
|
if (userContext.fabricContext.isVisible) {
|
||||||
};
|
firstContainerOpened = true;
|
||||||
|
openFirstContainer(explorer, userContext.fabricContext.databaseConnectionInfo.databaseId);
|
||||||
explorer = createExplorerFabricLegacy(initializationMessage, data.version);
|
|
||||||
await scheduleRefreshFabricToken(true);
|
|
||||||
resolve(explorer);
|
|
||||||
await explorer.refreshAllDatabases();
|
|
||||||
if (userContext.fabricContext.isVisible) {
|
|
||||||
firstContainerOpened = true;
|
|
||||||
openFirstContainer(explorer, userContext.fabricContext.databaseName);
|
|
||||||
}
|
|
||||||
// -----------------------------------------------------------------------------------------
|
|
||||||
} else if (fabricVersion === FABRIC_RPC_VERSION) {
|
|
||||||
const initializationMessage = data.message as InitializeMessageV3<CosmosDbArtifactType>;
|
|
||||||
explorer = createExplorerFabric(initializationMessage, data.version);
|
|
||||||
|
|
||||||
if (initializationMessage.artifactType === CosmosDbArtifactType.MIRRORED_KEY) {
|
|
||||||
// Do not show Home tab for Mirrored
|
|
||||||
useTabs.getState().closeReactTab(ReactTabKind.Home);
|
|
||||||
}
|
|
||||||
|
|
||||||
// All tokens used in fabric expire, so schedule a refresh
|
|
||||||
// For Mirrored key, we need the token right away to get the database and containers list.
|
|
||||||
if (isFabricMirroredKey()) {
|
|
||||||
await scheduleRefreshFabricToken(true);
|
|
||||||
} else {
|
|
||||||
scheduleRefreshFabricToken(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(explorer);
|
|
||||||
await explorer.refreshAllDatabases();
|
|
||||||
|
|
||||||
const { databaseName } = userContext.fabricContext;
|
|
||||||
if (userContext.fabricContext.isVisible && databaseName) {
|
|
||||||
firstContainerOpened = true;
|
|
||||||
openFirstContainer(explorer, databaseName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "newContainer":
|
case "newContainer":
|
||||||
explorer.onNewCollectionClicked();
|
explorer.onNewCollectionClicked();
|
||||||
break;
|
break;
|
||||||
case "authorizationToken":
|
case "authorizationToken":
|
||||||
case "allResourceTokens_v2":
|
case "allResourceTokens_v2": {
|
||||||
case "accessToken": {
|
|
||||||
handleCachedDataMessage(data);
|
handleCachedDataMessage(data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "explorerVisible": {
|
case "explorerVisible": {
|
||||||
userContext.fabricContext.isVisible = data.message.visible;
|
userContext.fabricContext.isVisible = data.message.visible;
|
||||||
if (userContext.fabricContext.isVisible && !firstContainerOpened) {
|
if (
|
||||||
const { databaseName } = userContext.fabricContext;
|
userContext.fabricContext.isVisible &&
|
||||||
if (databaseName !== undefined) {
|
!firstContainerOpened &&
|
||||||
firstContainerOpened = true;
|
userContext?.fabricContext?.databaseConnectionInfo?.databaseId !== undefined
|
||||||
openFirstContainer(explorer, databaseName);
|
) {
|
||||||
}
|
firstContainerOpened = true;
|
||||||
|
openFirstContainer(explorer, userContext.fabricContext.databaseConnectionInfo.databaseId);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -347,7 +299,7 @@ async function configureHostedWithAAD(config: AAD): Promise<Explorer> {
|
|||||||
);
|
);
|
||||||
if (!userContext.features.enableAadDataPlane) {
|
if (!userContext.features.enableAadDataPlane) {
|
||||||
Logger.logInfo(`AAD Feature flag is not enabled for account ${account.name}`, "Explorer/configureHostedWithAAD");
|
Logger.logInfo(`AAD Feature flag is not enabled for account ${account.name}`, "Explorer/configureHostedWithAAD");
|
||||||
if (isDataplaneRbacSupported(userContext.apiType)) {
|
if (userContext.apiType === "SQL") {
|
||||||
if (LocalStorageUtility.hasItem(StorageKey.DataPlaneRbacEnabled)) {
|
if (LocalStorageUtility.hasItem(StorageKey.DataPlaneRbacEnabled)) {
|
||||||
const isDataPlaneRbacSetting = LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled);
|
const isDataPlaneRbacSetting = LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled);
|
||||||
Logger.logInfo(
|
Logger.logInfo(
|
||||||
@@ -467,29 +419,13 @@ function configureHostedWithResourceToken(config: ResourceToken): Explorer {
|
|||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function createExplorerFabric(params: { connectionId: string; isVisible: boolean }): Explorer {
|
||||||
* Initialization for FabricMessageV2
|
|
||||||
* TODO: delete when FabricMessageV2 is deprecated
|
|
||||||
* @param params
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
function createExplorerFabricLegacy(
|
|
||||||
params: { connectionId: string; isVisible: boolean },
|
|
||||||
fabricClientRpcVersion: string,
|
|
||||||
): Explorer {
|
|
||||||
const artifactInfo: FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY] = {
|
|
||||||
connectionId: params.connectionId,
|
|
||||||
resourceTokenInfo: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
fabricContext: {
|
fabricContext: {
|
||||||
fabricClientRpcVersion,
|
connectionId: params.connectionId,
|
||||||
|
databaseConnectionInfo: undefined,
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
isVisible: params.isVisible ?? true,
|
isVisible: params.isVisible ?? true,
|
||||||
databaseName: undefined,
|
|
||||||
artifactType: CosmosDbArtifactType.MIRRORED_KEY,
|
|
||||||
artifactInfo,
|
|
||||||
},
|
},
|
||||||
authType: AuthType.ConnectionString,
|
authType: AuthType.ConnectionString,
|
||||||
databaseAccount: {
|
databaseAccount: {
|
||||||
@@ -503,102 +439,11 @@ function createExplorerFabricLegacy(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
useTabs.getState().closeAllTabs();
|
||||||
const explorer = new Explorer();
|
const explorer = new Explorer();
|
||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialization for FabricMessageV3 and above
|
|
||||||
* @param params
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const createExplorerFabric = (
|
|
||||||
params: InitializeMessageV3<CosmosDbArtifactType>,
|
|
||||||
fabricClientRpcVersion: string,
|
|
||||||
): Explorer => {
|
|
||||||
updateUserContext({
|
|
||||||
fabricContext: {
|
|
||||||
fabricClientRpcVersion,
|
|
||||||
databaseName: undefined,
|
|
||||||
isVisible: params.isVisible,
|
|
||||||
isReadOnly: params.isReadOnly,
|
|
||||||
artifactType: params.artifactType,
|
|
||||||
artifactInfo: undefined,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (params.artifactType === CosmosDbArtifactType.MIRRORED_KEY) {
|
|
||||||
updateUserContext({
|
|
||||||
authType: AuthType.ConnectionString, // TODO: will need its own type
|
|
||||||
databaseAccount: {
|
|
||||||
id: "",
|
|
||||||
location: "",
|
|
||||||
type: "",
|
|
||||||
name: "Mounted", // TODO: not used?
|
|
||||||
kind: AccountKind.Default,
|
|
||||||
properties: {
|
|
||||||
documentEndpoint: undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fabricContext: {
|
|
||||||
...userContext.fabricContext,
|
|
||||||
artifactInfo: {
|
|
||||||
connectionId: (params.artifactConnectionInfo as ArtifactConnectionInfo[CosmosDbArtifactType.MIRRORED_KEY])
|
|
||||||
.connectionId,
|
|
||||||
resourceTokenInfo: undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else if (params.artifactType === CosmosDbArtifactType.MIRRORED_AAD) {
|
|
||||||
updateUserContext({
|
|
||||||
databaseAccount: {
|
|
||||||
id: "",
|
|
||||||
location: "",
|
|
||||||
type: "",
|
|
||||||
name: "Mounted", // TODO: not used?
|
|
||||||
kind: AccountKind.Default,
|
|
||||||
properties: {
|
|
||||||
documentEndpoint: undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
authType: AuthType.AAD,
|
|
||||||
dataPlaneRbacEnabled: true,
|
|
||||||
aadToken: undefined,
|
|
||||||
masterKey: undefined,
|
|
||||||
fabricContext: {
|
|
||||||
...userContext.fabricContext,
|
|
||||||
artifactInfo: undefined,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else if (params.artifactType === CosmosDbArtifactType.NATIVE) {
|
|
||||||
const nativeParams = params as InitializeMessageV3<CosmosDbArtifactType.NATIVE>;
|
|
||||||
// Make it behave like Hosted/AAD/RBAC
|
|
||||||
updateUserContext({
|
|
||||||
databaseAccount: {
|
|
||||||
id: "",
|
|
||||||
location: "",
|
|
||||||
type: "",
|
|
||||||
name: "Native", // TODO: not used?
|
|
||||||
kind: AccountKind.Default,
|
|
||||||
properties: {
|
|
||||||
documentEndpoint: nativeParams.artifactConnectionInfo.accountEndpoint,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
authType: AuthType.AAD,
|
|
||||||
dataPlaneRbacEnabled: true,
|
|
||||||
aadToken: nativeParams.artifactConnectionInfo.accessToken,
|
|
||||||
masterKey: undefined,
|
|
||||||
fabricContext: {
|
|
||||||
...userContext.fabricContext,
|
|
||||||
databaseName: nativeParams.artifactConnectionInfo.databaseName,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const explorer = new Explorer();
|
|
||||||
return explorer;
|
|
||||||
};
|
|
||||||
|
|
||||||
function configureWithEncryptedToken(config: EncryptedToken): Explorer {
|
function configureWithEncryptedToken(config: EncryptedToken): Explorer {
|
||||||
const apiExperience = DefaultExperienceUtility.getDefaultExperienceFromApiKind(config.encryptedTokenMetadata.apiKind);
|
const apiExperience = DefaultExperienceUtility.getDefaultExperienceFromApiKind(config.encryptedTokenMetadata.apiKind);
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
@@ -707,7 +552,7 @@ async function configurePortal(): Promise<Explorer> {
|
|||||||
const { databaseAccount: account, subscriptionId, resourceGroup } = userContext;
|
const { databaseAccount: account, subscriptionId, resourceGroup } = userContext;
|
||||||
|
|
||||||
let dataPlaneRbacEnabled;
|
let dataPlaneRbacEnabled;
|
||||||
if (isDataplaneRbacSupported(userContext.apiType)) {
|
if (userContext.apiType === "SQL") {
|
||||||
if (LocalStorageUtility.hasItem(StorageKey.DataPlaneRbacEnabled)) {
|
if (LocalStorageUtility.hasItem(StorageKey.DataPlaneRbacEnabled)) {
|
||||||
const isDataPlaneRbacSetting = LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled);
|
const isDataPlaneRbacSetting = LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled);
|
||||||
Logger.logInfo(
|
Logger.logInfo(
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { clamp } from "@fluentui/react";
|
import { clamp } from "@fluentui/react";
|
||||||
import { OpenTab } from "Contracts/ActionContracts";
|
import { OpenTab } from "Contracts/ActionContracts";
|
||||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
import { isFabricMirrored } from "Platform/Fabric/FabricUtil";
|
|
||||||
import {
|
import {
|
||||||
AppStateComponentNames,
|
AppStateComponentNames,
|
||||||
OPEN_TABS_SUBCOMPONENT_NAME,
|
OPEN_TABS_SUBCOMPONENT_NAME,
|
||||||
@@ -12,6 +11,7 @@ import * as ViewModels from "../Contracts/ViewModels";
|
|||||||
import { CollectionTabKind } from "../Contracts/ViewModels";
|
import { CollectionTabKind } from "../Contracts/ViewModels";
|
||||||
import NotebookTabV2 from "../Explorer/Tabs/NotebookV2Tab";
|
import NotebookTabV2 from "../Explorer/Tabs/NotebookV2Tab";
|
||||||
import TabsBase from "../Explorer/Tabs/TabsBase";
|
import TabsBase from "../Explorer/Tabs/TabsBase";
|
||||||
|
import { Platform, configContext } from "./../ConfigContext";
|
||||||
|
|
||||||
export interface TabsState {
|
export interface TabsState {
|
||||||
openedTabs: TabsBase[];
|
openedTabs: TabsBase[];
|
||||||
@@ -51,11 +51,22 @@ export enum ReactTabKind {
|
|||||||
QueryCopilot,
|
QueryCopilot,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HACK: using this const when the configuration context is not initialized yet.
|
||||||
|
// Since Fabric is always setting the url param, use that instead of the regular config.
|
||||||
|
const isPlatformFabric = (() => {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
if (params.has("platform")) {
|
||||||
|
const platform = params.get("platform");
|
||||||
|
return platform === Platform.Fabric;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})();
|
||||||
|
|
||||||
export const useTabs: UseStore<TabsState> = create((set, get) => ({
|
export const useTabs: UseStore<TabsState> = create((set, get) => ({
|
||||||
openedTabs: [] as TabsBase[],
|
openedTabs: [],
|
||||||
openedReactTabs: [ReactTabKind.Home],
|
openedReactTabs: !isPlatformFabric ? [ReactTabKind.Home] : [],
|
||||||
activeTab: undefined as TabsBase,
|
activeTab: undefined,
|
||||||
activeReactTab: ReactTabKind.Home,
|
activeReactTab: !isPlatformFabric ? ReactTabKind.Home : undefined,
|
||||||
queryCopilotTabInitialInput: "",
|
queryCopilotTabInitialInput: "",
|
||||||
isTabExecuting: false,
|
isTabExecuting: false,
|
||||||
isQueryErrorThrown: false,
|
isQueryErrorThrown: false,
|
||||||
@@ -111,7 +122,7 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
if (updatedTabs.length === 0 && !isFabricMirrored()) {
|
if (updatedTabs.length === 0 && configContext.platform !== Platform.Fabric) {
|
||||||
set({ activeTab: undefined, activeReactTab: undefined });
|
set({ activeTab: undefined, activeReactTab: undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +162,7 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (get().openedTabs.length === 0 && !isFabricMirrored()) {
|
if (get().openedTabs.length === 0 && configContext.platform !== Platform.Fabric) {
|
||||||
set({ activeTab: undefined, activeReactTab: undefined });
|
set({ activeTab: undefined, activeReactTab: undefined });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user