mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-06-05 15:12:04 +01:00
Merge branch 'master' into users/tarazou/DedicatedGatewayType
This commit is contained in:
commit
feec5fb3e2
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@ -8,6 +8,9 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
jobs:
|
jobs:
|
||||||
codemetrics:
|
codemetrics:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -134,7 +137,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||||
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
|
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@ -145,11 +148,18 @@ jobs:
|
|||||||
- ./test/mongo/container.spec.ts
|
- ./test/mongo/container.spec.ts
|
||||||
- ./test/mongo/container32.spec.ts
|
- ./test/mongo/container32.spec.ts
|
||||||
- ./test/selfServe/selfServeExample.spec.ts
|
- ./test/selfServe/selfServeExample.spec.ts
|
||||||
# - ./test/notebooks/upload.spec.ts // TEMP disabled since notebooks service is off
|
|
||||||
- ./test/sql/resourceToken.spec.ts
|
- ./test/sql/resourceToken.spec.ts
|
||||||
- ./test/tables/container.spec.ts
|
- ./test/tables/container.spec.ts
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: "Az CLI login"
|
||||||
|
uses: azure/login@v1
|
||||||
|
with:
|
||||||
|
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||||
|
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||||
|
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||||
|
|
||||||
- name: Use Node.js 18.x
|
- name: Use Node.js 18.x
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
|
15
.github/workflows/cleanup.yml
vendored
15
.github/workflows/cleanup.yml
vendored
@ -9,6 +9,10 @@ on:
|
|||||||
# Once every hour
|
# Once every hour
|
||||||
- cron: "0 15 * * *"
|
- cron: "0 15 * * *"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||||
jobs:
|
jobs:
|
||||||
# This workflow contains a single job called "build"
|
# This workflow contains a single job called "build"
|
||||||
@ -16,10 +20,17 @@ jobs:
|
|||||||
name: "Cleanup Test Database Accounts"
|
name: "Cleanup Test Database Accounts"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }}
|
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||||
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: "Az CLI login"
|
||||||
|
uses: azure/login@v1
|
||||||
|
with:
|
||||||
|
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||||
|
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||||
|
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||||
|
|
||||||
- name: Use Node.js 18.x
|
- name: Use Node.js 18.x
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
|
3
images/Home_16.svg
Normal file
3
images/Home_16.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M7.31299 1.26164C7.69849 0.897163 8.30151 0.897163 8.68701 1.26164L13.5305 5.84098C13.8302 6.12431 14 6.51853 14 6.93094V12.5002C14 13.3286 13.3284 14.0002 12.5 14.0002H10.5C9.67157 14.0002 9 13.3286 9 12.5002V10.0002C9 9.72407 8.77614 9.50021 8.5 9.50021H7.5C7.22386 9.50021 7 9.72407 7 10.0002V12.5002C7 13.3286 6.32843 14.0002 5.5 14.0002H3.5C2.67157 14.0002 2 13.3286 2 12.5002V6.93094C2 6.51853 2.1698 6.12431 2.46948 5.84098L7.31299 1.26164ZM8 1.98828L3.15649 6.56762C3.0566 6.66207 3 6.79347 3 6.93094V12.5002C3 12.7763 3.22386 13.0002 3.5 13.0002H5.5C5.77614 13.0002 6 12.7763 6 12.5002V10.0002C6 9.17179 6.67157 8.50022 7.5 8.50022H8.5C9.32843 8.50022 10 9.17179 10 10.0002V12.5002C10 12.7763 10.2239 13.0002 10.5 13.0002H12.5C12.7761 13.0002 13 12.7763 13 12.5002V6.93094C13 6.79347 12.9434 6.66207 12.8435 6.56762L8 1.98828Z" fill="#0078D4" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 968 B |
@ -76,6 +76,10 @@ module.exports = {
|
|||||||
"^dnd-core$": "dnd-core/dist/cjs",
|
"^dnd-core$": "dnd-core/dist/cjs",
|
||||||
"^react-dnd$": "react-dnd/dist/cjs",
|
"^react-dnd$": "react-dnd/dist/cjs",
|
||||||
"^react-dnd-html5-backend$": "react-dnd-html5-backend/dist/cjs",
|
"^react-dnd-html5-backend$": "react-dnd-html5-backend/dist/cjs",
|
||||||
|
"d3-force": "<rootDir>/node_modules/d3-force/dist/d3-force.min.js",
|
||||||
|
"d3-quadtree": "<rootDir>/node_modules/d3-quadtree/dist/d3-quadtree.min.js",
|
||||||
|
"d3-scale-chromatic": "<rootDir>/node_modules/d3-scale-chromatic/dist/d3-scale-chromatic.min.js",
|
||||||
|
"d3-zoom": "<rootDir>/node_modules/d3-zoom/dist/d3-zoom.min.js",
|
||||||
},
|
},
|
||||||
|
|
||||||
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||||
@ -130,7 +134,6 @@ module.exports = {
|
|||||||
|
|
||||||
// The test environment that will be used for testing
|
// The test environment that will be used for testing
|
||||||
// testEnvironment: "jest-environment-jsdom",
|
// testEnvironment: "jest-environment-jsdom",
|
||||||
|
|
||||||
modulePaths: ["node_modules", "<rootDir>/src"],
|
modulePaths: ["node_modules", "<rootDir>/src"],
|
||||||
|
|
||||||
// Options that will be passed to the testEnvironment
|
// Options that will be passed to the testEnvironment
|
||||||
|
@ -2296,6 +2296,17 @@ a:link {
|
|||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.monaco-editor .quick-input-list-label {
|
||||||
|
/* Restore some of Monaco's default styles that are clobbered by our global styles */
|
||||||
|
padding: 0;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-editor .quick-input-list .highlight {
|
||||||
|
/* Padding in highlighted text within the quick input list breaks the flow of the text */
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
td a {
|
td a {
|
||||||
color: #393939;
|
color: #393939;
|
||||||
}
|
}
|
||||||
|
1902
package-lock.json
generated
1902
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
21
package.json
21
package.json
@ -7,8 +7,8 @@
|
|||||||
"@azure/arm-cosmosdb": "9.1.0",
|
"@azure/arm-cosmosdb": "9.1.0",
|
||||||
"@azure/cosmos": "4.0.1-beta.2",
|
"@azure/cosmos": "4.0.1-beta.2",
|
||||||
"@azure/cosmos-language-service": "0.0.5",
|
"@azure/cosmos-language-service": "0.0.5",
|
||||||
"@azure/identity": "1.2.1",
|
"@azure/identity": "1.5.2",
|
||||||
"@azure/ms-rest-nodeauth": "3.0.7",
|
"@azure/ms-rest-nodeauth": "3.1.1",
|
||||||
"@azure/msal-browser": "2.14.2",
|
"@azure/msal-browser": "2.14.2",
|
||||||
"@babel/plugin-proposal-class-properties": "7.12.1",
|
"@babel/plugin-proposal-class-properties": "7.12.1",
|
||||||
"@babel/plugin-proposal-decorators": "7.12.12",
|
"@babel/plugin-proposal-decorators": "7.12.12",
|
||||||
@ -46,6 +46,7 @@
|
|||||||
"@types/lodash": "4.14.171",
|
"@types/lodash": "4.14.171",
|
||||||
"@types/mkdirp": "1.0.1",
|
"@types/mkdirp": "1.0.1",
|
||||||
"@types/node-fetch": "2.5.7",
|
"@types/node-fetch": "2.5.7",
|
||||||
|
"@xmldom/xmldom": "0.7.13",
|
||||||
"applicationinsights": "1.8.0",
|
"applicationinsights": "1.8.0",
|
||||||
"bootstrap": "3.4.1",
|
"bootstrap": "3.4.1",
|
||||||
"canvas": "file:./canvas",
|
"canvas": "file:./canvas",
|
||||||
@ -54,7 +55,7 @@
|
|||||||
"copy-webpack-plugin": "11.0.0",
|
"copy-webpack-plugin": "11.0.0",
|
||||||
"crossroads": "0.12.2",
|
"crossroads": "0.12.2",
|
||||||
"css-element-queries": "1.1.1",
|
"css-element-queries": "1.1.1",
|
||||||
"d3": "6.1.1",
|
"d3": "7.8.5",
|
||||||
"datatables.net-colreorder-dt": "1.7.0",
|
"datatables.net-colreorder-dt": "1.7.0",
|
||||||
"datatables.net-dt": "1.13.8",
|
"datatables.net-dt": "1.13.8",
|
||||||
"date-fns": "1.29.0",
|
"date-fns": "1.29.0",
|
||||||
@ -69,12 +70,14 @@
|
|||||||
"i18next-browser-languagedetector": "6.0.1",
|
"i18next-browser-languagedetector": "6.0.1",
|
||||||
"i18next-http-backend": "1.0.23",
|
"i18next-http-backend": "1.0.23",
|
||||||
"iframe-resizer-react": "1.1.0",
|
"iframe-resizer-react": "1.1.0",
|
||||||
|
"immer": "9.0.6",
|
||||||
"immutable": "4.0.0-rc.12",
|
"immutable": "4.0.0-rc.12",
|
||||||
"is-ci": "2.0.0",
|
"is-ci": "2.0.0",
|
||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
"jquery-typeahead": "2.11.1",
|
"jquery-typeahead": "2.11.1",
|
||||||
"jquery-ui-dist": "1.13.2",
|
"jquery-ui-dist": "1.13.2",
|
||||||
"knockout": "3.5.1",
|
"knockout": "3.5.1",
|
||||||
|
"loader-utils": "2.0.3",
|
||||||
"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",
|
||||||
@ -98,10 +101,12 @@
|
|||||||
"reflect-metadata": "0.1.13",
|
"reflect-metadata": "0.1.13",
|
||||||
"rx-jupyter": "5.5.12",
|
"rx-jupyter": "5.5.12",
|
||||||
"sanitize-html": "2.3.3",
|
"sanitize-html": "2.3.3",
|
||||||
|
"shell-quote": "1.7.3",
|
||||||
"styled-components": "5.0.1",
|
"styled-components": "5.0.1",
|
||||||
"swr": "0.4.0",
|
"swr": "0.4.0",
|
||||||
"terser-webpack-plugin": "5.3.9",
|
"terser-webpack-plugin": "5.3.9",
|
||||||
"underscore": "1.9.1",
|
"tinykeys": "2.1.0",
|
||||||
|
"underscore": "1.12.1",
|
||||||
"utility-types": "3.10.0",
|
"utility-types": "3.10.0",
|
||||||
"zustand": "3.5.0"
|
"zustand": "3.5.0"
|
||||||
},
|
},
|
||||||
@ -170,25 +175,25 @@
|
|||||||
"less-vars-loader": "1.1.0",
|
"less-vars-loader": "1.1.0",
|
||||||
"mini-css-extract-plugin": "2.1.0",
|
"mini-css-extract-plugin": "2.1.0",
|
||||||
"monaco-editor-webpack-plugin": "7.1.0",
|
"monaco-editor-webpack-plugin": "7.1.0",
|
||||||
"node-fetch": "2.6.1",
|
"node-fetch": "2.6.7",
|
||||||
"playwright": "1.13.0",
|
"playwright": "1.13.0",
|
||||||
"prettier": "3.0.3",
|
"prettier": "3.0.3",
|
||||||
"process": "0.11.10",
|
"process": "0.11.10",
|
||||||
"querystring-es3": "0.2.1",
|
"querystring-es3": "0.2.1",
|
||||||
"raw-loader": "0.5.1",
|
"raw-loader": "0.5.1",
|
||||||
"react-dev-utils": "11.0.4",
|
"react-dev-utils": "12.0.1",
|
||||||
"rimraf": "3.0.0",
|
"rimraf": "3.0.0",
|
||||||
"sinon": "3.2.1",
|
"sinon": "3.2.1",
|
||||||
"style-loader": "0.23.0",
|
"style-loader": "0.23.0",
|
||||||
"ts-loader": "9.2.4",
|
"ts-loader": "9.2.4",
|
||||||
"typedoc": "0.21.5",
|
"typedoc": "0.22.15",
|
||||||
"typescript": "4.3.5",
|
"typescript": "4.3.5",
|
||||||
"url-loader": "4.1.1",
|
"url-loader": "4.1.1",
|
||||||
"wait-on": "4.0.2",
|
"wait-on": "4.0.2",
|
||||||
"webpack": "5.88.2",
|
"webpack": "5.88.2",
|
||||||
"webpack-bundle-analyzer": "4.9.1",
|
"webpack-bundle-analyzer": "4.9.1",
|
||||||
"webpack-cli": "5.1.4",
|
"webpack-cli": "5.1.4",
|
||||||
"webpack-dev-server": "4.15.1"
|
"webpack-dev-server": "4.15.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "patch-package",
|
"postinstall": "patch-package",
|
||||||
|
@ -124,7 +124,36 @@ export enum MongoBackendEndpointType {
|
|||||||
remote,
|
remote,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 435619 Add default endpoints per cloud and use regional only when available
|
export class BackendApi {
|
||||||
|
public static readonly GenerateToken: string = "GenerateToken";
|
||||||
|
public static readonly PortalSettings: string = "PortalSettings";
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PortalBackendEndpoints {
|
||||||
|
public static readonly Development: string = "https://localhost:7235";
|
||||||
|
public static readonly Mpac: string = "https://cdb-ms-mpac-pbe.cosmos.azure.com";
|
||||||
|
public static readonly Prod: string = "https://cdb-ms-prod-pbe.cosmos.azure.com";
|
||||||
|
public static readonly Fairfax: string = "https://cdb-ff-prod-pbe.cosmos.azure.us";
|
||||||
|
public static readonly Mooncake: string = "https://cdb-mc-prod-pbe.cosmos.azure.cn";
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MongoProxyEndpoints {
|
||||||
|
public static readonly Local: string = "https://localhost:7238";
|
||||||
|
public static readonly Mpac: string = "https://cdb-ms-mpac-mp.cosmos.azure.com";
|
||||||
|
public static readonly Prod: string = "https://cdb-ms-prod-mp.cosmos.azure.com";
|
||||||
|
public static readonly Fairfax: string = "https://cdb-ff-prod-mp.cosmos.azure.us";
|
||||||
|
public static readonly Mooncake: string = "https://cdb-mc-prod-mp.cosmos.azure.cn";
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CassandraProxyEndpoints {
|
||||||
|
public static readonly Development: string = "https://localhost:7240";
|
||||||
|
public static readonly Mpac: string = "https://cdb-ms-mpac-cp.cosmos.azure.com";
|
||||||
|
public static readonly Prod: string = "https://cdb-ms-prod-cp.cosmos.azure.com";
|
||||||
|
public static readonly Fairfax: string = "https://cdb-ff-prod-cp.cosmos.azure.us";
|
||||||
|
public static readonly Mooncake: string = "https://cdb-mc-prod-cp.cosmos.azure.cn";
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: Remove this when new backend is migrated over
|
||||||
export class CassandraBackend {
|
export class CassandraBackend {
|
||||||
public static readonly createOrDeleteApi: string = "api/cassandra/createordelete";
|
public static readonly createOrDeleteApi: string = "api/cassandra/createordelete";
|
||||||
public static readonly guestCreateOrDeleteApi: string = "api/guest/cassandra/createordelete";
|
public static readonly guestCreateOrDeleteApi: string = "api/guest/cassandra/createordelete";
|
||||||
@ -136,6 +165,17 @@ export class CassandraBackend {
|
|||||||
public static readonly guestSchemaApi: string = "api/guest/cassandra/schema";
|
public static readonly guestSchemaApi: string = "api/guest/cassandra/schema";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class CassandraProxyAPIs {
|
||||||
|
public static readonly createOrDeleteApi: string = "api/cassandra/createordelete";
|
||||||
|
public static readonly connectionStringCreateOrDeleteApi: string = "api/connectionstring/cassandra/createordelete";
|
||||||
|
public static readonly queryApi: string = "api/cassandra";
|
||||||
|
public static readonly connectionStringQueryApi: string = "api/connectionstring/cassandra";
|
||||||
|
public static readonly keysApi: string = "api/cassandra/keys";
|
||||||
|
public static readonly connectionStringKeysApi: string = "api/connectionstring/cassandra/keys";
|
||||||
|
public static readonly schemaApi: string = "api/cassandra/schema";
|
||||||
|
public static readonly connectionStringSchemaApi: string = "api/connectionstring/cassandra/schema";
|
||||||
|
}
|
||||||
|
|
||||||
export class Queries {
|
export class Queries {
|
||||||
public static CustomPageOption: string = "custom";
|
public static CustomPageOption: string = "custom";
|
||||||
public static UnlimitedPageOption: string = "unlimited";
|
public static UnlimitedPageOption: string = "unlimited";
|
||||||
@ -435,22 +475,6 @@ export class JunoEndpoints {
|
|||||||
public static readonly Stage = "https://tools-staging.cosmos.azure.com";
|
public static readonly Stage = "https://tools-staging.cosmos.azure.com";
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MongoProxyEndpoints {
|
|
||||||
public static readonly Development: string = "https://localhost:7238";
|
|
||||||
public static readonly Mpac: string = "https://cdb-ms-mpac-mp.cosmos.azure.com";
|
|
||||||
public static readonly Prod: string = "https://cdb-ms-prod-mp.cosmos.azure.com";
|
|
||||||
public static readonly Fairfax: string = "https://cdb-ff-prod-mp.cosmos.azure.us";
|
|
||||||
public static readonly Mooncake: string = "https://cdb-mc-prod-mp.cosmos.azure.cn";
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CassandraProxyEndpoints {
|
|
||||||
public static readonly Development: string = "https://localhost:7240";
|
|
||||||
public static readonly Mpac: string = "https://cdb-ms-mpac-cp.cosmos.azure.com";
|
|
||||||
public static readonly Prod: string = "https://cdb-ms-prod-cp.cosmos.azure.com";
|
|
||||||
public static readonly Fairfax: string = "https://cdb-ff-prod-cp.cosmos.azure.us";
|
|
||||||
public static readonly Mooncake: string = "https://cdb-mc-prod-cp.cosmos.azure.cn";
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PriorityLevel {
|
export class PriorityLevel {
|
||||||
public static readonly High = "high";
|
public static readonly High = "high";
|
||||||
public static readonly Low = "low";
|
public static readonly Low = "low";
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import * as Cosmos from "@azure/cosmos";
|
import * as Cosmos from "@azure/cosmos";
|
||||||
import { sendCachedDataMessage } from "Common/MessageHandler";
|
|
||||||
import { getAuthorizationTokenUsingResourceTokens } from "Common/getAuthorizationTokenUsingResourceTokens";
|
import { getAuthorizationTokenUsingResourceTokens } from "Common/getAuthorizationTokenUsingResourceTokens";
|
||||||
import { AuthorizationToken, MessageTypes } from "Contracts/MessageTypes";
|
import { AuthorizationToken } from "Contracts/MessageTypes";
|
||||||
import { checkDatabaseResourceTokensValidity } 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";
|
||||||
@ -51,15 +50,23 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
|||||||
case Cosmos.ResourceType.offer:
|
case Cosmos.ResourceType.offer:
|
||||||
case Cosmos.ResourceType.user:
|
case Cosmos.ResourceType.user:
|
||||||
case Cosmos.ResourceType.permission:
|
case Cosmos.ResourceType.permission:
|
||||||
// User master tokens
|
// For now, these operations aren't used, so fetching the authorization token is commented out.
|
||||||
const authorizationToken = await sendCachedDataMessage<AuthorizationToken>(
|
// This provider must return a real token to pass validation by the client, so we return the cached resource token
|
||||||
MessageTypes.GetAuthorizationToken,
|
// (which is a valid token, but won't work for these operations).
|
||||||
[requestInfo],
|
const resourceTokens2 = userContext.fabricContext.databaseConnectionInfo.resourceTokens;
|
||||||
userContext.fabricContext.connectionId,
|
return getAuthorizationTokenUsingResourceTokens(resourceTokens2, requestInfo.path, requestInfo.resourceId);
|
||||||
);
|
|
||||||
console.log("Response from Fabric: ", authorizationToken);
|
/* ************** TODO: Uncomment this code if we need to support these operations **************
|
||||||
headers[HttpHeaders.msDate] = authorizationToken.XDate;
|
// User master tokens
|
||||||
return decodeURIComponent(authorizationToken.PrimaryReadWriteToken);
|
const authorizationToken = await sendCachedDataMessage<AuthorizationToken>(
|
||||||
|
MessageTypes.GetAuthorizationToken,
|
||||||
|
[requestInfo],
|
||||||
|
userContext.fabricContext.connectionId,
|
||||||
|
);
|
||||||
|
console.log("Response from Fabric: ", authorizationToken);
|
||||||
|
headers[HttpHeaders.msDate] = authorizationToken.XDate;
|
||||||
|
return decodeURIComponent(authorizationToken.PrimaryReadWriteToken);
|
||||||
|
***********************************************************************************************/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ export function queryDocuments(
|
|||||||
query: string,
|
query: string,
|
||||||
continuationToken?: string,
|
continuationToken?: string,
|
||||||
): Promise<QueryResponse> {
|
): Promise<QueryResponse> {
|
||||||
if (!useMongoProxyEndpoint("resourcelist")) {
|
if (!useMongoProxyEndpoint("resourcelist") || !useMongoProxyEndpoint("queryDocuments")) {
|
||||||
return queryDocuments_ToBeDeprecated(databaseId, collection, isResourceList, query, continuationToken);
|
return queryDocuments_ToBeDeprecated(databaseId, collection, isResourceList, query, continuationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ export function queryDocuments(
|
|||||||
headers[CosmosSDKConstants.HttpHeaders.Continuation] = continuationToken;
|
headers[CosmosSDKConstants.HttpHeaders.Continuation] = continuationToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
const path = isResourceList ? "/resourcelist" : "";
|
const path = isResourceList ? "/resourcelist" : "/queryDocuments";
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}${path}`, {
|
.fetch(`${endpoint}${path}`, {
|
||||||
@ -672,6 +672,27 @@ export function getEndpoint(endpoint: string): string {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useMongoProxyEndpoint(api: string): boolean {
|
||||||
|
const activeMongoProxyEndpoints: string[] = [
|
||||||
|
MongoProxyEndpoints.Local,
|
||||||
|
MongoProxyEndpoints.Mpac,
|
||||||
|
MongoProxyEndpoints.Prod,
|
||||||
|
];
|
||||||
|
let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
|
||||||
|
if (
|
||||||
|
configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Local &&
|
||||||
|
userContext.databaseAccount.properties.ipRules?.length > 0
|
||||||
|
) {
|
||||||
|
canAccessMongoProxy = canAccessMongoProxy && configContext.MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
canAccessMongoProxy &&
|
||||||
|
configContext.NEW_MONGO_APIS?.includes(api) &&
|
||||||
|
activeMongoProxyEndpoints.includes(configContext.MONGO_PROXY_ENDPOINT)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: This function throws most of the time except on Forbidden which is a bit strange
|
// TODO: This function throws most of the time except on Forbidden which is a bit strange
|
||||||
// It causes problems for TypeScript understanding the types
|
// It causes problems for TypeScript understanding the types
|
||||||
async function errorHandling(response: Response, action: string, params: unknown): Promise<void> {
|
async function errorHandling(response: Response, action: string, params: unknown): Promise<void> {
|
||||||
@ -688,16 +709,3 @@ async function errorHandling(response: Response, action: string, params: unknown
|
|||||||
export function getARMCreateCollectionEndpoint(params: DataModels.MongoParameters): string {
|
export function getARMCreateCollectionEndpoint(params: DataModels.MongoParameters): string {
|
||||||
return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${userContext.databaseAccount.name}/mongodbDatabases/${params.db}/collections/${params.coll}`;
|
return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${userContext.databaseAccount.name}/mongodbDatabases/${params.db}/collections/${params.coll}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useMongoProxyEndpoint(api: string): boolean {
|
|
||||||
let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
|
|
||||||
if (userContext.databaseAccount.properties.ipRules?.length > 0) {
|
|
||||||
canAccessMongoProxy = canAccessMongoProxy && configContext.MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
canAccessMongoProxy &&
|
|
||||||
configContext.NEW_MONGO_APIS?.includes(api) &&
|
|
||||||
[MongoProxyEndpoints.Development, MongoProxyEndpoints.Mpac].includes(configContext.MONGO_PROXY_ENDPOINT)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
@ -122,14 +122,21 @@ const pollDataTransferJobOperation = async (
|
|||||||
|
|
||||||
updateDataTransferJob(body);
|
updateDataTransferJob(body);
|
||||||
|
|
||||||
if (status === "Cancelled" || status === "Failed" || status === "Faulted") {
|
if (status === "Cancelled") {
|
||||||
|
removeFromPolling(jobName);
|
||||||
|
clearMessage && clearMessage();
|
||||||
|
const cancelMessage = `Data transfer job ${jobName} cancelled`;
|
||||||
|
NotificationConsoleUtils.logConsoleError(cancelMessage);
|
||||||
|
throw new AbortError(cancelMessage);
|
||||||
|
}
|
||||||
|
if (status === "Failed" || status === "Faulted") {
|
||||||
removeFromPolling(jobName);
|
removeFromPolling(jobName);
|
||||||
const errorMessage = body?.properties?.error
|
const errorMessage = body?.properties?.error
|
||||||
? JSON.stringify(body?.properties?.error)
|
? JSON.stringify(body?.properties?.error)
|
||||||
: "Operation could not be completed";
|
: "Operation could not be completed";
|
||||||
const error = new Error(errorMessage);
|
const error = new Error(errorMessage);
|
||||||
clearMessage && clearMessage();
|
clearMessage && clearMessage();
|
||||||
NotificationConsoleUtils.logConsoleError(`Data transfer job ${jobName} Failed`);
|
NotificationConsoleUtils.logConsoleError(`Data transfer job ${jobName} failed: ${errorMessage}`);
|
||||||
throw new AbortError(error);
|
throw new AbortError(error);
|
||||||
}
|
}
|
||||||
if (status === "Completed") {
|
if (status === "Completed") {
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
import { CassandraProxyEndpoints, JunoEndpoints, MongoProxyEndpoints } from "Common/Constants";
|
import {
|
||||||
|
BackendApi,
|
||||||
|
CassandraProxyEndpoints,
|
||||||
|
JunoEndpoints,
|
||||||
|
MongoProxyEndpoints,
|
||||||
|
PortalBackendEndpoints,
|
||||||
|
} from "Common/Constants";
|
||||||
import {
|
import {
|
||||||
allowedAadEndpoints,
|
allowedAadEndpoints,
|
||||||
allowedArcadiaEndpoints,
|
allowedArcadiaEndpoints,
|
||||||
@ -39,11 +45,15 @@ export interface ConfigContext {
|
|||||||
ARCADIA_ENDPOINT: string;
|
ARCADIA_ENDPOINT: string;
|
||||||
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string;
|
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string;
|
||||||
BACKEND_ENDPOINT?: string;
|
BACKEND_ENDPOINT?: string;
|
||||||
|
PORTAL_BACKEND_ENDPOINT?: string;
|
||||||
|
NEW_BACKEND_APIS?: BackendApi[];
|
||||||
MONGO_BACKEND_ENDPOINT?: string;
|
MONGO_BACKEND_ENDPOINT?: string;
|
||||||
MONGO_PROXY_ENDPOINT?: string;
|
MONGO_PROXY_ENDPOINT?: string;
|
||||||
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED?: boolean;
|
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED?: boolean;
|
||||||
NEW_MONGO_APIS?: string[];
|
NEW_MONGO_APIS?: string[];
|
||||||
CASSANDRA_PROXY_ENDPOINT?: string;
|
CASSANDRA_PROXY_ENDPOINT?: string;
|
||||||
|
CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: boolean;
|
||||||
|
NEW_CASSANDRA_APIS?: string[];
|
||||||
PROXY_PATH?: string;
|
PROXY_PATH?: string;
|
||||||
JUNO_ENDPOINT: string;
|
JUNO_ENDPOINT: string;
|
||||||
GITHUB_CLIENT_ID: string;
|
GITHUB_CLIENT_ID: string;
|
||||||
@ -73,6 +83,7 @@ let configContext: Readonly<ConfigContext> = {
|
|||||||
`^https:\\/\\/.*\\.analysis-df\\.net$`,
|
`^https:\\/\\/.*\\.analysis-df\\.net$`,
|
||||||
`^https:\\/\\/.*\\.analysis-df\\.windows\\.net$`,
|
`^https:\\/\\/.*\\.analysis-df\\.windows\\.net$`,
|
||||||
`^https:\\/\\/.*\\.azure-test\\.net$`,
|
`^https:\\/\\/.*\\.azure-test\\.net$`,
|
||||||
|
`^https:\\/\\/cosmos-explorer-preview\\.azurewebsites\\.net`,
|
||||||
], // Webpack injects this at build time
|
], // Webpack injects this at build time
|
||||||
gitSha: process.env.GIT_SHA,
|
gitSha: process.env.GIT_SHA,
|
||||||
hostedExplorerURL: "https://cosmos.azure.com/",
|
hostedExplorerURL: "https://cosmos.azure.com/",
|
||||||
@ -88,17 +99,22 @@ let configContext: Readonly<ConfigContext> = {
|
|||||||
GITHUB_TEST_ENV_CLIENT_ID: "b63fc8cbf87fd3c6e2eb", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1777772
|
GITHUB_TEST_ENV_CLIENT_ID: "b63fc8cbf87fd3c6e2eb", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1777772
|
||||||
JUNO_ENDPOINT: JunoEndpoints.Prod,
|
JUNO_ENDPOINT: JunoEndpoints.Prod,
|
||||||
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
|
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
|
||||||
|
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod,
|
||||||
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
||||||
NEW_MONGO_APIS: [
|
NEW_MONGO_APIS: [
|
||||||
// "resourcelist",
|
"resourcelist",
|
||||||
// "createDocument",
|
"queryDocuments",
|
||||||
// "readDocument",
|
"createDocument",
|
||||||
// "updateDocument",
|
"readDocument",
|
||||||
// "deleteDocument",
|
"updateDocument",
|
||||||
// "createCollectionWithProxy",
|
"deleteDocument",
|
||||||
|
"createCollectionWithProxy",
|
||||||
|
"legacyMongoShell",
|
||||||
],
|
],
|
||||||
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
|
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
|
||||||
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod,
|
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod,
|
||||||
|
NEW_CASSANDRA_APIS: ["postQuery", "createOrDelete", "getKeys", "getSchema"],
|
||||||
|
CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
|
||||||
isTerminalEnabled: false,
|
isTerminalEnabled: false,
|
||||||
isPhoenixEnabled: false,
|
isPhoenixEnabled: false,
|
||||||
};
|
};
|
||||||
|
@ -159,6 +159,7 @@ export interface Collection extends Resource {
|
|||||||
geospatialConfig?: GeospatialConfig;
|
geospatialConfig?: GeospatialConfig;
|
||||||
schema?: ISchema;
|
schema?: ISchema;
|
||||||
requestSchema?: () => void;
|
requestSchema?: () => void;
|
||||||
|
computedProperties?: ComputedProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionsWithPagination {
|
export interface CollectionsWithPagination {
|
||||||
@ -197,6 +198,13 @@ export interface IndexingPolicy {
|
|||||||
spatialIndexes?: any;
|
spatialIndexes?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ComputedProperty {
|
||||||
|
name: string;
|
||||||
|
query: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ComputedProperties = ComputedProperty[];
|
||||||
|
|
||||||
export interface PartitionKey {
|
export interface PartitionKey {
|
||||||
paths: string[];
|
paths: string[];
|
||||||
kind: "Hash" | "Range" | "MultiHash";
|
kind: "Hash" | "Range" | "MultiHash";
|
||||||
|
@ -53,6 +53,7 @@ export type FabricMessageV2 =
|
|||||||
id: string;
|
id: string;
|
||||||
message: {
|
message: {
|
||||||
connectionId: string;
|
connectionId: string;
|
||||||
|
isVisible: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
@ -72,7 +73,7 @@ export type FabricMessageV2 =
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "setToolbarStatus";
|
type: "explorerVisible";
|
||||||
message: {
|
message: {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
};
|
};
|
||||||
|
@ -47,6 +47,7 @@ export enum MessageTypes {
|
|||||||
GetAllResourceTokens, // Data Explorer -> Fabric
|
GetAllResourceTokens, // Data Explorer -> Fabric
|
||||||
Ready, // Data Explorer -> Fabric
|
Ready, // Data Explorer -> Fabric
|
||||||
OpenCESCVAFeedbackBlade,
|
OpenCESCVAFeedbackBlade,
|
||||||
|
ActivateTab,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthorizationToken {
|
export interface AuthorizationToken {
|
||||||
|
@ -135,6 +135,7 @@ export interface Collection extends CollectionBase {
|
|||||||
changeFeedPolicy: ko.Observable<DataModels.ChangeFeedPolicy>;
|
changeFeedPolicy: ko.Observable<DataModels.ChangeFeedPolicy>;
|
||||||
geospatialConfig: ko.Observable<DataModels.GeospatialConfig>;
|
geospatialConfig: ko.Observable<DataModels.GeospatialConfig>;
|
||||||
documentIds: ko.ObservableArray<DocumentId>;
|
documentIds: ko.ObservableArray<DocumentId>;
|
||||||
|
computedProperties: ko.Observable<DataModels.ComputedProperties>;
|
||||||
|
|
||||||
cassandraKeys: CassandraTableKeys;
|
cassandraKeys: CassandraTableKeys;
|
||||||
cassandraSchema: CassandraTableKey[];
|
cassandraSchema: CassandraTableKey[];
|
||||||
@ -386,6 +387,7 @@ export interface DataExplorerInputsFrame {
|
|||||||
dnsSuffix?: string;
|
dnsSuffix?: string;
|
||||||
serverId?: string;
|
serverId?: string;
|
||||||
extensionEndpoint?: string;
|
extensionEndpoint?: string;
|
||||||
|
portalBackendEndpoint?: string;
|
||||||
mongoProxyEndpoint?: string;
|
mongoProxyEndpoint?: string;
|
||||||
cassandraProxyEndpoint?: string;
|
cassandraProxyEndpoint?: string;
|
||||||
subscriptionType?: SubscriptionType;
|
subscriptionType?: SubscriptionType;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* React component for Command button component.
|
* React component for Command button component.
|
||||||
*/
|
*/
|
||||||
|
import { KeyboardAction } from "KeyboardShortcuts";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
|
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
|
||||||
import { KeyCodes } from "../../../Common/Constants";
|
import { KeyCodes } from "../../../Common/Constants";
|
||||||
@ -30,7 +31,7 @@ export interface CommandButtonComponentProps {
|
|||||||
/**
|
/**
|
||||||
* Click handler for command button click
|
* Click handler for command button click
|
||||||
*/
|
*/
|
||||||
onCommandClick: (e: React.SyntheticEvent) => void;
|
onCommandClick: (e: React.SyntheticEvent | KeyboardEvent) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Label for the button
|
* Label for the button
|
||||||
@ -107,10 +108,17 @@ export interface CommandButtonComponentProps {
|
|||||||
* Vertical bar to divide buttons
|
* Vertical bar to divide buttons
|
||||||
*/
|
*/
|
||||||
isDivider?: boolean;
|
isDivider?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Aria-label for the button
|
* Aria-label for the button
|
||||||
*/
|
*/
|
||||||
ariaLabel: string;
|
ariaLabel: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If specified, a keyboard action that should trigger this button's onCommandClick handler when activated.
|
||||||
|
* If not specified, the button will not be triggerable by keyboard shortcuts.
|
||||||
|
*/
|
||||||
|
keyboardAction?: KeyboardAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CommandButtonComponent extends React.Component<CommandButtonComponentProps> {
|
export class CommandButtonComponent extends React.Component<CommandButtonComponentProps> {
|
||||||
|
@ -46,9 +46,25 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate(previous: EditorReactProps) {
|
public componentDidUpdate() {
|
||||||
if (this.props.content !== previous.content) {
|
if (!this.editor) {
|
||||||
this.editor?.setValue(this.props.content);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingContent = this.editor.getModel().getValue();
|
||||||
|
|
||||||
|
if (this.props.content !== existingContent) {
|
||||||
|
if (this.props.isReadOnly) {
|
||||||
|
this.editor.setValue(this.props.content);
|
||||||
|
} else {
|
||||||
|
this.editor.pushUndoStop();
|
||||||
|
this.editor.executeEdits("", [
|
||||||
|
{
|
||||||
|
range: this.editor.getModel().getFullModelRange(),
|
||||||
|
text: this.props.content,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,9 +87,14 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
|||||||
|
|
||||||
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
|
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
|
||||||
this.editor = editor;
|
this.editor = editor;
|
||||||
const queryEditorModel = this.editor.getModel();
|
|
||||||
if (!this.props.isReadOnly && this.props.onContentChanged) {
|
if (!this.props.isReadOnly && this.props.onContentChanged) {
|
||||||
queryEditorModel.onDidChangeContent(() => {
|
// Hooking the model's onDidChangeContent event because of some event ordering issues.
|
||||||
|
// If a single user input causes BOTH the editor content to change AND the cursor selection to change (which is likely),
|
||||||
|
// then there are some inconsistencies as to which event fires first.
|
||||||
|
// But the editor.onDidChangeModelContent event seems to always fire before the cursor selection event.
|
||||||
|
// (This is NOT true for the model's onDidChangeContent event, which sometimes fires after the cursor selection event.)
|
||||||
|
// If the cursor selection event fires first, then the calling component may re-render the component with old content, so we want to ensure the model content changed event always fires first.
|
||||||
|
this.editor.onDidChangeModelContent(() => {
|
||||||
const queryEditorModel = this.editor.getModel();
|
const queryEditorModel = this.editor.getModel();
|
||||||
this.props.onContentChanged(queryEditorModel.getValue());
|
this.props.onContentChanged(queryEditorModel.getValue());
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "@fluentui/react";
|
import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "@fluentui/react";
|
||||||
|
import {
|
||||||
|
ComputedPropertiesComponent,
|
||||||
|
ComputedPropertiesComponentProps,
|
||||||
|
} from "Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
import { isRunningOnPublicCloud } from "Utils/CloudUtils";
|
import { isRunningOnPublicCloud } from "Utils/CloudUtils";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
@ -108,6 +112,11 @@ export interface SettingsComponentState {
|
|||||||
indexesToAdd: AddMongoIndexProps[];
|
indexesToAdd: AddMongoIndexProps[];
|
||||||
indexTransformationProgress: number;
|
indexTransformationProgress: number;
|
||||||
|
|
||||||
|
computedPropertiesContent: DataModels.ComputedProperties;
|
||||||
|
computedPropertiesContentBaseline: DataModels.ComputedProperties;
|
||||||
|
shouldDiscardComputedProperties: boolean;
|
||||||
|
isComputedPropertiesDirty: boolean;
|
||||||
|
|
||||||
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
|
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
|
||||||
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
|
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
|
||||||
conflictResolutionPolicyPath: string;
|
conflictResolutionPolicyPath: string;
|
||||||
@ -132,6 +141,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
private offer: DataModels.Offer;
|
private offer: DataModels.Offer;
|
||||||
private changeFeedPolicyVisible: boolean;
|
private changeFeedPolicyVisible: boolean;
|
||||||
private isFixedContainer: boolean;
|
private isFixedContainer: boolean;
|
||||||
|
private shouldShowComputedPropertiesEditor: boolean;
|
||||||
private shouldShowIndexingPolicyEditor: boolean;
|
private shouldShowIndexingPolicyEditor: boolean;
|
||||||
private shouldShowPartitionKeyEditor: boolean;
|
private shouldShowPartitionKeyEditor: boolean;
|
||||||
private totalThroughputUsed: number;
|
private totalThroughputUsed: number;
|
||||||
@ -145,6 +155,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.collection = this.props.settingsTab.collection as ViewModels.Collection;
|
this.collection = this.props.settingsTab.collection as ViewModels.Collection;
|
||||||
this.offer = this.collection?.offer();
|
this.offer = this.collection?.offer();
|
||||||
this.isAnalyticalStorageEnabled = !!this.collection?.analyticalStorageTtl();
|
this.isAnalyticalStorageEnabled = !!this.collection?.analyticalStorageTtl();
|
||||||
|
this.shouldShowComputedPropertiesEditor = userContext.apiType === "SQL";
|
||||||
this.shouldShowIndexingPolicyEditor = userContext.apiType !== "Cassandra" && userContext.apiType !== "Mongo";
|
this.shouldShowIndexingPolicyEditor = userContext.apiType !== "Cassandra" && userContext.apiType !== "Mongo";
|
||||||
this.shouldShowPartitionKeyEditor = userContext.apiType === "SQL" && isRunningOnPublicCloud();
|
this.shouldShowPartitionKeyEditor = userContext.apiType === "SQL" && isRunningOnPublicCloud();
|
||||||
|
|
||||||
@ -198,6 +209,11 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
isMongoIndexingPolicyDiscardable: false,
|
isMongoIndexingPolicyDiscardable: false,
|
||||||
indexTransformationProgress: undefined,
|
indexTransformationProgress: undefined,
|
||||||
|
|
||||||
|
computedPropertiesContent: undefined,
|
||||||
|
computedPropertiesContentBaseline: undefined,
|
||||||
|
shouldDiscardComputedProperties: false,
|
||||||
|
isComputedPropertiesDirty: false,
|
||||||
|
|
||||||
conflictResolutionPolicyMode: undefined,
|
conflictResolutionPolicyMode: undefined,
|
||||||
conflictResolutionPolicyModeBaseline: undefined,
|
conflictResolutionPolicyModeBaseline: undefined,
|
||||||
conflictResolutionPolicyPath: undefined,
|
conflictResolutionPolicyPath: undefined,
|
||||||
@ -288,6 +304,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.state.isSubSettingsSaveable ||
|
this.state.isSubSettingsSaveable ||
|
||||||
this.state.isIndexingPolicyDirty ||
|
this.state.isIndexingPolicyDirty ||
|
||||||
this.state.isConflictResolutionDirty ||
|
this.state.isConflictResolutionDirty ||
|
||||||
|
this.state.isComputedPropertiesDirty ||
|
||||||
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicySaveable)
|
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicySaveable)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -298,6 +315,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.state.isSubSettingsDiscardable ||
|
this.state.isSubSettingsDiscardable ||
|
||||||
this.state.isIndexingPolicyDirty ||
|
this.state.isIndexingPolicyDirty ||
|
||||||
this.state.isConflictResolutionDirty ||
|
this.state.isConflictResolutionDirty ||
|
||||||
|
this.state.isComputedPropertiesDirty ||
|
||||||
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicyDiscardable)
|
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicyDiscardable)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -402,6 +420,9 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
isMongoIndexingPolicySaveable: false,
|
isMongoIndexingPolicySaveable: false,
|
||||||
isMongoIndexingPolicyDiscardable: false,
|
isMongoIndexingPolicyDiscardable: false,
|
||||||
isConflictResolutionDirty: false,
|
isConflictResolutionDirty: false,
|
||||||
|
computedPropertiesContent: this.state.computedPropertiesContentBaseline,
|
||||||
|
shouldDiscardComputedProperties: true,
|
||||||
|
isComputedPropertiesDirty: false,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -521,6 +542,31 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
private onMongoIndexingPolicyDiscardableChange = (isMongoIndexingPolicyDiscardable: boolean): void =>
|
private onMongoIndexingPolicyDiscardableChange = (isMongoIndexingPolicyDiscardable: boolean): void =>
|
||||||
this.setState({ isMongoIndexingPolicyDiscardable });
|
this.setState({ isMongoIndexingPolicyDiscardable });
|
||||||
|
|
||||||
|
private onComputedPropertiesContentChange = (newComputedProperties: DataModels.ComputedProperties): void =>
|
||||||
|
this.setState({ computedPropertiesContent: newComputedProperties });
|
||||||
|
|
||||||
|
private resetShouldDiscardComputedProperties = (): void => this.setState({ shouldDiscardComputedProperties: false });
|
||||||
|
|
||||||
|
private logComputedPropertiesSuccessMessage = (): void => {
|
||||||
|
if (this.props.settingsTab.onLoadStartKey) {
|
||||||
|
traceSuccess(
|
||||||
|
Action.Tab,
|
||||||
|
{
|
||||||
|
databaseName: this.collection.databaseId,
|
||||||
|
collectionName: this.collection.id(),
|
||||||
|
|
||||||
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
|
tabTitle: this.props.settingsTab.tabTitle(),
|
||||||
|
},
|
||||||
|
this.props.settingsTab.onLoadStartKey,
|
||||||
|
);
|
||||||
|
this.props.settingsTab.onLoadStartKey = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private onComputedPropertiesDirtyChange = (isComputedPropertiesDirty: boolean): void =>
|
||||||
|
this.setState({ isComputedPropertiesDirty: isComputedPropertiesDirty });
|
||||||
|
|
||||||
private calculateTotalThroughputUsed = (): void => {
|
private calculateTotalThroughputUsed = (): void => {
|
||||||
this.totalThroughputUsed = 0;
|
this.totalThroughputUsed = 0;
|
||||||
(useDatabases.getState().databases || []).forEach(async (database) => {
|
(useDatabases.getState().databases || []).forEach(async (database) => {
|
||||||
@ -643,7 +689,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
const indexingPolicyContent = this.collection.indexingPolicy();
|
const indexingPolicyContent = this.collection.indexingPolicy();
|
||||||
const conflictResolutionPolicy: DataModels.ConflictResolutionPolicy =
|
const conflictResolutionPolicy: DataModels.ConflictResolutionPolicy =
|
||||||
this.collection.conflictResolutionPolicy && this.collection.conflictResolutionPolicy();
|
this.collection.conflictResolutionPolicy && this.collection.conflictResolutionPolicy();
|
||||||
|
|
||||||
const conflictResolutionPolicyMode = parseConflictResolutionMode(conflictResolutionPolicy?.mode);
|
const conflictResolutionPolicyMode = parseConflictResolutionMode(conflictResolutionPolicy?.mode);
|
||||||
const conflictResolutionPolicyPath = conflictResolutionPolicy?.conflictResolutionPath;
|
const conflictResolutionPolicyPath = conflictResolutionPolicy?.conflictResolutionPath;
|
||||||
const conflictResolutionPolicyProcedure = parseConflictResolutionProcedure(
|
const conflictResolutionPolicyProcedure = parseConflictResolutionProcedure(
|
||||||
@ -652,6 +697,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
const geospatialConfigTypeString: string =
|
const geospatialConfigTypeString: string =
|
||||||
(this.collection.geospatialConfig && this.collection.geospatialConfig()?.type) || GeospatialConfigType.Geometry;
|
(this.collection.geospatialConfig && this.collection.geospatialConfig()?.type) || GeospatialConfigType.Geometry;
|
||||||
const geoSpatialConfigType = GeospatialConfigType[geospatialConfigTypeString as keyof typeof GeospatialConfigType];
|
const geoSpatialConfigType = GeospatialConfigType[geospatialConfigTypeString as keyof typeof GeospatialConfigType];
|
||||||
|
let computedPropertiesContent = this.collection.computedProperties();
|
||||||
|
if (!computedPropertiesContent || computedPropertiesContent.length === 0) {
|
||||||
|
computedPropertiesContent = [
|
||||||
|
{ name: "name_of_property", query: "query_to_compute_property" },
|
||||||
|
] as DataModels.ComputedProperties;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
throughput: offerThroughput,
|
throughput: offerThroughput,
|
||||||
@ -678,6 +729,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
conflictResolutionPolicyProcedureBaseline: conflictResolutionPolicyProcedure,
|
conflictResolutionPolicyProcedureBaseline: conflictResolutionPolicyProcedure,
|
||||||
geospatialConfigType: geoSpatialConfigType,
|
geospatialConfigType: geoSpatialConfigType,
|
||||||
geospatialConfigTypeBaseline: geoSpatialConfigType,
|
geospatialConfigTypeBaseline: geoSpatialConfigType,
|
||||||
|
computedPropertiesContent: computedPropertiesContent,
|
||||||
|
computedPropertiesContentBaseline: computedPropertiesContent,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -794,7 +847,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
private saveCollectionSettings = async (startKey: number): Promise<void> => {
|
private saveCollectionSettings = async (startKey: number): Promise<void> => {
|
||||||
const newCollection: DataModels.Collection = { ...this.collection.rawDataModel };
|
const newCollection: DataModels.Collection = { ...this.collection.rawDataModel };
|
||||||
|
|
||||||
if (this.state.isSubSettingsSaveable || this.state.isIndexingPolicyDirty || this.state.isConflictResolutionDirty) {
|
if (
|
||||||
|
this.state.isSubSettingsSaveable ||
|
||||||
|
this.state.isIndexingPolicyDirty ||
|
||||||
|
this.state.isConflictResolutionDirty ||
|
||||||
|
this.state.isComputedPropertiesDirty
|
||||||
|
) {
|
||||||
let defaultTtl: number;
|
let defaultTtl: number;
|
||||||
switch (this.state.timeToLive) {
|
switch (this.state.timeToLive) {
|
||||||
case TtlType.On:
|
case TtlType.On:
|
||||||
@ -832,6 +890,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
newCollection.conflictResolutionPolicy = conflictResolutionChanges;
|
newCollection.conflictResolutionPolicy = conflictResolutionChanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.state.isComputedPropertiesDirty) {
|
||||||
|
newCollection.computedProperties = this.state.computedPropertiesContent;
|
||||||
|
}
|
||||||
|
|
||||||
const updatedCollection: DataModels.Collection = await updateCollection(
|
const updatedCollection: DataModels.Collection = await updateCollection(
|
||||||
this.collection.databaseId,
|
this.collection.databaseId,
|
||||||
this.collection.id(),
|
this.collection.id(),
|
||||||
@ -845,6 +907,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.collection.conflictResolutionPolicy(updatedCollection.conflictResolutionPolicy);
|
this.collection.conflictResolutionPolicy(updatedCollection.conflictResolutionPolicy);
|
||||||
this.collection.changeFeedPolicy(updatedCollection.changeFeedPolicy);
|
this.collection.changeFeedPolicy(updatedCollection.changeFeedPolicy);
|
||||||
this.collection.geospatialConfig(updatedCollection.geospatialConfig);
|
this.collection.geospatialConfig(updatedCollection.geospatialConfig);
|
||||||
|
this.collection.computedProperties(updatedCollection.computedProperties);
|
||||||
|
|
||||||
if (wasIndexingPolicyModified) {
|
if (wasIndexingPolicyModified) {
|
||||||
await this.refreshIndexTransformationProgress();
|
await this.refreshIndexTransformationProgress();
|
||||||
@ -855,6 +918,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
isSubSettingsDiscardable: false,
|
isSubSettingsDiscardable: false,
|
||||||
isIndexingPolicyDirty: false,
|
isIndexingPolicyDirty: false,
|
||||||
isConflictResolutionDirty: false,
|
isConflictResolutionDirty: false,
|
||||||
|
isComputedPropertiesDirty: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1049,6 +1113,16 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
onMongoIndexingPolicyDiscardableChange: this.onMongoIndexingPolicyDiscardableChange,
|
onMongoIndexingPolicyDiscardableChange: this.onMongoIndexingPolicyDiscardableChange,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const computedPropertiesComponentProps: ComputedPropertiesComponentProps = {
|
||||||
|
computedPropertiesContent: this.state.computedPropertiesContent,
|
||||||
|
computedPropertiesContentBaseline: this.state.computedPropertiesContentBaseline,
|
||||||
|
logComputedPropertiesSuccessMessage: this.logComputedPropertiesSuccessMessage,
|
||||||
|
onComputedPropertiesContentChange: this.onComputedPropertiesContentChange,
|
||||||
|
onComputedPropertiesDirtyChange: this.onComputedPropertiesDirtyChange,
|
||||||
|
resetShouldDiscardComputedProperties: this.resetShouldDiscardComputedProperties,
|
||||||
|
shouldDiscardComputedProperties: this.state.shouldDiscardComputedProperties,
|
||||||
|
};
|
||||||
|
|
||||||
const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = {
|
const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = {
|
||||||
collection: this.collection,
|
collection: this.collection,
|
||||||
conflictResolutionPolicyMode: this.state.conflictResolutionPolicyMode,
|
conflictResolutionPolicyMode: this.state.conflictResolutionPolicyMode,
|
||||||
@ -1111,6 +1185,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.shouldShowComputedPropertiesEditor) {
|
||||||
|
tabs.push({
|
||||||
|
tab: SettingsV2TabTypes.ComputedPropertiesTab,
|
||||||
|
content: <ComputedPropertiesComponent {...computedPropertiesComponentProps} />,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const pivotProps: IPivotProps = {
|
const pivotProps: IPivotProps = {
|
||||||
onLinkClick: this.onPivotChange,
|
onLinkClick: this.onPivotChange,
|
||||||
selectedKey: SettingsV2TabTypes[this.state.selectedTab],
|
selectedKey: SettingsV2TabTypes[this.state.selectedTab],
|
||||||
|
@ -11,7 +11,6 @@ import {
|
|||||||
getThroughputApplyLongDelayMessage,
|
getThroughputApplyLongDelayMessage,
|
||||||
getThroughputApplyShortDelayMessage,
|
getThroughputApplyShortDelayMessage,
|
||||||
getToolTipContainer,
|
getToolTipContainer,
|
||||||
indexingPolicynUnsavedWarningMessage,
|
|
||||||
manualToAutoscaleDisclaimerElement,
|
manualToAutoscaleDisclaimerElement,
|
||||||
mongoIndexTransformationRefreshingMessage,
|
mongoIndexTransformationRefreshingMessage,
|
||||||
mongoIndexingPolicyAADError,
|
mongoIndexingPolicyAADError,
|
||||||
@ -39,7 +38,6 @@ class SettingsRenderUtilsTestComponent extends React.Component {
|
|||||||
|
|
||||||
{manualToAutoscaleDisclaimerElement}
|
{manualToAutoscaleDisclaimerElement}
|
||||||
{ttlWarning}
|
{ttlWarning}
|
||||||
{indexingPolicynUnsavedWarningMessage}
|
|
||||||
{updateThroughputDelayedApplyWarningMessage}
|
{updateThroughputDelayedApplyWarningMessage}
|
||||||
|
|
||||||
{getThroughputApplyDelayedMessage(false, 1000, "RU/s", "sampleDb", "sampleCollection", 2000)}
|
{getThroughputApplyDelayedMessage(false, 1000, "RU/s", "sampleDb", "sampleCollection", 2000)}
|
||||||
|
@ -61,6 +61,8 @@ export interface PriceBreakdown {
|
|||||||
currencySign: string;
|
currencySign: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type editorType = "indexPolicy" | "computedProperties";
|
||||||
|
|
||||||
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14, color: "windowtext" } };
|
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14, color: "windowtext" } };
|
||||||
|
|
||||||
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
|
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
|
||||||
@ -254,9 +256,10 @@ export const ttlWarning: JSX.Element = (
|
|||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const indexingPolicynUnsavedWarningMessage: JSX.Element = (
|
export const unsavedEditorWarningMessage = (editor: editorType): JSX.Element => (
|
||||||
<Text styles={infoAndToolTipTextStyle}>
|
<Text styles={infoAndToolTipTextStyle}>
|
||||||
You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes.
|
You have not saved the latest changes made to your{" "}
|
||||||
|
{editor === "indexPolicy" ? "indexing policy" : "computed properties"}. Please click save to confirm the changes.
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
import * as DataModels from "Contracts/DataModels";
|
||||||
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import { ComputedPropertiesComponent, ComputedPropertiesComponentProps } from "./ComputedPropertiesComponent";
|
||||||
|
|
||||||
|
describe("ComputedPropertiesComponent", () => {
|
||||||
|
const initialComputedPropertiesContent: DataModels.ComputedProperties = [
|
||||||
|
{
|
||||||
|
name: "prop1",
|
||||||
|
query: "query1",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const baseProps: ComputedPropertiesComponentProps = {
|
||||||
|
computedPropertiesContent: initialComputedPropertiesContent,
|
||||||
|
computedPropertiesContentBaseline: initialComputedPropertiesContent,
|
||||||
|
logComputedPropertiesSuccessMessage: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
onComputedPropertiesContentChange: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
onComputedPropertiesDirtyChange: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
resetShouldDiscardComputedProperties: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
shouldDiscardComputedProperties: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
const wrapper = shallow(<ComputedPropertiesComponent {...baseProps} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("computed properties are reset", () => {
|
||||||
|
const wrapper = shallow(<ComputedPropertiesComponent {...baseProps} />);
|
||||||
|
|
||||||
|
const computedPropertiesComponentInstance = wrapper.instance() as ComputedPropertiesComponent;
|
||||||
|
const resetComputedPropertiesEditorMockFn = jest.fn();
|
||||||
|
computedPropertiesComponentInstance.resetComputedPropertiesEditor = resetComputedPropertiesEditorMockFn;
|
||||||
|
|
||||||
|
wrapper.setProps({ shouldDiscardComputedProperties: true });
|
||||||
|
wrapper.update();
|
||||||
|
expect(resetComputedPropertiesEditorMockFn.mock.calls.length).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("dirty is set", () => {
|
||||||
|
let computedPropertiesComponent = new ComputedPropertiesComponent(baseProps);
|
||||||
|
expect(computedPropertiesComponent.IsComponentDirty()).toEqual(false);
|
||||||
|
|
||||||
|
const newProps = { ...baseProps, computedPropertiesContent: undefined as DataModels.ComputedProperties };
|
||||||
|
computedPropertiesComponent = new ComputedPropertiesComponent(newProps);
|
||||||
|
expect(computedPropertiesComponent.IsComponentDirty()).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,128 @@
|
|||||||
|
import { FontIcon, Link, MessageBar, MessageBarType, Stack, Text } from "@fluentui/react";
|
||||||
|
import * as DataModels from "Contracts/DataModels";
|
||||||
|
import { titleAndInputStackProps, unsavedEditorWarningMessage } from "Explorer/Controls/Settings/SettingsRenderUtils";
|
||||||
|
import { isDirty } from "Explorer/Controls/Settings/SettingsUtils";
|
||||||
|
import { loadMonaco } from "Explorer/LazyMonaco";
|
||||||
|
import * as monaco from "monaco-editor";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
export interface ComputedPropertiesComponentProps {
|
||||||
|
computedPropertiesContent: DataModels.ComputedProperties;
|
||||||
|
computedPropertiesContentBaseline: DataModels.ComputedProperties;
|
||||||
|
logComputedPropertiesSuccessMessage: () => void;
|
||||||
|
onComputedPropertiesContentChange: (newComputedProperties: DataModels.ComputedProperties) => void;
|
||||||
|
onComputedPropertiesDirtyChange: (isComputedPropertiesDirty: boolean) => void;
|
||||||
|
resetShouldDiscardComputedProperties: () => void;
|
||||||
|
shouldDiscardComputedProperties: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ComputedPropertiesComponentState {
|
||||||
|
computedPropertiesContentIsValid: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ComputedPropertiesComponent extends React.Component<
|
||||||
|
ComputedPropertiesComponentProps,
|
||||||
|
ComputedPropertiesComponentState
|
||||||
|
> {
|
||||||
|
private shouldCheckComponentIsDirty = true;
|
||||||
|
private computedPropertiesDiv = React.createRef<HTMLDivElement>();
|
||||||
|
private computedPropertiesEditor: monaco.editor.IStandaloneCodeEditor;
|
||||||
|
|
||||||
|
constructor(props: ComputedPropertiesComponentProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
computedPropertiesContentIsValid: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(): void {
|
||||||
|
if (this.props.shouldDiscardComputedProperties) {
|
||||||
|
this.resetComputedPropertiesEditor();
|
||||||
|
this.props.resetShouldDiscardComputedProperties();
|
||||||
|
}
|
||||||
|
this.onComponentUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
this.resetComputedPropertiesEditor();
|
||||||
|
this.onComponentUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public resetComputedPropertiesEditor = (): void => {
|
||||||
|
if (!this.computedPropertiesEditor) {
|
||||||
|
this.createComputedPropertiesEditor();
|
||||||
|
} else {
|
||||||
|
const indexingPolicyEditorModel = this.computedPropertiesEditor.getModel();
|
||||||
|
const value: string = JSON.stringify(this.props.computedPropertiesContent, undefined, 4);
|
||||||
|
indexingPolicyEditorModel.setValue(value);
|
||||||
|
}
|
||||||
|
this.onComponentUpdate();
|
||||||
|
};
|
||||||
|
|
||||||
|
private onComponentUpdate = (): void => {
|
||||||
|
if (!this.shouldCheckComponentIsDirty) {
|
||||||
|
this.shouldCheckComponentIsDirty = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.props.onComputedPropertiesDirtyChange(this.IsComponentDirty());
|
||||||
|
this.shouldCheckComponentIsDirty = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
public IsComponentDirty = (): boolean => {
|
||||||
|
if (
|
||||||
|
isDirty(this.props.computedPropertiesContent, this.props.computedPropertiesContentBaseline) &&
|
||||||
|
this.state.computedPropertiesContentIsValid
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
private async createComputedPropertiesEditor(): Promise<void> {
|
||||||
|
const value: string = JSON.stringify(this.props.computedPropertiesContent, undefined, 4);
|
||||||
|
const monaco = await loadMonaco();
|
||||||
|
this.computedPropertiesEditor = monaco.editor.create(this.computedPropertiesDiv.current, {
|
||||||
|
value: value,
|
||||||
|
language: "json",
|
||||||
|
ariaLabel: "Computed properties",
|
||||||
|
});
|
||||||
|
if (this.computedPropertiesEditor) {
|
||||||
|
const computedPropertiesEditorModel = this.computedPropertiesEditor.getModel();
|
||||||
|
computedPropertiesEditorModel.onDidChangeContent(this.onEditorContentChange.bind(this));
|
||||||
|
this.props.logComputedPropertiesSuccessMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onEditorContentChange = (): void => {
|
||||||
|
const computedPropertiesEditorModel = this.computedPropertiesEditor.getModel();
|
||||||
|
try {
|
||||||
|
const newComputedPropertiesContent = JSON.parse(
|
||||||
|
computedPropertiesEditorModel.getValue(),
|
||||||
|
) as DataModels.ComputedProperties;
|
||||||
|
this.props.onComputedPropertiesContentChange(newComputedPropertiesContent);
|
||||||
|
this.setState({ computedPropertiesContentIsValid: true });
|
||||||
|
} catch (e) {
|
||||||
|
this.setState({ computedPropertiesContentIsValid: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Stack {...titleAndInputStackProps}>
|
||||||
|
{isDirty(this.props.computedPropertiesContent, this.props.computedPropertiesContentBaseline) && (
|
||||||
|
<MessageBar messageBarType={MessageBarType.warning}>
|
||||||
|
{unsavedEditorWarningMessage("computedProperties")}
|
||||||
|
</MessageBar>
|
||||||
|
)}
|
||||||
|
<Text style={{ marginLeft: "30px", marginBottom: "10px" }}>
|
||||||
|
<Link target="_blank" href="https://aka.ms/computed-properties-preview/">
|
||||||
|
{"Learn more"} <FontIcon iconName="NavigateExternalInline" />
|
||||||
|
</Link>
|
||||||
|
  about how to define computed properties and how to use them.
|
||||||
|
</Text>
|
||||||
|
<div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.computedPropertiesDiv}></div>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@ import * as monaco from "monaco-editor";
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as DataModels from "../../../../Contracts/DataModels";
|
import * as DataModels from "../../../../Contracts/DataModels";
|
||||||
import { loadMonaco } from "../../../LazyMonaco";
|
import { loadMonaco } from "../../../LazyMonaco";
|
||||||
import { indexingPolicynUnsavedWarningMessage, titleAndInputStackProps } from "../SettingsRenderUtils";
|
import { titleAndInputStackProps, unsavedEditorWarningMessage } from "../SettingsRenderUtils";
|
||||||
import { isDirty, isIndexTransforming } from "../SettingsUtils";
|
import { isDirty, isIndexTransforming } from "../SettingsUtils";
|
||||||
import { IndexingPolicyRefreshComponent } from "./IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
|
import { IndexingPolicyRefreshComponent } from "./IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
|
||||||
|
|
||||||
@ -120,7 +120,7 @@ export class IndexingPolicyComponent extends React.Component<
|
|||||||
refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress}
|
refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress}
|
||||||
/>
|
/>
|
||||||
{isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && (
|
{isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && (
|
||||||
<MessageBar messageBarType={MessageBarType.warning}>{indexingPolicynUnsavedWarningMessage}</MessageBar>
|
<MessageBar messageBarType={MessageBarType.warning}>{unsavedEditorWarningMessage("indexPolicy")}</MessageBar>
|
||||||
)}
|
)}
|
||||||
<div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.indexingPolicyDiv}></div>
|
<div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.indexingPolicyDiv}></div>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -19,7 +19,6 @@ import {
|
|||||||
addMongoIndexStackProps,
|
addMongoIndexStackProps,
|
||||||
createAndAddMongoIndexStackProps,
|
createAndAddMongoIndexStackProps,
|
||||||
customDetailsListStyles,
|
customDetailsListStyles,
|
||||||
indexingPolicynUnsavedWarningMessage,
|
|
||||||
infoAndToolTipTextStyle,
|
infoAndToolTipTextStyle,
|
||||||
mediumWidthStackStyles,
|
mediumWidthStackStyles,
|
||||||
mongoCompoundIndexNotSupportedMessage,
|
mongoCompoundIndexNotSupportedMessage,
|
||||||
@ -27,15 +26,16 @@ import {
|
|||||||
onRenderRow,
|
onRenderRow,
|
||||||
separatorStyles,
|
separatorStyles,
|
||||||
subComponentStackProps,
|
subComponentStackProps,
|
||||||
|
unsavedEditorWarningMessage,
|
||||||
} from "../../SettingsRenderUtils";
|
} from "../../SettingsRenderUtils";
|
||||||
import {
|
import {
|
||||||
AddMongoIndexProps,
|
AddMongoIndexProps,
|
||||||
getMongoIndexType,
|
|
||||||
getMongoIndexTypeText,
|
|
||||||
isIndexTransforming,
|
|
||||||
MongoIndexIdField,
|
MongoIndexIdField,
|
||||||
MongoIndexTypes,
|
MongoIndexTypes,
|
||||||
MongoNotificationType,
|
MongoNotificationType,
|
||||||
|
getMongoIndexType,
|
||||||
|
getMongoIndexTypeText,
|
||||||
|
isIndexTransforming,
|
||||||
} from "../../SettingsUtils";
|
} from "../../SettingsUtils";
|
||||||
import { IndexingPolicyRefreshComponent } from "../IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
|
import { IndexingPolicyRefreshComponent } from "../IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
|
||||||
import { AddMongoIndexComponent } from "./AddMongoIndexComponent";
|
import { AddMongoIndexComponent } from "./AddMongoIndexComponent";
|
||||||
@ -297,7 +297,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
|||||||
if (this.getMongoWarningNotificationMessage()) {
|
if (this.getMongoWarningNotificationMessage()) {
|
||||||
warningMessage = this.getMongoWarningNotificationMessage();
|
warningMessage = this.getMongoWarningNotificationMessage();
|
||||||
} else if (this.isMongoIndexingPolicySaveable()) {
|
} else if (this.isMongoIndexingPolicySaveable()) {
|
||||||
warningMessage = indexingPolicynUnsavedWarningMessage;
|
warningMessage = unsavedEditorWarningMessage("indexPolicy");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -136,15 +136,15 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({ da
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getPercentageComplete = () => {
|
const getPercentageComplete = () => {
|
||||||
|
const jobStatus = portalDataTransferJob?.properties?.status;
|
||||||
|
const isCompleted = jobStatus === "Completed";
|
||||||
|
if (isCompleted) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
const processedCount = portalDataTransferJob?.properties?.processedCount;
|
const processedCount = portalDataTransferJob?.properties?.processedCount;
|
||||||
const totalCount = portalDataTransferJob?.properties?.totalCount;
|
const totalCount = portalDataTransferJob?.properties?.totalCount;
|
||||||
const jobStatus = portalDataTransferJob?.properties?.status;
|
const isJobInProgress = isCurrentJobInProgress(portalDataTransferJob);
|
||||||
const isCancelled = jobStatus === "Cancelled";
|
return isJobInProgress ? (totalCount > 0 ? processedCount / totalCount : null) : 0;
|
||||||
const isCompleted = jobStatus === "Completed";
|
|
||||||
if (totalCount <= 0 && !isCompleted) {
|
|
||||||
return isCancelled ? 0 : null;
|
|
||||||
}
|
|
||||||
return isCompleted ? 1 : processedCount / totalCount;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`ComputedPropertiesComponent renders 1`] = `
|
||||||
|
<Stack
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"marginBottom": "10px",
|
||||||
|
"marginLeft": "30px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<StyledLinkBase
|
||||||
|
href="https://aka.ms/computed-properties-preview/"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Learn more
|
||||||
|
|
||||||
|
<FontIcon
|
||||||
|
iconName="NavigateExternalInline"
|
||||||
|
/>
|
||||||
|
</StyledLinkBase>
|
||||||
|
about how to define computed properties and how to use them.
|
||||||
|
</Text>
|
||||||
|
<div
|
||||||
|
className="settingsV2IndexingPolicyEditor"
|
||||||
|
tabIndex={0}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
`;
|
@ -4,7 +4,7 @@ import * as ViewModels from "../../../Contracts/ViewModels";
|
|||||||
import { MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types";
|
import { MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types";
|
||||||
|
|
||||||
const zeroValue = 0;
|
const zeroValue = 0;
|
||||||
export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy;
|
export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy | DataModels.ComputedProperties;
|
||||||
export const TtlOff = "off";
|
export const TtlOff = "off";
|
||||||
export const TtlOn = "on";
|
export const TtlOn = "on";
|
||||||
export const TtlOnNoDefault = "on-nodefault";
|
export const TtlOnNoDefault = "on-nodefault";
|
||||||
@ -46,6 +46,7 @@ export enum SettingsV2TabTypes {
|
|||||||
SubSettingsTab,
|
SubSettingsTab,
|
||||||
IndexingPolicyTab,
|
IndexingPolicyTab,
|
||||||
PartitionKeyTab,
|
PartitionKeyTab,
|
||||||
|
ComputedPropertiesTab,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IsComponentDirtyResult {
|
export interface IsComponentDirtyResult {
|
||||||
@ -148,7 +149,9 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => {
|
|||||||
case SettingsV2TabTypes.IndexingPolicyTab:
|
case SettingsV2TabTypes.IndexingPolicyTab:
|
||||||
return "Indexing Policy";
|
return "Indexing Policy";
|
||||||
case SettingsV2TabTypes.PartitionKeyTab:
|
case SettingsV2TabTypes.PartitionKeyTab:
|
||||||
return "Partition Keys";
|
return "Partition Keys (preview)";
|
||||||
|
case SettingsV2TabTypes.ComputedPropertiesTab:
|
||||||
|
return "Computed Properties (preview)";
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown tab ${tab}`);
|
throw new Error(`Unknown tab ${tab}`);
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,12 @@ export const collection = {
|
|||||||
version: 2,
|
version: 2,
|
||||||
},
|
},
|
||||||
partitionKeyProperties: ["partitionKey"],
|
partitionKeyProperties: ["partitionKey"],
|
||||||
|
computedProperties: ko.observable<DataModels.ComputedProperties>([
|
||||||
|
{
|
||||||
|
name: "queryName",
|
||||||
|
query: "query",
|
||||||
|
},
|
||||||
|
]),
|
||||||
readSettings: () => {
|
readSettings: () => {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
|
@ -26,6 +26,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
Object {
|
Object {
|
||||||
"analyticalStorageTtl": [Function],
|
"analyticalStorageTtl": [Function],
|
||||||
"changeFeedPolicy": [Function],
|
"changeFeedPolicy": [Function],
|
||||||
|
"computedProperties": [Function],
|
||||||
"conflictResolutionPolicy": [Function],
|
"conflictResolutionPolicy": [Function],
|
||||||
"container": Explorer {
|
"container": Explorer {
|
||||||
"_isInitializingNotebooks": false,
|
"_isInitializingNotebooks": false,
|
||||||
@ -103,6 +104,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
Object {
|
Object {
|
||||||
"analyticalStorageTtl": [Function],
|
"analyticalStorageTtl": [Function],
|
||||||
"changeFeedPolicy": [Function],
|
"changeFeedPolicy": [Function],
|
||||||
|
"computedProperties": [Function],
|
||||||
"conflictResolutionPolicy": [Function],
|
"conflictResolutionPolicy": [Function],
|
||||||
"container": Explorer {
|
"container": Explorer {
|
||||||
"_isInitializingNotebooks": false,
|
"_isInitializingNotebooks": false,
|
||||||
@ -205,7 +207,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
/>
|
/>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
<PivotItem
|
<PivotItem
|
||||||
headerText="Partition Keys"
|
headerText="Partition Keys (preview)"
|
||||||
itemKey="PartitionKeyTab"
|
itemKey="PartitionKeyTab"
|
||||||
key="PartitionKeyTab"
|
key="PartitionKeyTab"
|
||||||
style={
|
style={
|
||||||
@ -219,6 +221,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
Object {
|
Object {
|
||||||
"analyticalStorageTtl": [Function],
|
"analyticalStorageTtl": [Function],
|
||||||
"changeFeedPolicy": [Function],
|
"changeFeedPolicy": [Function],
|
||||||
|
"computedProperties": [Function],
|
||||||
"conflictResolutionPolicy": [Function],
|
"conflictResolutionPolicy": [Function],
|
||||||
"container": Explorer {
|
"container": Explorer {
|
||||||
"_isInitializingNotebooks": false,
|
"_isInitializingNotebooks": false,
|
||||||
@ -296,6 +299,40 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
|
<PivotItem
|
||||||
|
headerText="Computed Properties (preview)"
|
||||||
|
itemKey="ComputedPropertiesTab"
|
||||||
|
key="ComputedPropertiesTab"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"marginTop": 20,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ComputedPropertiesComponent
|
||||||
|
computedPropertiesContent={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"name": "queryName",
|
||||||
|
"query": "query",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
computedPropertiesContentBaseline={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"name": "queryName",
|
||||||
|
"query": "query",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
logComputedPropertiesSuccessMessage={[Function]}
|
||||||
|
onComputedPropertiesContentChange={[Function]}
|
||||||
|
onComputedPropertiesDirtyChange={[Function]}
|
||||||
|
resetShouldDiscardComputedProperties={[Function]}
|
||||||
|
shouldDiscardComputedProperties={false}
|
||||||
|
/>
|
||||||
|
</PivotItem>
|
||||||
</StyledPivot>
|
</StyledPivot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -99,18 +99,6 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
</StyledLinkBase>
|
</StyledLinkBase>
|
||||||
.
|
.
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
|
||||||
styles={
|
|
||||||
Object {
|
|
||||||
"root": Object {
|
|
||||||
"color": "windowtext",
|
|
||||||
"fontSize": 14,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes.
|
|
||||||
</Text>
|
|
||||||
<Text
|
<Text
|
||||||
id="updateThroughputDelayedApplyWarningMessage"
|
id="updateThroughputDelayedApplyWarningMessage"
|
||||||
styles={
|
styles={
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
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 { 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";
|
||||||
@ -40,6 +41,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
|||||||
const buttons = useCommandBar((state) => state.contextButtons);
|
const buttons = useCommandBar((state) => state.contextButtons);
|
||||||
const isHidden = useCommandBar((state) => state.isHidden);
|
const isHidden = useCommandBar((state) => state.isHidden);
|
||||||
const backgroundColor = StyleConstants.BaseLight;
|
const backgroundColor = StyleConstants.BaseLight;
|
||||||
|
const setKeyboardHandlers = useKeyboardActionGroup(KeyboardActionGroup.COMMAND_BAR);
|
||||||
|
|
||||||
if (userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo") {
|
if (userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo") {
|
||||||
const buttons =
|
const buttons =
|
||||||
@ -105,6 +107,10 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const allButtons = staticButtons.concat(contextButtons).concat(controlButtons);
|
||||||
|
const keyboardHandlers = CommandBarUtil.createKeyboardHandlers(allButtons);
|
||||||
|
setKeyboardHandlers(keyboardHandlers);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="commandBarContainer" style={{ display: isHidden ? "none" : "initial" }}>
|
<div className="commandBarContainer" style={{ display: isHidden ? "none" : "initial" }}>
|
||||||
<FluentCommandBar
|
<FluentCommandBar
|
||||||
|
@ -2,10 +2,8 @@ import * as ko from "knockout";
|
|||||||
import { AuthType } from "../../../AuthType";
|
import { AuthType } from "../../../AuthType";
|
||||||
import { DatabaseAccount } from "../../../Contracts/DataModels";
|
import { DatabaseAccount } from "../../../Contracts/DataModels";
|
||||||
import { CollectionBase } from "../../../Contracts/ViewModels";
|
import { CollectionBase } from "../../../Contracts/ViewModels";
|
||||||
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
|
||||||
import { updateUserContext } from "../../../UserContext";
|
import { updateUserContext } from "../../../UserContext";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import NotebookManager from "../../Notebook/NotebookManager";
|
|
||||||
import { useNotebook } from "../../Notebook/useNotebook";
|
import { useNotebook } from "../../Notebook/useNotebook";
|
||||||
import { useDatabases } from "../../useDatabases";
|
import { useDatabases } from "../../useDatabases";
|
||||||
import { useSelectedNode } from "../../useSelectedNode";
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
@ -72,181 +70,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Enable notebook button", () => {
|
|
||||||
const enableNotebookBtnLabel = "Enable Notebooks (Preview)";
|
|
||||||
const selectedNodeState = useSelectedNode.getState();
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
mockExplorer = {} as Explorer;
|
|
||||||
updateUserContext({
|
|
||||||
portalEnv: "prod",
|
|
||||||
databaseAccount: {
|
|
||||||
properties: {
|
|
||||||
capabilities: [{ name: "EnableTable" }],
|
|
||||||
},
|
|
||||||
} as DatabaseAccount,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
updateUserContext({
|
|
||||||
portalEnv: "prod",
|
|
||||||
});
|
|
||||||
useNotebook.getState().setIsNotebookEnabled(false);
|
|
||||||
useNotebook.getState().setIsNotebooksEnabledForAccount(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Notebooks is already enabled - button should be hidden", () => {
|
|
||||||
useNotebook.getState().setIsNotebookEnabled(true);
|
|
||||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
|
||||||
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
|
||||||
expect(enableNotebookBtn).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Account is running on one of the national clouds - button should be hidden", () => {
|
|
||||||
updateUserContext({
|
|
||||||
portalEnv: "mooncake",
|
|
||||||
});
|
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
|
||||||
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
|
||||||
expect(enableNotebookBtn).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Notebooks is not enabled but is available - button should be shown and enabled", () => {
|
|
||||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
|
||||||
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
|
||||||
|
|
||||||
//TODO: modify once notebooks are available
|
|
||||||
expect(enableNotebookBtn).toBeUndefined();
|
|
||||||
//expect(enableNotebookBtn).toBeDefined();
|
|
||||||
//expect(enableNotebookBtn.disabled).toBe(false);
|
|
||||||
//expect(enableNotebookBtn.tooltipText).toBe("");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => {
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
|
||||||
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
|
||||||
|
|
||||||
//TODO: modify once notebooks are available
|
|
||||||
expect(enableNotebookBtn).toBeUndefined();
|
|
||||||
//expect(enableNotebookBtn).toBeDefined();
|
|
||||||
//expect(enableNotebookBtn.disabled).toBe(true);
|
|
||||||
//expect(enableNotebookBtn.tooltipText).toBe(
|
|
||||||
// "Notebooks are not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."
|
|
||||||
//);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Open Mongo shell button", () => {
|
|
||||||
const openMongoShellBtnLabel = "Open Mongo shell";
|
|
||||||
const selectedNodeState = useSelectedNode.getState();
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
mockExplorer = {} as Explorer;
|
|
||||||
updateUserContext({
|
|
||||||
databaseAccount: {
|
|
||||||
properties: {
|
|
||||||
capabilities: [{ name: "EnableTable" }],
|
|
||||||
},
|
|
||||||
} as DatabaseAccount,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
updateUserContext({
|
|
||||||
apiType: "SQL",
|
|
||||||
});
|
|
||||||
useNotebook.getState().setIsShellEnabled(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
updateUserContext({
|
|
||||||
apiType: "Mongo",
|
|
||||||
});
|
|
||||||
useNotebook.getState().setIsShellEnabled(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
useNotebook.getState().setIsNotebookEnabled(false);
|
|
||||||
useNotebook.getState().setIsNotebooksEnabledForAccount(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Mongo Api not available - button should be hidden", () => {
|
|
||||||
updateUserContext({
|
|
||||||
apiType: "SQL",
|
|
||||||
});
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
|
||||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
|
||||||
expect(openMongoShellBtn).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Running on a national cloud - button should be hidden", () => {
|
|
||||||
updateUserContext({
|
|
||||||
portalEnv: "mooncake",
|
|
||||||
});
|
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
|
||||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
|
||||||
expect(openMongoShellBtn).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Notebooks is not enabled and is unavailable - button should be hidden", () => {
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
|
||||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
|
||||||
expect(openMongoShellBtn).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Notebooks is not enabled and is available - button should be hidden", () => {
|
|
||||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
|
||||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
|
||||||
expect(openMongoShellBtn).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
|
|
||||||
useNotebook.getState().setIsNotebookEnabled(true);
|
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
|
||||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
|
||||||
expect(openMongoShellBtn).toBeDefined();
|
|
||||||
|
|
||||||
//TODO: modify once notebooks are available
|
|
||||||
expect(openMongoShellBtn.disabled).toBe(true);
|
|
||||||
//expect(openMongoShellBtn.disabled).toBe(false);
|
|
||||||
//expect(openMongoShellBtn.tooltipText).toBe("");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Notebooks is enabled and is available - button should be shown and enabled", () => {
|
|
||||||
useNotebook.getState().setIsNotebookEnabled(true);
|
|
||||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
|
||||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
|
||||||
expect(openMongoShellBtn).toBeDefined();
|
|
||||||
|
|
||||||
//TODO: modify once notebooks are available
|
|
||||||
expect(openMongoShellBtn.disabled).toBe(true);
|
|
||||||
//expect(openMongoShellBtn.disabled).toBe(false);
|
|
||||||
//expect(openMongoShellBtn.tooltipText).toBe("");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Notebooks is enabled and is available, terminal is unavailable due to ipRules - button should be hidden", () => {
|
|
||||||
useNotebook.getState().setIsNotebookEnabled(true);
|
|
||||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
|
||||||
useNotebook.getState().setIsShellEnabled(false);
|
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
|
||||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
|
||||||
expect(openMongoShellBtn).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Open Cassandra shell button", () => {
|
describe("Open Cassandra shell button", () => {
|
||||||
const openCassandraShellBtnLabel = "Open Cassandra shell";
|
const openCassandraShellBtnLabel = "Open Cassandra shell";
|
||||||
const selectedNodeState = useSelectedNode.getState();
|
const selectedNodeState = useSelectedNode.getState();
|
||||||
@ -305,42 +128,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||||
expect(openCassandraShellBtn).toBeUndefined();
|
expect(openCassandraShellBtn).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Notebooks is not enabled and is available - button should be shown and enabled", () => {
|
|
||||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
|
||||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
|
||||||
expect(openCassandraShellBtn).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
|
|
||||||
useNotebook.getState().setIsNotebookEnabled(true);
|
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
|
||||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
|
||||||
|
|
||||||
expect(openCassandraShellBtn).toBeDefined();
|
|
||||||
|
|
||||||
//TODO: modify once notebooks are available
|
|
||||||
expect(openCassandraShellBtn.disabled).toBe(true);
|
|
||||||
//expect(openCassandraShellBtn.disabled).toBe(false);
|
|
||||||
//expect(openCassandraShellBtn.tooltipText).toBe("");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Notebooks is enabled and is available - button should be shown and enabled", () => {
|
|
||||||
useNotebook.getState().setIsNotebookEnabled(true);
|
|
||||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
|
||||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
|
||||||
expect(openCassandraShellBtn).toBeDefined();
|
|
||||||
|
|
||||||
//TODO: modify once notebooks are available
|
|
||||||
expect(openCassandraShellBtn.disabled).toBe(true);
|
|
||||||
//expect(openCassandraShellBtn.disabled).toBe(false);
|
|
||||||
//expect(openCassandraShellBtn.tooltipText).toBe("");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Open Postgres and vCore Mongo buttons", () => {
|
describe("Open Postgres and vCore Mongo buttons", () => {
|
||||||
@ -368,62 +155,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("GitHub buttons", () => {
|
|
||||||
const connectToGitHubBtnLabel = "Connect to GitHub";
|
|
||||||
const manageGitHubSettingsBtnLabel = "Manage GitHub settings";
|
|
||||||
const selectedNodeState = useSelectedNode.getState();
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
mockExplorer = {} as Explorer;
|
|
||||||
updateUserContext({
|
|
||||||
databaseAccount: {
|
|
||||||
properties: {
|
|
||||||
capabilities: [{ name: "EnableTable" }],
|
|
||||||
},
|
|
||||||
} as DatabaseAccount,
|
|
||||||
});
|
|
||||||
|
|
||||||
mockExplorer.notebookManager = new NotebookManager();
|
|
||||||
mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
useNotebook.getState().setIsNotebookEnabled(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Notebooks is enabled and GitHubOAuthService is not logged in - connect to github button should be visible", () => {
|
|
||||||
useNotebook.getState().setIsNotebookEnabled(true);
|
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
|
||||||
const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel);
|
|
||||||
expect(connectToGitHubBtn).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Notebooks is enabled and GitHubOAuthService is logged in - manage github settings button should be visible", () => {
|
|
||||||
useNotebook.getState().setIsNotebookEnabled(true);
|
|
||||||
mockExplorer.notebookManager.gitHubOAuthService.isLoggedIn = jest.fn().mockReturnValue(true);
|
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
|
||||||
const manageGitHubSettingsBtn = buttons.find(
|
|
||||||
(button) => button.commandButtonLabel === manageGitHubSettingsBtnLabel,
|
|
||||||
);
|
|
||||||
expect(manageGitHubSettingsBtn).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Notebooks is not enabled - connect to github and manage github settings buttons should be hidden", () => {
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
|
||||||
|
|
||||||
const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel);
|
|
||||||
expect(connectToGitHubBtn).toBeUndefined();
|
|
||||||
|
|
||||||
const manageGitHubSettingsBtn = buttons.find(
|
|
||||||
(button) => button.commandButtonLabel === manageGitHubSettingsBtnLabel,
|
|
||||||
);
|
|
||||||
expect(manageGitHubSettingsBtn).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Resource token", () => {
|
describe("Resource token", () => {
|
||||||
const mockCollection = { id: ko.observable("test") } as CollectionBase;
|
const mockCollection = { id: ko.observable("test") } as CollectionBase;
|
||||||
useSelectedNode.getState().setSelectedNode(mockCollection);
|
useSelectedNode.getState().setSelectedNode(mockCollection);
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { KeyboardAction } from "KeyboardShortcuts";
|
||||||
|
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import AddCollectionIcon from "../../../../images/AddCollection.svg";
|
import AddCollectionIcon from "../../../../images/AddCollection.svg";
|
||||||
import AddDatabaseIcon from "../../../../images/AddDatabase.svg";
|
import AddDatabaseIcon from "../../../../images/AddDatabase.svg";
|
||||||
@ -6,13 +8,10 @@ import AddStoredProcedureIcon from "../../../../images/AddStoredProcedure.svg";
|
|||||||
import AddTriggerIcon from "../../../../images/AddTrigger.svg";
|
import AddTriggerIcon from "../../../../images/AddTrigger.svg";
|
||||||
import AddUdfIcon from "../../../../images/AddUdf.svg";
|
import AddUdfIcon from "../../../../images/AddUdf.svg";
|
||||||
import BrowseQueriesIcon from "../../../../images/BrowseQuery.svg";
|
import BrowseQueriesIcon from "../../../../images/BrowseQuery.svg";
|
||||||
import CosmosTerminalIcon from "../../../../images/Cosmos-Terminal.svg";
|
|
||||||
import FeedbackIcon from "../../../../images/Feedback-Command.svg";
|
import FeedbackIcon from "../../../../images/Feedback-Command.svg";
|
||||||
|
import HomeIcon from "../../../../images/Home_16.svg";
|
||||||
import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg";
|
import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg";
|
||||||
import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg";
|
import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg";
|
||||||
import GitHubIcon from "../../../../images/github.svg";
|
|
||||||
import NewNotebookIcon from "../../../../images/notebook/Notebook-new.svg";
|
|
||||||
import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-workspace.svg";
|
|
||||||
import OpenInTabIcon from "../../../../images/open-in-tab.svg";
|
import OpenInTabIcon from "../../../../images/open-in-tab.svg";
|
||||||
import SettingsIcon from "../../../../images/settings_15x15.svg";
|
import SettingsIcon from "../../../../images/settings_15x15.svg";
|
||||||
import SynapseIcon from "../../../../images/synapse-link.svg";
|
import SynapseIcon from "../../../../images/synapse-link.svg";
|
||||||
@ -20,7 +19,6 @@ import { AuthType } from "../../../AuthType";
|
|||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import { Platform, configContext } from "../../../ConfigContext";
|
import { Platform, configContext } from "../../../ConfigContext";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import { JunoClient } from "../../../Juno/JunoClient";
|
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils";
|
import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils";
|
||||||
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
|
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
|
||||||
@ -31,7 +29,6 @@ import { useNotebook } from "../../Notebook/useNotebook";
|
|||||||
import { OpenFullScreen } from "../../OpenFullScreen";
|
import { OpenFullScreen } from "../../OpenFullScreen";
|
||||||
import { AddDatabasePanel } from "../../Panes/AddDatabasePanel/AddDatabasePanel";
|
import { AddDatabasePanel } from "../../Panes/AddDatabasePanel/AddDatabasePanel";
|
||||||
import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane";
|
import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane";
|
||||||
import { GitHubReposPanel } from "../../Panes/GitHubReposPanel/GitHubReposPanel";
|
|
||||||
import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane";
|
import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane";
|
||||||
import { SettingsPane } from "../../Panes/SettingsPane/SettingsPane";
|
import { SettingsPane } from "../../Panes/SettingsPane/SettingsPane";
|
||||||
import { useDatabases } from "../../useDatabases";
|
import { useDatabases } from "../../useDatabases";
|
||||||
@ -57,7 +54,11 @@ export function createStaticCommandBarButtons(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (configContext.platform !== Platform.Fabric) {
|
if (configContext.platform !== Platform.Fabric) {
|
||||||
|
const homeBtn = createHomeButton();
|
||||||
|
buttons.push(homeBtn);
|
||||||
|
|
||||||
const newCollectionBtn = createNewCollectionGroup(container);
|
const newCollectionBtn = createNewCollectionGroup(container);
|
||||||
|
newCollectionBtn.keyboardAction = KeyboardAction.NEW_COLLECTION; // Just for the root button, not the child version we create below.
|
||||||
buttons.push(newCollectionBtn);
|
buttons.push(newCollectionBtn);
|
||||||
if (userContext.apiType !== "Tables" && userContext.apiType !== "Cassandra") {
|
if (userContext.apiType !== "Tables" && userContext.apiType !== "Cassandra") {
|
||||||
const addSynapseLink = createOpenSynapseLinkDialogButton(container);
|
const addSynapseLink = createOpenSynapseLinkDialogButton(container);
|
||||||
@ -75,57 +76,6 @@ export function createStaticCommandBarButtons(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useNotebook.getState().isNotebookEnabled) {
|
|
||||||
addDivider();
|
|
||||||
const notebookButtons: CommandButtonComponentProps[] = [];
|
|
||||||
|
|
||||||
const newNotebookButton = createNewNotebookButton(container);
|
|
||||||
newNotebookButton.children = [createNewNotebookButton(container), createuploadNotebookButton(container)];
|
|
||||||
notebookButtons.push(newNotebookButton);
|
|
||||||
|
|
||||||
if (container.notebookManager?.gitHubOAuthService) {
|
|
||||||
notebookButtons.push(createManageGitHubAccountButton(container));
|
|
||||||
}
|
|
||||||
if (useNotebook.getState().isPhoenixFeatures && configContext.isTerminalEnabled) {
|
|
||||||
notebookButtons.push(createOpenTerminalButton(container));
|
|
||||||
}
|
|
||||||
if (useNotebook.getState().isPhoenixNotebooks && selectedNodeState.isConnectedToContainer()) {
|
|
||||||
notebookButtons.push(createNotebookWorkspaceResetButton(container));
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(userContext.apiType === "Mongo" &&
|
|
||||||
useNotebook.getState().isShellEnabled &&
|
|
||||||
selectedNodeState.isDatabaseNodeOrNoneSelected()) ||
|
|
||||||
userContext.apiType === "Cassandra"
|
|
||||||
) {
|
|
||||||
notebookButtons.push(createDivider());
|
|
||||||
if (userContext.apiType === "Cassandra") {
|
|
||||||
notebookButtons.push(createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.Cassandra));
|
|
||||||
} else {
|
|
||||||
notebookButtons.push(createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.Mongo));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
notebookButtons.forEach((btn) => {
|
|
||||||
if (btn.commandButtonLabel.indexOf("Cassandra") !== -1) {
|
|
||||||
if (!useNotebook.getState().isPhoenixFeatures) {
|
|
||||||
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.cassandraShellTemporarilyDownMsg);
|
|
||||||
}
|
|
||||||
} else if (btn.commandButtonLabel.indexOf("Mongo") !== -1) {
|
|
||||||
if (!useNotebook.getState().isPhoenixFeatures) {
|
|
||||||
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.mongoShellTemporarilyDownMsg);
|
|
||||||
}
|
|
||||||
} else if (btn.commandButtonLabel.indexOf("Open Terminal") !== -1) {
|
|
||||||
if (!useNotebook.getState().isPhoenixFeatures) {
|
|
||||||
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg);
|
|
||||||
}
|
|
||||||
} else if (!useNotebook.getState().isPhoenixNotebooks) {
|
|
||||||
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg);
|
|
||||||
}
|
|
||||||
buttons.push(btn);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!selectedNodeState.isDatabaseNodeOrNoneSelected()) {
|
if (!selectedNodeState.isDatabaseNodeOrNoneSelected()) {
|
||||||
const isQuerySupported = userContext.apiType === "SQL" || userContext.apiType === "Gremlin";
|
const isQuerySupported = userContext.apiType === "SQL" || userContext.apiType === "Gremlin";
|
||||||
|
|
||||||
@ -146,6 +96,7 @@ export function createStaticCommandBarButtons(
|
|||||||
const newStoredProcedureBtn: CommandButtonComponentProps = {
|
const newStoredProcedureBtn: CommandButtonComponentProps = {
|
||||||
iconSrc: AddStoredProcedureIcon,
|
iconSrc: AddStoredProcedureIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.NEW_SPROC,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||||
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection);
|
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection);
|
||||||
@ -235,7 +186,7 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
|
|||||||
buttons.push(fullScreenButton);
|
buttons.push(fullScreenButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configContext.platform !== Platform.Emulator) {
|
if (configContext.platform === Platform.Portal) {
|
||||||
const label = "Feedback";
|
const label = "Feedback";
|
||||||
const feedbackButtonOptions: CommandButtonComponentProps = {
|
const feedbackButtonOptions: CommandButtonComponentProps = {
|
||||||
iconSrc: FeedbackIcon,
|
iconSrc: FeedbackIcon,
|
||||||
@ -285,6 +236,18 @@ function createNewCollectionGroup(container: Explorer): CommandButtonComponentPr
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createHomeButton(): CommandButtonComponentProps {
|
||||||
|
const label = "Home";
|
||||||
|
return {
|
||||||
|
iconSrc: HomeIcon,
|
||||||
|
iconAlt: label,
|
||||||
|
onCommandClick: () => useTabs.getState().openAndActivateReactTab(ReactTabKind.Home),
|
||||||
|
commandButtonLabel: label,
|
||||||
|
hasPopup: false,
|
||||||
|
ariaLabel: label,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonComponentProps {
|
function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonComponentProps {
|
||||||
if (configContext.platform === Platform.Emulator) {
|
if (configContext.platform === Platform.Emulator) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -317,6 +280,7 @@ function createNewDatabase(container: Explorer): CommandButtonComponentProps {
|
|||||||
return {
|
return {
|
||||||
iconSrc: AddDatabaseIcon,
|
iconSrc: AddDatabaseIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.NEW_DATABASE,
|
||||||
onCommandClick: async () => {
|
onCommandClick: async () => {
|
||||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
||||||
if (throughputCap && throughputCap !== -1) {
|
if (throughputCap && throughputCap !== -1) {
|
||||||
@ -337,6 +301,7 @@ function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandB
|
|||||||
id: "newQueryBtn",
|
id: "newQueryBtn",
|
||||||
iconSrc: AddSqlQueryIcon,
|
iconSrc: AddSqlQueryIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.NEW_QUERY,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||||
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection);
|
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection);
|
||||||
@ -352,6 +317,7 @@ function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandB
|
|||||||
id: "newQueryBtn",
|
id: "newQueryBtn",
|
||||||
iconSrc: AddSqlQueryIcon,
|
iconSrc: AddSqlQueryIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.NEW_QUERY,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||||
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection);
|
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection);
|
||||||
@ -377,6 +343,7 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState)
|
|||||||
const newStoredProcedureBtn: CommandButtonComponentProps = {
|
const newStoredProcedureBtn: CommandButtonComponentProps = {
|
||||||
iconSrc: AddStoredProcedureIcon,
|
iconSrc: AddStoredProcedureIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.NEW_SPROC,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||||
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection);
|
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection);
|
||||||
@ -396,6 +363,7 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState)
|
|||||||
const newUserDefinedFunctionBtn: CommandButtonComponentProps = {
|
const newUserDefinedFunctionBtn: CommandButtonComponentProps = {
|
||||||
iconSrc: AddUdfIcon,
|
iconSrc: AddUdfIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.NEW_UDF,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||||
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection);
|
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection);
|
||||||
@ -415,6 +383,7 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState)
|
|||||||
const newTriggerBtn: CommandButtonComponentProps = {
|
const newTriggerBtn: CommandButtonComponentProps = {
|
||||||
iconSrc: AddTriggerIcon,
|
iconSrc: AddTriggerIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.NEW_TRIGGER,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||||
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection);
|
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection);
|
||||||
@ -432,45 +401,12 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState)
|
|||||||
return buttons;
|
return buttons;
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyNotebooksTemporarilyDownStyle(buttonProps: CommandButtonComponentProps, tooltip: string): void {
|
|
||||||
if (!buttonProps.isDivider) {
|
|
||||||
buttonProps.disabled = true;
|
|
||||||
buttonProps.tooltipText = tooltip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createNewNotebookButton(container: Explorer): CommandButtonComponentProps {
|
|
||||||
const label = "New Notebook";
|
|
||||||
return {
|
|
||||||
id: "newNotebookBtn",
|
|
||||||
iconSrc: NewNotebookIcon,
|
|
||||||
iconAlt: label,
|
|
||||||
onCommandClick: () => container.onNewNotebookClicked(),
|
|
||||||
commandButtonLabel: label,
|
|
||||||
hasPopup: false,
|
|
||||||
disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(),
|
|
||||||
ariaLabel: label,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function createuploadNotebookButton(container: Explorer): CommandButtonComponentProps {
|
|
||||||
const label = "Upload to Notebook Server";
|
|
||||||
return {
|
|
||||||
iconSrc: NewNotebookIcon,
|
|
||||||
iconAlt: label,
|
|
||||||
onCommandClick: () => container.openUploadFilePanel(),
|
|
||||||
commandButtonLabel: label,
|
|
||||||
hasPopup: false,
|
|
||||||
disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(),
|
|
||||||
ariaLabel: label,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function createOpenQueryButton(container: Explorer): CommandButtonComponentProps {
|
function createOpenQueryButton(container: Explorer): CommandButtonComponentProps {
|
||||||
const label = "Open Query";
|
const label = "Open Query";
|
||||||
return {
|
return {
|
||||||
iconSrc: BrowseQueriesIcon,
|
iconSrc: BrowseQueriesIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.OPEN_QUERY,
|
||||||
onCommandClick: () =>
|
onCommandClick: () =>
|
||||||
useSidePanel.getState().openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={container} />),
|
useSidePanel.getState().openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={container} />),
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
@ -485,6 +421,7 @@ function createOpenQueryFromDiskButton(): CommandButtonComponentProps {
|
|||||||
return {
|
return {
|
||||||
iconSrc: OpenQueryFromDiskIcon,
|
iconSrc: OpenQueryFromDiskIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.OPEN_QUERY_FROM_DISK,
|
||||||
onCommandClick: () => useSidePanel.getState().openSidePanel("Load Query", <LoadQueryPane />),
|
onCommandClick: () => useSidePanel.getState().openSidePanel("Load Query", <LoadQueryPane />),
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
@ -493,19 +430,6 @@ function createOpenQueryFromDiskButton(): CommandButtonComponentProps {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createOpenTerminalButton(container: Explorer): CommandButtonComponentProps {
|
|
||||||
const label = "Open Terminal";
|
|
||||||
return {
|
|
||||||
iconSrc: CosmosTerminalIcon,
|
|
||||||
iconAlt: label,
|
|
||||||
onCommandClick: () => container.openNotebookTerminal(ViewModels.TerminalKind.Default),
|
|
||||||
commandButtonLabel: label,
|
|
||||||
hasPopup: false,
|
|
||||||
disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(),
|
|
||||||
ariaLabel: label,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function createOpenTerminalButtonByKind(
|
function createOpenTerminalButtonByKind(
|
||||||
container: Explorer,
|
container: Explorer,
|
||||||
terminalKind: ViewModels.TerminalKind,
|
terminalKind: ViewModels.TerminalKind,
|
||||||
@ -545,45 +469,6 @@ function createOpenTerminalButtonByKind(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNotebookWorkspaceResetButton(container: Explorer): CommandButtonComponentProps {
|
|
||||||
const label = "Reset Workspace";
|
|
||||||
return {
|
|
||||||
iconSrc: ResetWorkspaceIcon,
|
|
||||||
iconAlt: label,
|
|
||||||
onCommandClick: () => container.resetNotebookWorkspace(),
|
|
||||||
commandButtonLabel: label,
|
|
||||||
hasPopup: false,
|
|
||||||
disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(),
|
|
||||||
ariaLabel: label,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function createManageGitHubAccountButton(container: Explorer): CommandButtonComponentProps {
|
|
||||||
const connectedToGitHub: boolean = container.notebookManager?.gitHubOAuthService.isLoggedIn();
|
|
||||||
const label = connectedToGitHub ? "Manage GitHub settings" : "Connect to GitHub";
|
|
||||||
const junoClient = new JunoClient();
|
|
||||||
return {
|
|
||||||
iconSrc: GitHubIcon,
|
|
||||||
iconAlt: label,
|
|
||||||
onCommandClick: () => {
|
|
||||||
useSidePanel
|
|
||||||
.getState()
|
|
||||||
.openSidePanel(
|
|
||||||
label,
|
|
||||||
<GitHubReposPanel
|
|
||||||
explorer={container}
|
|
||||||
gitHubClientProp={container.notebookManager.gitHubClient}
|
|
||||||
junoClientProp={junoClient}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
commandButtonLabel: label,
|
|
||||||
hasPopup: false,
|
|
||||||
disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(),
|
|
||||||
ariaLabel: label,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function createStaticCommandBarButtonsForResourceToken(
|
function createStaticCommandBarButtonsForResourceToken(
|
||||||
container: Explorer,
|
container: Explorer,
|
||||||
selectedNodeState: SelectedNodeState,
|
selectedNodeState: SelectedNodeState,
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
IDropdownStyles,
|
IDropdownStyles,
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
|
import { KeyboardHandlerMap } from "KeyboardShortcuts";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import _ from "underscore";
|
import _ from "underscore";
|
||||||
import ChevronDownIcon from "../../../../images/Chevron_down.svg";
|
import ChevronDownIcon from "../../../../images/Chevron_down.svg";
|
||||||
@ -233,3 +234,28 @@ export const createConnectionStatus = (container: Explorer, poolId: PoolIdType,
|
|||||||
onRender: () => <ConnectionStatus container={container} poolId={poolId} />,
|
onRender: () => <ConnectionStatus container={container} poolId={poolId} />,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function createKeyboardHandlers(allButtons: CommandButtonComponentProps[]): KeyboardHandlerMap {
|
||||||
|
const handlers: KeyboardHandlerMap = {};
|
||||||
|
|
||||||
|
function createHandlers(buttons: CommandButtonComponentProps[]) {
|
||||||
|
buttons.forEach((button) => {
|
||||||
|
if (!button.disabled && button.keyboardAction) {
|
||||||
|
handlers[button.keyboardAction] = (e) => {
|
||||||
|
button.onCommandClick(e);
|
||||||
|
|
||||||
|
// If the handler is bound, it means the button is visible and enabled, so we should prevent the default action
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (button.children && button.children.length > 0) {
|
||||||
|
createHandlers(button.children);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createHandlers(allButtons);
|
||||||
|
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// 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 { useDatabases } from "Explorer/useDatabases";
|
||||||
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";
|
||||||
@ -40,97 +41,112 @@ function openCollectionTab(
|
|||||||
databases: ViewModels.Database[],
|
databases: ViewModels.Database[],
|
||||||
initialDatabaseIndex = 0,
|
initialDatabaseIndex = 0,
|
||||||
) {
|
) {
|
||||||
for (let i = initialDatabaseIndex; i < databases.length; i++) {
|
//if databases are not yet loaded, wait until loaded
|
||||||
const database: ViewModels.Database = databases[i];
|
if (!databases || databases.length === 0) {
|
||||||
if (!!action.databaseResourceId && database.id() !== action.databaseResourceId) {
|
const databaseActionHandler = (databases: ViewModels.Database[]) => {
|
||||||
continue;
|
databasesUnsubscription();
|
||||||
}
|
openCollectionTab(action, databases, 0);
|
||||||
|
return;
|
||||||
const collectionActionHandler = (collections: ViewModels.Collection[]) => {
|
|
||||||
if (!action.collectionResourceId && collections.length === 0) {
|
|
||||||
subscription.dispose();
|
|
||||||
openCollectionTab(action, databases, ++i);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let j = 0; j < collections.length; j++) {
|
|
||||||
const collection: ViewModels.Collection = collections[j];
|
|
||||||
if (!!action.collectionResourceId && collection.id() !== action.collectionResourceId) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// select the collection
|
|
||||||
collection.expandCollection();
|
|
||||||
|
|
||||||
if (
|
|
||||||
action.tabKind === ActionContracts.TabKind.SQLDocuments ||
|
|
||||||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLDocuments]
|
|
||||||
) {
|
|
||||||
collection.onDocumentDBDocumentsClick();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
action.tabKind === ActionContracts.TabKind.MongoDocuments ||
|
|
||||||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.MongoDocuments]
|
|
||||||
) {
|
|
||||||
collection.onMongoDBDocumentsClick();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
action.tabKind === ActionContracts.TabKind.SchemaAnalyzer ||
|
|
||||||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SchemaAnalyzer]
|
|
||||||
) {
|
|
||||||
collection.onSchemaAnalyzerClick();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
action.tabKind === ActionContracts.TabKind.TableEntities ||
|
|
||||||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.TableEntities]
|
|
||||||
) {
|
|
||||||
collection.onTableEntitiesClick();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
action.tabKind === ActionContracts.TabKind.Graph ||
|
|
||||||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.Graph]
|
|
||||||
) {
|
|
||||||
collection.onGraphDocumentsClick();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
action.tabKind === ActionContracts.TabKind.SQLQuery ||
|
|
||||||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLQuery]
|
|
||||||
) {
|
|
||||||
collection.onNewQueryClick(
|
|
||||||
collection,
|
|
||||||
undefined,
|
|
||||||
generateQueryText(action as ActionContracts.OpenQueryTab, collection.partitionKeyProperties),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
action.tabKind === ActionContracts.TabKind.ScaleSettings ||
|
|
||||||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.ScaleSettings]
|
|
||||||
) {
|
|
||||||
collection.onSettingsClick();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
subscription.dispose();
|
|
||||||
};
|
};
|
||||||
|
const databasesUnsubscription = useDatabases.subscribe(databaseActionHandler, (state) => state.databases);
|
||||||
|
} else {
|
||||||
|
for (let i = initialDatabaseIndex; i < databases.length; i++) {
|
||||||
|
const database: ViewModels.Database = databases[i];
|
||||||
|
if (!!action.databaseResourceId && database.id() !== action.databaseResourceId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const subscription = database.collections.subscribe((collections) => collectionActionHandler(collections));
|
//expand database first if not expanded to load the collections
|
||||||
if (database.collections && database.collections() && database.collections().length) {
|
if (!database.isDatabaseExpanded?.()) {
|
||||||
collectionActionHandler(database.collections());
|
database.expandDatabase?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
const collectionActionHandler = (collections: ViewModels.Collection[]) => {
|
||||||
|
if (!action.collectionResourceId && collections.length === 0) {
|
||||||
|
subscription.dispose();
|
||||||
|
openCollectionTab(action, databases, ++i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = 0; j < collections.length; j++) {
|
||||||
|
const collection: ViewModels.Collection = collections[j];
|
||||||
|
if (!!action.collectionResourceId && collection.id() !== action.collectionResourceId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// select the collection
|
||||||
|
collection.expandCollection();
|
||||||
|
|
||||||
|
if (
|
||||||
|
action.tabKind === ActionContracts.TabKind.SQLDocuments ||
|
||||||
|
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLDocuments]
|
||||||
|
) {
|
||||||
|
collection.onDocumentDBDocumentsClick();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
action.tabKind === ActionContracts.TabKind.MongoDocuments ||
|
||||||
|
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.MongoDocuments]
|
||||||
|
) {
|
||||||
|
collection.onMongoDBDocumentsClick();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
action.tabKind === ActionContracts.TabKind.SchemaAnalyzer ||
|
||||||
|
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SchemaAnalyzer]
|
||||||
|
) {
|
||||||
|
collection.onSchemaAnalyzerClick();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
action.tabKind === ActionContracts.TabKind.TableEntities ||
|
||||||
|
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.TableEntities]
|
||||||
|
) {
|
||||||
|
collection.onTableEntitiesClick();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
action.tabKind === ActionContracts.TabKind.Graph ||
|
||||||
|
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.Graph]
|
||||||
|
) {
|
||||||
|
collection.onGraphDocumentsClick();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
action.tabKind === ActionContracts.TabKind.SQLQuery ||
|
||||||
|
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLQuery]
|
||||||
|
) {
|
||||||
|
collection.onNewQueryClick(
|
||||||
|
collection,
|
||||||
|
undefined,
|
||||||
|
generateQueryText(action as ActionContracts.OpenQueryTab, collection.partitionKeyProperties),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
action.tabKind === ActionContracts.TabKind.ScaleSettings ||
|
||||||
|
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.ScaleSettings]
|
||||||
|
) {
|
||||||
|
collection.onSettingsClick();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subscription.dispose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const subscription = database.collections.subscribe((collections) => collectionActionHandler(collections));
|
||||||
|
if (database.collections && database.collections() && database.collections().length) {
|
||||||
|
collectionActionHandler(database.collections());
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,8 +202,8 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
required={true}
|
required={true}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
styles={getTextFieldStyles()}
|
styles={getTextFieldStyles()}
|
||||||
pattern="[^/?#\\]*[^/?# \\]"
|
pattern="[^/?#\\-]*[^/?#- \\]"
|
||||||
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
title="May not end with space nor contain characters '\' '/' '#' '?' '-'"
|
||||||
placeholder="Type a new keyspace id"
|
placeholder="Type a new keyspace id"
|
||||||
size={40}
|
size={40}
|
||||||
value={newKeyspaceId}
|
value={newKeyspaceId}
|
||||||
@ -292,8 +292,8 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
required={true}
|
required={true}
|
||||||
ariaLabel="addCollection-table Id Create table"
|
ariaLabel="addCollection-table Id Create table"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
pattern="[^/?#\\]*[^/?# \\]"
|
pattern="[^/?#\\-]*[^/?#- \\]"
|
||||||
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
title="May not end with space nor contain characters '\' '/' '#' '?' '-'"
|
||||||
placeholder="Enter table Id"
|
placeholder="Enter table Id"
|
||||||
size={20}
|
size={20}
|
||||||
value={tableId}
|
value={tableId}
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
Icon,
|
Icon,
|
||||||
IconButton,
|
IconButton,
|
||||||
Link,
|
Link,
|
||||||
|
MessageBar,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
TooltipHost,
|
TooltipHost,
|
||||||
@ -207,6 +208,7 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
|
|||||||
</Stack>
|
</Stack>
|
||||||
{createNewContainer ? (
|
{createNewContainer ? (
|
||||||
<Stack>
|
<Stack>
|
||||||
|
<MessageBar>All configurations except for unique keys will be copied from the source container</MessageBar>
|
||||||
<Stack className="panelGroupSpacing">
|
<Stack className="panelGroupSpacing">
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<span className="mandatoryStar">* </span>
|
<span className="mandatoryStar">* </span>
|
||||||
|
@ -630,7 +630,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
Enable sample database
|
Enable sample database
|
||||||
<InfoTooltip>
|
<InfoTooltip>
|
||||||
This is a sample database and collection with synthetic product data you can use to explore using
|
This is a sample database and collection with synthetic product data you can use to explore using
|
||||||
NoSQL queries and Copilot. This will appear as another database in the Data Explorer UI, and is
|
NoSQL queries and Query Advisor. This will appear as another database in the Data Explorer UI, and is
|
||||||
created by, and maintained by Microsoft at no cost to you.
|
created by, and maintained by Microsoft at no cost to you.
|
||||||
</InfoTooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</div>
|
||||||
@ -640,7 +640,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
label: { padding: 0 },
|
label: { padding: 0 },
|
||||||
}}
|
}}
|
||||||
className="padding"
|
className="padding"
|
||||||
ariaLabel="Enable sample db for Copilot"
|
ariaLabel="Enable sample db for Query Advisor"
|
||||||
checked={copilotSampleDBEnabled}
|
checked={copilotSampleDBEnabled}
|
||||||
onChange={handleSampleDatabaseChange}
|
onChange={handleSampleDatabaseChange}
|
||||||
/>
|
/>
|
||||||
|
@ -124,8 +124,8 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
|||||||
|
|
||||||
setIsExecuting(true);
|
setIsExecuting(true);
|
||||||
const entity: Entities.ITableEntity = entityFromAttributes(entities);
|
const entity: Entities.ITableEntity = entityFromAttributes(entities);
|
||||||
const newEntity: Entities.ITableEntity = await tableDataClient.createDocument(queryTablesTab.collection, entity);
|
|
||||||
try {
|
try {
|
||||||
|
const newEntity: Entities.ITableEntity = await tableDataClient.createDocument(queryTablesTab.collection, entity);
|
||||||
await tableEntityListViewModel.addEntityToCache(newEntity);
|
await tableEntityListViewModel.addEntityToCache(newEntity);
|
||||||
if (!tryInsertNewHeaders(tableEntityListViewModel, newEntity)) {
|
if (!tryInsertNewHeaders(tableEntityListViewModel, newEntity)) {
|
||||||
tableEntityListViewModel.redrawTableThrottled();
|
tableEntityListViewModel.redrawTableThrottled();
|
||||||
|
@ -49,7 +49,7 @@ export const QueryCopilotFeedbackModal = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={showFeedbackModal}>
|
<Modal isOpen={showFeedbackModal} styles={{ main: { borderRadius: 8, maxWidth: 600 } }}>
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<Stack style={{ padding: 24 }}>
|
<Stack style={{ padding: 24 }}>
|
||||||
<Stack horizontal horizontalAlign="space-between">
|
<Stack horizontal horizontalAlign="space-between">
|
||||||
@ -68,9 +68,14 @@ export const QueryCopilotFeedbackModal = ({
|
|||||||
rows={3}
|
rows={3}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
styles={{ root: { marginBottom: 14 } }}
|
styles={{
|
||||||
|
root: { marginBottom: 14 },
|
||||||
|
fieldGroup: { backgroundColor: "#F3F2F1", borderRadius: 4, borderColor: "#D1D1D1" },
|
||||||
|
}}
|
||||||
label="Query generated"
|
label="Query generated"
|
||||||
defaultValue={generatedQuery}
|
defaultValue={generatedQuery}
|
||||||
|
multiline
|
||||||
|
rows={3}
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
<Text style={{ fontSize: 12, marginBottom: 14 }}>
|
<Text style={{ fontSize: 12, marginBottom: 14 }}>
|
||||||
|
@ -3,6 +3,14 @@
|
|||||||
exports[`Query Copilot Feedback Modal snapshot test shoud render and match snapshot 1`] = `
|
exports[`Query Copilot Feedback Modal snapshot test shoud render and match snapshot 1`] = `
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"main": Object {
|
||||||
|
"borderRadius": 8,
|
||||||
|
"maxWidth": 600,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<form
|
<form
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
@ -67,9 +75,16 @@ exports[`Query Copilot Feedback Modal snapshot test shoud render and match snaps
|
|||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
defaultValue="test query"
|
defaultValue="test query"
|
||||||
label="Query generated"
|
label="Query generated"
|
||||||
|
multiline={true}
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
|
rows={3}
|
||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
|
"fieldGroup": Object {
|
||||||
|
"backgroundColor": "#F3F2F1",
|
||||||
|
"borderColor": "#D1D1D1",
|
||||||
|
"borderRadius": 4,
|
||||||
|
},
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"marginBottom": 14,
|
"marginBottom": 14,
|
||||||
},
|
},
|
||||||
@ -125,6 +140,14 @@ exports[`Query Copilot Feedback Modal snapshot test shoud render and match snaps
|
|||||||
exports[`Query Copilot Feedback Modal snapshot test should cancel submission 1`] = `
|
exports[`Query Copilot Feedback Modal snapshot test should cancel submission 1`] = `
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={false}
|
isOpen={false}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"main": Object {
|
||||||
|
"borderRadius": 8,
|
||||||
|
"maxWidth": 600,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<form
|
<form
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
@ -189,9 +212,16 @@ exports[`Query Copilot Feedback Modal snapshot test should cancel submission 1`]
|
|||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
defaultValue="test query"
|
defaultValue="test query"
|
||||||
label="Query generated"
|
label="Query generated"
|
||||||
|
multiline={true}
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
|
rows={3}
|
||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
|
"fieldGroup": Object {
|
||||||
|
"backgroundColor": "#F3F2F1",
|
||||||
|
"borderColor": "#D1D1D1",
|
||||||
|
"borderRadius": 4,
|
||||||
|
},
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"marginBottom": 14,
|
"marginBottom": 14,
|
||||||
},
|
},
|
||||||
@ -247,6 +277,14 @@ exports[`Query Copilot Feedback Modal snapshot test should cancel submission 1`]
|
|||||||
exports[`Query Copilot Feedback Modal snapshot test should close on cancel click 1`] = `
|
exports[`Query Copilot Feedback Modal snapshot test should close on cancel click 1`] = `
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={false}
|
isOpen={false}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"main": Object {
|
||||||
|
"borderRadius": 8,
|
||||||
|
"maxWidth": 600,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<form
|
<form
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
@ -311,9 +349,16 @@ exports[`Query Copilot Feedback Modal snapshot test should close on cancel click
|
|||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
defaultValue="test query"
|
defaultValue="test query"
|
||||||
label="Query generated"
|
label="Query generated"
|
||||||
|
multiline={true}
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
|
rows={3}
|
||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
|
"fieldGroup": Object {
|
||||||
|
"backgroundColor": "#F3F2F1",
|
||||||
|
"borderColor": "#D1D1D1",
|
||||||
|
"borderRadius": 4,
|
||||||
|
},
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"marginBottom": 14,
|
"marginBottom": 14,
|
||||||
},
|
},
|
||||||
@ -369,6 +414,14 @@ exports[`Query Copilot Feedback Modal snapshot test should close on cancel click
|
|||||||
exports[`Query Copilot Feedback Modal snapshot test should get user unput 1`] = `
|
exports[`Query Copilot Feedback Modal snapshot test should get user unput 1`] = `
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={false}
|
isOpen={false}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"main": Object {
|
||||||
|
"borderRadius": 8,
|
||||||
|
"maxWidth": 600,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<form
|
<form
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
@ -433,9 +486,16 @@ exports[`Query Copilot Feedback Modal snapshot test should get user unput 1`] =
|
|||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
defaultValue="test query"
|
defaultValue="test query"
|
||||||
label="Query generated"
|
label="Query generated"
|
||||||
|
multiline={true}
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
|
rows={3}
|
||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
|
"fieldGroup": Object {
|
||||||
|
"backgroundColor": "#F3F2F1",
|
||||||
|
"borderColor": "#D1D1D1",
|
||||||
|
"borderRadius": 4,
|
||||||
|
},
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"marginBottom": 14,
|
"marginBottom": 14,
|
||||||
},
|
},
|
||||||
@ -491,6 +551,14 @@ exports[`Query Copilot Feedback Modal snapshot test should get user unput 1`] =
|
|||||||
exports[`Query Copilot Feedback Modal snapshot test should not render dont show again button 1`] = `
|
exports[`Query Copilot Feedback Modal snapshot test should not render dont show again button 1`] = `
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={false}
|
isOpen={false}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"main": Object {
|
||||||
|
"borderRadius": 8,
|
||||||
|
"maxWidth": 600,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<form
|
<form
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
@ -555,9 +623,16 @@ exports[`Query Copilot Feedback Modal snapshot test should not render dont show
|
|||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
defaultValue="test query"
|
defaultValue="test query"
|
||||||
label="Query generated"
|
label="Query generated"
|
||||||
|
multiline={true}
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
|
rows={3}
|
||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
|
"fieldGroup": Object {
|
||||||
|
"backgroundColor": "#F3F2F1",
|
||||||
|
"borderColor": "#D1D1D1",
|
||||||
|
"borderRadius": 4,
|
||||||
|
},
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"marginBottom": 14,
|
"marginBottom": 14,
|
||||||
},
|
},
|
||||||
@ -613,6 +688,14 @@ exports[`Query Copilot Feedback Modal snapshot test should not render dont show
|
|||||||
exports[`Query Copilot Feedback Modal snapshot test should render dont show again button and check it 1`] = `
|
exports[`Query Copilot Feedback Modal snapshot test should render dont show again button and check it 1`] = `
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"main": Object {
|
||||||
|
"borderRadius": 8,
|
||||||
|
"maxWidth": 600,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<form
|
<form
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
@ -677,9 +760,16 @@ exports[`Query Copilot Feedback Modal snapshot test should render dont show agai
|
|||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
defaultValue="test query"
|
defaultValue="test query"
|
||||||
label="Query generated"
|
label="Query generated"
|
||||||
|
multiline={true}
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
|
rows={3}
|
||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
|
"fieldGroup": Object {
|
||||||
|
"backgroundColor": "#F3F2F1",
|
||||||
|
"borderColor": "#D1D1D1",
|
||||||
|
"borderRadius": 4,
|
||||||
|
},
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"marginBottom": 14,
|
"marginBottom": 14,
|
||||||
},
|
},
|
||||||
@ -750,6 +840,14 @@ exports[`Query Copilot Feedback Modal snapshot test should render dont show agai
|
|||||||
exports[`Query Copilot Feedback Modal snapshot test should submit submission 1`] = `
|
exports[`Query Copilot Feedback Modal snapshot test should submit submission 1`] = `
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={false}
|
isOpen={false}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"main": Object {
|
||||||
|
"borderRadius": 8,
|
||||||
|
"maxWidth": 600,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<form
|
<form
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
@ -814,9 +912,16 @@ exports[`Query Copilot Feedback Modal snapshot test should submit submission 1`]
|
|||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
defaultValue="test query"
|
defaultValue="test query"
|
||||||
label="Query generated"
|
label="Query generated"
|
||||||
|
multiline={true}
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
|
rows={3}
|
||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
|
"fieldGroup": Object {
|
||||||
|
"backgroundColor": "#F3F2F1",
|
||||||
|
"borderColor": "#D1D1D1",
|
||||||
|
"borderRadius": 4,
|
||||||
|
},
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"marginBottom": 14,
|
"marginBottom": 14,
|
||||||
},
|
},
|
||||||
|
@ -11,8 +11,8 @@ import {
|
|||||||
Link,
|
Link,
|
||||||
MessageBar,
|
MessageBar,
|
||||||
MessageBarType,
|
MessageBarType,
|
||||||
|
ProgressIndicator,
|
||||||
Separator,
|
Separator,
|
||||||
Spinner,
|
|
||||||
Stack,
|
Stack,
|
||||||
TeachingBubble,
|
TeachingBubble,
|
||||||
Text,
|
Text,
|
||||||
@ -36,7 +36,6 @@ import { userContext } from "UserContext";
|
|||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import React, { useRef, useState } from "react";
|
import React, { useRef, useState } from "react";
|
||||||
import HintIcon from "../../../images/Hint.svg";
|
import HintIcon from "../../../images/Hint.svg";
|
||||||
import CopilotIcon from "../../../images/QueryCopilotNewLogo.svg";
|
|
||||||
import RecentIcon from "../../../images/Recent.svg";
|
import RecentIcon from "../../../images/Recent.svg";
|
||||||
import errorIcon from "../../../images/close-black.svg";
|
import errorIcon from "../../../images/close-black.svg";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
@ -215,12 +214,12 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
const generateSQLQueryResponse: GenerateSQLQueryResponse = await response?.json();
|
const generateSQLQueryResponse: GenerateSQLQueryResponse = await response?.json();
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
if (generateSQLQueryResponse?.sql !== "N/A") {
|
if (generateSQLQueryResponse?.sql !== "N/A") {
|
||||||
let query = `-- **Prompt:** ${userPrompt}\r\n`;
|
const queryExplanation = `-- **Explanation of query:** ${
|
||||||
if (generateSQLQueryResponse.explanation) {
|
generateSQLQueryResponse.explanation ? generateSQLQueryResponse.explanation : "N/A"
|
||||||
query += `-- **Explanation of query:** ${generateSQLQueryResponse.explanation}\r\n`;
|
}\r\n`;
|
||||||
}
|
const currentGeneratedQuery = queryExplanation + generateSQLQueryResponse.sql;
|
||||||
query += generateSQLQueryResponse.sql;
|
const lastQuery = generatedQuery && query ? `${query}\r\n` : "";
|
||||||
setQuery(query);
|
setQuery(`${lastQuery}${currentGeneratedQuery}`);
|
||||||
setGeneratedQuery(generateSQLQueryResponse.sql);
|
setGeneratedQuery(generateSQLQueryResponse.sql);
|
||||||
setGeneratedQueryComments(generateSQLQueryResponse.explanation);
|
setGeneratedQueryComments(generateSQLQueryResponse.explanation);
|
||||||
setShowFeedbackBar(true);
|
setShowFeedbackBar(true);
|
||||||
@ -310,12 +309,388 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
className="copilot-prompt-pane"
|
className="copilot-prompt-pane"
|
||||||
styles={{ root: { backgroundColor: "#FAFAFA", padding: "16px 24px 0px" } }}
|
styles={{ root: { backgroundColor: "#FAFAFA", padding: "8px" } }}
|
||||||
id="copilot-textfield-label"
|
id="copilot-textfield-label"
|
||||||
>
|
>
|
||||||
<Stack horizontal>
|
<Stack
|
||||||
<Image src={CopilotIcon} style={{ width: 24, height: 24 }} alt="Copilot" role="none" />
|
horizontal
|
||||||
<Text style={{ marginLeft: 8, fontWeight: 600, fontSize: 16 }}>Copilot</Text>
|
styles={{
|
||||||
|
root: {
|
||||||
|
width: "100%",
|
||||||
|
borderWidth: 1,
|
||||||
|
borderStyle: "solid",
|
||||||
|
borderColor: "#D1D1D1",
|
||||||
|
borderRadius: 8,
|
||||||
|
boxShadow: "0px 4px 8px 0px #00000024",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack style={{ width: "100%" }}>
|
||||||
|
<Stack horizontal verticalAlign="center" style={{ padding: "8px 8px 0px 8px" }}>
|
||||||
|
<TextField
|
||||||
|
id="naturalLanguageInput"
|
||||||
|
value={userPrompt}
|
||||||
|
onChange={handleUserPromptChange}
|
||||||
|
onClick={() => {
|
||||||
|
inputEdited.current = true;
|
||||||
|
setShowSamplePrompts(true);
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter" && userPrompt) {
|
||||||
|
inputEdited.current = true;
|
||||||
|
startGenerateQueryProcess();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{ lineHeight: 30 }}
|
||||||
|
styles={{
|
||||||
|
root: { width: "100%" },
|
||||||
|
suffix: {
|
||||||
|
background: "none",
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
fieldGroup: {
|
||||||
|
borderRadius: 4,
|
||||||
|
borderColor: "#D1D1D1",
|
||||||
|
"::after": {
|
||||||
|
border: "inherit",
|
||||||
|
borderWidth: 2,
|
||||||
|
borderBottomColor: "#464FEB",
|
||||||
|
borderRadius: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
disabled={isGeneratingQuery}
|
||||||
|
autoComplete="off"
|
||||||
|
placeholder="Ask a question in natural language and we’ll generate the query for you."
|
||||||
|
aria-labelledby="copilot-textfield-label"
|
||||||
|
onRenderSuffix={() => {
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
iconProps={{ iconName: "Send" }}
|
||||||
|
disabled={isGeneratingQuery || !userPrompt.trim()}
|
||||||
|
style={{ background: "none" }}
|
||||||
|
onClick={() => startGenerateQueryProcess()}
|
||||||
|
aria-label="Send"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{showPromptTeachingBubble && copilotTeachingBubbleVisible && (
|
||||||
|
<TeachingBubble
|
||||||
|
calloutProps={{ directionalHint: DirectionalHint.bottomCenter }}
|
||||||
|
target="#naturalLanguageInput"
|
||||||
|
hasCloseButton={true}
|
||||||
|
closeButtonAriaLabel="Close"
|
||||||
|
onDismiss={() => toggleCopilotTeachingBubbleVisible(false)}
|
||||||
|
hasSmallHeadline={true}
|
||||||
|
headline="Write a prompt"
|
||||||
|
>
|
||||||
|
Write a prompt here and Query Advisor will generate the query for you. You can also choose from our{" "}
|
||||||
|
<Link
|
||||||
|
onClick={() => {
|
||||||
|
setShowSamplePrompts(true);
|
||||||
|
toggleCopilotTeachingBubbleVisible(false);
|
||||||
|
}}
|
||||||
|
style={{ color: "white", fontWeight: 600 }}
|
||||||
|
>
|
||||||
|
sample prompts
|
||||||
|
</Link>{" "}
|
||||||
|
or write your own query
|
||||||
|
</TeachingBubble>
|
||||||
|
)}
|
||||||
|
{showSamplePrompts && (
|
||||||
|
<Callout
|
||||||
|
styles={{ root: { minWidth: 400, maxWidth: "70vw" } }}
|
||||||
|
target="#naturalLanguageInput"
|
||||||
|
isBeakVisible={false}
|
||||||
|
onDismiss={() => setShowSamplePrompts(false)}
|
||||||
|
directionalHintFixed={true}
|
||||||
|
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||||
|
alignTargetEdge={true}
|
||||||
|
gapSpace={4}
|
||||||
|
>
|
||||||
|
<Stack>
|
||||||
|
{filteredHistories?.length > 0 && (
|
||||||
|
<Stack>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: "#0078D4",
|
||||||
|
marginLeft: 16,
|
||||||
|
padding: "4px 0",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Recent
|
||||||
|
</Text>
|
||||||
|
{filteredHistories.map((history, i) => (
|
||||||
|
<DefaultButton
|
||||||
|
key={i}
|
||||||
|
onClick={() => {
|
||||||
|
setUserPrompt(history);
|
||||||
|
setShowSamplePrompts(false);
|
||||||
|
inputEdited.current = true;
|
||||||
|
}}
|
||||||
|
onRenderIcon={() => <Image src={RecentIcon} styles={{ root: { overflow: "unset" } }} />}
|
||||||
|
styles={promptStyles}
|
||||||
|
>
|
||||||
|
{history}
|
||||||
|
</DefaultButton>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
{filteredSuggestedPrompts?.length > 0 && (
|
||||||
|
<Stack>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: "#0078D4",
|
||||||
|
marginLeft: 16,
|
||||||
|
padding: "4px 0",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Suggested Prompts
|
||||||
|
</Text>
|
||||||
|
{filteredSuggestedPrompts.map((prompt) => (
|
||||||
|
<DefaultButton
|
||||||
|
key={prompt.id}
|
||||||
|
onClick={() => {
|
||||||
|
setUserPrompt(prompt.text);
|
||||||
|
setShowSamplePrompts(false);
|
||||||
|
inputEdited.current = true;
|
||||||
|
}}
|
||||||
|
onRenderIcon={() => <Image src={HintIcon} />}
|
||||||
|
styles={promptStyles}
|
||||||
|
>
|
||||||
|
{prompt.text}
|
||||||
|
</DefaultButton>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
{(filteredHistories?.length > 0 || filteredSuggestedPrompts?.length > 0) && (
|
||||||
|
<Stack>
|
||||||
|
<Separator
|
||||||
|
styles={{
|
||||||
|
root: {
|
||||||
|
selectors: { "::before": { background: "#E1DFDD" } },
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
fontSize: 14,
|
||||||
|
marginLeft: 16,
|
||||||
|
padding: "4px 0",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Learn about{" "}
|
||||||
|
<Link target="_blank" href="https://aka.ms/cdb-copilot-writing">
|
||||||
|
writing effective prompts
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Callout>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
{!isGeneratingQuery && (
|
||||||
|
<Stack style={{ padding: 8 }}>
|
||||||
|
{!showFeedbackBar && (
|
||||||
|
<Text style={{ fontSize: 12 }}>
|
||||||
|
AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.{" "}
|
||||||
|
<Link href="https://aka.ms/cdb-copilot-preview-terms" target="_blank" style={{ color: "#0072D4" }}>
|
||||||
|
Read preview terms
|
||||||
|
</Link>
|
||||||
|
{showErrorMessageBar && (
|
||||||
|
<MessageBar messageBarType={MessageBarType.error}>
|
||||||
|
{errorMessage ? errorMessage : "We ran into an error and were not able to execute query."}
|
||||||
|
</MessageBar>
|
||||||
|
)}
|
||||||
|
{showInvalidQueryMessageBar && (
|
||||||
|
<MessageBar
|
||||||
|
messageBarType={MessageBarType.info}
|
||||||
|
styles={{ root: { backgroundColor: "#F0F6FF" }, icon: { color: "#015CDA" } }}
|
||||||
|
>
|
||||||
|
We were unable to generate a query based upon the prompt provided. Please modify the prompt and
|
||||||
|
try again. For examples of how to write a good prompt, please read
|
||||||
|
<Link href="https://aka.ms/cdb-copilot-writing" target="_blank">
|
||||||
|
this article.
|
||||||
|
</Link>{" "}
|
||||||
|
Our content guidelines can be found
|
||||||
|
<Link href="https://aka.ms/cdb-query-copilot" target="_blank">
|
||||||
|
here.
|
||||||
|
</Link>
|
||||||
|
</MessageBar>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
{showFeedbackBar && (
|
||||||
|
<Stack horizontal verticalAlign="center" style={{ maxHeight: 20 }}>
|
||||||
|
{userContext.feedbackPolicies?.policyAllowFeedback && (
|
||||||
|
<Stack horizontal verticalAlign="center">
|
||||||
|
<Text style={{ fontSize: 12 }}>Provide feedback</Text>
|
||||||
|
{showCallout && !hideFeedbackModalForLikedQueries && (
|
||||||
|
<Callout
|
||||||
|
role="status"
|
||||||
|
style={{ padding: "6px 12px" }}
|
||||||
|
styles={{
|
||||||
|
root: {
|
||||||
|
borderRadius: 8,
|
||||||
|
},
|
||||||
|
beakCurtain: {
|
||||||
|
borderRadius: 8,
|
||||||
|
},
|
||||||
|
calloutMain: {
|
||||||
|
borderRadius: 8,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
target="#likeBtn"
|
||||||
|
onDismiss={() => {
|
||||||
|
setShowCallout(false);
|
||||||
|
SubmitFeedback({
|
||||||
|
params: {
|
||||||
|
generatedQuery: generatedQuery,
|
||||||
|
likeQuery: likeQuery,
|
||||||
|
description: "",
|
||||||
|
userPrompt: userPrompt,
|
||||||
|
},
|
||||||
|
explorer,
|
||||||
|
databaseId,
|
||||||
|
containerId,
|
||||||
|
mode: isSampleCopilotActive ? "Sample" : "User",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
directionalHint={DirectionalHint.topCenter}
|
||||||
|
>
|
||||||
|
<Text>
|
||||||
|
Thank you. Need to give{" "}
|
||||||
|
<Link
|
||||||
|
onClick={() => {
|
||||||
|
setShowCallout(false);
|
||||||
|
openFeedbackModal(generatedQuery, true, userPrompt);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
more feedback?
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
</Callout>
|
||||||
|
)}
|
||||||
|
<IconButton
|
||||||
|
id="likeBtn"
|
||||||
|
style={{ marginLeft: 10 }}
|
||||||
|
aria-label="Like"
|
||||||
|
role="toggle"
|
||||||
|
iconProps={{ iconName: likeQuery === true ? "LikeSolid" : "Like" }}
|
||||||
|
onClick={() => {
|
||||||
|
setShowCallout(!likeQuery);
|
||||||
|
setLikeQuery(!likeQuery);
|
||||||
|
if (likeQuery === true) {
|
||||||
|
document.getElementById("likeStatus").innerHTML = "Unpressed";
|
||||||
|
}
|
||||||
|
if (likeQuery === false) {
|
||||||
|
document.getElementById("likeStatus").innerHTML = "Liked";
|
||||||
|
}
|
||||||
|
if (dislikeQuery) {
|
||||||
|
setDislikeQuery(!dislikeQuery);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
style={{ margin: "0 4px" }}
|
||||||
|
role="toggle"
|
||||||
|
aria-label="Dislike"
|
||||||
|
iconProps={{ iconName: dislikeQuery === true ? "DislikeSolid" : "Dislike" }}
|
||||||
|
onClick={() => {
|
||||||
|
let toggleStatusValue = "Unpressed";
|
||||||
|
if (!dislikeQuery) {
|
||||||
|
openFeedbackModal(generatedQuery, false, userPrompt);
|
||||||
|
setLikeQuery(false);
|
||||||
|
toggleStatusValue = "Disliked";
|
||||||
|
}
|
||||||
|
setDislikeQuery(!dislikeQuery);
|
||||||
|
setShowCallout(false);
|
||||||
|
document.getElementById("likeStatus").innerHTML = toggleStatusValue;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span role="status" style={{ position: "absolute", left: "-9999px" }} id="likeStatus"></span>
|
||||||
|
<Separator
|
||||||
|
vertical
|
||||||
|
styles={{
|
||||||
|
root: {
|
||||||
|
"::after": {
|
||||||
|
backgroundColor: "#767676",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
<CommandBarButton
|
||||||
|
className="copyQuery"
|
||||||
|
onClick={copyGeneratedCode}
|
||||||
|
iconProps={{ iconName: "Copy" }}
|
||||||
|
style={{ fontSize: 12, transition: "background-color 0.3s ease", height: "100%" }}
|
||||||
|
styles={{
|
||||||
|
root: {
|
||||||
|
backgroundColor: "inherit",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Copy code
|
||||||
|
</CommandBarButton>
|
||||||
|
<CommandBarButton
|
||||||
|
className="deleteQuery"
|
||||||
|
onClick={() => {
|
||||||
|
setShowDeletePopup(true);
|
||||||
|
}}
|
||||||
|
iconProps={{ iconName: "Delete" }}
|
||||||
|
style={{ fontSize: 12, transition: "background-color 0.3s ease", height: "100%" }}
|
||||||
|
styles={{
|
||||||
|
root: {
|
||||||
|
backgroundColor: "inherit",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Clear editor
|
||||||
|
</CommandBarButton>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
{isGeneratingQuery && (
|
||||||
|
<ProgressIndicator
|
||||||
|
label="Thinking..."
|
||||||
|
ariaLabel={getAriaLabel()}
|
||||||
|
barHeight={4}
|
||||||
|
styles={{
|
||||||
|
root: {
|
||||||
|
fontSize: 12,
|
||||||
|
width: "100%",
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
itemName: {
|
||||||
|
padding: "0px 8px",
|
||||||
|
},
|
||||||
|
itemProgress: {
|
||||||
|
borderBottomLeftRadius: 8,
|
||||||
|
borderBottomRightRadius: 8,
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
progressBar: {
|
||||||
|
backgroundImage:
|
||||||
|
"linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, rgb(24, 90, 189) 35%, rgb(71, 207, 250) 70%, rgb(180, 124, 248) 92%, rgba(0, 0, 0, 0))",
|
||||||
|
animationDuration: "5s",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
<IconButton
|
<IconButton
|
||||||
iconProps={{ imageProps: { src: errorIcon } }}
|
iconProps={{ imageProps: { src: errorIcon } }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -323,307 +698,10 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
clearFeedback();
|
clearFeedback();
|
||||||
resetMessageStates();
|
resetMessageStates();
|
||||||
}}
|
}}
|
||||||
styles={{
|
|
||||||
root: {
|
|
||||||
marginLeft: "auto !important",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
ariaLabel="Close"
|
ariaLabel="Close"
|
||||||
title="Close copilot"
|
title="Close copilot"
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack horizontal verticalAlign="center">
|
|
||||||
<TextField
|
|
||||||
id="naturalLanguageInput"
|
|
||||||
value={userPrompt}
|
|
||||||
onChange={handleUserPromptChange}
|
|
||||||
onClick={() => {
|
|
||||||
inputEdited.current = true;
|
|
||||||
setShowSamplePrompts(true);
|
|
||||||
}}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Enter" && userPrompt) {
|
|
||||||
inputEdited.current = true;
|
|
||||||
startGenerateQueryProcess();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
style={{ lineHeight: 30 }}
|
|
||||||
styles={{ root: { width: "95%" }, fieldGroup: { borderRadius: 6 } }}
|
|
||||||
disabled={isGeneratingQuery}
|
|
||||||
autoComplete="off"
|
|
||||||
placeholder="Ask a question in natural language and we’ll generate the query for you."
|
|
||||||
aria-labelledby="copilot-textfield-label"
|
|
||||||
/>
|
|
||||||
{showPromptTeachingBubble && copilotTeachingBubbleVisible && (
|
|
||||||
<TeachingBubble
|
|
||||||
calloutProps={{ directionalHint: DirectionalHint.bottomCenter }}
|
|
||||||
target="#naturalLanguageInput"
|
|
||||||
hasCloseButton={true}
|
|
||||||
closeButtonAriaLabel="Close"
|
|
||||||
onDismiss={() => toggleCopilotTeachingBubbleVisible(false)}
|
|
||||||
hasSmallHeadline={true}
|
|
||||||
headline="Write a prompt"
|
|
||||||
>
|
|
||||||
Write a prompt here and Copilot will generate the query for you. You can also choose from our{" "}
|
|
||||||
<Link
|
|
||||||
onClick={() => {
|
|
||||||
setShowSamplePrompts(true);
|
|
||||||
toggleCopilotTeachingBubbleVisible(false);
|
|
||||||
}}
|
|
||||||
style={{ color: "white", fontWeight: 600 }}
|
|
||||||
>
|
|
||||||
sample prompts
|
|
||||||
</Link>{" "}
|
|
||||||
or write your own query
|
|
||||||
</TeachingBubble>
|
|
||||||
)}
|
|
||||||
<IconButton
|
|
||||||
iconProps={{ iconName: "Send" }}
|
|
||||||
disabled={isGeneratingQuery || !userPrompt.trim()}
|
|
||||||
style={{ marginLeft: 8 }}
|
|
||||||
onClick={() => startGenerateQueryProcess()}
|
|
||||||
aria-label="Send"
|
|
||||||
/>
|
|
||||||
<div role="alert" aria-label={getAriaLabel()}>
|
|
||||||
{isGeneratingQuery && <Spinner style={{ marginLeft: 8 }} />}
|
|
||||||
</div>
|
|
||||||
{showSamplePrompts && (
|
|
||||||
<Callout
|
|
||||||
styles={{ root: { minWidth: 400, maxWidth: "70vw" } }}
|
|
||||||
target="#naturalLanguageInput"
|
|
||||||
isBeakVisible={false}
|
|
||||||
onDismiss={() => setShowSamplePrompts(false)}
|
|
||||||
directionalHintFixed={true}
|
|
||||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
|
||||||
alignTargetEdge={true}
|
|
||||||
gapSpace={4}
|
|
||||||
>
|
|
||||||
<Stack>
|
|
||||||
{filteredHistories?.length > 0 && (
|
|
||||||
<Stack>
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: 600,
|
|
||||||
color: "#0078D4",
|
|
||||||
marginLeft: 16,
|
|
||||||
padding: "4px 0",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Recent
|
|
||||||
</Text>
|
|
||||||
{filteredHistories.map((history, i) => (
|
|
||||||
<DefaultButton
|
|
||||||
key={i}
|
|
||||||
onClick={() => {
|
|
||||||
setUserPrompt(history);
|
|
||||||
setShowSamplePrompts(false);
|
|
||||||
inputEdited.current = true;
|
|
||||||
}}
|
|
||||||
onRenderIcon={() => <Image src={RecentIcon} styles={{ root: { overflow: "unset" } }} />}
|
|
||||||
styles={promptStyles}
|
|
||||||
>
|
|
||||||
{history}
|
|
||||||
</DefaultButton>
|
|
||||||
))}
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
{filteredSuggestedPrompts?.length > 0 && (
|
|
||||||
<Stack>
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: 600,
|
|
||||||
color: "#0078D4",
|
|
||||||
marginLeft: 16,
|
|
||||||
padding: "4px 0",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Suggested Prompts
|
|
||||||
</Text>
|
|
||||||
{filteredSuggestedPrompts.map((prompt) => (
|
|
||||||
<DefaultButton
|
|
||||||
key={prompt.id}
|
|
||||||
onClick={() => {
|
|
||||||
setUserPrompt(prompt.text);
|
|
||||||
setShowSamplePrompts(false);
|
|
||||||
inputEdited.current = true;
|
|
||||||
}}
|
|
||||||
onRenderIcon={() => <Image src={HintIcon} />}
|
|
||||||
styles={promptStyles}
|
|
||||||
>
|
|
||||||
{prompt.text}
|
|
||||||
</DefaultButton>
|
|
||||||
))}
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
{(filteredHistories?.length > 0 || filteredSuggestedPrompts?.length > 0) && (
|
|
||||||
<Stack>
|
|
||||||
<Separator
|
|
||||||
styles={{
|
|
||||||
root: {
|
|
||||||
selectors: { "::before": { background: "#E1DFDD" } },
|
|
||||||
padding: 0,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
fontSize: 14,
|
|
||||||
marginLeft: 16,
|
|
||||||
padding: "4px 0",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Learn about{" "}
|
|
||||||
<Link target="_blank" href="https://aka.ms/cdb-copilot-writing">
|
|
||||||
writing effective prompts
|
|
||||||
</Link>
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Callout>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<Stack style={{ margin: "8px 0" }}>
|
|
||||||
<Text style={{ fontSize: 12 }}>
|
|
||||||
AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.{" "}
|
|
||||||
<Link href="https://aka.ms/cdb-copilot-preview-terms" target="_blank" style={{ color: "#0072D4" }}>
|
|
||||||
Read preview terms
|
|
||||||
</Link>
|
|
||||||
{showErrorMessageBar && (
|
|
||||||
<MessageBar messageBarType={MessageBarType.error}>
|
|
||||||
{errorMessage ? errorMessage : "We ran into an error and were not able to execute query."}
|
|
||||||
</MessageBar>
|
|
||||||
)}
|
|
||||||
{showInvalidQueryMessageBar && (
|
|
||||||
<MessageBar
|
|
||||||
messageBarType={MessageBarType.info}
|
|
||||||
styles={{ root: { backgroundColor: "#F0F6FF" }, icon: { color: "#015CDA" } }}
|
|
||||||
>
|
|
||||||
We were unable to generate a query based upon the prompt provided. Please modify the prompt and try again.
|
|
||||||
For examples of how to write a good prompt, please read
|
|
||||||
<Link href="https://aka.ms/cdb-copilot-writing" target="_blank">
|
|
||||||
this article.
|
|
||||||
</Link>{" "}
|
|
||||||
Our content guidelines can be found
|
|
||||||
<Link href="https://aka.ms/cdb-query-copilot" target="_blank">
|
|
||||||
here.
|
|
||||||
</Link>
|
|
||||||
</MessageBar>
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
{showFeedbackBar && (
|
|
||||||
<Stack
|
|
||||||
style={{ backgroundColor: "#FFF8F0", padding: "2px 8px", minHeight: 32 }}
|
|
||||||
horizontal
|
|
||||||
verticalAlign="center"
|
|
||||||
>
|
|
||||||
{userContext.feedbackPolicies?.policyAllowFeedback && (
|
|
||||||
<Stack horizontal verticalAlign="center">
|
|
||||||
<Text style={{ fontWeight: 600, fontSize: 12 }}>Provide feedback on the query generated</Text>
|
|
||||||
{showCallout && !hideFeedbackModalForLikedQueries && (
|
|
||||||
<Callout
|
|
||||||
role="status"
|
|
||||||
style={{ padding: 8 }}
|
|
||||||
target="#likeBtn"
|
|
||||||
onDismiss={() => {
|
|
||||||
setShowCallout(false);
|
|
||||||
SubmitFeedback({
|
|
||||||
params: {
|
|
||||||
generatedQuery: generatedQuery,
|
|
||||||
likeQuery: likeQuery,
|
|
||||||
description: "",
|
|
||||||
userPrompt: userPrompt,
|
|
||||||
},
|
|
||||||
explorer,
|
|
||||||
databaseId,
|
|
||||||
containerId,
|
|
||||||
mode: isSampleCopilotActive ? "Sample" : "User",
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
directionalHint={DirectionalHint.topCenter}
|
|
||||||
>
|
|
||||||
<Text>
|
|
||||||
Thank you. Need to give{" "}
|
|
||||||
<Link
|
|
||||||
onClick={() => {
|
|
||||||
setShowCallout(false);
|
|
||||||
openFeedbackModal(generatedQuery, true, userPrompt);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
more feedback?
|
|
||||||
</Link>
|
|
||||||
</Text>
|
|
||||||
</Callout>
|
|
||||||
)}
|
|
||||||
<IconButton
|
|
||||||
id="likeBtn"
|
|
||||||
style={{ marginLeft: 20 }}
|
|
||||||
aria-label="Like"
|
|
||||||
role="toggle"
|
|
||||||
iconProps={{ iconName: likeQuery === true ? "LikeSolid" : "Like" }}
|
|
||||||
onClick={() => {
|
|
||||||
setShowCallout(!likeQuery);
|
|
||||||
setLikeQuery(!likeQuery);
|
|
||||||
if (likeQuery === true) {
|
|
||||||
document.getElementById("likeStatus").innerHTML = "Unpressed";
|
|
||||||
}
|
|
||||||
if (likeQuery === false) {
|
|
||||||
document.getElementById("likeStatus").innerHTML = "Liked";
|
|
||||||
}
|
|
||||||
if (dislikeQuery) {
|
|
||||||
setDislikeQuery(!dislikeQuery);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
style={{ margin: "0 10px" }}
|
|
||||||
role="toggle"
|
|
||||||
aria-label="Dislike"
|
|
||||||
iconProps={{ iconName: dislikeQuery === true ? "DislikeSolid" : "Dislike" }}
|
|
||||||
onClick={() => {
|
|
||||||
let toggleStatusValue = "Unpressed";
|
|
||||||
if (!dislikeQuery) {
|
|
||||||
openFeedbackModal(generatedQuery, false, userPrompt);
|
|
||||||
setLikeQuery(false);
|
|
||||||
toggleStatusValue = "Disliked";
|
|
||||||
}
|
|
||||||
setDislikeQuery(!dislikeQuery);
|
|
||||||
setShowCallout(false);
|
|
||||||
document.getElementById("likeStatus").innerHTML = toggleStatusValue;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span role="status" style={{ position: "absolute", left: "-9999px" }} id="likeStatus"></span>
|
|
||||||
<Separator vertical style={{ color: "#EDEBE9" }} />
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
<CommandBarButton
|
|
||||||
className="copyQuery"
|
|
||||||
onClick={copyGeneratedCode}
|
|
||||||
iconProps={{ iconName: "Copy" }}
|
|
||||||
style={{ margin: "0 10px", backgroundColor: "#FFF8F0", transition: "background-color 0.3s ease" }}
|
|
||||||
>
|
|
||||||
Copy query
|
|
||||||
</CommandBarButton>
|
|
||||||
<CommandBarButton
|
|
||||||
className="deleteQuery"
|
|
||||||
onClick={() => {
|
|
||||||
setShowDeletePopup(true);
|
|
||||||
}}
|
|
||||||
iconProps={{ iconName: "Delete" }}
|
|
||||||
style={{ margin: "0 10px", backgroundColor: "#FFF8F0", transition: "background-color 0.3s ease" }}
|
|
||||||
>
|
|
||||||
Delete query
|
|
||||||
</CommandBarButton>
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
{isSamplePromptsOpen && <SamplePrompts sampleProps={sampleProps} />}
|
{isSamplePromptsOpen && <SamplePrompts sampleProps={sampleProps} />}
|
||||||
{query !== "" && query.trim().length !== 0 && (
|
{query !== "" && query.trim().length !== 0 && (
|
||||||
<DeletePopup
|
<DeletePopup
|
||||||
|
@ -57,12 +57,12 @@ export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: Query
|
|||||||
|
|
||||||
const toggleCopilotButton = {
|
const toggleCopilotButton = {
|
||||||
iconSrc: QueryCommandIcon,
|
iconSrc: QueryCommandIcon,
|
||||||
iconAlt: "Copilot",
|
iconAlt: "Query Advisor",
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
toggleCopilot(true);
|
toggleCopilot(true);
|
||||||
},
|
},
|
||||||
commandButtonLabel: "Copilot",
|
commandButtonLabel: "Query Advisor",
|
||||||
ariaLabel: "Copilot",
|
ariaLabel: "Query Advisor",
|
||||||
hasPopup: false,
|
hasPopup: false,
|
||||||
disabled: copilotActive,
|
disabled: copilotActive,
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { FeedOptions } from "@azure/cosmos";
|
import { FeedOptions } from "@azure/cosmos";
|
||||||
import {
|
import {
|
||||||
Areas,
|
Areas,
|
||||||
|
BackendApi,
|
||||||
ConnectionStatusType,
|
ConnectionStatusType,
|
||||||
ContainerStatusType,
|
ContainerStatusType,
|
||||||
HttpStatusCodes,
|
HttpStatusCodes,
|
||||||
@ -30,6 +31,7 @@ import { Action } from "Shared/Telemetry/TelemetryConstants";
|
|||||||
import { traceFailure, traceStart, traceSuccess } from "Shared/Telemetry/TelemetryProcessor";
|
import { traceFailure, traceStart, traceSuccess } from "Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { getAuthorizationHeader } from "Utils/AuthorizationUtils";
|
import { getAuthorizationHeader } from "Utils/AuthorizationUtils";
|
||||||
|
import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils";
|
||||||
import { queryPagesUntilContentPresent } from "Utils/QueryUtils";
|
import { queryPagesUntilContentPresent } from "Utils/QueryUtils";
|
||||||
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import { useTabs } from "hooks/useTabs";
|
import { useTabs } from "hooks/useTabs";
|
||||||
@ -80,7 +82,11 @@ export const isCopilotFeatureRegistered = async (subscriptionId: string): Promis
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getCopilotEnabled = async (): Promise<boolean> => {
|
export const getCopilotEnabled = async (): Promise<boolean> => {
|
||||||
const url = `${configContext.BACKEND_ENDPOINT}/api/portalsettings/querycopilot`;
|
const backendEndpoint: string = useNewPortalBackendEndpoint(BackendApi.PortalSettings)
|
||||||
|
? configContext.PORTAL_BACKEND_ENDPOINT
|
||||||
|
: configContext.BACKEND_ENDPOINT;
|
||||||
|
|
||||||
|
const url = `${backendEndpoint}/api/portalsettings/querycopilot`;
|
||||||
const authorizationHeader: AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
const authorizationHeader: AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
||||||
const headers = { [authorizationHeader.header]: authorizationHeader.token };
|
const headers = { [authorizationHeader.header]: authorizationHeader.token };
|
||||||
|
|
||||||
|
@ -151,9 +151,9 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
{useQueryCopilot.getState().copilotEnabled && (
|
{useQueryCopilot.getState().copilotEnabled && (
|
||||||
<SplashScreenButton
|
<SplashScreenButton
|
||||||
imgSrc={CopilotIcon}
|
imgSrc={CopilotIcon}
|
||||||
title={"Query faster with Copilot"}
|
title={"Query faster with Query Advisor"}
|
||||||
description={
|
description={
|
||||||
"Copilot is your AI buddy that helps you write Azure Cosmos DB queries like a pro. Try it using our sample data set now!"
|
"Query Advisor is your AI buddy that helps you write Azure Cosmos DB queries like a pro. Try it using our sample data set now!"
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const copilotVersion = userContext.features.copilotVersion;
|
const copilotVersion = userContext.features.copilotVersion;
|
||||||
|
@ -3,6 +3,7 @@ import * as ko from "knockout";
|
|||||||
import Q from "q";
|
import Q from "q";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
|
import { CassandraProxyAPIs, CassandraProxyEndpoints } from "../../Common/Constants";
|
||||||
import { handleError } from "../../Common/ErrorHandlingUtils";
|
import { handleError } from "../../Common/ErrorHandlingUtils";
|
||||||
import * as HeadersUtility from "../../Common/HeadersUtility";
|
import * as HeadersUtility from "../../Common/HeadersUtility";
|
||||||
import { createDocument } from "../../Common/dataAccess/createDocument";
|
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||||
@ -171,8 +172,9 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
deferred.resolve(entity);
|
deferred.resolve(entity);
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
handleError(error, "AddRowCassandra", `Error while adding new row to table ${collection.id()}`);
|
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
||||||
deferred.reject(error);
|
handleError(errorText, "AddRowCassandra", `Error while adding new row to table ${collection.id()}`);
|
||||||
|
deferred.reject(errorText);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.finally(clearInProgressMessage);
|
.finally(clearInProgressMessage);
|
||||||
@ -261,6 +263,57 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
query: string,
|
query: string,
|
||||||
shouldNotify?: boolean,
|
shouldNotify?: boolean,
|
||||||
paginationToken?: string,
|
paginationToken?: string,
|
||||||
|
): Promise<Entities.IListTableEntitiesResult> {
|
||||||
|
if (!this.useCassandraProxyEndpoint("postQuery")) {
|
||||||
|
return this.queryDocuments_ToBeDeprecated(collection, query, shouldNotify, paginationToken);
|
||||||
|
}
|
||||||
|
const clearMessage =
|
||||||
|
shouldNotify && NotificationConsoleUtils.logConsoleProgress(`Querying rows for table ${collection.id()}`);
|
||||||
|
try {
|
||||||
|
const { authType, databaseAccount } = userContext;
|
||||||
|
|
||||||
|
const apiEndpoint: string =
|
||||||
|
authType === AuthType.EncryptedToken
|
||||||
|
? CassandraProxyAPIs.connectionStringQueryApi
|
||||||
|
: CassandraProxyAPIs.queryApi;
|
||||||
|
|
||||||
|
const data: any = await $.ajax(`${configContext.CASSANDRA_PROXY_ENDPOINT}/${apiEndpoint}`, {
|
||||||
|
type: "POST",
|
||||||
|
contentType: Constants.ContentType.applicationJson,
|
||||||
|
data: JSON.stringify({
|
||||||
|
accountName: databaseAccount?.name,
|
||||||
|
cassandraEndpoint: this.trimCassandraEndpoint(databaseAccount?.properties.cassandraEndpoint),
|
||||||
|
resourceId: databaseAccount?.id,
|
||||||
|
keyspaceId: collection.databaseId,
|
||||||
|
tableId: collection.id(),
|
||||||
|
query,
|
||||||
|
paginationToken,
|
||||||
|
}),
|
||||||
|
beforeSend: this.setAuthorizationHeader as any,
|
||||||
|
cache: false,
|
||||||
|
});
|
||||||
|
shouldNotify &&
|
||||||
|
NotificationConsoleUtils.logConsoleInfo(
|
||||||
|
`Successfully fetched ${data.result.length} rows for table ${collection.id()}`,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
Results: data.result,
|
||||||
|
ContinuationToken: data.paginationToken,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
shouldNotify &&
|
||||||
|
handleError(error, "QueryDocumentsCassandra", `Failed to query rows for table ${collection.id()}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage?.();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async queryDocuments_ToBeDeprecated(
|
||||||
|
collection: ViewModels.Collection,
|
||||||
|
query: string,
|
||||||
|
shouldNotify?: boolean,
|
||||||
|
paginationToken?: string,
|
||||||
): Promise<Entities.IListTableEntitiesResult> {
|
): Promise<Entities.IListTableEntitiesResult> {
|
||||||
const clearMessage =
|
const clearMessage =
|
||||||
shouldNotify && NotificationConsoleUtils.logConsoleProgress(`Querying rows for table ${collection.id()}`);
|
shouldNotify && NotificationConsoleUtils.logConsoleProgress(`Querying rows for table ${collection.id()}`);
|
||||||
@ -294,7 +347,11 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
shouldNotify &&
|
shouldNotify &&
|
||||||
handleError(error, "QueryDocumentsCassandra", `Failed to query rows for table ${collection.id()}`);
|
handleError(
|
||||||
|
error,
|
||||||
|
"QueryDocuments_ToBeDeprecated_Cassandra",
|
||||||
|
`Failed to query rows for table ${collection.id()}`,
|
||||||
|
);
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
clearMessage?.();
|
clearMessage?.();
|
||||||
@ -350,12 +407,13 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
|
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
||||||
handleError(
|
handleError(
|
||||||
error,
|
errorText,
|
||||||
"CreateKeyspaceCassandra",
|
"CreateKeyspaceCassandra",
|
||||||
`Error while creating a keyspace with query ${createKeyspaceQuery}`,
|
`Error while creating a keyspace with query ${createKeyspaceQuery}`,
|
||||||
);
|
);
|
||||||
deferred.reject(error);
|
deferred.reject(errorText);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.finally(clearInProgressMessage);
|
.finally(clearInProgressMessage);
|
||||||
@ -388,8 +446,13 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
handleError(error, "CreateTableCassandra", `Error while creating a table with query ${createTableQuery}`);
|
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
||||||
deferred.reject(error);
|
handleError(
|
||||||
|
errorText,
|
||||||
|
"CreateTableCassandra",
|
||||||
|
`Error while creating a table with query ${createTableQuery}`,
|
||||||
|
);
|
||||||
|
deferred.reject(errorText);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.finally(clearInProgressMessage);
|
.finally(clearInProgressMessage);
|
||||||
@ -402,6 +465,51 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getTableKeys(collection: ViewModels.Collection): Q.Promise<CassandraTableKeys> {
|
public getTableKeys(collection: ViewModels.Collection): Q.Promise<CassandraTableKeys> {
|
||||||
|
if (!this.useCassandraProxyEndpoint("getKeys")) {
|
||||||
|
return this.getTableKeys_ToBeDeprecated(collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!collection.cassandraKeys) {
|
||||||
|
return Q.resolve(collection.cassandraKeys);
|
||||||
|
}
|
||||||
|
const clearInProgressMessage = logConsoleProgress(`Fetching keys for table ${collection.id()}`);
|
||||||
|
const { authType, databaseAccount } = userContext;
|
||||||
|
const apiEndpoint: string =
|
||||||
|
authType === AuthType.EncryptedToken ? CassandraProxyAPIs.connectionStringKeysApi : CassandraProxyAPIs.keysApi;
|
||||||
|
|
||||||
|
let endpoint = `${configContext.CASSANDRA_PROXY_ENDPOINT}/${apiEndpoint}`;
|
||||||
|
const deferred = Q.defer<CassandraTableKeys>();
|
||||||
|
|
||||||
|
$.ajax(endpoint, {
|
||||||
|
type: "POST",
|
||||||
|
contentType: Constants.ContentType.applicationJson,
|
||||||
|
data: JSON.stringify({
|
||||||
|
accountName: databaseAccount?.name,
|
||||||
|
cassandraEndpoint: this.trimCassandraEndpoint(databaseAccount?.properties.cassandraEndpoint),
|
||||||
|
resourceId: databaseAccount?.id,
|
||||||
|
keyspaceId: collection.databaseId,
|
||||||
|
tableId: collection.id(),
|
||||||
|
}),
|
||||||
|
beforeSend: this.setAuthorizationHeader as any,
|
||||||
|
cache: false,
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
(data: CassandraTableKeys) => {
|
||||||
|
collection.cassandraKeys = data;
|
||||||
|
logConsoleInfo(`Successfully fetched keys for table ${collection.id()}`);
|
||||||
|
deferred.resolve(data);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
||||||
|
handleError(errorText, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
|
||||||
|
deferred.reject(errorText);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.done(clearInProgressMessage);
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTableKeys_ToBeDeprecated(collection: ViewModels.Collection): Q.Promise<CassandraTableKeys> {
|
||||||
if (!!collection.cassandraKeys) {
|
if (!!collection.cassandraKeys) {
|
||||||
return Q.resolve(collection.cassandraKeys);
|
return Q.resolve(collection.cassandraKeys);
|
||||||
}
|
}
|
||||||
@ -433,8 +541,9 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
deferred.resolve(data);
|
deferred.resolve(data);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
handleError(error, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
|
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
||||||
deferred.reject(error);
|
handleError(errorText, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
|
||||||
|
deferred.reject(errorText);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.done(clearInProgressMessage);
|
.done(clearInProgressMessage);
|
||||||
@ -442,6 +551,52 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getTableSchema(collection: ViewModels.Collection): Q.Promise<CassandraTableKey[]> {
|
public getTableSchema(collection: ViewModels.Collection): Q.Promise<CassandraTableKey[]> {
|
||||||
|
if (!this.useCassandraProxyEndpoint("getSchema")) {
|
||||||
|
return this.getTableSchema_ToBeDeprecated(collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!collection.cassandraSchema) {
|
||||||
|
return Q.resolve(collection.cassandraSchema);
|
||||||
|
}
|
||||||
|
const clearInProgressMessage = logConsoleProgress(`Fetching schema for table ${collection.id()}`);
|
||||||
|
const { databaseAccount, authType } = userContext;
|
||||||
|
const apiEndpoint: string =
|
||||||
|
authType === AuthType.EncryptedToken
|
||||||
|
? CassandraProxyAPIs.connectionStringSchemaApi
|
||||||
|
: CassandraProxyAPIs.schemaApi;
|
||||||
|
let endpoint = `${configContext.CASSANDRA_PROXY_ENDPOINT}/${apiEndpoint}`;
|
||||||
|
const deferred = Q.defer<CassandraTableKey[]>();
|
||||||
|
|
||||||
|
$.ajax(endpoint, {
|
||||||
|
type: "POST",
|
||||||
|
contentType: Constants.ContentType.applicationJson,
|
||||||
|
data: JSON.stringify({
|
||||||
|
accountName: databaseAccount?.name,
|
||||||
|
cassandraEndpoint: this.trimCassandraEndpoint(databaseAccount?.properties.cassandraEndpoint),
|
||||||
|
resourceId: databaseAccount?.id,
|
||||||
|
keyspaceId: collection.databaseId,
|
||||||
|
tableId: collection.id(),
|
||||||
|
}),
|
||||||
|
beforeSend: this.setAuthorizationHeader as any,
|
||||||
|
cache: false,
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
(data: any) => {
|
||||||
|
collection.cassandraSchema = data.columns;
|
||||||
|
logConsoleInfo(`Successfully fetched schema for table ${collection.id()}`);
|
||||||
|
deferred.resolve(data.columns);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
||||||
|
handleError(errorText, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
|
||||||
|
deferred.reject(errorText);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.done(clearInProgressMessage);
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTableSchema_ToBeDeprecated(collection: ViewModels.Collection): Q.Promise<CassandraTableKey[]> {
|
||||||
if (!!collection.cassandraSchema) {
|
if (!!collection.cassandraSchema) {
|
||||||
return Q.resolve(collection.cassandraSchema);
|
return Q.resolve(collection.cassandraSchema);
|
||||||
}
|
}
|
||||||
@ -473,8 +628,9 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
deferred.resolve(data.columns);
|
deferred.resolve(data.columns);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
handleError(error, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
|
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
||||||
deferred.reject(error);
|
handleError(errorText, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
|
||||||
|
deferred.reject(errorText);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.done(clearInProgressMessage);
|
.done(clearInProgressMessage);
|
||||||
@ -482,6 +638,44 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private createOrDeleteQuery(cassandraEndpoint: string, resourceId: string, query: string): Q.Promise<any> {
|
private createOrDeleteQuery(cassandraEndpoint: string, resourceId: string, query: string): Q.Promise<any> {
|
||||||
|
if (!this.useCassandraProxyEndpoint("createOrDelete")) {
|
||||||
|
return this.createOrDeleteQuery_ToBeDeprecated(cassandraEndpoint, resourceId, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
const deferred = Q.defer();
|
||||||
|
const { authType, databaseAccount } = userContext;
|
||||||
|
const apiEndpoint: string =
|
||||||
|
authType === AuthType.EncryptedToken
|
||||||
|
? CassandraProxyAPIs.connectionStringCreateOrDeleteApi
|
||||||
|
: CassandraProxyAPIs.createOrDeleteApi;
|
||||||
|
|
||||||
|
$.ajax(`${configContext.CASSANDRA_PROXY_ENDPOINT}/${apiEndpoint}`, {
|
||||||
|
type: "POST",
|
||||||
|
contentType: Constants.ContentType.applicationJson,
|
||||||
|
data: JSON.stringify({
|
||||||
|
accountName: databaseAccount?.name,
|
||||||
|
cassandraEndpoint: this.trimCassandraEndpoint(cassandraEndpoint),
|
||||||
|
resourceId: resourceId,
|
||||||
|
query: query,
|
||||||
|
}),
|
||||||
|
beforeSend: this.setAuthorizationHeader as any,
|
||||||
|
cache: false,
|
||||||
|
}).then(
|
||||||
|
(data: any) => {
|
||||||
|
deferred.resolve();
|
||||||
|
},
|
||||||
|
(reason) => {
|
||||||
|
deferred.reject(reason);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createOrDeleteQuery_ToBeDeprecated(
|
||||||
|
cassandraEndpoint: string,
|
||||||
|
resourceId: string,
|
||||||
|
query: string,
|
||||||
|
): Q.Promise<any> {
|
||||||
const deferred = Q.defer();
|
const deferred = Q.defer();
|
||||||
const { authType, databaseAccount } = userContext;
|
const { authType, databaseAccount } = userContext;
|
||||||
const apiEndpoint: string =
|
const apiEndpoint: string =
|
||||||
@ -547,4 +741,25 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
private getCassandraPartitionKeyProperty(collection: ViewModels.Collection): string {
|
private getCassandraPartitionKeyProperty(collection: ViewModels.Collection): string {
|
||||||
return collection.cassandraKeys.partitionKeys[0].property;
|
return collection.cassandraKeys.partitionKeys[0].property;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private useCassandraProxyEndpoint(api: string): boolean {
|
||||||
|
const activeCassandraProxyEndpoints: string[] = [
|
||||||
|
CassandraProxyEndpoints.Development,
|
||||||
|
CassandraProxyEndpoints.Mpac,
|
||||||
|
CassandraProxyEndpoints.Prod,
|
||||||
|
];
|
||||||
|
let canAccessCassandraProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
|
||||||
|
if (
|
||||||
|
configContext.CASSANDRA_PROXY_ENDPOINT !== CassandraProxyEndpoints.Development &&
|
||||||
|
userContext.databaseAccount.properties.ipRules?.length > 0
|
||||||
|
) {
|
||||||
|
canAccessCassandraProxy = canAccessCassandraProxy && configContext.CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
canAccessCassandraProxy &&
|
||||||
|
configContext.NEW_CASSANDRA_APIS?.includes(api) &&
|
||||||
|
activeCassandraProxyEndpoints.includes(configContext.CASSANDRA_PROXY_ENDPOINT)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { ItemDefinition, PartitionKey, PartitionKeyDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
import { ItemDefinition, PartitionKey, PartitionKeyDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||||
import { Platform, configContext } from "ConfigContext";
|
import { Platform, configContext } from "ConfigContext";
|
||||||
import { querySampleDocuments, readSampleDocument } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
import { querySampleDocuments, readSampleDocument } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||||
|
import { KeyboardAction } from "KeyboardShortcuts";
|
||||||
import { QueryConstants } from "Shared/Constants";
|
import { QueryConstants } from "Shared/Constants";
|
||||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
@ -462,7 +463,22 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
|
|
||||||
private initializeNewDocument = (): void => {
|
private initializeNewDocument = (): void => {
|
||||||
this.selectedDocumentId(null);
|
this.selectedDocumentId(null);
|
||||||
const defaultDocument: string = this.renderObjectForEditor({ id: "replace_with_new_document_id" }, null, 4);
|
const newDocument: any = {
|
||||||
|
id: "replace_with_new_document_id",
|
||||||
|
};
|
||||||
|
this.partitionKeyProperties.forEach((partitionKeyProperty) => {
|
||||||
|
let target = newDocument;
|
||||||
|
const keySegments = partitionKeyProperty.split(".");
|
||||||
|
const finalSegment = keySegments.pop();
|
||||||
|
|
||||||
|
// Initialize nested objects as needed
|
||||||
|
keySegments.forEach((segment) => {
|
||||||
|
target = target[segment] = target[segment] || {};
|
||||||
|
});
|
||||||
|
|
||||||
|
target[finalSegment] = "replace_with_new_partition_key_value";
|
||||||
|
});
|
||||||
|
const defaultDocument: string = this.renderObjectForEditor(newDocument, null, 4);
|
||||||
this.initialDocumentContent(defaultDocument);
|
this.initialDocumentContent(defaultDocument);
|
||||||
this.selectedDocumentContent.setBaseline(defaultDocument);
|
this.selectedDocumentContent.setBaseline(defaultDocument);
|
||||||
this.editorState(ViewModels.DocumentExplorerState.newDocumentValid);
|
this.editorState(ViewModels.DocumentExplorerState.newDocumentValid);
|
||||||
@ -893,6 +909,7 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
buttons.push({
|
buttons.push({
|
||||||
iconSrc: NewDocumentIcon,
|
iconSrc: NewDocumentIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.NEW_ITEM,
|
||||||
onCommandClick: this.onNewDocumentClick,
|
onCommandClick: this.onNewDocumentClick,
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
@ -907,6 +924,7 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
buttons.push({
|
buttons.push({
|
||||||
iconSrc: SaveIcon,
|
iconSrc: SaveIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.SAVE_ITEM,
|
||||||
onCommandClick: this.onSaveNewDocumentClick,
|
onCommandClick: this.onSaveNewDocumentClick,
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
@ -921,6 +939,7 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
buttons.push({
|
buttons.push({
|
||||||
iconSrc: DiscardIcon,
|
iconSrc: DiscardIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.CANCEL_OR_DISCARD,
|
||||||
onCommandClick: this.onRevertNewDocumentClick,
|
onCommandClick: this.onRevertNewDocumentClick,
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
@ -936,6 +955,7 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
buttons.push({
|
buttons.push({
|
||||||
iconSrc: SaveIcon,
|
iconSrc: SaveIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.SAVE_ITEM,
|
||||||
onCommandClick: this.onSaveExistingDocumentClick,
|
onCommandClick: this.onSaveExistingDocumentClick,
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
@ -950,6 +970,7 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
buttons.push({
|
buttons.push({
|
||||||
iconSrc: DiscardIcon,
|
iconSrc: DiscardIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.CANCEL_OR_DISCARD,
|
||||||
onCommandClick: this.onRevertExisitingDocumentClick,
|
onCommandClick: this.onRevertExisitingDocumentClick,
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
@ -965,6 +986,7 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
buttons.push({
|
buttons.push({
|
||||||
iconSrc: DeleteDocumentIcon,
|
iconSrc: DeleteDocumentIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.DELETE_ITEM,
|
||||||
onCommandClick: this.onDeleteExisitingDocumentClick,
|
onCommandClick: this.onDeleteExisitingDocumentClick,
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { useMongoProxyEndpoint } from "Common/MongoProxyClient";
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import { configContext } from "../../../ConfigContext";
|
import { configContext } from "../../../ConfigContext";
|
||||||
@ -9,7 +10,6 @@ import { isInvalidParentFrameOrigin, isReadyMessage } from "../../../Utils/Messa
|
|||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
|
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import TabsBase from "../TabsBase";
|
import TabsBase from "../TabsBase";
|
||||||
import { getMongoShellOrigin } from "./getMongoShellOrigin";
|
|
||||||
import { getMongoShellUrl } from "./getMongoShellUrl";
|
import { getMongoShellUrl } from "./getMongoShellUrl";
|
||||||
|
|
||||||
//eslint-disable-next-line
|
//eslint-disable-next-line
|
||||||
@ -50,13 +50,15 @@ export default class MongoShellTabComponent extends Component<
|
|||||||
IMongoShellTabComponentStates
|
IMongoShellTabComponentStates
|
||||||
> {
|
> {
|
||||||
private _logTraces: Map<string, number>;
|
private _logTraces: Map<string, number>;
|
||||||
|
private _useMongoProxyEndpoint: boolean;
|
||||||
|
|
||||||
constructor(props: IMongoShellTabComponentProps) {
|
constructor(props: IMongoShellTabComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this._logTraces = new Map();
|
this._logTraces = new Map();
|
||||||
|
this._useMongoProxyEndpoint = useMongoProxyEndpoint("legacyMongoShell");
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
url: getMongoShellUrl(),
|
url: getMongoShellUrl(this._useMongoProxyEndpoint),
|
||||||
};
|
};
|
||||||
|
|
||||||
props.onMongoShellTabAccessor({
|
props.onMongoShellTabAccessor({
|
||||||
@ -119,9 +121,10 @@ export default class MongoShellTabComponent extends Component<
|
|||||||
) + Constants.MongoDBAccounts.defaultPort.toString();
|
) + Constants.MongoDBAccounts.defaultPort.toString();
|
||||||
const databaseId = this.props.collection.databaseId;
|
const databaseId = this.props.collection.databaseId;
|
||||||
const collectionId = this.props.collection.id();
|
const collectionId = this.props.collection.id();
|
||||||
const apiEndpoint = configContext.BACKEND_ENDPOINT;
|
const apiEndpoint = this._useMongoProxyEndpoint
|
||||||
|
? configContext.MONGO_PROXY_ENDPOINT
|
||||||
|
: configContext.BACKEND_ENDPOINT;
|
||||||
const encryptedAuthToken: string = userContext.accessToken;
|
const encryptedAuthToken: string = userContext.accessToken;
|
||||||
const targetOrigin = getMongoShellOrigin();
|
|
||||||
|
|
||||||
shellIframe.contentWindow.postMessage(
|
shellIframe.contentWindow.postMessage(
|
||||||
{
|
{
|
||||||
@ -137,7 +140,7 @@ export default class MongoShellTabComponent extends Component<
|
|||||||
apiEndpoint: apiEndpoint,
|
apiEndpoint: apiEndpoint,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
targetOrigin,
|
window.origin,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,86 +0,0 @@
|
|||||||
import { extractFeatures } from "Platform/Hosted/extractFeatures";
|
|
||||||
import { configContext } from "../../../ConfigContext";
|
|
||||||
import { updateUserContext } from "../../../UserContext";
|
|
||||||
import { getMongoShellOrigin } from "./getMongoShellOrigin";
|
|
||||||
|
|
||||||
describe("getMongoShellOrigin", () => {
|
|
||||||
(window as { origin: string }).origin = "window_origin";
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
updateUserContext({
|
|
||||||
features: extractFeatures(
|
|
||||||
new URLSearchParams({
|
|
||||||
"feature.enableLegacyMongoShellV1": "false",
|
|
||||||
"feature.enableLegacyMongoShellV2": "false",
|
|
||||||
"feature.enableLegacyMongoShellV1Debug": "false",
|
|
||||||
"feature.enableLegacyMongoShellV2Debug": "false",
|
|
||||||
"feature.loadLegacyMongoShellFromBE": "false",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return by default", () => {
|
|
||||||
expect(getMongoShellOrigin()).toBe(window.origin);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return window.origin when enableLegacyMongoShellV1", () => {
|
|
||||||
updateUserContext({
|
|
||||||
features: extractFeatures(
|
|
||||||
new URLSearchParams({
|
|
||||||
"feature.enableLegacyMongoShellV1": "true",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(getMongoShellOrigin()).toBe(window.origin);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return window.origin when enableLegacyMongoShellV2===true", () => {
|
|
||||||
updateUserContext({
|
|
||||||
features: extractFeatures(
|
|
||||||
new URLSearchParams({
|
|
||||||
"feature.enableLegacyMongoShellV2": "true",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(getMongoShellOrigin()).toBe(window.origin);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return window.origin when enableLegacyMongoShellV1Debug===true", () => {
|
|
||||||
updateUserContext({
|
|
||||||
features: extractFeatures(
|
|
||||||
new URLSearchParams({
|
|
||||||
"feature.enableLegacyMongoShellV1Debug": "true",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(getMongoShellOrigin()).toBe(window.origin);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return window.origin when enableLegacyMongoShellV2Debug===true", () => {
|
|
||||||
updateUserContext({
|
|
||||||
features: extractFeatures(
|
|
||||||
new URLSearchParams({
|
|
||||||
"feature.enableLegacyMongoShellV2Debug": "true",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(getMongoShellOrigin()).toBe(window.origin);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return BACKEND_ENDPOINT when loadLegacyMongoShellFromBE===true", () => {
|
|
||||||
updateUserContext({
|
|
||||||
features: extractFeatures(
|
|
||||||
new URLSearchParams({
|
|
||||||
"feature.loadLegacyMongoShellFromBE": "true",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(getMongoShellOrigin()).toBe(configContext.BACKEND_ENDPOINT);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,10 +0,0 @@
|
|||||||
import { configContext } from "../../../ConfigContext";
|
|
||||||
import { userContext } from "../../../UserContext";
|
|
||||||
|
|
||||||
export function getMongoShellOrigin(): string {
|
|
||||||
if (userContext.features.loadLegacyMongoShellFromBE === true) {
|
|
||||||
return configContext.BACKEND_ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
return window.origin;
|
|
||||||
}
|
|
@ -1,9 +1,9 @@
|
|||||||
import { extractFeatures } from "Platform/Hosted/extractFeatures";
|
import { Platform, resetConfigContext, updateConfigContext } from "../../../ConfigContext";
|
||||||
import { Platform, configContext, resetConfigContext, updateConfigContext } from "../../../ConfigContext";
|
|
||||||
import { updateUserContext, userContext } from "../../../UserContext";
|
import { updateUserContext, userContext } from "../../../UserContext";
|
||||||
import { getExtensionEndpoint, getMongoShellUrl } from "./getMongoShellUrl";
|
import { getMongoShellUrl } from "./getMongoShellUrl";
|
||||||
|
|
||||||
const mongoBackendEndpoint = "https://localhost:1234";
|
const mongoBackendEndpoint = "https://localhost:1234";
|
||||||
|
const hostedExplorerURL = "https://cosmos.azure.com/";
|
||||||
|
|
||||||
describe("getMongoShellUrl", () => {
|
describe("getMongoShellUrl", () => {
|
||||||
let queryString = "";
|
let queryString = "";
|
||||||
@ -13,6 +13,7 @@ describe("getMongoShellUrl", () => {
|
|||||||
|
|
||||||
updateConfigContext({
|
updateConfigContext({
|
||||||
BACKEND_ENDPOINT: mongoBackendEndpoint,
|
BACKEND_ENDPOINT: mongoBackendEndpoint,
|
||||||
|
hostedExplorerURL: hostedExplorerURL,
|
||||||
platform: Platform.Hosted,
|
platform: Platform.Hosted,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -32,175 +33,18 @@ describe("getMongoShellUrl", () => {
|
|||||||
cassandraEndpoint: "fakeCassandraEndpoint",
|
cassandraEndpoint: "fakeCassandraEndpoint",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
features: extractFeatures(
|
|
||||||
new URLSearchParams({
|
|
||||||
"feature.enableLegacyMongoShellV1": "false",
|
|
||||||
"feature.enableLegacyMongoShellV2": "false",
|
|
||||||
"feature.enableLegacyMongoShellV1Debug": "false",
|
|
||||||
"feature.enableLegacyMongoShellV2Debug": "false",
|
|
||||||
"feature.loadLegacyMongoShellFromBE": "false",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
portalEnv: "prod",
|
portalEnv: "prod",
|
||||||
});
|
});
|
||||||
|
|
||||||
queryString = `resourceId=${userContext.databaseAccount.id}&accountName=${userContext.databaseAccount.name}&mongoEndpoint=${userContext.databaseAccount.properties.documentEndpoint}`;
|
queryString = `resourceId=${userContext.databaseAccount.id}&accountName=${userContext.databaseAccount.name}&mongoEndpoint=${userContext.databaseAccount.properties.documentEndpoint}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return /mongoshell/indexv2.html by default", () => {
|
it("should return /indexv2.html by default", () => {
|
||||||
expect(getMongoShellUrl()).toBe(`/mongoshell/indexv2.html?${queryString}`);
|
expect(getMongoShellUrl().toString()).toContain(`/indexv2.html?${queryString}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return /mongoshell/indexv2.html when portalEnv==localhost", () => {
|
it("should return /index.html when useMongoProxyEndpoint is true", () => {
|
||||||
updateUserContext({
|
const useMongoProxyEndpoint: boolean = true;
|
||||||
portalEnv: "localhost",
|
expect(getMongoShellUrl(useMongoProxyEndpoint).toString()).toContain(`/index.html?${queryString}`);
|
||||||
});
|
|
||||||
|
|
||||||
expect(getMongoShellUrl()).toBe(`/mongoshell/indexv2.html?${queryString}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return /mongoshell/index.html when enableLegacyMongoShellV1===true", () => {
|
|
||||||
updateUserContext({
|
|
||||||
features: extractFeatures(
|
|
||||||
new URLSearchParams({
|
|
||||||
"feature.enableLegacyMongoShellV1": "true",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(getMongoShellUrl()).toBe(`/mongoshell/index.html?${queryString}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return /mongoshell/index.html when enableLegacyMongoShellV2===true", () => {
|
|
||||||
updateUserContext({
|
|
||||||
features: extractFeatures(
|
|
||||||
new URLSearchParams({
|
|
||||||
"feature.enableLegacyMongoShellV2": "true",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(getMongoShellUrl()).toBe(`/mongoshell/indexv2.html?${queryString}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return /mongoshell/index.html when enableLegacyMongoShellV1Debug===true", () => {
|
|
||||||
updateUserContext({
|
|
||||||
features: extractFeatures(
|
|
||||||
new URLSearchParams({
|
|
||||||
"feature.enableLegacyMongoShellV1Debug": "true",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(getMongoShellUrl()).toBe(`/mongoshell/debug/index.html?${queryString}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return /mongoshell/index.html when enableLegacyMongoShellV2Debug===true", () => {
|
|
||||||
updateUserContext({
|
|
||||||
features: extractFeatures(
|
|
||||||
new URLSearchParams({
|
|
||||||
"feature.enableLegacyMongoShellV2Debug": "true",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(getMongoShellUrl()).toBe(`/mongoshell/debug/indexv2.html?${queryString}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("loadLegacyMongoShellFromBE===true", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
resetConfigContext();
|
|
||||||
updateConfigContext({
|
|
||||||
BACKEND_ENDPOINT: mongoBackendEndpoint,
|
|
||||||
platform: Platform.Hosted,
|
|
||||||
});
|
|
||||||
|
|
||||||
updateUserContext({
|
|
||||||
features: extractFeatures(
|
|
||||||
new URLSearchParams({
|
|
||||||
"feature.loadLegacyMongoShellFromBE": "true",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return /mongoshell/index.html", () => {
|
|
||||||
const endpoint = getExtensionEndpoint(configContext.platform, configContext.BACKEND_ENDPOINT);
|
|
||||||
expect(getMongoShellUrl()).toBe(`${endpoint}/content/mongoshell/debug/index.html?${queryString}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("configContext.platform !== Platform.Hosted, should return /mongoshell/indexv2.html", () => {
|
|
||||||
updateConfigContext({
|
|
||||||
platform: Platform.Portal,
|
|
||||||
});
|
|
||||||
|
|
||||||
const endpoint = getExtensionEndpoint(configContext.platform, configContext.BACKEND_ENDPOINT);
|
|
||||||
expect(getMongoShellUrl()).toBe(`${endpoint}/content/mongoshell/debug/index.html?${queryString}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("configContext.BACKEND_ENDPOINT !== '' and configContext.platform !== Platform.Hosted, should return /mongoshell/indexv2.html", () => {
|
|
||||||
resetConfigContext();
|
|
||||||
updateConfigContext({
|
|
||||||
platform: Platform.Portal,
|
|
||||||
BACKEND_ENDPOINT: mongoBackendEndpoint,
|
|
||||||
});
|
|
||||||
|
|
||||||
const endpoint = getExtensionEndpoint(configContext.platform, configContext.BACKEND_ENDPOINT);
|
|
||||||
expect(getMongoShellUrl()).toBe(`${endpoint}/content/mongoshell/debug/index.html?${queryString}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("configContext.BACKEND_ENDPOINT === '' and configContext.platform === Platform.Hosted, should return /mongoshell/indexv2.html", () => {
|
|
||||||
resetConfigContext();
|
|
||||||
updateConfigContext({
|
|
||||||
platform: Platform.Hosted,
|
|
||||||
});
|
|
||||||
|
|
||||||
const endpoint = getExtensionEndpoint(configContext.platform, configContext.BACKEND_ENDPOINT);
|
|
||||||
expect(getMongoShellUrl()).toBe(`${endpoint}/content/mongoshell/debug/index.html?${queryString}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("configContext.BACKEND_ENDPOINT === '' and configContext.platform !== Platform.Hosted, should return /mongoshell/indexv2.html", () => {
|
|
||||||
resetConfigContext();
|
|
||||||
updateConfigContext({
|
|
||||||
platform: Platform.Portal,
|
|
||||||
});
|
|
||||||
|
|
||||||
const endpoint = getExtensionEndpoint(configContext.platform, configContext.BACKEND_ENDPOINT);
|
|
||||||
expect(getMongoShellUrl()).toBe(`${endpoint}/content/mongoshell/debug/index.html?${queryString}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("getExtensionEndpoint", () => {
|
|
||||||
it("when platform === Platform.Hosted, backendEndpoint is undefined", () => {
|
|
||||||
expect(getExtensionEndpoint(Platform.Hosted, undefined)).toBe("");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when platform === Platform.Hosted, backendEndpoint === ''", () => {
|
|
||||||
expect(getExtensionEndpoint(Platform.Hosted, "")).toBe("");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when platform === Platform.Hosted, backendEndpoint === null", () => {
|
|
||||||
expect(getExtensionEndpoint(Platform.Hosted, null)).toBe("");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when platform === Platform.Hosted, backendEndpoint != ''", () => {
|
|
||||||
expect(getExtensionEndpoint(Platform.Hosted, "foo")).toBe("foo");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when platform === Platform.Portal, backendEndpoint is udefined", () => {
|
|
||||||
expect(getExtensionEndpoint(Platform.Portal, undefined)).toBe("");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when platform === Platform.Portal, backendEndpoint === ''", () => {
|
|
||||||
expect(getExtensionEndpoint(Platform.Portal, "")).toBe("");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when platform === Platform.Portal, backendEndpoint === null", () => {
|
|
||||||
expect(getExtensionEndpoint(Platform.Portal, null)).toBe("");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when platform !== Platform.Portal, backendEndpoint != ''", () => {
|
|
||||||
expect(getExtensionEndpoint(Platform.Portal, "foo")).toBe("foo");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,45 +1,11 @@
|
|||||||
import { configContext, Platform } from "../../../ConfigContext";
|
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
|
|
||||||
export function getMongoShellUrl(): string {
|
export function getMongoShellUrl(useMongoProxyEndpoint?: boolean): string {
|
||||||
const { databaseAccount: account } = userContext;
|
const { databaseAccount: account } = userContext;
|
||||||
const resourceId = account?.id;
|
const resourceId = account?.id;
|
||||||
const accountName = account?.name;
|
const accountName = account?.name;
|
||||||
const mongoEndpoint = account?.properties?.mongoEndpoint || account?.properties?.documentEndpoint;
|
const mongoEndpoint = account?.properties?.mongoEndpoint || account?.properties?.documentEndpoint;
|
||||||
const queryString = `resourceId=${resourceId}&accountName=${accountName}&mongoEndpoint=${mongoEndpoint}`;
|
const queryString = `resourceId=${resourceId}&accountName=${accountName}&mongoEndpoint=${mongoEndpoint}`;
|
||||||
|
|
||||||
if (userContext.features.enableLegacyMongoShellV1 === true) {
|
return useMongoProxyEndpoint ? `/mongoshell/index.html?${queryString}` : `/mongoshell/indexv2.html?${queryString}`;
|
||||||
return `/mongoshell/index.html?${queryString}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userContext.features.enableLegacyMongoShellV1Debug === true) {
|
|
||||||
return `/mongoshell/debug/index.html?${queryString}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userContext.features.enableLegacyMongoShellV2 === true) {
|
|
||||||
return `/mongoshell/indexv2.html?${queryString}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userContext.features.enableLegacyMongoShellV2Debug === true) {
|
|
||||||
return `/mongoshell/debug/indexv2.html?${queryString}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userContext.portalEnv === "localhost") {
|
|
||||||
return `/mongoshell/indexv2.html?${queryString}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userContext.features.loadLegacyMongoShellFromBE === true) {
|
|
||||||
const extensionEndpoint: string = getExtensionEndpoint(configContext.platform, configContext.BACKEND_ENDPOINT);
|
|
||||||
return `${extensionEndpoint}/content/mongoshell/debug/index.html?${queryString}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `/mongoshell/indexv2.html?${queryString}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getExtensionEndpoint(platform: string, backendEndpoint: string): string {
|
|
||||||
const runtimeEndpoint = platform === Platform.Hosted ? backendEndpoint : "";
|
|
||||||
|
|
||||||
const extensionEndpoint: string = backendEndpoint || runtimeEndpoint || "";
|
|
||||||
|
|
||||||
return extensionEndpoint;
|
|
||||||
}
|
}
|
||||||
|
@ -381,9 +381,13 @@ export const QueryResultSection: React.FC<QueryResultProps> = ({
|
|||||||
<img className="paneErrorIcon" src={InfoColor} alt="Error" />
|
<img className="paneErrorIcon" src={InfoColor} alt="Error" />
|
||||||
</span>
|
</span>
|
||||||
<span className="warningErrorDetailsLinkContainer">
|
<span className="warningErrorDetailsLinkContainer">
|
||||||
We have detected you may be using a subquery. Non-correlated subqueries are not currently supported.
|
We detected you may be using a subquery. To learn more about subqueries effectively,{" "}
|
||||||
<a href="https://docs.microsoft.com/en-us/azure/cosmos-db/sql-query-subquery">
|
<a
|
||||||
Please see Cosmos sub query documentation for further information
|
href="https://learn.microsoft.com/azure/cosmos-db/nosql/query/subquery"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
visit the documentation
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { sendMessage } from "Common/MessageHandler";
|
||||||
|
import { MessageTypes } from "Contracts/MessageTypes";
|
||||||
import { CopilotProvider } from "Explorer/QueryCopilot/QueryCopilotContext";
|
import { CopilotProvider } from "Explorer/QueryCopilot/QueryCopilotContext";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
@ -54,6 +56,11 @@ export class NewQueryTab extends TabsBase {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onActivate(): void {
|
||||||
|
this.propagateTabInformation(MessageTypes.ActivateTab);
|
||||||
|
super.onActivate();
|
||||||
|
}
|
||||||
|
|
||||||
public onTabClick(): void {
|
public onTabClick(): void {
|
||||||
useTabs.getState().activateTab(this);
|
useTabs.getState().activateTab(this);
|
||||||
this.iTabAccessor.onTabClickEvent();
|
this.iTabAccessor.onTabClickEvent();
|
||||||
@ -61,6 +68,7 @@ export class NewQueryTab extends TabsBase {
|
|||||||
|
|
||||||
public onCloseTabButtonClick(): void {
|
public onCloseTabButtonClick(): void {
|
||||||
useTabs.getState().closeTab(this);
|
useTabs.getState().closeTab(this);
|
||||||
|
this.propagateTabInformation(MessageTypes.CloseTab);
|
||||||
if (this.iTabAccessor) {
|
if (this.iTabAccessor) {
|
||||||
this.iTabAccessor.onCloseClickEvent(true);
|
this.iTabAccessor.onCloseClickEvent(true);
|
||||||
}
|
}
|
||||||
@ -69,4 +77,15 @@ export class NewQueryTab extends TabsBase {
|
|||||||
public getContainer(): Explorer {
|
public getContainer(): Explorer {
|
||||||
return this.props.container;
|
return this.props.container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private propagateTabInformation(type: MessageTypes): void {
|
||||||
|
sendMessage({
|
||||||
|
type,
|
||||||
|
data: {
|
||||||
|
kind: this.tabKind,
|
||||||
|
databaseId: this.collection?.databaseId,
|
||||||
|
collectionId: this.collection?.id?.(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import { OnExecuteQueryClick, QueryDocumentsPerPage } from "Explorer/QueryCopilo
|
|||||||
import { QueryCopilotSidebar } from "Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar";
|
import { QueryCopilotSidebar } from "Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar";
|
||||||
import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection";
|
import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection";
|
||||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
|
import { KeyboardAction } from "KeyboardShortcuts";
|
||||||
import { QueryConstants } from "Shared/Constants";
|
import { QueryConstants } from "Shared/Constants";
|
||||||
import { LocalStorageUtility, StorageKey, getRUThreshold, ruThresholdEnabled } from "Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey, getRUThreshold, ruThresholdEnabled } from "Shared/StorageUtility";
|
||||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||||
@ -21,6 +22,7 @@ import "react-splitter-layout/lib/index.css";
|
|||||||
import { format } from "react-string-format";
|
import { format } from "react-string-format";
|
||||||
import QueryCommandIcon from "../../../../images/CopilotCommand.svg";
|
import QueryCommandIcon from "../../../../images/CopilotCommand.svg";
|
||||||
import LaunchCopilot from "../../../../images/CopilotTabIcon.svg";
|
import LaunchCopilot from "../../../../images/CopilotTabIcon.svg";
|
||||||
|
import DownloadQueryIcon from "../../../../images/DownloadQuery.svg";
|
||||||
import CancelQueryIcon from "../../../../images/Entity_cancel.svg";
|
import CancelQueryIcon from "../../../../images/Entity_cancel.svg";
|
||||||
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
|
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
|
||||||
import SaveQueryIcon from "../../../../images/save-cosmos.svg";
|
import SaveQueryIcon from "../../../../images/save-cosmos.svg";
|
||||||
@ -134,7 +136,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
toggleState: ToggleState.Result,
|
toggleState: ToggleState.Result,
|
||||||
sqlQueryEditorContent: props.queryText || "SELECT * FROM c",
|
sqlQueryEditorContent: props.isPreferredApiMongoDB ? "{}" : props.queryText || "SELECT * FROM c",
|
||||||
selectedContent: "",
|
selectedContent: "",
|
||||||
queryResults: undefined,
|
queryResults: undefined,
|
||||||
error: "",
|
error: "",
|
||||||
@ -224,6 +226,20 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public onDownloadQueryClick = (): void => {
|
||||||
|
const text = this.getCurrentEditorQuery();
|
||||||
|
const queryFile = new File([text], `SavedQuery.txt`, { type: "text/plain" });
|
||||||
|
|
||||||
|
// It appears the most consistent to download a file from a blob is to create an anchor element and simulate clicking it
|
||||||
|
const blobUrl = URL.createObjectURL(queryFile);
|
||||||
|
const anchor = document.createElement("a");
|
||||||
|
anchor.href = blobUrl;
|
||||||
|
anchor.download = queryFile.name;
|
||||||
|
document.body.appendChild(anchor); // Must put the anchor in the document.
|
||||||
|
anchor.click();
|
||||||
|
document.body.removeChild(anchor); // Clean up the anchor.
|
||||||
|
};
|
||||||
|
|
||||||
public onSaveQueryClick = (): void => {
|
public onSaveQueryClick = (): void => {
|
||||||
useSidePanel.getState().openSidePanel("Save Query", <SaveQueryPane explorer={this.props.collection.container} />);
|
useSidePanel.getState().openSidePanel("Save Query", <SaveQueryPane explorer={this.props.collection.container} />);
|
||||||
};
|
};
|
||||||
@ -393,6 +409,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
buttons.push({
|
buttons.push({
|
||||||
iconSrc: ExecuteQueryIcon,
|
iconSrc: ExecuteQueryIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.EXECUTE_ITEM,
|
||||||
onCommandClick: this.props.isSampleCopilotActive
|
onCommandClick: this.props.isSampleCopilotActive
|
||||||
? () => OnExecuteQueryClick(this.props.copilotStore)
|
? () => OnExecuteQueryClick(this.props.copilotStore)
|
||||||
: this.onExecuteQueryClick,
|
: this.onExecuteQueryClick,
|
||||||
@ -403,14 +420,28 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.saveQueryButton.visible && configContext.platform !== Platform.Fabric) {
|
if (this.saveQueryButton.visible) {
|
||||||
const label = "Save Query";
|
if (configContext.platform !== Platform.Fabric) {
|
||||||
|
const label = "Save Query";
|
||||||
|
buttons.push({
|
||||||
|
iconSrc: SaveQueryIcon,
|
||||||
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.SAVE_ITEM,
|
||||||
|
onCommandClick: this.onSaveQueryClick,
|
||||||
|
commandButtonLabel: label,
|
||||||
|
ariaLabel: label,
|
||||||
|
hasPopup: false,
|
||||||
|
disabled: !this.saveQueryButton.enabled,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
buttons.push({
|
buttons.push({
|
||||||
iconSrc: SaveQueryIcon,
|
iconSrc: DownloadQueryIcon,
|
||||||
iconAlt: label,
|
iconAlt: "Download Query",
|
||||||
onCommandClick: this.onSaveQueryClick,
|
keyboardAction: KeyboardAction.DOWNLOAD_ITEM,
|
||||||
commandButtonLabel: label,
|
onCommandClick: this.onDownloadQueryClick,
|
||||||
ariaLabel: label,
|
commandButtonLabel: "Download Query",
|
||||||
|
ariaLabel: "Download Query",
|
||||||
hasPopup: false,
|
hasPopup: false,
|
||||||
disabled: !this.saveQueryButton.enabled,
|
disabled: !this.saveQueryButton.enabled,
|
||||||
});
|
});
|
||||||
@ -437,7 +468,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
hasPopup: false,
|
hasPopup: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const launchCopilotButton = {
|
const launchCopilotButton: CommandButtonComponentProps = {
|
||||||
iconSrc: LaunchCopilot,
|
iconSrc: LaunchCopilot,
|
||||||
iconAlt: mainButtonLabel,
|
iconAlt: mainButtonLabel,
|
||||||
onCommandClick: this.launchQueryCopilotChat,
|
onCommandClick: this.launchQueryCopilotChat,
|
||||||
@ -450,14 +481,15 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.copilotEnabled) {
|
if (this.props.copilotEnabled) {
|
||||||
const toggleCopilotButton = {
|
const toggleCopilotButton: CommandButtonComponentProps = {
|
||||||
iconSrc: QueryCommandIcon,
|
iconSrc: QueryCommandIcon,
|
||||||
iconAlt: "Copilot",
|
iconAlt: "Query Advisor",
|
||||||
|
keyboardAction: KeyboardAction.TOGGLE_COPILOT,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
this._toggleCopilot(!this.state.copilotActive);
|
this._toggleCopilot(!this.state.copilotActive);
|
||||||
},
|
},
|
||||||
commandButtonLabel: this.state.copilotActive ? "Disable Copilot" : "Enable Copilot",
|
commandButtonLabel: this.state.copilotActive ? "Disable Query Advisor" : "Enable Query Advisor",
|
||||||
ariaLabel: this.state.copilotActive ? "Disable Copilot" : "Enable Copilot",
|
ariaLabel: this.state.copilotActive ? "Disable Query Advisor" : "Enable Query Advisor",
|
||||||
hasPopup: false,
|
hasPopup: false,
|
||||||
};
|
};
|
||||||
buttons.push(toggleCopilotButton);
|
buttons.push(toggleCopilotButton);
|
||||||
@ -468,6 +500,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
buttons.push({
|
buttons.push({
|
||||||
iconSrc: CancelQueryIcon,
|
iconSrc: CancelQueryIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.CANCEL_OR_DISCARD,
|
||||||
onCommandClick: () => this.queryAbortController.abort(),
|
onCommandClick: () => this.queryAbortController.abort(),
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
@ -496,13 +529,16 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
};
|
};
|
||||||
|
|
||||||
public onChangeContent(newContent: string): void {
|
public onChangeContent(newContent: string): void {
|
||||||
|
// The copilot store's active query takes precedence over the local state,
|
||||||
|
// and we can't update both states in a single operation.
|
||||||
|
// So, we update the copilot store's state first, then update the local state.
|
||||||
|
if (this.state.copilotActive) {
|
||||||
|
this.props.copilotStore?.setQuery(newContent);
|
||||||
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
sqlQueryEditorContent: newContent,
|
sqlQueryEditorContent: newContent,
|
||||||
queryCopilotGeneratedQuery: "",
|
queryCopilotGeneratedQuery: "",
|
||||||
});
|
});
|
||||||
if (this.state.copilotActive) {
|
|
||||||
this.props.copilotStore?.setQuery(newContent);
|
|
||||||
}
|
|
||||||
if (this.isPreferredApiMongoDB) {
|
if (this.isPreferredApiMongoDB) {
|
||||||
if (newContent.length > 0) {
|
if (newContent.length > 0) {
|
||||||
this.executeQueryButton = {
|
this.executeQueryButton = {
|
||||||
@ -517,6 +553,8 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.saveQueryButton.enabled = newContent.length > 0;
|
||||||
|
|
||||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -544,7 +582,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||||
}
|
}
|
||||||
|
|
||||||
public setEditorContent(): string {
|
public getEditorContent(): string {
|
||||||
if (this.isCopilotTabActive && this.state.queryCopilotGeneratedQuery) {
|
if (this.isCopilotTabActive && this.state.queryCopilotGeneratedQuery) {
|
||||||
return this.state.queryCopilotGeneratedQuery;
|
return this.state.queryCopilotGeneratedQuery;
|
||||||
}
|
}
|
||||||
@ -601,7 +639,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
<div className="queryEditor" style={{ height: "100%" }}>
|
<div className="queryEditor" style={{ height: "100%" }}>
|
||||||
<EditorReact
|
<EditorReact
|
||||||
language={"sql"}
|
language={"sql"}
|
||||||
content={this.setEditorContent()}
|
content={this.getEditorContent()}
|
||||||
isReadOnly={false}
|
isReadOnly={false}
|
||||||
wordWrap={"on"}
|
wordWrap={"on"}
|
||||||
ariaLabel={"Editing Query"}
|
ariaLabel={"Editing Query"}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||||
import { Pivot, PivotItem } from "@fluentui/react";
|
import { Pivot, PivotItem } from "@fluentui/react";
|
||||||
|
import { KeyboardAction } from "KeyboardShortcuts";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
|
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
|
||||||
import DiscardIcon from "../../../../images/discard.svg";
|
import DiscardIcon from "../../../../images/discard.svg";
|
||||||
@ -321,6 +322,7 @@ export default class StoredProcedureTabComponent extends React.Component<
|
|||||||
buttons.push({
|
buttons.push({
|
||||||
iconSrc: SaveIcon,
|
iconSrc: SaveIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.SAVE_ITEM,
|
||||||
onCommandClick: this.onSaveClick,
|
onCommandClick: this.onSaveClick,
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
@ -334,6 +336,7 @@ export default class StoredProcedureTabComponent extends React.Component<
|
|||||||
buttons.push({
|
buttons.push({
|
||||||
iconSrc: SaveIcon,
|
iconSrc: SaveIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.SAVE_ITEM,
|
||||||
onCommandClick: this.onUpdateClick,
|
onCommandClick: this.onUpdateClick,
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
@ -347,6 +350,7 @@ export default class StoredProcedureTabComponent extends React.Component<
|
|||||||
buttons.push({
|
buttons.push({
|
||||||
iconSrc: DiscardIcon,
|
iconSrc: DiscardIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.CANCEL_OR_DISCARD,
|
||||||
onCommandClick: this.onDiscard,
|
onCommandClick: this.onDiscard,
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
@ -360,6 +364,7 @@ export default class StoredProcedureTabComponent extends React.Component<
|
|||||||
buttons.push({
|
buttons.push({
|
||||||
iconSrc: ExecuteQueryIcon,
|
iconSrc: ExecuteQueryIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.EXECUTE_ITEM,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
this.collection.container.openExecuteSprocParamsPanel(this.node);
|
this.collection.container.openExecuteSprocParamsPanel(this.node);
|
||||||
},
|
},
|
||||||
|
@ -6,6 +6,7 @@ import { IpRule } from "Contracts/DataModels";
|
|||||||
import { MessageTypes } from "Contracts/ExplorerContracts";
|
import { MessageTypes } from "Contracts/ExplorerContracts";
|
||||||
import { CollectionTabKind } from "Contracts/ViewModels";
|
import { CollectionTabKind } from "Contracts/ViewModels";
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
|
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import { QueryCopilotTab } from "Explorer/QueryCopilot/QueryCopilotTab";
|
import { QueryCopilotTab } from "Explorer/QueryCopilot/QueryCopilotTab";
|
||||||
import { SplashScreen } from "Explorer/SplashScreen/SplashScreen";
|
import { SplashScreen } from "Explorer/SplashScreen/SplashScreen";
|
||||||
import { ConnectTab } from "Explorer/Tabs/ConnectTab";
|
import { ConnectTab } from "Explorer/Tabs/ConnectTab";
|
||||||
@ -13,6 +14,7 @@ import { PostgresConnectTab } from "Explorer/Tabs/PostgresConnectTab";
|
|||||||
import { QuickstartTab } from "Explorer/Tabs/QuickstartTab";
|
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 { hasRUThresholdBeenConfigured } from "Shared/StorageUtility";
|
import { hasRUThresholdBeenConfigured } from "Shared/StorageUtility";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { CassandraProxyOutboundIPs, MongoProxyOutboundIPs, PortalBackendIPs } from "Utils/EndpointUtils";
|
import { CassandraProxyOutboundIPs, MongoProxyOutboundIPs, PortalBackendIPs } from "Utils/EndpointUtils";
|
||||||
@ -41,6 +43,16 @@ export const Tabs = ({ explorer }: TabsProps): JSX.Element => {
|
|||||||
showMongoAndCassandraProxiesNetworkSettingsWarningState,
|
showMongoAndCassandraProxiesNetworkSettingsWarningState,
|
||||||
setShowMongoAndCassandraProxiesNetworkSettingsWarningState,
|
setShowMongoAndCassandraProxiesNetworkSettingsWarningState,
|
||||||
] = useState<boolean>(showMongoAndCassandraProxiesNetworkSettingsWarning());
|
] = useState<boolean>(showMongoAndCassandraProxiesNetworkSettingsWarning());
|
||||||
|
|
||||||
|
const setKeyboardHandlers = useKeyboardActionGroup(KeyboardActionGroup.TABS);
|
||||||
|
useEffect(() => {
|
||||||
|
setKeyboardHandlers({
|
||||||
|
[KeyboardAction.SELECT_LEFT_TAB]: () => useTabs.getState().selectLeftTab(),
|
||||||
|
[KeyboardAction.SELECT_RIGHT_TAB]: () => useTabs.getState().selectRightTab(),
|
||||||
|
[KeyboardAction.CLOSE_TAB]: () => useTabs.getState().closeActiveTab(),
|
||||||
|
});
|
||||||
|
}, [setKeyboardHandlers]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tabsManagerContainer">
|
<div className="tabsManagerContainer">
|
||||||
{networkSettingsWarning && (
|
{networkSettingsWarning && (
|
||||||
@ -182,11 +194,9 @@ function TabNav({ tab, active, tabKind }: { tab?: Tab; active: boolean; tabKind?
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span className="tabNavText">{useObservable(tab?.tabTitle || getReactTabTitle())}</span>
|
<span className="tabNavText">{useObservable(tab?.tabTitle || getReactTabTitle())}</span>
|
||||||
{tabKind !== ReactTabKind.Home && (
|
<span className="tabIconSection">
|
||||||
<span className="tabIconSection">
|
<CloseButton tab={tab} active={active} hovering={hovering} tabKind={tabKind} />
|
||||||
<CloseButton tab={tab} active={active} hovering={hovering} tabKind={tabKind} />
|
</span>
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
@ -299,6 +309,9 @@ const isQueryErrorThrown = (tab?: Tab, tabKind?: ReactTabKind): boolean => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): JSX.Element => {
|
const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): JSX.Element => {
|
||||||
|
// React tabs have no context buttons.
|
||||||
|
useCommandBar.getState().setContextButtons([]);
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
switch (activeReactTab) {
|
switch (activeReactTab) {
|
||||||
case ReactTabKind.Connect:
|
case ReactTabKind.Connect:
|
||||||
@ -326,13 +339,17 @@ const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): J
|
|||||||
|
|
||||||
const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => {
|
const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => {
|
||||||
const ipRules: IpRule[] = userContext.databaseAccount?.properties?.ipRules;
|
const ipRules: IpRule[] = userContext.databaseAccount?.properties?.ipRules;
|
||||||
if ((userContext.apiType === "Mongo" || userContext.apiType === "Cassandra") && ipRules?.length) {
|
if (
|
||||||
|
((userContext.apiType === "Mongo" && configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Local) ||
|
||||||
|
(userContext.apiType === "Cassandra" &&
|
||||||
|
configContext.CASSANDRA_PROXY_ENDPOINT !== CassandraProxyEndpoints.Development)) &&
|
||||||
|
ipRules?.length
|
||||||
|
) {
|
||||||
const legacyPortalBackendIPs: string[] = PortalBackendIPs[configContext.BACKEND_ENDPOINT];
|
const legacyPortalBackendIPs: string[] = PortalBackendIPs[configContext.BACKEND_ENDPOINT];
|
||||||
const ipAddressesFromIPRules: string[] = ipRules.map((ipRule) => ipRule.ipAddressOrRange);
|
const ipAddressesFromIPRules: string[] = ipRules.map((ipRule) => ipRule.ipAddressOrRange);
|
||||||
const ipRulesIncludeLegacyPortalBackend: boolean =
|
const ipRulesIncludeLegacyPortalBackend: boolean = legacyPortalBackendIPs.every((legacyPortalBackendIP: string) =>
|
||||||
ipAddressesFromIPRules.filter((ipAddressFromIPRule) => legacyPortalBackendIPs.includes(ipAddressFromIPRule))
|
ipAddressesFromIPRules.includes(legacyPortalBackendIP),
|
||||||
?.length === legacyPortalBackendIPs.length;
|
);
|
||||||
|
|
||||||
if (!ipRulesIncludeLegacyPortalBackend) {
|
if (!ipRulesIncludeLegacyPortalBackend) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -346,9 +363,9 @@ const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => {
|
|||||||
? [...MongoProxyOutboundIPs[MongoProxyEndpoints.Mpac], ...MongoProxyOutboundIPs[MongoProxyEndpoints.Prod]]
|
? [...MongoProxyOutboundIPs[MongoProxyEndpoints.Mpac], ...MongoProxyOutboundIPs[MongoProxyEndpoints.Prod]]
|
||||||
: MongoProxyOutboundIPs[configContext.MONGO_PROXY_ENDPOINT];
|
: MongoProxyOutboundIPs[configContext.MONGO_PROXY_ENDPOINT];
|
||||||
|
|
||||||
const ipRulesIncludeMongoProxy: boolean =
|
const ipRulesIncludeMongoProxy: boolean = mongoProxyOutboundIPs.every((mongoProxyOutboundIP: string) =>
|
||||||
ipAddressesFromIPRules.filter((ipAddressFromIPRule) => mongoProxyOutboundIPs.includes(ipAddressFromIPRule))
|
ipAddressesFromIPRules.includes(mongoProxyOutboundIP),
|
||||||
?.length === mongoProxyOutboundIPs.length;
|
);
|
||||||
|
|
||||||
if (ipRulesIncludeMongoProxy) {
|
if (ipRulesIncludeMongoProxy) {
|
||||||
updateConfigContext({
|
updateConfigContext({
|
||||||
@ -370,9 +387,15 @@ const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => {
|
|||||||
]
|
]
|
||||||
: CassandraProxyOutboundIPs[configContext.CASSANDRA_PROXY_ENDPOINT];
|
: CassandraProxyOutboundIPs[configContext.CASSANDRA_PROXY_ENDPOINT];
|
||||||
|
|
||||||
const ipRulesIncludeCassandraProxy: boolean =
|
const ipRulesIncludeCassandraProxy: boolean = cassandraProxyOutboundIPs.every(
|
||||||
ipAddressesFromIPRules.filter((ipAddressFromIPRule) => cassandraProxyOutboundIPs.includes(ipAddressFromIPRule))
|
(cassandraProxyOutboundIP: string) => ipAddressesFromIPRules.includes(cassandraProxyOutboundIP),
|
||||||
?.length === cassandraProxyOutboundIPs.length;
|
);
|
||||||
|
|
||||||
|
if (ipRulesIncludeCassandraProxy) {
|
||||||
|
updateConfigContext({
|
||||||
|
CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return !ipRulesIncludeCassandraProxy;
|
return !ipRulesIncludeCassandraProxy;
|
||||||
}
|
}
|
||||||
|
@ -40,11 +40,10 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
this.database = options.database;
|
this.database = options.database;
|
||||||
this.rid = options.rid || (this.collection && this.collection.rid) || "";
|
this.rid = options.rid || (this.collection && this.collection.rid) || "";
|
||||||
this.tabKind = options.tabKind;
|
this.tabKind = options.tabKind;
|
||||||
this.tabTitle = ko.observable<string>(options.title);
|
this.tabTitle = ko.observable<string>(this.getTitle(options));
|
||||||
this.tabPath =
|
this.tabPath =
|
||||||
ko.observable(options.tabPath ?? "") ||
|
this.collection &&
|
||||||
(this.collection &&
|
ko.observable<string>(`${this.collection.databaseId}>${this.collection.id()}>${options.title}`);
|
||||||
ko.observable<string>(`${this.collection.databaseId}>${this.collection.id()}>${this.tabTitle()}`));
|
|
||||||
this.pendingNotification = ko.observable<DataModels.Notification>(undefined);
|
this.pendingNotification = ko.observable<DataModels.Notification>(undefined);
|
||||||
this.onLoadStartKey = options.onLoadStartKey;
|
this.onLoadStartKey = options.onLoadStartKey;
|
||||||
this.closeTabButton = {
|
this.closeTabButton = {
|
||||||
@ -143,6 +142,26 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
return (this.collection && this.collection.container) || (this.database && this.database.container);
|
return (this.collection && this.collection.container) || (this.database && this.database.container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getTitle(options: ViewModels.TabOptions): string {
|
||||||
|
const coll = this.collection?.id();
|
||||||
|
const db = this.database?.id();
|
||||||
|
if (coll) {
|
||||||
|
if (coll.length > 8) {
|
||||||
|
return coll.slice(0, 5) + "…" + options.title;
|
||||||
|
} else {
|
||||||
|
return coll + "." + options.title;
|
||||||
|
}
|
||||||
|
} else if (db) {
|
||||||
|
if (db.length > 8) {
|
||||||
|
return db.slice(0, 5) + "…" + options.title;
|
||||||
|
} else {
|
||||||
|
return db + "." + options.title;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return options.title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Renders a Javascript object to be displayed inside Monaco Editor */
|
/** Renders a Javascript object to be displayed inside Monaco Editor */
|
||||||
public renderObjectForEditor(value: any, replacer: any, space: string | number): string {
|
public renderObjectForEditor(value: any, replacer: any, space: string | number): string {
|
||||||
return JSON.stringify(value, replacer, space);
|
return JSON.stringify(value, replacer, space);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { TriggerDefinition } from "@azure/cosmos";
|
import { TriggerDefinition } from "@azure/cosmos";
|
||||||
import { Dropdown, IDropdownOption, Label, TextField } from "@fluentui/react";
|
import { Dropdown, IDropdownOption, Label, TextField } from "@fluentui/react";
|
||||||
|
import { KeyboardAction } from "KeyboardShortcuts";
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import DiscardIcon from "../../../images/discard.svg";
|
import DiscardIcon from "../../../images/discard.svg";
|
||||||
import SaveIcon from "../../../images/save-cosmos.svg";
|
import SaveIcon from "../../../images/save-cosmos.svg";
|
||||||
@ -218,6 +219,18 @@ export class TriggerTabContent extends Component<TriggerTab, ITriggerTabContentS
|
|||||||
return !!value;
|
return !!value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(_prevProps: TriggerTab, prevState: ITriggerTabContentState): void {
|
||||||
|
const { triggerBody, triggerId, triggerType, triggerOperation } = this.state;
|
||||||
|
if (
|
||||||
|
triggerId !== prevState.triggerId ||
|
||||||
|
triggerBody !== prevState.triggerBody ||
|
||||||
|
triggerType !== prevState.triggerType ||
|
||||||
|
triggerOperation !== prevState.triggerOperation
|
||||||
|
) {
|
||||||
|
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected getTabsButtons(): CommandButtonComponentProps[] {
|
protected getTabsButtons(): CommandButtonComponentProps[] {
|
||||||
const buttons: CommandButtonComponentProps[] = [];
|
const buttons: CommandButtonComponentProps[] = [];
|
||||||
const label = "Save";
|
const label = "Save";
|
||||||
@ -227,6 +240,7 @@ export class TriggerTabContent extends Component<TriggerTab, ITriggerTabContentS
|
|||||||
...this,
|
...this,
|
||||||
iconSrc: SaveIcon,
|
iconSrc: SaveIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.SAVE_ITEM,
|
||||||
onCommandClick: this.onSaveClick,
|
onCommandClick: this.onSaveClick,
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
@ -241,6 +255,7 @@ export class TriggerTabContent extends Component<TriggerTab, ITriggerTabContentS
|
|||||||
...this,
|
...this,
|
||||||
iconSrc: SaveIcon,
|
iconSrc: SaveIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.SAVE_ITEM,
|
||||||
onCommandClick: this.onUpdateClick,
|
onCommandClick: this.onUpdateClick,
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
@ -256,6 +271,7 @@ export class TriggerTabContent extends Component<TriggerTab, ITriggerTabContentS
|
|||||||
...this,
|
...this,
|
||||||
iconSrc: DiscardIcon,
|
iconSrc: DiscardIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.CANCEL_OR_DISCARD,
|
||||||
onCommandClick: this.onDiscard,
|
onCommandClick: this.onDiscard,
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
@ -287,7 +303,6 @@ export class TriggerTabContent extends Component<TriggerTab, ITriggerTabContentS
|
|||||||
};
|
};
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
|
||||||
const { triggerId, triggerType, triggerOperation, triggerBody, isIdEditable } = this.state;
|
const { triggerId, triggerType, triggerOperation, triggerBody, isIdEditable } = this.state;
|
||||||
return (
|
return (
|
||||||
<div className="tab-pane flexContainer trigger-form" role="tabpanel">
|
<div className="tab-pane flexContainer trigger-form" role="tabpanel">
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { UserDefinedFunctionDefinition } from "@azure/cosmos";
|
import { UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||||
import { Label, TextField } from "@fluentui/react";
|
import { Label, TextField } from "@fluentui/react";
|
||||||
|
import { KeyboardAction } from "KeyboardShortcuts";
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import DiscardIcon from "../../../images/discard.svg";
|
import DiscardIcon from "../../../images/discard.svg";
|
||||||
import SaveIcon from "../../../images/save-cosmos.svg";
|
import SaveIcon from "../../../images/save-cosmos.svg";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
import { createUserDefinedFunction } from "../../Common/dataAccess/createUserDefinedFunction";
|
import { createUserDefinedFunction } from "../../Common/dataAccess/createUserDefinedFunction";
|
||||||
import { updateUserDefinedFunction } from "../../Common/dataAccess/updateUserDefinedFunction";
|
import { updateUserDefinedFunction } from "../../Common/dataAccess/updateUserDefinedFunction";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
@ -80,6 +81,7 @@ export default class UserDefinedFunctionTabContent extends Component<
|
|||||||
setState: this.setState,
|
setState: this.setState,
|
||||||
iconSrc: SaveIcon,
|
iconSrc: SaveIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.SAVE_ITEM,
|
||||||
onCommandClick: this.onSaveClick,
|
onCommandClick: this.onSaveClick,
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
@ -94,6 +96,7 @@ export default class UserDefinedFunctionTabContent extends Component<
|
|||||||
...this,
|
...this,
|
||||||
iconSrc: SaveIcon,
|
iconSrc: SaveIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.SAVE_ITEM,
|
||||||
onCommandClick: this.onUpdateClick,
|
onCommandClick: this.onUpdateClick,
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
@ -109,6 +112,7 @@ export default class UserDefinedFunctionTabContent extends Component<
|
|||||||
...this,
|
...this,
|
||||||
iconSrc: DiscardIcon,
|
iconSrc: DiscardIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
keyboardAction: KeyboardAction.CANCEL_OR_DISCARD,
|
||||||
onCommandClick: this.onDiscard,
|
onCommandClick: this.onDiscard,
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
|
@ -58,6 +58,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
public indexingPolicy: ko.Observable<DataModels.IndexingPolicy>;
|
public indexingPolicy: ko.Observable<DataModels.IndexingPolicy>;
|
||||||
public uniqueKeyPolicy: DataModels.UniqueKeyPolicy;
|
public uniqueKeyPolicy: DataModels.UniqueKeyPolicy;
|
||||||
public usageSizeInKB: ko.Observable<number>;
|
public usageSizeInKB: ko.Observable<number>;
|
||||||
|
public computedProperties: ko.Observable<DataModels.ComputedProperties>;
|
||||||
|
|
||||||
public offer: ko.Observable<DataModels.Offer>;
|
public offer: ko.Observable<DataModels.Offer>;
|
||||||
public conflictResolutionPolicy: ko.Observable<DataModels.ConflictResolutionPolicy>;
|
public conflictResolutionPolicy: ko.Observable<DataModels.ConflictResolutionPolicy>;
|
||||||
@ -121,6 +122,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
this.schema = data.schema;
|
this.schema = data.schema;
|
||||||
this.requestSchema = data.requestSchema;
|
this.requestSchema = data.requestSchema;
|
||||||
this.geospatialConfig = ko.observable(data.geospatialConfig);
|
this.geospatialConfig = ko.observable(data.geospatialConfig);
|
||||||
|
this.computedProperties = ko.observable(data.computedProperties);
|
||||||
|
|
||||||
this.partitionKeyPropertyHeaders = this.partitionKey?.paths;
|
this.partitionKeyPropertyHeaders = this.partitionKey?.paths;
|
||||||
this.partitionKeyProperties = this.partitionKeyPropertyHeaders?.map((partitionKeyPropertyHeader, i) => {
|
this.partitionKeyProperties = this.partitionKeyPropertyHeaders?.map((partitionKeyPropertyHeader, i) => {
|
||||||
@ -306,7 +308,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
collectionName: this.id(),
|
collectionName: this.id(),
|
||||||
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
tabTitle: this.rawDataModel.id + " - Items",
|
tabTitle: "Items",
|
||||||
});
|
});
|
||||||
this.documentIds([]);
|
this.documentIds([]);
|
||||||
|
|
||||||
@ -314,7 +316,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
partitionKey: this.partitionKey,
|
partitionKey: this.partitionKey,
|
||||||
documentIds: ko.observableArray<DocumentId>([]),
|
documentIds: ko.observableArray<DocumentId>([]),
|
||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||||
title: this.rawDataModel.id + " - Items",
|
title: "Items",
|
||||||
collection: this,
|
collection: this,
|
||||||
node: this,
|
node: this,
|
||||||
tabPath: `${this.databaseId}>${this.id()}>Documents`,
|
tabPath: `${this.databaseId}>${this.id()}>Documents`,
|
||||||
|
@ -373,11 +373,6 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
iconSrc: NewNotebookIcon,
|
iconSrc: NewNotebookIcon,
|
||||||
onClick: () => container.onCreateDirectory(item, isGithubTree),
|
onClick: () => container.onCreateDirectory(item, isGithubTree),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: "New Notebook",
|
|
||||||
iconSrc: NewNotebookIcon,
|
|
||||||
onClick: () => container.onNewNotebookClicked(item, isGithubTree),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: "Upload File",
|
label: "Upload File",
|
||||||
iconSrc: NewNotebookIcon,
|
iconSrc: NewNotebookIcon,
|
||||||
@ -786,9 +781,6 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
<AccordionItemComponent title={"DATA"} isExpanded={!gitHubNotebooksContentRoot}>
|
<AccordionItemComponent title={"DATA"} isExpanded={!gitHubNotebooksContentRoot}>
|
||||||
<TreeComponent className="dataResourceTree" rootNode={dataRootNode} />
|
<TreeComponent className="dataResourceTree" rootNode={dataRootNode} />
|
||||||
</AccordionItemComponent>
|
</AccordionItemComponent>
|
||||||
<AccordionItemComponent title={"NOTEBOOKS"}>
|
|
||||||
<TreeComponent className="notebookResourceTree" rootNode={buildNotebooksTree()} />
|
|
||||||
</AccordionItemComponent>
|
|
||||||
</AccordionComponent>
|
</AccordionComponent>
|
||||||
|
|
||||||
{/* {buildGalleryCallout()} */}
|
{/* {buildGalleryCallout()} */}
|
||||||
|
@ -800,11 +800,6 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
iconSrc: NewNotebookIcon,
|
iconSrc: NewNotebookIcon,
|
||||||
onClick: () => this.container.onCreateDirectory(item),
|
onClick: () => this.container.onCreateDirectory(item),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: "New Notebook",
|
|
||||||
iconSrc: NewNotebookIcon,
|
|
||||||
onClick: () => this.container.onNewNotebookClicked(item),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: "Upload File",
|
label: "Upload File",
|
||||||
iconSrc: NewNotebookIcon,
|
iconSrc: NewNotebookIcon,
|
||||||
|
159
src/KeyboardShortcuts.tsx
Normal file
159
src/KeyboardShortcuts.tsx
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { PropsWithChildren, useEffect } from "react";
|
||||||
|
import { KeyBindingMap, tinykeys } from "tinykeys";
|
||||||
|
import create, { UseStore } from "zustand";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a keyboard shortcut handler.
|
||||||
|
* Return `true` to prevent the default action of the keyboard shortcut.
|
||||||
|
* Any other return value will allow the default action to proceed.
|
||||||
|
*/
|
||||||
|
export type KeyboardActionHandler = (e: KeyboardEvent) => boolean | void;
|
||||||
|
|
||||||
|
export type KeyboardHandlerMap = Partial<Record<KeyboardAction, KeyboardActionHandler>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The groups of keyboard actions that can be managed by the application.
|
||||||
|
* Each group can be updated separately, but, when updated, must be completely replaced.
|
||||||
|
*/
|
||||||
|
export enum KeyboardActionGroup {
|
||||||
|
TABS = "TABS",
|
||||||
|
COMMAND_BAR = "COMMAND_BAR",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The possible actions that can be triggered by keyboard shortcuts.
|
||||||
|
*/
|
||||||
|
export enum KeyboardAction {
|
||||||
|
NEW_QUERY = "NEW_QUERY",
|
||||||
|
EXECUTE_ITEM = "EXECUTE_ITEM",
|
||||||
|
CANCEL_OR_DISCARD = "CANCEL_OR_DISCARD",
|
||||||
|
SAVE_ITEM = "SAVE_ITEM",
|
||||||
|
DOWNLOAD_ITEM = "DOWNLOAD_ITEM",
|
||||||
|
OPEN_QUERY = "OPEN_QUERY",
|
||||||
|
OPEN_QUERY_FROM_DISK = "OPEN_QUERY_FROM_DISK",
|
||||||
|
NEW_SPROC = "NEW_SPROC",
|
||||||
|
NEW_UDF = "NEW_UDF",
|
||||||
|
NEW_TRIGGER = "NEW_TRIGGER",
|
||||||
|
NEW_DATABASE = "NEW_DATABASE",
|
||||||
|
NEW_COLLECTION = "NEW_CONTAINER",
|
||||||
|
NEW_ITEM = "NEW_ITEM",
|
||||||
|
DELETE_ITEM = "DELETE_ITEM",
|
||||||
|
TOGGLE_COPILOT = "TOGGLE_COPILOT",
|
||||||
|
SELECT_LEFT_TAB = "SELECT_LEFT_TAB",
|
||||||
|
SELECT_RIGHT_TAB = "SELECT_RIGHT_TAB",
|
||||||
|
CLOSE_TAB = "CLOSE_TAB",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The keyboard shortcuts for the application.
|
||||||
|
* This record maps each action to the keyboard shortcuts that trigger the action.
|
||||||
|
* Even if an action is specified here, it will not be triggered unless a handler is set for it.
|
||||||
|
*/
|
||||||
|
const bindings: Record<KeyboardAction, string[]> = {
|
||||||
|
// NOTE: The "$mod" special value is used to represent the "Control" key on Windows/Linux and the "Command" key on macOS.
|
||||||
|
// See https://www.npmjs.com/package/tinykeys#commonly-used-keys-and-codes for more information on the expected values for keyboard shortcuts.
|
||||||
|
|
||||||
|
[KeyboardAction.NEW_QUERY]: ["$mod+J", "Alt+N Q"],
|
||||||
|
[KeyboardAction.EXECUTE_ITEM]: ["Shift+Enter", "F5"],
|
||||||
|
[KeyboardAction.CANCEL_OR_DISCARD]: ["Escape"],
|
||||||
|
[KeyboardAction.SAVE_ITEM]: ["$mod+S"],
|
||||||
|
[KeyboardAction.DOWNLOAD_ITEM]: ["$mod+Shift+S"],
|
||||||
|
[KeyboardAction.OPEN_QUERY]: ["$mod+O"],
|
||||||
|
[KeyboardAction.OPEN_QUERY_FROM_DISK]: ["$mod+Shift+O"],
|
||||||
|
[KeyboardAction.NEW_SPROC]: ["Alt+N P"],
|
||||||
|
[KeyboardAction.NEW_UDF]: ["Alt+N F"],
|
||||||
|
[KeyboardAction.NEW_TRIGGER]: ["Alt+N T"],
|
||||||
|
[KeyboardAction.NEW_DATABASE]: ["Alt+N D"],
|
||||||
|
[KeyboardAction.NEW_COLLECTION]: ["Alt+N C"],
|
||||||
|
[KeyboardAction.NEW_ITEM]: ["Alt+N I"],
|
||||||
|
[KeyboardAction.DELETE_ITEM]: ["Alt+D"],
|
||||||
|
[KeyboardAction.TOGGLE_COPILOT]: ["$mod+P"],
|
||||||
|
[KeyboardAction.SELECT_LEFT_TAB]: ["$mod+Alt+[", "$mod+Shift+F6"],
|
||||||
|
[KeyboardAction.SELECT_RIGHT_TAB]: ["$mod+Alt+]", "$mod+F6"],
|
||||||
|
[KeyboardAction.CLOSE_TAB]: ["$mod+Alt+W"],
|
||||||
|
};
|
||||||
|
|
||||||
|
interface KeyboardShortcutState {
|
||||||
|
/**
|
||||||
|
* A set of all the keyboard shortcuts handlers.
|
||||||
|
*/
|
||||||
|
allHandlers: KeyboardHandlerMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of all the groups of keyboard shortcuts handlers.
|
||||||
|
*/
|
||||||
|
groups: Partial<Record<KeyboardActionGroup, KeyboardHandlerMap>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the keyboard shortcut handlers for the given group.
|
||||||
|
*/
|
||||||
|
setHandlers: (group: KeyboardActionGroup, handlers: KeyboardHandlerMap) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the calling component as the manager of the keyboard actions for the given group.
|
||||||
|
* @param group The group of keyboard actions to manage.
|
||||||
|
* @returns A function that can be used to set the keyboard action handlers for the given group.
|
||||||
|
*/
|
||||||
|
export const useKeyboardActionGroup = (group: KeyboardActionGroup) => (handlers: KeyboardHandlerMap) =>
|
||||||
|
useKeyboardActionHandlers.getState().setHandlers(group, handlers);
|
||||||
|
|
||||||
|
const useKeyboardActionHandlers: UseStore<KeyboardShortcutState> = create((set, get) => ({
|
||||||
|
allHandlers: {},
|
||||||
|
groups: {},
|
||||||
|
setHandlers: (group: KeyboardActionGroup, handlers: KeyboardHandlerMap) => {
|
||||||
|
const state = get();
|
||||||
|
const groups = { ...state.groups, [group]: handlers };
|
||||||
|
|
||||||
|
// Combine all the handlers from all the groups in the correct order.
|
||||||
|
const allHandlers: KeyboardHandlerMap = {};
|
||||||
|
eachKey(groups).forEach((group) => {
|
||||||
|
const groupHandlers = groups[group];
|
||||||
|
if (groupHandlers) {
|
||||||
|
eachKey(groupHandlers).forEach((action) => {
|
||||||
|
// Check for duplicate handlers in development mode.
|
||||||
|
// We don't want to raise an error here in production, but having duplicate handlers is a mistake.
|
||||||
|
if (process.env.NODE_ENV === "development" && allHandlers[action]) {
|
||||||
|
throw new Error(`Duplicate handler for Keyboard Action "${action}".`);
|
||||||
|
}
|
||||||
|
allHandlers[action] = groupHandlers[action];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
set({ groups, allHandlers });
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
function createHandler(action: KeyboardAction): KeyboardActionHandler {
|
||||||
|
return (e) => {
|
||||||
|
const state = useKeyboardActionHandlers.getState();
|
||||||
|
const handler = state.allHandlers[action];
|
||||||
|
if (handler && handler(e)) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const allHandlers: KeyBindingMap = {};
|
||||||
|
eachKey(bindings).forEach((action) => {
|
||||||
|
const shortcuts = bindings[action];
|
||||||
|
shortcuts.forEach((shortcut) => {
|
||||||
|
allHandlers[shortcut] = createHandler(action);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export function KeyboardShortcutRoot({ children }: PropsWithChildren<unknown>) {
|
||||||
|
useEffect(() => {
|
||||||
|
// We bind to the body because Fluent UI components sometimes shift focus to the body, which is above the root React component.
|
||||||
|
tinykeys(document.body, allHandlers);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A _typed_ version of `Object.keys` that preserves the original key type */
|
||||||
|
function eachKey<K extends string | number | symbol, V>(record: Partial<Record<K, V>>): K[] {
|
||||||
|
return Object.keys(record) as K[];
|
||||||
|
}
|
92
src/Main.tsx
92
src/Main.tsx
@ -1,3 +1,6 @@
|
|||||||
|
// Import this first, to ensure that the dev tools hook is copied before React is loaded.
|
||||||
|
import "./ReactDevTools";
|
||||||
|
|
||||||
// CSS Dependencies
|
// CSS Dependencies
|
||||||
import { initializeIcons, loadTheme } from "@fluentui/react";
|
import { initializeIcons, loadTheme } from "@fluentui/react";
|
||||||
import { QuickstartCarousel } from "Explorer/Quickstart/QuickstartCarousel";
|
import { QuickstartCarousel } from "Explorer/Quickstart/QuickstartCarousel";
|
||||||
@ -18,6 +21,7 @@ import "../externals/jquery.typeahead.min.js";
|
|||||||
// Image Dependencies
|
// Image Dependencies
|
||||||
import { Platform } from "ConfigContext";
|
import { Platform } from "ConfigContext";
|
||||||
import { QueryCopilotCarousel } from "Explorer/QueryCopilot/CopilotCarousel";
|
import { QueryCopilotCarousel } from "Explorer/QueryCopilot/CopilotCarousel";
|
||||||
|
import { KeyboardShortcutRoot } from "KeyboardShortcuts";
|
||||||
import "../images/CosmosDB_rgb_ui_lighttheme.ico";
|
import "../images/CosmosDB_rgb_ui_lighttheme.ico";
|
||||||
import hdeConnectImage from "../images/HdeConnectCosmosDB.svg";
|
import hdeConnectImage from "../images/HdeConnectCosmosDB.svg";
|
||||||
import "../images/favicon.ico";
|
import "../images/favicon.ico";
|
||||||
@ -88,52 +92,54 @@ const App: React.FunctionComponent = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flexContainer" aria-hidden="false">
|
<KeyboardShortcutRoot>
|
||||||
<div id="divExplorer" className="flexContainer hideOverflows">
|
<div className="flexContainer" aria-hidden="false">
|
||||||
<div id="freeTierTeachingBubble"> </div>
|
<div id="divExplorer" className="flexContainer hideOverflows">
|
||||||
{/* Main Command Bar - Start */}
|
<div id="freeTierTeachingBubble"> </div>
|
||||||
<CommandBar container={explorer} />
|
{/* Main Command Bar - Start */}
|
||||||
{/* Collections Tree and Tabs - Begin */}
|
<CommandBar container={explorer} />
|
||||||
<div className="resourceTreeAndTabs">
|
{/* Collections Tree and Tabs - Begin */}
|
||||||
{/* Collections Tree - Start */}
|
<div className="resourceTreeAndTabs">
|
||||||
{userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo" && (
|
{/* Collections Tree - Start */}
|
||||||
<div id="resourcetree" data-test="resourceTreeId" className="resourceTree">
|
{userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo" && (
|
||||||
<div className="collectionsTreeWithSplitter">
|
<div id="resourcetree" data-test="resourceTreeId" className="resourceTree">
|
||||||
{/* Collections Tree Expanded - Start */}
|
<div className="collectionsTreeWithSplitter">
|
||||||
<ResourceTreeContainer
|
{/* Collections Tree Expanded - Start */}
|
||||||
container={explorer}
|
<ResourceTreeContainer
|
||||||
toggleLeftPaneExpanded={toggleLeftPaneExpanded}
|
container={explorer}
|
||||||
isLeftPaneExpanded={isLeftPaneExpanded}
|
toggleLeftPaneExpanded={toggleLeftPaneExpanded}
|
||||||
/>
|
isLeftPaneExpanded={isLeftPaneExpanded}
|
||||||
{/* Collections Tree Expanded - End */}
|
/>
|
||||||
{/* Collections Tree Collapsed - Start */}
|
{/* Collections Tree Expanded - End */}
|
||||||
<CollapsedResourceTree
|
{/* Collections Tree Collapsed - Start */}
|
||||||
toggleLeftPaneExpanded={toggleLeftPaneExpanded}
|
<CollapsedResourceTree
|
||||||
isLeftPaneExpanded={isLeftPaneExpanded}
|
toggleLeftPaneExpanded={toggleLeftPaneExpanded}
|
||||||
/>
|
isLeftPaneExpanded={isLeftPaneExpanded}
|
||||||
{/* Collections Tree Collapsed - End */}
|
/>
|
||||||
|
{/* Collections Tree Collapsed - End */}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
<Tabs explorer={explorer} />
|
||||||
<Tabs explorer={explorer} />
|
</div>
|
||||||
</div>
|
{/* Collections Tree and Tabs - End */}
|
||||||
{/* Collections Tree and Tabs - End */}
|
<div
|
||||||
<div
|
className="dataExplorerErrorConsoleContainer"
|
||||||
className="dataExplorerErrorConsoleContainer"
|
role="contentinfo"
|
||||||
role="contentinfo"
|
aria-label="Notification console"
|
||||||
aria-label="Notification console"
|
id="explorerNotificationConsole"
|
||||||
id="explorerNotificationConsole"
|
>
|
||||||
>
|
<NotificationConsole />
|
||||||
<NotificationConsole />
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<SidePanel />
|
||||||
|
<Dialog />
|
||||||
|
{<QuickstartCarousel isOpen={isCarouselOpen} />}
|
||||||
|
{<SQLQuickstartTutorial />}
|
||||||
|
{<MongoQuickstartTutorial />}
|
||||||
|
{<QueryCopilotCarousel isOpen={isCopilotCarouselOpen} explorer={explorer} />}
|
||||||
</div>
|
</div>
|
||||||
<SidePanel />
|
</KeyboardShortcutRoot>
|
||||||
<Dialog />
|
|
||||||
{<QuickstartCarousel isOpen={isCarouselOpen} />}
|
|
||||||
{<SQLQuickstartTutorial />}
|
|
||||||
{<MongoQuickstartTutorial />}
|
|
||||||
{<QueryCopilotCarousel isOpen={isCopilotCarouselOpen} explorer={explorer} />}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { useBoolean } from "@fluentui/react-hooks";
|
import { useBoolean } from "@fluentui/react-hooks";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
|
import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import ConnectImage from "../../../../images/HdeConnectCosmosDB.svg";
|
import ConnectImage from "../../../../images/HdeConnectCosmosDB.svg";
|
||||||
import ErrorImage from "../../../../images/error.svg";
|
import ErrorImage from "../../../../images/error.svg";
|
||||||
import { AuthType } from "../../../AuthType";
|
import { AuthType } from "../../../AuthType";
|
||||||
import { HttpHeaders } from "../../../Common/Constants";
|
import { BackendApi, HttpHeaders } from "../../../Common/Constants";
|
||||||
import { configContext } from "../../../ConfigContext";
|
import { configContext } from "../../../ConfigContext";
|
||||||
import { GenerateTokenResponse } from "../../../Contracts/DataModels";
|
import { GenerateTokenResponse } from "../../../Contracts/DataModels";
|
||||||
import { isResourceTokenConnectionString } from "../Helpers/ResourceTokenUtils";
|
import { isResourceTokenConnectionString } from "../Helpers/ResourceTokenUtils";
|
||||||
@ -18,6 +19,23 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const fetchEncryptedToken = async (connectionString: string): Promise<string> => {
|
export const fetchEncryptedToken = async (connectionString: string): Promise<string> => {
|
||||||
|
if (!useNewPortalBackendEndpoint(BackendApi.GenerateToken)) {
|
||||||
|
return await fetchEncryptedToken_ToBeDeprecated(connectionString);
|
||||||
|
}
|
||||||
|
|
||||||
|
const headers = new Headers();
|
||||||
|
headers.append(HttpHeaders.connectionString, connectionString);
|
||||||
|
const url = configContext.PORTAL_BACKEND_ENDPOINT + "/api/connectionstring/token/generatetoken";
|
||||||
|
const response = await fetch(url, { headers, method: "POST" });
|
||||||
|
if (!response.ok) {
|
||||||
|
throw response;
|
||||||
|
}
|
||||||
|
|
||||||
|
const encryptedTokenResponse: string = await response.json();
|
||||||
|
return decodeURIComponent(encryptedTokenResponse);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchEncryptedToken_ToBeDeprecated = async (connectionString: string): Promise<string> => {
|
||||||
const headers = new Headers();
|
const headers = new Headers();
|
||||||
headers.append(HttpHeaders.connectionString, connectionString);
|
headers.append(HttpHeaders.connectionString, connectionString);
|
||||||
const url = configContext.BACKEND_ENDPOINT + "/api/guest/tokens/generateToken";
|
const url = configContext.BACKEND_ENDPOINT + "/api/guest/tokens/generateToken";
|
||||||
|
@ -31,11 +31,6 @@ export type Features = {
|
|||||||
readonly mongoProxyAPIs?: string;
|
readonly mongoProxyAPIs?: string;
|
||||||
readonly enableThroughputCap: boolean;
|
readonly enableThroughputCap: boolean;
|
||||||
readonly enableHierarchicalKeys: boolean;
|
readonly enableHierarchicalKeys: boolean;
|
||||||
readonly enableLegacyMongoShellV1: boolean;
|
|
||||||
readonly enableLegacyMongoShellV1Debug: boolean;
|
|
||||||
readonly enableLegacyMongoShellV2: boolean;
|
|
||||||
readonly enableLegacyMongoShellV2Debug: boolean;
|
|
||||||
readonly loadLegacyMongoShellFromBE: boolean;
|
|
||||||
readonly enableCopilot: boolean;
|
readonly enableCopilot: boolean;
|
||||||
readonly copilotVersion?: string;
|
readonly copilotVersion?: string;
|
||||||
readonly disableCopilotPhoenixGateaway: boolean;
|
readonly disableCopilotPhoenixGateaway: boolean;
|
||||||
@ -106,11 +101,6 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
|
|||||||
notebooksDownBanner: "true" === get("notebooksDownBanner"),
|
notebooksDownBanner: "true" === get("notebooksDownBanner"),
|
||||||
enableThroughputCap: "true" === get("enablethroughputcap"),
|
enableThroughputCap: "true" === get("enablethroughputcap"),
|
||||||
enableHierarchicalKeys: "true" === get("enablehierarchicalkeys"),
|
enableHierarchicalKeys: "true" === get("enablehierarchicalkeys"),
|
||||||
enableLegacyMongoShellV1: "true" === get("enablelegacymongoshellv1"),
|
|
||||||
enableLegacyMongoShellV1Debug: "true" === get("enablelegacymongoshellv1debug"),
|
|
||||||
enableLegacyMongoShellV2: "true" === get("enablelegacymongoshellv2"),
|
|
||||||
enableLegacyMongoShellV2Debug: "true" === get("enablelegacymongoshellv2debug"),
|
|
||||||
loadLegacyMongoShellFromBE: "true" === get("loadlegacymongoshellfrombe"),
|
|
||||||
enableCopilot: "true" === get("enablecopilot", "true"),
|
enableCopilot: "true" === get("enablecopilot", "true"),
|
||||||
copilotVersion: get("copilotversion") ?? "v2.0",
|
copilotVersion: get("copilotversion") ?? "v2.0",
|
||||||
disableCopilotPhoenixGateaway: "true" === get("disablecopilotphoenixgateaway"),
|
disableCopilotPhoenixGateaway: "true" === get("disablecopilotphoenixgateaway"),
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
if (window.parent !== window) {
|
if (window.parent !== window) {
|
||||||
(window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__ = (window.parent as any).__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
try {
|
||||||
|
(window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__ = (window.parent as any).__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
||||||
|
} catch {
|
||||||
|
// No-op. We can throw here if the parent is not the same origin (such as in the Azure portal).
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,7 @@ interface FabricContext {
|
|||||||
connectionId: string;
|
connectionId: string;
|
||||||
databaseConnectionInfo: FabricDatabaseConnectionInfo | undefined;
|
databaseConnectionInfo: FabricDatabaseConnectionInfo | undefined;
|
||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
|
isVisible: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AdminFeedbackControlPolicy =
|
export type AdminFeedbackControlPolicy =
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
import { CassandraProxyEndpoints, JunoEndpoints, MongoProxyEndpoints } from "Common/Constants";
|
import {
|
||||||
|
BackendApi,
|
||||||
|
CassandraProxyEndpoints,
|
||||||
|
JunoEndpoints,
|
||||||
|
MongoProxyEndpoints,
|
||||||
|
PortalBackendEndpoints,
|
||||||
|
} from "Common/Constants";
|
||||||
|
import { configContext } from "ConfigContext";
|
||||||
import * as Logger from "../Common/Logger";
|
import * as Logger from "../Common/Logger";
|
||||||
|
|
||||||
export function validateEndpoint(
|
export function validateEndpoint(
|
||||||
@ -75,7 +82,7 @@ export const MongoProxyOutboundIPs: { [key: string]: string[] } = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const allowedMongoProxyEndpoints: ReadonlyArray<string> = [
|
export const allowedMongoProxyEndpoints: ReadonlyArray<string> = [
|
||||||
MongoProxyEndpoints.Development,
|
MongoProxyEndpoints.Local,
|
||||||
MongoProxyEndpoints.Mpac,
|
MongoProxyEndpoints.Mpac,
|
||||||
MongoProxyEndpoints.Prod,
|
MongoProxyEndpoints.Prod,
|
||||||
MongoProxyEndpoints.Fairfax,
|
MongoProxyEndpoints.Fairfax,
|
||||||
@ -98,6 +105,14 @@ export const allowedCassandraProxyEndpoints: ReadonlyArray<string> = [
|
|||||||
CassandraProxyEndpoints.Mooncake,
|
CassandraProxyEndpoints.Mooncake,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const allowedCassandraProxyEndpoints_ToBeDeprecated: ReadonlyArray<string> = [
|
||||||
|
"https://main.documentdb.ext.azure.com",
|
||||||
|
"https://main.documentdb.ext.azure.cn",
|
||||||
|
"https://main.documentdb.ext.azure.us",
|
||||||
|
"https://main.cosmos.ext.azure",
|
||||||
|
"https://localhost:12901",
|
||||||
|
];
|
||||||
|
|
||||||
export const CassandraProxyOutboundIPs: { [key: string]: string[] } = {
|
export const CassandraProxyOutboundIPs: { [key: string]: string[] } = {
|
||||||
[CassandraProxyEndpoints.Mpac]: ["40.113.96.14", "104.42.11.145"],
|
[CassandraProxyEndpoints.Mpac]: ["40.113.96.14", "104.42.11.145"],
|
||||||
[CassandraProxyEndpoints.Prod]: ["137.117.230.240", "168.61.72.237"],
|
[CassandraProxyEndpoints.Prod]: ["137.117.230.240", "168.61.72.237"],
|
||||||
@ -129,3 +144,31 @@ export const allowedJunoOrigins: ReadonlyArray<string> = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const allowedNotebookServerUrls: ReadonlyArray<string> = [];
|
export const allowedNotebookServerUrls: ReadonlyArray<string> = [];
|
||||||
|
|
||||||
|
//
|
||||||
|
// Temporary function to determine if a portal backend API is supported by the
|
||||||
|
// new backend in this environment.
|
||||||
|
//
|
||||||
|
// TODO: Remove this function once new backend migration is completed for all environments.
|
||||||
|
//
|
||||||
|
export function useNewPortalBackendEndpoint(backendApi: string): boolean {
|
||||||
|
// This maps backend APIs to the environments supported by the new backend.
|
||||||
|
const newBackendApiEnvironmentMap: { [key: string]: string[] } = {
|
||||||
|
[BackendApi.GenerateToken]: [
|
||||||
|
PortalBackendEndpoints.Development,
|
||||||
|
PortalBackendEndpoints.Mpac,
|
||||||
|
PortalBackendEndpoints.Prod,
|
||||||
|
],
|
||||||
|
[BackendApi.PortalSettings]: [
|
||||||
|
PortalBackendEndpoints.Development,
|
||||||
|
PortalBackendEndpoints.Mpac,
|
||||||
|
PortalBackendEndpoints.Prod,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!newBackendApiEnvironmentMap[backendApi] || !configContext.PORTAL_BACKEND_ENDPOINT) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newBackendApiEnvironmentMap[backendApi].includes(configContext.PORTAL_BACKEND_ENDPOINT);
|
||||||
|
}
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
|
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
import { configContext } from "../../../../ConfigContext";
|
const apiVersion = "2024-02-15-preview";
|
||||||
const apiVersion = "2023-09-15-preview";
|
|
||||||
|
|
||||||
/* Lists the Cassandra keyspaces under an existing Azure Cosmos DB database account. */
|
/* Lists the Cassandra keyspaces under an existing Azure Cosmos DB database account. */
|
||||||
export async function listCassandraKeyspaces(
|
export async function listCassandraKeyspaces(
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
|
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
import { configContext } from "../../../../ConfigContext";
|
const apiVersion = "2024-02-15-preview";
|
||||||
const apiVersion = "2023-09-15-preview";
|
|
||||||
|
|
||||||
/* Retrieves the metrics determined by the given filter for the given database account and collection. */
|
/* Retrieves the metrics determined by the given filter for the given database account and collection. */
|
||||||
export async function listMetrics(
|
export async function listMetrics(
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
|
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
import { configContext } from "../../../../ConfigContext";
|
const apiVersion = "2024-02-15-preview";
|
||||||
const apiVersion = "2023-09-15-preview";
|
|
||||||
|
|
||||||
/* Retrieves the metrics determined by the given filter for the given collection, split by partition. */
|
/* Retrieves the metrics determined by the given filter for the given collection, split by partition. */
|
||||||
export async function listMetrics(
|
export async function listMetrics(
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
|
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
import { configContext } from "../../../../ConfigContext";
|
const apiVersion = "2024-02-15-preview";
|
||||||
const apiVersion = "2023-09-15-preview";
|
|
||||||
|
|
||||||
/* Retrieves the metrics determined by the given filter for the given collection and region, split by partition. */
|
/* Retrieves the metrics determined by the given filter for the given collection and region, split by partition. */
|
||||||
export async function listMetrics(
|
export async function listMetrics(
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
|
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
import { configContext } from "../../../../ConfigContext";
|
const apiVersion = "2024-02-15-preview";
|
||||||
const apiVersion = "2023-09-15-preview";
|
|
||||||
|
|
||||||
/* Retrieves the metrics determined by the given filter for the given database account, collection and region. */
|
/* Retrieves the metrics determined by the given filter for the given database account, collection and region. */
|
||||||
export async function listMetrics(
|
export async function listMetrics(
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
|
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
import { configContext } from "../../../../ConfigContext";
|
const apiVersion = "2024-02-15-preview";
|
||||||
const apiVersion = "2023-09-15-preview";
|
|
||||||
|
|
||||||
/* Retrieves the metrics determined by the given filter for the given database account and database. */
|
/* Retrieves the metrics determined by the given filter for the given database account and database. */
|
||||||
export async function listMetrics(
|
export async function listMetrics(
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
|
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
import { configContext } from "../../../../ConfigContext";
|
const apiVersion = "2024-02-15-preview";
|
||||||
const apiVersion = "2023-09-15-preview";
|
|
||||||
|
|
||||||
/* Retrieves the metrics determined by the given filter for the given database account and region. */
|
/* Retrieves the metrics determined by the given filter for the given database account and region. */
|
||||||
export async function listMetrics(
|
export async function listMetrics(
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
|
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
import { configContext } from "../../../../ConfigContext";
|
const apiVersion = "2024-02-15-preview";
|
||||||
const apiVersion = "2023-09-15-preview";
|
|
||||||
|
|
||||||
/* Retrieves the properties of an existing Azure Cosmos DB database account. */
|
/* Retrieves the properties of an existing Azure Cosmos DB database account. */
|
||||||
export async function get(
|
export async function get(
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
|
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
import { configContext } from "../../../../ConfigContext";
|
const apiVersion = "2024-02-15-preview";
|
||||||
const apiVersion = "2023-09-15-preview";
|
|
||||||
|
|
||||||
/* Lists the graphs under an existing Azure Cosmos DB database account. */
|
/* Lists the graphs under an existing Azure Cosmos DB database account. */
|
||||||
export async function listGraphs(
|
export async function listGraphs(
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
|
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
import { configContext } from "../../../../ConfigContext";
|
const apiVersion = "2024-02-15-preview";
|
||||||
const apiVersion = "2023-09-15-preview";
|
|
||||||
|
|
||||||
/* Lists the Gremlin databases under an existing Azure Cosmos DB database account. */
|
/* Lists the Gremlin databases under an existing Azure Cosmos DB database account. */
|
||||||
export async function listGremlinDatabases(
|
export async function listGremlinDatabases(
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
|
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
import { configContext } from "../../../../ConfigContext";
|
const apiVersion = "2024-02-15-preview";
|
||||||
const apiVersion = "2023-09-15-preview";
|
|
||||||
|
|
||||||
/* List Cosmos DB locations and their properties */
|
/* List Cosmos DB locations and their properties */
|
||||||
export async function list(subscriptionId: string): Promise<Types.LocationListResult | Types.CloudError> {
|
export async function list(subscriptionId: string): Promise<Types.LocationListResult | Types.CloudError> {
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
|
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
import { configContext } from "../../../../ConfigContext";
|
const apiVersion = "2024-02-15-preview";
|
||||||
const apiVersion = "2023-09-15-preview";
|
|
||||||
|
|
||||||
/* Lists the MongoDB databases under an existing Azure Cosmos DB database account. */
|
/* Lists the MongoDB databases under an existing Azure Cosmos DB database account. */
|
||||||
export async function listMongoDBDatabases(
|
export async function listMongoDBDatabases(
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
|
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
import { configContext } from "../../../../ConfigContext";
|
const apiVersion = "2024-02-15-preview";
|
||||||
const apiVersion = "2023-09-15-preview";
|
|
||||||
|
|
||||||
/* Lists all of the available Cosmos DB Resource Provider operations. */
|
/* Lists all of the available Cosmos DB Resource Provider operations. */
|
||||||
export async function list(): Promise<Types.OperationListResult> {
|
export async function list(): Promise<Types.OperationListResult> {
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
|
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
import { configContext } from "../../../../ConfigContext";
|
const apiVersion = "2024-02-15-preview";
|
||||||
const apiVersion = "2023-09-15-preview";
|
|
||||||
|
|
||||||
/* Retrieves the metrics determined by the given filter for the given partition key range id. */
|
/* Retrieves the metrics determined by the given filter for the given partition key range id. */
|
||||||
export async function listMetrics(
|
export async function listMetrics(
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
|
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
import { configContext } from "../../../../ConfigContext";
|
const apiVersion = "2024-02-15-preview";
|
||||||
const apiVersion = "2023-09-15-preview";
|
|
||||||
|
|
||||||
/* Retrieves the metrics determined by the given filter for the given partition key range id and region. */
|
/* Retrieves the metrics determined by the given filter for the given partition key range id and region. */
|
||||||
export async function listMetrics(
|
export async function listMetrics(
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
|
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
import { configContext } from "../../../../ConfigContext";
|
const apiVersion = "2024-02-15-preview";
|
||||||
const apiVersion = "2023-09-15-preview";
|
|
||||||
|
|
||||||
/* Retrieves the metrics determined by the given filter for the given database account. This url is only for PBS and Replication Latency data */
|
/* Retrieves the metrics determined by the given filter for the given database account. This url is only for PBS and Replication Latency data */
|
||||||
export async function listMetrics(
|
export async function listMetrics(
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
|
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
import { configContext } from "../../../../ConfigContext";
|
const apiVersion = "2024-02-15-preview";
|
||||||
const apiVersion = "2023-09-15-preview";
|
|
||||||
|
|
||||||
/* Retrieves the metrics determined by the given filter for the given account, source and target region. This url is only for PBS and Replication Latency data */
|
/* Retrieves the metrics determined by the given filter for the given account, source and target region. This url is only for PBS and Replication Latency data */
|
||||||
export async function listMetrics(
|
export async function listMetrics(
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
|
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
import { configContext } from "../../../../ConfigContext";
|
const apiVersion = "2024-02-15-preview";
|
||||||
const apiVersion = "2023-09-15-preview";
|
|
||||||
|
|
||||||
/* Retrieves the metrics determined by the given filter for the given account target region. This url is only for PBS and Replication Latency data */
|
/* Retrieves the metrics determined by the given filter for the given account target region. This url is only for PBS and Replication Latency data */
|
||||||
export async function listMetrics(
|
export async function listMetrics(
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
|
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
import { configContext } from "../../../../ConfigContext";
|
const apiVersion = "2024-02-15-preview";
|
||||||
const apiVersion = "2023-09-15-preview";
|
|
||||||
|
|
||||||
/* Lists the SQL databases under an existing Azure Cosmos DB database account. */
|
/* Lists the SQL databases under an existing Azure Cosmos DB database account. */
|
||||||
export async function listSqlDatabases(
|
export async function listSqlDatabases(
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
|
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
import { configContext } from "../../../../ConfigContext";
|
const apiVersion = "2024-02-15-preview";
|
||||||
const apiVersion = "2023-09-15-preview";
|
|
||||||
|
|
||||||
/* Lists the Tables under an existing Azure Cosmos DB database account. */
|
/* Lists the Tables under an existing Azure Cosmos DB database account. */
|
||||||
export async function listTables(
|
export async function listTables(
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json
|
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* The List operation response, that contains the client encryption keys and their properties. */
|
/* The List operation response, that contains the client encryption keys and their properties. */
|
||||||
@ -566,12 +566,14 @@ export interface DatabaseAccountGetProperties {
|
|||||||
minimalTlsVersion?: MinimalTlsVersion;
|
minimalTlsVersion?: MinimalTlsVersion;
|
||||||
|
|
||||||
/* Indicates the status of the Customer Managed Key feature on the account. In case there are errors, the property provides troubleshooting guidance. */
|
/* Indicates the status of the Customer Managed Key feature on the account. In case there are errors, the property provides troubleshooting guidance. */
|
||||||
customerManagedKeyStatus?: CustomerManagedKeyStatus;
|
customerManagedKeyStatus?: string;
|
||||||
|
|
||||||
/* Flag to indicate enabling/disabling of Priority Based Execution Preview feature on the account */
|
/* Flag to indicate enabling/disabling of Priority Based Execution Preview feature on the account */
|
||||||
enablePriorityBasedExecution?: boolean;
|
enablePriorityBasedExecution?: boolean;
|
||||||
/* Enum to indicate default Priority Level of request for Priority Based Execution. */
|
/* Enum to indicate default Priority Level of request for Priority Based Execution. */
|
||||||
defaultPriorityLevel?: DefaultPriorityLevel;
|
defaultPriorityLevel?: DefaultPriorityLevel;
|
||||||
|
|
||||||
|
/* Flag to indicate enabling/disabling of Per-Region Per-partition autoscale Preview feature on the account */
|
||||||
|
enablePerRegionPerPartitionAutoscale?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Properties to create and update Azure Cosmos DB database accounts. */
|
/* Properties to create and update Azure Cosmos DB database accounts. */
|
||||||
@ -663,12 +665,14 @@ export interface DatabaseAccountCreateUpdateProperties {
|
|||||||
minimalTlsVersion?: MinimalTlsVersion;
|
minimalTlsVersion?: MinimalTlsVersion;
|
||||||
|
|
||||||
/* Indicates the status of the Customer Managed Key feature on the account. In case there are errors, the property provides troubleshooting guidance. */
|
/* Indicates the status of the Customer Managed Key feature on the account. In case there are errors, the property provides troubleshooting guidance. */
|
||||||
customerManagedKeyStatus?: CustomerManagedKeyStatus;
|
customerManagedKeyStatus?: string;
|
||||||
|
|
||||||
/* Flag to indicate enabling/disabling of Priority Based Execution Preview feature on the account */
|
/* Flag to indicate enabling/disabling of Priority Based Execution Preview feature on the account */
|
||||||
enablePriorityBasedExecution?: boolean;
|
enablePriorityBasedExecution?: boolean;
|
||||||
/* Enum to indicate default Priority Level of request for Priority Based Execution. */
|
/* Enum to indicate default Priority Level of request for Priority Based Execution. */
|
||||||
defaultPriorityLevel?: DefaultPriorityLevel;
|
defaultPriorityLevel?: DefaultPriorityLevel;
|
||||||
|
|
||||||
|
/* Flag to indicate enabling/disabling of Per-Region Per-partition autoscale Preview feature on the account */
|
||||||
|
enablePerRegionPerPartitionAutoscale?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Parameters to create and update Cosmos DB database accounts. */
|
/* Parameters to create and update Cosmos DB database accounts. */
|
||||||
@ -763,12 +767,14 @@ export interface DatabaseAccountUpdateProperties {
|
|||||||
minimalTlsVersion?: MinimalTlsVersion;
|
minimalTlsVersion?: MinimalTlsVersion;
|
||||||
|
|
||||||
/* Indicates the status of the Customer Managed Key feature on the account. In case there are errors, the property provides troubleshooting guidance. */
|
/* Indicates the status of the Customer Managed Key feature on the account. In case there are errors, the property provides troubleshooting guidance. */
|
||||||
customerManagedKeyStatus?: CustomerManagedKeyStatus;
|
customerManagedKeyStatus?: string;
|
||||||
|
|
||||||
/* Flag to indicate enabling/disabling of Priority Based Execution Preview feature on the account */
|
/* Flag to indicate enabling/disabling of Priority Based Execution Preview feature on the account */
|
||||||
enablePriorityBasedExecution?: boolean;
|
enablePriorityBasedExecution?: boolean;
|
||||||
/* Enum to indicate default Priority Level of request for Priority Based Execution. */
|
/* Enum to indicate default Priority Level of request for Priority Based Execution. */
|
||||||
defaultPriorityLevel?: DefaultPriorityLevel;
|
defaultPriorityLevel?: DefaultPriorityLevel;
|
||||||
|
|
||||||
|
/* Flag to indicate enabling/disabling of Per-Region Per-partition autoscale Preview feature on the account */
|
||||||
|
enablePerRegionPerPartitionAutoscale?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Parameters for patching Azure Cosmos DB database account properties. */
|
/* Parameters for patching Azure Cosmos DB database account properties. */
|
||||||
@ -1256,6 +1262,9 @@ export interface SqlContainerResource {
|
|||||||
|
|
||||||
/* The configuration for defining Materialized Views. This must be specified only for creating a Materialized View container. */
|
/* The configuration for defining Materialized Views. This must be specified only for creating a Materialized View container. */
|
||||||
materializedViewDefinition?: MaterializedViewDefinition;
|
materializedViewDefinition?: MaterializedViewDefinition;
|
||||||
|
|
||||||
|
/* List of computed properties */
|
||||||
|
computedProperties?: ComputedProperty[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Cosmos DB indexing policy */
|
/* Cosmos DB indexing policy */
|
||||||
@ -1325,6 +1334,14 @@ export interface SpatialSpec {
|
|||||||
/* Indicates the spatial type of index. */
|
/* Indicates the spatial type of index. */
|
||||||
export type SpatialType = "Point" | "LineString" | "Polygon" | "MultiPolygon";
|
export type SpatialType = "Point" | "LineString" | "Polygon" | "MultiPolygon";
|
||||||
|
|
||||||
|
/* The definition of a computed property */
|
||||||
|
export interface ComputedProperty {
|
||||||
|
/* The name of a computed property, for example - "cp_lowerName" */
|
||||||
|
name?: string;
|
||||||
|
/* The query that evaluates the value for computed property, for example - "SELECT VALUE LOWER(c.name) FROM c" */
|
||||||
|
query?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/* The configuration of the partition key to be used for partitioning data into multiple partitions */
|
/* The configuration of the partition key to be used for partitioning data into multiple partitions */
|
||||||
export interface ContainerPartitionKey {
|
export interface ContainerPartitionKey {
|
||||||
/* List of paths using which data within the container can be partitioned */
|
/* List of paths using which data within the container can be partitioned */
|
||||||
@ -1929,6 +1946,8 @@ export interface RestoreParametersBase {
|
|||||||
restoreSource?: string;
|
restoreSource?: string;
|
||||||
/* Time to which the account has to be restored (ISO-8601 format). */
|
/* Time to which the account has to be restored (ISO-8601 format). */
|
||||||
restoreTimestampInUtc?: string;
|
restoreTimestampInUtc?: string;
|
||||||
|
/* Specifies whether the restored account will have Time-To-Live disabled upon the successful restore. */
|
||||||
|
restoreWithTtlDisabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Parameters to indicate the information about the restore. */
|
/* Parameters to indicate the information about the restore. */
|
||||||
@ -2072,19 +2091,5 @@ export type ContinuousTier = "Continuous7Days" | "Continuous30Days";
|
|||||||
/* Indicates the minimum allowed Tls version. The default is Tls 1.0, except for Cassandra and Mongo API's, which only work with Tls 1.2. */
|
/* Indicates the minimum allowed Tls version. The default is Tls 1.0, except for Cassandra and Mongo API's, which only work with Tls 1.2. */
|
||||||
export type MinimalTlsVersion = "Tls" | "Tls11" | "Tls12";
|
export type MinimalTlsVersion = "Tls" | "Tls11" | "Tls12";
|
||||||
|
|
||||||
/* Indicates the status of the Customer Managed Key feature on the account. In case there are errors, the property provides troubleshooting guidance. */
|
|
||||||
export type CustomerManagedKeyStatus =
|
|
||||||
| "Access to your account is currently revoked because the Azure Cosmos DB service is unable to obtain the AAD authentication token for the account's default identity; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#azure-active-directory-token-acquisition-error (4000)."
|
|
||||||
| "Access to your account is currently revoked because the Azure Cosmos DB account's key vault key URI does not follow the expected format; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#improper-syntax-detected-on-the-key-vault-uri-property (4006)."
|
|
||||||
| "Access to your account is currently revoked because the current default identity no longer has permission to the associated Key Vault key; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#default-identity-is-unauthorized-to-access-the-azure-key-vault-key (4002)."
|
|
||||||
| "Access to your account is currently revoked because the Azure Key Vault DNS name specified by the account's keyvaultkeyuri property could not be resolved; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#unable-to-resolve-the-key-vaults-dns (4009)."
|
|
||||||
| "Access to your account is currently revoked because the correspondent key is not found on the specified Key Vault; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#azure-key-vault-resource-not-found (4003)."
|
|
||||||
| "Access to your account is currently revoked because the Azure Cosmos DB service is unable to wrap or unwrap the key; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#internal-unwrapping-procedure-error (4005)."
|
|
||||||
| "Access to your account is currently revoked because the Azure Cosmos DB account has an undefined default identity; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#invalid-azure-cosmos-db-default-identity (4015)."
|
|
||||||
| "Access to your account is currently revoked because the access rules are blocking outbound requests to the Azure Key Vault service; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide (4016)."
|
|
||||||
| "Access to your account is currently revoked because the correspondent Azure Key Vault was not found; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#azure-key-vault-resource-not-found (4017)."
|
|
||||||
| "Access to your account is currently revoked; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide"
|
|
||||||
| "Access to the configured customer managed key confirmed.";
|
|
||||||
|
|
||||||
/* Enum to indicate default priorityLevel of requests */
|
/* Enum to indicate default priorityLevel of requests */
|
||||||
export type DefaultPriorityLevel = "High" | "Low";
|
export type DefaultPriorityLevel = "High" | "Low";
|
||||||
|
@ -2,7 +2,6 @@ import { createUri } from "Common/UrlUtility";
|
|||||||
import { DATA_EXPLORER_RPC_VERSION } from "Contracts/DataExplorerMessagesContract";
|
import { DATA_EXPLORER_RPC_VERSION } from "Contracts/DataExplorerMessagesContract";
|
||||||
import { FABRIC_RPC_VERSION, FabricMessageV2 } from "Contracts/FabricMessagesContract";
|
import { FABRIC_RPC_VERSION, FabricMessageV2 } from "Contracts/FabricMessagesContract";
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
|
||||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
|
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
|
||||||
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
|
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
|
||||||
@ -90,6 +89,7 @@ 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 = [FABRIC_RPC_VERSION];
|
const SUPPORTED_FABRIC_VERSIONS = [FABRIC_RPC_VERSION];
|
||||||
|
|
||||||
|
let firstContainerOpened = false;
|
||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
return new Promise<Explorer>((resolve) => {
|
return new Promise<Explorer>((resolve) => {
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
@ -121,7 +121,10 @@ async function configureFabric(): Promise<Explorer> {
|
|||||||
await scheduleRefreshDatabaseResourceToken(true);
|
await scheduleRefreshDatabaseResourceToken(true);
|
||||||
resolve(explorer);
|
resolve(explorer);
|
||||||
await explorer.refreshAllDatabases();
|
await explorer.refreshAllDatabases();
|
||||||
openFirstContainer(explorer, userContext.fabricContext.databaseConnectionInfo.databaseId);
|
if (userContext.fabricContext.isVisible && !firstContainerOpened) {
|
||||||
|
firstContainerOpened = true;
|
||||||
|
openFirstContainer(explorer, userContext.fabricContext.databaseConnectionInfo.databaseId);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "newContainer":
|
case "newContainer":
|
||||||
@ -132,8 +135,16 @@ async function configureFabric(): Promise<Explorer> {
|
|||||||
handleCachedDataMessage(data);
|
handleCachedDataMessage(data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "setToolbarStatus": {
|
case "explorerVisible": {
|
||||||
useCommandBar.getState().setIsHidden(data.message.visible === false);
|
userContext.fabricContext.isVisible = data.message.visible;
|
||||||
|
if (
|
||||||
|
userContext.fabricContext.isVisible &&
|
||||||
|
!firstContainerOpened &&
|
||||||
|
userContext?.fabricContext?.databaseConnectionInfo?.databaseId !== undefined
|
||||||
|
) {
|
||||||
|
firstContainerOpened = true;
|
||||||
|
openFirstContainer(explorer, userContext.fabricContext.databaseConnectionInfo.databaseId);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -327,12 +338,13 @@ function configureHostedWithResourceToken(config: ResourceToken): Explorer {
|
|||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createExplorerFabric(params: { connectionId: string }): Explorer {
|
function createExplorerFabric(params: { connectionId: string; isVisible: boolean }): Explorer {
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
fabricContext: {
|
fabricContext: {
|
||||||
connectionId: params.connectionId,
|
connectionId: params.connectionId,
|
||||||
databaseConnectionInfo: undefined,
|
databaseConnectionInfo: undefined,
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
|
isVisible: params.isVisible ?? true,
|
||||||
},
|
},
|
||||||
authType: AuthType.ConnectionString,
|
authType: AuthType.ConnectionString,
|
||||||
databaseAccount: {
|
databaseAccount: {
|
||||||
@ -485,6 +497,7 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
|
|||||||
ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT),
|
ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT),
|
||||||
MONGO_PROXY_ENDPOINT: inputs.mongoProxyEndpoint,
|
MONGO_PROXY_ENDPOINT: inputs.mongoProxyEndpoint,
|
||||||
CASSANDRA_PROXY_ENDPOINT: inputs.cassandraProxyEndpoint,
|
CASSANDRA_PROXY_ENDPOINT: inputs.cassandraProxyEndpoint,
|
||||||
|
PORTAL_BACKEND_ENDPOINT: inputs.portalBackendEndpoint,
|
||||||
});
|
});
|
||||||
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { clamp } from "@fluentui/react";
|
||||||
import create, { UseStore } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import { CollectionTabKind } from "../Contracts/ViewModels";
|
import { CollectionTabKind } from "../Contracts/ViewModels";
|
||||||
@ -29,6 +30,11 @@ export interface TabsState {
|
|||||||
setQueryCopilotTabInitialInput: (input: string) => void;
|
setQueryCopilotTabInitialInput: (input: string) => void;
|
||||||
setIsTabExecuting: (state: boolean) => void;
|
setIsTabExecuting: (state: boolean) => void;
|
||||||
setIsQueryErrorThrown: (state: boolean) => void;
|
setIsQueryErrorThrown: (state: boolean) => void;
|
||||||
|
getCurrentTabIndex: () => number;
|
||||||
|
selectTabByIndex: (index: number) => void;
|
||||||
|
selectLeftTab: () => void;
|
||||||
|
selectRightTab: () => void;
|
||||||
|
closeActiveTab: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ReactTabKind {
|
export enum ReactTabKind {
|
||||||
@ -105,7 +111,7 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
if (updatedTabs.length === 0 && configContext.platform !== Platform.Fabric) {
|
if (updatedTabs.length === 0 && configContext.platform !== Platform.Fabric) {
|
||||||
set({ activeTab: undefined, activeReactTab: ReactTabKind.Home });
|
set({ activeTab: undefined, activeReactTab: undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tab.tabId === activeTab.tabId && tabIndex !== -1) {
|
if (tab.tabId === activeTab.tabId && tabIndex !== -1) {
|
||||||
@ -143,7 +149,7 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (get().openedTabs.length === 0 && configContext.platform !== Platform.Fabric) {
|
if (get().openedTabs.length === 0 && configContext.platform !== Platform.Fabric) {
|
||||||
set({ activeTab: undefined, activeReactTab: ReactTabKind.Home });
|
set({ activeTab: undefined, activeReactTab: undefined });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -175,4 +181,44 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
|
|||||||
setIsQueryErrorThrown: (state: boolean) => {
|
setIsQueryErrorThrown: (state: boolean) => {
|
||||||
set({ isQueryErrorThrown: state });
|
set({ isQueryErrorThrown: state });
|
||||||
},
|
},
|
||||||
|
getCurrentTabIndex: () => {
|
||||||
|
const state = get();
|
||||||
|
if (state.activeReactTab !== undefined) {
|
||||||
|
return state.openedReactTabs.indexOf(state.activeReactTab);
|
||||||
|
} else if (state.activeTab !== undefined) {
|
||||||
|
const nonReactTabIndex = state.openedTabs.indexOf(state.activeTab);
|
||||||
|
if (nonReactTabIndex !== -1) {
|
||||||
|
return state.openedReactTabs.length + nonReactTabIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
},
|
||||||
|
selectTabByIndex: (index: number) => {
|
||||||
|
const state = get();
|
||||||
|
const totalTabCount = state.openedReactTabs.length + state.openedTabs.length;
|
||||||
|
const clampedIndex = clamp(index, totalTabCount - 1, 0);
|
||||||
|
|
||||||
|
if (clampedIndex < state.openedReactTabs.length) {
|
||||||
|
set({ activeTab: undefined, activeReactTab: state.openedReactTabs[clampedIndex] });
|
||||||
|
} else {
|
||||||
|
set({ activeTab: state.openedTabs[clampedIndex - state.openedReactTabs.length], activeReactTab: undefined });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectLeftTab: () => {
|
||||||
|
const state = get();
|
||||||
|
state.selectTabByIndex(state.getCurrentTabIndex() - 1);
|
||||||
|
},
|
||||||
|
selectRightTab: () => {
|
||||||
|
const state = get();
|
||||||
|
state.selectTabByIndex(state.getCurrentTabIndex() + 1);
|
||||||
|
},
|
||||||
|
closeActiveTab: () => {
|
||||||
|
const state = get();
|
||||||
|
if (state.activeReactTab !== undefined) {
|
||||||
|
state.closeReactTab(state.activeReactTab);
|
||||||
|
} else if (state.activeTab !== undefined) {
|
||||||
|
state.closeTab(state.activeTab);
|
||||||
|
}
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateUniqueName } from "../utils/shared";
|
import { generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
||||||
import { waitForExplorer } from "../utils/waitForExplorer";
|
import { waitForExplorer } from "../utils/waitForExplorer";
|
||||||
jest.setTimeout(120000);
|
jest.setTimeout(120000);
|
||||||
|
|
||||||
test("Cassandra keyspace and table CRUD", async () => {
|
test("Cassandra keyspace and table CRUD", async () => {
|
||||||
const keyspaceId = generateUniqueName("keyspace");
|
const keyspaceId = generateUniqueName("keyspace");
|
||||||
const tableId = generateUniqueName("table");
|
const tableId = generateUniqueName("table");
|
||||||
|
|
||||||
|
// We can't retrieve AZ CLI credentials from the browser so we get them here.
|
||||||
|
const token = await getAzureCLICredentialsToken();
|
||||||
page.setDefaultTimeout(50000);
|
page.setDefaultTimeout(50000);
|
||||||
|
|
||||||
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-cassandra-runner");
|
await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-cassandra-runner&token=${token}`);
|
||||||
await page.waitForSelector("iframe");
|
await page.waitForSelector("iframe");
|
||||||
const explorer = await waitForExplorer();
|
const explorer = await waitForExplorer();
|
||||||
|
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateDatabaseNameWithTimestamp, generateUniqueName } from "../utils/shared";
|
import { generateDatabaseNameWithTimestamp, generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
||||||
import { waitForExplorer } from "../utils/waitForExplorer";
|
import { waitForExplorer } from "../utils/waitForExplorer";
|
||||||
jest.setTimeout(240000);
|
jest.setTimeout(240000);
|
||||||
|
|
||||||
test("Graph CRUD", async () => {
|
test("Graph CRUD", async () => {
|
||||||
const databaseId = generateDatabaseNameWithTimestamp();
|
const databaseId = generateDatabaseNameWithTimestamp();
|
||||||
const containerId = generateUniqueName("container");
|
const containerId = generateUniqueName("container");
|
||||||
|
|
||||||
|
// We can't retrieve AZ CLI credentials from the browser so we get them here.
|
||||||
|
const token = await getAzureCLICredentialsToken();
|
||||||
page.setDefaultTimeout(50000);
|
page.setDefaultTimeout(50000);
|
||||||
|
|
||||||
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-gremlin-runner");
|
await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-gremlin-runner&token=${token}`);
|
||||||
const explorer = await waitForExplorer();
|
const explorer = await waitForExplorer();
|
||||||
|
|
||||||
// Create new database and graph
|
// Create new database and graph
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateDatabaseNameWithTimestamp, generateUniqueName } from "../utils/shared";
|
import { generateDatabaseNameWithTimestamp, generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
||||||
import { waitForExplorer } from "../utils/waitForExplorer";
|
import { waitForExplorer } from "../utils/waitForExplorer";
|
||||||
jest.setTimeout(240000);
|
jest.setTimeout(240000);
|
||||||
|
|
||||||
test("Mongo CRUD", async () => {
|
test("Mongo CRUD", async () => {
|
||||||
const databaseId = generateDatabaseNameWithTimestamp();
|
const databaseId = generateDatabaseNameWithTimestamp();
|
||||||
const containerId = generateUniqueName("container");
|
const containerId = generateUniqueName("container");
|
||||||
|
|
||||||
|
// We can't retrieve AZ CLI credentials from the browser so we get them here.
|
||||||
|
const token = await getAzureCLICredentialsToken();
|
||||||
page.setDefaultTimeout(50000);
|
page.setDefaultTimeout(50000);
|
||||||
|
|
||||||
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-mongo-runner");
|
await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-mongo-runner&token=${token}`);
|
||||||
const explorer = await waitForExplorer();
|
const explorer = await waitForExplorer();
|
||||||
|
|
||||||
// Create new database and collection
|
// Create new database and collection
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateDatabaseNameWithTimestamp, generateUniqueName } from "../utils/shared";
|
import { generateDatabaseNameWithTimestamp, generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
||||||
import { waitForExplorer } from "../utils/waitForExplorer";
|
import { waitForExplorer } from "../utils/waitForExplorer";
|
||||||
jest.setTimeout(240000);
|
jest.setTimeout(240000);
|
||||||
|
|
||||||
test("Mongo CRUD", async () => {
|
test("Mongo CRUD", async () => {
|
||||||
const databaseId = generateDatabaseNameWithTimestamp();
|
const databaseId = generateDatabaseNameWithTimestamp();
|
||||||
const containerId = generateUniqueName("container");
|
const containerId = generateUniqueName("container");
|
||||||
|
|
||||||
|
// We can't retrieve AZ CLI credentials from the browser so we get them here.
|
||||||
|
const token = await getAzureCLICredentialsToken();
|
||||||
page.setDefaultTimeout(50000);
|
page.setDefaultTimeout(50000);
|
||||||
|
|
||||||
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-mongo32-runner");
|
await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-mongo32-runner&token=${token}`);
|
||||||
const explorer = await waitForExplorer();
|
const explorer = await waitForExplorer();
|
||||||
|
|
||||||
// Create new database and collection
|
// Create new database and collection
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
|
import { getAzureCLICredentialsToken } from "../utils/shared";
|
||||||
|
|
||||||
test("Self Serve", async () => {
|
test("Self Serve", async () => {
|
||||||
await page.goto("https://localhost:1234/testExplorer.html?iframeSrc=selfServe.html");
|
// We can't retrieve AZ CLI credentials from the browser so we get them here.
|
||||||
|
const token = await getAzureCLICredentialsToken();
|
||||||
|
|
||||||
|
await page.goto(`https://localhost:1234/testExplorer.html?iframeSrc=selfServe.html&token=${token}`);
|
||||||
const handle = await page.waitForSelector("iframe");
|
const handle = await page.waitForSelector("iframe");
|
||||||
const frame = await handle.contentFrame();
|
const frame = await handle.contentFrame();
|
||||||
|
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateUniqueName } from "../utils/shared";
|
import { generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
||||||
import { waitForExplorer } from "../utils/waitForExplorer";
|
import { waitForExplorer } from "../utils/waitForExplorer";
|
||||||
jest.setTimeout(120000);
|
jest.setTimeout(120000);
|
||||||
|
|
||||||
test("SQL CRUD", async () => {
|
test("SQL CRUD", async () => {
|
||||||
const databaseId = generateUniqueName("db");
|
const databaseId = generateUniqueName("db");
|
||||||
const containerId = generateUniqueName("container");
|
const containerId = generateUniqueName("container");
|
||||||
|
|
||||||
|
// We can't retrieve AZ CLI credentials from the browser so we get them here.
|
||||||
|
const token = await getAzureCLICredentialsToken();
|
||||||
page.setDefaultTimeout(50000);
|
page.setDefaultTimeout(50000);
|
||||||
|
|
||||||
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-sql-runner-west-us");
|
await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-sql-runner-west-us&token=${token}`);
|
||||||
const explorer = await waitForExplorer();
|
const explorer = await waitForExplorer();
|
||||||
|
|
||||||
await explorer.click('[data-test="New Container"]');
|
await explorer.click('[data-test="New Container"]');
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user