Compare commits

..

1 Commits

Author SHA1 Message Date
Tanuj Mittal
9aeb349d74 Sandbox HTML and JavaScript outputs in iFrame 2021-04-02 17:27:17 -07:00
461 changed files with 46519 additions and 42734 deletions

View File

@@ -1,11 +1,11 @@
**/node_modules/
src/**/__mocks__/**/*
dist/
Contracts/
src/Api/Apis.ts
src/AuthType.ts
src/Bindings/BindingHandlersRegisterer.ts
src/Bindings/ReactBindingHandler.ts
src/Common/ArrayHashMap.ts
src/Common/Constants.ts
src/Common/CosmosClient.test.ts
src/Common/CosmosClient.ts
@@ -13,12 +13,15 @@ src/Common/DataAccessUtilityBase.test.ts
src/Common/DataAccessUtilityBase.ts
src/Common/EditableUtility.ts
src/Common/HashMap.test.ts
src/Common/HashMap.ts
src/Common/Logger.test.ts
src/Common/MessageHandler.test.ts
src/Common/MessageHandler.ts
src/Common/MongoProxyClient.test.ts
src/Common/MongoUtility.ts
src/Common/NotificationsClientBase.ts
src/Common/ObjectCache.test.ts
src/Common/ObjectCache.ts
src/Common/QueriesClient.ts
src/Common/Splitter.ts
src/Config.ts
@@ -42,6 +45,7 @@ src/Definitions/jquery.d.ts
src/Definitions/plotly.js-cartesian-dist.d-min.ts
src/Definitions/png.d.ts
src/Definitions/svg.d.ts
src/Definitions/worker.d.ts
src/Explorer/ComponentRegisterer.test.ts
src/Explorer/ComponentRegisterer.ts
src/Explorer/ContextMenuButtonFactory.ts
@@ -87,7 +91,8 @@ src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.test.ts
src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.ts
src/Explorer/Graph/GraphStyleComponent/GraphStyle.test.ts
src/Explorer/Graph/GraphStyleComponent/GraphStyleComponent.ts
src/Explorer/Graph/NewVertexComponent/NewVertex.test.ts
src/Explorer/Graph/NewVertexComponent/NewVertexComponent.ts
src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts
src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.ts
src/Explorer/Menus/ContextMenu.ts
@@ -116,16 +121,24 @@ src/Explorer/Panes/AddDatabasePane.ts
src/Explorer/Panes/BrowseQueriesPane.ts
src/Explorer/Panes/CassandraAddCollectionPane.ts
src/Explorer/Panes/ContextualPaneBase.ts
src/Explorer/Panes/DeleteCollectionConfirmationPane.test.ts
src/Explorer/Panes/DeleteCollectionConfirmationPane.ts
src/Explorer/Panes/DeleteDatabaseConfirmationPane.test.ts
src/Explorer/Panes/DeleteDatabaseConfirmationPane.ts
src/Explorer/Panes/GraphStylingPane.ts
# src/Explorer/Panes/NewVertexPane.ts
src/Explorer/Panes/LoadQueryPane.ts
src/Explorer/Panes/NewVertexPane.ts
src/Explorer/Panes/PaneComponents.ts
src/Explorer/Panes/RenewAdHocAccessPane.ts
src/Explorer/Panes/SaveQueryPane.ts
src/Explorer/Panes/SetupNotebooksPane.ts
src/Explorer/Panes/StringInputPane.ts
src/Explorer/Panes/SwitchDirectoryPane.ts
src/Explorer/Panes/Tables/AddTableEntityPane.ts
src/Explorer/Panes/Tables/EditTableEntityPane.ts
src/Explorer/Panes/Tables/EntityPropertyViewModel.ts
src/Explorer/Panes/Tables/QuerySelectPane.ts
src/Explorer/Panes/Tables/TableColumnOptionsPane.ts
src/Explorer/Panes/Tables/TableEntityPane.ts
src/Explorer/Panes/Tables/Validators/EntityPropertyNameValidator.ts
src/Explorer/Panes/Tables/Validators/EntityPropertyValidationCommon.ts
@@ -241,6 +254,7 @@ src/Utils/QueryUtils.test.ts
src/applyExplorerBindings.ts
src/global.d.ts
src/setupTests.ts
src/workers/upload/index.ts
src/Explorer/Controls/AccessibleElement/AccessibleElement.tsx
src/Explorer/Controls/Accordion/AccordionComponent.tsx
src/Explorer/Controls/AccountSwitch/AccountSwitchComponent.test.tsx
@@ -288,6 +302,8 @@ src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.t
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx
src/Explorer/Menus/CommandBar/CommandBarUtil.tsx
src/Explorer/Menus/NotificationConsole/NotificationConsoleComponent.test.tsx
src/Explorer/Menus/NotificationConsole/NotificationConsoleComponent.tsx
src/Explorer/Menus/NotificationConsole/NotificationConsoleComponentAdapter.tsx
src/Explorer/Notebook/NotebookComponent/NotebookComponent.tsx
src/Explorer/Notebook/NotebookComponent/NotebookComponentAdapter.tsx
src/Explorer/Notebook/NotebookComponent/NotebookComponentBootstrapper.tsx

View File

@@ -3,7 +3,7 @@ module.exports = {
browser: true,
es6: true,
},
plugins: ["@typescript-eslint", "no-null", "prefer-arrow", "react-hooks"],
plugins: ["@typescript-eslint", "no-null", "prefer-arrow"],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
globals: {
Atomics: "readonly",
@@ -11,7 +11,6 @@ module.exports = {
},
parser: "@typescript-eslint/parser",
parserOptions: {
project: ["./tsconfig.json", "./tsconfig.test.json"],
ecmaFeatures: {
jsx: true,
},
@@ -21,7 +20,7 @@ module.exports = {
overrides: [
{
files: ["**/*.tsx"],
extends: ["plugin:react/recommended"],
extends: ["plugin:react/recommended"], // TODO: Add react-hooks
plugins: ["react"],
},
{
@@ -36,7 +35,6 @@ module.exports = {
rules: {
"no-console": ["error", { allow: ["error", "warn", "dir"] }],
curly: "error",
"@typescript-eslint/switch-exhaustiveness-check": "error",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-extraneous-class": "error",
"no-null/no-null": "error",
@@ -44,8 +42,6 @@ module.exports = {
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
eqeqeq: "error",
"react/display-name": "off",
"react-hooks/rules-of-hooks": "warn", // TODO: error
"react-hooks/exhaustive-deps": "warn", // TODO: error
"no-restricted-syntax": [
"error",
{

View File

@@ -1 +0,0 @@
[Preview this branch](https://cosmos-explorer-preview.azurewebsites.net/pull/EDIT_THIS_NUMBER_IN_THE_PR_DESCRIPTION?feature.someFeatureFlagYouMightNeed=true)

View File

@@ -70,6 +70,7 @@ jobs:
- run: npm run test
build:
runs-on: ubuntu-latest
needs: [lint, format, compile, unittest]
name: "Build"
steps:
- uses: actions/checkout@v2
@@ -91,18 +92,9 @@ jobs:
with:
name: dist
path: dist/
- name: Upload build to preview blob storage
run: az storage blob upload-batch -d '$web' -s 'dist' --account-name cosmosexplorerpreview --subscription cosmosdb-portalteam-generaldemo --destination-path "${{github.event.pull_request.head.sha}}" --account-key="${PREVIEW_STORAGE_KEY}"
env:
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
- name: Upload preview config to blob storage
run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --subscription cosmosdb-portalteam-generaldemo --name "${{github.event.pull_request.head.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}"
env:
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
endtoendemulator:
name: "End To End Emulator Tests"
# Temporarily disabled. This test needs to be rewritten in playwright
if: false
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
@@ -127,22 +119,58 @@ jobs:
with:
name: screenshots
path: failed-*
endtoend:
name: "E2E"
accessibility:
name: "Accessibility | Hosted"
needs: [lint, format, compile, unittest]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- name: Accessibility Check
run: |
# Ubuntu gets mad when webpack runs too many files watchers
cat /proc/sys/fs/inotify/max_user_watches
sudo sysctl fs.inotify.max_user_watches=524288
sudo sysctl -p
npm ci
npm start &
npx wait-on -i 5000 https-get://0.0.0.0:1234/
node utils/accesibilityCheck.js
shell: bash
env:
NODE_TLS_REJECT_UNAUTHORIZED: 0
endtoendhosted:
name: "End to End Tests"
needs: [cleanupaccounts]
runs-on: ubuntu-latest
env:
NODE_TLS_REJECT_UNAUTHORIZED: 0
PORTAL_RUNNER_SUBSCRIPTION: ${{ secrets.PORTAL_RUNNER_SUBSCRIPTION }}
PORTAL_RUNNER_RESOURCE_GROUP: ${{ secrets.PORTAL_RUNNER_RESOURCE_GROUP }}
PORTAL_RUNNER_DATABASE_ACCOUNT: ${{ secrets.PORTAL_RUNNER_DATABASE_ACCOUNT }}
PORTAL_RUNNER_DATABASE_ACCOUNT_KEY: ${{ secrets.PORTAL_RUNNER_DATABASE_ACCOUNT_KEY }}
PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT: ${{ secrets.PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT }}
PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT_KEY: ${{ secrets.PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT_KEY }}
NOTEBOOKS_TEST_RUNNER_TENANT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_TENANT_ID }}
NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }}
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
PORTAL_RUNNER_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }}
MONGO_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
CASSANDRA_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_CASSANDRA }}
TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }}
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html"
strategy:
fail-fast: false
matrix:
test-file:
- ./test/cassandra/container.spec.ts
- ./test/graph/container.spec.ts
- ./test/sql/container.spec.ts
- ./test/mongo/container.spec.ts
- ./test/mongo/mongoIndexPolicy.spec.ts
- ./test/notebooks/uploadAndOpenNotebook.spec.ts
- ./test/selfServe/selfServeExample.spec.ts
- ./test/notebooks/upload.spec.ts
- ./test/sql/container.spec.ts
- ./test/sql/resourceToken.spec.ts
- ./test/tables/container.spec.ts
steps:
@@ -153,17 +181,30 @@ jobs:
node-version: 14.x
- run: npm ci
- run: npm start &
- run: node utils/cleanupDBs.js
- run: npm run wait-for-server
- name: ${{ matrix['test-file'] }}
run: |
# Run tests up to three times
for i in $(seq 1 3); do npx jest -c ./jest.config.playwright.js ${{ matrix['test-file'] }} && s=0 && break || s=$? && sleep 1; done; (exit $s)
run: npx jest -c ./jest.config.e2e.js --detectOpenHandles ${{ matrix['test-file'] }}
shell: bash
- uses: actions/upload-artifact@v2
if: failure()
with:
name: screenshots
path: screenshots/
path: failed-*
cleanupaccounts:
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 }}
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- run: npm ci
- run: node utils/cleanupDBs.js
nuget:
name: Publish Nuget
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')

View File

@@ -1,28 +0,0 @@
# This is a basic workflow to help you get started with Actions
name: Cleanup End to End Account Resources
on:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
schedule:
# Once every hour
- cron: "0 * * * *"
# 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"
cleanupaccounts:
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 }}
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- run: npm ci
- run: node utils/cleanupDBs.js

4
.gitignore vendored
View File

@@ -14,6 +14,4 @@ Contracts/*
.DS_Store
.cache/
.env
failure.png
screenshots/*
GettingStarted-ignore*.ipynb
failure.png

View File

@@ -153,7 +153,7 @@ Cosmos Explorer has been under constant development for over 5 years. As a resul
✅ DO
- Use [Playwright](https://github.com/microsoft/playwright) and [Jest](https://jestjs.io/)
- Use [Puppeteer](https://developers.google.com/web/tools/puppeteer) and [Jest](https://jestjs.io/)
- Write or modify an existing E2E test that covers the primary use case of any major feature.
- Use caution. Do not try to cover every case. End to End tests can be slow and brittle.

File diff suppressed because one or more lines are too long

View File

@@ -1,13 +0,0 @@
const isCI = require("is-ci");
module.exports = {
exitOnPageError: false,
launchOptions: {
headless: isCI,
slowMo: 10,
timeout: 60000,
},
contextOptions: {
ignoreHTTPSErrors: true,
},
};

12
jest-puppeteer.config.js Normal file
View File

@@ -0,0 +1,12 @@
const isCI = require("is-ci");
module.exports = {
launch: {
headless: isCI,
slowMo: 55,
defaultViewport: null,
ignoreHTTPSErrors: true,
args: ["--disable-web-security"],
exitOnPageError: false,
},
};

5
jest.config.e2e.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
preset: "jest-puppeteer",
testMatch: ["<rootDir>/test/**/*.spec.[jt]s?(x)"],
setupFiles: ["dotenv/config"],
};

View File

@@ -69,8 +69,8 @@ module.exports = {
moduleNameMapper: {
"^.*[.](svg|png|gif|less|css)$": "<rootDir>/mockModule",
"@nteract/stateful-components/(.*)$": "<rootDir>/mockModule",
"@fluentui/react/lib/(.*)$": "@fluentui/react/lib-commonjs/$1", // https://github.com/microsoft/fluentui/wiki/Version-8-release-notes
"monaco-editor/(.*)$": "<rootDir>/__mocks__/monaco-editor",
"worker-loader": "<rootDir>/mockModule",
"office-ui-fabric-react/lib/(.*)$": "office-ui-fabric-react/lib-commonjs/$1", // https://github.com/OfficeDev/office-ui-fabric-react/wiki/Fabric-6-Release-Notes
"^dnd-core$": "dnd-core/dist/cjs",
"^react-dnd$": "react-dnd/dist/cjs",
"^react-dnd-html5-backend$": "react-dnd-html5-backend/dist/cjs",

View File

@@ -1,7 +0,0 @@
module.exports = {
preset: "jest-playwright-preset",
testMatch: ["<rootDir>/test/**/*.spec.[jt]s?(x)"],
setupFiles: ["dotenv/config"],
testEnvironment: "./test/playwrightEnv.js",
setupFilesAfterEnv: ["expect-playwright"],
};

33189
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,16 +5,15 @@
"main": "index.js",
"dependencies": {
"@azure/arm-cosmosdb": "9.1.0",
"@azure/cosmos": "3.10.5",
"@azure/cosmos": "3.9.0",
"@azure/cosmos-language-service": "0.0.5",
"@azure/identity": "1.2.1",
"@azure/ms-rest-nodeauth": "3.0.7",
"@babel/plugin-proposal-class-properties": "7.12.1",
"@babel/plugin-proposal-decorators": "7.12.12",
"@fluentui/react": "8.10.1",
"@jupyterlab/services": "6.0.2",
"@jupyterlab/terminal": "3.0.3",
"@microsoft/applicationinsights-web": "2.6.1",
"@microsoft/applicationinsights-web": "2.5.9",
"@nteract/commutable": "7.4.2",
"@nteract/connected-components": "6.8.2",
"@nteract/core": "15.1.0",
@@ -26,7 +25,7 @@
"@nteract/iron-icons": "1.0.0",
"@nteract/jupyter-widgets": "2.0.0",
"@nteract/logos": "1.0.0",
"@nteract/markdown": "4.6.0",
"@nteract/markdown": "4.4.0",
"@nteract/monaco-editor": "3.2.2",
"@nteract/octicons": "2.0.0",
"@nteract/outputs": "3.0.9",
@@ -44,6 +43,8 @@
"@types/mkdirp": "1.0.1",
"@types/node-fetch": "2.5.7",
"@uifabric/react-cards": "0.109.110",
"@uifabric/react-hooks": "7.14.0",
"@uifabric/styling": "7.13.7",
"applicationinsights": "1.8.0",
"bootstrap": "3.4.1",
"canvas": "file:./canvas",
@@ -65,7 +66,6 @@
"i18next": "19.8.4",
"i18next-browser-languagedetector": "6.0.1",
"i18next-http-backend": "1.0.23",
"iframe-resizer-react": "1.1.0",
"immutable": "4.0.0-rc.12",
"is-ci": "2.0.0",
"jquery": "3.5.1",
@@ -76,9 +76,9 @@
"monaco-editor": "0.18.1",
"ms": "2.1.3",
"msal": "1.4.4",
"office-ui-fabric-react": "7.164.2",
"p-retry": "4.2.0",
"plotly.js-cartesian-dist-min": "1.52.3",
"post-robot": "10.0.42",
"q": "1.5.1",
"react": "16.13.1",
"react-animate-height": "2.0.8",
@@ -93,7 +93,6 @@
"reflect-metadata": "0.1.13",
"rx-jupyter": "5.5.12",
"rxjs": "6.6.3",
"sanitize-html": "2.3.3",
"styled-components": "4.3.2",
"swr": "0.4.0",
"terser-webpack-plugin": "3.1.0",
@@ -112,23 +111,26 @@
"@types/d3": "5.9.2",
"@types/enzyme": "3.10.7",
"@types/enzyme-adapter-react-16": "1.0.6",
"@types/expect-puppeteer": "4.4.5",
"@types/hasher": "0.0.31",
"@types/jest": "26.0.20",
"@types/jest-environment-puppeteer": "4.4.1",
"@types/memoize-one": "4.1.1",
"@types/node": "12.11.1",
"@types/post-robot": "10.0.1",
"@types/promise.prototype.finally": "2.0.3",
"@types/prop-types": "15.5.8",
"@types/puppeteer": "5.4.3",
"@types/q": "1.5.1",
"@types/react": "17.0.3",
"@types/react-dom": "17.0.3",
"@types/react": "17.0.0",
"@types/react-dom": "17.0.0",
"@types/react-notification-system": "0.2.39",
"@types/react-redux": "7.1.7",
"@types/sanitize-html": "1.27.2",
"@types/sinon": "2.3.3",
"@types/styled-components": "5.1.1",
"@types/underscore": "1.7.36",
"@typescript-eslint/eslint-plugin": "4.22.0",
"@typescript-eslint/parser": "4.22.0",
"@typescript-eslint/eslint-plugin": "4.0.1",
"@typescript-eslint/parser": "4.0.1",
"axe-puppeteer": "1.1.0",
"babel-jest": "24.9.0",
"babel-loader": "8.1.0",
"buffer": "5.1.0",
@@ -143,18 +145,16 @@
"eslint-plugin-no-null": "1.0.2",
"eslint-plugin-prefer-arrow": "1.2.2",
"eslint-plugin-react-hooks": "4.2.0",
"expect-playwright": "0.3.3",
"expose-loader": "0.7.5",
"fast-glob": "3.2.5",
"file-loader": "2.0.0",
"fs-extra": "7.0.0",
"html-inline-css-webpack-plugin": "1.11.0",
"html-loader": "0.5.5",
"html-loader-jest": "0.2.1",
"html-webpack-plugin": "4.5.2",
"html-webpack-plugin": "3.2.0",
"jest": "25.5.4",
"jest-canvas-mock": "2.1.0",
"jest-playwright-preset": "1.5.1",
"jest-puppeteer": "4.4.0",
"jest-trx-results-processor": "0.0.7",
"less": "3.8.1",
"less-loader": "4.1.0",
@@ -162,23 +162,23 @@
"mini-css-extract-plugin": "0.4.3",
"monaco-editor-webpack-plugin": "1.7.0",
"node-fetch": "2.6.1",
"playwright": "1.10.0",
"prettier": "2.2.1",
"puppeteer": "8.0.0",
"raw-loader": "0.5.1",
"react-dev-utils": "11.0.4",
"rimraf": "3.0.0",
"sinon": "3.2.1",
"style-loader": "0.23.0",
"ts-loader": "6.2.2",
"tslint": "5.11.0",
"tslint-microsoft-contrib": "6.0.0",
"typescript": "4.2.4",
"typescript": "4.0.2",
"url-loader": "1.1.1",
"wait-on": "4.0.2",
"webpack": "4.46.0",
"webpack": "4.43.0",
"webpack-bundle-analyzer": "3.6.1",
"webpack-cli": "3.3.10",
"webpack-dev-server": "3.11.0"
"webpack-dev-server": "3.11.0",
"worker-loader": "2.0.0"
},
"scripts": {
"start": "node --max-old-space-size=10196 node_modules/webpack-dev-server/bin/webpack-dev-server.js",
@@ -190,7 +190,7 @@
"pack:fast": "node --max_old_space_size=10196 ./node_modules/webpack/bin/webpack.js --mode development --progress",
"copyToConsumers": "node copyToConsumers",
"test": "rimraf coverage && jest",
"test:e2e": "jest -c ./jest.config.playwright.js --detectOpenHandles",
"test:e2e": "jest -c ./jest.config.e2e.js --detectOpenHandles",
"watch": "npm run start",
"wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/",
"build:ase": "gulp build:ase",
@@ -201,8 +201,8 @@
"format:check": "prettier --check \"{src,test}/**/*.{ts,tsx,html}\" \"*.{js,html}\"",
"lint": "tslint --project tsconfig.json && eslint \"**/*.{ts,tsx}\"",
"build:contracts": "npm run compile:contracts",
"strict:find": "node ./strict-null-checks/find.js",
"strict:add": "node ./strict-null-checks/auto-add.js",
"strictEligibleFiles": "node ./strict-migration-tools/index.js",
"autoAddStrictEligibleFiles": "node ./strict-migration-tools/autoAdd.js",
"compile:fullStrict": "tsc -p ./tsconfig.json --strictNullChecks",
"generateARMClients": "ts-node --compiler-options '{\"module\":\"commonjs\"}' utils/armClientGenerator/generator.ts"
},

View File

@@ -1,7 +0,0 @@
[defaults]
group = stfaul
sku = P1v2
appserviceplan = stfaul_asp_Linux_centralus_0
location = centralus
web = cosmos-explorer-preview

View File

@@ -1,20 +0,0 @@
# Cosmos Explorer Preview
Cosmos Explorer Preview makes it possible to try a working version of any commit on master or in a PR. No need to run the app locally or deploy to staging.
Initial support is for Hosted (Connection string only) or the Azure Portal. Examples:
Connection string URLs: https://cosmos-explorer-preview.azurewebsites.net/commit/COMMIT_SHA/hostedExplorer.html
Portal URLs: https://ms.portal.azure.com/?dataExplorerSource=https://cosmos-explorer-preview.azurewebsites.net/commit/COMMIT_SHA/explorer.html#home
In both cases replace `COMMIT_SHA` with the commit you want to view. It must have already completed its build on GitHub Actions.
### Architechture
- This folder contains a NodeJS app deployed to Azure App Service that powers preview URLs:
- Paths starting with `/commit/` are proxied to an Azure Storage account containing build artifacts
- Paths starting with `/proxy/` are proxied dynamically to Cosmos account endpoints. Required otherwise CORS would need to be configured for every account accessed.
- Paths starting with `/api/` are proxied to Portal APIs that do not support CORS.
- On GitHub Actions build completion:
- All files in dist are uploaded to an Azure Storage account namespaced by the SHA of the commit
- `/preview/config.json` is uploaded to the same folder with preview specific configuration

View File

@@ -1,3 +0,0 @@
{
"PROXY_PATH": "/proxy"
}

View File

@@ -1,68 +0,0 @@
const express = require("express");
const { createProxyMiddleware } = require("http-proxy-middleware");
const port = process.env.PORT || 3000;
const fetch = require("node-fetch");
const api = createProxyMiddleware("/api", {
target: "https://main.documentdb.ext.azure.com",
changeOrigin: true,
logLevel: "debug",
bypass: (req, res) => {
if (req.method === "OPTIONS") {
res.statusCode = 200;
res.send();
}
},
});
const proxy = createProxyMiddleware("/proxy", {
target: "https://main.documentdb.ext.azure.com",
changeOrigin: true,
secure: false,
logLevel: "debug",
pathRewrite: { "^/proxy": "" },
router: (req) => {
let newTarget = req.headers["x-ms-proxy-target"];
return newTarget;
},
});
const commit = createProxyMiddleware("/commit", {
target: "https://cosmosexplorerpreview.blob.core.windows.net",
changeOrigin: true,
secure: false,
logLevel: "debug",
pathRewrite: { "^/commit": "$web/" },
});
const app = express();
app.use(api);
app.use(proxy);
app.use(commit);
app.get("/pull/:pr(\\d+)", (req, res) => {
const pr = req.params.pr;
const [, query] = req.originalUrl.split("?");
const search = new URLSearchParams(query);
fetch("https://api.github.com/repos/Azure/cosmos-explorer/pulls/" + pr)
.then((response) => response.json())
.then(({ head: { ref, sha } }) => {
const prUrl = new URL("https://github.com/Azure/cosmos-explorer/pull/" + pr);
prUrl.hash = ref;
search.set("feature.pr", prUrl.href);
const explorer = new URL("https://cosmos-explorer-preview.azurewebsites.net/commit/" + sha + "/explorer.html");
explorer.search = search.toString();
const portal = new URL("https://ms.portal.azure.com/");
portal.searchParams.set("dataExplorerSource", explorer.href);
return res.redirect(portal.href);
})
.catch(() => res.sendStatus(500));
});
app.listen(port, () => {
console.log(`Example app listening on port: ${port}`);
});

1146
preview/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +0,0 @@
{
"name": "cosmos-explorer-preview",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"deploy": "az webapp up -n cosmos-explorer-preview --subscription cosmosdb-portalteam-generaldemo -g stfaul",
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "Microsoft Corporation",
"dependencies": {
"express": "^4.17.1",
"http-proxy-middleware": "^1.1.0",
"node-fetch": "^2.6.1"
}
}

View File

@@ -22,7 +22,13 @@ export interface ReactAdapter {
export class Registerer {
public static register(): void {
ko.bindingHandlers.react = {
init: (element: any, wrappedValueAccessor: () => any) => {
init: (
element: any,
wrappedValueAccessor: () => any,
allBindings?: ko.AllBindings,
viewModel?: any,
bindingContext?: ko.BindingContext
) => {
const adapter: ReactAdapter = wrappedValueAccessor();
if (adapter.setElement) {

View File

@@ -1,10 +0,0 @@
.schema-analyzer-cell-outputs {
padding: 10px;
}
.schema-analyzer-cell-output {
margin-bottom: 20px;
padding: 10px;
border-radius: 2px;
box-shadow: rgba(0, 0, 0, 13%) 0px 1.6px 3.6px 0px, rgba(0, 0, 0, 11%) 0px 0.3px 0.9px 0px;
}

View File

@@ -1,68 +0,0 @@
import { createImmutableOutput, JSONObject, OnDiskOutput } from "@nteract/commutable";
// import outputs individually to avoid increasing the bundle size
import { KernelOutputError } from "@nteract/outputs/lib/components/kernel-output-error";
import { Output } from "@nteract/outputs/lib/components/output";
import { StreamText } from "@nteract/outputs/lib/components/stream-text";
import { ContentRef } from "@nteract/types";
import "bootstrap/dist/css/bootstrap.css";
import postRobot from "post-robot";
import * as React from "react";
import * as ReactDOM from "react-dom";
import "../../externals/iframeResizer.contentWindow.min.js"; // Required for iFrameResizer to work
import "../Explorer/Notebook/NotebookRenderer/base.css";
import "../Explorer/Notebook/NotebookRenderer/default.css";
import "./CellOutputViewer.less";
import { TransformMedia } from "./TransformMedia";
export interface CellOutputViewerProps {
id: string;
contentRef: ContentRef;
outputsContainerClassName: string;
outputClassName: string;
outputs: OnDiskOutput[];
onMetadataChange: (metadata: JSONObject, mediaType: string, index?: number) => void;
}
const onInit = async () => {
postRobot.on(
"props",
{
window: window.parent,
domain: window.location.origin,
},
(event) => {
// Typescript definition for event is wrong. So read props by casting to <any>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const props = (event as any).data as CellOutputViewerProps;
const outputs = (
<div data-iframe-height className={props.outputsContainerClassName}>
{props.outputs?.map((output, index) => (
<div className={props.outputClassName} key={index}>
<Output output={createImmutableOutput(output)} key={index}>
<TransformMedia
output_type={"display_data"}
id={props.id}
contentRef={props.contentRef}
onMetadataChange={(metadata, mediaType) => props.onMetadataChange(metadata, mediaType, index)}
/>
<TransformMedia
output_type={"execute_result"}
id={props.id}
contentRef={props.contentRef}
onMetadataChange={(metadata, mediaType) => props.onMetadataChange(metadata, mediaType, index)}
/>
<KernelOutputError />
<StreamText />
</Output>
</div>
))}
</div>
);
ReactDOM.render(outputs, document.getElementById("cellOutput"));
}
);
};
// Entry point
window.addEventListener("load", onInit);

View File

@@ -1,138 +0,0 @@
import { ImmutableDisplayData, ImmutableExecuteResult, JSONObject } from "@nteract/commutable";
// import outputs individually to avoid increasing the bundle size
import { HTML } from "@nteract/outputs/lib/components/media/html";
import { Image } from "@nteract/outputs/lib/components/media/image";
import { JavaScript } from "@nteract/outputs/lib/components/media/javascript";
import { Json } from "@nteract/outputs/lib/components/media/json";
import { LaTeX } from "@nteract/outputs/lib/components/media/latex";
import { Plain } from "@nteract/outputs/lib/components/media/plain";
import { SVG } from "@nteract/outputs/lib/components/media/svg";
import { ContentRef } from "@nteract/types";
import React, { Suspense } from "react";
const EmptyTransform = (): JSX.Element => <></>;
const displayOrder = [
"application/vnd.jupyter.widget-view+json",
"application/vnd.vega.v5+json",
"application/vnd.vega.v4+json",
"application/vnd.vega.v3+json",
"application/vnd.vega.v2+json",
"application/vnd.vegalite.v4+json",
"application/vnd.vegalite.v3+json",
"application/vnd.vegalite.v2+json",
"application/vnd.vegalite.v1+json",
"application/geo+json",
"application/vnd.plotly.v1+json",
"text/vnd.plotly.v1+html",
"application/x-nteract-model-debug+json",
"application/vnd.dataresource+json",
"application/vdom.v1+json",
"application/json",
"application/javascript",
"text/html",
"text/markdown",
"text/latex",
"image/svg+xml",
"image/gif",
"image/png",
"image/jpeg",
"text/plain",
];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const transformsById = new Map<string, React.ComponentType<any>>([
["text/vnd.plotly.v1+html", React.lazy(() => import("@nteract/transform-plotly"))],
["application/vnd.plotly.v1+json", React.lazy(() => import("@nteract/transform-plotly"))],
["application/geo+json", EmptyTransform], // TODO: The geojson transform will likely need some work because of the basemap URL(s)
["application/x-nteract-model-debug+json", React.lazy(() => import("@nteract/transform-model-debug"))],
["application/vnd.dataresource+json", React.lazy(() => import("@nteract/data-explorer"))],
["application/vnd.jupyter.widget-view+json", React.lazy(() => import("./transforms/WidgetDisplay"))],
["application/vnd.vegalite.v1+json", React.lazy(() => import("./transforms/VegaLite1"))],
["application/vnd.vegalite.v2+json", React.lazy(() => import("./transforms/VegaLite2"))],
["application/vnd.vegalite.v3+json", React.lazy(() => import("./transforms/VegaLite3"))],
["application/vnd.vegalite.v4+json", React.lazy(() => import("./transforms/VegaLite4"))],
["application/vnd.vega.v2+json", React.lazy(() => import("./transforms/Vega2"))],
["application/vnd.vega.v3+json", React.lazy(() => import("./transforms/Vega3"))],
["application/vnd.vega.v4+json", React.lazy(() => import("./transforms/Vega4"))],
["application/vnd.vega.v5+json", React.lazy(() => import("./transforms/Vega5"))],
["application/vdom.v1+json", React.lazy(() => import("@nteract/transform-vdom"))],
["application/json", Json],
["application/javascript", JavaScript],
["text/html", HTML],
["text/markdown", React.lazy(() => import("@nteract/outputs/lib/components/media/markdown"))], // Markdown increases the bundle size so lazy load it
["text/latex", LaTeX],
["image/svg+xml", SVG],
["image/gif", Image],
["image/png", Image],
["image/jpeg", Image],
["text/plain", Plain],
]);
interface TransformMediaProps {
output_type: string;
id: string;
contentRef: ContentRef;
output?: ImmutableDisplayData | ImmutableExecuteResult;
onMetadataChange: (metadata: JSONObject, mediaType: string) => void;
}
export const TransformMedia = (props: TransformMediaProps): JSX.Element => {
const { Media, mediaType, data, metadata } = getMediaInfo(props);
// If we had no valid result, return an empty output
if (!mediaType || !data) {
return <></>;
}
return (
<Suspense fallback={<div>Loading...</div>}>
<Media
onMetadataChange={props.onMetadataChange}
data={data}
metadata={metadata}
contentRef={props.contentRef}
id={props.id}
/>
</Suspense>
);
};
const getMediaInfo = (props: TransformMediaProps) => {
const { output, output_type } = props;
// This component should only be used with display data and execute result
if (!output || !(output_type === "display_data" || output_type === "execute_result")) {
console.warn("connected transform media managed to get a non media bundle output");
return {
Media: EmptyTransform,
};
}
// Find the first mediaType in the output data that we support with a handler
const mediaType = displayOrder.find(
(key) =>
Object.prototype.hasOwnProperty.call(output.data, key) &&
(Object.prototype.hasOwnProperty.call(transformsById, key) || transformsById.get(key))
);
if (mediaType) {
const metadata = output.metadata.get(mediaType);
const data = output.data[mediaType];
const Media = transformsById.get(mediaType);
return {
Media,
mediaType,
data,
metadata,
};
}
return {
Media: EmptyTransform,
mediaType,
output,
};
};
export default TransformMedia;

View File

@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="height=device-height, width=device-width, initial-scale=1.0" />
<title>Cell Output Viewer</title>
</head>
<body>
<div class="cellOutput" id="cellOutput"></div>
</body>
</html>

View File

@@ -1 +0,0 @@
export { Vega2 as default } from "@nteract/transform-vega";

View File

@@ -1 +0,0 @@
export { Vega3 as default } from "@nteract/transform-vega";

View File

@@ -1 +0,0 @@
export { Vega4 as default } from "@nteract/transform-vega";

View File

@@ -1 +0,0 @@
export { Vega5 as default } from "@nteract/transform-vega";

View File

@@ -1 +0,0 @@
export { VegaLite1 as default } from "@nteract/transform-vega";

View File

@@ -1 +0,0 @@
export { VegaLite2 as default } from "@nteract/transform-vega";

View File

@@ -1 +0,0 @@
export { VegaLite3 as default } from "@nteract/transform-vega";

View File

@@ -1 +0,0 @@
export { VegaLite4 as default } from "@nteract/transform-vega";

View File

@@ -1 +0,0 @@
export { WidgetDisplay as default } from "@nteract/jupyter-widgets";

View File

@@ -1,9 +1,49 @@
import { HashMap } from "./HashMap";
/**
* Hash map of arrays which allows to:
* - push an item by key: add to array and create array if needed
* - remove item by key: remove from array and delete array if needed
*/
export class ArrayHashMap<T> extends Map<string, T[]> {
export class ArrayHashMap<T> {
private store: HashMap<T[]>;
constructor() {
this.store = new HashMap();
}
public has(key: string): boolean {
return this.store.has(key);
}
public get(key: string): T[] {
return this.store.get(key);
}
public size(): number {
return this.store.size();
}
public clear(): void {
this.store.clear();
}
public keys(): string[] {
return this.store.keys();
}
public delete(key: string): boolean {
return this.store.delete(key);
}
public forEach(key: string, iteratorFct: (value: T) => void) {
const values = this.store.get(key);
if (values) {
values.forEach((value) => iteratorFct(value));
}
}
/**
* Insert item into array.
* If no array, create one.
@@ -12,8 +52,16 @@ export class ArrayHashMap<T> extends Map<string, T[]> {
* @param item
*/
public push(key: string, item: T): void {
const array = this.get(key);
array ? array.includes(item) || array.push(item) : this.set(key, [item]);
let itemsArray: T[] = this.store.get(key);
if (!itemsArray) {
itemsArray = [item];
this.store.set(key, itemsArray);
return;
}
if (itemsArray.indexOf(item) === -1) {
itemsArray.push(item);
}
}
/**
@@ -22,11 +70,18 @@ export class ArrayHashMap<T> extends Map<string, T[]> {
* @param key
* @param itemToRemove
*/
public remove(key: string, itemToRemove: T): void {
const array = this.get(key);
if (array) {
const remaining = array.filter((item) => item !== itemToRemove);
remaining.length ? this.set(key, remaining) : this.delete(key);
public remove(key: string, itemToRemove: T) {
if (!this.store.has(key)) {
return;
}
const itemsArray = this.store.get(key);
const index = itemsArray.indexOf(itemToRemove);
if (index >= 0) {
itemsArray.splice(index, 1);
if (itemsArray.length === 0) {
this.store.delete(key);
}
}
}
}

View File

@@ -65,18 +65,28 @@ export class ClientDefaults {
public static readonly arcadiaTokenRefreshIntervalPaddingMs: number = 2000;
}
export enum AccountKind {
DocumentDB = "DocumentDB",
MongoDB = "MongoDB",
Parse = "Parse",
GlobalDocumentDB = "GlobalDocumentDB",
Default = "DocumentDB",
export class AccountKind {
public static DocumentDB: string = "DocumentDB";
public static MongoDB: string = "MongoDB";
public static Parse: string = "Parse";
public static GlobalDocumentDB: string = "GlobalDocumentDB";
public static Default: string = AccountKind.DocumentDB;
}
export class CorrelationBackend {
public static Url: string = "https://aka.ms/cosmosdbanalytics";
}
export class DefaultAccountExperience {
public static DocumentDB: string = "DocumentDB";
public static Graph: string = "Graph";
public static MongoDB: string = "MongoDB";
public static ApiForMongoDB: string = "Azure Cosmos DB for MongoDB API";
public static Table: string = "Table";
public static Cassandra: string = "Cassandra";
public static Default: string = DefaultAccountExperience.DocumentDB;
}
export class CapabilityNames {
public static EnableTable: string = "EnableTable";
public static EnableGremlin: string = "EnableGremlin";

View File

@@ -1,5 +1,5 @@
import { ResourceType } from "@azure/cosmos/dist-esm/common/constants";
import { Platform, resetConfigContext, updateConfigContext } from "../ConfigContext";
import { configContext, Platform, updateConfigContext, resetConfigContext } from "../ConfigContext";
import { updateUserContext } from "../UserContext";
import { endpoint, getTokenFromAuthService, requestPlugin, tokenProvider } from "./CosmosClient";
@@ -91,6 +91,7 @@ describe("endpoint", () => {
location: "foo",
type: "foo",
kind: "foo",
tags: [],
properties: {
documentEndpoint: "bar",
gremlinEndpoint: "foo",

View File

@@ -32,7 +32,7 @@ export const tokenProvider = async (requestInfo: RequestInfo) => {
};
export const requestPlugin: Cosmos.Plugin<any> = async (requestContext, next) => {
requestContext.endpoint = new URL(configContext.PROXY_PATH, window.location.href).href;
requestContext.endpoint = configContext.PROXY_PATH;
requestContext.headers["x-ms-proxy-target"] = endpoint();
return next(requestContext);
};
@@ -43,7 +43,12 @@ export const endpoint = () => {
const location = _global.parent ? _global.parent.location : _global.location;
return configContext.EMULATOR_ENDPOINT || location.origin;
}
return userContext.endpoint || userContext?.databaseAccount?.properties?.documentEndpoint;
return (
userContext.endpoint ||
(userContext.databaseAccount &&
userContext.databaseAccount.properties &&
userContext.databaseAccount.properties.documentEndpoint)
);
};
export async function getTokenFromAuthService(verb: string, resourceType: string, resourceId?: string): Promise<any> {
@@ -70,10 +75,7 @@ export async function getTokenFromAuthService(verb: string, resourceType: string
}
}
let _client: Cosmos.CosmosClient;
export function client(): Cosmos.CosmosClient {
if (_client) return _client;
const options: Cosmos.CosmosClientOptions = {
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
key: userContext.masterKey,
@@ -87,6 +89,5 @@ export function client(): Cosmos.CosmosClient {
if (configContext.PROXY_PATH !== undefined) {
(options as any).plugins = [{ on: "request", plugin: requestPlugin }];
}
_client = new Cosmos.CosmosClient(options);
return _client;
return new Cosmos.CosmosClient(options);
}

View File

@@ -1,7 +1,8 @@
import { DefaultAccountExperienceType } from "../DefaultAccountExperienceType";
import { userContext } from "../UserContext";
export const getEntityName = (): string => {
if (userContext.apiType === "Mongo") {
if (userContext.defaultExperience === DefaultAccountExperienceType.MongoDB) {
return "document";
}

View File

@@ -1,66 +0,0 @@
import { DatePicker, TextField } from "@fluentui/react";
import React, { FunctionComponent } from "react";
export interface TableEntityProps {
entityValueLabel?: string;
entityValuePlaceholder: string;
entityValue: string | Date;
isEntityTypeDate: boolean;
isEntityValueDisable?: boolean;
entityTimeValue: string;
entityValueType: string;
onEntityValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
onSelectDate: (date: Date | null | undefined) => void;
onEntityTimeValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
}
export const EntityValue: FunctionComponent<TableEntityProps> = ({
entityValueLabel,
entityValuePlaceholder,
entityValue,
isEntityTypeDate,
entityTimeValue,
entityValueType,
onEntityValueChange,
onSelectDate,
isEntityValueDisable,
onEntityTimeValueChange,
}: TableEntityProps): JSX.Element => {
if (isEntityTypeDate) {
return (
<>
<DatePicker
className="addEntityDatePicker"
placeholder={entityValuePlaceholder}
value={entityValue && new Date(entityValue)}
ariaLabel={entityValuePlaceholder}
onSelectDate={onSelectDate}
disabled={isEntityValueDisable}
/>
<TextField
label={entityValueLabel && entityValueLabel}
id="entityTimeId"
autoFocus
type="time"
value={entityTimeValue}
onChange={onEntityTimeValueChange}
disabled={isEntityValueDisable}
/>
</>
);
}
return (
<TextField
label={entityValueLabel && entityValueLabel}
className="addEntityTextField"
id="entityValueId"
autoFocus
disabled={isEntityValueDisable}
type={entityValueType}
placeholder={entityValuePlaceholder}
value={typeof entityValue === "string" && entityValue}
onChange={onEntityValueChange}
/>
);
};

View File

@@ -0,0 +1,70 @@
import { HashMap } from "./HashMap";
describe("HashMap", () => {
it("should test if key/val exists", () => {
const map = new HashMap<number>();
map.set("a", 123);
expect(map.has("a")).toBe(true);
expect(map.has("b")).toBe(false);
});
it("should get object back", () => {
const map = new HashMap<string>();
map.set("a", "123");
map.set("a", "456");
expect(map.get("a")).toBe("456");
expect(map.get("a")).not.toBe("123");
});
it("should return the right size", () => {
const map = new HashMap<string>();
map.set("a", "123");
map.set("b", "456");
expect(map.size()).toBe(2);
});
it("should be iterable", () => {
const map = new HashMap<number>();
map.set("a", 1);
map.set("b", 10);
map.set("c", 100);
map.set("d", 1000);
let i = 0;
map.forEach((key: string, value: number) => {
i += value;
});
expect(i).toBe(1111);
});
it("should be deleted", () => {
const map = new HashMap<number>();
map.set("a", 1);
map.set("b", 10);
expect(map.delete("a")).toBe(true);
expect(map.delete("c")).toBe(false);
expect(map.has("a")).toBe(false);
expect(map.has("b")).toBe(true);
});
it("should clear", () => {
const map = new HashMap<number>();
map.set("a", 1);
map.clear();
expect(map.size()).toBe(0);
expect(map.has("a")).toBe(false);
});
it("should return all keys", () => {
const map = new HashMap<number>();
map.set("a", 1);
map.set("b", 1);
expect(map.keys()).toEqual(["a", "b"]);
map.clear();
expect(map.keys().length).toBe(0);
});
});

45
src/Common/HashMap.ts Normal file
View File

@@ -0,0 +1,45 @@
/**
* Simple hashmap implementation that doesn't rely on ES6 Map nor polyfills
*/
export class HashMap<T> {
constructor(private container: { [key: string]: T } = {}) {}
public has(key: string): boolean {
return this.container.hasOwnProperty(key);
}
public set(key: string, value: T): void {
this.container[key] = value;
}
public get(key: string): T {
return this.container[key];
}
public size(): number {
return Object.keys(this.container).length;
}
public delete(key: string): boolean {
if (this.has(key)) {
delete this.container[key];
return true;
}
return false;
}
public clear(): void {
this.container = {};
}
public keys(): string[] {
return Object.keys(this.container);
}
public forEach(iteratorFct: (key: string, value: T) => void) {
for (const k in this.container) {
iteratorFct(k, this.container[k]);
}
}
}

View File

@@ -1,7 +1,7 @@
import { SeverityLevel } from "@microsoft/applicationinsights-web";
import { Diagnostics, MessageTypes } from "../Contracts/ExplorerContracts";
import { trackTrace } from "../Shared/appInsights";
import { sendMessage } from "./MessageHandler";
import { Diagnostics, MessageTypes } from "../Contracts/ExplorerContracts";
import { appInsights } from "../Shared/appInsights";
import { SeverityLevel } from "@microsoft/applicationinsights-web";
// TODO: Move to a separate Diagnostics folder
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -46,7 +46,7 @@ function _logEntry(entry: Diagnostics.LogEntry): void {
return SeverityLevel.Information;
}
})(entry.level);
trackTrace({ message: entry.message, severityLevel }, { area: entry.area });
appInsights.trackTrace({ message: entry.message, severityLevel }, { area: entry.area });
}
function _generateLogEntry(

View File

@@ -48,18 +48,32 @@ export function sendCachedDataMessage<TResponseDataModel>(
}
export function sendMessage(data: any): void {
_sendMessage({
signature: "pcIframe",
data: data,
});
if (canSendMessage()) {
// We try to find data explorer window first, then fallback to current window
const portalChildWindow = getDataExplorerWindow(window) || window;
portalChildWindow.parent.postMessage(
{
signature: "pcIframe",
data: data,
},
portalChildWindow.document.referrer
);
}
}
export function sendReadyMessage(): void {
_sendMessage({
signature: "pcIframe",
kind: "ready",
data: "ready",
});
if (canSendMessage()) {
// We try to find data explorer window first, then fallback to current window
const portalChildWindow = getDataExplorerWindow(window) || window;
portalChildWindow.parent.postMessage(
{
signature: "pcIframe",
kind: "ready",
data: "ready",
},
portalChildWindow.document.referrer
);
}
}
export function canSendMessage(): boolean {
@@ -75,17 +89,3 @@ export function runGarbageCollector() {
}
});
}
const _sendMessage = (message: any): void => {
if (canSendMessage()) {
// Portal window can receive messages from only child windows
const portalChildWindow = getDataExplorerWindow(window) || window;
if (portalChildWindow === window) {
// Current window is a child of portal, send message to portal window
portalChildWindow.parent.postMessage(message, portalChildWindow.document.referrer || "*");
} else {
// Current window is not a child of portal, send message to the child window instead (which is data explorer)
portalChildWindow.postMessage(message, portalChildWindow.location.origin || "*");
}
}
};

View File

@@ -5,10 +5,11 @@ import { configContext } from "../ConfigContext";
import * as DataModels from "../Contracts/DataModels";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { Collection } from "../Contracts/ViewModels";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import DocumentId from "../Explorer/Tree/DocumentId";
import { userContext } from "../UserContext";
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
import { userContext } from "../UserContext";
import { MinimalQueryIterator } from "./IteratorUtilities";
import { sendMessage } from "./MessageHandler";
@@ -61,7 +62,7 @@ export function queryDocuments(
query: string,
continuationToken?: string
): Promise<QueryResponse> {
const { databaseAccount } = userContext;
const databaseAccount = userContext.databaseAccount;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const params = {
db: databaseId,
@@ -121,7 +122,7 @@ export function readDocument(
collection: Collection,
documentId: DocumentId
): Promise<DataModels.DocumentId> {
const { databaseAccount } = userContext;
const databaseAccount = userContext.databaseAccount;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const idComponents = documentId.self.split("/");
const path = idComponents.slice(0, 4).join("/");
@@ -167,7 +168,7 @@ export function createDocument(
partitionKeyProperty: string,
documentContent: unknown
): Promise<DataModels.DocumentId> {
const { databaseAccount } = userContext;
const databaseAccount = userContext.databaseAccount;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const params = {
db: databaseId,
@@ -206,7 +207,7 @@ export function updateDocument(
documentId: DocumentId,
documentContent: string
): Promise<DataModels.DocumentId> {
const { databaseAccount } = userContext;
const databaseAccount = userContext.databaseAccount;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const idComponents = documentId.self.split("/");
const path = idComponents.slice(0, 5).join("/");
@@ -247,7 +248,7 @@ export function updateDocument(
}
export function deleteDocument(databaseId: string, collection: Collection, documentId: DocumentId): Promise<void> {
const { databaseAccount } = userContext;
const databaseAccount = userContext.databaseAccount;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const idComponents = documentId.self.split("/");
const path = idComponents.slice(0, 5).join("/");
@@ -289,7 +290,7 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
export function createMongoCollectionWithProxy(
params: DataModels.CreateCollectionParams
): Promise<DataModels.Collection> {
const { databaseAccount } = userContext;
const databaseAccount = userContext.databaseAccount;
const shardKey: string = params.partitionKey?.paths[0];
const mongoParams: DataModels.MongoParameters = {
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
@@ -347,7 +348,10 @@ export function getEndpoint(): string {
async function errorHandling(response: Response, action: string, params: unknown): Promise<void> {
const errorMessage = await response.text();
// Log the error where the user can see it
logConsoleError(`Error ${action}: ${errorMessage}, Payload: ${JSON.stringify(params)}`);
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error ${action}: ${errorMessage}, Payload: ${JSON.stringify(params)}`
);
if (response.status === HttpStatusCodes.Forbidden) {
sendMessage({ type: MessageTypes.ForbiddenError, reason: errorMessage });
return;

View File

@@ -7,7 +7,7 @@ describe("Object cache", () => {
cache.set("b", 2);
cache.set("c", 3);
cache.set("d", 4);
expect(cache.size).toBe(2);
expect(cache.size()).toBe(2);
});
it("should remove first added element to keep size at limit", () => {

View File

@@ -1,27 +1,56 @@
export class ObjectCache<T> extends Map<string, T> {
constructor(private limit: number) {
import { HashMap } from "./HashMap";
export class ObjectCache<T> extends HashMap<T> {
private keyQueue: string[]; // Last touched key FIFO to purge cache if too big.
private maxNbElements: number;
public constructor(maxNbElements: number) {
super();
this.keyQueue = [];
this.maxNbElements = maxNbElements;
this.clear();
}
public get(key: string): T | undefined {
return this.touch(key);
public clear(): void {
super.clear();
this.keyQueue = [];
}
public set(key: string, value: T): this {
if (this.size === this.limit) {
this.delete(this.keys().next().value);
public get(key: string): T {
this.markKeyAsTouched(key);
return super.get(key);
}
public set(key: string, value: T): void {
super.set(key, value);
this.markKeyAsTouched(key);
if (super.size() > this.maxNbElements && key !== this.keyQueue[0]) {
this.reduceCacheSize();
}
return this.touch(key, value), this;
}
private touch(key: string, value = super.get(key)) {
// Map keeps (re) insertion order according to ES6 spec
if (value) {
this.delete(key);
super.set(key, value);
/**
* Invalidate elements to keep the total number below the limit
*/
private reduceCacheSize(): void {
// remove a key
const oldKey = this.keyQueue.shift();
if (oldKey) {
super.delete(oldKey);
}
}
return value;
/**
* Bubble up this key as new.
* @param key
*/
private markKeyAsTouched(key: string) {
const n = this.keyQueue.indexOf(key);
if (n > -1) {
this.keyQueue.splice(n, 1);
}
this.keyQueue.push(key);
}
}

View File

@@ -1,8 +1,8 @@
import { configContext, Platform } from "../ConfigContext";
import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels";
import { userContext } from "../UserContext";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { userContext } from "../UserContext";
import { configContext, Platform } from "../ConfigContext";
const notificationsPath = () => {
switch (configContext.platform) {
@@ -20,7 +20,9 @@ export const fetchPortalNotifications = async (): Promise<DataModels.Notificatio
return [];
}
const { databaseAccount, resourceGroup, subscriptionId } = userContext;
const databaseAccount = userContext.databaseAccount;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const url = `${configContext.BACKEND_ENDPOINT}${notificationsPath()}?accountName=${
databaseAccount.name
}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;

View File

@@ -5,16 +5,16 @@ import * as ViewModels from "../Contracts/ViewModels";
import Explorer from "../Explorer/Explorer";
import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
import DocumentId from "../Explorer/Tree/DocumentId";
import { userContext } from "../UserContext";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import * as QueryUtils from "../Utils/QueryUtils";
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
import { userContext } from "../UserContext";
import { queryDocumentsPage } from "./dataAccess/queryDocumentsPage";
import { createCollection } from "./dataAccess/createCollection";
import { handleError } from "./ErrorHandlingUtils";
import { createDocument } from "./dataAccess/createDocument";
import { deleteDocument } from "./dataAccess/deleteDocument";
import { queryDocuments } from "./dataAccess/queryDocuments";
import { queryDocumentsPage } from "./dataAccess/queryDocumentsPage";
import { handleError } from "./ErrorHandlingUtils";
export class QueriesClient {
private static readonly PartitionKey: DataModels.PartitionKey = {
@@ -182,8 +182,11 @@ export class QueriesClient {
}
public getResourceId(): string {
const { databaseAccount, subscriptionId = "", resourceGroup = "" } = userContext;
const databaseAccountName = databaseAccount?.name || "";
const databaseAccount = userContext.databaseAccount;
const databaseAccountName = (databaseAccount && databaseAccount.name) || "";
const subscriptionId = userContext.subscriptionId || "";
const resourceGroup = userContext.resourceGroup || "";
return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDb/databaseAccounts/${databaseAccountName}`;
}
@@ -208,7 +211,7 @@ export class QueriesClient {
}
private fetchQueriesQuery(): string {
if (userContext.apiType === "Mongo") {
if (this.container.isPreferredApiMongoDB()) {
return QueriesClient.FetchMongoQuery;
}
return QueriesClient.FetchQuery;

View File

@@ -73,7 +73,7 @@ export class Splitter {
$(this.leftSide).resizable(splitterOptions);
}
private onResizeStart: JQueryUI.ResizableEvent = () => {
private onResizeStart: JQueryUI.ResizableEvent = (e: Event, ui: JQueryUI.ResizableUIParams) => {
if (this.direction === SplitterDirection.Vertical) {
$(".ui-resizable-helper").height("100%");
} else {
@@ -82,7 +82,9 @@ export class Splitter {
$("iframe").css("pointer-events", "none");
};
private onResizeStop: JQueryUI.ResizableEvent = () => $("iframe").css("pointer-events", "auto");
private onResizeStop: JQueryUI.ResizableEvent = (e: Event, ui: JQueryUI.ResizableUIParams) => {
$("iframe").css("pointer-events", "auto");
};
public collapseLeft() {
this.lastX = $(this.splitter).position().left;

View File

@@ -1,140 +0,0 @@
import {
Dropdown,
IDropdownOption,
IDropdownStyles,
IImageProps,
Image,
IStackTokens,
Stack,
TextField,
TooltipHost,
} from "@fluentui/react";
import React, { FunctionComponent } from "react";
import DeleteIcon from "../../images/delete.svg";
import EditIcon from "../../images/Edit_entity.svg";
import { CassandraType, TableType } from "../Explorer/Tables/Constants";
import { userContext } from "../UserContext";
import { EntityValue } from "./EntityValue";
const dropdownStyles: Partial<IDropdownStyles> = { dropdown: { width: 100 } };
export interface TableEntityProps {
entityTypeLabel?: string;
entityPropertyLabel?: string;
entityValueLabel?: string;
isDeleteOptionVisible: boolean;
entityProperty: string;
entityPropertyPlaceHolder: string;
selectedKey: string | number;
entityValuePlaceholder: string;
entityValue: string | Date;
isEntityTypeDate: boolean;
options: { key: string; text: string }[];
isPropertyTypeDisable: boolean;
entityTimeValue: string;
isEntityValueDisable?: boolean;
onDeleteEntity?: () => void;
onEditEntity?: () => void;
onEntityPropertyChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
onEntityTypeChange: (event: React.FormEvent<HTMLElement>, selectedParam: IDropdownOption) => void;
onEntityValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
onSelectDate: (date: Date | null | undefined) => void;
onEntityTimeValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
}
export const TableEntity: FunctionComponent<TableEntityProps> = ({
entityTypeLabel,
entityPropertyLabel,
isDeleteOptionVisible,
entityProperty,
selectedKey,
entityPropertyPlaceHolder,
entityValueLabel,
entityValuePlaceholder,
entityValue,
options,
isPropertyTypeDisable,
isEntityTypeDate,
entityTimeValue,
isEntityValueDisable,
onEditEntity,
onDeleteEntity,
onEntityPropertyChange,
onEntityTypeChange,
onEntityValueChange,
onSelectDate,
onEntityTimeValueChange,
}: TableEntityProps): JSX.Element => {
const imageProps: IImageProps = {
width: 16,
height: 30,
className: entityPropertyLabel ? "addRemoveIconLabel" : "addRemoveIcon",
};
const sectionStackTokens: IStackTokens = { childrenGap: 12 };
const getEntityValueType = (): string => {
const { Int, Smallint, Tinyint } = CassandraType;
const { Double, Int32, Int64 } = TableType;
if (
selectedKey === Double ||
selectedKey === Int32 ||
selectedKey === Int64 ||
selectedKey === Int ||
selectedKey === Smallint ||
selectedKey === Tinyint
) {
return "number";
}
return "string";
};
return (
<>
<Stack horizontal tokens={sectionStackTokens}>
<TextField
label={entityPropertyLabel && entityPropertyLabel}
id="entityPropertyId"
autoFocus
disabled={isPropertyTypeDisable}
placeholder={entityPropertyPlaceHolder}
value={entityProperty}
onChange={onEntityPropertyChange}
required
/>
<Dropdown
label={entityTypeLabel && entityTypeLabel}
selectedKey={selectedKey}
onChange={onEntityTypeChange}
options={options}
disabled={isPropertyTypeDisable}
id="entityTypeId"
styles={dropdownStyles}
/>
<EntityValue
entityValueLabel={entityValueLabel}
entityValueType={getEntityValueType()}
isEntityValueDisable={isEntityValueDisable}
entityValuePlaceholder={entityValuePlaceholder}
entityValue={entityValue}
isEntityTypeDate={isEntityTypeDate}
entityTimeValue={entityTimeValue}
onEntityValueChange={onEntityValueChange}
onSelectDate={onSelectDate}
onEntityTimeValueChange={onEntityTimeValueChange}
/>
{!isEntityValueDisable && (
<TooltipHost content="Edit property" id="editTooltip">
<Image {...imageProps} src={EditIcon} alt="editEntity" id="editEntity" onClick={onEditEntity} />
</TooltipHost>
)}
{isDeleteOptionVisible && userContext.apiType !== "Cassandra" && (
<TooltipHost content="Delete property" id="deleteTooltip">
<Image {...imageProps} src={DeleteIcon} alt="delete entity" id="deleteEntity" onClick={onDeleteEntity} />
</TooltipHost>
)}
</Stack>
</>
);
};

View File

@@ -1,5 +1,5 @@
import { ITooltipHostStyles, TooltipHost } from "@fluentui/react";
import { useId } from "@fluentui/react-hooks";
import { useId } from "@uifabric/react-hooks";
import { ITooltipHostStyles, TooltipHost } from "office-ui-fabric-react/lib/Tooltip";
import * as React from "react";
import InfoBubble from "../../../images/info-bubble.svg";

View File

@@ -1,8 +1,8 @@
import { Image, Stack, TextField } from "@fluentui/react";
import { Image, Stack, TextField } from "office-ui-fabric-react";
import React, { ChangeEvent, FunctionComponent, KeyboardEvent, useRef, useState } from "react";
import FolderIcon from "../../../images/folder_16x16.svg";
import * as Constants from "../Constants";
import { Tooltip } from "../Tooltip/Tooltip";
import * as Constants from "../../Common/Constants";
import { Tooltip } from "../Tooltip";
interface UploadProps {
label: string;

View File

@@ -2,7 +2,7 @@
exports[`requestPlugin Emulator builds a url for emulator proxy via webpack 1`] = `
Object {
"endpoint": "http://localhost/proxy",
"endpoint": "/proxy",
"headers": Object {
"x-ms-proxy-target": "http://localhost",
},
@@ -12,7 +12,7 @@ Object {
exports[`requestPlugin Hosted builds a proxy URL in development 1`] = `
Object {
"endpoint": "http://localhost/proxy",
"endpoint": "/proxy",
"headers": Object {
"x-ms-proxy-target": "baz",
},

View File

@@ -1,39 +0,0 @@
import { JSONObject, OperationResponse } from "@azure/cosmos";
import { CollectionBase } from "../../Contracts/ViewModels";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export const bulkCreateDocument = async (
collection: CollectionBase,
documents: JSONObject[]
): Promise<OperationResponse[]> => {
const clearMessage = logConsoleProgress(
`Executing ${documents.length} bulk operations for container ${collection.id()}`
);
try {
const response = await client()
.database(collection.databaseId)
.container(collection.id())
.items.bulk(
documents.map((doc) => ({ operationType: "Create", resourceBody: doc })),
{ continueOnError: true }
);
const successCount = response.filter((r) => r.statusCode === 201).length;
const throttledCount = response.filter((r) => r.statusCode === 429).length;
logConsoleInfo(
`${
documents.length
} operations completed for container ${collection.id()}. ${successCount} operations succeeded. ${throttledCount} operations throttled`
);
return response;
} catch (error) {
handleError(error, "BulkCreateDocument", `Error bulk creating items for container ${collection.id()}`);
throw error;
} finally {
clearMessage();
}
};

View File

@@ -2,10 +2,11 @@ jest.mock("../../Utils/arm/request");
jest.mock("../CosmosClient");
import { AuthType } from "../../AuthType";
import { CreateCollectionParams, DatabaseAccount } from "../../Contracts/DataModels";
import { updateUserContext } from "../../UserContext";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { armRequest } from "../../Utils/arm/request";
import { client } from "../CosmosClient";
import { constructRpOptions, createCollection } from "./createCollection";
import { createCollection, constructRpOptions } from "./createCollection";
import { updateUserContext } from "../../UserContext";
describe("createCollection", () => {
const createCollectionParams: CreateCollectionParams = {
@@ -21,7 +22,7 @@ describe("createCollection", () => {
databaseAccount: {
name: "test",
} as DatabaseAccount,
apiType: "SQL",
defaultExperience: DefaultAccountExperienceType.DocumentDB,
});
});

View File

@@ -1,32 +1,33 @@
import * as DataModels from "../../Contracts/DataModels";
import { AuthType } from "../../AuthType";
import { ContainerResponse, DatabaseResponse } from "@azure/cosmos";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
import { AuthType } from "../../AuthType";
import * as DataModels from "../../Contracts/DataModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import * as ARMTypes from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { createMongoCollectionWithProxy } from "../MongoProxyClient";
import { createUpdateSqlContainer, getSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import {
createUpdateCassandraTable,
getCassandraTable,
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import {
createUpdateGremlinGraph,
getGremlinGraph,
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import {
createUpdateMongoDBCollection,
getMongoDBCollection,
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { createUpdateSqlContainer, getSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import {
createUpdateGremlinGraph,
getGremlinGraph,
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import * as ARMTypes from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { createMongoCollectionWithProxy } from "../MongoProxyClient";
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
import { createDatabase } from "./createDatabase";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { handleError } from "../ErrorHandlingUtils";
export const createCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
const clearMessage = logConsoleProgress(
@@ -45,7 +46,7 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
await createDatabase(createDatabaseParams);
}
collection = await createCollectionWithARM(params);
} else if (userContext.apiType === "Mongo") {
} else if (userContext.defaultExperience === DefaultAccountExperienceType.MongoDB) {
collection = await createMongoCollectionWithProxy(params);
} else {
collection = await createCollectionWithSDK(params);
@@ -62,20 +63,20 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
};
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
const { apiType } = userContext;
switch (apiType) {
case "SQL":
const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
return createSqlContainer(params);
case "Mongo":
case DefaultAccountExperienceType.MongoDB:
return createMongoCollection(params);
case "Cassandra":
case DefaultAccountExperienceType.Cassandra:
return createCassandraTable(params);
case "Gremlin":
case DefaultAccountExperienceType.Graph:
return createGraph(params);
case "Tables":
case DefaultAccountExperienceType.Table:
return createTable(params);
default:
throw new Error(`Unsupported default experience type: ${apiType}`);
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
};

View File

@@ -1,36 +1,37 @@
import * as DataModels from "../../Contracts/DataModels";
import { AuthType } from "../../AuthType";
import { DatabaseResponse } from "@azure/cosmos";
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
import { AuthType } from "../../AuthType";
import * as DataModels from "../../Contracts/DataModels";
import { userContext } from "../../UserContext";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import {
CassandraKeyspaceCreateUpdateParameters,
GremlinDatabaseCreateUpdateParameters,
MongoDBDatabaseCreateUpdateParameters,
SqlDatabaseCreateUpdateParameters,
CreateUpdateOptions,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { createUpdateSqlDatabase, getSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import {
createUpdateCassandraKeyspace,
getCassandraKeyspace,
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import {
createUpdateGremlinDatabase,
getGremlinDatabase,
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import {
createUpdateMongoDBDatabase,
getMongoDBDatabase,
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { createUpdateSqlDatabase, getSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import {
CassandraKeyspaceCreateUpdateParameters,
CreateUpdateOptions,
GremlinDatabaseCreateUpdateParameters,
MongoDBDatabaseCreateUpdateParameters,
SqlDatabaseCreateUpdateParameters,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
createUpdateGremlinDatabase,
getGremlinDatabase,
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function createDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
const clearMessage = logConsoleProgress(`Creating a new database ${params.databaseId}`);
try {
if (userContext.apiType === "Tables") {
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
throw new Error("Creating database resources is not allowed for tables accounts");
}
const database: DataModels.Database = await (userContext.authType === AuthType.AAD && !userContext.useSDKOperations
@@ -48,19 +49,18 @@ export async function createDatabase(params: DataModels.CreateDatabaseParams): P
}
async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
const { apiType } = userContext;
switch (apiType) {
case "SQL":
const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
return createSqlDatabase(params);
case "Mongo":
case DefaultAccountExperienceType.MongoDB:
return createMongoDatabase(params);
case "Cassandra":
case DefaultAccountExperienceType.Cassandra:
return createCassandraKeyspace(params);
case "Gremlin":
case DefaultAccountExperienceType.Graph:
return createGremlineDatabase(params);
default:
throw new Error(`Unsupported default experience type: ${apiType}`);
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
}

View File

@@ -1,17 +1,18 @@
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import {
createUpdateSqlStoredProcedure,
getSqlStoredProcedure,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import {
SqlStoredProcedureCreateUpdateParameters,
SqlStoredProcedureResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import {
createUpdateSqlStoredProcedure,
getSqlStoredProcedure,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function createStoredProcedure(
databaseId: string,
@@ -20,7 +21,11 @@ export async function createStoredProcedure(
): Promise<StoredProcedureDefinition & Resource> {
const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
try {
const getResponse = await getSqlStoredProcedure(
userContext.subscriptionId,

View File

@@ -1,14 +1,15 @@
import { Resource, TriggerDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, TriggerDefinition } from "@azure/cosmos";
import {
SqlTriggerCreateUpdateParameters,
SqlTriggerResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function createTrigger(
databaseId: string,
@@ -17,7 +18,11 @@ export async function createTrigger(
): Promise<TriggerDefinition & Resource> {
const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
try {
const getResponse = await getSqlTrigger(
userContext.subscriptionId,

View File

@@ -1,17 +1,18 @@
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import {
createUpdateSqlUserDefinedFunction,
getSqlUserDefinedFunction,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import {
SqlUserDefinedFunctionCreateUpdateParameters,
SqlUserDefinedFunctionResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import {
createUpdateSqlUserDefinedFunction,
getSqlUserDefinedFunction,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function createUserDefinedFunction(
databaseId: string,
@@ -20,7 +21,11 @@ export async function createUserDefinedFunction(
): Promise<UserDefinedFunctionDefinition & Resource> {
const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
try {
const getResponse = await getSqlUserDefinedFunction(
userContext.subscriptionId,

View File

@@ -1,12 +1,13 @@
jest.mock("../../Utils/arm/request");
jest.mock("../MessageHandler");
jest.mock("../CosmosClient");
import { AuthType } from "../../AuthType";
import { DatabaseAccount } from "../../Contracts/DataModels";
import { updateUserContext } from "../../UserContext";
import { armRequest } from "../../Utils/arm/request";
import { client } from "../CosmosClient";
import { deleteCollection } from "./deleteCollection";
import { armRequest } from "../../Utils/arm/request";
import { AuthType } from "../../AuthType";
import { client } from "../CosmosClient";
import { updateUserContext } from "../../UserContext";
import { DatabaseAccount } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
describe("deleteCollection", () => {
beforeAll(() => {
@@ -14,7 +15,7 @@ describe("deleteCollection", () => {
databaseAccount: {
name: "test",
} as DatabaseAccount,
apiType: "SQL",
defaultExperience: DefaultAccountExperienceType.DocumentDB,
});
});

View File

@@ -1,13 +1,14 @@
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { deleteCassandraTable } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { deleteGremlinGraph } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { deleteMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { deleteSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { deleteCassandraTable } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { deleteMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { deleteGremlinGraph } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { deleteTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
import { client } from "../CosmosClient";
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
@@ -27,21 +28,23 @@ export async function deleteCollection(databaseId: string, collectionId: string)
}
function deleteCollectionWithARM(databaseId: string, collectionId: string): Promise<void> {
const { subscriptionId, resourceGroup, apiType, databaseAccount } = userContext;
const accountName = databaseAccount.name;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
switch (apiType) {
case "SQL":
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
return deleteSqlContainer(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
case "Mongo":
case DefaultAccountExperienceType.MongoDB:
return deleteMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
case "Cassandra":
case DefaultAccountExperienceType.Cassandra:
return deleteCassandraTable(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
case "Gremlin":
case DefaultAccountExperienceType.Graph:
return deleteGremlinGraph(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
case "Tables":
case DefaultAccountExperienceType.Table:
return deleteTable(subscriptionId, resourceGroup, accountName, collectionId);
default:
throw new Error(`Unsupported default experience type: ${apiType}`);
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
}

View File

@@ -1,12 +1,13 @@
jest.mock("../../Utils/arm/request");
jest.mock("../MessageHandler");
jest.mock("../CosmosClient");
import { AuthType } from "../../AuthType";
import { DatabaseAccount } from "../../Contracts/DataModels";
import { updateUserContext } from "../../UserContext";
import { armRequest } from "../../Utils/arm/request";
import { client } from "../CosmosClient";
import { deleteDatabase } from "./deleteDatabase";
import { armRequest } from "../../Utils/arm/request";
import { AuthType } from "../../AuthType";
import { client } from "../CosmosClient";
import { updateUserContext } from "../../UserContext";
import { DatabaseAccount } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
describe("deleteDatabase", () => {
beforeAll(() => {
@@ -14,7 +15,7 @@ describe("deleteDatabase", () => {
databaseAccount: {
name: "test",
} as DatabaseAccount,
apiType: "SQL",
defaultExperience: DefaultAccountExperienceType.DocumentDB,
});
});

View File

@@ -1,18 +1,19 @@
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { deleteCassandraKeyspace } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { deleteGremlinDatabase } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { deleteMongoDBDatabase } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { deleteSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { deleteCassandraKeyspace } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { deleteMongoDBDatabase } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { deleteGremlinDatabase } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
import { client } from "../CosmosClient";
export async function deleteDatabase(databaseId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
try {
if (userContext.apiType === "Tables") {
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
throw new Error("Deleting database resources is not allowed for tables accounts");
}
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) {
@@ -30,19 +31,21 @@ export async function deleteDatabase(databaseId: string): Promise<void> {
}
function deleteDatabaseWithARM(databaseId: string): Promise<void> {
const { subscriptionId, resourceGroup, apiType, databaseAccount } = userContext;
const accountName = databaseAccount.name;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
switch (apiType) {
case "SQL":
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
return deleteSqlDatabase(subscriptionId, resourceGroup, accountName, databaseId);
case "Mongo":
case DefaultAccountExperienceType.MongoDB:
return deleteMongoDBDatabase(subscriptionId, resourceGroup, accountName, databaseId);
case "Cassandra":
case DefaultAccountExperienceType.Cassandra:
return deleteCassandraKeyspace(subscriptionId, resourceGroup, accountName, databaseId);
case "Gremlin":
case DefaultAccountExperienceType.Graph:
return deleteGremlinDatabase(subscriptionId, resourceGroup, accountName, databaseId);
default:
throw new Error(`Unsupported default experience type: ${apiType}`);
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
}

View File

@@ -1,9 +1,10 @@
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { deleteSqlStoredProcedure } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient";
import { deleteSqlStoredProcedure } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function deleteStoredProcedure(
databaseId: string,
@@ -12,7 +13,11 @@ export async function deleteStoredProcedure(
): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
await deleteSqlStoredProcedure(
userContext.subscriptionId,
userContext.resourceGroup,

View File

@@ -1,14 +1,19 @@
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { deleteSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient";
import { deleteSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function deleteTrigger(databaseId: string, collectionId: string, triggerId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
await deleteSqlTrigger(
userContext.subscriptionId,
userContext.resourceGroup,

View File

@@ -1,14 +1,19 @@
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { deleteSqlUserDefinedFunction } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient";
import { deleteSqlUserDefinedFunction } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function deleteUserDefinedFunction(databaseId: string, collectionId: string, id: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
await deleteSqlUserDefinedFunction(
userContext.subscriptionId,
userContext.resourceGroup,

View File

@@ -1,8 +1,8 @@
import { AuthType } from "../../AuthType";
import { configContext } from "../../ConfigContext";
import { userContext } from "../../UserContext";
import { armRequest } from "../../Utils/arm/request";
import { configContext } from "../../ConfigContext";
import { handleError } from "../ErrorHandlingUtils";
import { userContext } from "../../UserContext";
interface TimeSeriesData {
data: {
@@ -45,9 +45,9 @@ export const getCollectionUsageSizeInKB = async (databaseName: string, container
return undefined;
}
const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = databaseAccount.name;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const filter = `DatabaseName eq '${databaseName}' and CollectionName eq '${containerName}'`;
const metricNames = "DataUsage,IndexUsage";
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/providers/microsoft.insights/metrics`;

View File

@@ -1,9 +1,10 @@
jest.mock("../CosmosClient");
import { AuthType } from "../../AuthType";
import { DatabaseAccount } from "../../Contracts/DataModels";
import { updateUserContext } from "../../UserContext";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient";
import { readCollection } from "./readCollection";
import { updateUserContext } from "../../UserContext";
describe("readCollection", () => {
beforeAll(() => {
@@ -12,7 +13,7 @@ describe("readCollection", () => {
databaseAccount: {
name: "test",
} as DatabaseAccount,
apiType: "SQL",
defaultExperience: DefaultAccountExperienceType.DocumentDB,
});
});

View File

@@ -1,20 +1,25 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Offer, ReadCollectionOfferParams } from "../../Contracts/DataModels";
import { userContext } from "../../UserContext";
import { handleError } from "../ErrorHandlingUtils";
import { getSqlContainerThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { getMongoDBCollectionThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { getCassandraTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { getGremlinGraphThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { getMongoDBCollectionThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { getSqlContainerThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { getTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { handleError } from "../ErrorHandlingUtils";
import { readOfferWithSDK } from "./readOfferWithSDK";
import { userContext } from "../../UserContext";
export const readCollectionOffer = async (params: ReadCollectionOfferParams): Promise<Offer> => {
const clearMessage = logConsoleProgress(`Querying offer for collection ${params.collectionId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
if (
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
return await readCollectionOfferWithARM(params.databaseId, params.collectionId);
}
@@ -28,13 +33,15 @@ export const readCollectionOffer = async (params: ReadCollectionOfferParams): Pr
};
const readCollectionOfferWithARM = async (databaseId: string, collectionId: string): Promise<Offer> => {
const { subscriptionId, resourceGroup, apiType, databaseAccount } = userContext;
const accountName = databaseAccount.name;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
let rpResponse;
try {
switch (apiType) {
case "SQL":
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
rpResponse = await getSqlContainerThroughput(
subscriptionId,
resourceGroup,
@@ -43,7 +50,7 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
collectionId
);
break;
case "Mongo":
case DefaultAccountExperienceType.MongoDB:
rpResponse = await getMongoDBCollectionThroughput(
subscriptionId,
resourceGroup,
@@ -52,7 +59,7 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
collectionId
);
break;
case "Cassandra":
case DefaultAccountExperienceType.Cassandra:
rpResponse = await getCassandraTableThroughput(
subscriptionId,
resourceGroup,
@@ -61,7 +68,7 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
collectionId
);
break;
case "Gremlin":
case DefaultAccountExperienceType.Graph:
rpResponse = await getGremlinGraphThroughput(
subscriptionId,
resourceGroup,
@@ -70,11 +77,11 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
collectionId
);
break;
case "Tables":
case DefaultAccountExperienceType.Table:
rpResponse = await getTableThroughput(subscriptionId, resourceGroup, accountName, collectionId);
break;
default:
throw new Error(`Unsupported default experience type: ${apiType}`);
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
} catch (error) {
if (error.code !== "NotFound") {

View File

@@ -2,10 +2,11 @@ jest.mock("../../Utils/arm/request");
jest.mock("../CosmosClient");
import { AuthType } from "../../AuthType";
import { DatabaseAccount } from "../../Contracts/DataModels";
import { updateUserContext } from "../../UserContext";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { armRequest } from "../../Utils/arm/request";
import { client } from "../CosmosClient";
import { readCollections } from "./readCollections";
import { updateUserContext } from "../../UserContext";
describe("readCollections", () => {
beforeAll(() => {
@@ -13,7 +14,7 @@ describe("readCollections", () => {
databaseAccount: {
name: "test",
} as DatabaseAccount,
apiType: "SQL",
defaultExperience: DefaultAccountExperienceType.DocumentDB,
});
});

View File

@@ -1,19 +1,25 @@
import { AuthType } from "../../AuthType";
import * as DataModels from "../../Contracts/DataModels";
import { userContext } from "../../UserContext";
import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { listMongoDBCollections } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { listSqlContainers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { listTables } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { listSqlContainers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { listMongoDBCollections } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { listTables } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
if (
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
return await readCollectionsWithARM(databaseId);
}
@@ -29,28 +35,29 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Collection[]> {
let rpResponse;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
const { subscriptionId, resourceGroup, apiType, databaseAccount } = userContext;
const accountName = databaseAccount.name;
switch (apiType) {
case "SQL":
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
rpResponse = await listSqlContainers(subscriptionId, resourceGroup, accountName, databaseId);
break;
case "Mongo":
case DefaultAccountExperienceType.MongoDB:
rpResponse = await listMongoDBCollections(subscriptionId, resourceGroup, accountName, databaseId);
break;
case "Cassandra":
case DefaultAccountExperienceType.Cassandra:
rpResponse = await listCassandraTables(subscriptionId, resourceGroup, accountName, databaseId);
break;
case "Gremlin":
case DefaultAccountExperienceType.Graph:
rpResponse = await listGremlinGraphs(subscriptionId, resourceGroup, accountName, databaseId);
break;
case "Tables":
case DefaultAccountExperienceType.Table:
rpResponse = await listTables(subscriptionId, resourceGroup, accountName);
break;
default:
throw new Error(`Unsupported default experience type: ${apiType}`);
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
return rpResponse?.value?.map((collection) => collection.properties?.resource as DataModels.Collection);

View File

@@ -1,19 +1,24 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Offer, ReadDatabaseOfferParams } from "../../Contracts/DataModels";
import { userContext } from "../../UserContext";
import { getSqlDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { getMongoDBDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { getCassandraKeyspaceThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { getGremlinDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { getMongoDBDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { getSqlDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { readOfferWithSDK } from "./readOfferWithSDK";
import { userContext } from "../../UserContext";
export const readDatabaseOffer = async (params: ReadDatabaseOfferParams): Promise<Offer> => {
const clearMessage = logConsoleProgress(`Querying offer for database ${params.databaseId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
if (
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
return await readDatabaseOfferWithARM(params.databaseId);
}
@@ -27,26 +32,28 @@ export const readDatabaseOffer = async (params: ReadDatabaseOfferParams): Promis
};
const readDatabaseOfferWithARM = async (databaseId: string): Promise<Offer> => {
const { subscriptionId, resourceGroup, apiType, databaseAccount } = userContext;
const accountName = databaseAccount.name;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
let rpResponse;
try {
switch (apiType) {
case "SQL":
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
rpResponse = await getSqlDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break;
case "Mongo":
case DefaultAccountExperienceType.MongoDB:
rpResponse = await getMongoDBDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break;
case "Cassandra":
case DefaultAccountExperienceType.Cassandra:
rpResponse = await getCassandraKeyspaceThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break;
case "Gremlin":
case DefaultAccountExperienceType.Graph:
rpResponse = await getGremlinDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break;
default:
throw new Error(`Unsupported default experience type: ${apiType}`);
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
} catch (error) {
if (error.code !== "NotFound") {

View File

@@ -2,10 +2,11 @@ jest.mock("../../Utils/arm/request");
jest.mock("../CosmosClient");
import { AuthType } from "../../AuthType";
import { DatabaseAccount } from "../../Contracts/DataModels";
import { updateUserContext } from "../../UserContext";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { armRequest } from "../../Utils/arm/request";
import { client } from "../CosmosClient";
import { readDatabases } from "./readDatabases";
import { updateUserContext } from "../../UserContext";
describe("readDatabases", () => {
beforeAll(() => {
@@ -13,7 +14,7 @@ describe("readDatabases", () => {
databaseAccount: {
name: "test",
} as DatabaseAccount,
apiType: "SQL",
defaultExperience: DefaultAccountExperienceType.DocumentDB,
});
});

View File

@@ -1,19 +1,24 @@
import { AuthType } from "../../AuthType";
import * as DataModels from "../../Contracts/DataModels";
import { userContext } from "../../UserContext";
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { listSqlDatabases } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { listSqlDatabases } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function readDatabases(): Promise<DataModels.Database[]> {
let databases: DataModels.Database[];
const clearMessage = logConsoleProgress(`Querying databases`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
if (
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
databases = await readDatabasesWithARM();
} else {
const sdkResponse = await client().databases.readAll().fetchAll();
@@ -29,24 +34,26 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
async function readDatabasesWithARM(): Promise<DataModels.Database[]> {
let rpResponse;
const { subscriptionId, resourceGroup, apiType, databaseAccount } = userContext;
const accountName = databaseAccount.name;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
switch (apiType) {
case "SQL":
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
rpResponse = await listSqlDatabases(subscriptionId, resourceGroup, accountName);
break;
case "Mongo":
case DefaultAccountExperienceType.MongoDB:
rpResponse = await listMongoDBDatabases(subscriptionId, resourceGroup, accountName);
break;
case "Cassandra":
case DefaultAccountExperienceType.Cassandra:
rpResponse = await listCassandraKeyspaces(subscriptionId, resourceGroup, accountName);
break;
case "Gremlin":
case DefaultAccountExperienceType.Graph:
rpResponse = await listGremlinDatabases(subscriptionId, resourceGroup, accountName);
break;
default:
throw new Error(`Unsupported default experience type: ${apiType}`);
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
return rpResponse?.value?.map((database) => database.properties?.resource as DataModels.Database);

View File

@@ -1,9 +1,9 @@
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { getMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { MongoDBCollectionResource } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { handleError } from "../ErrorHandlingUtils";
import { AuthType } from "../../AuthType";
export async function readMongoDBCollectionThroughRP(
databaseId: string,
@@ -13,9 +13,9 @@ export async function readMongoDBCollectionThroughRP(
return undefined;
}
let collection: MongoDBCollectionResource;
const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = databaseAccount.name;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const clearMessage = logConsoleProgress(`Reading container ${collectionId}`);
try {

View File

@@ -1,10 +1,11 @@
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { listSqlStoredProcedures } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { listSqlStoredProcedures } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function readStoredProcedures(
databaseId: string,
@@ -12,7 +13,11 @@ export async function readStoredProcedures(
): Promise<(StoredProcedureDefinition & Resource)[]> {
const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const rpResponse = await listSqlStoredProcedures(
userContext.subscriptionId,
userContext.resourceGroup,

View File

@@ -1,9 +1,10 @@
import { Resource, TriggerDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, TriggerDefinition } from "@azure/cosmos";
import { client } from "../CosmosClient";
import { listSqlTriggers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { userContext } from "../../UserContext";
import { handleError } from "../ErrorHandlingUtils";
export async function readTriggers(
@@ -12,7 +13,11 @@ export async function readTriggers(
): Promise<(TriggerDefinition & Resource)[]> {
const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const rpResponse = await listSqlTriggers(
userContext.subscriptionId,
userContext.resourceGroup,

View File

@@ -1,23 +1,27 @@
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { listSqlUserDefinedFunctions } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { listSqlUserDefinedFunctions } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function readUserDefinedFunctions(
databaseId: string,
collectionId: string
): Promise<(UserDefinedFunctionDefinition & Resource)[]> {
const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`);
const { authType, useSDKOperations, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
try {
if (authType === AuthType.AAD && !useSDKOperations && apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const rpResponse = await listSqlUserDefinedFunctions(
subscriptionId,
resourceGroup,
databaseAccount.name,
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId
);

View File

@@ -1,43 +1,51 @@
import { ContainerDefinition } from "@azure/cosmos";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { AuthType } from "../../AuthType";
import { Collection } from "../../Contracts/DataModels";
import { userContext } from "../../UserContext";
import { ContainerDefinition } from "@azure/cosmos";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import {
CreateUpdateOptions,
ExtendedResourceProperties,
MongoDBCollectionCreateUpdateParameters,
MongoDBCollectionResource,
SqlContainerCreateUpdateParameters,
SqlContainerResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { client } from "../CosmosClient";
import { createUpdateSqlContainer, getSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import {
createUpdateCassandraTable,
getCassandraTable,
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import {
createUpdateGremlinGraph,
getGremlinGraph,
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import {
createUpdateMongoDBCollection,
getMongoDBCollection,
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { createUpdateSqlContainer, getSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import {
ExtendedResourceProperties,
MongoDBCollectionCreateUpdateParameters,
SqlContainerCreateUpdateParameters,
SqlContainerResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
createUpdateGremlinGraph,
getGremlinGraph,
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function updateCollection(
databaseId: string,
collectionId: string,
newCollection: Partial<Collection>,
newCollection: Collection,
options: RequestOptions = {}
): Promise<Collection> {
let collection: Collection;
const clearMessage = logConsoleProgress(`Updating container ${collectionId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
if (
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
collection = await updateCollectionWithARM(databaseId, collectionId, newCollection);
} else {
const sdkResponse = await client()
@@ -61,31 +69,24 @@ export async function updateCollection(
async function updateCollectionWithARM(
databaseId: string,
collectionId: string,
newCollection: Partial<Collection>
newCollection: Collection
): Promise<Collection> {
const { subscriptionId, resourceGroup, apiType, databaseAccount } = userContext;
const accountName = databaseAccount.name;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
switch (apiType) {
case "SQL":
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
return updateSqlContainer(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case "Cassandra":
case DefaultAccountExperienceType.Cassandra:
return updateCassandraTable(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case "Gremlin":
case DefaultAccountExperienceType.Graph:
return updateGremlinGraph(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case "Tables":
case DefaultAccountExperienceType.Table:
return updateTable(collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case "Mongo":
return updateMongoDBCollection(
databaseId,
collectionId,
subscriptionId,
resourceGroup,
accountName,
newCollection
);
default:
throw new Error(`Unsupported default experience type: ${apiType}`);
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
}
@@ -95,7 +96,7 @@ async function updateSqlContainer(
subscriptionId: string,
resourceGroup: string,
accountName: string,
newCollection: Partial<Collection>
newCollection: Collection
): Promise<Collection> {
const getResponse = await getSqlContainer(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) {
@@ -114,26 +115,35 @@ async function updateSqlContainer(
throw new Error(`Sql container to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
}
export async function updateMongoDBCollection(
export async function updateMongoDBCollectionThroughRP(
databaseId: string,
collectionId: string,
subscriptionId: string,
resourceGroup: string,
accountName: string,
newCollection: Partial<Collection>
): Promise<Collection> {
newCollection: MongoDBCollectionResource,
updateOptions?: CreateUpdateOptions
): Promise<MongoDBCollectionResource> {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const getResponse = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) {
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
const updateParams: MongoDBCollectionCreateUpdateParameters = {
properties: {
resource: newCollection,
options: updateOptions,
},
};
const updateResponse = await createUpdateMongoDBCollection(
subscriptionId,
resourceGroup,
accountName,
databaseId,
collectionId,
getResponse as MongoDBCollectionCreateUpdateParameters
updateParams
);
return updateResponse && (updateResponse.properties.resource as Collection);
return updateResponse && (updateResponse.properties.resource as MongoDBCollectionResource);
}
throw new Error(
@@ -147,7 +157,7 @@ async function updateCassandraTable(
subscriptionId: string,
resourceGroup: string,
accountName: string,
newCollection: Partial<Collection>
newCollection: Collection
): Promise<Collection> {
const getResponse = await getCassandraTable(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) {
@@ -174,7 +184,7 @@ async function updateGremlinGraph(
subscriptionId: string,
resourceGroup: string,
accountName: string,
newCollection: Partial<Collection>
newCollection: Collection
): Promise<Collection> {
const getResponse = await getGremlinGraph(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) {
@@ -198,7 +208,7 @@ async function updateTable(
subscriptionId: string,
resourceGroup: string,
accountName: string,
newCollection: Partial<Collection>
newCollection: Collection
): Promise<Collection> {
const getResponse = await getTable(subscriptionId, resourceGroup, accountName, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) {

View File

@@ -1,53 +1,54 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { HttpHeaders } from "../Constants";
import { Offer, SDKOfferDefinition, UpdateOfferParams } from "../../Contracts/DataModels";
import { OfferDefinition } from "@azure/cosmos";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { AuthType } from "../../AuthType";
import { Offer, SDKOfferDefinition, UpdateOfferParams } from "../../Contracts/DataModels";
import { userContext } from "../../UserContext";
import { ThroughputSettingsUpdateParameters } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { parseSDKOfferResponse } from "../OfferUtility";
import { readCollectionOffer } from "./readCollectionOffer";
import { readDatabaseOffer } from "./readDatabaseOffer";
import {
updateSqlDatabaseThroughput,
migrateSqlDatabaseToAutoscale,
migrateSqlDatabaseToManualThroughput,
migrateSqlContainerToAutoscale,
migrateSqlContainerToManualThroughput,
updateSqlContainerThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import {
updateCassandraKeyspaceThroughput,
migrateCassandraKeyspaceToAutoscale,
migrateCassandraKeyspaceToManualThroughput,
migrateCassandraTableToAutoscale,
migrateCassandraTableToManualThroughput,
updateCassandraKeyspaceThroughput,
updateCassandraTableThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import {
updateMongoDBDatabaseThroughput,
migrateMongoDBDatabaseToAutoscale,
migrateMongoDBDatabaseToManualThroughput,
migrateMongoDBCollectionToAutoscale,
migrateMongoDBCollectionToManualThroughput,
updateMongoDBCollectionThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import {
updateGremlinDatabaseThroughput,
migrateGremlinDatabaseToAutoscale,
migrateGremlinDatabaseToManualThroughput,
migrateGremlinGraphToAutoscale,
migrateGremlinGraphToManualThroughput,
updateGremlinDatabaseThroughput,
updateGremlinGraphThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import {
migrateMongoDBCollectionToAutoscale,
migrateMongoDBCollectionToManualThroughput,
migrateMongoDBDatabaseToAutoscale,
migrateMongoDBDatabaseToManualThroughput,
updateMongoDBCollectionThroughput,
updateMongoDBDatabaseThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import {
migrateSqlContainerToAutoscale,
migrateSqlContainerToManualThroughput,
migrateSqlDatabaseToAutoscale,
migrateSqlDatabaseToManualThroughput,
updateSqlContainerThroughput,
updateSqlDatabaseThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { userContext } from "../../UserContext";
import {
migrateTableToAutoscale,
migrateTableToManualThroughput,
updateTableThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { ThroughputSettingsUpdateParameters } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { HttpHeaders } from "../Constants";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { parseSDKOfferResponse } from "../OfferUtility";
import { readCollectionOffer } from "./readCollectionOffer";
import { readDatabaseOffer } from "./readDatabaseOffer";
export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> => {
let updatedOffer: Offer;
@@ -60,7 +61,7 @@ export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> =>
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) {
if (params.collectionId) {
updatedOffer = await updateCollectionOfferWithARM(params);
} else if (userContext.apiType === "Tables") {
} else if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
// update table's database offer with SDK since RP doesn't support it
updatedOffer = await updateOfferWithSDK(params);
} else {
@@ -81,24 +82,24 @@ export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> =>
const updateCollectionOfferWithARM = async (params: UpdateOfferParams): Promise<Offer> => {
try {
switch (userContext.apiType) {
case "SQL":
switch (userContext.defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
await updateSqlContainerOffer(params);
break;
case "Mongo":
case DefaultAccountExperienceType.MongoDB:
await updateMongoCollectionOffer(params);
break;
case "Cassandra":
case DefaultAccountExperienceType.Cassandra:
await updateCassandraTableOffer(params);
break;
case "Gremlin":
case DefaultAccountExperienceType.Graph:
await updateGremlinGraphOffer(params);
break;
case "Tables":
case DefaultAccountExperienceType.Table:
await updateTableOffer(params);
break;
default:
throw new Error(`Unsupported default experience type: ${userContext.apiType}`);
throw new Error(`Unsupported default experience type: ${userContext.defaultExperience}`);
}
} catch (error) {
if (error.code !== "MethodNotAllowed") {
@@ -115,21 +116,21 @@ const updateCollectionOfferWithARM = async (params: UpdateOfferParams): Promise<
const updateDatabaseOfferWithARM = async (params: UpdateOfferParams): Promise<Offer> => {
try {
switch (userContext.apiType) {
case "SQL":
switch (userContext.defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
await updateSqlDatabaseOffer(params);
break;
case "Mongo":
case DefaultAccountExperienceType.MongoDB:
await updateMongoDatabaseOffer(params);
break;
case "Cassandra":
case DefaultAccountExperienceType.Cassandra:
await updateCassandraKeyspaceOffer(params);
break;
case "Gremlin":
case DefaultAccountExperienceType.Graph:
await updateGremlinDatabaseOffer(params);
break;
default:
throw new Error(`Unsupported default experience type: ${userContext.apiType}`);
throw new Error(`Unsupported default experience type: ${userContext.defaultExperience}`);
}
} catch (error) {
if (error.code !== "MethodNotAllowed") {
@@ -144,8 +145,9 @@ const updateDatabaseOfferWithARM = async (params: UpdateOfferParams): Promise<Of
};
const updateSqlContainerOffer = async (params: UpdateOfferParams): Promise<void> => {
const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = databaseAccount.name;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateSqlContainerToAutoscale(
@@ -177,8 +179,9 @@ const updateSqlContainerOffer = async (params: UpdateOfferParams): Promise<void>
};
const updateMongoCollectionOffer = async (params: UpdateOfferParams): Promise<void> => {
const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = databaseAccount.name;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateMongoDBCollectionToAutoscale(
@@ -210,8 +213,9 @@ const updateMongoCollectionOffer = async (params: UpdateOfferParams): Promise<vo
};
const updateCassandraTableOffer = async (params: UpdateOfferParams): Promise<void> => {
const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = databaseAccount.name;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateCassandraTableToAutoscale(
@@ -243,8 +247,9 @@ const updateCassandraTableOffer = async (params: UpdateOfferParams): Promise<voi
};
const updateGremlinGraphOffer = async (params: UpdateOfferParams): Promise<void> => {
const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = databaseAccount.name;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateGremlinGraphToAutoscale(
@@ -276,8 +281,9 @@ const updateGremlinGraphOffer = async (params: UpdateOfferParams): Promise<void>
};
const updateTableOffer = async (params: UpdateOfferParams): Promise<void> => {
const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = databaseAccount.name;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateTableToAutoscale(subscriptionId, resourceGroup, accountName, params.collectionId);
@@ -290,8 +296,9 @@ const updateTableOffer = async (params: UpdateOfferParams): Promise<void> => {
};
const updateSqlDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => {
const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = databaseAccount.name;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateSqlDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
@@ -304,8 +311,9 @@ const updateSqlDatabaseOffer = async (params: UpdateOfferParams): Promise<void>
};
const updateMongoDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => {
const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = databaseAccount.name;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateMongoDBDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
@@ -318,8 +326,9 @@ const updateMongoDatabaseOffer = async (params: UpdateOfferParams): Promise<void
};
const updateCassandraKeyspaceOffer = async (params: UpdateOfferParams): Promise<void> => {
const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = databaseAccount.name;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateCassandraKeyspaceToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
@@ -332,8 +341,9 @@ const updateCassandraKeyspaceOffer = async (params: UpdateOfferParams): Promise<
};
const updateGremlinDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => {
const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = databaseAccount.name;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateGremlinDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);

View File

@@ -1,17 +1,18 @@
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import {
createUpdateSqlStoredProcedure,
getSqlStoredProcedure,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import {
SqlStoredProcedureCreateUpdateParameters,
SqlStoredProcedureResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import {
createUpdateSqlStoredProcedure,
getSqlStoredProcedure,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function updateStoredProcedure(
databaseId: string,
@@ -20,13 +21,15 @@ export async function updateStoredProcedure(
): Promise<StoredProcedureDefinition & Resource> {
const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`);
try {
const { authType, useSDKOperations, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
if (authType === AuthType.AAD && !useSDKOperations && apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const getResponse = await getSqlStoredProcedure(
subscriptionId,
resourceGroup,
databaseAccount.name,
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
storedProcedure.id
@@ -40,9 +43,9 @@ export async function updateStoredProcedure(
},
};
const rpResponse = await createUpdateSqlStoredProcedure(
subscriptionId,
resourceGroup,
databaseAccount.name,
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
storedProcedure.id,

View File

@@ -1,14 +1,15 @@
import { TriggerDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import {
SqlTriggerCreateUpdateParameters,
SqlTriggerResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { TriggerDefinition } from "@azure/cosmos";
import { client } from "../CosmosClient";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function updateTrigger(
databaseId: string,
@@ -16,13 +17,16 @@ export async function updateTrigger(
trigger: TriggerDefinition
): Promise<TriggerDefinition> {
const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`);
const { authType, useSDKOperations, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
try {
if (authType === AuthType.AAD && !useSDKOperations && apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const getResponse = await getSqlTrigger(
subscriptionId,
resourceGroup,
databaseAccount.name,
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
trigger.id
@@ -36,9 +40,9 @@ export async function updateTrigger(
},
};
const rpResponse = await createUpdateSqlTrigger(
subscriptionId,
resourceGroup,
databaseAccount.name,
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
trigger.id,

View File

@@ -1,17 +1,18 @@
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import {
createUpdateSqlUserDefinedFunction,
getSqlUserDefinedFunction,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import {
SqlUserDefinedFunctionCreateUpdateParameters,
SqlUserDefinedFunctionResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import {
createUpdateSqlUserDefinedFunction,
getSqlUserDefinedFunction,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function updateUserDefinedFunction(
databaseId: string,
@@ -19,13 +20,16 @@ export async function updateUserDefinedFunction(
userDefinedFunction: UserDefinedFunctionDefinition
): Promise<UserDefinedFunctionDefinition & Resource> {
const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`);
const { authType, useSDKOperations, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
try {
if (authType === AuthType.AAD && !useSDKOperations && apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const getResponse = await getSqlUserDefinedFunction(
subscriptionId,
resourceGroup,
databaseAccount.name,
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
userDefinedFunction.id
@@ -39,9 +43,9 @@ export async function updateUserDefinedFunction(
},
};
const rpResponse = await createUpdateSqlUserDefinedFunction(
subscriptionId,
resourceGroup,
databaseAccount.name,
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
userDefinedFunction.id,

View File

@@ -26,7 +26,6 @@ export interface ConfigContext {
GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it.
hostedExplorerURL: string;
armAPIVersion?: string;
allowedJunoOrigins: string[];
}
// Default configuration
@@ -54,13 +53,6 @@ let configContext: Readonly<ConfigContext> = {
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306
JUNO_ENDPOINT: "https://tools.cosmos.azure.com",
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
allowedJunoOrigins: [
"https://juno-test.documents-dev.windows-int.net",
"https://juno-test2.documents-dev.windows-int.net",
"https://tools.cosmos.azure.com",
"https://tools-staging.cosmos.azure.com",
"https://localhost",
],
};
export function resetConfigContext(): void {
@@ -94,18 +86,13 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
});
if (response.status === 200) {
try {
const { allowedParentFrameOrigins, allowedJunoOrigins, ...externalConfig } = await response.json();
const { allowedParentFrameOrigins, ...externalConfig } = await response.json();
Object.assign(configContext, externalConfig);
if (allowedParentFrameOrigins && allowedParentFrameOrigins.length > 0) {
updateConfigContext({
allowedParentFrameOrigins: [...configContext.allowedParentFrameOrigins, ...allowedParentFrameOrigins],
});
}
if (allowedJunoOrigins && allowedJunoOrigins.length > 0) {
updateConfigContext({
allowedJunoOrigins: [...configContext.allowedJunoOrigins, ...allowedJunoOrigins],
});
}
} catch (error) {
console.error("Unable to parse json in config file");
console.error(error);

View File

@@ -4,7 +4,6 @@
export enum TabKind {
SQLDocuments,
MongoDocuments,
SchemaAnalyzer,
TableEntities,
Graph,
SQLQuery,

View File

@@ -4,6 +4,7 @@ export interface DatabaseAccount {
location: string;
type: string;
kind: string;
tags: any;
properties: DatabaseAccountExtendedProperties;
}
@@ -120,10 +121,6 @@ export interface ISchemaRequest {
}
export interface Collection extends Resource {
// Only in Mongo collections loaded via ARM
shardKey?: {
[key: string]: string;
};
defaultTtl?: number;
indexingPolicy?: IndexingPolicy;
partitionKey?: PartitionKey;

View File

@@ -15,6 +15,7 @@ import StoredProcedure from "../Explorer/Tree/StoredProcedure";
import Trigger from "../Explorer/Tree/Trigger";
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
import { SelfServeType } from "../SelfServe/SelfServeUtils";
import { UploadDetails } from "../workers/upload/definitions";
import * as DataModels from "./DataModels";
import { SubscriptionType } from "./SubscriptionType";
@@ -22,14 +23,6 @@ export interface TokenProvider {
getAuthHeader(): Promise<Headers>;
}
export interface UploadDetailsRecord {
fileName: string;
numSucceeded: number;
numFailed: number;
numThrottled: number;
errors: string[];
}
export interface QueryResultsMetadata {
hasMoreResults: boolean;
firstItemIndex: number;
@@ -141,7 +134,6 @@ export interface Collection extends CollectionBase {
onTableEntitiesClick(): void;
onGraphDocumentsClick(): void;
onMongoDBDocumentsClick(): void;
onSchemaAnalyzerClick(): void;
openTab(): void;
onSettingsClick: () => Promise<void>;
@@ -182,7 +174,7 @@ export interface Collection extends CollectionBase {
onDragOver(source: Collection, event: { originalEvent: DragEvent }): void;
onDrop(source: Collection, event: { originalEvent: DragEvent }): void;
uploadFiles(fileList: FileList): Promise<{ data: UploadDetailsRecord[] }>;
uploadFiles(fileList: FileList): Promise<UploadDetails>;
getLabel(): string;
getPendingThroughputSplitNotification(): Promise<DataModels.Notification>;
@@ -277,6 +269,7 @@ export interface TabOptions {
tabKind: CollectionTabKind;
title: string;
tabPath: string;
isActive: ko.Observable<boolean>;
hashLocation: string;
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]) => void;
isTabsContentExpanded?: ko.Observable<boolean>;
@@ -367,7 +360,6 @@ export enum CollectionTabKind {
Schema = 19,
CollectionSettingsV2 = 20,
DatabaseSettingsV2 = 21,
SchemaAnalyzer = 22,
}
export enum TerminalKind {
@@ -398,9 +390,6 @@ export interface DataExplorerInputsFrame {
dataExplorerVersion?: string;
defaultCollectionThroughput?: CollectionCreationDefaults;
flights?: readonly string[];
features?: {
[key: string]: string;
};
}
export interface SelfServeFrameInputs {

View File

@@ -0,0 +1,8 @@
export enum DefaultAccountExperienceType {
DocumentDB = "DocumentDB",
Graph = "Graph",
MongoDB = "MongoDB",
Table = "Table",
Cassandra = "Cassandra",
ApiForMongoDB = "Azure Cosmos DB for MongoDB API",
}

7
src/Definitions/worker.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
declare module "worker-loader!*" {
class WebpackWorker extends Worker {
constructor();
}
export default WebpackWorker;
}

View File

@@ -8,6 +8,10 @@ describe("Component Registerer", () => {
expect(ko.components.isRegistered("input-typeahead")).toBe(true);
});
it("should register new-vertex-form component", () => {
expect(ko.components.isRegistered("new-vertex-form")).toBe(true);
});
it("should register error-display component", () => {
expect(ko.components.isRegistered("error-display")).toBe(true);
});
@@ -20,14 +24,83 @@ describe("Component Registerer", () => {
expect(ko.components.isRegistered("json-editor")).toBe(true);
});
it("should register documents-tab component", () => {
expect(ko.components.isRegistered("documents-tab")).toBe(true);
});
it("should register stored-procedure-tab component", () => {
expect(ko.components.isRegistered("stored-procedure-tab")).toBe(true);
});
it("should register trigger-tab component", () => {
expect(ko.components.isRegistered("trigger-tab")).toBe(true);
});
it("should register user-defined-function-tab component", () => {
expect(ko.components.isRegistered("user-defined-function-tab")).toBe(true);
});
it("should register settings-tab-v2 component", () => {
expect(ko.components.isRegistered("database-settings-tab-v2")).toBe(true);
expect(ko.components.isRegistered("collection-settings-tab-v2")).toBe(true);
});
it("should register query-tab component", () => {
expect(ko.components.isRegistered("query-tab")).toBe(true);
});
it("should register tables-query-tab component", () => {
expect(ko.components.isRegistered("tables-query-tab")).toBe(true);
});
it("should register graph-tab component", () => {
expect(ko.components.isRegistered("graph-tab")).toBe(true);
});
it("should register notebookv2-tab component", () => {
expect(ko.components.isRegistered("notebookv2-tab")).toBe(true);
});
it("should register terminal-tab component", () => {
expect(ko.components.isRegistered("terminal-tab")).toBe(true);
});
it("should register mongo-shell-tab component", () => {
expect(ko.components.isRegistered("mongo-shell-tab")).toBe(true);
});
it("should registeradd-collection-pane component", () => {
expect(ko.components.isRegistered("add-collection-pane")).toBe(true);
});
it("should register delete-collection-confirmation-pane component", () => {
expect(ko.components.isRegistered("delete-collection-confirmation-pane")).toBe(true);
});
it("should register save-query-pane component", () => {
expect(ko.components.isRegistered("save-query-pane")).toBe(true);
});
it("should register browse-queries-pane component", () => {
expect(ko.components.isRegistered("browse-queries-pane")).toBe(true);
});
it("should register graph-new-vertex-pane component", () => {
expect(ko.components.isRegistered("graph-new-vertex-pane")).toBe(true);
});
it("should register graph-styling-pane component", () => {
expect(ko.components.isRegistered("graph-styling-pane")).toBe(true);
});
it("should register string-input-pane component", () => {
expect(ko.components.isRegistered("string-input-pane")).toBe(true);
});
it("should register setup-notebooks-pane component", () => {
expect(ko.components.isRegistered("setup-notebooks-pane")).toBe(true);
});
it("should register dynamic-list component", () => {
expect(ko.components.isRegistered("dynamic-list")).toBe(true);
});

View File

@@ -7,9 +7,27 @@ import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahea
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
import { GraphStyleComponent } from "./Graph/GraphStyleComponent/GraphStyleComponent";
import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent";
import * as PaneComponents from "./Panes/PaneComponents";
import ConflictsTab from "./Tabs/ConflictsTab";
import DatabaseSettingsTab from "./Tabs/DatabaseSettingsTab";
import DocumentsTab from "./Tabs/DocumentsTab";
import GalleryTab from "./Tabs/GalleryTab";
import GraphTab from "./Tabs/GraphTab";
import MongoShellTab from "./Tabs/MongoShellTab";
import NotebookTabV2 from "./Tabs/NotebookV2Tab";
import NotebookViewerTab from "./Tabs/NotebookViewerTab";
import QueryTab from "./Tabs/QueryTab";
import QueryTablesTab from "./Tabs/QueryTablesTab";
import { DatabaseSettingsTabV2, SettingsTabV2 } from "./Tabs/SettingsTabV2";
import StoredProcedureTab from "./Tabs/StoredProcedureTab";
import TabsManagerTemplate from "./Tabs/TabsManager.html";
import TerminalTab from "./Tabs/TerminalTab";
import TriggerTab from "./Tabs/TriggerTab";
import UserDefinedFunctionTab from "./Tabs/UserDefinedFunctionTab";
ko.components.register("input-typeahead", new InputTypeaheadComponent());
ko.components.register("new-vertex-form", NewVertexComponent);
ko.components.register("error-display", new ErrorDisplayComponent());
ko.components.register("graph-style", GraphStyleComponent);
ko.components.register("editor", new EditorComponent());
@@ -17,11 +35,46 @@ ko.components.register("json-editor", new JsonEditorComponent());
ko.components.register("diff-editor", new DiffEditorComponent());
ko.components.register("dynamic-list", DynamicListComponent);
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
ko.components.register("tabs-manager", { template: TabsManagerTemplate });
// Collection Tabs
[
DocumentsTab,
StoredProcedureTab,
TriggerTab,
UserDefinedFunctionTab,
SettingsTabV2,
QueryTab,
QueryTablesTab,
GraphTab,
MongoShellTab,
ConflictsTab,
NotebookTabV2,
TerminalTab,
GalleryTab,
NotebookViewerTab,
DatabaseSettingsTab,
DatabaseSettingsTabV2,
].forEach(({ component: { name, template } }) => ko.components.register(name, { template }));
// Panes
ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent());
ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent());
ko.components.register(
"delete-collection-confirmation-pane",
new PaneComponents.DeleteCollectionConfirmationPaneComponent()
);
ko.components.register("graph-new-vertex-pane", new PaneComponents.GraphNewVertexPaneComponent());
ko.components.register("graph-styling-pane", new PaneComponents.GraphStylingPaneComponent());
ko.components.register("table-add-entity-pane", new PaneComponents.TableAddEntityPaneComponent());
ko.components.register("table-edit-entity-pane", new PaneComponents.TableEditEntityPaneComponent());
ko.components.register("table-column-options-pane", new PaneComponents.TableColumnOptionsPaneComponent());
ko.components.register("table-query-select-pane", new PaneComponents.TableQuerySelectPaneComponent());
ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent());
ko.components.register("load-query-pane", new PaneComponents.LoadQueryPaneComponent());
ko.components.register("save-query-pane", new PaneComponents.SaveQueryPaneComponent());
ko.components.register("browse-queries-pane", new PaneComponents.BrowseQueriesPaneComponent());
ko.components.register("string-input-pane", new PaneComponents.StringInputPaneComponent());
ko.components.register("setup-notebooks-pane", new PaneComponents.SetupNotebooksPaneComponent());
ko.components.register("github-repos-pane", new PaneComponents.GitHubReposPaneComponent());

View File

@@ -10,6 +10,7 @@ import DeleteTriggerIcon from "../../images/DeleteTrigger.svg";
import DeleteUDFIcon from "../../images/DeleteUDF.svg";
import HostedTerminalIcon from "../../images/Hosted-Terminal.svg";
import * as ViewModels from "../Contracts/ViewModels";
import { DefaultAccountExperienceType } from "../DefaultAccountExperienceType";
import { userContext } from "../UserContext";
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
import Explorer from "./Explorer";
@@ -29,16 +30,16 @@ export interface DatabaseContextMenuButtonParams {
* New resource tree (in ReactJS)
*/
export class ResourceTreeContextMenuButtonFactory {
public static createDatabaseContextMenu(container: Explorer, databaseId: string): TreeNodeMenuItem[] {
public static createDatabaseContextMenu(container: Explorer): TreeNodeMenuItem[] {
const items: TreeNodeMenuItem[] = [
{
iconSrc: AddCollectionIcon,
onClick: () => container.onNewCollectionClicked(databaseId),
onClick: () => container.onNewCollectionClicked(),
label: container.addCollectionText(),
},
];
if (userContext.apiType !== "Tables") {
if (userContext.defaultExperience !== DefaultAccountExperienceType.Table) {
items.push({
iconSrc: DeleteDatabaseIcon,
onClick: () => container.openDeleteDatabaseConfirmationPane(),
@@ -54,7 +55,7 @@ export class ResourceTreeContextMenuButtonFactory {
selectedCollection: ViewModels.Collection
): TreeNodeMenuItem[] {
const items: TreeNodeMenuItem[] = [];
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
if (container.isPreferredApiDocumentDB() || container.isPreferredApiGraph()) {
items.push({
iconSrc: AddSqlQueryIcon,
onClick: () => selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null),
@@ -62,7 +63,7 @@ export class ResourceTreeContextMenuButtonFactory {
});
}
if (userContext.apiType === "Mongo") {
if (container.isPreferredApiMongoDB()) {
items.push({
iconSrc: AddSqlQueryIcon,
onClick: () => selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null),
@@ -79,7 +80,7 @@ export class ResourceTreeContextMenuButtonFactory {
});
}
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
if (container.isPreferredApiDocumentDB() || container.isPreferredApiGraph()) {
items.push({
iconSrc: AddStoredProcedureIcon,
onClick: () => {
@@ -122,7 +123,7 @@ export class ResourceTreeContextMenuButtonFactory {
container: Explorer,
storedProcedure: StoredProcedure
): TreeNodeMenuItem[] {
if (userContext.apiType === "Cassandra") {
if (container.isPreferredApiCassandra()) {
return [];
}
@@ -136,7 +137,7 @@ export class ResourceTreeContextMenuButtonFactory {
}
public static createTriggerContextMenuItems(container: Explorer, trigger: Trigger): TreeNodeMenuItem[] {
if (userContext.apiType === "Cassandra") {
if (container.isPreferredApiCassandra()) {
return [];
}
@@ -153,7 +154,7 @@ export class ResourceTreeContextMenuButtonFactory {
container: Explorer,
userDefinedFunction: UserDefinedFunction
): TreeNodeMenuItem[] {
if (userContext.apiType === "Cassandra") {
if (container.isPreferredApiCassandra()) {
return [];
}

View File

@@ -1,8 +1,9 @@
import { DefaultButton, IButtonStyles, IContextualMenuItem, IContextualMenuProps } from "@fluentui/react";
import * as React from "react";
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
import * as Logger from "../../../Common/Logger";
import { ArcadiaWorkspace, SparkPool } from "../../../Contracts/DataModels";
import { DefaultButton, IButtonStyles } from "office-ui-fabric-react/lib/Button";
import { IContextualMenuItem, IContextualMenuProps } from "office-ui-fabric-react/lib/ContextualMenu";
import * as Logger from "../../../Common/Logger";
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
export interface ArcadiaMenuPickerProps {
selectText?: string;

View File

@@ -1,11 +1,10 @@
import { Icon, Label, Stack } from "@fluentui/react";
import { Icon, Label, Stack } from "office-ui-fabric-react";
import * as React from "react";
import { accordionStackTokens } from "../Settings/SettingsRenderUtils";
export interface CollapsibleSectionProps {
title: string;
isExpandedByDefault: boolean;
onExpand?: () => void;
}
export interface CollapsibleSectionState {
@@ -24,12 +23,6 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
this.setState({ isExpanded: !this.state.isExpanded });
};
public componentDidUpdate(): void {
if (this.state.isExpanded && this.props.onExpand) {
this.props.onExpand();
}
}
public render(): JSX.Element {
return (
<>

View File

@@ -1,21 +1,15 @@
import * as React from "react";
import { Dialog as FluentDialog, DialogType, DialogFooter, IDialogProps } from "office-ui-fabric-react/lib/Dialog";
import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button";
import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
import { Link } from "office-ui-fabric-react/lib/Link";
import {
ChoiceGroup,
DefaultButton,
Dialog as FluentDialog,
DialogFooter,
DialogType,
FontIcon,
IButtonProps,
IChoiceGroupProps,
IDialogProps,
IProgressIndicatorProps,
ITextFieldProps,
Link,
PrimaryButton,
ProgressIndicator,
TextField,
} from "@fluentui/react";
import React, { FunctionComponent } from "react";
} from "office-ui-fabric-react";
export interface TextFieldProps extends ITextFieldProps {
label: string;
@@ -56,69 +50,61 @@ const DIALOG_TITLE_FONT_SIZE = "17px";
const DIALOG_TITLE_FONT_WEIGHT = 400;
const DIALOG_SUBTEXT_FONT_SIZE = "15px";
export const Dialog: FunctionComponent<DialogProps> = ({
title,
subText,
isModal,
visible,
choiceGroupProps,
textFieldProps,
linkProps,
progressIndicatorProps,
primaryButtonText,
secondaryButtonText,
onPrimaryButtonClick,
onSecondaryButtonClick,
primaryButtonDisabled,
type,
showCloseButton,
onDismiss,
}: DialogProps) => {
const dialogProps: IDialogProps = {
hidden: !visible,
dialogContentProps: {
type: type || DialogType.normal,
title,
subText,
styles: {
title: { fontSize: DIALOG_TITLE_FONT_SIZE, fontWeight: DIALOG_TITLE_FONT_WEIGHT },
subText: { fontSize: DIALOG_SUBTEXT_FONT_SIZE },
export class Dialog extends React.Component<DialogProps> {
constructor(props: DialogProps) {
super(props);
}
public render(): JSX.Element {
const dialogProps: IDialogProps = {
hidden: !this.props.visible,
dialogContentProps: {
type: this.props.type || DialogType.normal,
title: this.props.title,
subText: this.props.subText,
styles: {
title: { fontSize: DIALOG_TITLE_FONT_SIZE, fontWeight: DIALOG_TITLE_FONT_WEIGHT },
subText: { fontSize: DIALOG_SUBTEXT_FONT_SIZE },
},
showCloseButton: this.props.showCloseButton || false,
onDismiss: this.props.onDismiss,
},
showCloseButton: showCloseButton || false,
onDismiss,
},
modalProps: { isBlocking: isModal, isDarkOverlay: false },
minWidth: DIALOG_MIN_WIDTH,
maxWidth: DIALOG_MAX_WIDTH,
};
modalProps: { isBlocking: this.props.isModal, isDarkOverlay: false },
minWidth: DIALOG_MIN_WIDTH,
maxWidth: DIALOG_MAX_WIDTH,
};
const choiceGroupProps: IChoiceGroupProps = this.props.choiceGroupProps;
const textFieldProps: ITextFieldProps = this.props.textFieldProps;
const linkProps: LinkProps = this.props.linkProps;
const progressIndicatorProps: IProgressIndicatorProps = this.props.progressIndicatorProps;
const primaryButtonProps: IButtonProps = {
text: this.props.primaryButtonText,
disabled: this.props.primaryButtonDisabled || false,
onClick: this.props.onPrimaryButtonClick,
};
const secondaryButtonProps: IButtonProps =
this.props.secondaryButtonText && this.props.onSecondaryButtonClick
? {
text: this.props.secondaryButtonText,
onClick: this.props.onSecondaryButtonClick,
}
: undefined;
const primaryButtonProps: IButtonProps = {
text: primaryButtonText,
disabled: primaryButtonDisabled || false,
onClick: onPrimaryButtonClick,
};
const secondaryButtonProps: IButtonProps =
secondaryButtonText && onSecondaryButtonClick
? {
text: secondaryButtonText,
onClick: onSecondaryButtonClick,
}
: undefined;
return (
<FluentDialog {...dialogProps}>
{choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />}
{textFieldProps && <TextField {...textFieldProps} />}
{linkProps && (
<Link href={linkProps.linkUrl} target="_blank">
{linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
</Link>
)}
{progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />}
<DialogFooter>
<PrimaryButton {...primaryButtonProps} />
{secondaryButtonProps && <DefaultButton {...secondaryButtonProps} />}
</DialogFooter>
</FluentDialog>
);
};
return (
<FluentDialog {...dialogProps}>
{choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />}
{textFieldProps && <TextField {...textFieldProps} />}
{linkProps && (
<Link href={linkProps.linkUrl} target="_blank">
{linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
</Link>
)}
{progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />}
<DialogFooter>
<PrimaryButton {...primaryButtonProps} />
{secondaryButtonProps && <DefaultButton {...secondaryButtonProps} />}
</DialogFooter>
</FluentDialog>
);
}
}

View File

@@ -1,6 +1,6 @@
import * as ViewModels from "../../../Contracts/ViewModels";
import { loadMonaco, monaco } from "../../LazyMonaco";
import template from "./diff-editor-component.html";
import * as monaco from "monaco-editor";
/**
* Helper class for ko component registration
@@ -92,7 +92,7 @@ export class DiffEditorViewModel {
/**
* Create the monaco editor on diff mode and attach to DOM
*/
protected async createDiffEditor(
protected createDiffEditor(
originalContent: string,
modifiedContent: string,
createCallback: (e: monaco.editor.IStandaloneDiffEditor) => void
@@ -111,7 +111,7 @@ export class DiffEditorViewModel {
}
const language = this.params.editorLanguage || "json";
const monaco = await loadMonaco();
const originalModel = monaco.editor.createModel(originalContent, language);
const modifiedModel = monaco.editor.createModel(modifiedContent, language);
const diffEditor: monaco.editor.IStandaloneDiffEditor = monaco.editor.createDiffEditor(

View File

@@ -2,9 +2,9 @@
* React component for Switch Directory
*/
import { Dropdown, IDropdownOption, IDropdownProps } from "@fluentui/react";
import * as React from "react";
import _ from "underscore";
import * as React from "react";
import { Dropdown, IDropdownOption, IDropdownProps } from "office-ui-fabric-react/lib/Dropdown";
import { Tenant } from "../../../Contracts/DataModels";
export interface DefaultDirectoryDropdownProps {

Some files were not shown because too many files have changed in this diff Show More