mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-26 12:21:23 +00:00
Compare commits
15 Commits
users/tara
...
add-dp-rba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10a8505b9a | ||
|
|
ef7c2fe2f7 | ||
|
|
4c7aca95e1 | ||
|
|
2243ad895a | ||
|
|
b2d5f91fe1 | ||
|
|
a712193477 | ||
|
|
5ee411693c | ||
|
|
16c7b2567b | ||
|
|
78d9a0cd8d | ||
|
|
c6ad538559 | ||
|
|
2bc09a6efe | ||
|
|
d3a3033b25 | ||
|
|
6bdc714e11 | ||
|
|
5042f28229 | ||
|
|
e1430fd06f |
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@@ -8,9 +8,6 @@ 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
|
||||||
@@ -137,7 +134,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||||
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -148,18 +145,11 @@ 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,10 +9,6 @@ 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"
|
||||||
@@ -20,17 +16,10 @@ jobs:
|
|||||||
name: "Cleanup Test Database Accounts"
|
name: "Cleanup Test Database Accounts"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_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:
|
||||||
|
|||||||
@@ -76,10 +76,6 @@ 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
|
||||||
@@ -134,6 +130,7 @@ 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
|
||||||
|
|||||||
1888
package-lock.json
generated
1888
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.5.2",
|
"@azure/identity": "1.2.1",
|
||||||
"@azure/ms-rest-nodeauth": "3.1.1",
|
"@azure/ms-rest-nodeauth": "3.0.7",
|
||||||
"@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,7 +46,6 @@
|
|||||||
"@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",
|
||||||
@@ -55,7 +54,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": "7.8.5",
|
"d3": "6.1.1",
|
||||||
"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",
|
||||||
@@ -70,14 +69,12 @@
|
|||||||
"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",
|
||||||
@@ -101,12 +98,10 @@
|
|||||||
"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",
|
||||||
"tinykeys": "2.1.0",
|
"underscore": "1.9.1",
|
||||||
"underscore": "1.12.1",
|
|
||||||
"utility-types": "3.10.0",
|
"utility-types": "3.10.0",
|
||||||
"zustand": "3.5.0"
|
"zustand": "3.5.0"
|
||||||
},
|
},
|
||||||
@@ -175,25 +170,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.7",
|
"node-fetch": "2.6.1",
|
||||||
"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": "12.0.1",
|
"react-dev-utils": "11.0.4",
|
||||||
"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.22.15",
|
"typedoc": "0.21.5",
|
||||||
"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.2"
|
"webpack-dev-server": "4.15.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "patch-package",
|
"postinstall": "patch-package",
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ export class PortalBackendEndpoints {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class MongoProxyEndpoints {
|
export class MongoProxyEndpoints {
|
||||||
public static readonly Local: string = "https://localhost:7238";
|
public static readonly Development: string = "https://localhost:7238";
|
||||||
public static readonly Mpac: string = "https://cdb-ms-mpac-mp.cosmos.azure.com";
|
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 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 Fairfax: string = "https://cdb-ff-prod-mp.cosmos.azure.us";
|
||||||
@@ -179,6 +179,9 @@ export class CassandraProxyAPIs {
|
|||||||
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";
|
||||||
|
public static setAutomaticRBACOption: string = "Automatic";
|
||||||
|
public static setTrueRBACOption: string = "True";
|
||||||
|
public static setFalseRBACOption: string = "False";
|
||||||
public static itemsPerPage: number = 100;
|
public static itemsPerPage: number = 100;
|
||||||
public static unlimitedItemsPerPage: number = 100; // TODO: Figure out appropriate value so it works for accounts with a large number of partitions
|
public static unlimitedItemsPerPage: number = 100; // TODO: Figure out appropriate value so it works for accounts with a large number of partitions
|
||||||
public static containersPerPage: number = 50;
|
public static containersPerPage: number = 50;
|
||||||
@@ -249,7 +252,6 @@ export class HttpHeaders {
|
|||||||
public static partitionKey: string = "x-ms-documentdb-partitionkey";
|
public static partitionKey: string = "x-ms-documentdb-partitionkey";
|
||||||
public static migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput";
|
public static migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput";
|
||||||
public static migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot";
|
public static migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot";
|
||||||
public static xAPIKey: string = "X-API-Key";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ContentType {
|
export class ContentType {
|
||||||
|
|||||||
@@ -672,28 +672,6 @@ 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,
|
|
||||||
MongoProxyEndpoints.Fairfax,
|
|
||||||
];
|
|
||||||
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> {
|
||||||
@@ -710,3 +688,24 @@ 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 {
|
||||||
|
const activeMongoProxyEndpoints: string[] = [
|
||||||
|
MongoProxyEndpoints.Development,
|
||||||
|
MongoProxyEndpoints.Mpac,
|
||||||
|
MongoProxyEndpoints.Prod,
|
||||||
|
];
|
||||||
|
let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
|
||||||
|
if (
|
||||||
|
configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Development &&
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
|
|||||||
<Image
|
<Image
|
||||||
{...imageProps}
|
{...imageProps}
|
||||||
src={EditIcon}
|
src={EditIcon}
|
||||||
alt={`Edit ${entityProperty} entity`}
|
alt="editEntity"
|
||||||
onClick={onEditEntity}
|
onClick={onEditEntity}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onKeyPress={handleKeyPress}
|
onKeyPress={handleKeyPress}
|
||||||
@@ -156,7 +156,7 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
|
|||||||
<Image
|
<Image
|
||||||
{...imageProps}
|
{...imageProps}
|
||||||
src={DeleteIcon}
|
src={DeleteIcon}
|
||||||
alt={`Delete ${entityProperty} entity`}
|
alt="delete entity"
|
||||||
id="deleteEntity"
|
id="deleteEntity"
|
||||||
onClick={onDeleteEntity}
|
onClick={onDeleteEntity}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
|||||||
@@ -42,9 +42,6 @@ export interface ConfigContext {
|
|||||||
ARM_API_VERSION: string;
|
ARM_API_VERSION: string;
|
||||||
GRAPH_ENDPOINT: string;
|
GRAPH_ENDPOINT: string;
|
||||||
GRAPH_API_VERSION: string;
|
GRAPH_API_VERSION: string;
|
||||||
CATALOG_ENDPOINT: string;
|
|
||||||
CATALOG_API_VERSION: string;
|
|
||||||
CATALOG_API_KEY: string;
|
|
||||||
ARCADIA_ENDPOINT: string;
|
ARCADIA_ENDPOINT: string;
|
||||||
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string;
|
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string;
|
||||||
BACKEND_ENDPOINT?: string;
|
BACKEND_ENDPOINT?: string;
|
||||||
@@ -86,7 +83,6 @@ 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/",
|
||||||
@@ -96,9 +92,6 @@ let configContext: Readonly<ConfigContext> = {
|
|||||||
ARM_API_VERSION: "2016-06-01",
|
ARM_API_VERSION: "2016-06-01",
|
||||||
GRAPH_ENDPOINT: "https://graph.microsoft.com",
|
GRAPH_ENDPOINT: "https://graph.microsoft.com",
|
||||||
GRAPH_API_VERSION: "1.6",
|
GRAPH_API_VERSION: "1.6",
|
||||||
CATALOG_ENDPOINT: "https://catalogapi.azure.com/",
|
|
||||||
CATALOG_API_VERSION: "2023-05-01-preview",
|
|
||||||
CATALOG_API_KEY: "",
|
|
||||||
ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.net",
|
ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.net",
|
||||||
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
|
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
|
||||||
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1189306
|
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1189306
|
||||||
@@ -115,7 +108,6 @@ let configContext: Readonly<ConfigContext> = {
|
|||||||
"updateDocument",
|
"updateDocument",
|
||||||
"deleteDocument",
|
"deleteDocument",
|
||||||
"createCollectionWithProxy",
|
"createCollectionWithProxy",
|
||||||
"legacyMongoShell",
|
|
||||||
],
|
],
|
||||||
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
|
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
|
||||||
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod,
|
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod,
|
||||||
|
|||||||
@@ -420,7 +420,6 @@ export interface SelfServeFrameInputs {
|
|||||||
authorizationToken: string;
|
authorizationToken: string;
|
||||||
csmEndpoint: string;
|
csmEndpoint: string;
|
||||||
flights?: readonly string[];
|
flights?: readonly string[];
|
||||||
catalogAPIKey: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MonacoEditorSettings {
|
export class MonacoEditorSettings {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* 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";
|
||||||
@@ -31,7 +30,7 @@ export interface CommandButtonComponentProps {
|
|||||||
/**
|
/**
|
||||||
* Click handler for command button click
|
* Click handler for command button click
|
||||||
*/
|
*/
|
||||||
onCommandClick: (e: React.SyntheticEvent | KeyboardEvent) => void;
|
onCommandClick: (e: React.SyntheticEvent) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Label for the button
|
* Label for the button
|
||||||
@@ -108,17 +107,10 @@ 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> {
|
||||||
|
|||||||
@@ -54,17 +54,13 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
|||||||
const existingContent = this.editor.getModel().getValue();
|
const existingContent = this.editor.getModel().getValue();
|
||||||
|
|
||||||
if (this.props.content !== existingContent) {
|
if (this.props.content !== existingContent) {
|
||||||
if (this.props.isReadOnly) {
|
this.editor.pushUndoStop();
|
||||||
this.editor.setValue(this.props.content);
|
this.editor.executeEdits("", [
|
||||||
} else {
|
{
|
||||||
this.editor.pushUndoStop();
|
range: this.editor.getModel().getFullModelRange(),
|
||||||
this.editor.executeEdits("", [
|
text: this.props.content,
|
||||||
{
|
},
|
||||||
range: this.editor.getModel().getFullModelRange(),
|
]);
|
||||||
text: this.props.content,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ export class NodePropertiesComponent extends React.Component<
|
|||||||
onActivated={this.setIsDeleteConfirm.bind(this, true)}
|
onActivated={this.setIsDeleteConfirm.bind(this, true)}
|
||||||
aria-label="Delete this vertex"
|
aria-label="Delete this vertex"
|
||||||
>
|
>
|
||||||
<img src={DeleteIcon} alt="Delete" role="button" />
|
<img src={DeleteIcon} alt="Delete" />
|
||||||
</AccessibleElement>
|
</AccessibleElement>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -406,7 +406,7 @@ export class NodePropertiesComponent extends React.Component<
|
|||||||
aria-label="Edit properties"
|
aria-label="Edit properties"
|
||||||
onActivated={expandClickHandler}
|
onActivated={expandClickHandler}
|
||||||
>
|
>
|
||||||
<img src={EditIcon} alt="Edit" role="button" />
|
<img src={EditIcon} alt="Edit" />
|
||||||
</AccessibleElement>
|
</AccessibleElement>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -184,18 +184,12 @@ export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = (
|
|||||||
className="rightPaneTrashIcon rightPaneBtns"
|
className="rightPaneTrashIcon rightPaneBtns"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role="button"
|
role="button"
|
||||||
aria-label={`Delete ${data.key}`}
|
|
||||||
onClick={(event: React.MouseEvent<HTMLDivElement>) => removeNewVertexProperty(event, index)}
|
onClick={(event: React.MouseEvent<HTMLDivElement>) => removeNewVertexProperty(event, index)}
|
||||||
onKeyPress={(event: React.KeyboardEvent<HTMLDivElement>) =>
|
onKeyPress={(event: React.KeyboardEvent<HTMLDivElement>) =>
|
||||||
removeNewVertexPropertyKeyPress(event, index)
|
removeNewVertexPropertyKeyPress(event, index)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<img
|
<img className="refreshcol rightPaneTrashIconImg" src={DeleteIcon} alt="Remove property" />
|
||||||
aria-label="hidden"
|
|
||||||
className="refreshcol rightPaneTrashIconImg"
|
|
||||||
src={DeleteIcon}
|
|
||||||
alt="Remove property"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
*/
|
*/
|
||||||
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";
|
||||||
@@ -41,7 +40,6 @@ 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 =
|
||||||
@@ -107,10 +105,6 @@ 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
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { KeyboardAction } from "KeyboardShortcuts";
|
|
||||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
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";
|
||||||
@@ -58,7 +57,6 @@ export function createStaticCommandBarButtons(
|
|||||||
buttons.push(homeBtn);
|
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);
|
||||||
@@ -96,7 +94,6 @@ 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);
|
||||||
@@ -280,7 +277,6 @@ 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) {
|
||||||
@@ -301,7 +297,6 @@ 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);
|
||||||
@@ -317,7 +312,6 @@ 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);
|
||||||
@@ -343,7 +337,6 @@ 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);
|
||||||
@@ -363,7 +356,6 @@ 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);
|
||||||
@@ -383,7 +375,6 @@ 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);
|
||||||
@@ -406,7 +397,6 @@ function createOpenQueryButton(container: Explorer): CommandButtonComponentProps
|
|||||||
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,
|
||||||
@@ -421,7 +411,6 @@ 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,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ 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";
|
||||||
@@ -234,28 +233,3 @@ 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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -41,6 +41,13 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
? Constants.Queries.UnlimitedPageOption
|
? Constants.Queries.UnlimitedPageOption
|
||||||
: Constants.Queries.CustomPageOption,
|
: Constants.Queries.CustomPageOption,
|
||||||
);
|
);
|
||||||
|
const [enableDataPlaneRBACOption, setEnableDataPlaneRBACOption] = useState<string>(
|
||||||
|
LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled) === Constants.Queries.setAutomaticRBACOption
|
||||||
|
? Constants.Queries.setAutomaticRBACOption
|
||||||
|
: LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled) === Constants.Queries.setTrueRBACOption
|
||||||
|
? Constants.Queries.setTrueRBACOption
|
||||||
|
: Constants.Queries.setFalseRBACOption
|
||||||
|
);
|
||||||
const [ruThresholdEnabled, setRUThresholdEnabled] = useState<boolean>(isRUThresholdEnabled());
|
const [ruThresholdEnabled, setRUThresholdEnabled] = useState<boolean>(isRUThresholdEnabled());
|
||||||
const [ruThreshold, setRUThreshold] = useState<number>(getRUThreshold());
|
const [ruThreshold, setRUThreshold] = useState<number>(getRUThreshold());
|
||||||
const [queryTimeoutEnabled, setQueryTimeoutEnabled] = useState<boolean>(
|
const [queryTimeoutEnabled, setQueryTimeoutEnabled] = useState<boolean>(
|
||||||
@@ -110,7 +117,14 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
StorageKey.ActualItemPerPage,
|
StorageKey.ActualItemPerPage,
|
||||||
isCustomPageOptionSelected() ? customItemPerPage : Constants.Queries.unlimitedItemsPerPage,
|
isCustomPageOptionSelected() ? customItemPerPage : Constants.Queries.unlimitedItemsPerPage,
|
||||||
);
|
);
|
||||||
|
|
||||||
LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, customItemPerPage);
|
LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, customItemPerPage);
|
||||||
|
|
||||||
|
LocalStorageUtility.setEntryString(
|
||||||
|
StorageKey.DataPlaneRbacEnabled,
|
||||||
|
enableDataPlaneRBACOption
|
||||||
|
);
|
||||||
|
|
||||||
LocalStorageUtility.setEntryBoolean(StorageKey.RUThresholdEnabled, ruThresholdEnabled);
|
LocalStorageUtility.setEntryBoolean(StorageKey.RUThresholdEnabled, ruThresholdEnabled);
|
||||||
LocalStorageUtility.setEntryBoolean(StorageKey.QueryTimeoutEnabled, queryTimeoutEnabled);
|
LocalStorageUtility.setEntryBoolean(StorageKey.QueryTimeoutEnabled, queryTimeoutEnabled);
|
||||||
LocalStorageUtility.setEntryNumber(StorageKey.RetryAttempts, retryAttempts);
|
LocalStorageUtility.setEntryNumber(StorageKey.RetryAttempts, retryAttempts);
|
||||||
@@ -197,6 +211,12 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
{ key: Constants.PriorityLevel.High, text: "High" },
|
{ key: Constants.PriorityLevel.High, text: "High" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const dataPlaneRBACOptionsList: IChoiceGroupOption[] = [
|
||||||
|
{ key: Constants.Queries.setAutomaticRBACOption, text: "Automatic" },
|
||||||
|
{ key: Constants.Queries.setTrueRBACOption, text: "True" },
|
||||||
|
{ key: Constants.Queries.setFalseRBACOption, text: "False"}
|
||||||
|
];
|
||||||
|
|
||||||
const handleOnPriorityLevelOptionChange = (
|
const handleOnPriorityLevelOptionChange = (
|
||||||
ev: React.FormEvent<HTMLInputElement>,
|
ev: React.FormEvent<HTMLInputElement>,
|
||||||
option: IChoiceGroupOption,
|
option: IChoiceGroupOption,
|
||||||
@@ -208,6 +228,10 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
setPageOption(option.key);
|
setPageOption(option.key);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOnDataPlaneRBACOptionChange = (ev: React.FormEvent<HTMLInputElement>, option: IChoiceGroupOption): void => {
|
||||||
|
setEnableDataPlaneRBACOption(option.key);
|
||||||
|
};
|
||||||
|
|
||||||
const handleOnRUThresholdToggleChange = (ev: React.MouseEvent<HTMLElement>, checked?: boolean): void => {
|
const handleOnRUThresholdToggleChange = (ev: React.MouseEvent<HTMLElement>, checked?: boolean): void => {
|
||||||
setRUThresholdEnabled(checked);
|
setRUThresholdEnabled(checked);
|
||||||
};
|
};
|
||||||
@@ -361,6 +385,27 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{(
|
||||||
|
<div className="settingsSection">
|
||||||
|
<div className="settingsSectionPart">
|
||||||
|
<fieldset>
|
||||||
|
<legend id="enableDataPlaneRBACOptions" className="settingsSectionLabel legendLabel">
|
||||||
|
Enable DataPlane RBAC
|
||||||
|
</legend>
|
||||||
|
<InfoTooltip>
|
||||||
|
Choose Automatic to enable DataPlane RBAC automatically. True/False to voluntarily enable/disable DataPlane RBAC
|
||||||
|
</InfoTooltip>
|
||||||
|
<ChoiceGroup
|
||||||
|
ariaLabelledBy="enableDataPlaneRBACOptions"
|
||||||
|
selectedKey={enableDataPlaneRBACOption}
|
||||||
|
options={dataPlaneRBACOptionsList}
|
||||||
|
styles={choiceButtonStyles}
|
||||||
|
onChange={handleOnDataPlaneRBACOptionChange}
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{userContext.apiType === "SQL" && (
|
{userContext.apiType === "SQL" && (
|
||||||
<>
|
<>
|
||||||
<div className="settingsSection">
|
<div className="settingsSection">
|
||||||
@@ -630,7 +675,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 Query Advisor. This will appear as another database in the Data Explorer UI, and is
|
NoSQL queries and Copilot. 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 +685,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
label: { padding: 0 },
|
label: { padding: 0 },
|
||||||
}}
|
}}
|
||||||
className="padding"
|
className="padding"
|
||||||
ariaLabel="Enable sample db for Query Advisor"
|
ariaLabel="Enable sample db for Copilot"
|
||||||
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();
|
||||||
@@ -261,7 +261,6 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
|||||||
<TextField
|
<TextField
|
||||||
multiline
|
multiline
|
||||||
rows={5}
|
rows={5}
|
||||||
ariaLabel={entityAttributeProperty}
|
|
||||||
value={entityAttributeValue}
|
value={entityAttributeValue}
|
||||||
onChange={(event, newInput?: string) => {
|
onChange={(event, newInput?: string) => {
|
||||||
entityChange(newInput, selectedRow, "value");
|
entityChange(newInput, selectedRow, "value");
|
||||||
|
|||||||
@@ -385,7 +385,7 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
hasSmallHeadline={true}
|
hasSmallHeadline={true}
|
||||||
headline="Write a prompt"
|
headline="Write a prompt"
|
||||||
>
|
>
|
||||||
Write a prompt here and Query Advisor will generate the query for you. You can also choose from our{" "}
|
Write a prompt here and Copilot will generate the query for you. You can also choose from our{" "}
|
||||||
<Link
|
<Link
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowSamplePrompts(true);
|
setShowSamplePrompts(true);
|
||||||
|
|||||||
@@ -57,12 +57,12 @@ export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: Query
|
|||||||
|
|
||||||
const toggleCopilotButton = {
|
const toggleCopilotButton = {
|
||||||
iconSrc: QueryCommandIcon,
|
iconSrc: QueryCommandIcon,
|
||||||
iconAlt: "Query Advisor",
|
iconAlt: "Copilot",
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
toggleCopilot(true);
|
toggleCopilot(true);
|
||||||
},
|
},
|
||||||
commandButtonLabel: "Query Advisor",
|
commandButtonLabel: "Copilot",
|
||||||
ariaLabel: "Query Advisor",
|
ariaLabel: "Copilot",
|
||||||
hasPopup: false,
|
hasPopup: false,
|
||||||
disabled: copilotActive,
|
disabled: copilotActive,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 Query Advisor"}
|
title={"Query faster with Copilot"}
|
||||||
description={
|
description={
|
||||||
"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!"
|
"Copilot 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;
|
||||||
|
|||||||
@@ -172,9 +172,8 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
deferred.resolve(entity);
|
deferred.resolve(entity);
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
handleError(error, "AddRowCassandra", `Error while adding new row to table ${collection.id()}`);
|
||||||
handleError(errorText, "AddRowCassandra", `Error while adding new row to table ${collection.id()}`);
|
deferred.reject(error);
|
||||||
deferred.reject(errorText);
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.finally(clearInProgressMessage);
|
.finally(clearInProgressMessage);
|
||||||
@@ -407,13 +406,12 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
|
||||||
handleError(
|
handleError(
|
||||||
errorText,
|
error,
|
||||||
"CreateKeyspaceCassandra",
|
"CreateKeyspaceCassandra",
|
||||||
`Error while creating a keyspace with query ${createKeyspaceQuery}`,
|
`Error while creating a keyspace with query ${createKeyspaceQuery}`,
|
||||||
);
|
);
|
||||||
deferred.reject(errorText);
|
deferred.reject(error);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.finally(clearInProgressMessage);
|
.finally(clearInProgressMessage);
|
||||||
@@ -446,13 +444,8 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
handleError(error, "CreateTableCassandra", `Error while creating a table with query ${createTableQuery}`);
|
||||||
handleError(
|
deferred.reject(error);
|
||||||
errorText,
|
|
||||||
"CreateTableCassandra",
|
|
||||||
`Error while creating a table with query ${createTableQuery}`,
|
|
||||||
);
|
|
||||||
deferred.reject(errorText);
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.finally(clearInProgressMessage);
|
.finally(clearInProgressMessage);
|
||||||
@@ -500,9 +493,8 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
deferred.resolve(data);
|
deferred.resolve(data);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
handleError(error, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
|
||||||
handleError(errorText, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
|
deferred.reject(error);
|
||||||
deferred.reject(errorText);
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.done(clearInProgressMessage);
|
.done(clearInProgressMessage);
|
||||||
@@ -541,9 +533,8 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
deferred.resolve(data);
|
deferred.resolve(data);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
handleError(error, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
|
||||||
handleError(errorText, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
|
deferred.reject(error);
|
||||||
deferred.reject(errorText);
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.done(clearInProgressMessage);
|
.done(clearInProgressMessage);
|
||||||
@@ -587,9 +578,8 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
deferred.resolve(data.columns);
|
deferred.resolve(data.columns);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
handleError(error, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
|
||||||
handleError(errorText, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
|
deferred.reject(error);
|
||||||
deferred.reject(errorText);
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.done(clearInProgressMessage);
|
.done(clearInProgressMessage);
|
||||||
@@ -628,9 +618,8 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
deferred.resolve(data.columns);
|
deferred.resolve(data.columns);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
handleError(error, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
|
||||||
handleError(errorText, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
|
deferred.reject(error);
|
||||||
deferred.reject(errorText);
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.done(clearInProgressMessage);
|
.done(clearInProgressMessage);
|
||||||
@@ -744,7 +733,6 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
|
|
||||||
private useCassandraProxyEndpoint(api: string): boolean {
|
private useCassandraProxyEndpoint(api: string): boolean {
|
||||||
const activeCassandraProxyEndpoints: string[] = [
|
const activeCassandraProxyEndpoints: string[] = [
|
||||||
CassandraProxyEndpoints.Development,
|
|
||||||
CassandraProxyEndpoints.Mpac,
|
CassandraProxyEndpoints.Mpac,
|
||||||
CassandraProxyEndpoints.Prod,
|
CassandraProxyEndpoints.Prod,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
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";
|
||||||
@@ -463,22 +462,7 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
|
|
||||||
private initializeNewDocument = (): void => {
|
private initializeNewDocument = (): void => {
|
||||||
this.selectedDocumentId(null);
|
this.selectedDocumentId(null);
|
||||||
const newDocument: any = {
|
const defaultDocument: string = this.renderObjectForEditor({ id: "replace_with_new_document_id" }, null, 4);
|
||||||
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);
|
||||||
@@ -909,7 +893,6 @@ 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,
|
||||||
@@ -924,7 +907,6 @@ 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,
|
||||||
@@ -939,7 +921,6 @@ 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,
|
||||||
@@ -955,7 +936,6 @@ 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,
|
||||||
@@ -970,7 +950,6 @@ 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,
|
||||||
@@ -986,7 +965,6 @@ 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,4 +1,3 @@
|
|||||||
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";
|
||||||
@@ -10,6 +9,7 @@ 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,15 +50,13 @@ 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(this._useMongoProxyEndpoint),
|
url: getMongoShellUrl(),
|
||||||
};
|
};
|
||||||
|
|
||||||
props.onMongoShellTabAccessor({
|
props.onMongoShellTabAccessor({
|
||||||
@@ -121,10 +119,9 @@ 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 = this._useMongoProxyEndpoint
|
const apiEndpoint = configContext.BACKEND_ENDPOINT;
|
||||||
? 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(
|
||||||
{
|
{
|
||||||
@@ -140,7 +137,7 @@ export default class MongoShellTabComponent extends Component<
|
|||||||
apiEndpoint: apiEndpoint,
|
apiEndpoint: apiEndpoint,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
window.origin,
|
targetOrigin,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
86
src/Explorer/Tabs/MongoShellTab/getMongoShellOrigin.test.ts
Normal file
86
src/Explorer/Tabs/MongoShellTab/getMongoShellOrigin.test.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
10
src/Explorer/Tabs/MongoShellTab/getMongoShellOrigin.ts
Normal file
10
src/Explorer/Tabs/MongoShellTab/getMongoShellOrigin.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
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,6 +1,7 @@
|
|||||||
import { Platform, resetConfigContext, updateConfigContext } from "../../../ConfigContext";
|
import { extractFeatures } from "Platform/Hosted/extractFeatures";
|
||||||
|
import { Platform, configContext, resetConfigContext, updateConfigContext } from "../../../ConfigContext";
|
||||||
import { updateUserContext, userContext } from "../../../UserContext";
|
import { updateUserContext, userContext } from "../../../UserContext";
|
||||||
import { getMongoShellUrl } from "./getMongoShellUrl";
|
import { getExtensionEndpoint, getMongoShellUrl } from "./getMongoShellUrl";
|
||||||
|
|
||||||
const mongoBackendEndpoint = "https://localhost:1234";
|
const mongoBackendEndpoint = "https://localhost:1234";
|
||||||
|
|
||||||
@@ -31,18 +32,175 @@ 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 /indexv2.html by default", () => {
|
it("should return /mongoshell/indexv2.html by default", () => {
|
||||||
expect(getMongoShellUrl().toString()).toContain(`/indexv2.html?${queryString}`);
|
expect(getMongoShellUrl()).toBe(`/mongoshell/indexv2.html?${queryString}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return /index.html when useMongoProxyEndpoint is true", () => {
|
it("should return /mongoshell/indexv2.html when portalEnv==localhost", () => {
|
||||||
const useMongoProxyEndpoint: boolean = true;
|
updateUserContext({
|
||||||
expect(getMongoShellUrl(useMongoProxyEndpoint).toString()).toContain(`/index.html?${queryString}`);
|
portalEnv: "localhost",
|
||||||
|
});
|
||||||
|
|
||||||
|
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,11 +1,45 @@
|
|||||||
|
import { configContext, Platform } from "../../../ConfigContext";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
|
|
||||||
export function getMongoShellUrl(useMongoProxyEndpoint?: boolean): string {
|
export function getMongoShellUrl(): 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}`;
|
||||||
|
|
||||||
return useMongoProxyEndpoint ? `/mongoshell/index.html?${queryString}` : `/mongoshell/indexv2.html?${queryString}`;
|
if (userContext.features.enableLegacyMongoShellV1 === true) {
|
||||||
|
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,13 +381,9 @@ 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 detected you may be using a subquery. To learn more about subqueries effectively,{" "}
|
We have detected you may be using a subquery. Non-correlated subqueries are not currently supported.
|
||||||
<a
|
<a href="https://docs.microsoft.com/en-us/azure/cosmos-db/sql-query-subquery">
|
||||||
href="https://learn.microsoft.com/azure/cosmos-db/nosql/query/subquery"
|
Please see Cosmos sub query documentation for further information
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
visit the documentation
|
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ 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";
|
||||||
@@ -22,7 +21,6 @@ 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";
|
||||||
@@ -226,20 +224,6 @@ 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} />);
|
||||||
};
|
};
|
||||||
@@ -409,7 +393,6 @@ 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,
|
||||||
@@ -420,28 +403,14 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.saveQueryButton.visible) {
|
if (this.saveQueryButton.visible && configContext.platform !== Platform.Fabric) {
|
||||||
if (configContext.platform !== Platform.Fabric) {
|
const label = "Save Query";
|
||||||
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: DownloadQueryIcon,
|
iconSrc: SaveQueryIcon,
|
||||||
iconAlt: "Download Query",
|
iconAlt: label,
|
||||||
keyboardAction: KeyboardAction.DOWNLOAD_ITEM,
|
onCommandClick: this.onSaveQueryClick,
|
||||||
onCommandClick: this.onDownloadQueryClick,
|
commandButtonLabel: label,
|
||||||
commandButtonLabel: "Download Query",
|
ariaLabel: label,
|
||||||
ariaLabel: "Download Query",
|
|
||||||
hasPopup: false,
|
hasPopup: false,
|
||||||
disabled: !this.saveQueryButton.enabled,
|
disabled: !this.saveQueryButton.enabled,
|
||||||
});
|
});
|
||||||
@@ -468,7 +437,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
hasPopup: false,
|
hasPopup: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const launchCopilotButton: CommandButtonComponentProps = {
|
const launchCopilotButton = {
|
||||||
iconSrc: LaunchCopilot,
|
iconSrc: LaunchCopilot,
|
||||||
iconAlt: mainButtonLabel,
|
iconAlt: mainButtonLabel,
|
||||||
onCommandClick: this.launchQueryCopilotChat,
|
onCommandClick: this.launchQueryCopilotChat,
|
||||||
@@ -481,15 +450,14 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.copilotEnabled) {
|
if (this.props.copilotEnabled) {
|
||||||
const toggleCopilotButton: CommandButtonComponentProps = {
|
const toggleCopilotButton = {
|
||||||
iconSrc: QueryCommandIcon,
|
iconSrc: QueryCommandIcon,
|
||||||
iconAlt: "Query Advisor",
|
iconAlt: "Copilot",
|
||||||
keyboardAction: KeyboardAction.TOGGLE_COPILOT,
|
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
this._toggleCopilot(!this.state.copilotActive);
|
this._toggleCopilot(!this.state.copilotActive);
|
||||||
},
|
},
|
||||||
commandButtonLabel: this.state.copilotActive ? "Disable Query Advisor" : "Enable Query Advisor",
|
commandButtonLabel: this.state.copilotActive ? "Disable Copilot" : "Enable Copilot",
|
||||||
ariaLabel: this.state.copilotActive ? "Disable Query Advisor" : "Enable Query Advisor",
|
ariaLabel: this.state.copilotActive ? "Disable Copilot" : "Enable Copilot",
|
||||||
hasPopup: false,
|
hasPopup: false,
|
||||||
};
|
};
|
||||||
buttons.push(toggleCopilotButton);
|
buttons.push(toggleCopilotButton);
|
||||||
@@ -500,7 +468,6 @@ 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,
|
||||||
@@ -553,8 +520,6 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.saveQueryButton.enabled = newContent.length > 0;
|
|
||||||
|
|
||||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||||
import { Pivot, PivotItem } from "@fluentui/react";
|
import { Pivot, PivotItem } from "@fluentui/react";
|
||||||
import { 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";
|
||||||
@@ -322,7 +321,6 @@ 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,
|
||||||
@@ -336,7 +334,6 @@ 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,
|
||||||
@@ -350,7 +347,6 @@ 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,
|
||||||
@@ -364,7 +360,6 @@ 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,7 +6,6 @@ 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";
|
||||||
@@ -14,7 +13,6 @@ 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";
|
||||||
@@ -43,16 +41,6 @@ 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 && (
|
||||||
@@ -309,9 +297,6 @@ 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:
|
||||||
@@ -340,7 +325,7 @@ 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 (
|
if (
|
||||||
((userContext.apiType === "Mongo" && configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Local) ||
|
((userContext.apiType === "Mongo" && configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Development) ||
|
||||||
(userContext.apiType === "Cassandra" &&
|
(userContext.apiType === "Cassandra" &&
|
||||||
configContext.CASSANDRA_PROXY_ENDPOINT !== CassandraProxyEndpoints.Development)) &&
|
configContext.CASSANDRA_PROXY_ENDPOINT !== CassandraProxyEndpoints.Development)) &&
|
||||||
ipRules?.length
|
ipRules?.length
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { TriggerDefinition } from "@azure/cosmos";
|
import { TriggerDefinition } from "@azure/cosmos";
|
||||||
import { Dropdown, IDropdownOption, Label, TextField } from "@fluentui/react";
|
import { Dropdown, IDropdownOption, Label, TextField } from "@fluentui/react";
|
||||||
import { 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";
|
||||||
@@ -219,18 +218,6 @@ 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";
|
||||||
@@ -240,7 +227,6 @@ 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,
|
||||||
@@ -255,7 +241,6 @@ 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,
|
||||||
@@ -271,7 +256,6 @@ 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,
|
||||||
@@ -303,6 +287,7 @@ 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,13 +1,12 @@
|
|||||||
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";
|
||||||
@@ -81,7 +80,6 @@ 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,
|
||||||
@@ -96,7 +94,6 @@ 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,
|
||||||
@@ -112,7 +109,6 @@ 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,
|
||||||
|
|||||||
@@ -1,159 +0,0 @@
|
|||||||
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[];
|
|
||||||
}
|
|
||||||
89
src/Main.tsx
89
src/Main.tsx
@@ -21,7 +21,6 @@ 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";
|
||||||
@@ -92,54 +91,52 @@ const App: React.FunctionComponent = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardShortcutRoot>
|
<div className="flexContainer" aria-hidden="false">
|
||||||
<div className="flexContainer" aria-hidden="false">
|
<div id="divExplorer" className="flexContainer hideOverflows">
|
||||||
<div id="divExplorer" className="flexContainer hideOverflows">
|
<div id="freeTierTeachingBubble"> </div>
|
||||||
<div id="freeTierTeachingBubble"> </div>
|
{/* Main Command Bar - Start */}
|
||||||
{/* Main Command Bar - Start */}
|
<CommandBar container={explorer} />
|
||||||
<CommandBar container={explorer} />
|
{/* Collections Tree and Tabs - Begin */}
|
||||||
{/* Collections Tree and Tabs - Begin */}
|
<div className="resourceTreeAndTabs">
|
||||||
<div className="resourceTreeAndTabs">
|
{/* Collections Tree - Start */}
|
||||||
{/* Collections Tree - Start */}
|
{userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo" && (
|
||||||
{userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo" && (
|
<div id="resourcetree" data-test="resourceTreeId" className="resourceTree">
|
||||||
<div id="resourcetree" data-test="resourceTreeId" className="resourceTree">
|
<div className="collectionsTreeWithSplitter">
|
||||||
<div className="collectionsTreeWithSplitter">
|
{/* Collections Tree Expanded - Start */}
|
||||||
{/* Collections Tree Expanded - Start */}
|
<ResourceTreeContainer
|
||||||
<ResourceTreeContainer
|
container={explorer}
|
||||||
container={explorer}
|
toggleLeftPaneExpanded={toggleLeftPaneExpanded}
|
||||||
toggleLeftPaneExpanded={toggleLeftPaneExpanded}
|
isLeftPaneExpanded={isLeftPaneExpanded}
|
||||||
isLeftPaneExpanded={isLeftPaneExpanded}
|
/>
|
||||||
/>
|
{/* Collections Tree Expanded - End */}
|
||||||
{/* Collections Tree Expanded - End */}
|
{/* Collections Tree Collapsed - Start */}
|
||||||
{/* Collections Tree Collapsed - Start */}
|
<CollapsedResourceTree
|
||||||
<CollapsedResourceTree
|
toggleLeftPaneExpanded={toggleLeftPaneExpanded}
|
||||||
toggleLeftPaneExpanded={toggleLeftPaneExpanded}
|
isLeftPaneExpanded={isLeftPaneExpanded}
|
||||||
isLeftPaneExpanded={isLeftPaneExpanded}
|
/>
|
||||||
/>
|
{/* Collections Tree Collapsed - End */}
|
||||||
{/* Collections Tree Collapsed - End */}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
<Tabs explorer={explorer} />
|
)}
|
||||||
</div>
|
<Tabs explorer={explorer} />
|
||||||
{/* Collections Tree and Tabs - End */}
|
</div>
|
||||||
<div
|
{/* Collections Tree and Tabs - End */}
|
||||||
className="dataExplorerErrorConsoleContainer"
|
<div
|
||||||
role="contentinfo"
|
className="dataExplorerErrorConsoleContainer"
|
||||||
aria-label="Notification console"
|
role="contentinfo"
|
||||||
id="explorerNotificationConsole"
|
aria-label="Notification console"
|
||||||
>
|
id="explorerNotificationConsole"
|
||||||
<NotificationConsole />
|
>
|
||||||
</div>
|
<NotificationConsole />
|
||||||
</div>
|
</div>
|
||||||
<SidePanel />
|
|
||||||
<Dialog />
|
|
||||||
{<QuickstartCarousel isOpen={isCarouselOpen} />}
|
|
||||||
{<SQLQuickstartTutorial />}
|
|
||||||
{<MongoQuickstartTutorial />}
|
|
||||||
{<QueryCopilotCarousel isOpen={isCopilotCarouselOpen} explorer={explorer} />}
|
|
||||||
</div>
|
</div>
|
||||||
</KeyboardShortcutRoot>
|
<SidePanel />
|
||||||
|
<Dialog />
|
||||||
|
{<QuickstartCarousel isOpen={isCarouselOpen} />}
|
||||||
|
{<SQLQuickstartTutorial />}
|
||||||
|
{<MongoQuickstartTutorial />}
|
||||||
|
{<QueryCopilotCarousel isOpen={isCopilotCarouselOpen} explorer={explorer} />}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export type Features = {
|
|||||||
readonly enableTtl: boolean;
|
readonly enableTtl: boolean;
|
||||||
readonly executeSproc: boolean;
|
readonly executeSproc: boolean;
|
||||||
readonly enableAadDataPlane: boolean;
|
readonly enableAadDataPlane: boolean;
|
||||||
|
readonly enableDataPlaneRbac: boolean;
|
||||||
readonly enableResourceGraph: boolean;
|
readonly enableResourceGraph: boolean;
|
||||||
readonly enableKoResourceTree: boolean;
|
readonly enableKoResourceTree: boolean;
|
||||||
readonly hostedDataExplorer: boolean;
|
readonly hostedDataExplorer: boolean;
|
||||||
@@ -31,6 +32,11 @@ 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;
|
||||||
@@ -69,6 +75,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
|
|||||||
canExceedMaximumValue: "true" === get("canexceedmaximumvalue"),
|
canExceedMaximumValue: "true" === get("canexceedmaximumvalue"),
|
||||||
cosmosdb: "true" === get("cosmosdb"),
|
cosmosdb: "true" === get("cosmosdb"),
|
||||||
enableAadDataPlane: "true" === get("enableaaddataplane"),
|
enableAadDataPlane: "true" === get("enableaaddataplane"),
|
||||||
|
enableDataPlaneRbac: "true" === get("enabledataplanerbac"),
|
||||||
enableResourceGraph: "true" === get("enableresourcegraph"),
|
enableResourceGraph: "true" === get("enableresourcegraph"),
|
||||||
enableChangeFeedPolicy: "true" === get("enablechangefeedpolicy"),
|
enableChangeFeedPolicy: "true" === get("enablechangefeedpolicy"),
|
||||||
enableFixedCollectionWithSharedThroughput: "true" === get("enablefixedcollectionwithsharedthroughput"),
|
enableFixedCollectionWithSharedThroughput: "true" === get("enablefixedcollectionwithsharedthroughput"),
|
||||||
@@ -101,6 +108,11 @@ 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"),
|
||||||
|
|||||||
@@ -117,7 +117,6 @@ const handleMessage = async (event: MessageEvent): Promise<void> => {
|
|||||||
|
|
||||||
updateConfigContext({
|
updateConfigContext({
|
||||||
ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT),
|
ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT),
|
||||||
CATALOG_API_KEY: inputs.catalogAPIKey,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
import { configContext } from "../../ConfigContext";
|
import { configContext } from "../../ConfigContext";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { get } from "../../Utils/arm/generatedClients/cosmos/locations";
|
import { armRequestWithoutPolling } from "../../Utils/arm/request";
|
||||||
import { armRequestWithoutPolling, getOfferingIdsRequest } from "../../Utils/arm/request";
|
|
||||||
import { selfServeTraceFailure, selfServeTraceStart, selfServeTraceSuccess } from "../SelfServeTelemetryProcessor";
|
import { selfServeTraceFailure, selfServeTraceStart, selfServeTraceSuccess } from "../SelfServeTelemetryProcessor";
|
||||||
import { RefreshResult } from "../SelfServeTypes";
|
import { RefreshResult } from "../SelfServeTypes";
|
||||||
import SqlX from "./SqlX";
|
import SqlX from "./SqlX";
|
||||||
import {
|
import {
|
||||||
FetchPricesResponse,
|
FetchPricesResponse,
|
||||||
GetOfferingIdsResponse,
|
|
||||||
OfferingIdMap,
|
|
||||||
OfferingIdRequest,
|
|
||||||
PriceMapAndCurrencyCode,
|
PriceMapAndCurrencyCode,
|
||||||
RegionItem,
|
RegionItem,
|
||||||
RegionsResponse,
|
RegionsResponse,
|
||||||
@@ -170,21 +166,11 @@ export const getRegions = async (): Promise<Array<RegionItem>> => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRegionShortName = async (regionDisplayName: string): Promise<string> => {
|
|
||||||
const locationsList = await get(userContext.subscriptionId, regionDisplayName);
|
|
||||||
|
|
||||||
if ("id" in locationsList) {
|
|
||||||
const locationId = locationsList.id;
|
|
||||||
return locationId.substring(locationId.lastIndexOf("/") + 1);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getFetchPricesPathForRegion = (subscriptionId: string): string => {
|
const getFetchPricesPathForRegion = (subscriptionId: string): string => {
|
||||||
return `/subscriptions/${subscriptionId}/providers/Microsoft.CostManagement/fetchPrices`;
|
return `/subscriptions/${subscriptionId}/providers/Microsoft.CostManagement/fetchPrices`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPriceMapAndCurrencyCode = async (map: OfferingIdMap): Promise<PriceMapAndCurrencyCode> => {
|
export const getPriceMapAndCurrencyCode = async (regions: Array<RegionItem>): Promise<PriceMapAndCurrencyCode> => {
|
||||||
const telemetryData = {
|
const telemetryData = {
|
||||||
feature: "Calculate approximate cost",
|
feature: "Calculate approximate cost",
|
||||||
function: "getPriceMapAndCurrencyCode",
|
function: "getPriceMapAndCurrencyCode",
|
||||||
@@ -195,94 +181,39 @@ export const getPriceMapAndCurrencyCode = async (map: OfferingIdMap): Promise<Pr
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const priceMap = new Map<string, Map<string, number>>();
|
const priceMap = new Map<string, Map<string, number>>();
|
||||||
let billingCurrency;
|
let currencyCode;
|
||||||
for (const region of map.keys()) {
|
for (const regionItem of regions) {
|
||||||
const regionPriceMap = new Map<string, number>();
|
const regionPriceMap = new Map<string, number>();
|
||||||
const regionShortName = await getRegionShortName(region);
|
|
||||||
const requestBody: OfferingIdRequest = {
|
|
||||||
location: regionShortName,
|
|
||||||
ids: Array.from(map.get(region).keys()),
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await armRequestWithoutPolling<FetchPricesResponse>({
|
const response = await armRequestWithoutPolling<FetchPricesResponse>({
|
||||||
host: configContext.ARM_ENDPOINT,
|
host: configContext.ARM_ENDPOINT,
|
||||||
path: getFetchPricesPathForRegion(userContext.subscriptionId),
|
path: getFetchPricesPathForRegion(userContext.subscriptionId),
|
||||||
method: "POST",
|
method: "POST",
|
||||||
apiVersion: "2023-04-01-preview",
|
apiVersion: "2020-01-01-preview",
|
||||||
body: requestBody,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const item of response.result) {
|
|
||||||
if (item.error) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (billingCurrency === undefined) {
|
|
||||||
billingCurrency = item.billingCurrency;
|
|
||||||
} else if (item.billingCurrency !== billingCurrency) {
|
|
||||||
throw Error("Currency Code Mismatch: Currency code not same for all regions / skus.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const offeringId = item.id;
|
|
||||||
const skuName = map.get(region).get(offeringId);
|
|
||||||
const unitPriceinBillingCurrency = item.prices.find((x) => x.type === "Consumption")
|
|
||||||
?.unitPriceinBillingCurrency;
|
|
||||||
regionPriceMap.set(skuName, unitPriceinBillingCurrency);
|
|
||||||
}
|
|
||||||
priceMap.set(region, regionPriceMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
selfServeTraceSuccess(telemetryData, getPriceMapAndCurrencyCodeTimestamp);
|
|
||||||
return { priceMap: priceMap, billingCurrency: billingCurrency };
|
|
||||||
} catch (err) {
|
|
||||||
const failureTelemetry = { err, selfServeClassName: SqlX.name };
|
|
||||||
selfServeTraceFailure(failureTelemetry, getPriceMapAndCurrencyCodeTimestamp);
|
|
||||||
return { priceMap: undefined, billingCurrency: undefined };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getOfferingIdPathForRegion = (): string => {
|
|
||||||
return `/skus?serviceFamily=Databases&service=Azure Cosmos DB`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getOfferingIds = async (regions: Array<RegionItem>): Promise<OfferingIdMap> => {
|
|
||||||
const telemetryData = {
|
|
||||||
feature: "Get Offering Ids to calculate approximate cost",
|
|
||||||
function: "getOfferingIds",
|
|
||||||
description: "fetch offering ids API call",
|
|
||||||
selfServeClassName: SqlX.name,
|
|
||||||
};
|
|
||||||
const getOfferingIdsCodeTimestamp = selfServeTraceStart(telemetryData);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const offeringIdMap = new Map<string, Map<string, string>>();
|
|
||||||
for (const regionItem of regions) {
|
|
||||||
const regionOfferingIdMap = new Map<string, string>();
|
|
||||||
const regionShortName = await getRegionShortName(regionItem.locationName);
|
|
||||||
|
|
||||||
const response = await getOfferingIdsRequest<GetOfferingIdsResponse>({
|
|
||||||
host: configContext.CATALOG_ENDPOINT,
|
|
||||||
path: getOfferingIdPathForRegion(),
|
|
||||||
method: "GET",
|
|
||||||
apiVersion: "2023-05-01-preview",
|
|
||||||
queryParams: {
|
queryParams: {
|
||||||
filter: "armRegionName eq '" + regionShortName + "'",
|
filter:
|
||||||
|
"armRegionName eq '" +
|
||||||
|
regionItem.locationName.split(" ").join("").toLowerCase() +
|
||||||
|
"' and serviceFamily eq 'Databases' and productName eq 'Azure Cosmos DB Dedicated Gateway - General Purpose'",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const item of response.result.items) {
|
for (const item of response.result.Items) {
|
||||||
if (item.offeringProperties?.length > 0) {
|
if (currencyCode === undefined) {
|
||||||
regionOfferingIdMap.set(item.offeringProperties[0].offeringId, item.skuName);
|
currencyCode = item.currencyCode;
|
||||||
|
} else if (item.currencyCode !== currencyCode) {
|
||||||
|
throw Error("Currency Code Mismatch: Currency code not same for all regions / skus.");
|
||||||
}
|
}
|
||||||
|
regionPriceMap.set(item.skuName, item.retailPrice);
|
||||||
}
|
}
|
||||||
offeringIdMap.set(regionItem.locationName, regionOfferingIdMap);
|
priceMap.set(regionItem.locationName, regionPriceMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
selfServeTraceSuccess(telemetryData, getOfferingIdsCodeTimestamp);
|
selfServeTraceSuccess(telemetryData, getPriceMapAndCurrencyCodeTimestamp);
|
||||||
return offeringIdMap;
|
return { priceMap: priceMap, currencyCode: currencyCode };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const failureTelemetry = { err, selfServeClassName: SqlX.name };
|
const failureTelemetry = { err, selfServeClassName: SqlX.name };
|
||||||
selfServeTraceFailure(failureTelemetry, getOfferingIdsCodeTimestamp);
|
selfServeTraceFailure(failureTelemetry, getPriceMapAndCurrencyCodeTimestamp);
|
||||||
return undefined;
|
return { priceMap: undefined, currencyCode: undefined };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import { BladeType, generateBladeLink } from "../SelfServeUtils";
|
|||||||
import {
|
import {
|
||||||
deleteDedicatedGatewayResource,
|
deleteDedicatedGatewayResource,
|
||||||
getCurrentProvisioningState,
|
getCurrentProvisioningState,
|
||||||
getOfferingIds,
|
|
||||||
getPriceMapAndCurrencyCode,
|
getPriceMapAndCurrencyCode,
|
||||||
getRegions,
|
getRegions,
|
||||||
refreshDedicatedGatewayProvisioning,
|
refreshDedicatedGatewayProvisioning,
|
||||||
@@ -371,10 +370,9 @@ export default class SqlX extends SelfServeBaseClass {
|
|||||||
});
|
});
|
||||||
|
|
||||||
regions = await getRegions();
|
regions = await getRegions();
|
||||||
const offeringIdMap = await getOfferingIds(regions);
|
const priceMapAndCurrencyCode = await getPriceMapAndCurrencyCode(regions);
|
||||||
const priceMapAndCurrencyCode = await getPriceMapAndCurrencyCode(offeringIdMap);
|
|
||||||
priceMap = priceMapAndCurrencyCode.priceMap;
|
priceMap = priceMapAndCurrencyCode.priceMap;
|
||||||
currencyCode = priceMapAndCurrencyCode.billingCurrency;
|
currencyCode = priceMapAndCurrencyCode.currencyCode;
|
||||||
|
|
||||||
const response = await getCurrentProvisioningState();
|
const response = await getCurrentProvisioningState();
|
||||||
if (response.status && response.status !== "Deleting") {
|
if (response.status && response.status !== "Deleting") {
|
||||||
|
|||||||
@@ -30,51 +30,23 @@ export type UpdateDedicatedGatewayRequestProperties = {
|
|||||||
serviceType: string;
|
serviceType: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FetchPricesResponse = Array<PriceItem>;
|
export type FetchPricesResponse = {
|
||||||
|
Items: Array<PriceItem>;
|
||||||
export type PriceItem = {
|
NextPageLink: string | undefined;
|
||||||
prices: Array<PriceType>;
|
Count: number;
|
||||||
id: string;
|
|
||||||
billingCurrency: string;
|
|
||||||
error: PriceError;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PriceType = {
|
|
||||||
type: string;
|
|
||||||
unitPriceinBillingCurrency: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PriceError = {
|
|
||||||
type: string;
|
|
||||||
description: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PriceMapAndCurrencyCode = {
|
export type PriceMapAndCurrencyCode = {
|
||||||
priceMap: Map<string, Map<string, number>>;
|
priceMap: Map<string, Map<string, number>>;
|
||||||
billingCurrency: string;
|
currencyCode: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GetOfferingIdsResponse = {
|
export type PriceItem = {
|
||||||
items: Array<OfferingIdItem>;
|
retailPrice: number;
|
||||||
nextPageLink: string | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type OfferingIdItem = {
|
|
||||||
skuName: string;
|
skuName: string;
|
||||||
offeringProperties: Array<OfferingProperties>;
|
currencyCode: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OfferingProperties = {
|
|
||||||
offeringId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type OfferingIdRequest = {
|
|
||||||
ids: Array<string>;
|
|
||||||
location: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type OfferingIdMap = Map<string, Map<string, string>>;
|
|
||||||
|
|
||||||
export type RegionsResponse = {
|
export type RegionsResponse = {
|
||||||
properties: RegionsProperties;
|
properties: RegionsProperties;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import * as StringUtility from "./StringUtility";
|
|||||||
export { LocalStorageUtility, SessionStorageUtility };
|
export { LocalStorageUtility, SessionStorageUtility };
|
||||||
export enum StorageKey {
|
export enum StorageKey {
|
||||||
ActualItemPerPage,
|
ActualItemPerPage,
|
||||||
|
DataPlaneRbacEnabled,
|
||||||
|
DataPlaneRbacDisabled,
|
||||||
|
isDataPlaneRbacAutomatic,
|
||||||
RUThresholdEnabled,
|
RUThresholdEnabled,
|
||||||
RUThreshold,
|
RUThreshold,
|
||||||
QueryTimeoutEnabled,
|
QueryTimeoutEnabled,
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ interface UserContext {
|
|||||||
sampleDataConnectionInfo?: ParsedResourceTokenConnectionString;
|
sampleDataConnectionInfo?: ParsedResourceTokenConnectionString;
|
||||||
readonly vcoreMongoConnectionParams?: VCoreMongoConnectionParams;
|
readonly vcoreMongoConnectionParams?: VCoreMongoConnectionParams;
|
||||||
readonly feedbackPolicies?: AdminFeedbackPolicySettings;
|
readonly feedbackPolicies?: AdminFeedbackPolicySettings;
|
||||||
|
readonly dataPlaneRbacEnabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres" | "VCoreMongo";
|
export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres" | "VCoreMongo";
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export const MongoProxyOutboundIPs: { [key: string]: string[] } = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const allowedMongoProxyEndpoints: ReadonlyArray<string> = [
|
export const allowedMongoProxyEndpoints: ReadonlyArray<string> = [
|
||||||
MongoProxyEndpoints.Local,
|
MongoProxyEndpoints.Development,
|
||||||
MongoProxyEndpoints.Mpac,
|
MongoProxyEndpoints.Mpac,
|
||||||
MongoProxyEndpoints.Prod,
|
MongoProxyEndpoints.Prod,
|
||||||
MongoProxyEndpoints.Fairfax,
|
MongoProxyEndpoints.Fairfax,
|
||||||
@@ -154,16 +154,8 @@ export const allowedNotebookServerUrls: ReadonlyArray<string> = [];
|
|||||||
export function useNewPortalBackendEndpoint(backendApi: string): boolean {
|
export function useNewPortalBackendEndpoint(backendApi: string): boolean {
|
||||||
// This maps backend APIs to the environments supported by the new backend.
|
// This maps backend APIs to the environments supported by the new backend.
|
||||||
const newBackendApiEnvironmentMap: { [key: string]: string[] } = {
|
const newBackendApiEnvironmentMap: { [key: string]: string[] } = {
|
||||||
[BackendApi.GenerateToken]: [
|
[BackendApi.GenerateToken]: [PortalBackendEndpoints.Development],
|
||||||
PortalBackendEndpoints.Development,
|
[BackendApi.PortalSettings]: [PortalBackendEndpoints.Development, PortalBackendEndpoints.Mpac],
|
||||||
PortalBackendEndpoints.Mpac,
|
|
||||||
PortalBackendEndpoints.Prod,
|
|
||||||
],
|
|
||||||
[BackendApi.PortalSettings]: [
|
|
||||||
PortalBackendEndpoints.Development,
|
|
||||||
PortalBackendEndpoints.Mpac,
|
|
||||||
PortalBackendEndpoints.Prod,
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!newBackendApiEnvironmentMap[backendApi] || !configContext.PORTAL_BACKEND_ENDPOINT) {
|
if (!newBackendApiEnvironmentMap[backendApi] || !configContext.PORTAL_BACKEND_ENDPOINT) {
|
||||||
|
|||||||
@@ -160,52 +160,3 @@ async function getOperationStatus(operationStatusUrl: string) {
|
|||||||
}
|
}
|
||||||
throw new Error(`Operation Response: ${JSON.stringify(body)}. Retrying.`);
|
throw new Error(`Operation Response: ${JSON.stringify(body)}. Retrying.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getOfferingIdsRequest<T>({
|
|
||||||
host,
|
|
||||||
path,
|
|
||||||
apiVersion,
|
|
||||||
method,
|
|
||||||
body: requestBody,
|
|
||||||
queryParams,
|
|
||||||
}: Options): Promise<{ result: T; operationStatusUrl: string }> {
|
|
||||||
const url = new URL(path, host);
|
|
||||||
url.searchParams.append("api-version", configContext.armAPIVersion || apiVersion);
|
|
||||||
if (queryParams) {
|
|
||||||
queryParams.filter && url.searchParams.append("$filter", queryParams.filter);
|
|
||||||
queryParams.metricNames && url.searchParams.append("metricnames", queryParams.metricNames);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!configContext.CATALOG_API_KEY) {
|
|
||||||
throw new Error("No catalog API key provided");
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await window.fetch(url.href, {
|
|
||||||
method,
|
|
||||||
headers: {
|
|
||||||
[HttpHeaders.xAPIKey]: configContext.CATALOG_API_KEY,
|
|
||||||
},
|
|
||||||
body: requestBody ? JSON.stringify(requestBody) : undefined,
|
|
||||||
});
|
|
||||||
if (!response.ok) {
|
|
||||||
let error: ARMError;
|
|
||||||
try {
|
|
||||||
const errorResponse = (await response.json()) as ParsedErrorResponse;
|
|
||||||
if ("error" in errorResponse) {
|
|
||||||
error = new ARMError(errorResponse.error.message);
|
|
||||||
error.code = errorResponse.error.code;
|
|
||||||
} else {
|
|
||||||
error = new ARMError(errorResponse.message);
|
|
||||||
error.code = errorResponse.code;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(await response.text());
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
const operationStatusUrl = (response.headers && response.headers.get("location")) || "";
|
|
||||||
const responseBody = (await response.json()) as T;
|
|
||||||
return { result: responseBody, operationStatusUrl: operationStatusUrl };
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { FABRIC_RPC_VERSION, FabricMessageV2 } from "Contracts/FabricMessagesCon
|
|||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
|
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
|
||||||
|
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||||
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
|
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
|
||||||
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
@@ -270,8 +271,30 @@ async function configureHostedWithAAD(config: AAD): Promise<Explorer> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (!account.properties.disableLocalAuth) {
|
if(LocalStorageUtility.hasItem(StorageKey.DataPlaneRbacEnabled)) {
|
||||||
keys = await listKeys(subscriptionId, resourceGroup, account.name);
|
var isDataPlaneRbacSetting = LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled);
|
||||||
|
if (isDataPlaneRbacSetting == "Automatic")
|
||||||
|
{
|
||||||
|
if (!account.properties.disableLocalAuth) {
|
||||||
|
keys = await listKeys(subscriptionId, resourceGroup, account.name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
updateUserContext({
|
||||||
|
dataPlaneRbacEnabled: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(isDataPlaneRbacSetting == "True") {
|
||||||
|
updateUserContext({
|
||||||
|
dataPlaneRbacEnabled: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
keys = await listKeys(subscriptionId, resourceGroup, account.name);
|
||||||
|
updateUserContext({
|
||||||
|
dataPlaneRbacEnabled: false
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (userContext.features.enableAadDataPlane) {
|
if (userContext.features.enableAadDataPlane) {
|
||||||
@@ -393,8 +416,9 @@ async function configurePortal(): Promise<Explorer> {
|
|||||||
updateUserContext({
|
updateUserContext({
|
||||||
authType: AuthType.AAD,
|
authType: AuthType.AAD,
|
||||||
});
|
});
|
||||||
|
|
||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
return new Promise((resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
// In development mode, try to load the iframe message from session storage.
|
// In development mode, try to load the iframe message from session storage.
|
||||||
// This allows webpack hot reload to function properly in the portal
|
// This allows webpack hot reload to function properly in the portal
|
||||||
if (process.env.NODE_ENV === "development" && !window.location.search.includes("disablePortalInitCache")) {
|
if (process.env.NODE_ENV === "development" && !window.location.search.includes("disablePortalInitCache")) {
|
||||||
@@ -407,6 +431,7 @@ async function configurePortal(): Promise<Explorer> {
|
|||||||
console.dir(message);
|
console.dir(message);
|
||||||
updateContextsFromPortalMessage(message);
|
updateContextsFromPortalMessage(message);
|
||||||
explorer = new Explorer();
|
explorer = new Explorer();
|
||||||
|
|
||||||
// In development mode, save the iframe message from the portal in session storage.
|
// In development mode, save the iframe message from the portal in session storage.
|
||||||
// This allows webpack hot reload to funciton properly
|
// This allows webpack hot reload to funciton properly
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === "development") {
|
||||||
@@ -415,11 +440,11 @@ async function configurePortal(): Promise<Explorer> {
|
|||||||
resolve(explorer);
|
resolve(explorer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In the Portal, configuration of Explorer happens via iframe message
|
// In the Portal, configuration of Explorer happens via iframe message
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
"message",
|
"message",
|
||||||
(event) => {
|
async (event) => {
|
||||||
if (isInvalidParentFrameOrigin(event)) {
|
if (isInvalidParentFrameOrigin(event)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -449,6 +474,37 @@ async function configurePortal(): Promise<Explorer> {
|
|||||||
setTimeout(() => explorer.openNPSSurveyDialog(), 3000);
|
setTimeout(() => explorer.openNPSSurveyDialog(), 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let dbAccount = userContext.databaseAccount;
|
||||||
|
let keys: DatabaseAccountListKeysResult = {};
|
||||||
|
const account = userContext.databaseAccount;
|
||||||
|
const subscriptionId = userContext.subscriptionId;
|
||||||
|
const resourceGroup = userContext.resourceGroup;
|
||||||
|
if(LocalStorageUtility.hasItem(StorageKey.DataPlaneRbacEnabled)) {
|
||||||
|
var isDataPlaneRbacSetting = LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled);
|
||||||
|
if (isDataPlaneRbacSetting == "Automatic")
|
||||||
|
{
|
||||||
|
if (!account.properties.disableLocalAuth) {
|
||||||
|
keys = await listKeys(subscriptionId, resourceGroup, account.name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
updateUserContext({
|
||||||
|
dataPlaneRbacEnabled: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(isDataPlaneRbacSetting == "True") {
|
||||||
|
updateUserContext({
|
||||||
|
dataPlaneRbacEnabled: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
keys = await listKeys(subscriptionId, resourceGroup, account.name);
|
||||||
|
updateUserContext({
|
||||||
|
dataPlaneRbacEnabled: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (openAction) {
|
if (openAction) {
|
||||||
handleOpenAction(openAction, useDatabases.getState().databases, explorer);
|
handleOpenAction(openAction, useDatabases.getState().databases, explorer);
|
||||||
}
|
}
|
||||||
@@ -469,9 +525,11 @@ async function configurePortal(): Promise<Explorer> {
|
|||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
sendReadyMessage();
|
sendReadyMessage();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldForwardMessage(message: PortalMessage, messageOrigin: string) {
|
function shouldForwardMessage(message: PortalMessage, messageOrigin: string) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
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";
|
||||||
@@ -30,11 +29,6 @@ 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 {
|
||||||
@@ -181,44 +175,4 @@ 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,18 +1,15 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
import { generateUniqueName } 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&token=${token}`);
|
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-cassandra-runner");
|
||||||
await page.waitForSelector("iframe");
|
await page.waitForSelector("iframe");
|
||||||
const explorer = await waitForExplorer();
|
const explorer = await waitForExplorer();
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateDatabaseNameWithTimestamp, generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
import { generateDatabaseNameWithTimestamp, generateUniqueName } 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&token=${token}`);
|
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-gremlin-runner");
|
||||||
const explorer = await waitForExplorer();
|
const explorer = await waitForExplorer();
|
||||||
|
|
||||||
// Create new database and graph
|
// Create new database and graph
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateDatabaseNameWithTimestamp, generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
import { generateDatabaseNameWithTimestamp, generateUniqueName } 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&token=${token}`);
|
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-mongo-runner");
|
||||||
const explorer = await waitForExplorer();
|
const explorer = await waitForExplorer();
|
||||||
|
|
||||||
// Create new database and collection
|
// Create new database and collection
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateDatabaseNameWithTimestamp, generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
import { generateDatabaseNameWithTimestamp, generateUniqueName } 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&token=${token}`);
|
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-mongo32-runner");
|
||||||
const explorer = await waitForExplorer();
|
const explorer = await waitForExplorer();
|
||||||
|
|
||||||
// Create new database and collection
|
// Create new database and collection
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
import { getAzureCLICredentialsToken } from "../utils/shared";
|
|
||||||
|
|
||||||
test("Self Serve", async () => {
|
test("Self Serve", async () => {
|
||||||
// We can't retrieve AZ CLI credentials from the browser so we get them here.
|
await page.goto("https://localhost:1234/testExplorer.html?iframeSrc=selfServe.html");
|
||||||
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,18 +1,15 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
import { generateUniqueName } 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&token=${token}`);
|
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-sql-runner-west-us");
|
||||||
const explorer = await waitForExplorer();
|
const explorer = await waitForExplorer();
|
||||||
|
|
||||||
await explorer.click('[data-test="New Container"]');
|
await explorer.click('[data-test="New Container"]');
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
import { CosmosDBManagementClient } from "@azure/arm-cosmosdb";
|
import { CosmosDBManagementClient } from "@azure/arm-cosmosdb";
|
||||||
import { CosmosClient, PermissionMode } from "@azure/cosmos";
|
import { CosmosClient, PermissionMode } from "@azure/cosmos";
|
||||||
|
import * as msRestNodeAuth from "@azure/ms-rest-nodeauth";
|
||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateUniqueName, getAzureCLICredentials } from "../utils/shared";
|
import { generateUniqueName } from "../utils/shared";
|
||||||
jest.setTimeout(120000);
|
jest.setTimeout(120000);
|
||||||
|
|
||||||
const subscriptionId = process.env["AZURE_SUBSCRIPTION_ID"] ?? "";
|
const clientId = "fd8753b0-0707-4e32-84e9-2532af865fb4";
|
||||||
|
const secret = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET"];
|
||||||
|
const tenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
|
||||||
|
const subscriptionId = "69e02f2d-f059-4409-9eac-97e8a276ae2c";
|
||||||
const resourceGroupName = "runners";
|
const resourceGroupName = "runners";
|
||||||
|
|
||||||
test("Resource token", async () => {
|
test("Resource token", async () => {
|
||||||
const credentials = await getAzureCLICredentials();
|
const credentials = await msRestNodeAuth.loginWithServicePrincipalSecret(clientId, secret, tenantId);
|
||||||
const armClient = new CosmosDBManagementClient(credentials, subscriptionId);
|
const armClient = new CosmosDBManagementClient(credentials, subscriptionId);
|
||||||
const account = await armClient.databaseAccounts.get(resourceGroupName, "portal-sql-runner-west-us");
|
const account = await armClient.databaseAccounts.get(resourceGroupName, "portal-sql-runner-west-us");
|
||||||
const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, "portal-sql-runner-west-us");
|
const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, "portal-sql-runner-west-us");
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
import { generateUniqueName } from "../utils/shared";
|
||||||
import { waitForExplorer } from "../utils/waitForExplorer";
|
import { waitForExplorer } from "../utils/waitForExplorer";
|
||||||
|
|
||||||
jest.setTimeout(120000);
|
jest.setTimeout(120000);
|
||||||
|
|
||||||
test("Tables CRUD", async () => {
|
test("Tables CRUD", async () => {
|
||||||
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-tables-runner&token=${token}`);
|
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-tables-runner");
|
||||||
const explorer = await waitForExplorer();
|
const explorer = await waitForExplorer();
|
||||||
|
|
||||||
await page.waitForSelector('text="Querying databases"', { state: "detached" });
|
await page.waitForSelector('text="Querying databases"', { state: "detached" });
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
|
import { ClientSecretCredential } from "@azure/identity";
|
||||||
import "../../less/hostedexplorer.less";
|
import "../../less/hostedexplorer.less";
|
||||||
import { DataExplorerInputsFrame } from "../../src/Contracts/ViewModels";
|
import { DataExplorerInputsFrame } from "../../src/Contracts/ViewModels";
|
||||||
import { updateUserContext } from "../../src/UserContext";
|
import { updateUserContext } from "../../src/UserContext";
|
||||||
@@ -10,13 +11,29 @@ const urlSearchParams = new URLSearchParams(window.location.search);
|
|||||||
const accountName = urlSearchParams.get("accountName") || "portal-sql-runner-west-us";
|
const accountName = urlSearchParams.get("accountName") || "portal-sql-runner-west-us";
|
||||||
const selfServeType = urlSearchParams.get("selfServeType") || "example";
|
const selfServeType = urlSearchParams.get("selfServeType") || "example";
|
||||||
const iframeSrc = urlSearchParams.get("iframeSrc") || "explorer.html?platform=Portal&disablePortalInitCache";
|
const iframeSrc = urlSearchParams.get("iframeSrc") || "explorer.html?platform=Portal&disablePortalInitCache";
|
||||||
const token = urlSearchParams.get("token");
|
|
||||||
|
if (!process.env.AZURE_CLIENT_SECRET) {
|
||||||
|
throw new Error(
|
||||||
|
"process.env.AZURE_CLIENT_SECRET was not set! Set it in your .env file and restart webpack dev server",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Azure SDK clients accept the credential as a parameter
|
||||||
|
const credentials = new ClientSecretCredential(
|
||||||
|
process.env.AZURE_TENANT_ID,
|
||||||
|
process.env.AZURE_CLIENT_ID,
|
||||||
|
process.env.AZURE_CLIENT_SECRET,
|
||||||
|
{
|
||||||
|
authorityHost: "https://localhost:1234",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
console.log("Resource Group:", resourceGroup);
|
console.log("Resource Group:", resourceGroup);
|
||||||
console.log("Subcription: ", subscriptionId);
|
console.log("Subcription: ", subscriptionId);
|
||||||
console.log("Account Name: ", accountName);
|
console.log("Account Name: ", accountName);
|
||||||
|
|
||||||
const initTestExplorer = async (): Promise<void> => {
|
const initTestExplorer = async (): Promise<void> => {
|
||||||
|
const { token } = await credentials.getToken("https://management.azure.com//.default");
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
authorizationToken: `bearer ${token}`,
|
authorizationToken: `bearer ${token}`,
|
||||||
});
|
});
|
||||||
@@ -35,9 +52,6 @@ const initTestExplorer = async (): Promise<void> => {
|
|||||||
dnsSuffix: "documents.azure.com",
|
dnsSuffix: "documents.azure.com",
|
||||||
serverId: "prod1",
|
serverId: "prod1",
|
||||||
extensionEndpoint: "/proxy",
|
extensionEndpoint: "/proxy",
|
||||||
portalBackendEndpoint: "https://cdb-ms-mpac-pbe.cosmos.azure.com",
|
|
||||||
mongoProxyEndpoint: "https://cdb-ms-mpac-mp.cosmos.azure.com",
|
|
||||||
cassandraProxyEndpoint: "https://cdb-ms-mpac-cp.cosmos.azure.com",
|
|
||||||
subscriptionType: 3,
|
subscriptionType: 3,
|
||||||
quotaId: "Internal_2014-09-01",
|
quotaId: "Internal_2014-09-01",
|
||||||
isTryCosmosDBSubscription: false,
|
isTryCosmosDBSubscription: false,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { AzureCliCredentials } from "@azure/ms-rest-nodeauth";
|
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
|
||||||
export function generateUniqueName(baseName = "", length = 4): string {
|
export function generateUniqueName(baseName = "", length = 4): string {
|
||||||
@@ -8,13 +7,3 @@ export function generateUniqueName(baseName = "", length = 4): string {
|
|||||||
export function generateDatabaseNameWithTimestamp(baseName = "db", length = 1): string {
|
export function generateDatabaseNameWithTimestamp(baseName = "db", length = 1): string {
|
||||||
return `${baseName}${crypto.randomBytes(length).toString("hex")}-${Date.now()}`;
|
return `${baseName}${crypto.randomBytes(length).toString("hex")}-${Date.now()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAzureCLICredentials(): Promise<AzureCliCredentials> {
|
|
||||||
return await AzureCliCredentials.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAzureCLICredentialsToken(): Promise<string> {
|
|
||||||
const credentials = await getAzureCLICredentials();
|
|
||||||
const token = (await credentials.getToken()).accessToken;
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ const msRestNodeAuth = require("@azure/ms-rest-nodeauth");
|
|||||||
const { CosmosDBManagementClient } = require("@azure/arm-cosmosdb");
|
const { CosmosDBManagementClient } = require("@azure/arm-cosmosdb");
|
||||||
const ms = require("ms");
|
const ms = require("ms");
|
||||||
|
|
||||||
const subscriptionId = process.env["AZURE_SUBSCRIPTION_ID"];
|
const clientId = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_ID"];
|
||||||
|
const secret = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET"];
|
||||||
|
const tenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
|
||||||
|
const subscriptionId = "69e02f2d-f059-4409-9eac-97e8a276ae2c";
|
||||||
const resourceGroupName = "runners";
|
const resourceGroupName = "runners";
|
||||||
|
|
||||||
const thirtyMinutesAgo = new Date(Date.now() - 1000 * 60 * 30).getTime();
|
const thirtyMinutesAgo = new Date(Date.now() - 1000 * 60 * 30).getTime();
|
||||||
@@ -16,7 +19,7 @@ function friendlyTime(date) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const credentials = await msRestNodeAuth.AzureCliCredentials.create();
|
const credentials = await msRestNodeAuth.loginWithServicePrincipalSecret(clientId, secret, tenantId);
|
||||||
const client = new CosmosDBManagementClient(credentials, subscriptionId);
|
const client = new CosmosDBManagementClient(credentials, subscriptionId);
|
||||||
const accounts = await client.databaseAccounts.list(resourceGroupName);
|
const accounts = await client.databaseAccounts.list(resourceGroupName);
|
||||||
for (const account of accounts) {
|
for (const account of accounts) {
|
||||||
@@ -35,7 +38,7 @@ async function main() {
|
|||||||
} else if (account.capabilities.find((c) => c.name === "EnableCassandra")) {
|
} else if (account.capabilities.find((c) => c.name === "EnableCassandra")) {
|
||||||
const cassandraDatabases = await client.cassandraResources.listCassandraKeyspaces(
|
const cassandraDatabases = await client.cassandraResources.listCassandraKeyspaces(
|
||||||
resourceGroupName,
|
resourceGroupName,
|
||||||
account.name,
|
account.name
|
||||||
);
|
);
|
||||||
for (const database of cassandraDatabases) {
|
for (const database of cassandraDatabases) {
|
||||||
const timestamp = Number(database.resource._ts) * 1000;
|
const timestamp = Number(database.resource._ts) * 1000;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
<clear />
|
<clear />
|
||||||
<add name="X-Xss-Protection" value="1; mode=block" />
|
<add name="X-Xss-Protection" value="1; mode=block" />
|
||||||
<add name="X-Content-Type-Options" value="nosniff" />
|
<add name="X-Content-Type-Options" value="nosniff" />
|
||||||
<add name="Content-Security-Policy" value="frame-ancestors 'self' portal.azure.com *.portal.azure.com portal.azure.us portal.azure.cn portal.microsoftazure.de df.onecloud.azure-test.net *.fabric.microsoft.com *.powerbi.com *.analysis-df.windows.net cosmos-explorer-preview.azurewebsites.net" />
|
<add name="Content-Security-Policy" value="frame-ancestors 'self' portal.azure.com *.portal.azure.com portal.azure.us portal.azure.cn portal.microsoftazure.de df.onecloud.azure-test.net *.fabric.microsoft.com *.powerbi.com *.analysis-df.windows.net" />
|
||||||
</customHeaders>
|
</customHeaders>
|
||||||
<redirectHeaders>
|
<redirectHeaders>
|
||||||
<clear />
|
<clear />
|
||||||
|
|||||||
Reference in New Issue
Block a user