diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a53b92645..fd097a194 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,9 @@ on: pull_request: branches: - master +permissions: + id-token: write + contents: read jobs: codemetrics: runs-on: ubuntu-latest @@ -134,7 +137,7 @@ jobs: runs-on: ubuntu-latest env: NODE_TLS_REJECT_UNAUTHORIZED: 0 - NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }} + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} strategy: fail-fast: false matrix: @@ -145,11 +148,18 @@ jobs: - ./test/mongo/container.spec.ts - ./test/mongo/container32.spec.ts - ./test/selfServe/selfServeExample.spec.ts - # - ./test/notebooks/upload.spec.ts // TEMP disabled since notebooks service is off - ./test/sql/resourceToken.spec.ts - ./test/tables/container.spec.ts steps: - uses: actions/checkout@v4 + + - name: "Az CLI login" + uses: azure/login@v1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + - name: Use Node.js 18.x uses: actions/setup-node@v4 with: diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index 229477f0b..6698951ae 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -9,6 +9,10 @@ on: # Once every hour - cron: "0 15 * * *" +permissions: + id-token: write + contents: read + # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # This workflow contains a single job called "build" @@ -16,10 +20,17 @@ jobs: name: "Cleanup Test Database Accounts" runs-on: ubuntu-latest env: - NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }} - NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }} + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} steps: - uses: actions/checkout@v2 + + - name: "Az CLI login" + uses: azure/login@v1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + - name: Use Node.js 18.x uses: actions/setup-node@v1 with: diff --git a/images/Home_16.svg b/images/Home_16.svg new file mode 100644 index 000000000..80facc866 --- /dev/null +++ b/images/Home_16.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/jest.config.js b/jest.config.js index c00efdac6..b4f660063 100644 --- a/jest.config.js +++ b/jest.config.js @@ -76,6 +76,10 @@ module.exports = { "^dnd-core$": "dnd-core/dist/cjs", "^react-dnd$": "react-dnd/dist/cjs", "^react-dnd-html5-backend$": "react-dnd-html5-backend/dist/cjs", + "d3-force": "/node_modules/d3-force/dist/d3-force.min.js", + "d3-quadtree": "/node_modules/d3-quadtree/dist/d3-quadtree.min.js", + "d3-scale-chromatic": "/node_modules/d3-scale-chromatic/dist/d3-scale-chromatic.min.js", + "d3-zoom": "/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 @@ -130,7 +134,6 @@ module.exports = { // The test environment that will be used for testing // testEnvironment: "jest-environment-jsdom", - modulePaths: ["node_modules", "/src"], // Options that will be passed to the testEnvironment diff --git a/less/documentDB.less b/less/documentDB.less index d8ae159db..357d159b3 100644 --- a/less/documentDB.less +++ b/less/documentDB.less @@ -2296,6 +2296,17 @@ a:link { display: none !important; } +.monaco-editor .quick-input-list-label { + /* Restore some of Monaco's default styles that are clobbered by our global styles */ + padding: 0; + line-height: 22px; +} + +.monaco-editor .quick-input-list .highlight { + /* Padding in highlighted text within the quick input list breaks the flow of the text */ + padding: 0; +} + td a { color: #393939; } diff --git a/package-lock.json b/package-lock.json index d3a36e3f3..8dcfe5d50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,8 @@ "@azure/arm-cosmosdb": "9.1.0", "@azure/cosmos": "4.0.1-beta.2", "@azure/cosmos-language-service": "0.0.5", - "@azure/identity": "1.2.1", - "@azure/ms-rest-nodeauth": "3.0.7", + "@azure/identity": "1.5.2", + "@azure/ms-rest-nodeauth": "3.1.1", "@azure/msal-browser": "2.14.2", "@babel/plugin-proposal-class-properties": "7.12.1", "@babel/plugin-proposal-decorators": "7.12.12", @@ -51,6 +51,7 @@ "@types/lodash": "4.14.171", "@types/mkdirp": "1.0.1", "@types/node-fetch": "2.5.7", + "@xmldom/xmldom": "0.7.13", "applicationinsights": "1.8.0", "bootstrap": "3.4.1", "canvas": "file:./canvas", @@ -59,7 +60,7 @@ "copy-webpack-plugin": "11.0.0", "crossroads": "0.12.2", "css-element-queries": "1.1.1", - "d3": "6.1.1", + "d3": "7.8.5", "datatables.net-colreorder-dt": "1.7.0", "datatables.net-dt": "1.13.8", "date-fns": "1.29.0", @@ -74,12 +75,14 @@ "i18next-browser-languagedetector": "6.0.1", "i18next-http-backend": "1.0.23", "iframe-resizer-react": "1.1.0", + "immer": "9.0.6", "immutable": "4.0.0-rc.12", "is-ci": "2.0.0", "jquery": "3.7.1", "jquery-typeahead": "2.11.1", "jquery-ui-dist": "1.13.2", "knockout": "3.5.1", + "loader-utils": "2.0.3", "mkdirp": "1.0.4", "monaco-editor": "0.44.0", "ms": "2.1.3", @@ -103,10 +106,12 @@ "reflect-metadata": "0.1.13", "rx-jupyter": "5.5.12", "sanitize-html": "2.3.3", + "shell-quote": "1.7.3", "styled-components": "5.0.1", "swr": "0.4.0", "terser-webpack-plugin": "5.3.9", - "underscore": "1.9.1", + "tinykeys": "2.1.0", + "underscore": "1.12.1", "utility-types": "3.10.0", "zustand": "3.5.0" }, @@ -175,25 +180,25 @@ "less-vars-loader": "1.1.0", "mini-css-extract-plugin": "2.1.0", "monaco-editor-webpack-plugin": "7.1.0", - "node-fetch": "2.6.1", + "node-fetch": "2.6.7", "playwright": "1.13.0", "prettier": "3.0.3", "process": "0.11.10", "querystring-es3": "0.2.1", "raw-loader": "0.5.1", - "react-dev-utils": "11.0.4", + "react-dev-utils": "12.0.1", "rimraf": "3.0.0", "sinon": "3.2.1", "style-loader": "0.23.0", "ts-loader": "9.2.4", - "typedoc": "0.21.5", + "typedoc": "0.22.15", "typescript": "4.3.5", "url-loader": "4.1.1", "wait-on": "4.0.2", "webpack": "5.88.2", "webpack-bundle-analyzer": "4.9.1", "webpack-cli": "5.1.4", - "webpack-dev-server": "4.15.1" + "webpack-dev-server": "4.15.2" } }, "canvas": { @@ -248,14 +253,6 @@ "tslib": "^1.10.0" } }, - "node_modules/@azure/core-asynciterator-polyfill": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@azure/core-asynciterator-polyfill/-/core-asynciterator-polyfill-1.0.2.tgz", - "integrity": "sha512-3rkP4LnnlWawl0LZptJOdXNrT/fHp2eQMadoasa6afspXdpGrtPZuAQc2PD0cpgyuoXtUWyC3tv7xfntjGS5Dw==", - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/@azure/core-auth": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.5.0.tgz", @@ -274,66 +271,35 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "node_modules/@azure/core-http": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@azure/core-http/-/core-http-1.2.6.tgz", - "integrity": "sha512-odtH7UMKtekc5YQ86xg9GlVHNXR6pq2JgJ5FBo7/jbOjNGdBqcrIVrZx2bevXVJz/uUTSx6vUf62gzTXTfqYSQ==", + "node_modules/@azure/core-client": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.9.2.tgz", + "integrity": "sha512-kRdry/rav3fUKHl/aDLd/pDLcB+4pOFwPPTVEExuMyaI5r+JBbMWqRbCY1pn5BniDaU3lRxO9eaQ1AmSMehl/w==", "dependencies": { - "@azure/abort-controller": "^1.0.0", - "@azure/core-asynciterator-polyfill": "^1.0.0", - "@azure/core-auth": "^1.3.0", - "@azure/core-tracing": "1.0.0-preview.11", + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.9.1", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.6.1", "@azure/logger": "^1.0.0", - "@types/node-fetch": "^2.5.0", - "@types/tunnel": "^0.0.1", - "form-data": "^3.0.0", - "node-fetch": "^2.6.0", - "process": "^0.11.10", - "tough-cookie": "^4.0.0", - "tslib": "^2.2.0", - "tunnel": "^0.0.6", - "uuid": "^8.3.0", - "xml2js": "^0.4.19" + "tslib": "^2.6.2" }, "engines": { - "node": ">=8.0.0" + "node": ">=18.0.0" } }, - "node_modules/@azure/core-http/node_modules/@azure/core-tracing": { - "version": "1.0.0-preview.11", - "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.11.tgz", - "integrity": "sha512-frF0pJc9HTmKncVokhBxCqipjbql02DThQ1ZJ9wLi7SDMLdPAFyDI5xZNzX5guLz+/DtPkY+SGK2li9FIXqshQ==", + "node_modules/@azure/core-client/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", "dependencies": { - "@opencensus/web-types": "0.0.7", - "@opentelemetry/api": "1.0.0-rc.0", - "tslib": "^2.0.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=8.0.0" + "node": ">=18.0.0" } }, - "node_modules/@azure/core-http/node_modules/@opentelemetry/api": { - "version": "1.0.0-rc.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.0.0-rc.0.tgz", - "integrity": "sha512-iXKByCMfrlO5S6Oh97BuM56tM2cIBB0XsL/vWF/AtJrJEKx4MC/Xdu0xDsGXMGcNWpqF7ujMsjjnp0+UHBwnDQ==", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@azure/core-http/node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@azure/core-http/node_modules/tslib": { + "node_modules/@azure/core-client/node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" @@ -454,42 +420,44 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/@azure/identity": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-1.2.1.tgz", - "integrity": "sha512-vCzV4Xg5hWJ2e4Et0waOmIEgYHsqtGF06kklnqblZg0hKDLKxTAX5FzKYuDMk1CctY2UdEmWFcA2li2uOXOLXQ==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-1.5.2.tgz", + "integrity": "sha512-vqyeRbd2i0h9F4mqW5JbkP1xfabqKQ21l/81osKhpOQ2LtwaJW6nw4+0PsVYnxcbPHFCIZt6EWAk74a3OGYZJA==", "dependencies": { - "@azure/core-http": "^1.2.0", - "@azure/core-tracing": "1.0.0-preview.9", + "@azure/core-auth": "^1.3.0", + "@azure/core-client": "^1.0.0", + "@azure/core-rest-pipeline": "^1.1.0", + "@azure/core-tracing": "1.0.0-preview.12", "@azure/logger": "^1.0.0", - "@azure/msal-node": "1.0.0-beta.1", - "@opentelemetry/api": "^0.10.2", + "@azure/msal-node": "1.0.0-beta.6", + "@types/stoppable": "^1.1.0", "axios": "^0.21.1", "events": "^3.0.0", "jws": "^4.0.0", "msal": "^1.0.2", "open": "^7.0.0", "qs": "^6.7.0", + "stoppable": "^1.1.0", "tslib": "^2.0.0", "uuid": "^8.3.0" }, "engines": { - "node": ">=8.0.0" + "node": ">=12.0.0" }, "optionalDependencies": { - "keytar": "^5.4.0" + "keytar": "^7.3.0" } }, "node_modules/@azure/identity/node_modules/@azure/core-tracing": { - "version": "1.0.0-preview.9", - "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.9.tgz", - "integrity": "sha512-zczolCLJ5QG42AEPQ+Qg9SRYNUyB+yZ5dzof4YEc+dyWczO9G2sBqbAjLB7IqrsdHN2apkiB2oXeDKCsq48jug==", + "version": "1.0.0-preview.12", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.12.tgz", + "integrity": "sha512-nvo2Wc4EKZGN6eFu9n3U7OXmASmL8VxoPIH7xaD6OlQqi44bouF0YIi9ID5rEsKLiAU59IYx6M297nqWVMWPDg==", "dependencies": { - "@opencensus/web-types": "0.0.7", - "@opentelemetry/api": "^0.10.2", - "tslib": "^2.0.0" + "@opentelemetry/api": "^1.0.0", + "tslib": "^2.2.0" }, "engines": { - "node": ">=8.0.0" + "node": ">=12.0.0" } }, "node_modules/@azure/identity/node_modules/tslib": { @@ -596,13 +564,13 @@ } }, "node_modules/@azure/ms-rest-nodeauth": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@azure/ms-rest-nodeauth/-/ms-rest-nodeauth-3.0.7.tgz", - "integrity": "sha512-7Q1MyMB+eqUQy8JO+virSIzAjqR2UbKXE/YQZe+53gC8yakm8WOQ5OzGfPP+eyHqeRs6bQESyw2IC5feLWlT2A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@azure/ms-rest-nodeauth/-/ms-rest-nodeauth-3.1.1.tgz", + "integrity": "sha512-UA/8dgLy3+ZiwJjAZHxL4MUB14fFQPkaAOZ94jsTW/Z6WmoOeny2+cLk0+dyIX/iH6qSrEWKwbStEeB970B9pA==", "dependencies": { "@azure/ms-rest-azure-env": "^2.0.0", "@azure/ms-rest-js": "^2.0.4", - "adal-node": "^0.1.28" + "adal-node": "^0.2.2" } }, "node_modules/@azure/msal-browser": { @@ -629,61 +597,17 @@ } }, "node_modules/@azure/msal-node": { - "version": "1.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.0.0-beta.1.tgz", - "integrity": "sha512-dO/bgVScpl5loZfsfhHXmFLTNoDxGvUiZIsJCe1+HpHyFWXwGsBZ71P5ixbxRhhf/bPpZS3X+/rm1Fq2uUucJw==", + "version": "1.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.0.0-beta.6.tgz", + "integrity": "sha512-ZQI11Uz1j0HJohb9JZLRD8z0moVcPks1AFW4Q/Gcl67+QvH4aKEJti7fjCcipEEZYb/qzLSO8U6IZgPYytsiJQ==", "deprecated": "A newer major version of this library is available. Please upgrade to the latest available version.", "dependencies": { - "@azure/msal-common": "^1.7.2", - "axios": "^0.19.2", + "@azure/msal-common": "^4.0.0", + "axios": "^0.21.1", "jsonwebtoken": "^8.5.1", "uuid": "^8.3.0" } }, - "node_modules/@azure/msal-node/node_modules/@azure/msal-common": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-1.7.2.tgz", - "integrity": "sha512-3/voCdFKONENX+5tMrNOBSrVJb6NbE7YB8vc4FZ/4ZbjpK7GVtq9Bu1MW+HZhrmsUzSF/joHx0ZIJDYIequ/jg==", - "dependencies": { - "debug": "^4.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@azure/msal-node/node_modules/axios": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", - "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", - "deprecated": "Critical security vulnerability fixed in v0.21.1. For more information, see https://github.com/axios/axios/pull/3410", - "dependencies": { - "follow-redirects": "1.5.10" - } - }, - "node_modules/@azure/msal-node/node_modules/follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "dependencies": { - "debug": "=3.1.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/@azure/msal-node/node_modules/follow-redirects/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/@azure/msal-node/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/@babel/code-frame": { "version": "7.23.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.4.tgz", @@ -7066,17 +6990,6 @@ "node": ">=10" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/@mapbox/node-pre-gyp/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -10179,29 +10092,10 @@ "@octokit/openapi-types": "^19.0.2" } }, - "node_modules/@opencensus/web-types": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/@opencensus/web-types/-/web-types-0.0.7.tgz", - "integrity": "sha512-xB+w7ZDAu3YBzqH44rCmG9/RlrOmFuDPt/bpf17eJr8eZSrLt7nc7LnWdxM9Mmoj/YKMHpxRg28txu3TcpiL+g==", - "engines": { - "node": ">=6.0" - } - }, "node_modules/@opentelemetry/api": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-0.10.2.tgz", - "integrity": "sha512-GtpMGd6vkzDMYcpu2t9LlhEgMy/SzBwRnz48EejlRArYqZzqSzAsKmegUK7zHgl+EOIaK9mKHhnRaQu3qw20cA==", - "dependencies": { - "@opentelemetry/context-base": "^0.10.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@opentelemetry/context-base": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-base/-/context-base-0.10.2.tgz", - "integrity": "sha512-hZNKjKOYsckoOEgBziGMnBcX0M7EtstnCmwz5jZUOUYwlZ+/xxX6z3jPu1XVO2Jivk0eLfuP9GP+vFD49CMetw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.8.0.tgz", + "integrity": "sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==", "engines": { "node": ">=8.0.0" } @@ -12834,6 +12728,12 @@ "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", "devOptional": true }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "dev": true + }, "node_modules/@types/post-robot": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/@types/post-robot/-/post-robot-10.0.1.tgz", @@ -13613,6 +13513,14 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "devOptional": true }, + "node_modules/@types/stoppable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@types/stoppable/-/stoppable-1.1.3.tgz", + "integrity": "sha512-7wGKIBJGE4ZxFjk9NkjAxZMLlIXroETqP1FJCdoSvKmEznwmBxQFmTB1dsCkAvVcNemuSZM5qkkd9HE/NL2JTw==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/styled-components": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.1.tgz", @@ -13639,14 +13547,6 @@ "@types/jest": "*" } }, - "node_modules/@types/tunnel": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.1.tgz", - "integrity": "sha512-AOqu6bQu5MSWwYvehMXLukFHnupHrpZ8nvgae5Ggie9UwzDR1CCwoXgSSWNZJuyOlCdfdsWMA5F2LlmvyoTv8A==", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/underscore": { "version": "1.7.36", "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.7.36.tgz", @@ -14183,6 +14083,14 @@ } } }, + "node_modules/@xmldom/xmldom": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.13.tgz", + "integrity": "sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -14300,29 +14208,39 @@ } }, "node_modules/adal-node": { - "version": "0.1.28", - "resolved": "https://registry.npmjs.org/adal-node/-/adal-node-0.1.28.tgz", - "integrity": "sha512-98nQ5MQSyJR0ZY/R0Mue/cv4OkebRyKz4hS40GdkZU42Bq49ldHeup7UeAo/0vROMB57CX2et6IF0U/Pe1rY3A==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/adal-node/-/adal-node-0.2.4.tgz", + "integrity": "sha512-zIcvbwQFKMUtKxxj8YMHeTT1o/TPXfVNsTXVgXD8sxwV6h4AFQgK77dRciGhuEF9/Sdm3UQPJVPc/6XxrccSeA==", "deprecated": "This package is no longer supported. Please migrate to @azure/msal-node.", "dependencies": { - "@types/node": "^8.0.47", - "async": ">=0.6.0", + "@xmldom/xmldom": "^0.8.3", + "async": "^2.6.3", + "axios": "^0.21.1", "date-utils": "*", "jws": "3.x.x", - "request": ">= 2.52.0", "underscore": ">= 1.3.1", "uuid": "^3.1.0", - "xmldom": ">= 0.1.x", "xpath.js": "~1.1.0" }, "engines": { "node": ">= 0.6.15" } }, - "node_modules/adal-node/node_modules/@types/node": { - "version": "8.10.66", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz", - "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==" + "node_modules/adal-node/node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/adal-node/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dependencies": { + "lodash": "^4.17.14" + } }, "node_modules/adal-node/node_modules/jwa": { "version": "1.4.1", @@ -14638,7 +14556,9 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/archy": { "version": "1.0.0", @@ -14646,16 +14566,6 @@ "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", "dev": true }, - "node_modules/are-we-there-yet": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", - "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", - "optional": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -14939,7 +14849,9 @@ "node_modules/async": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true, + "peer": true }, "node_modules/async-hook-jl": { "version": "1.7.6", @@ -15083,6 +14995,32 @@ "webpack": ">=2" } }, + "node_modules/babel-loader/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/babel-loader/node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/babel-loader/node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -16418,7 +16356,9 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", + "dev": true, "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -16695,7 +16635,9 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/content-disposition": { "version": "0.5.4", @@ -16969,7 +16911,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "devOptional": true + "dev": true }, "node_modules/cosmiconfig": { "version": "5.2.1", @@ -17317,40 +17259,43 @@ } }, "node_modules/d3": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/d3/-/d3-6.1.1.tgz", - "integrity": "sha512-bJYW9wlS2uvP2EoMkcPptrUzLMHQKCbiSW+/la8iGSLZgs4KbI/f3Fch4RtnUA9PA+/nPlwyFYzTwDjX80Of8w==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", + "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==", "dependencies": { - "d3-array": "2", - "d3-axis": "2", - "d3-brush": "2", - "d3-chord": "2", - "d3-color": "2", - "d3-contour": "2", - "d3-delaunay": "5", - "d3-dispatch": "2", - "d3-drag": "2", - "d3-dsv": "2", - "d3-ease": "2", - "d3-fetch": "2", - "d3-force": "2", - "d3-format": "2", - "d3-geo": "2", - "d3-hierarchy": "2", - "d3-interpolate": "2", - "d3-path": "2", - "d3-polygon": "2", - "d3-quadtree": "2", - "d3-random": "2", - "d3-scale": "3", - "d3-scale-chromatic": "2", - "d3-selection": "2", - "d3-shape": "2", - "d3-time": "2", - "d3-time-format": "3", - "d3-timer": "2", - "d3-transition": "2", - "d3-zoom": "2" + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-array": { @@ -17362,9 +17307,12 @@ } }, "node_modules/d3-axis": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-2.1.0.tgz", - "integrity": "sha512-z/G2TQMyuf0X3qP+Mh+2PimoJD41VOCjViJzT0BHeL/+JQAofkiWZbWxlwFGb1N8EN+Cl/CW+MUKbVzr1689Cw==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-brush": { "version": "2.1.0", @@ -17379,11 +17327,14 @@ } }, "node_modules/d3-chord": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-2.0.0.tgz", - "integrity": "sha512-D5PZb7EDsRNdGU4SsjQyKhja8Zgu+SHZfUSO5Ls8Wsn+jsAKUUGkcshLxMg9HDFxG3KqavGWaWkJ8EpU8ojuig==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", "dependencies": { - "d3-path": "1 - 2" + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-collection": { @@ -17397,19 +17348,36 @@ "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" }, "node_modules/d3-contour": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-2.0.0.tgz", - "integrity": "sha512-9unAtvIaNk06UwqBmvsdHX7CZ+NPDZnn8TtNH1myW93pWJkhsV25JcgnYAu0Ck5Veb1DHiCv++Ic5uvJ+h50JA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", "dependencies": { - "d3-array": "2" + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour/node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-delaunay": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-5.3.0.tgz", - "integrity": "sha512-amALSrOllWVLaHTnDLHwMIiz0d1bBu9gZXd1FiLfXf8sHcX9jrcj81TVZOqD4UX7MgBZZ07c8GxzEgBpJqc74w==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", "dependencies": { - "delaunator": "4" + "delaunator": "5" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-dispatch": { @@ -17427,24 +17395,46 @@ } }, "node_modules/d3-dsv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-2.0.0.tgz", - "integrity": "sha512-E+Pn8UJYx9mViuIUkoc93gJGGYut6mSDKy2+XaPwccwkRGlR+LO97L2VCCRjQivTwLHkSnAJG7yo00BWY6QM+w==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", "dependencies": { - "commander": "2", - "iconv-lite": "0.4", + "commander": "7", + "iconv-lite": "0.6", "rw": "1" }, "bin": { - "csv2json": "bin/dsv2json", - "csv2tsv": "bin/dsv2dsv", - "dsv2dsv": "bin/dsv2dsv", - "dsv2json": "bin/dsv2json", - "json2csv": "bin/json2dsv", - "json2dsv": "bin/json2dsv", - "json2tsv": "bin/json2dsv", - "tsv2csv": "bin/dsv2dsv", - "tsv2json": "bin/dsv2json" + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-dsv/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, "node_modules/d3-ease": { @@ -17453,21 +17443,27 @@ "integrity": "sha512-68/n9JWarxXkOWMshcT5IcjbB+agblQUaIsbnXmrzejn2O82n3p2A9R2zEB9HIEFWKFwPAEDDN8gR0VdSAyyAQ==" }, "node_modules/d3-fetch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-2.0.0.tgz", - "integrity": "sha512-TkYv/hjXgCryBeNKiclrwqZH7Nb+GaOwo3Neg24ZVWA3MKB+Rd+BY84Nh6tmNEMcjUik1CSUWjXYndmeO6F7sw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", "dependencies": { - "d3-dsv": "1 - 2" + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-force": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-2.1.1.tgz", - "integrity": "sha512-nAuHEzBqMvpFVMf9OX75d00OxvOXdxY+xECIXjW6Gv8BRrXu6gAWbv/9XKrvfJ5i5DCokDW7RYE50LRoK092ew==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", "dependencies": { - "d3-dispatch": "1 - 2", - "d3-quadtree": "1 - 2", - "d3-timer": "1 - 2" + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-format": { @@ -17476,11 +17472,14 @@ "integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==" }, "node_modules/d3-geo": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-2.0.2.tgz", - "integrity": "sha512-8pM1WGMLGFuhq9S+FpPURxic+gKzjluCD/CHTuUF3mXMeiCo0i6R0tO1s4+GArRFde96SLcW/kOFRjoAosPsFA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", "dependencies": { - "d3-array": "^2.5.0" + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-glyphedge": { @@ -17494,9 +17493,12 @@ "integrity": "sha512-KS3fUT2ReD4RlGCjvCEm1RgMtp2NFZumdMu4DBzQK8AZv3fXRM6Xm8I4fSU07UXvH4xxg03NwWKWdvxfS/yc4w==" }, "node_modules/d3-hierarchy": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-2.0.0.tgz", - "integrity": "sha512-SwIdqM3HxQX2214EG9GTjgmCc/mbSx4mQBn+DuEETubhOw6/U3fmnji4uCVrmzOydMHSO1nZle5gh6HB/wdOzw==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-interpolate": { "version": "2.0.1", @@ -17507,9 +17509,12 @@ } }, "node_modules/d3-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-2.0.0.tgz", - "integrity": "sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-path-arrows": { "version": "0.4.0", @@ -17531,19 +17536,28 @@ "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" }, "node_modules/d3-polygon": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-2.0.0.tgz", - "integrity": "sha512-MsexrCK38cTGermELs0cO1d79DcTsQRN7IWMJKczD/2kBjzNXxLUWP33qRF6VDpiLV/4EI4r6Gs0DAWQkE8pSQ==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-quadtree": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-2.0.0.tgz", - "integrity": "sha512-b0Ed2t1UUalJpc3qXzKi+cPGxeXRr4KU9YSlocN74aTzp6R/Ud43t79yLLqxHRWZfsvWXmbDWPpoENK1K539xw==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-random": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-2.2.2.tgz", - "integrity": "sha512-0D9P8TRj6qDAtHhRQn6EfdOtHMfsUWanl3yb/84C4DqpZ+VsgfI5iTVRNRbELCfNvRfpMr8OrqqUTQ6ANGCijw==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-sankey-circular": { "version": "0.25.0", @@ -17573,12 +17587,15 @@ } }, "node_modules/d3-scale-chromatic": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-2.0.0.tgz", - "integrity": "sha512-LLqy7dJSL8yDy7NRmf6xSlsFZ6zYvJ4BcWFE4zBrOPnQERv9zj24ohnXKRbyi9YHnYV+HN1oEO3iFK971/gkzA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", "dependencies": { - "d3-color": "1 - 2", - "d3-interpolate": "1 - 2" + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-selection": { @@ -17641,23 +17658,181 @@ "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==" }, "node_modules/d3-zoom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-2.0.0.tgz", - "integrity": "sha512-fFg7aoaEm9/jf+qfstak0IYpnesZLiMX6GZvXtUSdv8RH2o4E2qeelgdU09eKS6wGuiGMfcnMI0nTIqWzRHGpw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", "dependencies": { - "d3-dispatch": "1 - 2", - "d3-drag": "2", - "d3-interpolate": "1 - 2", - "d3-selection": "2", - "d3-transition": "2" + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "engines": { + "node": ">=12" } }, "node_modules/d3/node_modules/d3-shape": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-2.1.0.tgz", - "integrity": "sha512-PnjUqfM2PpskbSLTJvAzp2Wv4CZsnAgTfcVRTwW03QR3MkXF8Uo7B1y/lWkAsmbKwuecto++4NlsYcvYpXpTHA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", "dependencies": { - "d3-path": "1 - 2" + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" } }, "node_modules/dashdash": { @@ -17810,7 +17985,9 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "mimic-response": "^2.0.0" }, @@ -18077,9 +18254,12 @@ } }, "node_modules/delaunator": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz", - "integrity": "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "dependencies": { + "robust-predicates": "^3.0.2" + } }, "node_modules/delayed-stream": { "version": "1.0.0", @@ -18099,7 +18279,9 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/denodeify": { "version": "1.2.1", @@ -18153,15 +18335,12 @@ } }, "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", "optional": true, - "bin": { - "detect-libc": "bin/detect-libc.js" - }, "engines": { - "node": ">=0.10" + "node": ">=8" } }, "node_modules/detect-newline": { @@ -20274,9 +20453,9 @@ "optional": true }, "node_modules/filesize": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz", - "integrity": "sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg==", + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", + "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==", "dev": true, "engines": { "node": ">= 0.4.0" @@ -20674,22 +20853,200 @@ } }, "node_modules/fork-ts-checker-webpack-plugin": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-4.1.6.tgz", - "integrity": "sha512-DUxuQaKoqfNne8iikd14SAkh5uw4+8vNifp6gmA73yYNS6ywLIWSLD/n/mBzHQRpW3J7rbATEakmiA8JvkTyZw==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz", + "integrity": "sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.5.5", - "chalk": "^2.4.1", - "micromatch": "^3.1.10", + "@babel/code-frame": "^7.8.3", + "@types/json-schema": "^7.0.5", + "chalk": "^4.1.0", + "chokidar": "^3.4.2", + "cosmiconfig": "^6.0.0", + "deepmerge": "^4.2.2", + "fs-extra": "^9.0.0", + "glob": "^7.1.6", + "memfs": "^3.1.2", "minimatch": "^3.0.4", - "semver": "^5.6.0", - "tapable": "^1.0.0", - "worker-rpc": "^0.1.0" + "schema-utils": "2.7.0", + "semver": "^7.3.2", + "tapable": "^1.0.0" }, "engines": { - "node": ">=6.11.5", + "node": ">=10", "yarn": ">=1.0.0" + }, + "peerDependencies": { + "eslint": ">= 6", + "typescript": ">= 2.7", + "vue-template-compiler": "*", + "webpack": ">= 4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + } + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/tapable": { @@ -20701,6 +21058,30 @@ "node": ">=6" } }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -20936,43 +21317,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==", - "optional": true, - "dependencies": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "node_modules/gauge/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gauge/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "optional": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -21324,16 +21668,18 @@ "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" }, "node_modules/gzip-size": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", - "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", "dev": true, "dependencies": { - "duplexer": "^0.1.1", - "pify": "^4.0.1" + "duplexer": "^0.1.2" }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/handle-thing": { @@ -21342,36 +21688,6 @@ "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/handlebars/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -21469,7 +21785,9 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/has-value": { "version": "1.0.0", @@ -21758,6 +22076,32 @@ "html-loader": "^0.5.1" } }, + "node_modules/html-loader/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/html-loader/node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/html-minifier": { "version": "3.5.21", "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", @@ -22237,6 +22581,14 @@ "node-fetch": "2.6.1" } }, + "node_modules/i18next-http-backend/node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -22332,10 +22684,9 @@ } }, "node_modules/immer": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/immer/-/immer-8.0.1.tgz", - "integrity": "sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA==", - "dev": true, + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.6.tgz", + "integrity": "sha512-G95ivKpy+EvVAnAab4fVa4YGYn24J1SpEktnJX7JJ45Bd7xqME/SCplFzYFmTbrkwZbQ4xJK1xMTUYBkN6pWsQ==", "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" @@ -22787,7 +23138,9 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "number-is-nan": "^1.0.0" }, @@ -28790,9 +29143,9 @@ } }, "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", "dev": true }, "node_modules/jsonfile": { @@ -28919,22 +29272,16 @@ "integrity": "sha512-yQa1dz+FilQ+w3JM6GH2V/wnFeQhfbkK9stvs3UiraW3GOEO7zrOBBh0ZuHsrzeN1xx6v7P5EpA2JtOUUnfN/w==" }, "node_modules/keytar": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/keytar/-/keytar-5.6.0.tgz", - "integrity": "sha512-ueulhshHSGoryfRXaIvTj0BV1yB0KddBGhGoqCxSN9LR1Ks1GKuuCdVhF+2/YOs5fMl6MlTI9On1a4DHDXoTow==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", + "integrity": "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==", "hasInstallScript": true, "optional": true, "dependencies": { - "nan": "2.14.1", - "prebuild-install": "5.3.3" + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.0.1" } }, - "node_modules/keytar/node_modules/nan": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", - "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", - "optional": true - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -29208,29 +29555,16 @@ } }, "node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dev": true, + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.3.tgz", + "integrity": "sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==", "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", - "json5": "^1.0.1" + "json5": "^2.1.2" }, "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/loader-utils/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" + "node": ">=8.9.0" } }, "node_modules/locate-path": { @@ -29612,15 +29946,15 @@ } }, "node_modules/marked": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz", - "integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "dev": true, "bin": { - "marked": "bin/marked" + "marked": "bin/marked.js" }, "engines": { - "node": ">= 10" + "node": ">= 12" } }, "node_modules/martinez-polygon-clipping": { @@ -31326,12 +31660,6 @@ "node": ">=12" } }, - "node_modules/microevent.ts": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz", - "integrity": "sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==", - "dev": true - }, "node_modules/micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -31407,7 +31735,9 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true, "optional": true, + "peer": true, "engines": { "node": ">=8" }, @@ -31822,19 +32152,61 @@ } }, "node_modules/node-abi": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz", - "integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==", + "version": "3.60.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.60.0.tgz", + "integrity": "sha512-zcGgwoXbzw9NczqbGzAWL/ToDYAxv1V8gL1D67ClbdkIfeeDBbY0GelZtC25ayLvVjr2q2cloHeQV1R0QAWqRQ==", "optional": true, "dependencies": { - "semver": "^5.4.1" + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" } }, + "node_modules/node-abi/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "optional": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abi/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" }, + "node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "optional": true + }, "node_modules/node-dir": { "version": "0.1.17", "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", @@ -31849,11 +32221,22 @@ } }, "node_modules/node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, "engines": { "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, "node_modules/node-forge": { @@ -31952,12 +32335,6 @@ "url": "https://github.com/sponsors/antelle" } }, - "node_modules/noop-logger": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", - "integrity": "sha512-6kM8CLXvuW5crTxsAtva2YLrRrDaiTIkIePWs9moLHqbFWT94WpNFjwS/5dfLfECg5i/lkmw3aoqVidxt23TEQ==", - "optional": true - }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -32011,18 +32388,6 @@ "node": ">=8" } }, - "node_modules/npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "optional": true, - "dependencies": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -32045,7 +32410,9 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", + "dev": true, "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -33519,44 +33886,81 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/prebuild-install": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz", - "integrity": "sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", + "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", "optional": true, "dependencies": { - "detect-libc": "^1.0.3", + "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", - "minimist": "^1.2.0", - "mkdirp": "^0.5.1", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", "napi-build-utils": "^1.0.1", - "node-abi": "^2.7.0", - "noop-logger": "^0.1.1", - "npmlog": "^4.0.1", + "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", - "simple-get": "^3.0.3", + "simple-get": "^4.0.0", "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0", - "which-pm-runs": "^1.0.0" + "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" }, "engines": { - "node": ">=6" + "node": ">=10" } }, - "node_modules/prebuild-install/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "node_modules/prebuild-install/node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "optional": true, "dependencies": { - "minimist": "^1.2.6" + "mimic-response": "^3.1.0" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prebuild-install/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prebuild-install/node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" } }, "node_modules/prelude-ls": { @@ -33740,7 +34144,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "devOptional": true + "dev": true }, "node_modules/process-on-spawn": { "version": "1.0.0", @@ -34147,106 +34551,110 @@ } }, "node_modules/react-dev-utils": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz", - "integrity": "sha512-dx0LvIGHcOPtKbeiSUM4jqpBl3TcY7CDjZdfOIcKeznE7BWr9dg0iPG90G5yfVQ+p/rGNMXdbfStvzQZEVEi4A==", + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", + "integrity": "sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==", "dev": true, "dependencies": { - "@babel/code-frame": "7.10.4", - "address": "1.1.2", - "browserslist": "4.14.2", - "chalk": "2.4.2", - "cross-spawn": "7.0.3", - "detect-port-alt": "1.1.6", - "escape-string-regexp": "2.0.0", - "filesize": "6.1.0", - "find-up": "4.1.0", - "fork-ts-checker-webpack-plugin": "4.1.6", - "global-modules": "2.0.0", - "globby": "11.0.1", - "gzip-size": "5.1.1", - "immer": "8.0.1", - "is-root": "2.1.0", - "loader-utils": "2.0.0", - "open": "^7.0.2", - "pkg-up": "3.1.0", - "prompts": "2.4.0", - "react-error-overlay": "^6.0.9", - "recursive-readdir": "2.2.2", - "shell-quote": "1.7.2", - "strip-ansi": "6.0.0", - "text-table": "0.2.0" + "@babel/code-frame": "^7.16.0", + "address": "^1.1.2", + "browserslist": "^4.18.1", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "detect-port-alt": "^1.1.6", + "escape-string-regexp": "^4.0.0", + "filesize": "^8.0.6", + "find-up": "^5.0.0", + "fork-ts-checker-webpack-plugin": "^6.5.0", + "global-modules": "^2.0.0", + "globby": "^11.0.4", + "gzip-size": "^6.0.0", + "immer": "^9.0.7", + "is-root": "^2.1.0", + "loader-utils": "^3.2.0", + "open": "^8.4.0", + "pkg-up": "^3.1.0", + "prompts": "^2.4.2", + "react-error-overlay": "^6.0.11", + "recursive-readdir": "^2.2.2", + "shell-quote": "^1.7.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/react-dev-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/react-dev-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" - } - }, - "node_modules/react-dev-utils/node_modules/@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/react-dev-utils/node_modules/browserslist": { - "version": "4.14.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.2.tgz", - "integrity": "sha512-HI4lPveGKUR0x2StIz+2FXfDk9SfVMrxn6PLh1JeGUwcuoDkdKZebWiyLRJ68iIPDpMI4JLVDf7S7XzslgWOhw==", - "dev": true, - "dependencies": { - "caniuse-lite": "^1.0.30001125", - "electron-to-chromium": "^1.3.564", - "escalade": "^3.0.2", - "node-releases": "^1.1.61" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" }, "funding": { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/react-dev-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/react-dev-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/react-dev-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/react-dev-utils/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "dependencies": { - "locate-path": "^5.0.0", + "locate-path": "^6.0.0", "path-exists": "^4.0.0" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/react-dev-utils/node_modules/globby": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", - "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - }, "engines": { "node": ">=10" }, @@ -34254,48 +34662,94 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/react-dev-utils/node_modules/loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "node_modules/react-dev-utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, "engines": { - "node": ">=8.9.0" + "node": ">=8" + } + }, + "node_modules/react-dev-utils/node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/react-dev-utils/node_modules/loader-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", + "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "dev": true, + "engines": { + "node": ">= 12.13.0" } }, "node_modules/react-dev-utils/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "dependencies": { - "p-locate": "^4.1.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/react-dev-utils/node_modules/node-releases": { - "version": "1.1.77", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.77.tgz", - "integrity": "sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ==", - "dev": true - }, - "node_modules/react-dev-utils/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/react-dev-utils/node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", "dev": true, "dependencies": { - "p-limit": "^2.2.0" + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dev-utils/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dev-utils/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/react-dev-utils/node_modules/path-exists": { @@ -34307,35 +34761,13 @@ "node": ">=8" } }, - "node_modules/react-dev-utils/node_modules/prompts": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz", - "integrity": "sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ==", + "node_modules/react-dev-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/react-dev-utils/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/react-dev-utils/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" + "has-flag": "^4.0.0" }, "engines": { "node": ">=8" @@ -34877,7 +35309,7 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "devOptional": true, + "dev": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -34892,7 +35324,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "devOptional": true + "dev": true }, "node_modules/readdirp": { "version": "3.6.0", @@ -35674,6 +36106,11 @@ "rimraf": "bin.js" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" + }, "node_modules/rst-selector-parser": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", @@ -36409,10 +36846,9 @@ } }, "node_modules/shell-quote": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", - "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", - "dev": true + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==" }, "node_modules/shellwords": { "version": "0.1.1", @@ -36420,9 +36856,9 @@ "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==" }, "node_modules/shiki": { - "version": "0.9.15", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.9.15.tgz", - "integrity": "sha512-/Y0z9IzhJ8nD9nbceORCqu6NgT9X6I8Fk8c3SICHI5NbZRLdZYFaB233gwct9sU0vvSypyaL/qaKvzyQGJBZSw==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.10.1.tgz", + "integrity": "sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==", "dev": true, "dependencies": { "jsonc-parser": "^3.0.0", @@ -36482,7 +36918,9 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "decompress-response": "^4.2.0", "once": "^1.3.1", @@ -37051,6 +37489,15 @@ "node": ">=0.10.0" } }, + "node_modules/stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -37083,7 +37530,9 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -37097,7 +37546,9 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -37106,7 +37557,9 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "ansi-regex": "^2.0.0" }, @@ -37254,6 +37707,32 @@ "node": ">= 0.12.0" } }, + "node_modules/style-loader/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/style-loader/node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/style-loader/node_modules/schema-utils": { "version": "0.4.7", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", @@ -37786,6 +38265,11 @@ "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" }, + "node_modules/tinykeys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tinykeys/-/tinykeys-2.1.0.tgz", + "integrity": "sha512-/MESnqBD1xItZJn5oGQ4OsNORQgJfPP96XSGoyu4eLpwpL0ifO0SYR5OD76u0YMhMXsqkb0UqvI9+yXTh4xv8Q==" + }, "node_modules/tinyqueue": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-1.2.3.tgz", @@ -37885,6 +38369,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "devOptional": true, "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", @@ -37899,6 +38384,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "devOptional": true, "engines": { "node": ">= 4.0.0" } @@ -38297,19 +38783,16 @@ } }, "node_modules/typedoc": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.21.5.tgz", - "integrity": "sha512-uRDRmYheE5Iju9Zz0X50pTASTpBorIHFt02F5Y8Dt4eBt55h3mwk1CBSY2+EfwBxY16N4Xm7f8KXhnfFZ0AmBw==", + "version": "0.22.15", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.15.tgz", + "integrity": "sha512-CMd1lrqQbFvbx6S9G6fL4HKp3GoIuhujJReWqlIvSb2T26vGai+8Os3Mde7Pn832pXYemd9BMuuYWhFpL5st0Q==", "dev": true, "dependencies": { - "glob": "^7.1.7", - "handlebars": "^4.7.7", + "glob": "^7.2.0", "lunr": "^2.3.9", - "marked": "^2.1.1", - "minimatch": "^3.0.0", - "progress": "^2.0.3", - "shiki": "^0.9.3", - "typedoc-default-themes": "^0.12.10" + "marked": "^4.0.12", + "minimatch": "^5.0.1", + "shiki": "^0.10.1" }, "bin": { "typedoc": "bin/typedoc" @@ -38318,16 +38801,28 @@ "node": ">= 12.10.0" }, "peerDependencies": { - "typescript": "4.0.x || 4.1.x || 4.2.x || 4.3.x" + "typescript": "4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x || 4.5.x || 4.6.x" } }, - "node_modules/typedoc-default-themes": { - "version": "0.12.10", - "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.12.10.tgz", - "integrity": "sha512-fIS001cAYHkyQPidWXmHuhs8usjP5XVJjWB8oZGqkTowZaz3v7g3KDZeeqE82FBrmkAnIBOY3jgy7lnPnqATbA==", + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": ">= 8" + "node": ">=10" } }, "node_modules/typescript": { @@ -38437,9 +38932,9 @@ } }, "node_modules/underscore": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", - "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", + "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==" }, "node_modules/unherit": { "version": "1.1.3", @@ -39264,21 +39759,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/webpack-bundle-analyzer/node_modules/gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "dev": true, - "dependencies": { - "duplexer": "^0.1.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/webpack-cli": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", @@ -39355,9 +39835,9 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "dev": true, "dependencies": { "colorette": "^2.0.10", @@ -39431,9 +39911,9 @@ } }, "node_modules/webpack-dev-server": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", - "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", "dev": true, "dependencies": { "@types/bonjour": "^3.5.9", @@ -39464,7 +39944,7 @@ "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", + "webpack-dev-middleware": "^5.3.4", "ws": "^8.13.0" }, "bin": { @@ -39784,15 +40264,6 @@ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" }, - "node_modules/which-pm-runs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", - "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", - "optional": true, - "engines": { - "node": ">=4" - } - }, "node_modules/which-typed-array": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", @@ -39815,7 +40286,9 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } @@ -39948,21 +40421,6 @@ "node": ">=0.10.0" } }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true - }, - "node_modules/worker-rpc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.1.1.tgz", - "integrity": "sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg==", - "dev": true, - "dependencies": { - "microevent.ts": "~0.1.1" - } - }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -40108,26 +40566,6 @@ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" }, - "node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xml2js/node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "engines": { - "node": ">=4.0" - } - }, "node_modules/xmlbuilder": { "version": "8.2.2", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", @@ -40143,14 +40581,6 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "devOptional": true }, - "node_modules/xmldom": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.6.0.tgz", - "integrity": "sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg==", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/xpath.js": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/xpath.js/-/xpath.js-1.1.0.tgz", diff --git a/package.json b/package.json index a7bfec4b8..3a2f8b742 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "@azure/arm-cosmosdb": "9.1.0", "@azure/cosmos": "4.0.1-beta.2", "@azure/cosmos-language-service": "0.0.5", - "@azure/identity": "1.2.1", - "@azure/ms-rest-nodeauth": "3.0.7", + "@azure/identity": "1.5.2", + "@azure/ms-rest-nodeauth": "3.1.1", "@azure/msal-browser": "2.14.2", "@babel/plugin-proposal-class-properties": "7.12.1", "@babel/plugin-proposal-decorators": "7.12.12", @@ -46,6 +46,7 @@ "@types/lodash": "4.14.171", "@types/mkdirp": "1.0.1", "@types/node-fetch": "2.5.7", + "@xmldom/xmldom": "0.7.13", "applicationinsights": "1.8.0", "bootstrap": "3.4.1", "canvas": "file:./canvas", @@ -54,7 +55,7 @@ "copy-webpack-plugin": "11.0.0", "crossroads": "0.12.2", "css-element-queries": "1.1.1", - "d3": "6.1.1", + "d3": "7.8.5", "datatables.net-colreorder-dt": "1.7.0", "datatables.net-dt": "1.13.8", "date-fns": "1.29.0", @@ -69,12 +70,14 @@ "i18next-browser-languagedetector": "6.0.1", "i18next-http-backend": "1.0.23", "iframe-resizer-react": "1.1.0", + "immer": "9.0.6", "immutable": "4.0.0-rc.12", "is-ci": "2.0.0", "jquery": "3.7.1", "jquery-typeahead": "2.11.1", "jquery-ui-dist": "1.13.2", "knockout": "3.5.1", + "loader-utils": "2.0.3", "mkdirp": "1.0.4", "monaco-editor": "0.44.0", "ms": "2.1.3", @@ -98,10 +101,12 @@ "reflect-metadata": "0.1.13", "rx-jupyter": "5.5.12", "sanitize-html": "2.3.3", + "shell-quote": "1.7.3", "styled-components": "5.0.1", "swr": "0.4.0", "terser-webpack-plugin": "5.3.9", - "underscore": "1.9.1", + "tinykeys": "2.1.0", + "underscore": "1.12.1", "utility-types": "3.10.0", "zustand": "3.5.0" }, @@ -170,25 +175,25 @@ "less-vars-loader": "1.1.0", "mini-css-extract-plugin": "2.1.0", "monaco-editor-webpack-plugin": "7.1.0", - "node-fetch": "2.6.1", + "node-fetch": "2.6.7", "playwright": "1.13.0", "prettier": "3.0.3", "process": "0.11.10", "querystring-es3": "0.2.1", "raw-loader": "0.5.1", - "react-dev-utils": "11.0.4", + "react-dev-utils": "12.0.1", "rimraf": "3.0.0", "sinon": "3.2.1", "style-loader": "0.23.0", "ts-loader": "9.2.4", - "typedoc": "0.21.5", + "typedoc": "0.22.15", "typescript": "4.3.5", "url-loader": "4.1.1", "wait-on": "4.0.2", "webpack": "5.88.2", "webpack-bundle-analyzer": "4.9.1", "webpack-cli": "5.1.4", - "webpack-dev-server": "4.15.1" + "webpack-dev-server": "4.15.2" }, "scripts": { "postinstall": "patch-package", diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts index 3a00cb10e..69bd5ed49 100644 --- a/src/Common/Constants.ts +++ b/src/Common/Constants.ts @@ -124,7 +124,36 @@ export enum MongoBackendEndpointType { remote, } -// TODO: 435619 Add default endpoints per cloud and use regional only when available +export class BackendApi { + public static readonly GenerateToken: string = "GenerateToken"; + public static readonly PortalSettings: string = "PortalSettings"; +} + +export class PortalBackendEndpoints { + public static readonly Development: string = "https://localhost:7235"; + public static readonly Mpac: string = "https://cdb-ms-mpac-pbe.cosmos.azure.com"; + public static readonly Prod: string = "https://cdb-ms-prod-pbe.cosmos.azure.com"; + public static readonly Fairfax: string = "https://cdb-ff-prod-pbe.cosmos.azure.us"; + public static readonly Mooncake: string = "https://cdb-mc-prod-pbe.cosmos.azure.cn"; +} + +export class MongoProxyEndpoints { + public static readonly Local: string = "https://localhost:7238"; + public static readonly Mpac: string = "https://cdb-ms-mpac-mp.cosmos.azure.com"; + public static readonly Prod: string = "https://cdb-ms-prod-mp.cosmos.azure.com"; + public static readonly Fairfax: string = "https://cdb-ff-prod-mp.cosmos.azure.us"; + public static readonly Mooncake: string = "https://cdb-mc-prod-mp.cosmos.azure.cn"; +} + +export class CassandraProxyEndpoints { + public static readonly Development: string = "https://localhost:7240"; + public static readonly Mpac: string = "https://cdb-ms-mpac-cp.cosmos.azure.com"; + public static readonly Prod: string = "https://cdb-ms-prod-cp.cosmos.azure.com"; + public static readonly Fairfax: string = "https://cdb-ff-prod-cp.cosmos.azure.us"; + public static readonly Mooncake: string = "https://cdb-mc-prod-cp.cosmos.azure.cn"; +} + +//TODO: Remove this when new backend is migrated over export class CassandraBackend { public static readonly createOrDeleteApi: string = "api/cassandra/createordelete"; public static readonly guestCreateOrDeleteApi: string = "api/guest/cassandra/createordelete"; @@ -136,6 +165,17 @@ export class CassandraBackend { public static readonly guestSchemaApi: string = "api/guest/cassandra/schema"; } +export class CassandraProxyAPIs { + public static readonly createOrDeleteApi: string = "api/cassandra/createordelete"; + public static readonly connectionStringCreateOrDeleteApi: string = "api/connectionstring/cassandra/createordelete"; + public static readonly queryApi: string = "api/cassandra"; + public static readonly connectionStringQueryApi: string = "api/connectionstring/cassandra"; + public static readonly keysApi: string = "api/cassandra/keys"; + public static readonly connectionStringKeysApi: string = "api/connectionstring/cassandra/keys"; + public static readonly schemaApi: string = "api/cassandra/schema"; + public static readonly connectionStringSchemaApi: string = "api/connectionstring/cassandra/schema"; +} + export class Queries { public static CustomPageOption: string = "custom"; public static UnlimitedPageOption: string = "unlimited"; @@ -435,22 +475,6 @@ export class JunoEndpoints { public static readonly Stage = "https://tools-staging.cosmos.azure.com"; } -export class MongoProxyEndpoints { - public static readonly Development: string = "https://localhost:7238"; - public static readonly Mpac: string = "https://cdb-ms-mpac-mp.cosmos.azure.com"; - public static readonly Prod: string = "https://cdb-ms-prod-mp.cosmos.azure.com"; - public static readonly Fairfax: string = "https://cdb-ff-prod-mp.cosmos.azure.us"; - public static readonly Mooncake: string = "https://cdb-mc-prod-mp.cosmos.azure.cn"; -} - -export class CassandraProxyEndpoints { - public static readonly Development: string = "https://localhost:7240"; - public static readonly Mpac: string = "https://cdb-ms-mpac-cp.cosmos.azure.com"; - public static readonly Prod: string = "https://cdb-ms-prod-cp.cosmos.azure.com"; - public static readonly Fairfax: string = "https://cdb-ff-prod-cp.cosmos.azure.us"; - public static readonly Mooncake: string = "https://cdb-mc-prod-cp.cosmos.azure.cn"; -} - export class PriorityLevel { public static readonly High = "high"; public static readonly Low = "low"; diff --git a/src/Common/CosmosClient.ts b/src/Common/CosmosClient.ts index f35efbd00..f75c47454 100644 --- a/src/Common/CosmosClient.ts +++ b/src/Common/CosmosClient.ts @@ -1,7 +1,6 @@ import * as Cosmos from "@azure/cosmos"; -import { sendCachedDataMessage } from "Common/MessageHandler"; import { getAuthorizationTokenUsingResourceTokens } from "Common/getAuthorizationTokenUsingResourceTokens"; -import { AuthorizationToken, MessageTypes } from "Contracts/MessageTypes"; +import { AuthorizationToken } from "Contracts/MessageTypes"; import { checkDatabaseResourceTokensValidity } from "Platform/Fabric/FabricUtil"; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import { AuthType } from "../AuthType"; @@ -51,15 +50,23 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => { case Cosmos.ResourceType.offer: case Cosmos.ResourceType.user: case Cosmos.ResourceType.permission: - // User master tokens - const authorizationToken = await sendCachedDataMessage( - MessageTypes.GetAuthorizationToken, - [requestInfo], - userContext.fabricContext.connectionId, - ); - console.log("Response from Fabric: ", authorizationToken); - headers[HttpHeaders.msDate] = authorizationToken.XDate; - return decodeURIComponent(authorizationToken.PrimaryReadWriteToken); + // For now, these operations aren't used, so fetching the authorization token is commented out. + // This provider must return a real token to pass validation by the client, so we return the cached resource token + // (which is a valid token, but won't work for these operations). + const resourceTokens2 = userContext.fabricContext.databaseConnectionInfo.resourceTokens; + return getAuthorizationTokenUsingResourceTokens(resourceTokens2, requestInfo.path, requestInfo.resourceId); + + /* ************** TODO: Uncomment this code if we need to support these operations ************** + // User master tokens + const authorizationToken = await sendCachedDataMessage( + MessageTypes.GetAuthorizationToken, + [requestInfo], + userContext.fabricContext.connectionId, + ); + console.log("Response from Fabric: ", authorizationToken); + headers[HttpHeaders.msDate] = authorizationToken.XDate; + return decodeURIComponent(authorizationToken.PrimaryReadWriteToken); + ***********************************************************************************************/ } } diff --git a/src/Common/MongoProxyClient.ts b/src/Common/MongoProxyClient.ts index 357ce1f50..907b0305e 100644 --- a/src/Common/MongoProxyClient.ts +++ b/src/Common/MongoProxyClient.ts @@ -67,7 +67,7 @@ export function queryDocuments( query: string, continuationToken?: string, ): Promise { - if (!useMongoProxyEndpoint("resourcelist")) { + if (!useMongoProxyEndpoint("resourcelist") || !useMongoProxyEndpoint("queryDocuments")) { return queryDocuments_ToBeDeprecated(databaseId, collection, isResourceList, query, continuationToken); } @@ -106,7 +106,7 @@ export function queryDocuments( headers[CosmosSDKConstants.HttpHeaders.Continuation] = continuationToken; } - const path = isResourceList ? "/resourcelist" : ""; + const path = isResourceList ? "/resourcelist" : "/queryDocuments"; return window .fetch(`${endpoint}${path}`, { @@ -672,6 +672,27 @@ export function getEndpoint(endpoint: string): string { return url; } +export function useMongoProxyEndpoint(api: string): boolean { + const activeMongoProxyEndpoints: string[] = [ + MongoProxyEndpoints.Local, + MongoProxyEndpoints.Mpac, + MongoProxyEndpoints.Prod, + ]; + let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled"; + if ( + configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Local && + userContext.databaseAccount.properties.ipRules?.length > 0 + ) { + canAccessMongoProxy = canAccessMongoProxy && configContext.MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED; + } + + return ( + canAccessMongoProxy && + configContext.NEW_MONGO_APIS?.includes(api) && + activeMongoProxyEndpoints.includes(configContext.MONGO_PROXY_ENDPOINT) + ); +} + // TODO: This function throws most of the time except on Forbidden which is a bit strange // It causes problems for TypeScript understanding the types async function errorHandling(response: Response, action: string, params: unknown): Promise { @@ -688,16 +709,3 @@ async function errorHandling(response: Response, action: string, params: unknown 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}`; } - -function useMongoProxyEndpoint(api: string): boolean { - let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled"; - if (userContext.databaseAccount.properties.ipRules?.length > 0) { - canAccessMongoProxy = canAccessMongoProxy && configContext.MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED; - } - - return ( - canAccessMongoProxy && - configContext.NEW_MONGO_APIS?.includes(api) && - [MongoProxyEndpoints.Development, MongoProxyEndpoints.Mpac].includes(configContext.MONGO_PROXY_ENDPOINT) - ); -} diff --git a/src/Common/dataAccess/dataTransfers.ts b/src/Common/dataAccess/dataTransfers.ts index 138257c15..e639f9965 100644 --- a/src/Common/dataAccess/dataTransfers.ts +++ b/src/Common/dataAccess/dataTransfers.ts @@ -122,14 +122,21 @@ const pollDataTransferJobOperation = async ( updateDataTransferJob(body); - if (status === "Cancelled" || status === "Failed" || status === "Faulted") { + if (status === "Cancelled") { + removeFromPolling(jobName); + clearMessage && clearMessage(); + const cancelMessage = `Data transfer job ${jobName} cancelled`; + NotificationConsoleUtils.logConsoleError(cancelMessage); + throw new AbortError(cancelMessage); + } + if (status === "Failed" || status === "Faulted") { removeFromPolling(jobName); const errorMessage = body?.properties?.error ? JSON.stringify(body?.properties?.error) : "Operation could not be completed"; const error = new Error(errorMessage); clearMessage && clearMessage(); - NotificationConsoleUtils.logConsoleError(`Data transfer job ${jobName} Failed`); + NotificationConsoleUtils.logConsoleError(`Data transfer job ${jobName} failed: ${errorMessage}`); throw new AbortError(error); } if (status === "Completed") { diff --git a/src/ConfigContext.ts b/src/ConfigContext.ts index 3422b200f..061f25286 100644 --- a/src/ConfigContext.ts +++ b/src/ConfigContext.ts @@ -1,4 +1,10 @@ -import { CassandraProxyEndpoints, JunoEndpoints, MongoProxyEndpoints } from "Common/Constants"; +import { + BackendApi, + CassandraProxyEndpoints, + JunoEndpoints, + MongoProxyEndpoints, + PortalBackendEndpoints, +} from "Common/Constants"; import { allowedAadEndpoints, allowedArcadiaEndpoints, @@ -39,11 +45,15 @@ export interface ConfigContext { ARCADIA_ENDPOINT: string; ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string; BACKEND_ENDPOINT?: string; + PORTAL_BACKEND_ENDPOINT?: string; + NEW_BACKEND_APIS?: BackendApi[]; MONGO_BACKEND_ENDPOINT?: string; MONGO_PROXY_ENDPOINT?: string; MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED?: boolean; NEW_MONGO_APIS?: string[]; CASSANDRA_PROXY_ENDPOINT?: string; + CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: boolean; + NEW_CASSANDRA_APIS?: string[]; PROXY_PATH?: string; JUNO_ENDPOINT: string; GITHUB_CLIENT_ID: string; @@ -73,6 +83,7 @@ let configContext: Readonly = { `^https:\\/\\/.*\\.analysis-df\\.net$`, `^https:\\/\\/.*\\.analysis-df\\.windows\\.net$`, `^https:\\/\\/.*\\.azure-test\\.net$`, + `^https:\\/\\/cosmos-explorer-preview\\.azurewebsites\\.net`, ], // Webpack injects this at build time gitSha: process.env.GIT_SHA, hostedExplorerURL: "https://cosmos.azure.com/", @@ -88,17 +99,22 @@ let configContext: Readonly = { GITHUB_TEST_ENV_CLIENT_ID: "b63fc8cbf87fd3c6e2eb", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1777772 JUNO_ENDPOINT: JunoEndpoints.Prod, BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com", + PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod, MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod, NEW_MONGO_APIS: [ - // "resourcelist", - // "createDocument", - // "readDocument", - // "updateDocument", - // "deleteDocument", - // "createCollectionWithProxy", + "resourcelist", + "queryDocuments", + "createDocument", + "readDocument", + "updateDocument", + "deleteDocument", + "createCollectionWithProxy", + "legacyMongoShell", ], MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED: false, CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod, + NEW_CASSANDRA_APIS: ["postQuery", "createOrDelete", "getKeys", "getSchema"], + CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: false, isTerminalEnabled: false, isPhoenixEnabled: false, }; diff --git a/src/Contracts/DataModels.ts b/src/Contracts/DataModels.ts index ac7b9499e..3c8a5175d 100644 --- a/src/Contracts/DataModels.ts +++ b/src/Contracts/DataModels.ts @@ -159,6 +159,7 @@ export interface Collection extends Resource { geospatialConfig?: GeospatialConfig; schema?: ISchema; requestSchema?: () => void; + computedProperties?: ComputedProperties; } export interface CollectionsWithPagination { @@ -197,6 +198,13 @@ export interface IndexingPolicy { spatialIndexes?: any; } +export interface ComputedProperty { + name: string; + query: string; +} + +export type ComputedProperties = ComputedProperty[]; + export interface PartitionKey { paths: string[]; kind: "Hash" | "Range" | "MultiHash"; diff --git a/src/Contracts/FabricMessagesContract.ts b/src/Contracts/FabricMessagesContract.ts index d0b36f1c6..b381fd845 100644 --- a/src/Contracts/FabricMessagesContract.ts +++ b/src/Contracts/FabricMessagesContract.ts @@ -53,6 +53,7 @@ export type FabricMessageV2 = id: string; message: { connectionId: string; + isVisible: boolean; }; } | { @@ -72,7 +73,7 @@ export type FabricMessageV2 = }; } | { - type: "setToolbarStatus"; + type: "explorerVisible"; message: { visible: boolean; }; diff --git a/src/Contracts/MessageTypes.ts b/src/Contracts/MessageTypes.ts index 1cbd86bb8..a19b69e5f 100644 --- a/src/Contracts/MessageTypes.ts +++ b/src/Contracts/MessageTypes.ts @@ -47,6 +47,7 @@ export enum MessageTypes { GetAllResourceTokens, // Data Explorer -> Fabric Ready, // Data Explorer -> Fabric OpenCESCVAFeedbackBlade, + ActivateTab, } export interface AuthorizationToken { diff --git a/src/Contracts/ViewModels.ts b/src/Contracts/ViewModels.ts index bbf5455bb..66fd54ec4 100644 --- a/src/Contracts/ViewModels.ts +++ b/src/Contracts/ViewModels.ts @@ -135,6 +135,7 @@ export interface Collection extends CollectionBase { changeFeedPolicy: ko.Observable; geospatialConfig: ko.Observable; documentIds: ko.ObservableArray; + computedProperties: ko.Observable; cassandraKeys: CassandraTableKeys; cassandraSchema: CassandraTableKey[]; @@ -386,6 +387,7 @@ export interface DataExplorerInputsFrame { dnsSuffix?: string; serverId?: string; extensionEndpoint?: string; + portalBackendEndpoint?: string; mongoProxyEndpoint?: string; cassandraProxyEndpoint?: string; subscriptionType?: SubscriptionType; diff --git a/src/Explorer/Controls/CommandButton/CommandButtonComponent.tsx b/src/Explorer/Controls/CommandButton/CommandButtonComponent.tsx index 1e5cfc171..6337f947e 100644 --- a/src/Explorer/Controls/CommandButton/CommandButtonComponent.tsx +++ b/src/Explorer/Controls/CommandButton/CommandButtonComponent.tsx @@ -1,6 +1,7 @@ /** * React component for Command button component. */ +import { KeyboardAction } from "KeyboardShortcuts"; import * as React from "react"; import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png"; import { KeyCodes } from "../../../Common/Constants"; @@ -30,7 +31,7 @@ export interface CommandButtonComponentProps { /** * Click handler for command button click */ - onCommandClick: (e: React.SyntheticEvent) => void; + onCommandClick: (e: React.SyntheticEvent | KeyboardEvent) => void; /** * Label for the button @@ -107,10 +108,17 @@ export interface CommandButtonComponentProps { * Vertical bar to divide buttons */ isDivider?: boolean; + /** * Aria-label for the button */ 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 { diff --git a/src/Explorer/Controls/Editor/EditorReact.tsx b/src/Explorer/Controls/Editor/EditorReact.tsx index 956253a05..47268c856 100644 --- a/src/Explorer/Controls/Editor/EditorReact.tsx +++ b/src/Explorer/Controls/Editor/EditorReact.tsx @@ -46,9 +46,25 @@ export class EditorReact extends React.Component { + // Hooking the model's onDidChangeContent event because of some event ordering issues. + // If a single user input causes BOTH the editor content to change AND the cursor selection to change (which is likely), + // then there are some inconsistencies as to which event fires first. + // But the editor.onDidChangeModelContent event seems to always fire before the cursor selection event. + // (This is NOT true for the model's onDidChangeContent event, which sometimes fires after the cursor selection event.) + // If the cursor selection event fires first, then the calling component may re-render the component with old content, so we want to ensure the model content changed event always fires first. + this.editor.onDidChangeModelContent(() => { const queryEditorModel = this.editor.getModel(); this.props.onContentChanged(queryEditorModel.getValue()); }); diff --git a/src/Explorer/Controls/Settings/SettingsComponent.tsx b/src/Explorer/Controls/Settings/SettingsComponent.tsx index 18a0e1e9e..574d75603 100644 --- a/src/Explorer/Controls/Settings/SettingsComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsComponent.tsx @@ -1,4 +1,8 @@ import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "@fluentui/react"; +import { + ComputedPropertiesComponent, + ComputedPropertiesComponentProps, +} from "Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent"; import { useDatabases } from "Explorer/useDatabases"; import { isRunningOnPublicCloud } from "Utils/CloudUtils"; import * as React from "react"; @@ -108,6 +112,11 @@ export interface SettingsComponentState { indexesToAdd: AddMongoIndexProps[]; indexTransformationProgress: number; + computedPropertiesContent: DataModels.ComputedProperties; + computedPropertiesContentBaseline: DataModels.ComputedProperties; + shouldDiscardComputedProperties: boolean; + isComputedPropertiesDirty: boolean; + conflictResolutionPolicyMode: DataModels.ConflictResolutionMode; conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode; conflictResolutionPolicyPath: string; @@ -132,6 +141,7 @@ export class SettingsComponent extends React.Component this.setState({ isMongoIndexingPolicyDiscardable }); + private onComputedPropertiesContentChange = (newComputedProperties: DataModels.ComputedProperties): void => + this.setState({ computedPropertiesContent: newComputedProperties }); + + private resetShouldDiscardComputedProperties = (): void => this.setState({ shouldDiscardComputedProperties: false }); + + private logComputedPropertiesSuccessMessage = (): void => { + if (this.props.settingsTab.onLoadStartKey) { + traceSuccess( + Action.Tab, + { + databaseName: this.collection.databaseId, + collectionName: this.collection.id(), + + dataExplorerArea: Constants.Areas.Tab, + tabTitle: this.props.settingsTab.tabTitle(), + }, + this.props.settingsTab.onLoadStartKey, + ); + this.props.settingsTab.onLoadStartKey = undefined; + } + }; + + private onComputedPropertiesDirtyChange = (isComputedPropertiesDirty: boolean): void => + this.setState({ isComputedPropertiesDirty: isComputedPropertiesDirty }); + private calculateTotalThroughputUsed = (): void => { this.totalThroughputUsed = 0; (useDatabases.getState().databases || []).forEach(async (database) => { @@ -643,7 +689,6 @@ export class SettingsComponent extends React.Component => { const newCollection: DataModels.Collection = { ...this.collection.rawDataModel }; - if (this.state.isSubSettingsSaveable || this.state.isIndexingPolicyDirty || this.state.isConflictResolutionDirty) { + if ( + this.state.isSubSettingsSaveable || + this.state.isIndexingPolicyDirty || + this.state.isConflictResolutionDirty || + this.state.isComputedPropertiesDirty + ) { let defaultTtl: number; switch (this.state.timeToLive) { case TtlType.On: @@ -832,6 +890,10 @@ export class SettingsComponent extends React.Component, + }); + } + const pivotProps: IPivotProps = { onLinkClick: this.onPivotChange, selectedKey: SettingsV2TabTypes[this.state.selectedTab], diff --git a/src/Explorer/Controls/Settings/SettingsRenderUtils.test.tsx b/src/Explorer/Controls/Settings/SettingsRenderUtils.test.tsx index 2e66a86f9..06a9d4a0c 100644 --- a/src/Explorer/Controls/Settings/SettingsRenderUtils.test.tsx +++ b/src/Explorer/Controls/Settings/SettingsRenderUtils.test.tsx @@ -11,7 +11,6 @@ import { getThroughputApplyLongDelayMessage, getThroughputApplyShortDelayMessage, getToolTipContainer, - indexingPolicynUnsavedWarningMessage, manualToAutoscaleDisclaimerElement, mongoIndexTransformationRefreshingMessage, mongoIndexingPolicyAADError, @@ -39,7 +38,6 @@ class SettingsRenderUtilsTestComponent extends React.Component { {manualToAutoscaleDisclaimerElement} {ttlWarning} - {indexingPolicynUnsavedWarningMessage} {updateThroughputDelayedApplyWarningMessage} {getThroughputApplyDelayedMessage(false, 1000, "RU/s", "sampleDb", "sampleCollection", 2000)} diff --git a/src/Explorer/Controls/Settings/SettingsRenderUtils.tsx b/src/Explorer/Controls/Settings/SettingsRenderUtils.tsx index fefd2f6e5..1fe4536f0 100644 --- a/src/Explorer/Controls/Settings/SettingsRenderUtils.tsx +++ b/src/Explorer/Controls/Settings/SettingsRenderUtils.tsx @@ -61,6 +61,8 @@ export interface PriceBreakdown { currencySign: string; } +export type editorType = "indexPolicy" | "computedProperties"; + export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14, color: "windowtext" } }; export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = { @@ -254,9 +256,10 @@ export const ttlWarning: JSX.Element = ( ); -export const indexingPolicynUnsavedWarningMessage: JSX.Element = ( +export const unsavedEditorWarningMessage = (editor: editorType): JSX.Element => ( - You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes. + You have not saved the latest changes made to your{" "} + {editor === "indexPolicy" ? "indexing policy" : "computed properties"}. Please click save to confirm the changes. ); diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent.test.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent.test.tsx new file mode 100644 index 000000000..811bc17ba --- /dev/null +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent.test.tsx @@ -0,0 +1,56 @@ +import * as DataModels from "Contracts/DataModels"; +import { shallow } from "enzyme"; +import React from "react"; +import { ComputedPropertiesComponent, ComputedPropertiesComponentProps } from "./ComputedPropertiesComponent"; + +describe("ComputedPropertiesComponent", () => { + const initialComputedPropertiesContent: DataModels.ComputedProperties = [ + { + name: "prop1", + query: "query1", + }, + ]; + const baseProps: ComputedPropertiesComponentProps = { + computedPropertiesContent: initialComputedPropertiesContent, + computedPropertiesContentBaseline: initialComputedPropertiesContent, + logComputedPropertiesSuccessMessage: () => { + return; + }, + onComputedPropertiesContentChange: () => { + return; + }, + onComputedPropertiesDirtyChange: () => { + return; + }, + resetShouldDiscardComputedProperties: () => { + return; + }, + shouldDiscardComputedProperties: false, + }; + + it("renders", () => { + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + }); + + it("computed properties are reset", () => { + const wrapper = shallow(); + + const computedPropertiesComponentInstance = wrapper.instance() as ComputedPropertiesComponent; + const resetComputedPropertiesEditorMockFn = jest.fn(); + computedPropertiesComponentInstance.resetComputedPropertiesEditor = resetComputedPropertiesEditorMockFn; + + wrapper.setProps({ shouldDiscardComputedProperties: true }); + wrapper.update(); + expect(resetComputedPropertiesEditorMockFn.mock.calls.length).toEqual(1); + }); + + it("dirty is set", () => { + let computedPropertiesComponent = new ComputedPropertiesComponent(baseProps); + expect(computedPropertiesComponent.IsComponentDirty()).toEqual(false); + + const newProps = { ...baseProps, computedPropertiesContent: undefined as DataModels.ComputedProperties }; + computedPropertiesComponent = new ComputedPropertiesComponent(newProps); + expect(computedPropertiesComponent.IsComponentDirty()).toEqual(true); + }); +}); diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent.tsx new file mode 100644 index 000000000..ad3b12fec --- /dev/null +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent.tsx @@ -0,0 +1,128 @@ +import { FontIcon, Link, MessageBar, MessageBarType, Stack, Text } from "@fluentui/react"; +import * as DataModels from "Contracts/DataModels"; +import { titleAndInputStackProps, unsavedEditorWarningMessage } from "Explorer/Controls/Settings/SettingsRenderUtils"; +import { isDirty } from "Explorer/Controls/Settings/SettingsUtils"; +import { loadMonaco } from "Explorer/LazyMonaco"; +import * as monaco from "monaco-editor"; +import * as React from "react"; + +export interface ComputedPropertiesComponentProps { + computedPropertiesContent: DataModels.ComputedProperties; + computedPropertiesContentBaseline: DataModels.ComputedProperties; + logComputedPropertiesSuccessMessage: () => void; + onComputedPropertiesContentChange: (newComputedProperties: DataModels.ComputedProperties) => void; + onComputedPropertiesDirtyChange: (isComputedPropertiesDirty: boolean) => void; + resetShouldDiscardComputedProperties: () => void; + shouldDiscardComputedProperties: boolean; +} + +interface ComputedPropertiesComponentState { + computedPropertiesContentIsValid: boolean; +} + +export class ComputedPropertiesComponent extends React.Component< + ComputedPropertiesComponentProps, + ComputedPropertiesComponentState +> { + private shouldCheckComponentIsDirty = true; + private computedPropertiesDiv = React.createRef(); + private computedPropertiesEditor: monaco.editor.IStandaloneCodeEditor; + + constructor(props: ComputedPropertiesComponentProps) { + super(props); + this.state = { + computedPropertiesContentIsValid: true, + }; + } + + componentDidUpdate(): void { + if (this.props.shouldDiscardComputedProperties) { + this.resetComputedPropertiesEditor(); + this.props.resetShouldDiscardComputedProperties(); + } + this.onComponentUpdate(); + } + + componentDidMount(): void { + this.resetComputedPropertiesEditor(); + this.onComponentUpdate(); + } + + public resetComputedPropertiesEditor = (): void => { + if (!this.computedPropertiesEditor) { + this.createComputedPropertiesEditor(); + } else { + const indexingPolicyEditorModel = this.computedPropertiesEditor.getModel(); + const value: string = JSON.stringify(this.props.computedPropertiesContent, undefined, 4); + indexingPolicyEditorModel.setValue(value); + } + this.onComponentUpdate(); + }; + + private onComponentUpdate = (): void => { + if (!this.shouldCheckComponentIsDirty) { + this.shouldCheckComponentIsDirty = true; + return; + } + this.props.onComputedPropertiesDirtyChange(this.IsComponentDirty()); + this.shouldCheckComponentIsDirty = false; + }; + + public IsComponentDirty = (): boolean => { + if ( + isDirty(this.props.computedPropertiesContent, this.props.computedPropertiesContentBaseline) && + this.state.computedPropertiesContentIsValid + ) { + return true; + } + + return false; + }; + + private async createComputedPropertiesEditor(): Promise { + const value: string = JSON.stringify(this.props.computedPropertiesContent, undefined, 4); + const monaco = await loadMonaco(); + this.computedPropertiesEditor = monaco.editor.create(this.computedPropertiesDiv.current, { + value: value, + language: "json", + ariaLabel: "Computed properties", + }); + if (this.computedPropertiesEditor) { + const computedPropertiesEditorModel = this.computedPropertiesEditor.getModel(); + computedPropertiesEditorModel.onDidChangeContent(this.onEditorContentChange.bind(this)); + this.props.logComputedPropertiesSuccessMessage(); + } + } + + private onEditorContentChange = (): void => { + const computedPropertiesEditorModel = this.computedPropertiesEditor.getModel(); + try { + const newComputedPropertiesContent = JSON.parse( + computedPropertiesEditorModel.getValue(), + ) as DataModels.ComputedProperties; + this.props.onComputedPropertiesContentChange(newComputedPropertiesContent); + this.setState({ computedPropertiesContentIsValid: true }); + } catch (e) { + this.setState({ computedPropertiesContentIsValid: false }); + } + }; + + public render(): JSX.Element { + return ( + + {isDirty(this.props.computedPropertiesContent, this.props.computedPropertiesContentBaseline) && ( + + {unsavedEditorWarningMessage("computedProperties")} + + )} + + + {"Learn more"} + +   about how to define computed properties and how to use them. + +
+
+ ); + } +} diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx index 1f216b241..4d6ca765f 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx @@ -3,7 +3,7 @@ import * as monaco from "monaco-editor"; import * as React from "react"; import * as DataModels from "../../../../Contracts/DataModels"; import { loadMonaco } from "../../../LazyMonaco"; -import { indexingPolicynUnsavedWarningMessage, titleAndInputStackProps } from "../SettingsRenderUtils"; +import { titleAndInputStackProps, unsavedEditorWarningMessage } from "../SettingsRenderUtils"; import { isDirty, isIndexTransforming } from "../SettingsUtils"; import { IndexingPolicyRefreshComponent } from "./IndexingPolicyRefresh/IndexingPolicyRefreshComponent"; @@ -120,7 +120,7 @@ export class IndexingPolicyComponent extends React.Component< refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress} /> {isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && ( - {indexingPolicynUnsavedWarningMessage} + {unsavedEditorWarningMessage("indexPolicy")} )}
diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.tsx index c3b09286e..a55630532 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.tsx @@ -19,7 +19,6 @@ import { addMongoIndexStackProps, createAndAddMongoIndexStackProps, customDetailsListStyles, - indexingPolicynUnsavedWarningMessage, infoAndToolTipTextStyle, mediumWidthStackStyles, mongoCompoundIndexNotSupportedMessage, @@ -27,15 +26,16 @@ import { onRenderRow, separatorStyles, subComponentStackProps, + unsavedEditorWarningMessage, } from "../../SettingsRenderUtils"; import { AddMongoIndexProps, - getMongoIndexType, - getMongoIndexTypeText, - isIndexTransforming, MongoIndexIdField, MongoIndexTypes, MongoNotificationType, + getMongoIndexType, + getMongoIndexTypeText, + isIndexTransforming, } from "../../SettingsUtils"; import { IndexingPolicyRefreshComponent } from "../IndexingPolicyRefresh/IndexingPolicyRefreshComponent"; import { AddMongoIndexComponent } from "./AddMongoIndexComponent"; @@ -297,7 +297,7 @@ export class MongoIndexingPolicyComponent extends React.Component = ({ da }; const getPercentageComplete = () => { + const jobStatus = portalDataTransferJob?.properties?.status; + const isCompleted = jobStatus === "Completed"; + if (isCompleted) { + return 1; + } const processedCount = portalDataTransferJob?.properties?.processedCount; const totalCount = portalDataTransferJob?.properties?.totalCount; - const jobStatus = portalDataTransferJob?.properties?.status; - const isCancelled = jobStatus === "Cancelled"; - const isCompleted = jobStatus === "Completed"; - if (totalCount <= 0 && !isCompleted) { - return isCancelled ? 0 : null; - } - return isCompleted ? 1 : processedCount / totalCount; + const isJobInProgress = isCurrentJobInProgress(portalDataTransferJob); + return isJobInProgress ? (totalCount > 0 ? processedCount / totalCount : null) : 0; }; return ( diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/__snapshots__/ComputedPropertiesComponent.test.tsx.snap b/src/Explorer/Controls/Settings/SettingsSubComponents/__snapshots__/ComputedPropertiesComponent.test.tsx.snap new file mode 100644 index 000000000..86a9bda74 --- /dev/null +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/__snapshots__/ComputedPropertiesComponent.test.tsx.snap @@ -0,0 +1,36 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ComputedPropertiesComponent renders 1`] = ` + + + + Learn more + + + +   about how to define computed properties and how to use them. + +
+ +`; diff --git a/src/Explorer/Controls/Settings/SettingsUtils.tsx b/src/Explorer/Controls/Settings/SettingsUtils.tsx index 930249d9d..869ae323a 100644 --- a/src/Explorer/Controls/Settings/SettingsUtils.tsx +++ b/src/Explorer/Controls/Settings/SettingsUtils.tsx @@ -4,7 +4,7 @@ import * as ViewModels from "../../../Contracts/ViewModels"; import { MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types"; const zeroValue = 0; -export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy; +export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy | DataModels.ComputedProperties; export const TtlOff = "off"; export const TtlOn = "on"; export const TtlOnNoDefault = "on-nodefault"; @@ -46,6 +46,7 @@ export enum SettingsV2TabTypes { SubSettingsTab, IndexingPolicyTab, PartitionKeyTab, + ComputedPropertiesTab, } export interface IsComponentDirtyResult { @@ -148,7 +149,9 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => { case SettingsV2TabTypes.IndexingPolicyTab: return "Indexing Policy"; case SettingsV2TabTypes.PartitionKeyTab: - return "Partition Keys"; + return "Partition Keys (preview)"; + case SettingsV2TabTypes.ComputedPropertiesTab: + return "Computed Properties (preview)"; default: throw new Error(`Unknown tab ${tab}`); } diff --git a/src/Explorer/Controls/Settings/TestUtils.tsx b/src/Explorer/Controls/Settings/TestUtils.tsx index 41b11ca68..d0c794025 100644 --- a/src/Explorer/Controls/Settings/TestUtils.tsx +++ b/src/Explorer/Controls/Settings/TestUtils.tsx @@ -40,6 +40,12 @@ export const collection = { version: 2, }, partitionKeyProperties: ["partitionKey"], + computedProperties: ko.observable([ + { + name: "queryName", + query: "query", + }, + ]), readSettings: () => { return; }, diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap index 1ca3cddf1..ab7abac11 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap @@ -26,6 +26,7 @@ exports[`SettingsComponent renders 1`] = ` Object { "analyticalStorageTtl": [Function], "changeFeedPolicy": [Function], + "computedProperties": [Function], "conflictResolutionPolicy": [Function], "container": Explorer { "_isInitializingNotebooks": false, @@ -103,6 +104,7 @@ exports[`SettingsComponent renders 1`] = ` Object { "analyticalStorageTtl": [Function], "changeFeedPolicy": [Function], + "computedProperties": [Function], "conflictResolutionPolicy": [Function], "container": Explorer { "_isInitializingNotebooks": false, @@ -205,7 +207,7 @@ exports[`SettingsComponent renders 1`] = ` /> + + +
diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsRenderUtils.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsRenderUtils.test.tsx.snap index 004862ffe..5a71353cd 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsRenderUtils.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsRenderUtils.test.tsx.snap @@ -99,18 +99,6 @@ exports[`SettingsUtils functions render 1`] = ` . - - You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes. - = ({ container }: Props) => { const buttons = useCommandBar((state) => state.contextButtons); const isHidden = useCommandBar((state) => state.isHidden); const backgroundColor = StyleConstants.BaseLight; + const setKeyboardHandlers = useKeyboardActionGroup(KeyboardActionGroup.COMMAND_BAR); if (userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo") { const buttons = @@ -105,6 +107,10 @@ export const CommandBar: React.FC = ({ container }: Props) => { }, }; + const allButtons = staticButtons.concat(contextButtons).concat(controlButtons); + const keyboardHandlers = CommandBarUtil.createKeyboardHandlers(allButtons); + setKeyboardHandlers(keyboardHandlers); + return (
{ }); }); - describe("Enable notebook button", () => { - const enableNotebookBtnLabel = "Enable Notebooks (Preview)"; - const selectedNodeState = useSelectedNode.getState(); - - beforeAll(() => { - mockExplorer = {} as Explorer; - updateUserContext({ - portalEnv: "prod", - databaseAccount: { - properties: { - capabilities: [{ name: "EnableTable" }], - }, - } as DatabaseAccount, - }); - }); - - afterEach(() => { - updateUserContext({ - portalEnv: "prod", - }); - useNotebook.getState().setIsNotebookEnabled(false); - useNotebook.getState().setIsNotebooksEnabledForAccount(false); - }); - - it("Notebooks is already enabled - button should be hidden", () => { - useNotebook.getState().setIsNotebookEnabled(true); - useNotebook.getState().setIsNotebooksEnabledForAccount(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel); - expect(enableNotebookBtn).toBeUndefined(); - }); - - it("Account is running on one of the national clouds - button should be hidden", () => { - updateUserContext({ - portalEnv: "mooncake", - }); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel); - expect(enableNotebookBtn).toBeUndefined(); - }); - - it("Notebooks is not enabled but is available - button should be shown and enabled", () => { - useNotebook.getState().setIsNotebooksEnabledForAccount(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel); - - //TODO: modify once notebooks are available - expect(enableNotebookBtn).toBeUndefined(); - //expect(enableNotebookBtn).toBeDefined(); - //expect(enableNotebookBtn.disabled).toBe(false); - //expect(enableNotebookBtn.tooltipText).toBe(""); - }); - - it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => { - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel); - - //TODO: modify once notebooks are available - expect(enableNotebookBtn).toBeUndefined(); - //expect(enableNotebookBtn).toBeDefined(); - //expect(enableNotebookBtn.disabled).toBe(true); - //expect(enableNotebookBtn.tooltipText).toBe( - // "Notebooks are not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks." - //); - }); - }); - - describe("Open Mongo shell button", () => { - const openMongoShellBtnLabel = "Open Mongo shell"; - const selectedNodeState = useSelectedNode.getState(); - - beforeAll(() => { - mockExplorer = {} as Explorer; - updateUserContext({ - databaseAccount: { - properties: { - capabilities: [{ name: "EnableTable" }], - }, - } as DatabaseAccount, - }); - }); - - afterAll(() => { - updateUserContext({ - apiType: "SQL", - }); - useNotebook.getState().setIsShellEnabled(false); - }); - - beforeEach(() => { - updateUserContext({ - apiType: "Mongo", - }); - useNotebook.getState().setIsShellEnabled(true); - }); - - afterEach(() => { - useNotebook.getState().setIsNotebookEnabled(false); - useNotebook.getState().setIsNotebooksEnabledForAccount(false); - }); - - it("Mongo Api not available - button should be hidden", () => { - updateUserContext({ - apiType: "SQL", - }); - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeUndefined(); - }); - - it("Running on a national cloud - button should be hidden", () => { - updateUserContext({ - portalEnv: "mooncake", - }); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeUndefined(); - }); - - it("Notebooks is not enabled and is unavailable - button should be hidden", () => { - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeUndefined(); - }); - - it("Notebooks is not enabled and is available - button should be hidden", () => { - useNotebook.getState().setIsNotebooksEnabledForAccount(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeUndefined(); - }); - - it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => { - useNotebook.getState().setIsNotebookEnabled(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeDefined(); - - //TODO: modify once notebooks are available - expect(openMongoShellBtn.disabled).toBe(true); - //expect(openMongoShellBtn.disabled).toBe(false); - //expect(openMongoShellBtn.tooltipText).toBe(""); - }); - - it("Notebooks is enabled and is available - button should be shown and enabled", () => { - useNotebook.getState().setIsNotebookEnabled(true); - useNotebook.getState().setIsNotebooksEnabledForAccount(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeDefined(); - - //TODO: modify once notebooks are available - expect(openMongoShellBtn.disabled).toBe(true); - //expect(openMongoShellBtn.disabled).toBe(false); - //expect(openMongoShellBtn.tooltipText).toBe(""); - }); - - it("Notebooks is enabled and is available, terminal is unavailable due to ipRules - button should be hidden", () => { - useNotebook.getState().setIsNotebookEnabled(true); - useNotebook.getState().setIsNotebooksEnabledForAccount(true); - useNotebook.getState().setIsShellEnabled(false); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeUndefined(); - }); - }); - describe("Open Cassandra shell button", () => { const openCassandraShellBtnLabel = "Open Cassandra shell"; const selectedNodeState = useSelectedNode.getState(); @@ -305,42 +128,6 @@ describe("CommandBarComponentButtonFactory tests", () => { const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); expect(openCassandraShellBtn).toBeUndefined(); }); - - it("Notebooks is not enabled and is available - button should be shown and enabled", () => { - useNotebook.getState().setIsNotebooksEnabledForAccount(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); - expect(openCassandraShellBtn).toBeUndefined(); - }); - - it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => { - useNotebook.getState().setIsNotebookEnabled(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); - - expect(openCassandraShellBtn).toBeDefined(); - - //TODO: modify once notebooks are available - expect(openCassandraShellBtn.disabled).toBe(true); - //expect(openCassandraShellBtn.disabled).toBe(false); - //expect(openCassandraShellBtn.tooltipText).toBe(""); - }); - - it("Notebooks is enabled and is available - button should be shown and enabled", () => { - useNotebook.getState().setIsNotebookEnabled(true); - useNotebook.getState().setIsNotebooksEnabledForAccount(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); - expect(openCassandraShellBtn).toBeDefined(); - - //TODO: modify once notebooks are available - expect(openCassandraShellBtn.disabled).toBe(true); - //expect(openCassandraShellBtn.disabled).toBe(false); - //expect(openCassandraShellBtn.tooltipText).toBe(""); - }); }); describe("Open Postgres and vCore Mongo buttons", () => { @@ -368,62 +155,6 @@ describe("CommandBarComponentButtonFactory tests", () => { }); }); - describe("GitHub buttons", () => { - const connectToGitHubBtnLabel = "Connect to GitHub"; - const manageGitHubSettingsBtnLabel = "Manage GitHub settings"; - const selectedNodeState = useSelectedNode.getState(); - - beforeAll(() => { - mockExplorer = {} as Explorer; - updateUserContext({ - databaseAccount: { - properties: { - capabilities: [{ name: "EnableTable" }], - }, - } as DatabaseAccount, - }); - - mockExplorer.notebookManager = new NotebookManager(); - mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined); - }); - - afterEach(() => { - jest.resetAllMocks(); - useNotebook.getState().setIsNotebookEnabled(false); - }); - - it("Notebooks is enabled and GitHubOAuthService is not logged in - connect to github button should be visible", () => { - useNotebook.getState().setIsNotebookEnabled(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel); - expect(connectToGitHubBtn).toBeDefined(); - }); - - it("Notebooks is enabled and GitHubOAuthService is logged in - manage github settings button should be visible", () => { - useNotebook.getState().setIsNotebookEnabled(true); - mockExplorer.notebookManager.gitHubOAuthService.isLoggedIn = jest.fn().mockReturnValue(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const manageGitHubSettingsBtn = buttons.find( - (button) => button.commandButtonLabel === manageGitHubSettingsBtnLabel, - ); - expect(manageGitHubSettingsBtn).toBeDefined(); - }); - - it("Notebooks is not enabled - connect to github and manage github settings buttons should be hidden", () => { - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - - const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel); - expect(connectToGitHubBtn).toBeUndefined(); - - const manageGitHubSettingsBtn = buttons.find( - (button) => button.commandButtonLabel === manageGitHubSettingsBtnLabel, - ); - expect(manageGitHubSettingsBtn).toBeUndefined(); - }); - }); - describe("Resource token", () => { const mockCollection = { id: ko.observable("test") } as CollectionBase; useSelectedNode.getState().setSelectedNode(mockCollection); diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx index ecfd3fdb9..a1aa3e49b 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx @@ -1,3 +1,5 @@ +import { KeyboardAction } from "KeyboardShortcuts"; +import { ReactTabKind, useTabs } from "hooks/useTabs"; import * as React from "react"; import AddCollectionIcon from "../../../../images/AddCollection.svg"; import AddDatabaseIcon from "../../../../images/AddDatabase.svg"; @@ -6,13 +8,10 @@ import AddStoredProcedureIcon from "../../../../images/AddStoredProcedure.svg"; import AddTriggerIcon from "../../../../images/AddTrigger.svg"; import AddUdfIcon from "../../../../images/AddUdf.svg"; import BrowseQueriesIcon from "../../../../images/BrowseQuery.svg"; -import CosmosTerminalIcon from "../../../../images/Cosmos-Terminal.svg"; import FeedbackIcon from "../../../../images/Feedback-Command.svg"; +import HomeIcon from "../../../../images/Home_16.svg"; import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg"; import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg"; -import GitHubIcon from "../../../../images/github.svg"; -import NewNotebookIcon from "../../../../images/notebook/Notebook-new.svg"; -import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-workspace.svg"; import OpenInTabIcon from "../../../../images/open-in-tab.svg"; import SettingsIcon from "../../../../images/settings_15x15.svg"; import SynapseIcon from "../../../../images/synapse-link.svg"; @@ -20,7 +19,6 @@ import { AuthType } from "../../../AuthType"; import * as Constants from "../../../Common/Constants"; import { Platform, configContext } from "../../../ConfigContext"; import * as ViewModels from "../../../Contracts/ViewModels"; -import { JunoClient } from "../../../Juno/JunoClient"; import { userContext } from "../../../UserContext"; import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils"; import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils"; @@ -31,7 +29,6 @@ import { useNotebook } from "../../Notebook/useNotebook"; import { OpenFullScreen } from "../../OpenFullScreen"; import { AddDatabasePanel } from "../../Panes/AddDatabasePanel/AddDatabasePanel"; import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane"; -import { GitHubReposPanel } from "../../Panes/GitHubReposPanel/GitHubReposPanel"; import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane"; import { SettingsPane } from "../../Panes/SettingsPane/SettingsPane"; import { useDatabases } from "../../useDatabases"; @@ -57,7 +54,11 @@ export function createStaticCommandBarButtons( }; if (configContext.platform !== Platform.Fabric) { + const homeBtn = createHomeButton(); + buttons.push(homeBtn); + const newCollectionBtn = createNewCollectionGroup(container); + newCollectionBtn.keyboardAction = KeyboardAction.NEW_COLLECTION; // Just for the root button, not the child version we create below. buttons.push(newCollectionBtn); if (userContext.apiType !== "Tables" && userContext.apiType !== "Cassandra") { const addSynapseLink = createOpenSynapseLinkDialogButton(container); @@ -75,57 +76,6 @@ export function createStaticCommandBarButtons( } } - if (useNotebook.getState().isNotebookEnabled) { - addDivider(); - const notebookButtons: CommandButtonComponentProps[] = []; - - const newNotebookButton = createNewNotebookButton(container); - newNotebookButton.children = [createNewNotebookButton(container), createuploadNotebookButton(container)]; - notebookButtons.push(newNotebookButton); - - if (container.notebookManager?.gitHubOAuthService) { - notebookButtons.push(createManageGitHubAccountButton(container)); - } - if (useNotebook.getState().isPhoenixFeatures && configContext.isTerminalEnabled) { - notebookButtons.push(createOpenTerminalButton(container)); - } - if (useNotebook.getState().isPhoenixNotebooks && selectedNodeState.isConnectedToContainer()) { - notebookButtons.push(createNotebookWorkspaceResetButton(container)); - } - if ( - (userContext.apiType === "Mongo" && - useNotebook.getState().isShellEnabled && - selectedNodeState.isDatabaseNodeOrNoneSelected()) || - userContext.apiType === "Cassandra" - ) { - notebookButtons.push(createDivider()); - if (userContext.apiType === "Cassandra") { - notebookButtons.push(createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.Cassandra)); - } else { - notebookButtons.push(createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.Mongo)); - } - } - - notebookButtons.forEach((btn) => { - if (btn.commandButtonLabel.indexOf("Cassandra") !== -1) { - if (!useNotebook.getState().isPhoenixFeatures) { - applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.cassandraShellTemporarilyDownMsg); - } - } else if (btn.commandButtonLabel.indexOf("Mongo") !== -1) { - if (!useNotebook.getState().isPhoenixFeatures) { - applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.mongoShellTemporarilyDownMsg); - } - } else if (btn.commandButtonLabel.indexOf("Open Terminal") !== -1) { - if (!useNotebook.getState().isPhoenixFeatures) { - applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg); - } - } else if (!useNotebook.getState().isPhoenixNotebooks) { - applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg); - } - buttons.push(btn); - }); - } - if (!selectedNodeState.isDatabaseNodeOrNoneSelected()) { const isQuerySupported = userContext.apiType === "SQL" || userContext.apiType === "Gremlin"; @@ -146,6 +96,7 @@ export function createStaticCommandBarButtons( const newStoredProcedureBtn: CommandButtonComponentProps = { iconSrc: AddStoredProcedureIcon, iconAlt: label, + keyboardAction: KeyboardAction.NEW_SPROC, onCommandClick: () => { const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection(); selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection); @@ -235,7 +186,7 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt buttons.push(fullScreenButton); } - if (configContext.platform !== Platform.Emulator) { + if (configContext.platform === Platform.Portal) { const label = "Feedback"; const feedbackButtonOptions: CommandButtonComponentProps = { iconSrc: FeedbackIcon, @@ -285,6 +236,18 @@ function createNewCollectionGroup(container: Explorer): CommandButtonComponentPr }; } +function createHomeButton(): CommandButtonComponentProps { + const label = "Home"; + return { + iconSrc: HomeIcon, + iconAlt: label, + onCommandClick: () => useTabs.getState().openAndActivateReactTab(ReactTabKind.Home), + commandButtonLabel: label, + hasPopup: false, + ariaLabel: label, + }; +} + function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonComponentProps { if (configContext.platform === Platform.Emulator) { return undefined; @@ -317,6 +280,7 @@ function createNewDatabase(container: Explorer): CommandButtonComponentProps { return { iconSrc: AddDatabaseIcon, iconAlt: label, + keyboardAction: KeyboardAction.NEW_DATABASE, onCommandClick: async () => { const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit; if (throughputCap && throughputCap !== -1) { @@ -337,6 +301,7 @@ function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandB id: "newQueryBtn", iconSrc: AddSqlQueryIcon, iconAlt: label, + keyboardAction: KeyboardAction.NEW_QUERY, onCommandClick: () => { const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection(); selectedCollection && selectedCollection.onNewQueryClick(selectedCollection); @@ -352,6 +317,7 @@ function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandB id: "newQueryBtn", iconSrc: AddSqlQueryIcon, iconAlt: label, + keyboardAction: KeyboardAction.NEW_QUERY, onCommandClick: () => { const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection(); selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection); @@ -377,6 +343,7 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState) const newStoredProcedureBtn: CommandButtonComponentProps = { iconSrc: AddStoredProcedureIcon, iconAlt: label, + keyboardAction: KeyboardAction.NEW_SPROC, onCommandClick: () => { const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection(); selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection); @@ -396,6 +363,7 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState) const newUserDefinedFunctionBtn: CommandButtonComponentProps = { iconSrc: AddUdfIcon, iconAlt: label, + keyboardAction: KeyboardAction.NEW_UDF, onCommandClick: () => { const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection(); selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection); @@ -415,6 +383,7 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState) const newTriggerBtn: CommandButtonComponentProps = { iconSrc: AddTriggerIcon, iconAlt: label, + keyboardAction: KeyboardAction.NEW_TRIGGER, onCommandClick: () => { const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection(); selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection); @@ -432,45 +401,12 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState) return buttons; } -function applyNotebooksTemporarilyDownStyle(buttonProps: CommandButtonComponentProps, tooltip: string): void { - if (!buttonProps.isDivider) { - buttonProps.disabled = true; - buttonProps.tooltipText = tooltip; - } -} - -function createNewNotebookButton(container: Explorer): CommandButtonComponentProps { - const label = "New Notebook"; - return { - id: "newNotebookBtn", - iconSrc: NewNotebookIcon, - iconAlt: label, - onCommandClick: () => container.onNewNotebookClicked(), - commandButtonLabel: label, - hasPopup: false, - disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(), - ariaLabel: label, - }; -} - -function createuploadNotebookButton(container: Explorer): CommandButtonComponentProps { - const label = "Upload to Notebook Server"; - return { - iconSrc: NewNotebookIcon, - iconAlt: label, - onCommandClick: () => container.openUploadFilePanel(), - commandButtonLabel: label, - hasPopup: false, - disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(), - ariaLabel: label, - }; -} - function createOpenQueryButton(container: Explorer): CommandButtonComponentProps { const label = "Open Query"; return { iconSrc: BrowseQueriesIcon, iconAlt: label, + keyboardAction: KeyboardAction.OPEN_QUERY, onCommandClick: () => useSidePanel.getState().openSidePanel("Open Saved Queries", ), commandButtonLabel: label, @@ -485,6 +421,7 @@ function createOpenQueryFromDiskButton(): CommandButtonComponentProps { return { iconSrc: OpenQueryFromDiskIcon, iconAlt: label, + keyboardAction: KeyboardAction.OPEN_QUERY_FROM_DISK, onCommandClick: () => useSidePanel.getState().openSidePanel("Load Query", ), commandButtonLabel: label, ariaLabel: label, @@ -493,19 +430,6 @@ function createOpenQueryFromDiskButton(): CommandButtonComponentProps { }; } -function createOpenTerminalButton(container: Explorer): CommandButtonComponentProps { - const label = "Open Terminal"; - return { - iconSrc: CosmosTerminalIcon, - iconAlt: label, - onCommandClick: () => container.openNotebookTerminal(ViewModels.TerminalKind.Default), - commandButtonLabel: label, - hasPopup: false, - disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(), - ariaLabel: label, - }; -} - function createOpenTerminalButtonByKind( container: Explorer, terminalKind: ViewModels.TerminalKind, @@ -545,45 +469,6 @@ function createOpenTerminalButtonByKind( }; } -function createNotebookWorkspaceResetButton(container: Explorer): CommandButtonComponentProps { - const label = "Reset Workspace"; - return { - iconSrc: ResetWorkspaceIcon, - iconAlt: label, - onCommandClick: () => container.resetNotebookWorkspace(), - commandButtonLabel: label, - hasPopup: false, - disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(), - ariaLabel: label, - }; -} - -function createManageGitHubAccountButton(container: Explorer): CommandButtonComponentProps { - const connectedToGitHub: boolean = container.notebookManager?.gitHubOAuthService.isLoggedIn(); - const label = connectedToGitHub ? "Manage GitHub settings" : "Connect to GitHub"; - const junoClient = new JunoClient(); - return { - iconSrc: GitHubIcon, - iconAlt: label, - onCommandClick: () => { - useSidePanel - .getState() - .openSidePanel( - label, - , - ); - }, - commandButtonLabel: label, - hasPopup: false, - disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(), - ariaLabel: label, - }; -} - function createStaticCommandBarButtonsForResourceToken( container: Explorer, selectedNodeState: SelectedNodeState, diff --git a/src/Explorer/Menus/CommandBar/CommandBarUtil.tsx b/src/Explorer/Menus/CommandBar/CommandBarUtil.tsx index 384bd06cd..fc67ad894 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarUtil.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarUtil.tsx @@ -7,6 +7,7 @@ import { IDropdownStyles, } from "@fluentui/react"; import { useQueryCopilot } from "hooks/useQueryCopilot"; +import { KeyboardHandlerMap } from "KeyboardShortcuts"; import * as React from "react"; import _ from "underscore"; import ChevronDownIcon from "../../../../images/Chevron_down.svg"; @@ -233,3 +234,28 @@ export const createConnectionStatus = (container: Explorer, poolId: PoolIdType, onRender: () => , }; }; + +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; +} diff --git a/src/Explorer/OpenActions/OpenActions.tsx b/src/Explorer/OpenActions/OpenActions.tsx index 876f8e391..f3ef288c8 100644 --- a/src/Explorer/OpenActions/OpenActions.tsx +++ b/src/Explorer/OpenActions/OpenActions.tsx @@ -1,4 +1,5 @@ // TODO convert this file to an action registry in order to have actions and their handlers be more tightly coupled. +import { useDatabases } from "Explorer/useDatabases"; import React from "react"; import { ActionContracts } from "../../Contracts/ExplorerContracts"; import * as ViewModels from "../../Contracts/ViewModels"; @@ -40,97 +41,112 @@ function openCollectionTab( databases: ViewModels.Database[], initialDatabaseIndex = 0, ) { - for (let i = initialDatabaseIndex; i < databases.length; i++) { - const database: ViewModels.Database = databases[i]; - if (!!action.databaseResourceId && database.id() !== action.databaseResourceId) { - continue; - } - - const collectionActionHandler = (collections: ViewModels.Collection[]) => { - if (!action.collectionResourceId && collections.length === 0) { - subscription.dispose(); - openCollectionTab(action, databases, ++i); - return; - } - - for (let j = 0; j < collections.length; j++) { - const collection: ViewModels.Collection = collections[j]; - if (!!action.collectionResourceId && collection.id() !== action.collectionResourceId) { - continue; - } - - // select the collection - collection.expandCollection(); - - if ( - action.tabKind === ActionContracts.TabKind.SQLDocuments || - action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLDocuments] - ) { - collection.onDocumentDBDocumentsClick(); - break; - } - - if ( - action.tabKind === ActionContracts.TabKind.MongoDocuments || - action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.MongoDocuments] - ) { - collection.onMongoDBDocumentsClick(); - break; - } - - if ( - action.tabKind === ActionContracts.TabKind.SchemaAnalyzer || - action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SchemaAnalyzer] - ) { - collection.onSchemaAnalyzerClick(); - break; - } - - if ( - action.tabKind === ActionContracts.TabKind.TableEntities || - action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.TableEntities] - ) { - collection.onTableEntitiesClick(); - break; - } - - if ( - action.tabKind === ActionContracts.TabKind.Graph || - action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.Graph] - ) { - collection.onGraphDocumentsClick(); - break; - } - - if ( - action.tabKind === ActionContracts.TabKind.SQLQuery || - action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLQuery] - ) { - collection.onNewQueryClick( - collection, - undefined, - generateQueryText(action as ActionContracts.OpenQueryTab, collection.partitionKeyProperties), - ); - break; - } - - if ( - action.tabKind === ActionContracts.TabKind.ScaleSettings || - action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.ScaleSettings] - ) { - collection.onSettingsClick(); - break; - } - } - subscription.dispose(); + //if databases are not yet loaded, wait until loaded + if (!databases || databases.length === 0) { + const databaseActionHandler = (databases: ViewModels.Database[]) => { + databasesUnsubscription(); + openCollectionTab(action, databases, 0); + return; }; + const databasesUnsubscription = useDatabases.subscribe(databaseActionHandler, (state) => state.databases); + } else { + for (let i = initialDatabaseIndex; i < databases.length; i++) { + const database: ViewModels.Database = databases[i]; + if (!!action.databaseResourceId && database.id() !== action.databaseResourceId) { + continue; + } - const subscription = database.collections.subscribe((collections) => collectionActionHandler(collections)); - if (database.collections && database.collections() && database.collections().length) { - collectionActionHandler(database.collections()); + //expand database first if not expanded to load the collections + if (!database.isDatabaseExpanded?.()) { + database.expandDatabase?.(); + } + + const collectionActionHandler = (collections: ViewModels.Collection[]) => { + if (!action.collectionResourceId && collections.length === 0) { + subscription.dispose(); + openCollectionTab(action, databases, ++i); + return; + } + + for (let j = 0; j < collections.length; j++) { + const collection: ViewModels.Collection = collections[j]; + if (!!action.collectionResourceId && collection.id() !== action.collectionResourceId) { + continue; + } + + // select the collection + collection.expandCollection(); + + if ( + action.tabKind === ActionContracts.TabKind.SQLDocuments || + action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLDocuments] + ) { + collection.onDocumentDBDocumentsClick(); + break; + } + + if ( + action.tabKind === ActionContracts.TabKind.MongoDocuments || + action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.MongoDocuments] + ) { + collection.onMongoDBDocumentsClick(); + break; + } + + if ( + action.tabKind === ActionContracts.TabKind.SchemaAnalyzer || + action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SchemaAnalyzer] + ) { + collection.onSchemaAnalyzerClick(); + break; + } + + if ( + action.tabKind === ActionContracts.TabKind.TableEntities || + action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.TableEntities] + ) { + collection.onTableEntitiesClick(); + break; + } + + if ( + action.tabKind === ActionContracts.TabKind.Graph || + action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.Graph] + ) { + collection.onGraphDocumentsClick(); + break; + } + + if ( + action.tabKind === ActionContracts.TabKind.SQLQuery || + action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLQuery] + ) { + collection.onNewQueryClick( + collection, + undefined, + generateQueryText(action as ActionContracts.OpenQueryTab, collection.partitionKeyProperties), + ); + break; + } + + if ( + action.tabKind === ActionContracts.TabKind.ScaleSettings || + action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.ScaleSettings] + ) { + collection.onSettingsClick(); + break; + } + } + subscription.dispose(); + }; + + const subscription = database.collections.subscribe((collections) => collectionActionHandler(collections)); + if (database.collections && database.collections() && database.collections().length) { + collectionActionHandler(database.collections()); + } + + break; } - - break; } } diff --git a/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.tsx b/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.tsx index dbef20b12..cd91a6628 100644 --- a/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.tsx +++ b/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.tsx @@ -202,8 +202,8 @@ export const CassandraAddCollectionPane: FunctionComponent = ({ {createNewContainer ? ( + All configurations except for unique keys will be copied from the source container diff --git a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx index 1f0eda4f7..9b2412c4b 100644 --- a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx +++ b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx @@ -630,7 +630,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ Enable sample database This is a sample database and collection with synthetic product data you can use to explore using - NoSQL queries and Copilot. This will appear as another database in the Data Explorer UI, and is + NoSQL queries and Query Advisor. This will appear as another database in the Data Explorer UI, and is created by, and maintained by Microsoft at no cost to you.
@@ -640,7 +640,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ label: { padding: 0 }, }} className="padding" - ariaLabel="Enable sample db for Copilot" + ariaLabel="Enable sample db for Query Advisor" checked={copilotSampleDBEnabled} onChange={handleSampleDatabaseChange} /> diff --git a/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx b/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx index 1f23a60bc..9ad999aa0 100644 --- a/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx +++ b/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx @@ -124,8 +124,8 @@ export const AddTableEntityPanel: FunctionComponent = setIsExecuting(true); const entity: Entities.ITableEntity = entityFromAttributes(entities); - const newEntity: Entities.ITableEntity = await tableDataClient.createDocument(queryTablesTab.collection, entity); try { + const newEntity: Entities.ITableEntity = await tableDataClient.createDocument(queryTablesTab.collection, entity); await tableEntityListViewModel.addEntityToCache(newEntity); if (!tryInsertNewHeaders(tableEntityListViewModel, newEntity)) { tableEntityListViewModel.redrawTableThrottled(); diff --git a/src/Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal.tsx b/src/Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal.tsx index d8d68f4cc..c96f8d179 100644 --- a/src/Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal.tsx +++ b/src/Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal.tsx @@ -49,7 +49,7 @@ export const QueryCopilotFeedbackModal = ({ }; return ( - +
@@ -68,9 +68,14 @@ export const QueryCopilotFeedbackModal = ({ rows={3} /> diff --git a/src/Explorer/QueryCopilot/Modal/__snapshots__/QueryCopilotFeedbackModal.test.tsx.snap b/src/Explorer/QueryCopilot/Modal/__snapshots__/QueryCopilotFeedbackModal.test.tsx.snap index dc7f4c96f..356b86977 100644 --- a/src/Explorer/QueryCopilot/Modal/__snapshots__/QueryCopilotFeedbackModal.test.tsx.snap +++ b/src/Explorer/QueryCopilot/Modal/__snapshots__/QueryCopilotFeedbackModal.test.tsx.snap @@ -3,6 +3,14 @@ exports[`Query Copilot Feedback Modal snapshot test shoud render and match snapshot 1`] = ` = ({ const generateSQLQueryResponse: GenerateSQLQueryResponse = await response?.json(); if (response.ok) { if (generateSQLQueryResponse?.sql !== "N/A") { - let query = `-- **Prompt:** ${userPrompt}\r\n`; - if (generateSQLQueryResponse.explanation) { - query += `-- **Explanation of query:** ${generateSQLQueryResponse.explanation}\r\n`; - } - query += generateSQLQueryResponse.sql; - setQuery(query); + const queryExplanation = `-- **Explanation of query:** ${ + generateSQLQueryResponse.explanation ? generateSQLQueryResponse.explanation : "N/A" + }\r\n`; + const currentGeneratedQuery = queryExplanation + generateSQLQueryResponse.sql; + const lastQuery = generatedQuery && query ? `${query}\r\n` : ""; + setQuery(`${lastQuery}${currentGeneratedQuery}`); setGeneratedQuery(generateSQLQueryResponse.sql); setGeneratedQueryComments(generateSQLQueryResponse.explanation); setShowFeedbackBar(true); @@ -310,12 +309,388 @@ export const QueryCopilotPromptbar: React.FC = ({ return ( - - Copilot - Copilot + + + + { + inputEdited.current = true; + setShowSamplePrompts(true); + }} + onKeyDown={(e) => { + if (e.key === "Enter" && userPrompt) { + inputEdited.current = true; + startGenerateQueryProcess(); + } + }} + style={{ lineHeight: 30 }} + styles={{ + root: { width: "100%" }, + suffix: { + background: "none", + padding: 0, + }, + fieldGroup: { + borderRadius: 4, + borderColor: "#D1D1D1", + "::after": { + border: "inherit", + borderWidth: 2, + borderBottomColor: "#464FEB", + borderRadius: 4, + }, + }, + }} + disabled={isGeneratingQuery} + autoComplete="off" + placeholder="Ask a question in natural language and we’ll generate the query for you." + aria-labelledby="copilot-textfield-label" + onRenderSuffix={() => { + return ( + startGenerateQueryProcess()} + aria-label="Send" + /> + ); + }} + /> + {showPromptTeachingBubble && copilotTeachingBubbleVisible && ( + toggleCopilotTeachingBubbleVisible(false)} + hasSmallHeadline={true} + headline="Write a prompt" + > + Write a prompt here and Query Advisor will generate the query for you. You can also choose from our{" "} + { + setShowSamplePrompts(true); + toggleCopilotTeachingBubbleVisible(false); + }} + style={{ color: "white", fontWeight: 600 }} + > + sample prompts + {" "} + or write your own query + + )} + {showSamplePrompts && ( + setShowSamplePrompts(false)} + directionalHintFixed={true} + directionalHint={DirectionalHint.bottomLeftEdge} + alignTargetEdge={true} + gapSpace={4} + > + + {filteredHistories?.length > 0 && ( + + + Recent + + {filteredHistories.map((history, i) => ( + { + setUserPrompt(history); + setShowSamplePrompts(false); + inputEdited.current = true; + }} + onRenderIcon={() => } + styles={promptStyles} + > + {history} + + ))} + + )} + {filteredSuggestedPrompts?.length > 0 && ( + + + Suggested Prompts + + {filteredSuggestedPrompts.map((prompt) => ( + { + setUserPrompt(prompt.text); + setShowSamplePrompts(false); + inputEdited.current = true; + }} + onRenderIcon={() => } + styles={promptStyles} + > + {prompt.text} + + ))} + + )} + {(filteredHistories?.length > 0 || filteredSuggestedPrompts?.length > 0) && ( + + + + Learn about{" "} + + writing effective prompts + + + + )} + + + )} + + {!isGeneratingQuery && ( + + {!showFeedbackBar && ( + + AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.{" "} + + Read preview terms + + {showErrorMessageBar && ( + + {errorMessage ? errorMessage : "We ran into an error and were not able to execute query."} + + )} + {showInvalidQueryMessageBar && ( + + We were unable to generate a query based upon the prompt provided. Please modify the prompt and + try again. For examples of how to write a good prompt, please read + + this article. + {" "} + Our content guidelines can be found + + here. + + + )} + + )} + {showFeedbackBar && ( + + {userContext.feedbackPolicies?.policyAllowFeedback && ( + + Provide feedback + {showCallout && !hideFeedbackModalForLikedQueries && ( + { + setShowCallout(false); + SubmitFeedback({ + params: { + generatedQuery: generatedQuery, + likeQuery: likeQuery, + description: "", + userPrompt: userPrompt, + }, + explorer, + databaseId, + containerId, + mode: isSampleCopilotActive ? "Sample" : "User", + }); + }} + directionalHint={DirectionalHint.topCenter} + > + + Thank you. Need to give{" "} + { + setShowCallout(false); + openFeedbackModal(generatedQuery, true, userPrompt); + }} + > + more feedback? + + + + )} + { + setShowCallout(!likeQuery); + setLikeQuery(!likeQuery); + if (likeQuery === true) { + document.getElementById("likeStatus").innerHTML = "Unpressed"; + } + if (likeQuery === false) { + document.getElementById("likeStatus").innerHTML = "Liked"; + } + if (dislikeQuery) { + setDislikeQuery(!dislikeQuery); + } + }} + /> + { + let toggleStatusValue = "Unpressed"; + if (!dislikeQuery) { + openFeedbackModal(generatedQuery, false, userPrompt); + setLikeQuery(false); + toggleStatusValue = "Disliked"; + } + setDislikeQuery(!dislikeQuery); + setShowCallout(false); + document.getElementById("likeStatus").innerHTML = toggleStatusValue; + }} + /> + + + + )} + + Copy code + + { + setShowDeletePopup(true); + }} + iconProps={{ iconName: "Delete" }} + style={{ fontSize: 12, transition: "background-color 0.3s ease", height: "100%" }} + styles={{ + root: { + backgroundColor: "inherit", + }, + }} + > + Clear editor + + + )} + + )} + {isGeneratingQuery && ( + + )} + { @@ -323,307 +698,10 @@ export const QueryCopilotPromptbar: React.FC = ({ clearFeedback(); resetMessageStates(); }} - styles={{ - root: { - marginLeft: "auto !important", - }, - }} ariaLabel="Close" title="Close copilot" /> - - { - inputEdited.current = true; - setShowSamplePrompts(true); - }} - onKeyDown={(e) => { - if (e.key === "Enter" && userPrompt) { - inputEdited.current = true; - startGenerateQueryProcess(); - } - }} - style={{ lineHeight: 30 }} - styles={{ root: { width: "95%" }, fieldGroup: { borderRadius: 6 } }} - disabled={isGeneratingQuery} - autoComplete="off" - placeholder="Ask a question in natural language and we’ll generate the query for you." - aria-labelledby="copilot-textfield-label" - /> - {showPromptTeachingBubble && copilotTeachingBubbleVisible && ( - toggleCopilotTeachingBubbleVisible(false)} - hasSmallHeadline={true} - headline="Write a prompt" - > - Write a prompt here and Copilot will generate the query for you. You can also choose from our{" "} - { - setShowSamplePrompts(true); - toggleCopilotTeachingBubbleVisible(false); - }} - style={{ color: "white", fontWeight: 600 }} - > - sample prompts - {" "} - or write your own query - - )} - startGenerateQueryProcess()} - aria-label="Send" - /> -
- {isGeneratingQuery && } -
- {showSamplePrompts && ( - setShowSamplePrompts(false)} - directionalHintFixed={true} - directionalHint={DirectionalHint.bottomLeftEdge} - alignTargetEdge={true} - gapSpace={4} - > - - {filteredHistories?.length > 0 && ( - - - Recent - - {filteredHistories.map((history, i) => ( - { - setUserPrompt(history); - setShowSamplePrompts(false); - inputEdited.current = true; - }} - onRenderIcon={() => } - styles={promptStyles} - > - {history} - - ))} - - )} - {filteredSuggestedPrompts?.length > 0 && ( - - - Suggested Prompts - - {filteredSuggestedPrompts.map((prompt) => ( - { - setUserPrompt(prompt.text); - setShowSamplePrompts(false); - inputEdited.current = true; - }} - onRenderIcon={() => } - styles={promptStyles} - > - {prompt.text} - - ))} - - )} - {(filteredHistories?.length > 0 || filteredSuggestedPrompts?.length > 0) && ( - - - - Learn about{" "} - - writing effective prompts - - - - )} - - - )} -
- - - - AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.{" "} - - Read preview terms - - {showErrorMessageBar && ( - - {errorMessage ? errorMessage : "We ran into an error and were not able to execute query."} - - )} - {showInvalidQueryMessageBar && ( - - We were unable to generate a query based upon the prompt provided. Please modify the prompt and try again. - For examples of how to write a good prompt, please read - - this article. - {" "} - Our content guidelines can be found - - here. - - - )} - - - - {showFeedbackBar && ( - - {userContext.feedbackPolicies?.policyAllowFeedback && ( - - Provide feedback on the query generated - {showCallout && !hideFeedbackModalForLikedQueries && ( - { - setShowCallout(false); - SubmitFeedback({ - params: { - generatedQuery: generatedQuery, - likeQuery: likeQuery, - description: "", - userPrompt: userPrompt, - }, - explorer, - databaseId, - containerId, - mode: isSampleCopilotActive ? "Sample" : "User", - }); - }} - directionalHint={DirectionalHint.topCenter} - > - - Thank you. Need to give{" "} - { - setShowCallout(false); - openFeedbackModal(generatedQuery, true, userPrompt); - }} - > - more feedback? - - - - )} - { - setShowCallout(!likeQuery); - setLikeQuery(!likeQuery); - if (likeQuery === true) { - document.getElementById("likeStatus").innerHTML = "Unpressed"; - } - if (likeQuery === false) { - document.getElementById("likeStatus").innerHTML = "Liked"; - } - if (dislikeQuery) { - setDislikeQuery(!dislikeQuery); - } - }} - /> - { - let toggleStatusValue = "Unpressed"; - if (!dislikeQuery) { - openFeedbackModal(generatedQuery, false, userPrompt); - setLikeQuery(false); - toggleStatusValue = "Disliked"; - } - setDislikeQuery(!dislikeQuery); - setShowCallout(false); - document.getElementById("likeStatus").innerHTML = toggleStatusValue; - }} - /> - - - - )} - - Copy query - - { - setShowDeletePopup(true); - }} - iconProps={{ iconName: "Delete" }} - style={{ margin: "0 10px", backgroundColor: "#FFF8F0", transition: "background-color 0.3s ease" }} - > - Delete query - - - )} {isSamplePromptsOpen && } {query !== "" && query.trim().length !== 0 && ( = ({ explorer }: Query const toggleCopilotButton = { iconSrc: QueryCommandIcon, - iconAlt: "Copilot", + iconAlt: "Query Advisor", onCommandClick: () => { toggleCopilot(true); }, - commandButtonLabel: "Copilot", - ariaLabel: "Copilot", + commandButtonLabel: "Query Advisor", + ariaLabel: "Query Advisor", hasPopup: false, disabled: copilotActive, }; diff --git a/src/Explorer/QueryCopilot/Shared/QueryCopilotClient.ts b/src/Explorer/QueryCopilot/Shared/QueryCopilotClient.ts index 6d2cafdc5..59101cbb3 100644 --- a/src/Explorer/QueryCopilot/Shared/QueryCopilotClient.ts +++ b/src/Explorer/QueryCopilot/Shared/QueryCopilotClient.ts @@ -1,6 +1,7 @@ import { FeedOptions } from "@azure/cosmos"; import { Areas, + BackendApi, ConnectionStatusType, ContainerStatusType, HttpStatusCodes, @@ -30,6 +31,7 @@ import { Action } from "Shared/Telemetry/TelemetryConstants"; import { traceFailure, traceStart, traceSuccess } from "Shared/Telemetry/TelemetryProcessor"; import { userContext } from "UserContext"; import { getAuthorizationHeader } from "Utils/AuthorizationUtils"; +import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils"; import { queryPagesUntilContentPresent } from "Utils/QueryUtils"; import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot"; import { useTabs } from "hooks/useTabs"; @@ -80,7 +82,11 @@ export const isCopilotFeatureRegistered = async (subscriptionId: string): Promis }; export const getCopilotEnabled = async (): Promise => { - const url = `${configContext.BACKEND_ENDPOINT}/api/portalsettings/querycopilot`; + const backendEndpoint: string = useNewPortalBackendEndpoint(BackendApi.PortalSettings) + ? configContext.PORTAL_BACKEND_ENDPOINT + : configContext.BACKEND_ENDPOINT; + + const url = `${backendEndpoint}/api/portalsettings/querycopilot`; const authorizationHeader: AuthorizationTokenHeaderMetadata = getAuthorizationHeader(); const headers = { [authorizationHeader.header]: authorizationHeader.token }; diff --git a/src/Explorer/SplashScreen/SplashScreen.tsx b/src/Explorer/SplashScreen/SplashScreen.tsx index aa7e7dcd9..f4ebb9cd0 100644 --- a/src/Explorer/SplashScreen/SplashScreen.tsx +++ b/src/Explorer/SplashScreen/SplashScreen.tsx @@ -151,9 +151,9 @@ export class SplashScreen extends React.Component { {useQueryCopilot.getState().copilotEnabled && ( { const copilotVersion = userContext.features.copilotVersion; diff --git a/src/Explorer/Tables/TableDataClient.ts b/src/Explorer/Tables/TableDataClient.ts index 830a2544f..a9d7c96a6 100644 --- a/src/Explorer/Tables/TableDataClient.ts +++ b/src/Explorer/Tables/TableDataClient.ts @@ -3,6 +3,7 @@ import * as ko from "knockout"; import Q from "q"; import { AuthType } from "../../AuthType"; import * as Constants from "../../Common/Constants"; +import { CassandraProxyAPIs, CassandraProxyEndpoints } from "../../Common/Constants"; import { handleError } from "../../Common/ErrorHandlingUtils"; import * as HeadersUtility from "../../Common/HeadersUtility"; import { createDocument } from "../../Common/dataAccess/createDocument"; @@ -171,8 +172,9 @@ export class CassandraAPIDataClient extends TableDataClient { deferred.resolve(entity); }, (error) => { - handleError(error, "AddRowCassandra", `Error while adding new row to table ${collection.id()}`); - deferred.reject(error); + const errorText = error.responseJSON?.message ?? JSON.stringify(error); + handleError(errorText, "AddRowCassandra", `Error while adding new row to table ${collection.id()}`); + deferred.reject(errorText); }, ) .finally(clearInProgressMessage); @@ -261,6 +263,57 @@ export class CassandraAPIDataClient extends TableDataClient { query: string, shouldNotify?: boolean, paginationToken?: string, + ): Promise { + if (!this.useCassandraProxyEndpoint("postQuery")) { + return this.queryDocuments_ToBeDeprecated(collection, query, shouldNotify, paginationToken); + } + const clearMessage = + shouldNotify && NotificationConsoleUtils.logConsoleProgress(`Querying rows for table ${collection.id()}`); + try { + const { authType, databaseAccount } = userContext; + + const apiEndpoint: string = + authType === AuthType.EncryptedToken + ? CassandraProxyAPIs.connectionStringQueryApi + : CassandraProxyAPIs.queryApi; + + const data: any = await $.ajax(`${configContext.CASSANDRA_PROXY_ENDPOINT}/${apiEndpoint}`, { + type: "POST", + contentType: Constants.ContentType.applicationJson, + data: JSON.stringify({ + accountName: databaseAccount?.name, + cassandraEndpoint: this.trimCassandraEndpoint(databaseAccount?.properties.cassandraEndpoint), + resourceId: databaseAccount?.id, + keyspaceId: collection.databaseId, + tableId: collection.id(), + query, + paginationToken, + }), + beforeSend: this.setAuthorizationHeader as any, + cache: false, + }); + shouldNotify && + NotificationConsoleUtils.logConsoleInfo( + `Successfully fetched ${data.result.length} rows for table ${collection.id()}`, + ); + return { + Results: data.result, + ContinuationToken: data.paginationToken, + }; + } catch (error) { + shouldNotify && + handleError(error, "QueryDocumentsCassandra", `Failed to query rows for table ${collection.id()}`); + throw error; + } finally { + clearMessage?.(); + } + } + + public async queryDocuments_ToBeDeprecated( + collection: ViewModels.Collection, + query: string, + shouldNotify?: boolean, + paginationToken?: string, ): Promise { const clearMessage = shouldNotify && NotificationConsoleUtils.logConsoleProgress(`Querying rows for table ${collection.id()}`); @@ -294,7 +347,11 @@ export class CassandraAPIDataClient extends TableDataClient { }; } catch (error) { shouldNotify && - handleError(error, "QueryDocumentsCassandra", `Failed to query rows for table ${collection.id()}`); + handleError( + error, + "QueryDocuments_ToBeDeprecated_Cassandra", + `Failed to query rows for table ${collection.id()}`, + ); throw error; } finally { clearMessage?.(); @@ -350,12 +407,13 @@ export class CassandraAPIDataClient extends TableDataClient { deferred.resolve(); }, (error) => { + const errorText = error.responseJSON?.message ?? JSON.stringify(error); handleError( - error, + errorText, "CreateKeyspaceCassandra", `Error while creating a keyspace with query ${createKeyspaceQuery}`, ); - deferred.reject(error); + deferred.reject(errorText); }, ) .finally(clearInProgressMessage); @@ -388,8 +446,13 @@ export class CassandraAPIDataClient extends TableDataClient { deferred.resolve(); }, (error) => { - handleError(error, "CreateTableCassandra", `Error while creating a table with query ${createTableQuery}`); - deferred.reject(error); + const errorText = error.responseJSON?.message ?? JSON.stringify(error); + handleError( + errorText, + "CreateTableCassandra", + `Error while creating a table with query ${createTableQuery}`, + ); + deferred.reject(errorText); }, ) .finally(clearInProgressMessage); @@ -402,6 +465,51 @@ export class CassandraAPIDataClient extends TableDataClient { } public getTableKeys(collection: ViewModels.Collection): Q.Promise { + if (!this.useCassandraProxyEndpoint("getKeys")) { + return this.getTableKeys_ToBeDeprecated(collection); + } + + if (!!collection.cassandraKeys) { + return Q.resolve(collection.cassandraKeys); + } + const clearInProgressMessage = logConsoleProgress(`Fetching keys for table ${collection.id()}`); + const { authType, databaseAccount } = userContext; + const apiEndpoint: string = + authType === AuthType.EncryptedToken ? CassandraProxyAPIs.connectionStringKeysApi : CassandraProxyAPIs.keysApi; + + let endpoint = `${configContext.CASSANDRA_PROXY_ENDPOINT}/${apiEndpoint}`; + const deferred = Q.defer(); + + $.ajax(endpoint, { + type: "POST", + contentType: Constants.ContentType.applicationJson, + data: JSON.stringify({ + accountName: databaseAccount?.name, + cassandraEndpoint: this.trimCassandraEndpoint(databaseAccount?.properties.cassandraEndpoint), + resourceId: databaseAccount?.id, + keyspaceId: collection.databaseId, + tableId: collection.id(), + }), + beforeSend: this.setAuthorizationHeader as any, + cache: false, + }) + .then( + (data: CassandraTableKeys) => { + collection.cassandraKeys = data; + logConsoleInfo(`Successfully fetched keys for table ${collection.id()}`); + deferred.resolve(data); + }, + (error: any) => { + const errorText = error.responseJSON?.message ?? JSON.stringify(error); + handleError(errorText, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`); + deferred.reject(errorText); + }, + ) + .done(clearInProgressMessage); + return deferred.promise; + } + + public getTableKeys_ToBeDeprecated(collection: ViewModels.Collection): Q.Promise { if (!!collection.cassandraKeys) { return Q.resolve(collection.cassandraKeys); } @@ -433,8 +541,9 @@ export class CassandraAPIDataClient extends TableDataClient { deferred.resolve(data); }, (error: any) => { - handleError(error, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`); - deferred.reject(error); + const errorText = error.responseJSON?.message ?? JSON.stringify(error); + handleError(errorText, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`); + deferred.reject(errorText); }, ) .done(clearInProgressMessage); @@ -442,6 +551,52 @@ export class CassandraAPIDataClient extends TableDataClient { } public getTableSchema(collection: ViewModels.Collection): Q.Promise { + if (!this.useCassandraProxyEndpoint("getSchema")) { + return this.getTableSchema_ToBeDeprecated(collection); + } + + if (!!collection.cassandraSchema) { + return Q.resolve(collection.cassandraSchema); + } + const clearInProgressMessage = logConsoleProgress(`Fetching schema for table ${collection.id()}`); + const { databaseAccount, authType } = userContext; + const apiEndpoint: string = + authType === AuthType.EncryptedToken + ? CassandraProxyAPIs.connectionStringSchemaApi + : CassandraProxyAPIs.schemaApi; + let endpoint = `${configContext.CASSANDRA_PROXY_ENDPOINT}/${apiEndpoint}`; + const deferred = Q.defer(); + + $.ajax(endpoint, { + type: "POST", + contentType: Constants.ContentType.applicationJson, + data: JSON.stringify({ + accountName: databaseAccount?.name, + cassandraEndpoint: this.trimCassandraEndpoint(databaseAccount?.properties.cassandraEndpoint), + resourceId: databaseAccount?.id, + keyspaceId: collection.databaseId, + tableId: collection.id(), + }), + beforeSend: this.setAuthorizationHeader as any, + cache: false, + }) + .then( + (data: any) => { + collection.cassandraSchema = data.columns; + logConsoleInfo(`Successfully fetched schema for table ${collection.id()}`); + deferred.resolve(data.columns); + }, + (error: any) => { + const errorText = error.responseJSON?.message ?? JSON.stringify(error); + handleError(errorText, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`); + deferred.reject(errorText); + }, + ) + .done(clearInProgressMessage); + return deferred.promise; + } + + public getTableSchema_ToBeDeprecated(collection: ViewModels.Collection): Q.Promise { if (!!collection.cassandraSchema) { return Q.resolve(collection.cassandraSchema); } @@ -473,8 +628,9 @@ export class CassandraAPIDataClient extends TableDataClient { deferred.resolve(data.columns); }, (error: any) => { - handleError(error, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`); - deferred.reject(error); + const errorText = error.responseJSON?.message ?? JSON.stringify(error); + handleError(errorText, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`); + deferred.reject(errorText); }, ) .done(clearInProgressMessage); @@ -482,6 +638,44 @@ export class CassandraAPIDataClient extends TableDataClient { } private createOrDeleteQuery(cassandraEndpoint: string, resourceId: string, query: string): Q.Promise { + if (!this.useCassandraProxyEndpoint("createOrDelete")) { + return this.createOrDeleteQuery_ToBeDeprecated(cassandraEndpoint, resourceId, query); + } + + const deferred = Q.defer(); + const { authType, databaseAccount } = userContext; + const apiEndpoint: string = + authType === AuthType.EncryptedToken + ? CassandraProxyAPIs.connectionStringCreateOrDeleteApi + : CassandraProxyAPIs.createOrDeleteApi; + + $.ajax(`${configContext.CASSANDRA_PROXY_ENDPOINT}/${apiEndpoint}`, { + type: "POST", + contentType: Constants.ContentType.applicationJson, + data: JSON.stringify({ + accountName: databaseAccount?.name, + cassandraEndpoint: this.trimCassandraEndpoint(cassandraEndpoint), + resourceId: resourceId, + query: query, + }), + beforeSend: this.setAuthorizationHeader as any, + cache: false, + }).then( + (data: any) => { + deferred.resolve(); + }, + (reason) => { + deferred.reject(reason); + }, + ); + return deferred.promise; + } + + private createOrDeleteQuery_ToBeDeprecated( + cassandraEndpoint: string, + resourceId: string, + query: string, + ): Q.Promise { const deferred = Q.defer(); const { authType, databaseAccount } = userContext; const apiEndpoint: string = @@ -547,4 +741,25 @@ export class CassandraAPIDataClient extends TableDataClient { private getCassandraPartitionKeyProperty(collection: ViewModels.Collection): string { return collection.cassandraKeys.partitionKeys[0].property; } + + private useCassandraProxyEndpoint(api: string): boolean { + const activeCassandraProxyEndpoints: string[] = [ + CassandraProxyEndpoints.Development, + CassandraProxyEndpoints.Mpac, + CassandraProxyEndpoints.Prod, + ]; + let canAccessCassandraProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled"; + if ( + configContext.CASSANDRA_PROXY_ENDPOINT !== CassandraProxyEndpoints.Development && + userContext.databaseAccount.properties.ipRules?.length > 0 + ) { + canAccessCassandraProxy = canAccessCassandraProxy && configContext.CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED; + } + + return ( + canAccessCassandraProxy && + configContext.NEW_CASSANDRA_APIS?.includes(api) && + activeCassandraProxyEndpoints.includes(configContext.CASSANDRA_PROXY_ENDPOINT) + ); + } } diff --git a/src/Explorer/Tabs/DocumentsTab.ts b/src/Explorer/Tabs/DocumentsTab.ts index 3591378da..c79f7eb4f 100644 --- a/src/Explorer/Tabs/DocumentsTab.ts +++ b/src/Explorer/Tabs/DocumentsTab.ts @@ -1,6 +1,7 @@ import { ItemDefinition, PartitionKey, PartitionKeyDefinition, QueryIterator, Resource } from "@azure/cosmos"; import { Platform, configContext } from "ConfigContext"; import { querySampleDocuments, readSampleDocument } from "Explorer/QueryCopilot/QueryCopilotUtilities"; +import { KeyboardAction } from "KeyboardShortcuts"; import { QueryConstants } from "Shared/Constants"; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import * as ko from "knockout"; @@ -462,7 +463,22 @@ export default class DocumentsTab extends TabsBase { private initializeNewDocument = (): void => { this.selectedDocumentId(null); - const defaultDocument: string = this.renderObjectForEditor({ id: "replace_with_new_document_id" }, null, 4); + const newDocument: any = { + id: "replace_with_new_document_id", + }; + this.partitionKeyProperties.forEach((partitionKeyProperty) => { + let target = newDocument; + const keySegments = partitionKeyProperty.split("."); + const finalSegment = keySegments.pop(); + + // Initialize nested objects as needed + keySegments.forEach((segment) => { + target = target[segment] = target[segment] || {}; + }); + + target[finalSegment] = "replace_with_new_partition_key_value"; + }); + const defaultDocument: string = this.renderObjectForEditor(newDocument, null, 4); this.initialDocumentContent(defaultDocument); this.selectedDocumentContent.setBaseline(defaultDocument); this.editorState(ViewModels.DocumentExplorerState.newDocumentValid); @@ -893,6 +909,7 @@ export default class DocumentsTab extends TabsBase { buttons.push({ iconSrc: NewDocumentIcon, iconAlt: label, + keyboardAction: KeyboardAction.NEW_ITEM, onCommandClick: this.onNewDocumentClick, commandButtonLabel: label, ariaLabel: label, @@ -907,6 +924,7 @@ export default class DocumentsTab extends TabsBase { buttons.push({ iconSrc: SaveIcon, iconAlt: label, + keyboardAction: KeyboardAction.SAVE_ITEM, onCommandClick: this.onSaveNewDocumentClick, commandButtonLabel: label, ariaLabel: label, @@ -921,6 +939,7 @@ export default class DocumentsTab extends TabsBase { buttons.push({ iconSrc: DiscardIcon, iconAlt: label, + keyboardAction: KeyboardAction.CANCEL_OR_DISCARD, onCommandClick: this.onRevertNewDocumentClick, commandButtonLabel: label, ariaLabel: label, @@ -936,6 +955,7 @@ export default class DocumentsTab extends TabsBase { buttons.push({ iconSrc: SaveIcon, iconAlt: label, + keyboardAction: KeyboardAction.SAVE_ITEM, onCommandClick: this.onSaveExistingDocumentClick, commandButtonLabel: label, ariaLabel: label, @@ -950,6 +970,7 @@ export default class DocumentsTab extends TabsBase { buttons.push({ iconSrc: DiscardIcon, iconAlt: label, + keyboardAction: KeyboardAction.CANCEL_OR_DISCARD, onCommandClick: this.onRevertExisitingDocumentClick, commandButtonLabel: label, ariaLabel: label, @@ -965,6 +986,7 @@ export default class DocumentsTab extends TabsBase { buttons.push({ iconSrc: DeleteDocumentIcon, iconAlt: label, + keyboardAction: KeyboardAction.DELETE_ITEM, onCommandClick: this.onDeleteExisitingDocumentClick, commandButtonLabel: label, ariaLabel: label, diff --git a/src/Explorer/Tabs/MongoShellTab/MongoShellTabComponent.tsx b/src/Explorer/Tabs/MongoShellTab/MongoShellTabComponent.tsx index cb47a070d..052c81ce6 100644 --- a/src/Explorer/Tabs/MongoShellTab/MongoShellTabComponent.tsx +++ b/src/Explorer/Tabs/MongoShellTab/MongoShellTabComponent.tsx @@ -1,3 +1,4 @@ +import { useMongoProxyEndpoint } from "Common/MongoProxyClient"; import React, { Component } from "react"; import * as Constants from "../../../Common/Constants"; import { configContext } from "../../../ConfigContext"; @@ -9,7 +10,6 @@ import { isInvalidParentFrameOrigin, isReadyMessage } from "../../../Utils/Messa import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils"; import Explorer from "../../Explorer"; import TabsBase from "../TabsBase"; -import { getMongoShellOrigin } from "./getMongoShellOrigin"; import { getMongoShellUrl } from "./getMongoShellUrl"; //eslint-disable-next-line @@ -50,13 +50,15 @@ export default class MongoShellTabComponent extends Component< IMongoShellTabComponentStates > { private _logTraces: Map; + private _useMongoProxyEndpoint: boolean; constructor(props: IMongoShellTabComponentProps) { super(props); this._logTraces = new Map(); + this._useMongoProxyEndpoint = useMongoProxyEndpoint("legacyMongoShell"); this.state = { - url: getMongoShellUrl(), + url: getMongoShellUrl(this._useMongoProxyEndpoint), }; props.onMongoShellTabAccessor({ @@ -119,9 +121,10 @@ export default class MongoShellTabComponent extends Component< ) + Constants.MongoDBAccounts.defaultPort.toString(); const databaseId = this.props.collection.databaseId; const collectionId = this.props.collection.id(); - const apiEndpoint = configContext.BACKEND_ENDPOINT; + const apiEndpoint = this._useMongoProxyEndpoint + ? configContext.MONGO_PROXY_ENDPOINT + : configContext.BACKEND_ENDPOINT; const encryptedAuthToken: string = userContext.accessToken; - const targetOrigin = getMongoShellOrigin(); shellIframe.contentWindow.postMessage( { @@ -137,7 +140,7 @@ export default class MongoShellTabComponent extends Component< apiEndpoint: apiEndpoint, }, }, - targetOrigin, + window.origin, ); } diff --git a/src/Explorer/Tabs/MongoShellTab/getMongoShellOrigin.test.ts b/src/Explorer/Tabs/MongoShellTab/getMongoShellOrigin.test.ts deleted file mode 100644 index 8f62b2a0c..000000000 --- a/src/Explorer/Tabs/MongoShellTab/getMongoShellOrigin.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { extractFeatures } from "Platform/Hosted/extractFeatures"; -import { configContext } from "../../../ConfigContext"; -import { updateUserContext } from "../../../UserContext"; -import { getMongoShellOrigin } from "./getMongoShellOrigin"; - -describe("getMongoShellOrigin", () => { - (window as { origin: string }).origin = "window_origin"; - - beforeEach(() => { - updateUserContext({ - features: extractFeatures( - new URLSearchParams({ - "feature.enableLegacyMongoShellV1": "false", - "feature.enableLegacyMongoShellV2": "false", - "feature.enableLegacyMongoShellV1Debug": "false", - "feature.enableLegacyMongoShellV2Debug": "false", - "feature.loadLegacyMongoShellFromBE": "false", - }), - ), - }); - }); - - it("should return by default", () => { - expect(getMongoShellOrigin()).toBe(window.origin); - }); - - it("should return window.origin when enableLegacyMongoShellV1", () => { - updateUserContext({ - features: extractFeatures( - new URLSearchParams({ - "feature.enableLegacyMongoShellV1": "true", - }), - ), - }); - - expect(getMongoShellOrigin()).toBe(window.origin); - }); - - it("should return window.origin when enableLegacyMongoShellV2===true", () => { - updateUserContext({ - features: extractFeatures( - new URLSearchParams({ - "feature.enableLegacyMongoShellV2": "true", - }), - ), - }); - - expect(getMongoShellOrigin()).toBe(window.origin); - }); - - it("should return window.origin when enableLegacyMongoShellV1Debug===true", () => { - updateUserContext({ - features: extractFeatures( - new URLSearchParams({ - "feature.enableLegacyMongoShellV1Debug": "true", - }), - ), - }); - - expect(getMongoShellOrigin()).toBe(window.origin); - }); - - it("should return window.origin when enableLegacyMongoShellV2Debug===true", () => { - updateUserContext({ - features: extractFeatures( - new URLSearchParams({ - "feature.enableLegacyMongoShellV2Debug": "true", - }), - ), - }); - - expect(getMongoShellOrigin()).toBe(window.origin); - }); - - it("should return BACKEND_ENDPOINT when loadLegacyMongoShellFromBE===true", () => { - updateUserContext({ - features: extractFeatures( - new URLSearchParams({ - "feature.loadLegacyMongoShellFromBE": "true", - }), - ), - }); - - expect(getMongoShellOrigin()).toBe(configContext.BACKEND_ENDPOINT); - }); -}); diff --git a/src/Explorer/Tabs/MongoShellTab/getMongoShellOrigin.ts b/src/Explorer/Tabs/MongoShellTab/getMongoShellOrigin.ts deleted file mode 100644 index 774a4443c..000000000 --- a/src/Explorer/Tabs/MongoShellTab/getMongoShellOrigin.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { configContext } from "../../../ConfigContext"; -import { userContext } from "../../../UserContext"; - -export function getMongoShellOrigin(): string { - if (userContext.features.loadLegacyMongoShellFromBE === true) { - return configContext.BACKEND_ENDPOINT; - } - - return window.origin; -} diff --git a/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.test.ts b/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.test.ts index 1d75f682d..0c138ff61 100644 --- a/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.test.ts +++ b/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.test.ts @@ -1,9 +1,9 @@ -import { extractFeatures } from "Platform/Hosted/extractFeatures"; -import { Platform, configContext, resetConfigContext, updateConfigContext } from "../../../ConfigContext"; +import { Platform, resetConfigContext, updateConfigContext } from "../../../ConfigContext"; import { updateUserContext, userContext } from "../../../UserContext"; -import { getExtensionEndpoint, getMongoShellUrl } from "./getMongoShellUrl"; +import { getMongoShellUrl } from "./getMongoShellUrl"; const mongoBackendEndpoint = "https://localhost:1234"; +const hostedExplorerURL = "https://cosmos.azure.com/"; describe("getMongoShellUrl", () => { let queryString = ""; @@ -13,6 +13,7 @@ describe("getMongoShellUrl", () => { updateConfigContext({ BACKEND_ENDPOINT: mongoBackendEndpoint, + hostedExplorerURL: hostedExplorerURL, platform: Platform.Hosted, }); @@ -32,175 +33,18 @@ describe("getMongoShellUrl", () => { cassandraEndpoint: "fakeCassandraEndpoint", }, }, - features: extractFeatures( - new URLSearchParams({ - "feature.enableLegacyMongoShellV1": "false", - "feature.enableLegacyMongoShellV2": "false", - "feature.enableLegacyMongoShellV1Debug": "false", - "feature.enableLegacyMongoShellV2Debug": "false", - "feature.loadLegacyMongoShellFromBE": "false", - }), - ), portalEnv: "prod", }); queryString = `resourceId=${userContext.databaseAccount.id}&accountName=${userContext.databaseAccount.name}&mongoEndpoint=${userContext.databaseAccount.properties.documentEndpoint}`; }); - it("should return /mongoshell/indexv2.html by default", () => { - expect(getMongoShellUrl()).toBe(`/mongoshell/indexv2.html?${queryString}`); + it("should return /indexv2.html by default", () => { + expect(getMongoShellUrl().toString()).toContain(`/indexv2.html?${queryString}`); }); - it("should return /mongoshell/indexv2.html when portalEnv==localhost", () => { - updateUserContext({ - 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"); + it("should return /index.html when useMongoProxyEndpoint is true", () => { + const useMongoProxyEndpoint: boolean = true; + expect(getMongoShellUrl(useMongoProxyEndpoint).toString()).toContain(`/index.html?${queryString}`); }); }); diff --git a/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.ts b/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.ts index a029fe440..a3b49b373 100644 --- a/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.ts +++ b/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.ts @@ -1,45 +1,11 @@ -import { configContext, Platform } from "../../../ConfigContext"; import { userContext } from "../../../UserContext"; -export function getMongoShellUrl(): string { +export function getMongoShellUrl(useMongoProxyEndpoint?: boolean): string { const { databaseAccount: account } = userContext; const resourceId = account?.id; const accountName = account?.name; const mongoEndpoint = account?.properties?.mongoEndpoint || account?.properties?.documentEndpoint; const queryString = `resourceId=${resourceId}&accountName=${accountName}&mongoEndpoint=${mongoEndpoint}`; - 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; + return useMongoProxyEndpoint ? `/mongoshell/index.html?${queryString}` : `/mongoshell/indexv2.html?${queryString}`; } diff --git a/src/Explorer/Tabs/QueryTab/QueryResultSection.tsx b/src/Explorer/Tabs/QueryTab/QueryResultSection.tsx index fc93d98a1..30d2ed9c1 100644 --- a/src/Explorer/Tabs/QueryTab/QueryResultSection.tsx +++ b/src/Explorer/Tabs/QueryTab/QueryResultSection.tsx @@ -381,9 +381,13 @@ export const QueryResultSection: React.FC = ({ Error - We have detected you may be using a subquery. Non-correlated subqueries are not currently supported. - - Please see Cosmos sub query documentation for further information + We detected you may be using a subquery. To learn more about subqueries effectively,{" "} + + visit the documentation diff --git a/src/Explorer/Tabs/QueryTab/QueryTab.tsx b/src/Explorer/Tabs/QueryTab/QueryTab.tsx index 47c2ce2ba..f297f91f4 100644 --- a/src/Explorer/Tabs/QueryTab/QueryTab.tsx +++ b/src/Explorer/Tabs/QueryTab/QueryTab.tsx @@ -1,3 +1,5 @@ +import { sendMessage } from "Common/MessageHandler"; +import { MessageTypes } from "Contracts/MessageTypes"; import { CopilotProvider } from "Explorer/QueryCopilot/QueryCopilotContext"; import { userContext } from "UserContext"; import React from "react"; @@ -54,6 +56,11 @@ export class NewQueryTab extends TabsBase { ); } + public onActivate(): void { + this.propagateTabInformation(MessageTypes.ActivateTab); + super.onActivate(); + } + public onTabClick(): void { useTabs.getState().activateTab(this); this.iTabAccessor.onTabClickEvent(); @@ -61,6 +68,7 @@ export class NewQueryTab extends TabsBase { public onCloseTabButtonClick(): void { useTabs.getState().closeTab(this); + this.propagateTabInformation(MessageTypes.CloseTab); if (this.iTabAccessor) { this.iTabAccessor.onCloseClickEvent(true); } @@ -69,4 +77,15 @@ export class NewQueryTab extends TabsBase { public getContainer(): Explorer { return this.props.container; } + + private propagateTabInformation(type: MessageTypes): void { + sendMessage({ + type, + data: { + kind: this.tabKind, + databaseId: this.collection?.databaseId, + collectionId: this.collection?.id?.(), + }, + }); + } } diff --git a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx index ff44a4be3..532ce4662 100644 --- a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx +++ b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx @@ -10,6 +10,7 @@ import { OnExecuteQueryClick, QueryDocumentsPerPage } from "Explorer/QueryCopilo import { QueryCopilotSidebar } from "Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar"; import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection"; import { useSelectedNode } from "Explorer/useSelectedNode"; +import { KeyboardAction } from "KeyboardShortcuts"; import { QueryConstants } from "Shared/Constants"; import { LocalStorageUtility, StorageKey, getRUThreshold, ruThresholdEnabled } from "Shared/StorageUtility"; import { Action } from "Shared/Telemetry/TelemetryConstants"; @@ -21,6 +22,7 @@ import "react-splitter-layout/lib/index.css"; import { format } from "react-string-format"; import QueryCommandIcon from "../../../../images/CopilotCommand.svg"; import LaunchCopilot from "../../../../images/CopilotTabIcon.svg"; +import DownloadQueryIcon from "../../../../images/DownloadQuery.svg"; import CancelQueryIcon from "../../../../images/Entity_cancel.svg"; import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg"; import SaveQueryIcon from "../../../../images/save-cosmos.svg"; @@ -134,7 +136,7 @@ export default class QueryTabComponent extends React.Component { + 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 => { useSidePanel.getState().openSidePanel("Save Query", ); }; @@ -393,6 +409,7 @@ export default class QueryTabComponent extends React.Component OnExecuteQueryClick(this.props.copilotStore) : this.onExecuteQueryClick, @@ -403,14 +420,28 @@ export default class QueryTabComponent extends React.Component { this._toggleCopilot(!this.state.copilotActive); }, - commandButtonLabel: this.state.copilotActive ? "Disable Copilot" : "Enable Copilot", - ariaLabel: this.state.copilotActive ? "Disable Copilot" : "Enable Copilot", + commandButtonLabel: this.state.copilotActive ? "Disable Query Advisor" : "Enable Query Advisor", + ariaLabel: this.state.copilotActive ? "Disable Query Advisor" : "Enable Query Advisor", hasPopup: false, }; buttons.push(toggleCopilotButton); @@ -468,6 +500,7 @@ export default class QueryTabComponent extends React.Component this.queryAbortController.abort(), commandButtonLabel: label, ariaLabel: label, @@ -496,13 +529,16 @@ export default class QueryTabComponent extends React.Component 0) { this.executeQueryButton = { @@ -517,6 +553,8 @@ export default class QueryTabComponent extends React.Component 0; + useCommandBar.getState().setContextButtons(this.getTabsButtons()); } @@ -544,7 +582,7 @@ export default class QueryTabComponent extends React.Component { this.collection.container.openExecuteSprocParamsPanel(this.node); }, diff --git a/src/Explorer/Tabs/Tabs.tsx b/src/Explorer/Tabs/Tabs.tsx index fc152aa92..b33f3d4af 100644 --- a/src/Explorer/Tabs/Tabs.tsx +++ b/src/Explorer/Tabs/Tabs.tsx @@ -6,6 +6,7 @@ import { IpRule } from "Contracts/DataModels"; import { MessageTypes } from "Contracts/ExplorerContracts"; import { CollectionTabKind } from "Contracts/ViewModels"; import Explorer from "Explorer/Explorer"; +import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter"; import { QueryCopilotTab } from "Explorer/QueryCopilot/QueryCopilotTab"; import { SplashScreen } from "Explorer/SplashScreen/SplashScreen"; import { ConnectTab } from "Explorer/Tabs/ConnectTab"; @@ -13,6 +14,7 @@ import { PostgresConnectTab } from "Explorer/Tabs/PostgresConnectTab"; import { QuickstartTab } from "Explorer/Tabs/QuickstartTab"; import { VcoreMongoConnectTab } from "Explorer/Tabs/VCoreMongoConnectTab"; import { VcoreMongoQuickstartTab } from "Explorer/Tabs/VCoreMongoQuickstartTab"; +import { KeyboardAction, KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts"; import { hasRUThresholdBeenConfigured } from "Shared/StorageUtility"; import { userContext } from "UserContext"; import { CassandraProxyOutboundIPs, MongoProxyOutboundIPs, PortalBackendIPs } from "Utils/EndpointUtils"; @@ -41,6 +43,16 @@ export const Tabs = ({ explorer }: TabsProps): JSX.Element => { showMongoAndCassandraProxiesNetworkSettingsWarningState, setShowMongoAndCassandraProxiesNetworkSettingsWarningState, ] = useState(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 (
{networkSettingsWarning && ( @@ -182,11 +194,9 @@ function TabNav({ tab, active, tabKind }: { tab?: Tab; active: boolean; tabKind? )} {useObservable(tab?.tabTitle || getReactTabTitle())} - {tabKind !== ReactTabKind.Home && ( - - - - )} + + +
@@ -299,6 +309,9 @@ const isQueryErrorThrown = (tab?: Tab, tabKind?: ReactTabKind): boolean => { }; const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): JSX.Element => { + // React tabs have no context buttons. + useCommandBar.getState().setContextButtons([]); + // eslint-disable-next-line no-console switch (activeReactTab) { case ReactTabKind.Connect: @@ -326,13 +339,17 @@ const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): J const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => { const ipRules: IpRule[] = userContext.databaseAccount?.properties?.ipRules; - if ((userContext.apiType === "Mongo" || userContext.apiType === "Cassandra") && ipRules?.length) { + if ( + ((userContext.apiType === "Mongo" && configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Local) || + (userContext.apiType === "Cassandra" && + configContext.CASSANDRA_PROXY_ENDPOINT !== CassandraProxyEndpoints.Development)) && + ipRules?.length + ) { const legacyPortalBackendIPs: string[] = PortalBackendIPs[configContext.BACKEND_ENDPOINT]; const ipAddressesFromIPRules: string[] = ipRules.map((ipRule) => ipRule.ipAddressOrRange); - const ipRulesIncludeLegacyPortalBackend: boolean = - ipAddressesFromIPRules.filter((ipAddressFromIPRule) => legacyPortalBackendIPs.includes(ipAddressFromIPRule)) - ?.length === legacyPortalBackendIPs.length; - + const ipRulesIncludeLegacyPortalBackend: boolean = legacyPortalBackendIPs.every((legacyPortalBackendIP: string) => + ipAddressesFromIPRules.includes(legacyPortalBackendIP), + ); if (!ipRulesIncludeLegacyPortalBackend) { return false; } @@ -346,9 +363,9 @@ const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => { ? [...MongoProxyOutboundIPs[MongoProxyEndpoints.Mpac], ...MongoProxyOutboundIPs[MongoProxyEndpoints.Prod]] : MongoProxyOutboundIPs[configContext.MONGO_PROXY_ENDPOINT]; - const ipRulesIncludeMongoProxy: boolean = - ipAddressesFromIPRules.filter((ipAddressFromIPRule) => mongoProxyOutboundIPs.includes(ipAddressFromIPRule)) - ?.length === mongoProxyOutboundIPs.length; + const ipRulesIncludeMongoProxy: boolean = mongoProxyOutboundIPs.every((mongoProxyOutboundIP: string) => + ipAddressesFromIPRules.includes(mongoProxyOutboundIP), + ); if (ipRulesIncludeMongoProxy) { updateConfigContext({ @@ -370,9 +387,15 @@ const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => { ] : CassandraProxyOutboundIPs[configContext.CASSANDRA_PROXY_ENDPOINT]; - const ipRulesIncludeCassandraProxy: boolean = - ipAddressesFromIPRules.filter((ipAddressFromIPRule) => cassandraProxyOutboundIPs.includes(ipAddressFromIPRule)) - ?.length === cassandraProxyOutboundIPs.length; + const ipRulesIncludeCassandraProxy: boolean = cassandraProxyOutboundIPs.every( + (cassandraProxyOutboundIP: string) => ipAddressesFromIPRules.includes(cassandraProxyOutboundIP), + ); + + if (ipRulesIncludeCassandraProxy) { + updateConfigContext({ + CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: true, + }); + } return !ipRulesIncludeCassandraProxy; } diff --git a/src/Explorer/Tabs/TabsBase.ts b/src/Explorer/Tabs/TabsBase.ts index a6f3a45dd..0425eac91 100644 --- a/src/Explorer/Tabs/TabsBase.ts +++ b/src/Explorer/Tabs/TabsBase.ts @@ -40,11 +40,10 @@ export default class TabsBase extends WaitsForTemplateViewModel { this.database = options.database; this.rid = options.rid || (this.collection && this.collection.rid) || ""; this.tabKind = options.tabKind; - this.tabTitle = ko.observable(options.title); + this.tabTitle = ko.observable(this.getTitle(options)); this.tabPath = - ko.observable(options.tabPath ?? "") || - (this.collection && - ko.observable(`${this.collection.databaseId}>${this.collection.id()}>${this.tabTitle()}`)); + this.collection && + ko.observable(`${this.collection.databaseId}>${this.collection.id()}>${options.title}`); this.pendingNotification = ko.observable(undefined); this.onLoadStartKey = options.onLoadStartKey; this.closeTabButton = { @@ -143,6 +142,26 @@ export default class TabsBase extends WaitsForTemplateViewModel { return (this.collection && this.collection.container) || (this.database && this.database.container); } + public getTitle(options: ViewModels.TabOptions): string { + const coll = this.collection?.id(); + const db = this.database?.id(); + if (coll) { + if (coll.length > 8) { + return coll.slice(0, 5) + "…" + options.title; + } else { + return coll + "." + options.title; + } + } else if (db) { + if (db.length > 8) { + return db.slice(0, 5) + "…" + options.title; + } else { + return db + "." + options.title; + } + } else { + return options.title; + } + } + /** Renders a Javascript object to be displayed inside Monaco Editor */ public renderObjectForEditor(value: any, replacer: any, space: string | number): string { return JSON.stringify(value, replacer, space); diff --git a/src/Explorer/Tabs/TriggerTabContent.tsx b/src/Explorer/Tabs/TriggerTabContent.tsx index bf756598e..5fd28502a 100644 --- a/src/Explorer/Tabs/TriggerTabContent.tsx +++ b/src/Explorer/Tabs/TriggerTabContent.tsx @@ -1,5 +1,6 @@ import { TriggerDefinition } from "@azure/cosmos"; import { Dropdown, IDropdownOption, Label, TextField } from "@fluentui/react"; +import { KeyboardAction } from "KeyboardShortcuts"; import React, { Component } from "react"; import DiscardIcon from "../../../images/discard.svg"; import SaveIcon from "../../../images/save-cosmos.svg"; @@ -218,6 +219,18 @@ export class TriggerTabContent extends Component diff --git a/src/Explorer/Tabs/UserDefinedFunctionTabContent.tsx b/src/Explorer/Tabs/UserDefinedFunctionTabContent.tsx index c0d78ff9a..98b90c045 100644 --- a/src/Explorer/Tabs/UserDefinedFunctionTabContent.tsx +++ b/src/Explorer/Tabs/UserDefinedFunctionTabContent.tsx @@ -1,12 +1,13 @@ import { UserDefinedFunctionDefinition } from "@azure/cosmos"; import { Label, TextField } from "@fluentui/react"; +import { KeyboardAction } from "KeyboardShortcuts"; import React, { Component } from "react"; import DiscardIcon from "../../../images/discard.svg"; import SaveIcon from "../../../images/save-cosmos.svg"; import * as Constants from "../../Common/Constants"; +import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"; import { createUserDefinedFunction } from "../../Common/dataAccess/createUserDefinedFunction"; import { updateUserDefinedFunction } from "../../Common/dataAccess/updateUserDefinedFunction"; -import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"; import * as ViewModels from "../../Contracts/ViewModels"; import { Action } from "../../Shared/Telemetry/TelemetryConstants"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; @@ -80,6 +81,7 @@ export default class UserDefinedFunctionTabContent extends Component< setState: this.setState, iconSrc: SaveIcon, iconAlt: label, + keyboardAction: KeyboardAction.SAVE_ITEM, onCommandClick: this.onSaveClick, commandButtonLabel: label, ariaLabel: label, @@ -94,6 +96,7 @@ export default class UserDefinedFunctionTabContent extends Component< ...this, iconSrc: SaveIcon, iconAlt: label, + keyboardAction: KeyboardAction.SAVE_ITEM, onCommandClick: this.onUpdateClick, commandButtonLabel: label, ariaLabel: label, @@ -109,6 +112,7 @@ export default class UserDefinedFunctionTabContent extends Component< ...this, iconSrc: DiscardIcon, iconAlt: label, + keyboardAction: KeyboardAction.CANCEL_OR_DISCARD, onCommandClick: this.onDiscard, commandButtonLabel: label, ariaLabel: label, diff --git a/src/Explorer/Tree/Collection.ts b/src/Explorer/Tree/Collection.ts index 4b5411d09..d7c673620 100644 --- a/src/Explorer/Tree/Collection.ts +++ b/src/Explorer/Tree/Collection.ts @@ -58,6 +58,7 @@ export default class Collection implements ViewModels.Collection { public indexingPolicy: ko.Observable; public uniqueKeyPolicy: DataModels.UniqueKeyPolicy; public usageSizeInKB: ko.Observable; + public computedProperties: ko.Observable; public offer: ko.Observable; public conflictResolutionPolicy: ko.Observable; @@ -121,6 +122,7 @@ export default class Collection implements ViewModels.Collection { this.schema = data.schema; this.requestSchema = data.requestSchema; this.geospatialConfig = ko.observable(data.geospatialConfig); + this.computedProperties = ko.observable(data.computedProperties); this.partitionKeyPropertyHeaders = this.partitionKey?.paths; this.partitionKeyProperties = this.partitionKeyPropertyHeaders?.map((partitionKeyPropertyHeader, i) => { @@ -306,7 +308,7 @@ export default class Collection implements ViewModels.Collection { collectionName: this.id(), dataExplorerArea: Constants.Areas.Tab, - tabTitle: this.rawDataModel.id + " - Items", + tabTitle: "Items", }); this.documentIds([]); @@ -314,7 +316,7 @@ export default class Collection implements ViewModels.Collection { partitionKey: this.partitionKey, documentIds: ko.observableArray([]), tabKind: ViewModels.CollectionTabKind.Documents, - title: this.rawDataModel.id + " - Items", + title: "Items", collection: this, node: this, tabPath: `${this.databaseId}>${this.id()}>Documents`, diff --git a/src/Explorer/Tree/ResourceTree.tsx b/src/Explorer/Tree/ResourceTree.tsx index b245f327f..b5f759534 100644 --- a/src/Explorer/Tree/ResourceTree.tsx +++ b/src/Explorer/Tree/ResourceTree.tsx @@ -373,11 +373,6 @@ export const ResourceTree: React.FC = ({ container }: Resourc iconSrc: NewNotebookIcon, onClick: () => container.onCreateDirectory(item, isGithubTree), }, - { - label: "New Notebook", - iconSrc: NewNotebookIcon, - onClick: () => container.onNewNotebookClicked(item, isGithubTree), - }, { label: "Upload File", iconSrc: NewNotebookIcon, @@ -786,9 +781,6 @@ export const ResourceTree: React.FC = ({ container }: Resourc - - - {/* {buildGalleryCallout()} */} diff --git a/src/Explorer/Tree/ResourceTreeAdapter.tsx b/src/Explorer/Tree/ResourceTreeAdapter.tsx index abe05e85f..b22f0f916 100644 --- a/src/Explorer/Tree/ResourceTreeAdapter.tsx +++ b/src/Explorer/Tree/ResourceTreeAdapter.tsx @@ -800,11 +800,6 @@ export class ResourceTreeAdapter implements ReactAdapter { iconSrc: NewNotebookIcon, onClick: () => this.container.onCreateDirectory(item), }, - { - label: "New Notebook", - iconSrc: NewNotebookIcon, - onClick: () => this.container.onNewNotebookClicked(item), - }, { label: "Upload File", iconSrc: NewNotebookIcon, diff --git a/src/KeyboardShortcuts.tsx b/src/KeyboardShortcuts.tsx new file mode 100644 index 000000000..98f988038 --- /dev/null +++ b/src/KeyboardShortcuts.tsx @@ -0,0 +1,159 @@ +import * as React from "react"; +import { PropsWithChildren, useEffect } from "react"; +import { KeyBindingMap, tinykeys } from "tinykeys"; +import create, { UseStore } from "zustand"; + +/** + * Represents a keyboard shortcut handler. + * Return `true` to prevent the default action of the keyboard shortcut. + * Any other return value will allow the default action to proceed. + */ +export type KeyboardActionHandler = (e: KeyboardEvent) => boolean | void; + +export type KeyboardHandlerMap = Partial>; + +/** + * 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 = { + // 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>; + + /** + * 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 = 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) { + 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(record: Partial>): K[] { + return Object.keys(record) as K[]; +} diff --git a/src/Main.tsx b/src/Main.tsx index d62f9b45e..f79ee29f1 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -1,3 +1,6 @@ +// Import this first, to ensure that the dev tools hook is copied before React is loaded. +import "./ReactDevTools"; + // CSS Dependencies import { initializeIcons, loadTheme } from "@fluentui/react"; import { QuickstartCarousel } from "Explorer/Quickstart/QuickstartCarousel"; @@ -18,6 +21,7 @@ import "../externals/jquery.typeahead.min.js"; // Image Dependencies import { Platform } from "ConfigContext"; import { QueryCopilotCarousel } from "Explorer/QueryCopilot/CopilotCarousel"; +import { KeyboardShortcutRoot } from "KeyboardShortcuts"; import "../images/CosmosDB_rgb_ui_lighttheme.ico"; import hdeConnectImage from "../images/HdeConnectCosmosDB.svg"; import "../images/favicon.ico"; @@ -88,52 +92,54 @@ const App: React.FunctionComponent = () => { } return ( -
-
-
- {/* Main Command Bar - Start */} - - {/* Collections Tree and Tabs - Begin */} -
- {/* Collections Tree - Start */} - {userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo" && ( -
-
- {/* Collections Tree Expanded - Start */} - - {/* Collections Tree Expanded - End */} - {/* Collections Tree Collapsed - Start */} - - {/* Collections Tree Collapsed - End */} + +
+
+
+ {/* Main Command Bar - Start */} + + {/* Collections Tree and Tabs - Begin */} +
+ {/* Collections Tree - Start */} + {userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo" && ( +
+
+ {/* Collections Tree Expanded - Start */} + + {/* Collections Tree Expanded - End */} + {/* Collections Tree Collapsed - Start */} + + {/* Collections Tree Collapsed - End */} +
-
- )} - -
- {/* Collections Tree and Tabs - End */} - + {/* Collections Tree and Tabs - End */} +
+ + + {} + {} + {} + {}
- - - {} - {} - {} - {} -
+ ); }; diff --git a/src/Platform/Hosted/Components/ConnectExplorer.tsx b/src/Platform/Hosted/Components/ConnectExplorer.tsx index dd291f279..749f248ba 100644 --- a/src/Platform/Hosted/Components/ConnectExplorer.tsx +++ b/src/Platform/Hosted/Components/ConnectExplorer.tsx @@ -1,10 +1,11 @@ import { useBoolean } from "@fluentui/react-hooks"; import { userContext } from "UserContext"; +import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils"; import * as React from "react"; import ConnectImage from "../../../../images/HdeConnectCosmosDB.svg"; import ErrorImage from "../../../../images/error.svg"; import { AuthType } from "../../../AuthType"; -import { HttpHeaders } from "../../../Common/Constants"; +import { BackendApi, HttpHeaders } from "../../../Common/Constants"; import { configContext } from "../../../ConfigContext"; import { GenerateTokenResponse } from "../../../Contracts/DataModels"; import { isResourceTokenConnectionString } from "../Helpers/ResourceTokenUtils"; @@ -18,6 +19,23 @@ interface Props { } export const fetchEncryptedToken = async (connectionString: string): Promise => { + if (!useNewPortalBackendEndpoint(BackendApi.GenerateToken)) { + return await fetchEncryptedToken_ToBeDeprecated(connectionString); + } + + const headers = new Headers(); + headers.append(HttpHeaders.connectionString, connectionString); + const url = configContext.PORTAL_BACKEND_ENDPOINT + "/api/connectionstring/token/generatetoken"; + const response = await fetch(url, { headers, method: "POST" }); + if (!response.ok) { + throw response; + } + + const encryptedTokenResponse: string = await response.json(); + return decodeURIComponent(encryptedTokenResponse); +}; + +export const fetchEncryptedToken_ToBeDeprecated = async (connectionString: string): Promise => { const headers = new Headers(); headers.append(HttpHeaders.connectionString, connectionString); const url = configContext.BACKEND_ENDPOINT + "/api/guest/tokens/generateToken"; diff --git a/src/Platform/Hosted/extractFeatures.ts b/src/Platform/Hosted/extractFeatures.ts index 7bf3c8a3f..5bd84516e 100644 --- a/src/Platform/Hosted/extractFeatures.ts +++ b/src/Platform/Hosted/extractFeatures.ts @@ -31,11 +31,6 @@ export type Features = { readonly mongoProxyAPIs?: string; readonly enableThroughputCap: boolean; readonly enableHierarchicalKeys: boolean; - readonly enableLegacyMongoShellV1: boolean; - readonly enableLegacyMongoShellV1Debug: boolean; - readonly enableLegacyMongoShellV2: boolean; - readonly enableLegacyMongoShellV2Debug: boolean; - readonly loadLegacyMongoShellFromBE: boolean; readonly enableCopilot: boolean; readonly copilotVersion?: string; readonly disableCopilotPhoenixGateaway: boolean; @@ -106,11 +101,6 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear notebooksDownBanner: "true" === get("notebooksDownBanner"), enableThroughputCap: "true" === get("enablethroughputcap"), 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"), copilotVersion: get("copilotversion") ?? "v2.0", disableCopilotPhoenixGateaway: "true" === get("disablecopilotphoenixgateaway"), diff --git a/src/ReactDevTools.ts b/src/ReactDevTools.ts index 09947f934..2a12d81d5 100644 --- a/src/ReactDevTools.ts +++ b/src/ReactDevTools.ts @@ -1,3 +1,7 @@ if (window.parent !== window) { - (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__ = (window.parent as any).__REACT_DEVTOOLS_GLOBAL_HOOK__; + try { + (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__ = (window.parent as any).__REACT_DEVTOOLS_GLOBAL_HOOK__; + } catch { + // No-op. We can throw here if the parent is not the same origin (such as in the Azure portal). + } } diff --git a/src/UserContext.ts b/src/UserContext.ts index daae0f052..2fa1bb946 100644 --- a/src/UserContext.ts +++ b/src/UserContext.ts @@ -51,6 +51,7 @@ interface FabricContext { connectionId: string; databaseConnectionInfo: FabricDatabaseConnectionInfo | undefined; isReadOnly: boolean; + isVisible: boolean; } export type AdminFeedbackControlPolicy = diff --git a/src/Utils/EndpointUtils.ts b/src/Utils/EndpointUtils.ts index 3443d8c71..97e733b98 100644 --- a/src/Utils/EndpointUtils.ts +++ b/src/Utils/EndpointUtils.ts @@ -1,4 +1,11 @@ -import { CassandraProxyEndpoints, JunoEndpoints, MongoProxyEndpoints } from "Common/Constants"; +import { + BackendApi, + CassandraProxyEndpoints, + JunoEndpoints, + MongoProxyEndpoints, + PortalBackendEndpoints, +} from "Common/Constants"; +import { configContext } from "ConfigContext"; import * as Logger from "../Common/Logger"; export function validateEndpoint( @@ -75,7 +82,7 @@ export const MongoProxyOutboundIPs: { [key: string]: string[] } = { }; export const allowedMongoProxyEndpoints: ReadonlyArray = [ - MongoProxyEndpoints.Development, + MongoProxyEndpoints.Local, MongoProxyEndpoints.Mpac, MongoProxyEndpoints.Prod, MongoProxyEndpoints.Fairfax, @@ -98,6 +105,14 @@ export const allowedCassandraProxyEndpoints: ReadonlyArray = [ CassandraProxyEndpoints.Mooncake, ]; +export const allowedCassandraProxyEndpoints_ToBeDeprecated: ReadonlyArray = [ + "https://main.documentdb.ext.azure.com", + "https://main.documentdb.ext.azure.cn", + "https://main.documentdb.ext.azure.us", + "https://main.cosmos.ext.azure", + "https://localhost:12901", +]; + export const CassandraProxyOutboundIPs: { [key: string]: string[] } = { [CassandraProxyEndpoints.Mpac]: ["40.113.96.14", "104.42.11.145"], [CassandraProxyEndpoints.Prod]: ["137.117.230.240", "168.61.72.237"], @@ -129,3 +144,31 @@ export const allowedJunoOrigins: ReadonlyArray = [ ]; export const allowedNotebookServerUrls: ReadonlyArray = []; + +// +// Temporary function to determine if a portal backend API is supported by the +// new backend in this environment. +// +// TODO: Remove this function once new backend migration is completed for all environments. +// +export function useNewPortalBackendEndpoint(backendApi: string): boolean { + // This maps backend APIs to the environments supported by the new backend. + const newBackendApiEnvironmentMap: { [key: string]: string[] } = { + [BackendApi.GenerateToken]: [ + PortalBackendEndpoints.Development, + PortalBackendEndpoints.Mpac, + PortalBackendEndpoints.Prod, + ], + [BackendApi.PortalSettings]: [ + PortalBackendEndpoints.Development, + PortalBackendEndpoints.Mpac, + PortalBackendEndpoints.Prod, + ], + }; + + if (!newBackendApiEnvironmentMap[backendApi] || !configContext.PORTAL_BACKEND_ENDPOINT) { + return false; + } + + return newBackendApiEnvironmentMap[backendApi].includes(configContext.PORTAL_BACKEND_ENDPOINT); +} diff --git a/src/Utils/arm/generatedClients/cosmos/cassandraResources.ts b/src/Utils/arm/generatedClients/cosmos/cassandraResources.ts index 2a7762328..461e516bf 100644 --- a/src/Utils/arm/generatedClients/cosmos/cassandraResources.ts +++ b/src/Utils/arm/generatedClients/cosmos/cassandraResources.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json */ +import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -import { configContext } from "../../../../ConfigContext"; -const apiVersion = "2023-09-15-preview"; +const apiVersion = "2024-02-15-preview"; /* Lists the Cassandra keyspaces under an existing Azure Cosmos DB database account. */ export async function listCassandraKeyspaces( diff --git a/src/Utils/arm/generatedClients/cosmos/collection.ts b/src/Utils/arm/generatedClients/cosmos/collection.ts index 86b116f01..4a9a9c198 100644 --- a/src/Utils/arm/generatedClients/cosmos/collection.ts +++ b/src/Utils/arm/generatedClients/cosmos/collection.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json */ +import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -import { configContext } from "../../../../ConfigContext"; -const apiVersion = "2023-09-15-preview"; +const apiVersion = "2024-02-15-preview"; /* Retrieves the metrics determined by the given filter for the given database account and collection. */ export async function listMetrics( diff --git a/src/Utils/arm/generatedClients/cosmos/collectionPartition.ts b/src/Utils/arm/generatedClients/cosmos/collectionPartition.ts index 8b3f407da..6fed487b6 100644 --- a/src/Utils/arm/generatedClients/cosmos/collectionPartition.ts +++ b/src/Utils/arm/generatedClients/cosmos/collectionPartition.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json */ +import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -import { configContext } from "../../../../ConfigContext"; -const apiVersion = "2023-09-15-preview"; +const apiVersion = "2024-02-15-preview"; /* Retrieves the metrics determined by the given filter for the given collection, split by partition. */ export async function listMetrics( diff --git a/src/Utils/arm/generatedClients/cosmos/collectionPartitionRegion.ts b/src/Utils/arm/generatedClients/cosmos/collectionPartitionRegion.ts index ec0d5e0ea..b33c904d9 100644 --- a/src/Utils/arm/generatedClients/cosmos/collectionPartitionRegion.ts +++ b/src/Utils/arm/generatedClients/cosmos/collectionPartitionRegion.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json */ +import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -import { configContext } from "../../../../ConfigContext"; -const apiVersion = "2023-09-15-preview"; +const apiVersion = "2024-02-15-preview"; /* Retrieves the metrics determined by the given filter for the given collection and region, split by partition. */ export async function listMetrics( diff --git a/src/Utils/arm/generatedClients/cosmos/collectionRegion.ts b/src/Utils/arm/generatedClients/cosmos/collectionRegion.ts index 281d98289..984cb146a 100644 --- a/src/Utils/arm/generatedClients/cosmos/collectionRegion.ts +++ b/src/Utils/arm/generatedClients/cosmos/collectionRegion.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json */ +import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -import { configContext } from "../../../../ConfigContext"; -const apiVersion = "2023-09-15-preview"; +const apiVersion = "2024-02-15-preview"; /* Retrieves the metrics determined by the given filter for the given database account, collection and region. */ export async function listMetrics( diff --git a/src/Utils/arm/generatedClients/cosmos/database.ts b/src/Utils/arm/generatedClients/cosmos/database.ts index 1d01fcbd7..7b286c4ec 100644 --- a/src/Utils/arm/generatedClients/cosmos/database.ts +++ b/src/Utils/arm/generatedClients/cosmos/database.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json */ +import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -import { configContext } from "../../../../ConfigContext"; -const apiVersion = "2023-09-15-preview"; +const apiVersion = "2024-02-15-preview"; /* Retrieves the metrics determined by the given filter for the given database account and database. */ export async function listMetrics( diff --git a/src/Utils/arm/generatedClients/cosmos/databaseAccountRegion.ts b/src/Utils/arm/generatedClients/cosmos/databaseAccountRegion.ts index 38fdbd89e..09f17c35b 100644 --- a/src/Utils/arm/generatedClients/cosmos/databaseAccountRegion.ts +++ b/src/Utils/arm/generatedClients/cosmos/databaseAccountRegion.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json */ +import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -import { configContext } from "../../../../ConfigContext"; -const apiVersion = "2023-09-15-preview"; +const apiVersion = "2024-02-15-preview"; /* Retrieves the metrics determined by the given filter for the given database account and region. */ export async function listMetrics( diff --git a/src/Utils/arm/generatedClients/cosmos/databaseAccounts.ts b/src/Utils/arm/generatedClients/cosmos/databaseAccounts.ts index 32aeb05eb..4b52b631b 100644 --- a/src/Utils/arm/generatedClients/cosmos/databaseAccounts.ts +++ b/src/Utils/arm/generatedClients/cosmos/databaseAccounts.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json */ +import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -import { configContext } from "../../../../ConfigContext"; -const apiVersion = "2023-09-15-preview"; +const apiVersion = "2024-02-15-preview"; /* Retrieves the properties of an existing Azure Cosmos DB database account. */ export async function get( diff --git a/src/Utils/arm/generatedClients/cosmos/graphResources.ts b/src/Utils/arm/generatedClients/cosmos/graphResources.ts index 807380575..d51d44d77 100644 --- a/src/Utils/arm/generatedClients/cosmos/graphResources.ts +++ b/src/Utils/arm/generatedClients/cosmos/graphResources.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json */ +import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -import { configContext } from "../../../../ConfigContext"; -const apiVersion = "2023-09-15-preview"; +const apiVersion = "2024-02-15-preview"; /* Lists the graphs under an existing Azure Cosmos DB database account. */ export async function listGraphs( diff --git a/src/Utils/arm/generatedClients/cosmos/gremlinResources.ts b/src/Utils/arm/generatedClients/cosmos/gremlinResources.ts index ffad0a38a..6e8656400 100644 --- a/src/Utils/arm/generatedClients/cosmos/gremlinResources.ts +++ b/src/Utils/arm/generatedClients/cosmos/gremlinResources.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json */ +import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -import { configContext } from "../../../../ConfigContext"; -const apiVersion = "2023-09-15-preview"; +const apiVersion = "2024-02-15-preview"; /* Lists the Gremlin databases under an existing Azure Cosmos DB database account. */ export async function listGremlinDatabases( diff --git a/src/Utils/arm/generatedClients/cosmos/locations.ts b/src/Utils/arm/generatedClients/cosmos/locations.ts index 5c28d60f0..6ec4cdc62 100644 --- a/src/Utils/arm/generatedClients/cosmos/locations.ts +++ b/src/Utils/arm/generatedClients/cosmos/locations.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json */ +import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -import { configContext } from "../../../../ConfigContext"; -const apiVersion = "2023-09-15-preview"; +const apiVersion = "2024-02-15-preview"; /* List Cosmos DB locations and their properties */ export async function list(subscriptionId: string): Promise { diff --git a/src/Utils/arm/generatedClients/cosmos/mongoDBResources.ts b/src/Utils/arm/generatedClients/cosmos/mongoDBResources.ts index b99584e1c..22b316904 100644 --- a/src/Utils/arm/generatedClients/cosmos/mongoDBResources.ts +++ b/src/Utils/arm/generatedClients/cosmos/mongoDBResources.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json */ +import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -import { configContext } from "../../../../ConfigContext"; -const apiVersion = "2023-09-15-preview"; +const apiVersion = "2024-02-15-preview"; /* Lists the MongoDB databases under an existing Azure Cosmos DB database account. */ export async function listMongoDBDatabases( diff --git a/src/Utils/arm/generatedClients/cosmos/operations.ts b/src/Utils/arm/generatedClients/cosmos/operations.ts index 5a01456ce..ae87fbb48 100644 --- a/src/Utils/arm/generatedClients/cosmos/operations.ts +++ b/src/Utils/arm/generatedClients/cosmos/operations.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json */ +import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -import { configContext } from "../../../../ConfigContext"; -const apiVersion = "2023-09-15-preview"; +const apiVersion = "2024-02-15-preview"; /* Lists all of the available Cosmos DB Resource Provider operations. */ export async function list(): Promise { diff --git a/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeId.ts b/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeId.ts index a66e6294c..d9b5d6dfa 100644 --- a/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeId.ts +++ b/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeId.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json */ +import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -import { configContext } from "../../../../ConfigContext"; -const apiVersion = "2023-09-15-preview"; +const apiVersion = "2024-02-15-preview"; /* Retrieves the metrics determined by the given filter for the given partition key range id. */ export async function listMetrics( diff --git a/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeIdRegion.ts b/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeIdRegion.ts index 3625b0859..6ec2ba50a 100644 --- a/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeIdRegion.ts +++ b/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeIdRegion.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json */ +import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -import { configContext } from "../../../../ConfigContext"; -const apiVersion = "2023-09-15-preview"; +const apiVersion = "2024-02-15-preview"; /* Retrieves the metrics determined by the given filter for the given partition key range id and region. */ export async function listMetrics( diff --git a/src/Utils/arm/generatedClients/cosmos/percentile.ts b/src/Utils/arm/generatedClients/cosmos/percentile.ts index b7a8d2841..cbbc751dc 100644 --- a/src/Utils/arm/generatedClients/cosmos/percentile.ts +++ b/src/Utils/arm/generatedClients/cosmos/percentile.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json */ +import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -import { configContext } from "../../../../ConfigContext"; -const apiVersion = "2023-09-15-preview"; +const apiVersion = "2024-02-15-preview"; /* Retrieves the metrics determined by the given filter for the given database account. This url is only for PBS and Replication Latency data */ export async function listMetrics( diff --git a/src/Utils/arm/generatedClients/cosmos/percentileSourceTarget.ts b/src/Utils/arm/generatedClients/cosmos/percentileSourceTarget.ts index aa9432e8f..88c05dd11 100644 --- a/src/Utils/arm/generatedClients/cosmos/percentileSourceTarget.ts +++ b/src/Utils/arm/generatedClients/cosmos/percentileSourceTarget.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json */ +import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -import { configContext } from "../../../../ConfigContext"; -const apiVersion = "2023-09-15-preview"; +const apiVersion = "2024-02-15-preview"; /* Retrieves the metrics determined by the given filter for the given account, source and target region. This url is only for PBS and Replication Latency data */ export async function listMetrics( diff --git a/src/Utils/arm/generatedClients/cosmos/percentileTarget.ts b/src/Utils/arm/generatedClients/cosmos/percentileTarget.ts index 43f102890..87359e9f0 100644 --- a/src/Utils/arm/generatedClients/cosmos/percentileTarget.ts +++ b/src/Utils/arm/generatedClients/cosmos/percentileTarget.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json */ +import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -import { configContext } from "../../../../ConfigContext"; -const apiVersion = "2023-09-15-preview"; +const apiVersion = "2024-02-15-preview"; /* Retrieves the metrics determined by the given filter for the given account target region. This url is only for PBS and Replication Latency data */ export async function listMetrics( diff --git a/src/Utils/arm/generatedClients/cosmos/sqlResources.ts b/src/Utils/arm/generatedClients/cosmos/sqlResources.ts index 321d68639..f85ddb636 100644 --- a/src/Utils/arm/generatedClients/cosmos/sqlResources.ts +++ b/src/Utils/arm/generatedClients/cosmos/sqlResources.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json */ +import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -import { configContext } from "../../../../ConfigContext"; -const apiVersion = "2023-09-15-preview"; +const apiVersion = "2024-02-15-preview"; /* Lists the SQL databases under an existing Azure Cosmos DB database account. */ export async function listSqlDatabases( diff --git a/src/Utils/arm/generatedClients/cosmos/tableResources.ts b/src/Utils/arm/generatedClients/cosmos/tableResources.ts index 3bc16cf0e..0da78793e 100644 --- a/src/Utils/arm/generatedClients/cosmos/tableResources.ts +++ b/src/Utils/arm/generatedClients/cosmos/tableResources.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json */ +import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -import { configContext } from "../../../../ConfigContext"; -const apiVersion = "2023-09-15-preview"; +const apiVersion = "2024-02-15-preview"; /* Lists the Tables under an existing Azure Cosmos DB database account. */ export async function listTables( diff --git a/src/Utils/arm/generatedClients/cosmos/types.ts b/src/Utils/arm/generatedClients/cosmos/types.ts index 3252b38fe..4f69d223c 100644 --- a/src/Utils/arm/generatedClients/cosmos/types.ts +++ b/src/Utils/arm/generatedClients/cosmos/types.ts @@ -3,7 +3,7 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json */ /* The List operation response, that contains the client encryption keys and their properties. */ @@ -566,12 +566,14 @@ export interface DatabaseAccountGetProperties { minimalTlsVersion?: MinimalTlsVersion; /* Indicates the status of the Customer Managed Key feature on the account. In case there are errors, the property provides troubleshooting guidance. */ - customerManagedKeyStatus?: CustomerManagedKeyStatus; - + customerManagedKeyStatus?: string; /* Flag to indicate enabling/disabling of Priority Based Execution Preview feature on the account */ enablePriorityBasedExecution?: boolean; /* Enum to indicate default Priority Level of request for Priority Based Execution. */ defaultPriorityLevel?: DefaultPriorityLevel; + + /* Flag to indicate enabling/disabling of Per-Region Per-partition autoscale Preview feature on the account */ + enablePerRegionPerPartitionAutoscale?: boolean; } /* Properties to create and update Azure Cosmos DB database accounts. */ @@ -663,12 +665,14 @@ export interface DatabaseAccountCreateUpdateProperties { minimalTlsVersion?: MinimalTlsVersion; /* Indicates the status of the Customer Managed Key feature on the account. In case there are errors, the property provides troubleshooting guidance. */ - customerManagedKeyStatus?: CustomerManagedKeyStatus; - + customerManagedKeyStatus?: string; /* Flag to indicate enabling/disabling of Priority Based Execution Preview feature on the account */ enablePriorityBasedExecution?: boolean; /* Enum to indicate default Priority Level of request for Priority Based Execution. */ defaultPriorityLevel?: DefaultPriorityLevel; + + /* Flag to indicate enabling/disabling of Per-Region Per-partition autoscale Preview feature on the account */ + enablePerRegionPerPartitionAutoscale?: boolean; } /* Parameters to create and update Cosmos DB database accounts. */ @@ -763,12 +767,14 @@ export interface DatabaseAccountUpdateProperties { minimalTlsVersion?: MinimalTlsVersion; /* Indicates the status of the Customer Managed Key feature on the account. In case there are errors, the property provides troubleshooting guidance. */ - customerManagedKeyStatus?: CustomerManagedKeyStatus; - + customerManagedKeyStatus?: string; /* Flag to indicate enabling/disabling of Priority Based Execution Preview feature on the account */ enablePriorityBasedExecution?: boolean; /* Enum to indicate default Priority Level of request for Priority Based Execution. */ defaultPriorityLevel?: DefaultPriorityLevel; + + /* Flag to indicate enabling/disabling of Per-Region Per-partition autoscale Preview feature on the account */ + enablePerRegionPerPartitionAutoscale?: boolean; } /* Parameters for patching Azure Cosmos DB database account properties. */ @@ -1256,6 +1262,9 @@ export interface SqlContainerResource { /* The configuration for defining Materialized Views. This must be specified only for creating a Materialized View container. */ materializedViewDefinition?: MaterializedViewDefinition; + + /* List of computed properties */ + computedProperties?: ComputedProperty[]; } /* Cosmos DB indexing policy */ @@ -1325,6 +1334,14 @@ export interface SpatialSpec { /* Indicates the spatial type of index. */ export type SpatialType = "Point" | "LineString" | "Polygon" | "MultiPolygon"; +/* The definition of a computed property */ +export interface ComputedProperty { + /* The name of a computed property, for example - "cp_lowerName" */ + name?: string; + /* The query that evaluates the value for computed property, for example - "SELECT VALUE LOWER(c.name) FROM c" */ + query?: string; +} + /* The configuration of the partition key to be used for partitioning data into multiple partitions */ export interface ContainerPartitionKey { /* List of paths using which data within the container can be partitioned */ @@ -1929,6 +1946,8 @@ export interface RestoreParametersBase { restoreSource?: string; /* Time to which the account has to be restored (ISO-8601 format). */ restoreTimestampInUtc?: string; + /* Specifies whether the restored account will have Time-To-Live disabled upon the successful restore. */ + restoreWithTtlDisabled?: boolean; } /* Parameters to indicate the information about the restore. */ @@ -2072,19 +2091,5 @@ export type ContinuousTier = "Continuous7Days" | "Continuous30Days"; /* Indicates the minimum allowed Tls version. The default is Tls 1.0, except for Cassandra and Mongo API's, which only work with Tls 1.2. */ export type MinimalTlsVersion = "Tls" | "Tls11" | "Tls12"; -/* Indicates the status of the Customer Managed Key feature on the account. In case there are errors, the property provides troubleshooting guidance. */ -export type CustomerManagedKeyStatus = - | "Access to your account is currently revoked because the Azure Cosmos DB service is unable to obtain the AAD authentication token for the account's default identity; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#azure-active-directory-token-acquisition-error (4000)." - | "Access to your account is currently revoked because the Azure Cosmos DB account's key vault key URI does not follow the expected format; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#improper-syntax-detected-on-the-key-vault-uri-property (4006)." - | "Access to your account is currently revoked because the current default identity no longer has permission to the associated Key Vault key; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#default-identity-is-unauthorized-to-access-the-azure-key-vault-key (4002)." - | "Access to your account is currently revoked because the Azure Key Vault DNS name specified by the account's keyvaultkeyuri property could not be resolved; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#unable-to-resolve-the-key-vaults-dns (4009)." - | "Access to your account is currently revoked because the correspondent key is not found on the specified Key Vault; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#azure-key-vault-resource-not-found (4003)." - | "Access to your account is currently revoked because the Azure Cosmos DB service is unable to wrap or unwrap the key; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#internal-unwrapping-procedure-error (4005)." - | "Access to your account is currently revoked because the Azure Cosmos DB account has an undefined default identity; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#invalid-azure-cosmos-db-default-identity (4015)." - | "Access to your account is currently revoked because the access rules are blocking outbound requests to the Azure Key Vault service; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide (4016)." - | "Access to your account is currently revoked because the correspondent Azure Key Vault was not found; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#azure-key-vault-resource-not-found (4017)." - | "Access to your account is currently revoked; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide" - | "Access to the configured customer managed key confirmed."; - /* Enum to indicate default priorityLevel of requests */ export type DefaultPriorityLevel = "High" | "Low"; diff --git a/src/hooks/useKnockoutExplorer.ts b/src/hooks/useKnockoutExplorer.ts index 22b5078c9..4e80b3476 100644 --- a/src/hooks/useKnockoutExplorer.ts +++ b/src/hooks/useKnockoutExplorer.ts @@ -2,7 +2,6 @@ import { createUri } from "Common/UrlUtility"; import { DATA_EXPLORER_RPC_VERSION } from "Contracts/DataExplorerMessagesContract"; import { FABRIC_RPC_VERSION, FabricMessageV2 } from "Contracts/FabricMessagesContract"; import Explorer from "Explorer/Explorer"; -import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter"; import { useSelectedNode } from "Explorer/useSelectedNode"; import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil"; import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility"; @@ -90,6 +89,7 @@ async function configureFabric(): Promise { // These are the versions of Fabric that Data Explorer supports. const SUPPORTED_FABRIC_VERSIONS = [FABRIC_RPC_VERSION]; + let firstContainerOpened = false; let explorer: Explorer; return new Promise((resolve) => { window.addEventListener( @@ -121,7 +121,10 @@ async function configureFabric(): Promise { await scheduleRefreshDatabaseResourceToken(true); resolve(explorer); await explorer.refreshAllDatabases(); - openFirstContainer(explorer, userContext.fabricContext.databaseConnectionInfo.databaseId); + if (userContext.fabricContext.isVisible && !firstContainerOpened) { + firstContainerOpened = true; + openFirstContainer(explorer, userContext.fabricContext.databaseConnectionInfo.databaseId); + } break; } case "newContainer": @@ -132,8 +135,16 @@ async function configureFabric(): Promise { handleCachedDataMessage(data); break; } - case "setToolbarStatus": { - useCommandBar.getState().setIsHidden(data.message.visible === false); + case "explorerVisible": { + userContext.fabricContext.isVisible = data.message.visible; + if ( + userContext.fabricContext.isVisible && + !firstContainerOpened && + userContext?.fabricContext?.databaseConnectionInfo?.databaseId !== undefined + ) { + firstContainerOpened = true; + openFirstContainer(explorer, userContext.fabricContext.databaseConnectionInfo.databaseId); + } break; } default: @@ -327,12 +338,13 @@ function configureHostedWithResourceToken(config: ResourceToken): Explorer { return explorer; } -function createExplorerFabric(params: { connectionId: string }): Explorer { +function createExplorerFabric(params: { connectionId: string; isVisible: boolean }): Explorer { updateUserContext({ fabricContext: { connectionId: params.connectionId, databaseConnectionInfo: undefined, isReadOnly: true, + isVisible: params.isVisible ?? true, }, authType: AuthType.ConnectionString, databaseAccount: { @@ -485,6 +497,7 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) { ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT), MONGO_PROXY_ENDPOINT: inputs.mongoProxyEndpoint, CASSANDRA_PROXY_ENDPOINT: inputs.cassandraProxyEndpoint, + PORTAL_BACKEND_ENDPOINT: inputs.portalBackendEndpoint, }); updateUserContext({ diff --git a/src/hooks/useTabs.ts b/src/hooks/useTabs.ts index 136e0ce2c..982768afa 100644 --- a/src/hooks/useTabs.ts +++ b/src/hooks/useTabs.ts @@ -1,3 +1,4 @@ +import { clamp } from "@fluentui/react"; import create, { UseStore } from "zustand"; import * as ViewModels from "../Contracts/ViewModels"; import { CollectionTabKind } from "../Contracts/ViewModels"; @@ -29,6 +30,11 @@ export interface TabsState { setQueryCopilotTabInitialInput: (input: string) => void; setIsTabExecuting: (state: boolean) => void; setIsQueryErrorThrown: (state: boolean) => void; + getCurrentTabIndex: () => number; + selectTabByIndex: (index: number) => void; + selectLeftTab: () => void; + selectRightTab: () => void; + closeActiveTab: () => void; } export enum ReactTabKind { @@ -105,7 +111,7 @@ export const useTabs: UseStore = create((set, get) => ({ return true; }); if (updatedTabs.length === 0 && configContext.platform !== Platform.Fabric) { - set({ activeTab: undefined, activeReactTab: ReactTabKind.Home }); + set({ activeTab: undefined, activeReactTab: undefined }); } if (tab.tabId === activeTab.tabId && tabIndex !== -1) { @@ -143,7 +149,7 @@ export const useTabs: UseStore = create((set, get) => ({ }); if (get().openedTabs.length === 0 && configContext.platform !== Platform.Fabric) { - set({ activeTab: undefined, activeReactTab: ReactTabKind.Home }); + set({ activeTab: undefined, activeReactTab: undefined }); } } }, @@ -175,4 +181,44 @@ export const useTabs: UseStore = create((set, get) => ({ setIsQueryErrorThrown: (state: boolean) => { 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); + } + }, })); diff --git a/test/cassandra/container.spec.ts b/test/cassandra/container.spec.ts index 48acddb79..80d5df41d 100644 --- a/test/cassandra/container.spec.ts +++ b/test/cassandra/container.spec.ts @@ -1,15 +1,18 @@ import { jest } from "@jest/globals"; import "expect-playwright"; -import { generateUniqueName } from "../utils/shared"; +import { generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared"; import { waitForExplorer } from "../utils/waitForExplorer"; jest.setTimeout(120000); test("Cassandra keyspace and table CRUD", async () => { const keyspaceId = generateUniqueName("keyspace"); const tableId = generateUniqueName("table"); + + // We can't retrieve AZ CLI credentials from the browser so we get them here. + const token = await getAzureCLICredentialsToken(); page.setDefaultTimeout(50000); - await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-cassandra-runner"); + await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-cassandra-runner&token=${token}`); await page.waitForSelector("iframe"); const explorer = await waitForExplorer(); diff --git a/test/graph/container.spec.ts b/test/graph/container.spec.ts index 3e6155f1c..e7f288da5 100644 --- a/test/graph/container.spec.ts +++ b/test/graph/container.spec.ts @@ -1,15 +1,18 @@ import { jest } from "@jest/globals"; import "expect-playwright"; -import { generateDatabaseNameWithTimestamp, generateUniqueName } from "../utils/shared"; +import { generateDatabaseNameWithTimestamp, generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared"; import { waitForExplorer } from "../utils/waitForExplorer"; jest.setTimeout(240000); test("Graph CRUD", async () => { const databaseId = generateDatabaseNameWithTimestamp(); const containerId = generateUniqueName("container"); + + // We can't retrieve AZ CLI credentials from the browser so we get them here. + const token = await getAzureCLICredentialsToken(); page.setDefaultTimeout(50000); - await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-gremlin-runner"); + await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-gremlin-runner&token=${token}`); const explorer = await waitForExplorer(); // Create new database and graph diff --git a/test/mongo/container.spec.ts b/test/mongo/container.spec.ts index 88a311784..baafefbbe 100644 --- a/test/mongo/container.spec.ts +++ b/test/mongo/container.spec.ts @@ -1,15 +1,18 @@ import { jest } from "@jest/globals"; import "expect-playwright"; -import { generateDatabaseNameWithTimestamp, generateUniqueName } from "../utils/shared"; +import { generateDatabaseNameWithTimestamp, generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared"; import { waitForExplorer } from "../utils/waitForExplorer"; jest.setTimeout(240000); test("Mongo CRUD", async () => { const databaseId = generateDatabaseNameWithTimestamp(); const containerId = generateUniqueName("container"); + + // We can't retrieve AZ CLI credentials from the browser so we get them here. + const token = await getAzureCLICredentialsToken(); page.setDefaultTimeout(50000); - await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-mongo-runner"); + await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-mongo-runner&token=${token}`); const explorer = await waitForExplorer(); // Create new database and collection diff --git a/test/mongo/container32.spec.ts b/test/mongo/container32.spec.ts index 25466e266..c71f9d0cc 100644 --- a/test/mongo/container32.spec.ts +++ b/test/mongo/container32.spec.ts @@ -1,15 +1,18 @@ import { jest } from "@jest/globals"; import "expect-playwright"; -import { generateDatabaseNameWithTimestamp, generateUniqueName } from "../utils/shared"; +import { generateDatabaseNameWithTimestamp, generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared"; import { waitForExplorer } from "../utils/waitForExplorer"; jest.setTimeout(240000); test("Mongo CRUD", async () => { const databaseId = generateDatabaseNameWithTimestamp(); const containerId = generateUniqueName("container"); + + // We can't retrieve AZ CLI credentials from the browser so we get them here. + const token = await getAzureCLICredentialsToken(); page.setDefaultTimeout(50000); - await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-mongo32-runner"); + await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-mongo32-runner&token=${token}`); const explorer = await waitForExplorer(); // Create new database and collection diff --git a/test/selfServe/selfServeExample.spec.ts b/test/selfServe/selfServeExample.spec.ts index 7e10c1ce2..3678f5b35 100644 --- a/test/selfServe/selfServeExample.spec.ts +++ b/test/selfServe/selfServeExample.spec.ts @@ -1,5 +1,10 @@ +import { getAzureCLICredentialsToken } from "../utils/shared"; + test("Self Serve", async () => { - await page.goto("https://localhost:1234/testExplorer.html?iframeSrc=selfServe.html"); + // We can't retrieve AZ CLI credentials from the browser so we get them here. + const token = await getAzureCLICredentialsToken(); + + await page.goto(`https://localhost:1234/testExplorer.html?iframeSrc=selfServe.html&token=${token}`); const handle = await page.waitForSelector("iframe"); const frame = await handle.contentFrame(); diff --git a/test/sql/container.spec.ts b/test/sql/container.spec.ts index aead57b92..a1aacfd42 100644 --- a/test/sql/container.spec.ts +++ b/test/sql/container.spec.ts @@ -1,15 +1,18 @@ import { jest } from "@jest/globals"; import "expect-playwright"; -import { generateUniqueName } from "../utils/shared"; +import { generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared"; import { waitForExplorer } from "../utils/waitForExplorer"; jest.setTimeout(120000); test("SQL CRUD", async () => { const databaseId = generateUniqueName("db"); const containerId = generateUniqueName("container"); + + // We can't retrieve AZ CLI credentials from the browser so we get them here. + const token = await getAzureCLICredentialsToken(); page.setDefaultTimeout(50000); - await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-sql-runner-west-us"); + await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-sql-runner-west-us&token=${token}`); const explorer = await waitForExplorer(); await explorer.click('[data-test="New Container"]'); diff --git a/test/sql/resourceToken.spec.ts b/test/sql/resourceToken.spec.ts index f2e9d94ef..18228c7ed 100644 --- a/test/sql/resourceToken.spec.ts +++ b/test/sql/resourceToken.spec.ts @@ -1,19 +1,15 @@ import { CosmosDBManagementClient } from "@azure/arm-cosmosdb"; import { CosmosClient, PermissionMode } from "@azure/cosmos"; -import * as msRestNodeAuth from "@azure/ms-rest-nodeauth"; import { jest } from "@jest/globals"; import "expect-playwright"; -import { generateUniqueName } from "../utils/shared"; +import { generateUniqueName, getAzureCLICredentials } from "../utils/shared"; jest.setTimeout(120000); -const clientId = "fd8753b0-0707-4e32-84e9-2532af865fb4"; -const secret = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET"]; -const tenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47"; -const subscriptionId = "69e02f2d-f059-4409-9eac-97e8a276ae2c"; +const subscriptionId = process.env["AZURE_SUBSCRIPTION_ID"] ?? ""; const resourceGroupName = "runners"; test("Resource token", async () => { - const credentials = await msRestNodeAuth.loginWithServicePrincipalSecret(clientId, secret, tenantId); + const credentials = await getAzureCLICredentials(); const armClient = new CosmosDBManagementClient(credentials, subscriptionId); const account = await armClient.databaseAccounts.get(resourceGroupName, "portal-sql-runner-west-us"); const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, "portal-sql-runner-west-us"); diff --git a/test/tables/container.spec.ts b/test/tables/container.spec.ts index d8daa0a16..98687d60f 100644 --- a/test/tables/container.spec.ts +++ b/test/tables/container.spec.ts @@ -1,15 +1,17 @@ import { jest } from "@jest/globals"; import "expect-playwright"; -import { generateUniqueName } from "../utils/shared"; +import { generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared"; import { waitForExplorer } from "../utils/waitForExplorer"; jest.setTimeout(120000); test("Tables CRUD", async () => { const tableId = generateUniqueName("table"); + // We can't retrieve AZ CLI credentials from the browser so we get them here. + const token = await getAzureCLICredentialsToken(); page.setDefaultTimeout(50000); - await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-tables-runner"); + await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-tables-runner&token=${token}`); const explorer = await waitForExplorer(); await page.waitForSelector('text="Querying databases"', { state: "detached" }); diff --git a/test/testExplorer/TestExplorer.ts b/test/testExplorer/TestExplorer.ts index 684673590..4dbeb86b6 100644 --- a/test/testExplorer/TestExplorer.ts +++ b/test/testExplorer/TestExplorer.ts @@ -1,5 +1,4 @@ /* eslint-disable no-console */ -import { ClientSecretCredential } from "@azure/identity"; import "../../less/hostedexplorer.less"; import { DataExplorerInputsFrame } from "../../src/Contracts/ViewModels"; import { updateUserContext } from "../../src/UserContext"; @@ -11,29 +10,13 @@ const urlSearchParams = new URLSearchParams(window.location.search); const accountName = urlSearchParams.get("accountName") || "portal-sql-runner-west-us"; const selfServeType = urlSearchParams.get("selfServeType") || "example"; const iframeSrc = urlSearchParams.get("iframeSrc") || "explorer.html?platform=Portal&disablePortalInitCache"; - -if (!process.env.AZURE_CLIENT_SECRET) { - throw new Error( - "process.env.AZURE_CLIENT_SECRET was not set! Set it in your .env file and restart webpack dev server", - ); -} - -// Azure SDK clients accept the credential as a parameter -const credentials = new ClientSecretCredential( - process.env.AZURE_TENANT_ID, - process.env.AZURE_CLIENT_ID, - process.env.AZURE_CLIENT_SECRET, - { - authorityHost: "https://localhost:1234", - }, -); +const token = urlSearchParams.get("token"); console.log("Resource Group:", resourceGroup); console.log("Subcription: ", subscriptionId); console.log("Account Name: ", accountName); const initTestExplorer = async (): Promise => { - const { token } = await credentials.getToken("https://management.azure.com//.default"); updateUserContext({ authorizationToken: `bearer ${token}`, }); @@ -52,6 +35,9 @@ const initTestExplorer = async (): Promise => { dnsSuffix: "documents.azure.com", serverId: "prod1", extensionEndpoint: "/proxy", + portalBackendEndpoint: "https://cdb-ms-mpac-pbe.cosmos.azure.com", + mongoProxyEndpoint: "https://cdb-ms-mpac-mp.cosmos.azure.com", + cassandraProxyEndpoint: "https://cdb-ms-mpac-cp.cosmos.azure.com", subscriptionType: 3, quotaId: "Internal_2014-09-01", isTryCosmosDBSubscription: false, diff --git a/test/utils/shared.ts b/test/utils/shared.ts index 118736129..59ef0994c 100644 --- a/test/utils/shared.ts +++ b/test/utils/shared.ts @@ -1,3 +1,4 @@ +import { AzureCliCredentials } from "@azure/ms-rest-nodeauth"; import crypto from "crypto"; export function generateUniqueName(baseName = "", length = 4): string { @@ -7,3 +8,13 @@ export function generateUniqueName(baseName = "", length = 4): string { export function generateDatabaseNameWithTimestamp(baseName = "db", length = 1): string { return `${baseName}${crypto.randomBytes(length).toString("hex")}-${Date.now()}`; } + +export async function getAzureCLICredentials(): Promise { + return await AzureCliCredentials.create(); +} + +export async function getAzureCLICredentialsToken(): Promise { + const credentials = await getAzureCLICredentials(); + const token = (await credentials.getToken()).accessToken; + return token; +} diff --git a/utils/armClientGenerator/generator.ts b/utils/armClientGenerator/generator.ts index 01745830a..1a50e9082 100644 --- a/utils/armClientGenerator/generator.ts +++ b/utils/armClientGenerator/generator.ts @@ -16,7 +16,7 @@ Results of this file should be checked into the repo. */ // CHANGE THESE VALUES TO GENERATE NEW CLIENTS -const version = "2023-11-15-preview"; +const version = "2024-02-15-preview"; /* The following are legal options for resourceName but you generally will only use cosmos-db: "cosmos-db" | "managedCassandra" | "mongorbac" | "notebook" | "privateEndpointConnection" | "privateLinkResources" | "rbac" | "restorable" | "services" | "dataTransferService" diff --git a/utils/cleanupDBs.js b/utils/cleanupDBs.js index 72fcfbafd..b2bbf0be8 100644 --- a/utils/cleanupDBs.js +++ b/utils/cleanupDBs.js @@ -2,10 +2,7 @@ const msRestNodeAuth = require("@azure/ms-rest-nodeauth"); const { CosmosDBManagementClient } = require("@azure/arm-cosmosdb"); const ms = require("ms"); -const clientId = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_ID"]; -const secret = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET"]; -const tenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47"; -const subscriptionId = "69e02f2d-f059-4409-9eac-97e8a276ae2c"; +const subscriptionId = process.env["AZURE_SUBSCRIPTION_ID"]; const resourceGroupName = "runners"; const thirtyMinutesAgo = new Date(Date.now() - 1000 * 60 * 30).getTime(); @@ -19,7 +16,7 @@ function friendlyTime(date) { } async function main() { - const credentials = await msRestNodeAuth.loginWithServicePrincipalSecret(clientId, secret, tenantId); + const credentials = await msRestNodeAuth.AzureCliCredentials.create(); const client = new CosmosDBManagementClient(credentials, subscriptionId); const accounts = await client.databaseAccounts.list(resourceGroupName); for (const account of accounts) { @@ -38,7 +35,7 @@ async function main() { } else if (account.capabilities.find((c) => c.name === "EnableCassandra")) { const cassandraDatabases = await client.cassandraResources.listCassandraKeyspaces( resourceGroupName, - account.name + account.name, ); for (const database of cassandraDatabases) { const timestamp = Number(database.resource._ts) * 1000; diff --git a/web.config b/web.config index 9d9ff2619..4a967e52a 100644 --- a/web.config +++ b/web.config @@ -30,7 +30,7 @@ - +