Compare commits

..

14 Commits

Author SHA1 Message Date
Steve Faulkner
6870bd9b54 Tweaks 2020-07-23 21:53:46 -05:00
Steve Faulkner
8aeff8fb45 Tweaks 2020-07-23 21:52:05 -05:00
Steve Faulkner
ee6f635458 Tweak 2020-07-23 19:03:43 -05:00
Steve Faulkner
ad115a2cce Functional version 2020-07-23 19:02:09 -05:00
Steve Faulkner
0c255a55c8 Fix strict 2020-07-23 18:40:55 -05:00
Steve Faulkner
08e84d93b5 Delete -> destory 2020-07-23 18:20:16 -05:00
Steve Faulkner
e2895b62b4 More updates 2020-07-23 18:18:58 -05:00
Steve Faulkner
155aacdf63 Sanitize usage of delete 2020-07-23 18:17:30 -05:00
Steve Faulkner
f4f2d00d7f More tweaks 2020-07-23 18:12:58 -05:00
Steve Faulkner
f1812077e9 Split up generators 2020-07-23 16:35:05 -05:00
Steve Faulkner
cfe9bd8303 More updates 2020-07-23 11:17:20 -05:00
Steve Faulkner
9db8d11801 Setup Namespaces 2020-07-22 22:40:29 -05:00
Steve Faulkner
769a2e7d1c more tweaks 2020-07-22 21:56:45 -05:00
Steve Faulkner
df544f88b2 First pass at a generated ARM client 2020-07-22 21:37:13 -05:00
256 changed files with 14687 additions and 8233 deletions

View File

@@ -298,9 +298,11 @@ src/Utils/DatabaseAccountUtils.ts
src/Utils/JunoUtils.ts src/Utils/JunoUtils.ts
src/Utils/MessageValidation.ts src/Utils/MessageValidation.ts
src/Utils/NotebookConfigurationUtils.ts src/Utils/NotebookConfigurationUtils.ts
src/Utils/NotificationConsoleUtils.ts
src/Utils/OfferUtils.test.ts src/Utils/OfferUtils.test.ts
src/Utils/OfferUtils.ts src/Utils/OfferUtils.ts
src/Utils/PricingUtils.test.ts src/Utils/PricingUtils.test.ts
src/Utils/PricingUtils.ts
src/Utils/QueryUtils.test.ts src/Utils/QueryUtils.test.ts
src/Utils/QueryUtils.ts src/Utils/QueryUtils.ts
src/Utils/StringUtils.test.ts src/Utils/StringUtils.test.ts

View File

@@ -3,7 +3,7 @@ module.exports = {
browser: true, browser: true,
es6: true es6: true
}, },
plugins: ["@typescript-eslint", "no-null", "prefer-arrow"], plugins: ["@typescript-eslint", "no-null"],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
globals: { globals: {
Atomics: "readonly", Atomics: "readonly",
@@ -40,8 +40,6 @@ module.exports = {
"@typescript-eslint/no-unused-vars": "error", "@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-extraneous-class": "error", "@typescript-eslint/no-extraneous-class": "error",
"no-null/no-null": "error", "no-null/no-null": "error",
"@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-explicit-any": "error"
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
eqeqeq: "error"
} }
}; };

2
.github/CODEOWNERS vendored
View File

@@ -1 +1 @@
* @Azure/cosmos-explorer-owners @Azure/azure-cosmos-explorer-developers * @Azure/cosmos-explorer-owners

View File

@@ -1,13 +1,9 @@
name: CI name: CI
on: on:
push: push:
branches: branches: [master]
- master
- hotfix/*
- release/*
pull_request: pull_request:
branches: branches: [master]
- master
jobs: jobs:
compile: compile:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -56,7 +52,6 @@ jobs:
- run: npm run test - run: npm run test
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [lint, format, compile, unittest]
name: "Build" name: "Build"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -80,7 +75,6 @@ jobs:
path: dist/ path: dist/
endtoendemulator: endtoendemulator:
name: "End To End Tests | Emulator | SQL" name: "End To End Tests | Emulator | SQL"
needs: [lint, format, compile, unittest]
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -107,7 +101,6 @@ jobs:
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
endtoendsql: endtoendsql:
name: "End To End Tests | SQL" name: "End To End Tests | SQL"
needs: [lint, format, compile, unittest]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -134,14 +127,8 @@ jobs:
NODE_TLS_REJECT_UNAUTHORIZED: 0 NODE_TLS_REJECT_UNAUTHORIZED: 0
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
CYPRESS_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }} CYPRESS_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }}
- uses: actions/upload-artifact@v2
name: videos
if: ${{ failure() }}
with:
path: "**/*.mp4"
endtoendmongo: endtoendmongo:
name: "End To End Tests | Mongo" name: "End To End Tests | Mongo"
needs: [lint, format, compile, unittest]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -168,14 +155,8 @@ jobs:
NODE_TLS_REJECT_UNAUTHORIZED: 0 NODE_TLS_REJECT_UNAUTHORIZED: 0
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
CYPRESS_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }} CYPRESS_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
- uses: actions/upload-artifact@v2
if: ${{ failure() }}
name: videos
with:
path: "**/*.mp4"
accessibility: accessibility:
name: "Accessibility | Hosted" name: "Accessibility | Hosted"
needs: [lint, format, compile, unittest]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -198,7 +179,7 @@ jobs:
NODE_TLS_REJECT_UNAUTHORIZED: 0 NODE_TLS_REJECT_UNAUTHORIZED: 0
nuget: nuget:
name: Publish Nuget name: Publish Nuget
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/') if: github.ref == 'refs/heads/master'
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo] needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo]
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
@@ -222,7 +203,7 @@ jobs:
path: "*.nupkg" path: "*.nupkg"
nugetmpac: nugetmpac:
name: Publish Nuget MPAC name: Publish Nuget MPAC
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/') if: github.ref == 'refs/heads/master'
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo] needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo]
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:

View File

@@ -3,7 +3,7 @@
"pluginsFile": false, "pluginsFile": false,
"fixturesFolder": false, "fixturesFolder": false,
"supportFile": "./support/index.js", "supportFile": "./support/index.js",
"defaultCommandTimeout": 90000, "defaultCommandTimeout": 60000,
"chromeWebSecurity": false, "chromeWebSecurity": false,
"reporter": "mochawesome", "reporter": "mochawesome",
"reporterOptions": { "reporterOptions": {

View File

@@ -6,8 +6,8 @@
"scripts": { "scripts": {
"test": "cypress run", "test": "cypress run",
"wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/", "wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/",
"test:sql": "cypress run --browser chrome --spec \"./integration/dataexplorer/SQL/*\"", "test:sql": "cypress run --browser chrome --headless --spec \"./integration/dataexplorer/SQL/*\"",
"test:ci": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/ https-get://0.0.0.0:8081/_explorer/index.html && cypress run --browser edge --headless", "test:ci": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/ https-get://0.0.0.0:8081/_explorer/index.html && cypress run --browser chrome --headless",
"test:debug": "cypress open" "test:debug": "cypress open"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -39,10 +39,10 @@ module.exports = {
// An object that configures minimum threshold enforcement for coverage results // An object that configures minimum threshold enforcement for coverage results
coverageThreshold: { coverageThreshold: {
global: { global: {
branches: 20, branches: 18,
functions: 24, functions: 22,
lines: 30, lines: 28,
statements: 29.0 statements: 27
} }
}, },

94
package-lock.json generated
View File

@@ -5,14 +5,13 @@
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"@azure/cosmos": { "@azure/cosmos": {
"version": "3.9.0", "version": "3.7.4",
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-3.9.0.tgz", "resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-3.7.4.tgz",
"integrity": "sha512-SA+QB54I8Dvg/ZolHpsEDLK/sbSB9sFmSU1ElnMTFw88TVik+LYHq4o/srU2TY6Gr1BketjPmgLVEqrmnRvjkw==", "integrity": "sha512-IbSEadapQDajSCXj7gUc8OklkOd/oAY4w7XBLHouWc4iKQTtntb2DmGjhrbh2W5Ku+pmBSr1GTApCjQ55iIjlQ==",
"requires": { "requires": {
"@types/debug": "^4.1.4", "@types/debug": "^4.1.4",
"debug": "^4.1.1", "debug": "^4.1.1",
"fast-json-stable-stringify": "^2.0.0", "fast-json-stable-stringify": "^2.0.0",
"jsbi": "^3.1.3",
"node-abort-controller": "^1.0.4", "node-abort-controller": "^1.0.4",
"node-fetch": "^2.6.0", "node-fetch": "^2.6.0",
"os-name": "^3.1.0", "os-name": "^3.1.0",
@@ -23,14 +22,14 @@
}, },
"dependencies": { "dependencies": {
"tslib": { "tslib": {
"version": "2.0.1", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz",
"integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==" "integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g=="
}, },
"uuid": { "uuid": {
"version": "8.3.0", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.2.0.tgz",
"integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==" "integrity": "sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q=="
} }
} }
}, },
@@ -7721,11 +7720,6 @@
"@types/react": "*" "@types/react": "*"
} }
}, },
"@types/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="
},
"@types/shallowequal": { "@types/shallowequal": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/shallowequal/-/shallowequal-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@types/shallowequal/-/shallowequal-1.1.1.tgz",
@@ -9572,11 +9566,6 @@
"resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz",
"integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=" "integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA="
}, },
"base64-arraybuffer": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz",
"integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ=="
},
"base64-js": { "base64-js": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
@@ -10133,9 +10122,9 @@
"dev": true "dev": true
}, },
"canvas": { "canvas": {
"version": "2.6.1", "version": "2.6.0",
"resolved": "https://registry.npmjs.org/canvas/-/canvas-2.6.1.tgz", "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.6.0.tgz",
"integrity": "sha512-S98rKsPcuhfTcYbtF53UIJhcbgIAK533d1kJKMwsMwAIFgfd58MOyxRud3kktlzWiEkFliaJtvyZCBtud/XVEA==", "integrity": "sha512-bEO9f1ThmbknLPxCa8Es7obPlN9W3stB1bo7njlhOFKIdUTldeTqXCh9YclCPAi2pSQs84XA0jq/QEZXSzgyMw==",
"requires": { "requires": {
"nan": "^2.14.0", "nan": "^2.14.0",
"node-pre-gyp": "^0.11.0", "node-pre-gyp": "^0.11.0",
@@ -10940,14 +10929,6 @@
"resolved": "https://registry.npmjs.org/css-element-queries/-/css-element-queries-1.1.1.tgz", "resolved": "https://registry.npmjs.org/css-element-queries/-/css-element-queries-1.1.1.tgz",
"integrity": "sha512-/PX6Bkk77ShgbOx/mpawHdEvS3PGgy1mmMktcztDPndWdMJxcorcQiivrs+nEljqtBpvNEhAmQky9tQR6FSm8Q==" "integrity": "sha512-/PX6Bkk77ShgbOx/mpawHdEvS3PGgy1mmMktcztDPndWdMJxcorcQiivrs+nEljqtBpvNEhAmQky9tQR6FSm8Q=="
}, },
"css-line-break": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-1.1.1.tgz",
"integrity": "sha512-1feNVaM4Fyzdj4mKPIQNL2n70MmuYzAXZ1aytlROFX1JsOo070OsugwGjj7nl6jnDJWHDM8zRZswkmeYVWZJQA==",
"requires": {
"base64-arraybuffer": "^0.2.0"
}
},
"css-loader": { "css-loader": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-1.0.0.tgz", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-1.0.0.tgz",
@@ -12818,12 +12799,6 @@
"integrity": "sha1-EjaoEjkTkKGHetQAfCbnRTQclR8=", "integrity": "sha1-EjaoEjkTkKGHetQAfCbnRTQclR8=",
"dev": true "dev": true
}, },
"eslint-plugin-prefer-arrow": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-prefer-arrow/-/eslint-plugin-prefer-arrow-1.2.2.tgz",
"integrity": "sha512-C8YMhL+r8RMeMdYAw/rQtE6xNdMulj+zGWud/qIGnlmomiPRaLDGLMeskZ3alN6uMBojmooRimtdrXebLN4svQ==",
"dev": true
},
"eslint-plugin-react": { "eslint-plugin-react": {
"version": "7.20.0", "version": "7.20.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.20.0.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.20.0.tgz",
@@ -15637,14 +15612,6 @@
} }
} }
}, },
"html2canvas": {
"version": "1.0.0-rc.5",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.0.0-rc.5.tgz",
"integrity": "sha512-DtNqPxJNXPoTajs+lVQzGS1SULRI4GQaROeU5R41xH8acffHukxRh/NBVcTBsfCkJSkLq91rih5TpbEwUP9yWA==",
"requires": {
"css-line-break": "1.1.1"
}
},
"htmlparser2": { "htmlparser2": {
"version": "3.10.1", "version": "3.10.1",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
@@ -20205,11 +20172,6 @@
"esprima": "^4.0.0" "esprima": "^4.0.0"
} }
}, },
"jsbi": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/jsbi/-/jsbi-3.1.3.tgz",
"integrity": "sha512-nBJqA0C6Qns+ZxurbEoIR56wyjiUszpNy70FHvxO5ervMoCbZVE3z3kxr5nKGhlxr/9MhKTSUBs7cAwwuf3g9w=="
},
"jsbn": { "jsbn": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
@@ -21540,9 +21502,9 @@
} }
}, },
"needle": { "needle": {
"version": "2.5.0", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/needle/-/needle-2.5.0.tgz", "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.1.tgz",
"integrity": "sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA==", "integrity": "sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g==",
"requires": { "requires": {
"debug": "^3.2.6", "debug": "^3.2.6",
"iconv-lite": "^0.4.4", "iconv-lite": "^0.4.4",
@@ -22297,11 +22259,11 @@
"integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=" "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo="
}, },
"p-retry": { "p-retry": {
"version": "4.2.0", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.2.0.tgz", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz",
"integrity": "sha512-jPH38/MRh263KKcq0wBNOGFJbm+U6784RilTmHjB/HM9kH9V8WlCpVUcdOmip9cjXOh6MxZ5yk1z2SjDUJfWmA==", "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==",
"dev": true,
"requires": { "requires": {
"@types/retry": "^0.12.0",
"retry": "^0.12.0" "retry": "^0.12.0"
} }
}, },
@@ -24079,7 +24041,8 @@
"retry": { "retry": {
"version": "0.12.0", "version": "0.12.0",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
"integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=",
"dev": true
}, },
"reusify": { "reusify": {
"version": "1.0.4", "version": "1.0.4",
@@ -24636,9 +24599,9 @@
"integrity": "sha1-ZfDBWZNSs1Ny7KrlolDmEHN27Wk=" "integrity": "sha1-ZfDBWZNSs1Ny7KrlolDmEHN27Wk="
}, },
"simple-concat": { "simple-concat": {
"version": "1.0.1", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz",
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY="
}, },
"simple-get": { "simple-get": {
"version": "3.1.0", "version": "3.1.0",
@@ -27482,15 +27445,6 @@
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true "dev": true
}, },
"p-retry": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz",
"integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==",
"dev": true,
"requires": {
"retry": "^0.12.0"
}
},
"schema-utils": { "schema-utils": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",

View File

@@ -4,7 +4,7 @@
"description": "Cosmos Explorer", "description": "Cosmos Explorer",
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"@azure/cosmos": "3.9.0", "@azure/cosmos": "3.7.4",
"@azure/cosmos-language-service": "0.0.4", "@azure/cosmos-language-service": "0.0.4",
"@jupyterlab/services": "4.2.0", "@jupyterlab/services": "4.2.0",
"@jupyterlab/terminal": "1.2.1", "@jupyterlab/terminal": "1.2.1",
@@ -42,7 +42,7 @@
"applicationinsights": "1.8.0", "applicationinsights": "1.8.0",
"babel-polyfill": "6.26.0", "babel-polyfill": "6.26.0",
"bootstrap": "3.4.1", "bootstrap": "3.4.1",
"canvas": "2.6.1", "canvas": "2.6.0",
"clean-webpack-plugin": "0.1.19", "clean-webpack-plugin": "0.1.19",
"copy-webpack-plugin": "6.0.2", "copy-webpack-plugin": "6.0.2",
"crossroads": "0.12.2", "crossroads": "0.12.2",
@@ -56,7 +56,6 @@
"es6-symbol": "3.1.3", "es6-symbol": "3.1.3",
"eslint-plugin-jest": "23.13.2", "eslint-plugin-jest": "23.13.2",
"hasher": "1.2.0", "hasher": "1.2.0",
"html2canvas": "1.0.0-rc.5",
"immutable": "4.0.0-rc.12", "immutable": "4.0.0-rc.12",
"is-ci": "2.0.0", "is-ci": "2.0.0",
"jquery": "3.5.1", "jquery": "3.5.1",
@@ -67,7 +66,6 @@
"monaco-editor": "0.15.6", "monaco-editor": "0.15.6",
"object.entries": "1.1.0", "object.entries": "1.1.0",
"office-ui-fabric-react": "7.121.10", "office-ui-fabric-react": "7.121.10",
"p-retry": "4.2.0",
"plotly.js-cartesian-dist-min": "1.52.3", "plotly.js-cartesian-dist-min": "1.52.3",
"promise-polyfill": "8.1.0", "promise-polyfill": "8.1.0",
"promise.prototype.finally": "3.1.0", "promise.prototype.finally": "3.1.0",
@@ -135,7 +133,6 @@
"eslint": "7.3.1", "eslint": "7.3.1",
"eslint-cli": "1.1.1", "eslint-cli": "1.1.1",
"eslint-plugin-no-null": "1.0.2", "eslint-plugin-no-null": "1.0.2",
"eslint-plugin-prefer-arrow": "1.2.2",
"eslint-plugin-react": "7.20.0", "eslint-plugin-react": "7.20.0",
"expose-loader": "0.7.5", "expose-loader": "0.7.5",
"file-loader": "2.0.0", "file-loader": "2.0.0",

View File

@@ -3,8 +3,8 @@
"offerThroughput": 400, "offerThroughput": 400,
"databaseLevelThroughput": false, "databaseLevelThroughput": false,
"collectionId": "Persons", "collectionId": "Persons",
"createNewDatabase": true, "rupmEnabled": false,
"partitionKey": { "kind": "Hash", "paths": ["/firstname"], "version": 1 }, "partitionKey": { "kind": "Hash", "paths": ["/firstname"] },
"data": [ "data": [
{ {
"firstname": "Eva", "firstname": "Eva",

13
src/Api/Apis.ts Normal file
View File

@@ -0,0 +1,13 @@
import * as ViewModels from "../Contracts/ViewModels";
export class DefaultApi implements ViewModels.CosmosDbApi {
public isSystemDatabasePredicate = (database: ViewModels.Database): boolean => {
return false;
};
}
export class CassandraApi implements ViewModels.CosmosDbApi {
public isSystemDatabasePredicate = (database: ViewModels.Database): boolean => {
return database.id() === "system";
};
}

View File

@@ -1,5 +1,5 @@
import { AutopilotTier } from "../Contracts/DataModels"; import { AutopilotTier } from "../Contracts/DataModels";
import { configContext } from "../ConfigContext"; import { config } from "../Config";
import { HashMap } from "./HashMap"; import { HashMap } from "./HashMap";
export class AuthorizationEndpoints { export class AuthorizationEndpoints {
@@ -7,23 +7,14 @@ export class AuthorizationEndpoints {
public static common: string = "https://login.windows.net/"; public static common: string = "https://login.windows.net/";
} }
export class CodeOfConductEndpoints {
public static privacyStatement: string = "https://aka.ms/ms-privacy-policy";
public static codeOfConduct: string = "https://aka.ms/cosmos-code-of-conduct";
public static termsOfUse: string = "https://aka.ms/ms-terms-of-use";
}
export class BackendEndpoints { export class BackendEndpoints {
public static localhost: string = "https://localhost:12900"; public static localhost: string = "https://localhost:12900";
public static dev: string = "https://ext.documents-dev.windows-int.net"; public static dev: string = "https://ext.documents-dev.windows-int.net";
public static productionPortal: string = configContext.BACKEND_ENDPOINT || "https://main.documentdb.ext.azure.com"; public static productionPortal: string = config.BACKEND_ENDPOINT || "https://main.documentdb.ext.azure.com";
} }
export class EndpointsRegex { export class EndpointsRegex {
public static readonly cassandra = [ public static readonly cassandra = "AccountEndpoint=(.*).cassandra.cosmosdb.azure.com";
"AccountEndpoint=(.*).cassandra.cosmosdb.azure.com",
"HostName=(.*).cassandra.cosmos.azure.com"
];
public static readonly mongo = "mongodb://.*:(.*)@(.*).documents.azure.com"; public static readonly mongo = "mongodb://.*:(.*)@(.*).documents.azure.com";
public static readonly mongoCompute = "mongodb://.*:(.*)@(.*).mongo.cosmos.azure.com"; public static readonly mongoCompute = "mongodb://.*:(.*)@(.*).mongo.cosmos.azure.com";
public static readonly sql = "AccountEndpoint=https://(.*).documents.azure.com"; public static readonly sql = "AccountEndpoint=https://(.*).documents.azure.com";
@@ -110,7 +101,6 @@ export class CapabilityNames {
public static readonly EnableNotebooks: string = "EnableNotebooks"; public static readonly EnableNotebooks: string = "EnableNotebooks";
public static readonly EnableStorageAnalytics: string = "EnableStorageAnalytics"; public static readonly EnableStorageAnalytics: string = "EnableStorageAnalytics";
public static readonly EnableMongo: string = "EnableMongo"; public static readonly EnableMongo: string = "EnableMongo";
public static readonly EnableServerless: string = "EnableServerless";
} }
export class Features { export class Features {
@@ -122,8 +112,6 @@ export class Features {
public static readonly enableTtl = "enablettl"; public static readonly enableTtl = "enablettl";
public static readonly enableNotebooks = "enablenotebooks"; public static readonly enableNotebooks = "enablenotebooks";
public static readonly enableGalleryPublish = "enablegallerypublish"; public static readonly enableGalleryPublish = "enablegallerypublish";
public static readonly enableCodeOfConduct = "enablecodeofconduct";
public static readonly enableLinkInjection = "enablelinkinjection";
public static readonly enableSpark = "enablespark"; public static readonly enableSpark = "enablespark";
public static readonly livyEndpoint = "livyendpoint"; public static readonly livyEndpoint = "livyendpoint";
public static readonly notebookServerUrl = "notebookserverurl"; public static readonly notebookServerUrl = "notebookserverurl";
@@ -134,7 +122,6 @@ export class Features {
public static readonly enableAutoPilotV2 = "enableautopilotv2"; public static readonly enableAutoPilotV2 = "enableautopilotv2";
public static readonly ttl90Days = "ttl90days"; public static readonly ttl90Days = "ttl90days";
public static readonly enableRightPanelV2 = "enablerightpanelv2"; public static readonly enableRightPanelV2 = "enablerightpanelv2";
public static readonly enableSDKoperations = "enablesdkoperations";
} }
export class AfecFeatures { export class AfecFeatures {

View File

@@ -1,7 +1,6 @@
import { CosmosClient, tokenProvider, endpoint, requestPlugin, getTokenFromAuthService } from "./CosmosClient";
import { ResourceType } from "@azure/cosmos/dist-esm/common/constants"; import { ResourceType } from "@azure/cosmos/dist-esm/common/constants";
import { configContext, Platform, updateConfigContext, resetConfigContext } from "../ConfigContext"; import { config, Platform } from "../Config";
import { updateUserContext } from "../UserContext";
import { endpoint, getTokenFromAuthService, requestPlugin, tokenProvider } from "./CosmosClient";
describe("tokenProvider", () => { describe("tokenProvider", () => {
const options = { const options = {
@@ -33,9 +32,7 @@ describe("tokenProvider", () => {
}); });
it("does not call the auth service if a master key is set", async () => { it("does not call the auth service if a master key is set", async () => {
updateUserContext({ CosmosClient.masterKey("foo");
masterKey: "foo"
});
await tokenProvider(options); await tokenProvider(options);
expect((window.fetch as any).mock.calls.length).toBe(0); expect((window.fetch as any).mock.calls.length).toBe(0);
}); });
@@ -44,7 +41,7 @@ describe("tokenProvider", () => {
describe("getTokenFromAuthService", () => { describe("getTokenFromAuthService", () => {
beforeEach(() => { beforeEach(() => {
delete window.dataExplorer; delete window.dataExplorer;
resetConfigContext(); delete config.BACKEND_ENDPOINT;
window.fetch = jest.fn().mockImplementation(() => { window.fetch = jest.fn().mockImplementation(() => {
return { return {
json: () => "{}", json: () => "{}",
@@ -67,9 +64,7 @@ describe("getTokenFromAuthService", () => {
}); });
it("builds the correct URL in dev", () => { it("builds the correct URL in dev", () => {
updateConfigContext({ config.BACKEND_ENDPOINT = "https://localhost:1234";
BACKEND_ENDPOINT: "https://localhost:1234"
});
getTokenFromAuthService("GET", "dbs", "foo"); getTokenFromAuthService("GET", "dbs", "foo");
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
"https://localhost:1234/api/guest/runtimeproxy/authorizationTokens", "https://localhost:1234/api/guest/runtimeproxy/authorizationTokens",
@@ -80,8 +75,7 @@ describe("getTokenFromAuthService", () => {
describe("endpoint", () => { describe("endpoint", () => {
it("falls back to _databaseAccount", () => { it("falls back to _databaseAccount", () => {
updateUserContext({ CosmosClient.databaseAccount({
databaseAccount: {
id: "foo", id: "foo",
name: "foo", name: "foo",
location: "foo", location: "foo",
@@ -94,14 +88,11 @@ describe("endpoint", () => {
tableEndpoint: "foo", tableEndpoint: "foo",
cassandraEndpoint: "foo" cassandraEndpoint: "foo"
} }
}
}); });
expect(endpoint()).toEqual("bar"); expect(endpoint()).toEqual("bar");
}); });
it("uses _endpoint if set", () => { it("uses _endpoint if set", () => {
updateUserContext({ CosmosClient.endpoint("baz");
endpoint: "baz"
});
expect(endpoint()).toEqual("baz"); expect(endpoint()).toEqual("baz");
}); });
}); });
@@ -109,17 +100,17 @@ describe("endpoint", () => {
describe("requestPlugin", () => { describe("requestPlugin", () => {
beforeEach(() => { beforeEach(() => {
delete window.dataExplorerPlatform; delete window.dataExplorerPlatform;
resetConfigContext(); delete config.PROXY_PATH;
delete config.BACKEND_ENDPOINT;
delete config.PROXY_PATH;
}); });
describe("Hosted", () => { describe("Hosted", () => {
it("builds a proxy URL in development", () => { it("builds a proxy URL in development", () => {
const next = jest.fn(); const next = jest.fn();
updateConfigContext({ config.platform = Platform.Hosted;
platform: Platform.Hosted, config.BACKEND_ENDPOINT = "https://localhost:1234";
BACKEND_ENDPOINT: "https://localhost:1234", config.PROXY_PATH = "/proxy";
PROXY_PATH: "/proxy"
});
const headers = {}; const headers = {};
const endpoint = "https://docs.azure.com"; const endpoint = "https://docs.azure.com";
const path = "/dbs/foo"; const path = "/dbs/foo";
@@ -131,7 +122,8 @@ describe("requestPlugin", () => {
describe("Emulator", () => { describe("Emulator", () => {
it("builds a url for emulator proxy via webpack", () => { it("builds a url for emulator proxy via webpack", () => {
const next = jest.fn(); const next = jest.fn();
updateConfigContext({ platform: Platform.Emulator, PROXY_PATH: "/proxy" }); config.platform = Platform.Emulator;
config.PROXY_PATH = "/proxy";
const headers = {}; const headers = {};
const endpoint = ""; const endpoint = "";
const path = "/dbs/foo"; const path = "/dbs/foo";

View File

@@ -1,28 +1,39 @@
import * as Cosmos from "@azure/cosmos"; import * as Cosmos from "@azure/cosmos";
import { RequestInfo, setAuthorizationTokenHeaderUsingMasterKey } from "@azure/cosmos"; import { RequestInfo, setAuthorizationTokenHeaderUsingMasterKey } from "@azure/cosmos";
import { configContext, Platform } from "../ConfigContext"; import { DatabaseAccount } from "../Contracts/DataModels";
import { logConsoleError } from "../Utils/NotificationConsoleUtils"; import { HttpHeaders, EmulatorMasterKey } from "./Constants";
import { EmulatorMasterKey, HttpHeaders } from "./Constants"; import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils";
import { userContext } from "../UserContext"; import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { config, Platform } from "../Config";
let _client: Cosmos.CosmosClient;
let _masterKey: string;
let _endpoint: string;
let _authorizationToken: string;
let _accessToken: string;
let _databaseAccount: DatabaseAccount;
let _subscriptionId: string;
let _resourceGroup: string;
let _resourceToken: string;
const _global = typeof self === "undefined" ? window : self; const _global = typeof self === "undefined" ? window : self;
export const tokenProvider = async (requestInfo: RequestInfo) => { export const tokenProvider = async (requestInfo: RequestInfo) => {
const { verb, resourceId, resourceType, headers } = requestInfo; const { verb, resourceId, resourceType, headers } = requestInfo;
if (configContext.platform === Platform.Emulator) { if (config.platform === Platform.Emulator) {
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK. // TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
await setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey); await setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
return decodeURIComponent(headers.authorization); return decodeURIComponent(headers.authorization);
} }
if (userContext.masterKey) { if (_masterKey) {
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK. // TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
await setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey); await setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
return decodeURIComponent(headers.authorization); return decodeURIComponent(headers.authorization);
} }
if (userContext.resourceToken) { if (_resourceToken) {
return userContext.resourceToken; return _resourceToken;
} }
const result = await getTokenFromAuthService(verb, resourceType, resourceId); const result = await getTokenFromAuthService(verb, resourceType, resourceId);
@@ -31,33 +42,28 @@ export const tokenProvider = async (requestInfo: RequestInfo) => {
}; };
export const requestPlugin: Cosmos.Plugin<any> = async (requestContext, next) => { export const requestPlugin: Cosmos.Plugin<any> = async (requestContext, next) => {
requestContext.endpoint = configContext.PROXY_PATH; requestContext.endpoint = config.PROXY_PATH;
requestContext.headers["x-ms-proxy-target"] = endpoint(); requestContext.headers["x-ms-proxy-target"] = endpoint();
return next(requestContext); return next(requestContext);
}; };
export const endpoint = () => { export const endpoint = () => {
if (configContext.platform === Platform.Emulator) { if (config.platform === Platform.Emulator) {
// In worker scope, _global(self).parent does not exist // In worker scope, _global(self).parent does not exist
const location = _global.parent ? _global.parent.location : _global.location; const location = _global.parent ? _global.parent.location : _global.location;
return configContext.EMULATOR_ENDPOINT || location.origin; return config.EMULATOR_ENDPOINT || location.origin;
} }
return ( return _endpoint || (_databaseAccount && _databaseAccount.properties && _databaseAccount.properties.documentEndpoint);
userContext.endpoint ||
(userContext.databaseAccount &&
userContext.databaseAccount.properties &&
userContext.databaseAccount.properties.documentEndpoint)
);
}; };
export async function getTokenFromAuthService(verb: string, resourceType: string, resourceId?: string): Promise<any> { export async function getTokenFromAuthService(verb: string, resourceType: string, resourceId?: string): Promise<any> {
try { try {
const host = configContext.BACKEND_ENDPOINT || _global.dataExplorer.extensionEndpoint(); const host = config.BACKEND_ENDPOINT || _global.dataExplorer.extensionEndpoint();
const response = await _global.fetch(host + "/api/guest/runtimeproxy/authorizationTokens", { const response = await _global.fetch(host + "/api/guest/runtimeproxy/authorizationTokens", {
method: "POST", method: "POST",
headers: { headers: {
"content-type": "application/json", "content-type": "application/json",
"x-ms-encrypted-auth-token": userContext.accessToken "x-ms-encrypted-auth-token": _accessToken
}, },
body: JSON.stringify({ body: JSON.stringify({
verb, verb,
@@ -69,15 +75,22 @@ export async function getTokenFromAuthService(verb: string, resourceType: string
const result = JSON.parse(await response.json()); const result = JSON.parse(await response.json());
return result; return result;
} catch (error) { } catch (error) {
logConsoleError(`Failed to get authorization headers for ${resourceType}: ${JSON.stringify(error)}`); NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to get authorization headers for ${resourceType}: ${JSON.stringify(error)}`
);
return Promise.reject(error); return Promise.reject(error);
} }
} }
export function client(): Cosmos.CosmosClient { export const CosmosClient = {
client(): Cosmos.CosmosClient {
if (_client) {
return _client;
}
const options: Cosmos.CosmosClientOptions = { const options: Cosmos.CosmosClientOptions = {
endpoint: endpoint() || " ", // CosmosClient gets upset if we pass a falsy value here endpoint: endpoint() || " ", // CosmosClient gets upset if we pass a falsy value here
key: userContext.masterKey, key: _masterKey,
tokenProvider, tokenProvider,
connectionPolicy: { connectionPolicy: {
enableEndpointDiscovery: false enableEndpointDiscovery: false
@@ -89,5 +102,79 @@ export function client(): Cosmos.CosmosClient {
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === "development") {
(options as any).plugins = [{ on: "request", plugin: requestPlugin }]; (options as any).plugins = [{ on: "request", plugin: requestPlugin }];
} }
return new Cosmos.CosmosClient(options); _client = new Cosmos.CosmosClient(options);
return _client;
},
authorizationToken(value?: string): string {
if (typeof value === "undefined") {
return _authorizationToken;
} }
_authorizationToken = value;
_client = null;
return value;
},
accessToken(value?: string): string {
if (typeof value === "undefined") {
return _accessToken;
}
_accessToken = value;
_client = null;
return value;
},
masterKey(value?: string): string {
if (typeof value === "undefined") {
return _masterKey;
}
_client = null;
_masterKey = value;
return value;
},
endpoint(value?: string): string {
if (typeof value === "undefined") {
return _endpoint;
}
_client = null;
_endpoint = value;
return value;
},
databaseAccount(value?: DatabaseAccount): DatabaseAccount {
if (typeof value === "undefined") {
return _databaseAccount || ({} as any);
}
_client = null;
_databaseAccount = value;
return value;
},
subscriptionId(value?: string): string {
if (typeof value === "undefined") {
return _subscriptionId;
}
_client = null;
_subscriptionId = value;
return value;
},
resourceGroup(value?: string): string {
if (typeof value === "undefined") {
return _resourceGroup;
}
_client = null;
_resourceGroup = value;
return value;
},
resourceToken(value?: string): string {
if (typeof value === "undefined") {
return _resourceToken;
}
_client = null;
_resourceToken = value;
return value;
}
};

View File

@@ -6,23 +6,25 @@ import * as ViewModels from "../Contracts/ViewModels";
import Q from "q"; import Q from "q";
import { import {
ConflictDefinition, ConflictDefinition,
ContainerDefinition,
ContainerResponse,
DatabaseResponse,
FeedOptions, FeedOptions,
ItemDefinition, ItemDefinition,
PartitionKeyDefinition,
QueryIterator, QueryIterator,
Resource, Resource,
TriggerDefinition, TriggerDefinition
OfferDefinition
} from "@azure/cosmos"; } from "@azure/cosmos";
import { client } from "./CosmosClient"; import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
import { CosmosClient } from "./CosmosClient";
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
import { sendCachedDataMessage } from "./MessageHandler"; import { MessageHandler } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts"; import { MessageTypes } from "../Contracts/ExplorerContracts";
import { OfferUtils } from "../Utils/OfferUtils"; import { OfferUtils } from "../Utils/OfferUtils";
import { RequestOptions } from "@azure/cosmos/dist-esm"; import { RequestOptions } from "@azure/cosmos/dist-esm";
import StoredProcedure from "../Explorer/Tree/StoredProcedure"; import StoredProcedure from "../Explorer/Tree/StoredProcedure";
import { Platform, configContext } from "../ConfigContext";
import DocumentId from "../Explorer/Tree/DocumentId";
import ConflictId from "../Explorer/Tree/ConflictId";
export function getCommonQueryOptions(options: FeedOptions): any { export function getCommonQueryOptions(options: FeedOptions): any {
const storedItemPerPageSetting: number = LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage); const storedItemPerPageSetting: number = LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage);
@@ -41,26 +43,28 @@ export function getCommonQueryOptions(options: FeedOptions): any {
return options; return options;
} }
export function queryDocuments( // TODO: Add timeout for all promises
export abstract class DataAccessUtilityBase {
public queryDocuments(
databaseId: string, databaseId: string,
containerId: string, containerId: string,
query: string, query: string,
options: any options: any
): Q.Promise<QueryIterator<ItemDefinition & Resource>> { ): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
options = getCommonQueryOptions(options); options = getCommonQueryOptions(options);
const documentsIterator = client() const documentsIterator = CosmosClient.client()
.database(databaseId) .database(databaseId)
.container(containerId) .container(containerId)
.items.query(query, options); .items.query(query, options);
return Q(documentsIterator); return Q(documentsIterator);
} }
export function readStoredProcedures( public readStoredProcedures(
collection: ViewModels.Collection, collection: ViewModels.Collection,
options?: any options?: any
): Q.Promise<DataModels.StoredProcedure[]> { ): Q.Promise<DataModels.StoredProcedure[]> {
return Q( return Q(
client() CosmosClient.client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.scripts.storedProcedures.readAll(options) .scripts.storedProcedures.readAll(options)
@@ -69,13 +73,13 @@ export function readStoredProcedures(
); );
} }
export function readStoredProcedure( public readStoredProcedure(
collection: ViewModels.Collection, collection: ViewModels.Collection,
requestedResource: DataModels.Resource, requestedResource: DataModels.Resource,
options?: any options?: any
): Q.Promise<DataModels.StoredProcedure> { ): Q.Promise<DataModels.StoredProcedure> {
return Q( return Q(
client() CosmosClient.client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.scripts.storedProcedure(requestedResource.id) .scripts.storedProcedure(requestedResource.id)
@@ -83,12 +87,12 @@ export function readStoredProcedure(
.then(response => response.resource as DataModels.StoredProcedure) .then(response => response.resource as DataModels.StoredProcedure)
); );
} }
export function readUserDefinedFunctions( public readUserDefinedFunctions(
collection: ViewModels.Collection, collection: ViewModels.Collection,
options: any options: any
): Q.Promise<DataModels.UserDefinedFunction[]> { ): Q.Promise<DataModels.UserDefinedFunction[]> {
return Q( return Q(
client() CosmosClient.client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.scripts.userDefinedFunctions.readAll(options) .scripts.userDefinedFunctions.readAll(options)
@@ -96,13 +100,13 @@ export function readUserDefinedFunctions(
.then(response => response.resources as DataModels.UserDefinedFunction[]) .then(response => response.resources as DataModels.UserDefinedFunction[])
); );
} }
export function readUserDefinedFunction( public readUserDefinedFunction(
collection: ViewModels.Collection, collection: ViewModels.Collection,
requestedResource: DataModels.Resource, requestedResource: DataModels.Resource,
options?: any options?: any
): Q.Promise<DataModels.UserDefinedFunction> { ): Q.Promise<DataModels.UserDefinedFunction> {
return Q( return Q(
client() CosmosClient.client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.scripts.userDefinedFunction(requestedResource.id) .scripts.userDefinedFunction(requestedResource.id)
@@ -111,9 +115,9 @@ export function readUserDefinedFunction(
); );
} }
export function readTriggers(collection: ViewModels.Collection, options: any): Q.Promise<DataModels.Trigger[]> { public readTriggers(collection: ViewModels.Collection, options: any): Q.Promise<DataModels.Trigger[]> {
return Q( return Q(
client() CosmosClient.client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.scripts.triggers.readAll(options) .scripts.triggers.readAll(options)
@@ -122,13 +126,13 @@ export function readTriggers(collection: ViewModels.Collection, options: any): Q
); );
} }
export function readTrigger( public readTrigger(
collection: ViewModels.Collection, collection: ViewModels.Collection,
requestedResource: DataModels.Resource, requestedResource: DataModels.Resource,
options?: any options?: any
): Q.Promise<DataModels.Trigger> { ): Q.Promise<DataModels.Trigger> {
return Q( return Q(
client() CosmosClient.client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.scripts.trigger(requestedResource.id) .scripts.trigger(requestedResource.id)
@@ -137,7 +141,7 @@ export function readTrigger(
); );
} }
export function executeStoredProcedure( public executeStoredProcedure(
collection: ViewModels.Collection, collection: ViewModels.Collection,
storedProcedure: StoredProcedure, storedProcedure: StoredProcedure,
partitionKeyValue: any, partitionKeyValue: any,
@@ -146,7 +150,7 @@ export function executeStoredProcedure(
// TODO remove this deferred. Kept it because of timeout code at bottom of function // TODO remove this deferred. Kept it because of timeout code at bottom of function
const deferred = Q.defer<any>(); const deferred = Q.defer<any>();
client() CosmosClient.client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.scripts.storedProcedure(storedProcedure.id()) .scripts.storedProcedure(storedProcedure.id())
@@ -165,11 +169,11 @@ export function executeStoredProcedure(
); );
} }
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> { public readDocument(collection: ViewModels.CollectionBase, documentId: ViewModels.DocumentId): Q.Promise<any> {
const partitionKey = documentId.partitionKeyValue; const partitionKey = documentId.partitionKeyValue;
return Q( return Q(
client() CosmosClient.client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.item(documentId.id(), partitionKey) .item(documentId.id(), partitionKey)
@@ -178,14 +182,14 @@ export function readDocument(collection: ViewModels.CollectionBase, documentId:
); );
} }
export function getPartitionKeyHeaderForConflict(conflictId: ConflictId): Object { public getPartitionKeyHeaderForConflict(conflictId: ViewModels.ConflictId): Object {
const partitionKeyDefinition: DataModels.PartitionKey = conflictId.partitionKey; const partitionKeyDefinition: DataModels.PartitionKey = conflictId.partitionKey;
const partitionKeyValue: any = conflictId.partitionKeyValue; const partitionKeyValue: any = conflictId.partitionKeyValue;
return getPartitionKeyHeader(partitionKeyDefinition, partitionKeyValue); return this.getPartitionKeyHeader(partitionKeyDefinition, partitionKeyValue);
} }
export function getPartitionKeyHeader(partitionKeyDefinition: DataModels.PartitionKey, partitionKeyValue: any): Object { public getPartitionKeyHeader(partitionKeyDefinition: DataModels.PartitionKey, partitionKeyValue: any): Object {
if (!partitionKeyDefinition) { if (!partitionKeyDefinition) {
return undefined; return undefined;
} }
@@ -197,15 +201,32 @@ export function getPartitionKeyHeader(partitionKeyDefinition: DataModels.Partiti
return [partitionKeyValue]; return [partitionKeyValue];
} }
export function updateDocument( public updateCollection(
databaseId: string,
collectionId: string,
newCollection: DataModels.Collection,
options: any = {}
): Q.Promise<DataModels.Collection> {
return Q(
CosmosClient.client()
.database(databaseId)
.container(collectionId)
.replace(newCollection as ContainerDefinition, options)
.then(async (response: ContainerResponse) => {
return this.refreshCachedResources().then(() => response.resource as DataModels.Collection);
})
);
}
public updateDocument(
collection: ViewModels.CollectionBase, collection: ViewModels.CollectionBase,
documentId: DocumentId, documentId: ViewModels.DocumentId,
newDocument: any newDocument: any
): Q.Promise<any> { ): Q.Promise<any> {
const partitionKey = documentId.partitionKeyValue; const partitionKey = documentId.partitionKeyValue;
return Q( return Q(
client() CosmosClient.client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.item(documentId.id(), partitionKey) .item(documentId.id(), partitionKey)
@@ -214,29 +235,28 @@ export function updateDocument(
); );
} }
export function updateOffer( public updateOffer(
offer: DataModels.Offer, offer: DataModels.Offer,
newOffer: DataModels.Offer, newOffer: DataModels.Offer,
options?: RequestOptions options?: RequestOptions
): Q.Promise<DataModels.Offer> { ): Q.Promise<DataModels.Offer> {
return Q( return Q(
client() CosmosClient.client()
.offer(offer.id) .offer(offer.id)
// TODO Remove casting when SDK types are fixed (https://github.com/Azure/azure-sdk-for-js/issues/10660) .replace(newOffer, options)
.replace((newOffer as unknown) as OfferDefinition, options)
.then(response => { .then(response => {
return Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => response.resource); return Promise.all([this.refreshCachedOffers(), this.refreshCachedResources()]).then(() => response.resource);
}) })
); );
} }
export function updateStoredProcedure( public updateStoredProcedure(
collection: ViewModels.Collection, collection: ViewModels.Collection,
storedProcedure: DataModels.StoredProcedure, storedProcedure: DataModels.StoredProcedure,
options: any options: any
): Q.Promise<DataModels.StoredProcedure> { ): Q.Promise<DataModels.StoredProcedure> {
return Q( return Q(
client() CosmosClient.client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.scripts.storedProcedure(storedProcedure.id) .scripts.storedProcedure(storedProcedure.id)
@@ -245,13 +265,13 @@ export function updateStoredProcedure(
); );
} }
export function updateUserDefinedFunction( public updateUserDefinedFunction(
collection: ViewModels.Collection, collection: ViewModels.Collection,
userDefinedFunction: DataModels.UserDefinedFunction, userDefinedFunction: DataModels.UserDefinedFunction,
options?: any options?: any
): Q.Promise<DataModels.UserDefinedFunction> { ): Q.Promise<DataModels.UserDefinedFunction> {
return Q( return Q(
client() CosmosClient.client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.scripts.userDefinedFunction(userDefinedFunction.id) .scripts.userDefinedFunction(userDefinedFunction.id)
@@ -260,13 +280,13 @@ export function updateUserDefinedFunction(
); );
} }
export function updateTrigger( public updateTrigger(
collection: ViewModels.Collection, collection: ViewModels.Collection,
trigger: DataModels.Trigger, trigger: DataModels.Trigger,
options?: any options?: any
): Q.Promise<DataModels.Trigger> { ): Q.Promise<DataModels.Trigger> {
return Q( return Q(
client() CosmosClient.client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.scripts.trigger(trigger.id) .scripts.trigger(trigger.id)
@@ -275,9 +295,9 @@ export function updateTrigger(
); );
} }
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> { public createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
return Q( return Q(
client() CosmosClient.client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.items.create(newDocument) .items.create(newDocument)
@@ -285,13 +305,13 @@ export function createDocument(collection: ViewModels.CollectionBase, newDocumen
); );
} }
export function createStoredProcedure( public createStoredProcedure(
collection: ViewModels.Collection, collection: ViewModels.Collection,
newStoredProcedure: DataModels.StoredProcedure, newStoredProcedure: DataModels.StoredProcedure,
options?: any options?: any
): Q.Promise<DataModels.StoredProcedure> { ): Q.Promise<DataModels.StoredProcedure> {
return Q( return Q(
client() CosmosClient.client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.scripts.storedProcedures.create(newStoredProcedure, options) .scripts.storedProcedures.create(newStoredProcedure, options)
@@ -299,13 +319,13 @@ export function createStoredProcedure(
); );
} }
export function createUserDefinedFunction( public createUserDefinedFunction(
collection: ViewModels.Collection, collection: ViewModels.Collection,
newUserDefinedFunction: DataModels.UserDefinedFunction, newUserDefinedFunction: DataModels.UserDefinedFunction,
options: any options: any
): Q.Promise<DataModels.UserDefinedFunction> { ): Q.Promise<DataModels.UserDefinedFunction> {
return Q( return Q(
client() CosmosClient.client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.scripts.userDefinedFunctions.create(newUserDefinedFunction, options) .scripts.userDefinedFunctions.create(newUserDefinedFunction, options)
@@ -313,13 +333,13 @@ export function createUserDefinedFunction(
); );
} }
export function createTrigger( public createTrigger(
collection: ViewModels.Collection, collection: ViewModels.Collection,
newTrigger: DataModels.Trigger, newTrigger: DataModels.Trigger,
options?: any options?: any
): Q.Promise<DataModels.Trigger> { ): Q.Promise<DataModels.Trigger> {
return Q( return Q(
client() CosmosClient.client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.scripts.triggers.create(newTrigger as TriggerDefinition, options) .scripts.triggers.create(newTrigger as TriggerDefinition, options)
@@ -327,11 +347,11 @@ export function createTrigger(
); );
} }
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> { public deleteDocument(collection: ViewModels.CollectionBase, documentId: ViewModels.DocumentId): Q.Promise<any> {
const partitionKey = documentId.partitionKeyValue; const partitionKey = documentId.partitionKeyValue;
return Q( return Q(
client() CosmosClient.client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.item(documentId.id(), partitionKey) .item(documentId.id(), partitionKey)
@@ -339,15 +359,15 @@ export function deleteDocument(collection: ViewModels.CollectionBase, documentId
); );
} }
export function deleteConflict( public deleteConflict(
collection: ViewModels.CollectionBase, collection: ViewModels.CollectionBase,
conflictId: ConflictId, conflictId: ViewModels.ConflictId,
options: any = {} options: any = {}
): Q.Promise<any> { ): Q.Promise<any> {
options.partitionKey = options.partitionKey || getPartitionKeyHeaderForConflict(conflictId); options.partitionKey = options.partitionKey || this.getPartitionKeyHeaderForConflict(conflictId);
return Q( return Q(
client() CosmosClient.client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.conflict(conflictId.id()) .conflict(conflictId.id())
@@ -355,13 +375,32 @@ export function deleteConflict(
); );
} }
export function deleteStoredProcedure( public deleteCollection(collection: ViewModels.Collection, options: any): Q.Promise<any> {
return Q(
CosmosClient.client()
.database(collection.databaseId)
.container(collection.id())
.delete()
.then(() => this.refreshCachedResources())
);
}
public deleteDatabase(database: ViewModels.Database, options: any): Q.Promise<any> {
return Q(
CosmosClient.client()
.database(database.id())
.delete()
.then(() => this.refreshCachedResources())
);
}
public deleteStoredProcedure(
collection: ViewModels.Collection, collection: ViewModels.Collection,
storedProcedure: DataModels.StoredProcedure, storedProcedure: DataModels.StoredProcedure,
options: any options: any
): Q.Promise<any> { ): Q.Promise<any> {
return Q( return Q(
client() CosmosClient.client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.scripts.storedProcedure(storedProcedure.id) .scripts.storedProcedure(storedProcedure.id)
@@ -369,13 +408,13 @@ export function deleteStoredProcedure(
); );
} }
export function deleteUserDefinedFunction( public deleteUserDefinedFunction(
collection: ViewModels.Collection, collection: ViewModels.Collection,
userDefinedFunction: DataModels.UserDefinedFunction, userDefinedFunction: DataModels.UserDefinedFunction,
options: any options: any
): Q.Promise<any> { ): Q.Promise<any> {
return Q( return Q(
client() CosmosClient.client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.scripts.userDefinedFunction(userDefinedFunction.id) .scripts.userDefinedFunction(userDefinedFunction.id)
@@ -383,13 +422,9 @@ export function deleteUserDefinedFunction(
); );
} }
export function deleteTrigger( public deleteTrigger(collection: ViewModels.Collection, trigger: DataModels.Trigger, options: any): Q.Promise<any> {
collection: ViewModels.Collection,
trigger: DataModels.Trigger,
options: any
): Q.Promise<any> {
return Q( return Q(
client() CosmosClient.client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.scripts.trigger(trigger.id) .scripts.trigger(trigger.id)
@@ -397,7 +432,27 @@ export function deleteTrigger(
); );
} }
export function readCollectionQuotaInfo( public readCollections(database: ViewModels.Database, options: any): Q.Promise<DataModels.Collection[]> {
return Q(
CosmosClient.client()
.database(database.id())
.containers.readAll()
.fetchAll()
.then(response => response.resources as DataModels.Collection[])
);
}
public readCollection(databaseId: string, collectionId: string): Q.Promise<DataModels.Collection> {
return Q(
CosmosClient.client()
.database(databaseId)
.container(collectionId)
.read()
.then(response => response.resource as DataModels.Collection)
);
}
public readCollectionQuotaInfo(
collection: ViewModels.Collection, collection: ViewModels.Collection,
options: any options: any
): Q.Promise<DataModels.CollectionQuotaInfo> { ): Q.Promise<DataModels.CollectionQuotaInfo> {
@@ -407,7 +462,7 @@ export function readCollectionQuotaInfo(
options.initialHeaders[Constants.HttpHeaders.populatePartitionStatistics] = true; options.initialHeaders[Constants.HttpHeaders.populatePartitionStatistics] = true;
return Q( return Q(
client() CosmosClient.client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.read(options) .read(options)
@@ -432,37 +487,16 @@ export function readCollectionQuotaInfo(
); );
} }
export function readOffers(options: any): Q.Promise<DataModels.Offer[]> { public readOffers(options: any): Q.Promise<DataModels.Offer[]> {
if (options.isServerless) {
return Q([]); // Reading offers is not supported for serverless accounts
}
try {
if (configContext.platform === Platform.Portal) {
return sendCachedDataMessage<DataModels.Offer[]>(MessageTypes.AllOffers, [
(<any>window).dataExplorer.databaseAccount().id,
Constants.ClientDefaults.portalCacheTimeoutMs
]);
}
} catch (error) {
// If error getting cached Offers, continue on and read via SDK
}
return Q( return Q(
client() CosmosClient.client()
.offers.readAll() .offers.readAll()
.fetchAll() .fetchAll()
.then(response => response.resources) .then(response => response.resources)
.catch(error => {
// This should be removed when we can correctly identify if an account is serverless when connected using connection string too.
if (error.message.includes("Reading or replacing offers is not supported for serverless accounts")) {
return [];
}
throw error;
})
); );
} }
export function readOffer(requestedResource: DataModels.Offer, options: any): Q.Promise<DataModels.OfferWithHeaders> { public readOffer(requestedResource: DataModels.Offer, options: any): Q.Promise<DataModels.OfferWithHeaders> {
options = options || {}; options = options || {};
options.initialHeaders = options.initialHeaders || {}; options.initialHeaders = options.initialHeaders || {};
if (!OfferUtils.isOfferV1(requestedResource)) { if (!OfferUtils.isOfferV1(requestedResource)) {
@@ -470,38 +504,168 @@ export function readOffer(requestedResource: DataModels.Offer, options: any): Q.
} }
return Q( return Q(
client() CosmosClient.client()
.offer(requestedResource.id) .offer(requestedResource.id)
.read(options) .read(options)
.then(response => ({ ...response.resource, headers: response.headers })) .then(response => ({ ...response.resource, headers: response.headers }))
); );
} }
export function refreshCachedOffers(): Q.Promise<void> { public readDatabases(options: any): Q.Promise<DataModels.Database[]> {
if (configContext.platform === Platform.Portal) { return Q(
return sendCachedDataMessage(MessageTypes.RefreshOffers, []); CosmosClient.client()
.databases.readAll()
.fetchAll()
.then(response => response.resources as DataModels.Database[])
);
}
public getOrCreateDatabaseAndCollection(
request: DataModels.CreateDatabaseAndCollectionRequest,
options: any
): Q.Promise<DataModels.Collection> {
const databaseOptions: any = options && _.omit(options, "sharedOfferThroughput");
const {
databaseId,
databaseLevelThroughput,
collectionId,
partitionKey,
indexingPolicy,
uniqueKeyPolicy,
offerThroughput,
analyticalStorageTtl,
hasAutoPilotV2FeatureFlag
} = request;
const createBody: DatabaseRequest = {
id: databaseId
};
// TODO: replace when SDK support autopilot
const initialHeaders = request.autoPilot
? !hasAutoPilotV2FeatureFlag
? {
[Constants.HttpHeaders.autoPilotThroughputSDK]: JSON.stringify({
maxThroughput: request.autoPilot.maxThroughput
})
}
: {
[Constants.HttpHeaders.autoPilotTier]: request.autoPilot.autopilotTier
}
: undefined;
if (databaseLevelThroughput) {
if (request.autoPilot) {
databaseOptions.initialHeaders = initialHeaders;
}
createBody.throughput = offerThroughput;
}
return Q(
CosmosClient.client()
.databases.createIfNotExists(createBody, databaseOptions)
.then(response => {
return response.database.containers.create(
{
id: collectionId,
partitionKey: (partitionKey || undefined) as PartitionKeyDefinition,
indexingPolicy: indexingPolicy ? indexingPolicy : undefined,
uniqueKeyPolicy: uniqueKeyPolicy ? uniqueKeyPolicy : undefined,
analyticalStorageTtl: analyticalStorageTtl,
throughput: databaseLevelThroughput || request.autoPilot ? undefined : offerThroughput
} as ContainerRequest, // TODO: remove cast when https://github.com/Azure/azure-cosmos-js/issues/423 is fixed
{
initialHeaders: databaseLevelThroughput ? undefined : initialHeaders
}
);
})
.then(containerResponse => containerResponse.resource as DataModels.Collection)
.finally(() => this.refreshCachedResources(options))
);
}
public createDatabase(request: DataModels.CreateDatabaseRequest, options: any): Q.Promise<DataModels.Database> {
var deferred = Q.defer<DataModels.Database>();
this._createDatabase(request, options).then(
(createdDatabase: DataModels.Database) => {
this.refreshCachedOffers().then(() => {
deferred.resolve(createdDatabase);
});
},
_createDatabaseError => {
deferred.reject(_createDatabaseError);
}
);
return deferred.promise;
}
public refreshCachedOffers(): Q.Promise<void> {
if (MessageHandler.canSendMessage()) {
return MessageHandler.sendCachedDataMessage(MessageTypes.RefreshOffers, []);
} else { } else {
return Q(); return Q();
} }
} }
export function refreshCachedResources(options?: any): Q.Promise<void> { public refreshCachedResources(options?: any): Q.Promise<void> {
if (configContext.platform === Platform.Portal) { if (MessageHandler.canSendMessage()) {
return sendCachedDataMessage(MessageTypes.RefreshResources, []); return MessageHandler.sendCachedDataMessage(MessageTypes.RefreshResources, []);
} else { } else {
return Q(); return Q();
} }
} }
export function queryConflicts( public queryConflicts(
databaseId: string, databaseId: string,
containerId: string, containerId: string,
query: string, query: string,
options: any options: any
): Q.Promise<QueryIterator<ConflictDefinition & Resource>> { ): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
const documentsIterator = client() const documentsIterator = CosmosClient.client()
.database(databaseId) .database(databaseId)
.container(containerId) .container(containerId)
.conflicts.query(query, options); .conflicts.query(query, options);
return Q(documentsIterator); return Q(documentsIterator);
} }
public updateOfferThroughputBeyondLimit(
request: DataModels.UpdateOfferThroughputRequest,
options: any
): Q.Promise<void> {
throw new Error("Updating throughput beyond specified limit is not supported on this platform");
}
private _createDatabase(
request: DataModels.CreateDatabaseRequest,
options: any = {}
): Q.Promise<DataModels.Database> {
const { databaseId, databaseLevelThroughput, offerThroughput, autoPilot, hasAutoPilotV2FeatureFlag } = request;
const createBody: DatabaseRequest = { id: databaseId };
const databaseOptions: any = options && _.omit(options, "sharedOfferThroughput");
// TODO: replace when SDK support autopilot
const initialHeaders = autoPilot
? !hasAutoPilotV2FeatureFlag
? {
[Constants.HttpHeaders.autoPilotThroughputSDK]: JSON.stringify({ maxThroughput: autoPilot.maxThroughput })
}
: {
[Constants.HttpHeaders.autoPilotTier]: autoPilot.autopilotTier
}
: undefined;
if (!!databaseLevelThroughput) {
if (autoPilot) {
databaseOptions.initialHeaders = initialHeaders;
}
createBody.throughput = offerThroughput;
}
return Q(
CosmosClient.client()
.databases.create(createBody, databaseOptions)
.then((response: DatabaseResponse) => {
return this.refreshCachedResources(databaseOptions).then(() => response.resource);
})
);
}
}

View File

@@ -1,39 +1,42 @@
import * as Constants from "./Constants"; import * as Constants from "./Constants";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import * as ErrorParserUtility from "./ErrorParserUtility";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import Q from "q"; import Q from "q";
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos"; import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import * as DataAccessUtilityBase from "./DataAccessUtilityBase"; import { DataAccessUtilityBase } from "./DataAccessUtilityBase";
import * as Logger from "./Logger"; import * as Logger from "./Logger";
import { MessageHandler } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { MinimalQueryIterator, nextPage } from "./IteratorUtilities"; import { MinimalQueryIterator, nextPage } from "./IteratorUtilities";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils";
import { RequestOptions } from "@azure/cosmos/dist-esm"; import { RequestOptions } from "@azure/cosmos/dist-esm";
import StoredProcedure from "../Explorer/Tree/StoredProcedure"; import StoredProcedure from "../Explorer/Tree/StoredProcedure";
import ConflictId from "../Explorer/Tree/ConflictId";
import DocumentId from "../Explorer/Tree/DocumentId";
import { sendNotificationForError } from "./dataAccess/sendNotificationForError";
// TODO: Log all promise resolutions and errors with verbosity levels // TODO: Log all promise resolutions and errors with verbosity levels
export function queryDocuments( export default class DocumentClientUtilityBase {
constructor(private _dataAccessUtility: DataAccessUtilityBase) {}
public queryDocuments(
databaseId: string, databaseId: string,
containerId: string, containerId: string,
query: string, query: string,
options: any options: any
): Q.Promise<QueryIterator<ItemDefinition & Resource>> { ): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
return DataAccessUtilityBase.queryDocuments(databaseId, containerId, query, options); return this._dataAccessUtility.queryDocuments(databaseId, containerId, query, options);
} }
export function queryConflicts( public queryConflicts(
databaseId: string, databaseId: string,
containerId: string, containerId: string,
query: string, query: string,
options: any options: any
): Q.Promise<QueryIterator<ConflictDefinition & Resource>> { ): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
return DataAccessUtilityBase.queryConflicts(databaseId, containerId, query, options); return this._dataAccessUtility.queryConflicts(databaseId, containerId, query, options);
} }
export function getEntityName() { public getEntityName() {
const defaultExperience = const defaultExperience =
window.dataExplorer && window.dataExplorer.defaultExperience && window.dataExplorer.defaultExperience(); window.dataExplorer && window.dataExplorer.defaultExperience && window.dataExplorer.defaultExperience();
if (defaultExperience === Constants.DefaultAccountExperience.MongoDB) { if (defaultExperience === Constants.DefaultAccountExperience.MongoDB) {
@@ -42,7 +45,7 @@ export function getEntityName() {
return "item"; return "item";
} }
export function readStoredProcedures( public readStoredProcedures(
collection: ViewModels.Collection, collection: ViewModels.Collection,
options: any = {} options: any = {}
): Q.Promise<DataModels.StoredProcedure[]> { ): Q.Promise<DataModels.StoredProcedure[]> {
@@ -51,7 +54,8 @@ export function readStoredProcedures(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
`Querying stored procedures for container ${collection.id()}` `Querying stored procedures for container ${collection.id()}`
); );
DataAccessUtilityBase.readStoredProcedures(collection, options) this._dataAccessUtility
.readStoredProcedures(collection, options)
.then( .then(
(storedProcedures: DataModels.StoredProcedure[]) => { (storedProcedures: DataModels.StoredProcedure[]) => {
deferred.resolve(storedProcedures); deferred.resolve(storedProcedures);
@@ -62,7 +66,7 @@ export function readStoredProcedures(
`Failed to query stored procedures for container ${collection.id()}: ${JSON.stringify(error)}` `Failed to query stored procedures for container ${collection.id()}: ${JSON.stringify(error)}`
); );
Logger.logError(JSON.stringify(error), "ReadStoredProcedures", error.code); Logger.logError(JSON.stringify(error), "ReadStoredProcedures", error.code);
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -73,15 +77,15 @@ export function readStoredProcedures(
return deferred.promise; return deferred.promise;
} }
export function readStoredProcedure( public readStoredProcedure(
collection: ViewModels.Collection, collection: ViewModels.Collection,
requestedResource: DataModels.Resource, requestedResource: DataModels.Resource,
options?: any options?: any
): Q.Promise<DataModels.StoredProcedure> { ): Q.Promise<DataModels.StoredProcedure> {
return DataAccessUtilityBase.readStoredProcedure(collection, requestedResource, options); return this._dataAccessUtility.readStoredProcedure(collection, requestedResource, options);
} }
export function readUserDefinedFunctions( public readUserDefinedFunctions(
collection: ViewModels.Collection, collection: ViewModels.Collection,
options: any = {} options: any = {}
): Q.Promise<DataModels.UserDefinedFunction[]> { ): Q.Promise<DataModels.UserDefinedFunction[]> {
@@ -90,7 +94,8 @@ export function readUserDefinedFunctions(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
`Querying user defined functions for collection ${collection.id()}` `Querying user defined functions for collection ${collection.id()}`
); );
DataAccessUtilityBase.readUserDefinedFunctions(collection, options) this._dataAccessUtility
.readUserDefinedFunctions(collection, options)
.then( .then(
(userDefinedFunctions: DataModels.UserDefinedFunction[]) => { (userDefinedFunctions: DataModels.UserDefinedFunction[]) => {
deferred.resolve(userDefinedFunctions); deferred.resolve(userDefinedFunctions);
@@ -101,7 +106,7 @@ export function readUserDefinedFunctions(
`Failed to query user defined functions for container ${collection.id()}: ${JSON.stringify(error)}` `Failed to query user defined functions for container ${collection.id()}: ${JSON.stringify(error)}`
); );
Logger.logError(JSON.stringify(error), "ReadUDFs", error.code); Logger.logError(JSON.stringify(error), "ReadUDFs", error.code);
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -112,22 +117,23 @@ export function readUserDefinedFunctions(
return deferred.promise; return deferred.promise;
} }
export function readUserDefinedFunction( public readUserDefinedFunction(
collection: ViewModels.Collection, collection: ViewModels.Collection,
requestedResource: DataModels.Resource, requestedResource: DataModels.Resource,
options: any options: any
): Q.Promise<DataModels.UserDefinedFunction> { ): Q.Promise<DataModels.UserDefinedFunction> {
return DataAccessUtilityBase.readUserDefinedFunction(collection, requestedResource, options); return this._dataAccessUtility.readUserDefinedFunction(collection, requestedResource, options);
} }
export function readTriggers(collection: ViewModels.Collection, options: any): Q.Promise<DataModels.Trigger[]> { public readTriggers(collection: ViewModels.Collection, options: any): Q.Promise<DataModels.Trigger[]> {
var deferred = Q.defer<DataModels.Trigger[]>(); var deferred = Q.defer<DataModels.Trigger[]>();
const id = NotificationConsoleUtils.logConsoleMessage( const id = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
`Querying triggers for container ${collection.id()}` `Querying triggers for container ${collection.id()}`
); );
DataAccessUtilityBase.readTriggers(collection, options) this._dataAccessUtility
.readTriggers(collection, options)
.then( .then(
(triggers: DataModels.Trigger[]) => { (triggers: DataModels.Trigger[]) => {
deferred.resolve(triggers); deferred.resolve(triggers);
@@ -138,7 +144,7 @@ export function readTriggers(collection: ViewModels.Collection, options: any): Q
`Failed to query triggers for container ${collection.id()}: ${JSON.stringify(error)}` `Failed to query triggers for container ${collection.id()}: ${JSON.stringify(error)}`
); );
Logger.logError(JSON.stringify(error), "ReadTriggers", error.code); Logger.logError(JSON.stringify(error), "ReadTriggers", error.code);
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -149,15 +155,15 @@ export function readTriggers(collection: ViewModels.Collection, options: any): Q
return deferred.promise; return deferred.promise;
} }
export function readTrigger( public readTrigger(
collection: ViewModels.Collection, collection: ViewModels.Collection,
requestedResource: DataModels.Resource, requestedResource: DataModels.Resource,
options?: any options?: any
): Q.Promise<DataModels.Trigger> { ): Q.Promise<DataModels.Trigger> {
return DataAccessUtilityBase.readTrigger(collection, requestedResource, options); return this._dataAccessUtility.readTrigger(collection, requestedResource, options);
} }
export function executeStoredProcedure( public executeStoredProcedure(
collection: ViewModels.Collection, collection: ViewModels.Collection,
storedProcedure: StoredProcedure, storedProcedure: StoredProcedure,
partitionKeyValue: any, partitionKeyValue: any,
@@ -169,7 +175,8 @@ export function executeStoredProcedure(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
`Executing stored procedure ${storedProcedure.id()}` `Executing stored procedure ${storedProcedure.id()}`
); );
DataAccessUtilityBase.executeStoredProcedure(collection, storedProcedure, partitionKeyValue, params) this._dataAccessUtility
.executeStoredProcedure(collection, storedProcedure, partitionKeyValue, params)
.then( .then(
(response: any) => { (response: any) => {
deferred.resolve(response); deferred.resolve(response);
@@ -186,7 +193,7 @@ export function executeStoredProcedure(
)}` )}`
); );
Logger.logError(JSON.stringify(error), "ExecuteStoredProcedure", error.code); Logger.logError(JSON.stringify(error), "ExecuteStoredProcedure", error.code);
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -197,14 +204,14 @@ export function executeStoredProcedure(
return deferred.promise; return deferred.promise;
} }
export function queryDocumentsPage( public queryDocumentsPage(
resourceName: string, resourceName: string,
documentsIterator: MinimalQueryIterator, documentsIterator: MinimalQueryIterator,
firstItemIndex: number, firstItemIndex: number,
options: any options: any
): Q.Promise<ViewModels.QueryResults> { ): Q.Promise<ViewModels.QueryResults> {
var deferred = Q.defer<ViewModels.QueryResults>(); var deferred = Q.defer<ViewModels.QueryResults>();
const entityName = getEntityName(); const entityName = this.getEntityName();
const id = NotificationConsoleUtils.logConsoleMessage( const id = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
`Querying ${entityName} for container ${resourceName}` `Querying ${entityName} for container ${resourceName}`
@@ -225,7 +232,7 @@ export function queryDocumentsPage(
`Failed to query ${entityName} for container ${resourceName}: ${JSON.stringify(error)}` `Failed to query ${entityName} for container ${resourceName}: ${JSON.stringify(error)}`
); );
Logger.logError(JSON.stringify(error), "QueryDocumentsPage", error.code); Logger.logError(JSON.stringify(error), "QueryDocumentsPage", error.code);
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -236,14 +243,15 @@ export function queryDocumentsPage(
return deferred.promise; return deferred.promise;
} }
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> { public readDocument(collection: ViewModels.CollectionBase, documentId: ViewModels.DocumentId): Q.Promise<any> {
var deferred = Q.defer<any>(); var deferred = Q.defer<any>();
const entityName = getEntityName(); const entityName = this.getEntityName();
const id = NotificationConsoleUtils.logConsoleMessage( const id = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
`Reading ${entityName} ${documentId.id()}` `Reading ${entityName} ${documentId.id()}`
); );
DataAccessUtilityBase.readDocument(collection, documentId) this._dataAccessUtility
.readDocument(collection, documentId)
.then( .then(
(document: any) => { (document: any) => {
deferred.resolve(document); deferred.resolve(document);
@@ -254,7 +262,7 @@ export function readDocument(collection: ViewModels.CollectionBase, documentId:
`Failed to read ${entityName} ${documentId.id()}: ${JSON.stringify(error)}` `Failed to read ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`
); );
Logger.logError(JSON.stringify(error), "ReadDocument", error.code); Logger.logError(JSON.stringify(error), "ReadDocument", error.code);
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -265,18 +273,56 @@ export function readDocument(collection: ViewModels.CollectionBase, documentId:
return deferred.promise; return deferred.promise;
} }
export function updateDocument( public updateCollection(
databaseId: string,
collection: ViewModels.Collection,
newCollection: DataModels.Collection
): Q.Promise<DataModels.Collection> {
var deferred = Q.defer<any>();
const id = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Updating container ${collection.id()}`
);
this._dataAccessUtility
.updateCollection(databaseId, collection.id(), newCollection)
.then(
(replacedCollection: DataModels.Collection) => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully updated container ${collection.id()}`
);
deferred.resolve(replacedCollection);
},
(error: any) => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to update container ${collection.id()}: ${JSON.stringify(error)}`
);
Logger.logError(JSON.stringify(error), "UpdateCollection", error.code);
this.sendNotificationForError(error);
deferred.reject(error);
}
)
.finally(() => {
NotificationConsoleUtils.clearInProgressMessageWithId(id);
});
return deferred.promise;
}
public updateDocument(
collection: ViewModels.CollectionBase, collection: ViewModels.CollectionBase,
documentId: DocumentId, documentId: ViewModels.DocumentId,
newDocument: any newDocument: any
): Q.Promise<any> { ): Q.Promise<any> {
var deferred = Q.defer<any>(); var deferred = Q.defer<any>();
const entityName = getEntityName(); const entityName = this.getEntityName();
const id = NotificationConsoleUtils.logConsoleMessage( const id = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
`Updating ${entityName} ${documentId.id()}` `Updating ${entityName} ${documentId.id()}`
); );
DataAccessUtilityBase.updateDocument(collection, documentId, newDocument) this._dataAccessUtility
.updateDocument(collection, documentId, newDocument)
.then( .then(
(updatedDocument: any) => { (updatedDocument: any) => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
@@ -291,7 +337,7 @@ export function updateDocument(
`Failed to update ${entityName} ${documentId.id()}: ${JSON.stringify(error)}` `Failed to update ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`
); );
Logger.logError(JSON.stringify(error), "UpdateDocument", error.code); Logger.logError(JSON.stringify(error), "UpdateDocument", error.code);
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -302,7 +348,7 @@ export function updateDocument(
return deferred.promise; return deferred.promise;
} }
export function updateOffer( public updateOffer(
offer: DataModels.Offer, offer: DataModels.Offer,
newOffer: DataModels.Offer, newOffer: DataModels.Offer,
options: RequestOptions options: RequestOptions
@@ -312,7 +358,8 @@ export function updateOffer(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
`Updating offer for resource ${offer.resource}` `Updating offer for resource ${offer.resource}`
); );
DataAccessUtilityBase.updateOffer(offer, newOffer, options) this._dataAccessUtility
.updateOffer(offer, newOffer, options)
.then( .then(
(replacedOffer: DataModels.Offer) => { (replacedOffer: DataModels.Offer) => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
@@ -335,7 +382,7 @@ export function updateOffer(
"UpdateOffer", "UpdateOffer",
error.code error.code
); );
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -346,7 +393,43 @@ export function updateOffer(
return deferred.promise; return deferred.promise;
} }
export function updateStoredProcedure( public updateOfferThroughputBeyondLimit(
requestPayload: DataModels.UpdateOfferThroughputRequest,
options: any = {}
): Q.Promise<void> {
const deferred: Q.Deferred<void> = Q.defer<void>();
const resourceDescriptionInfo: string = requestPayload.collectionName
? `database ${requestPayload.databaseName} and container ${requestPayload.collectionName}`
: `database ${requestPayload.databaseName}`;
const id = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Requesting increase in throughput to ${requestPayload.throughput} for ${resourceDescriptionInfo}`
);
this._dataAccessUtility
.updateOfferThroughputBeyondLimit(requestPayload, options)
.then(
() => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully requested an increase in throughput to ${requestPayload.throughput} for ${resourceDescriptionInfo}`
);
deferred.resolve();
},
(error: any) => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to request an increase in throughput for ${requestPayload.throughput}: ${JSON.stringify(error)}`
);
this.sendNotificationForError(error);
deferred.reject(error);
}
)
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
return deferred.promise.timeout(Constants.ClientDefaults.requestTimeoutMs);
}
public updateStoredProcedure(
collection: ViewModels.Collection, collection: ViewModels.Collection,
storedProcedure: DataModels.StoredProcedure, storedProcedure: DataModels.StoredProcedure,
options?: any options?: any
@@ -356,7 +439,8 @@ export function updateStoredProcedure(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
`Updating stored procedure ${storedProcedure.id}` `Updating stored procedure ${storedProcedure.id}`
); );
DataAccessUtilityBase.updateStoredProcedure(collection, storedProcedure, options) this._dataAccessUtility
.updateStoredProcedure(collection, storedProcedure, options)
.then( .then(
(updatedStoredProcedure: DataModels.StoredProcedure) => { (updatedStoredProcedure: DataModels.StoredProcedure) => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
@@ -371,7 +455,7 @@ export function updateStoredProcedure(
`Error while updating stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}` `Error while updating stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`
); );
Logger.logError(JSON.stringify(error), "UpdateStoredProcedure", error.code); Logger.logError(JSON.stringify(error), "UpdateStoredProcedure", error.code);
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -382,7 +466,7 @@ export function updateStoredProcedure(
return deferred.promise; return deferred.promise;
} }
export function updateUserDefinedFunction( public updateUserDefinedFunction(
collection: ViewModels.Collection, collection: ViewModels.Collection,
userDefinedFunction: DataModels.UserDefinedFunction, userDefinedFunction: DataModels.UserDefinedFunction,
options: any = {} options: any = {}
@@ -392,7 +476,8 @@ export function updateUserDefinedFunction(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
`Updating user defined function ${userDefinedFunction.id}` `Updating user defined function ${userDefinedFunction.id}`
); );
DataAccessUtilityBase.updateUserDefinedFunction(collection, userDefinedFunction, options) this._dataAccessUtility
.updateUserDefinedFunction(collection, userDefinedFunction, options)
.then( .then(
(updatedUserDefinedFunction: DataModels.UserDefinedFunction) => { (updatedUserDefinedFunction: DataModels.UserDefinedFunction) => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
@@ -407,7 +492,7 @@ export function updateUserDefinedFunction(
`Error while updating user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}` `Error while updating user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`
); );
Logger.logError(JSON.stringify(error), "UpdateUDF", error.code); Logger.logError(JSON.stringify(error), "UpdateUDF", error.code);
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -418,13 +503,11 @@ export function updateUserDefinedFunction(
return deferred.promise; return deferred.promise;
} }
export function updateTrigger( public updateTrigger(collection: ViewModels.Collection, trigger: DataModels.Trigger): Q.Promise<DataModels.Trigger> {
collection: ViewModels.Collection,
trigger: DataModels.Trigger
): Q.Promise<DataModels.Trigger> {
var deferred = Q.defer<any>(); var deferred = Q.defer<any>();
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, `Updating trigger ${trigger.id}`); const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, `Updating trigger ${trigger.id}`);
DataAccessUtilityBase.updateTrigger(collection, trigger) this._dataAccessUtility
.updateTrigger(collection, trigger)
.then( .then(
(updatedTrigger: DataModels.Trigger) => { (updatedTrigger: DataModels.Trigger) => {
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, `Updated trigger ${trigger.id}`); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, `Updated trigger ${trigger.id}`);
@@ -436,7 +519,7 @@ export function updateTrigger(
`Error while updating trigger ${trigger.id}:\n ${JSON.stringify(error)}` `Error while updating trigger ${trigger.id}:\n ${JSON.stringify(error)}`
); );
Logger.logError(JSON.stringify(error), "UpdateTrigger", error.code); Logger.logError(JSON.stringify(error), "UpdateTrigger", error.code);
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -447,14 +530,15 @@ export function updateTrigger(
return deferred.promise; return deferred.promise;
} }
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> { public createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
var deferred = Q.defer<any>(); var deferred = Q.defer<any>();
const entityName = getEntityName(); const entityName = this.getEntityName();
const id = NotificationConsoleUtils.logConsoleMessage( const id = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
`Creating new ${entityName} for container ${collection.id()}` `Creating new ${entityName} for container ${collection.id()}`
); );
DataAccessUtilityBase.createDocument(collection, newDocument) this._dataAccessUtility
.createDocument(collection, newDocument)
.then( .then(
(savedDocument: any) => { (savedDocument: any) => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
@@ -469,7 +553,7 @@ export function createDocument(collection: ViewModels.CollectionBase, newDocumen
`Error while creating new ${entityName} for container ${collection.id()}:\n ${JSON.stringify(error)}` `Error while creating new ${entityName} for container ${collection.id()}:\n ${JSON.stringify(error)}`
); );
Logger.logError(JSON.stringify(error), "CreateDocument", error.code); Logger.logError(JSON.stringify(error), "CreateDocument", error.code);
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -480,7 +564,7 @@ export function createDocument(collection: ViewModels.CollectionBase, newDocumen
return deferred.promise; return deferred.promise;
} }
export function createStoredProcedure( public createStoredProcedure(
collection: ViewModels.Collection, collection: ViewModels.Collection,
newStoredProcedure: DataModels.StoredProcedure, newStoredProcedure: DataModels.StoredProcedure,
options?: any options?: any
@@ -490,7 +574,8 @@ export function createStoredProcedure(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
`Creating stored procedure for container ${collection.id()}` `Creating stored procedure for container ${collection.id()}`
); );
DataAccessUtilityBase.createStoredProcedure(collection, newStoredProcedure, options) this._dataAccessUtility
.createStoredProcedure(collection, newStoredProcedure, options)
.then( .then(
(createdStoredProcedure: DataModels.StoredProcedure) => { (createdStoredProcedure: DataModels.StoredProcedure) => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
@@ -505,7 +590,7 @@ export function createStoredProcedure(
`Error while creating stored procedure for container ${collection.id()}:\n ${JSON.stringify(error)}` `Error while creating stored procedure for container ${collection.id()}:\n ${JSON.stringify(error)}`
); );
Logger.logError(JSON.stringify(error), "CreateStoredProcedure", error.code); Logger.logError(JSON.stringify(error), "CreateStoredProcedure", error.code);
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -516,7 +601,7 @@ export function createStoredProcedure(
return deferred.promise; return deferred.promise;
} }
export function createUserDefinedFunction( public createUserDefinedFunction(
collection: ViewModels.Collection, collection: ViewModels.Collection,
newUserDefinedFunction: DataModels.UserDefinedFunction, newUserDefinedFunction: DataModels.UserDefinedFunction,
options?: any options?: any
@@ -526,7 +611,8 @@ export function createUserDefinedFunction(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
`Creating user defined function for container ${collection.id()}` `Creating user defined function for container ${collection.id()}`
); );
DataAccessUtilityBase.createUserDefinedFunction(collection, newUserDefinedFunction, options) this._dataAccessUtility
.createUserDefinedFunction(collection, newUserDefinedFunction, options)
.then( .then(
(createdUserDefinedFunction: DataModels.UserDefinedFunction) => { (createdUserDefinedFunction: DataModels.UserDefinedFunction) => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
@@ -541,7 +627,7 @@ export function createUserDefinedFunction(
`Error while creating user defined function for container ${collection.id()}:\n ${JSON.stringify(error)}` `Error while creating user defined function for container ${collection.id()}:\n ${JSON.stringify(error)}`
); );
Logger.logError(JSON.stringify(error), "CreateUDF", error.code); Logger.logError(JSON.stringify(error), "CreateUDF", error.code);
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -552,7 +638,7 @@ export function createUserDefinedFunction(
return deferred.promise; return deferred.promise;
} }
export function createTrigger( public createTrigger(
collection: ViewModels.Collection, collection: ViewModels.Collection,
newTrigger: DataModels.Trigger, newTrigger: DataModels.Trigger,
options: any = {} options: any = {}
@@ -562,7 +648,8 @@ export function createTrigger(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
`Creating trigger for container ${collection.id()}` `Creating trigger for container ${collection.id()}`
); );
DataAccessUtilityBase.createTrigger(collection, newTrigger, options) this._dataAccessUtility
.createTrigger(collection, newTrigger, options)
.then( .then(
(createdTrigger: DataModels.Trigger) => { (createdTrigger: DataModels.Trigger) => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
@@ -577,7 +664,7 @@ export function createTrigger(
`Error while creating trigger for container ${collection.id()}:\n ${JSON.stringify(error)}` `Error while creating trigger for container ${collection.id()}:\n ${JSON.stringify(error)}`
); );
Logger.logError(JSON.stringify(error), "CreateTrigger", error.code); Logger.logError(JSON.stringify(error), "CreateTrigger", error.code);
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -588,14 +675,15 @@ export function createTrigger(
return deferred.promise; return deferred.promise;
} }
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> { public deleteDocument(collection: ViewModels.CollectionBase, documentId: ViewModels.DocumentId): Q.Promise<any> {
var deferred = Q.defer<any>(); var deferred = Q.defer<any>();
const entityName = getEntityName(); const entityName = this.getEntityName();
const id = NotificationConsoleUtils.logConsoleMessage( const id = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
`Deleting ${entityName} ${documentId.id()}` `Deleting ${entityName} ${documentId.id()}`
); );
DataAccessUtilityBase.deleteDocument(collection, documentId) this._dataAccessUtility
.deleteDocument(collection, documentId)
.then( .then(
(response: any) => { (response: any) => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
@@ -610,7 +698,7 @@ export function deleteDocument(collection: ViewModels.CollectionBase, documentId
`Error while deleting ${entityName} ${documentId.id()}:\n ${JSON.stringify(error)}` `Error while deleting ${entityName} ${documentId.id()}:\n ${JSON.stringify(error)}`
); );
Logger.logError(JSON.stringify(error), "DeleteDocument", error.code); Logger.logError(JSON.stringify(error), "DeleteDocument", error.code);
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -621,9 +709,9 @@ export function deleteDocument(collection: ViewModels.CollectionBase, documentId
return deferred.promise; return deferred.promise;
} }
export function deleteConflict( public deleteConflict(
collection: ViewModels.CollectionBase, collection: ViewModels.CollectionBase,
conflictId: ConflictId, conflictId: ViewModels.ConflictId,
options?: any options?: any
): Q.Promise<any> { ): Q.Promise<any> {
var deferred = Q.defer<any>(); var deferred = Q.defer<any>();
@@ -632,7 +720,8 @@ export function deleteConflict(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
`Deleting conflict ${conflictId.id()}` `Deleting conflict ${conflictId.id()}`
); );
DataAccessUtilityBase.deleteConflict(collection, conflictId, options) this._dataAccessUtility
.deleteConflict(collection, conflictId, options)
.then( .then(
(response: any) => { (response: any) => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
@@ -647,7 +736,7 @@ export function deleteConflict(
`Error while deleting conflict ${conflictId.id()}:\n ${JSON.stringify(error)}` `Error while deleting conflict ${conflictId.id()}:\n ${JSON.stringify(error)}`
); );
Logger.logError(JSON.stringify(error), "DeleteConflict", error.code); Logger.logError(JSON.stringify(error), "DeleteConflict", error.code);
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -658,7 +747,75 @@ export function deleteConflict(
return deferred.promise; return deferred.promise;
} }
export function deleteStoredProcedure( public deleteCollection(collection: ViewModels.Collection, options: any = {}): Q.Promise<any> {
var deferred = Q.defer<any>();
const id = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Deleting container ${collection.id()}`
);
this._dataAccessUtility
.deleteCollection(collection, options)
.then(
(response: any) => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully deleted container ${collection.id()}`
);
deferred.resolve(response);
},
(error: any) => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error while deleting container ${collection.id()}:\n ${JSON.stringify(error)}`
);
Logger.logError(JSON.stringify(error), "DeleteCollection", error.code);
this.sendNotificationForError(error);
deferred.reject(error);
}
)
.finally(() => {
NotificationConsoleUtils.clearInProgressMessageWithId(id);
});
return deferred.promise;
}
public deleteDatabase(database: ViewModels.Database, options: any = {}): Q.Promise<any> {
var deferred = Q.defer<any>();
const id = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Deleting database ${database.id()}`
);
this._dataAccessUtility
.deleteDatabase(database, options)
.then(
(response: any) => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully deleted database ${database.id()}`
);
deferred.resolve(response);
},
(error: any) => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error while deleting database ${database.id()}:\n ${JSON.stringify(error)}`
);
Logger.logError(JSON.stringify(error), "DeleteDatabase", error.code);
this.sendNotificationForError(error);
deferred.reject(error);
}
)
.finally(() => {
NotificationConsoleUtils.clearInProgressMessageWithId(id);
});
return deferred.promise;
}
public deleteStoredProcedure(
collection: ViewModels.Collection, collection: ViewModels.Collection,
storedProcedure: DataModels.StoredProcedure, storedProcedure: DataModels.StoredProcedure,
options: any = {} options: any = {}
@@ -669,7 +826,8 @@ export function deleteStoredProcedure(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
`Deleting stored procedure ${storedProcedure.id}` `Deleting stored procedure ${storedProcedure.id}`
); );
DataAccessUtilityBase.deleteStoredProcedure(collection, storedProcedure, options) this._dataAccessUtility
.deleteStoredProcedure(collection, storedProcedure, options)
.then( .then(
(response: any) => { (response: any) => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
@@ -684,7 +842,7 @@ export function deleteStoredProcedure(
`Error while deleting stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}` `Error while deleting stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`
); );
Logger.logError(JSON.stringify(error), "DeleteStoredProcedure", error.code); Logger.logError(JSON.stringify(error), "DeleteStoredProcedure", error.code);
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -695,7 +853,7 @@ export function deleteStoredProcedure(
return deferred.promise; return deferred.promise;
} }
export function deleteUserDefinedFunction( public deleteUserDefinedFunction(
collection: ViewModels.Collection, collection: ViewModels.Collection,
userDefinedFunction: DataModels.UserDefinedFunction, userDefinedFunction: DataModels.UserDefinedFunction,
options: any = {} options: any = {}
@@ -705,7 +863,8 @@ export function deleteUserDefinedFunction(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
`Deleting user defined function ${userDefinedFunction.id}` `Deleting user defined function ${userDefinedFunction.id}`
); );
DataAccessUtilityBase.deleteUserDefinedFunction(collection, userDefinedFunction, options) this._dataAccessUtility
.deleteUserDefinedFunction(collection, userDefinedFunction, options)
.then( .then(
(response: any) => { (response: any) => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
@@ -720,7 +879,7 @@ export function deleteUserDefinedFunction(
`Error while deleting user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}` `Error while deleting user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`
); );
Logger.logError(JSON.stringify(error), "DeleteUDF", error.code); Logger.logError(JSON.stringify(error), "DeleteUDF", error.code);
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -731,17 +890,21 @@ export function deleteUserDefinedFunction(
return deferred.promise; return deferred.promise;
} }
export function deleteTrigger( public deleteTrigger(
collection: ViewModels.Collection, collection: ViewModels.Collection,
trigger: DataModels.Trigger, trigger: DataModels.Trigger,
options: any = {} options: any = {}
): Q.Promise<DataModels.Trigger> { ): Q.Promise<DataModels.Trigger> {
var deferred = Q.defer<any>(); var deferred = Q.defer<any>();
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, `Deleting trigger ${trigger.id}`); const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, `Deleting trigger ${trigger.id}`);
DataAccessUtilityBase.deleteTrigger(collection, trigger, options) this._dataAccessUtility
.deleteTrigger(collection, trigger, options)
.then( .then(
(response: any) => { (response: any) => {
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, `Successfully deleted trigger ${trigger.id}`); NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully deleted trigger ${trigger.id}`
);
deferred.resolve(response); deferred.resolve(response);
}, },
(error: any) => { (error: any) => {
@@ -750,7 +913,7 @@ export function deleteTrigger(
`Error while deleting trigger ${trigger.id}:\n ${JSON.stringify(error)}` `Error while deleting trigger ${trigger.id}:\n ${JSON.stringify(error)}`
); );
Logger.logError(JSON.stringify(error), "DeleteTrigger", error.code); Logger.logError(JSON.stringify(error), "DeleteTrigger", error.code);
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -761,15 +924,74 @@ export function deleteTrigger(
return deferred.promise; return deferred.promise;
} }
export function refreshCachedResources(options: any = {}): Q.Promise<void> { public refreshCachedResources(options: any = {}): Q.Promise<void> {
return DataAccessUtilityBase.refreshCachedResources(options); return this._dataAccessUtility.refreshCachedResources(options);
} }
export function refreshCachedOffers(): Q.Promise<void> { public refreshCachedOffers(): Q.Promise<void> {
return DataAccessUtilityBase.refreshCachedOffers(); return this._dataAccessUtility.refreshCachedOffers();
} }
export function readCollectionQuotaInfo( public readCollections(database: ViewModels.Database, options: any = {}): Q.Promise<DataModels.Collection[]> {
var deferred = Q.defer<DataModels.Collection[]>();
const id = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Querying containers for database ${database.id()}`
);
this._dataAccessUtility
.readCollections(database, options)
.then(
(collections: DataModels.Collection[]) => {
deferred.resolve(collections);
},
(error: any) => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error while querying containers for database ${database.id()}:\n ${JSON.stringify(error)}`
);
Logger.logError(JSON.stringify(error), "ReadCollections", error.code);
this.sendNotificationForError(error);
deferred.reject(error);
}
)
.finally(() => {
NotificationConsoleUtils.clearInProgressMessageWithId(id);
});
return deferred.promise;
}
public readCollection(databaseId: string, collectionId: string): Q.Promise<DataModels.Collection> {
const deferred = Q.defer<DataModels.Collection>();
const id = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Querying container ${collectionId}`
);
this._dataAccessUtility
.readCollection(databaseId, collectionId)
.then(
(collection: DataModels.Collection) => {
deferred.resolve(collection);
},
(error: any) => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error while querying containers for database ${databaseId}:\n ${JSON.stringify(error)}`
);
Logger.logError(JSON.stringify(error), "ReadCollections", error.code);
this.sendNotificationForError(error);
deferred.reject(error);
}
)
.finally(() => {
NotificationConsoleUtils.clearInProgressMessageWithId(id);
});
return deferred.promise;
}
public readCollectionQuotaInfo(
collection: ViewModels.Collection, collection: ViewModels.Collection,
options?: any options?: any
): Q.Promise<DataModels.CollectionQuotaInfo> { ): Q.Promise<DataModels.CollectionQuotaInfo> {
@@ -779,7 +1001,8 @@ export function readCollectionQuotaInfo(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
`Querying quota info for container ${collection.id}` `Querying quota info for container ${collection.id}`
); );
DataAccessUtilityBase.readCollectionQuotaInfo(collection, options) this._dataAccessUtility
.readCollectionQuotaInfo(collection, options)
.then( .then(
(quota: DataModels.CollectionQuotaInfo) => { (quota: DataModels.CollectionQuotaInfo) => {
deferred.resolve(quota); deferred.resolve(quota);
@@ -790,7 +1013,7 @@ export function readCollectionQuotaInfo(
`Error while querying quota info for container ${collection.id}:\n ${JSON.stringify(error)}` `Error while querying quota info for container ${collection.id}:\n ${JSON.stringify(error)}`
); );
Logger.logError(JSON.stringify(error), "ReadCollectionQuotaInfo", error.code); Logger.logError(JSON.stringify(error), "ReadCollectionQuotaInfo", error.code);
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -801,11 +1024,12 @@ export function readCollectionQuotaInfo(
return deferred.promise; return deferred.promise;
} }
export function readOffers(options: any = {}): Q.Promise<DataModels.Offer[]> { public readOffers(options: any = {}): Q.Promise<DataModels.Offer[]> {
var deferred = Q.defer<DataModels.Offer[]>(); var deferred = Q.defer<DataModels.Offer[]>();
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Querying offers"); const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Querying offers");
DataAccessUtilityBase.readOffers(options) this._dataAccessUtility
.readOffers(options)
.then( .then(
(offers: DataModels.Offer[]) => { (offers: DataModels.Offer[]) => {
deferred.resolve(offers); deferred.resolve(offers);
@@ -816,7 +1040,7 @@ export function readOffers(options: any = {}): Q.Promise<DataModels.Offer[]> {
`Error while querying offers:\n ${JSON.stringify(error)}` `Error while querying offers:\n ${JSON.stringify(error)}`
); );
Logger.logError(JSON.stringify(error), "ReadOffers", error.code); Logger.logError(JSON.stringify(error), "ReadOffers", error.code);
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -827,14 +1051,12 @@ export function readOffers(options: any = {}): Q.Promise<DataModels.Offer[]> {
return deferred.promise; return deferred.promise;
} }
export function readOffer( public readOffer(requestedResource: DataModels.Offer, options: any = {}): Q.Promise<DataModels.OfferWithHeaders> {
requestedResource: DataModels.Offer,
options: any = {}
): Q.Promise<DataModels.OfferWithHeaders> {
var deferred = Q.defer<DataModels.OfferWithHeaders>(); var deferred = Q.defer<DataModels.OfferWithHeaders>();
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Querying offer"); const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Querying offer");
DataAccessUtilityBase.readOffer(requestedResource, options) this._dataAccessUtility
.readOffer(requestedResource, options)
.then( .then(
(offer: DataModels.OfferWithHeaders) => { (offer: DataModels.OfferWithHeaders) => {
deferred.resolve(offer); deferred.resolve(offer);
@@ -845,7 +1067,7 @@ export function readOffer(
`Error while querying offer:\n ${JSON.stringify(error)}` `Error while querying offer:\n ${JSON.stringify(error)}`
); );
Logger.logError(JSON.stringify(error), "ReadOffer", error.code); Logger.logError(JSON.stringify(error), "ReadOffer", error.code);
sendNotificationForError(error); this.sendNotificationForError(error);
deferred.reject(error); deferred.reject(error);
} }
) )
@@ -855,3 +1077,108 @@ export function readOffer(
return deferred.promise; return deferred.promise;
} }
public readDatabases(options: any): Q.Promise<DataModels.Database[]> {
var deferred = Q.defer<any>();
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Querying databases");
this._dataAccessUtility
.readDatabases(options)
.then(
(databases: DataModels.Database[]) => {
deferred.resolve(databases);
},
(error: any) => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error while querying databases:\n ${JSON.stringify(error)}`
);
Logger.logError(JSON.stringify(error), "ReadDatabases", error.code);
this.sendNotificationForError(error);
deferred.reject(error);
}
)
.finally(() => {
NotificationConsoleUtils.clearInProgressMessageWithId(id);
});
return deferred.promise;
}
public getOrCreateDatabaseAndCollection(
request: DataModels.CreateDatabaseAndCollectionRequest,
options: any = {}
): Q.Promise<DataModels.Collection> {
const deferred: Q.Deferred<DataModels.Collection> = Q.defer<DataModels.Collection>();
const id = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Creating a new container ${request.collectionId} for database ${request.databaseId}`
);
this._dataAccessUtility
.getOrCreateDatabaseAndCollection(request, options)
.then(
(collection: DataModels.Collection) => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully created container ${request.collectionId}`
);
deferred.resolve(collection);
},
(error: any) => {
const sanitizedError = ErrorParserUtility.replaceKnownError(JSON.stringify(error));
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error while creating container ${request.collectionId}:\n ${sanitizedError}`
);
this.sendNotificationForError(error);
deferred.reject(error);
}
)
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
return deferred.promise;
}
public createDatabase(request: DataModels.CreateDatabaseRequest, options: any = {}): Q.Promise<DataModels.Database> {
const deferred: Q.Deferred<DataModels.Database> = Q.defer<DataModels.Database>();
const id = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Creating a new database ${request.databaseId}`
);
this._dataAccessUtility
.createDatabase(request, options)
.then(
(database: DataModels.Database) => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully created database ${request.databaseId}`
);
deferred.resolve(database);
},
(error: any) => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error while creating database ${request.databaseId}:\n ${JSON.stringify(error)}`
);
this.sendNotificationForError(error);
deferred.reject(error);
}
)
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
return deferred.promise;
}
public sendNotificationForError(error: any) {
if (error && error.code === Constants.HttpStatusCodes.Forbidden) {
if (error.message && error.message.toLowerCase().indexOf("sharedoffer is disabled for your account") > 0) {
return;
}
MessageHandler.sendMessage({
type: MessageTypes.ForbiddenError,
reason: error && error.message ? error.message : error
});
}
}
}

View File

@@ -1,4 +1,4 @@
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
export function replaceKnownError(err: string): string { export function replaceKnownError(err: string): string {
@@ -7,8 +7,6 @@ export function replaceKnownError(err: string): string {
err.indexOf("SharedOffer is Disabled for your account") >= 0 err.indexOf("SharedOffer is Disabled for your account") >= 0
) { ) {
return "Database throughput is not supported for internal subscriptions."; return "Database throughput is not supported for internal subscriptions.";
} else if (err.indexOf("Partition key paths must contain only valid") >= 0) {
return "Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
} }
return err; return err;

View File

@@ -1,26 +1,46 @@
jest.mock("./MessageHandler");
import { LogEntryLevel } from "../Contracts/Diagnostics"; import { LogEntryLevel } from "../Contracts/Diagnostics";
import * as Logger from "./Logger"; import * as Logger from "./Logger";
import { MessageHandler } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts"; import { MessageTypes } from "../Contracts/ExplorerContracts";
import { sendMessage } from "./MessageHandler";
describe("Logger", () => { describe("Logger", () => {
let sendMessageSpy: jasmine.Spy;
beforeEach(() => { beforeEach(() => {
jest.resetAllMocks(); sendMessageSpy = spyOn(MessageHandler, "sendMessage");
});
afterEach(() => {
sendMessageSpy = null;
}); });
it("should log info messages", () => { it("should log info messages", () => {
Logger.logInfo("Test info", "DocDB"); Logger.logInfo("Test info", "DocDB");
expect(sendMessage).toBeCalled(); const spyArgs = sendMessageSpy.calls.mostRecent().args[0];
expect(spyArgs.type).toBe(MessageTypes.LogInfo);
expect(spyArgs.data).toContain(LogEntryLevel.Verbose);
expect(spyArgs.data).toContain("DocDB");
expect(spyArgs.data).toContain("Test info");
}); });
it("should log error messages", () => { it("should log error messages", () => {
Logger.logError("Test error", "DocDB"); Logger.logError("Test error", "DocDB");
expect(sendMessage).toBeCalled(); const spyArgs = sendMessageSpy.calls.mostRecent().args[0];
expect(spyArgs.type).toBe(MessageTypes.LogInfo);
expect(spyArgs.data).toContain(LogEntryLevel.Error);
expect(spyArgs.data).toContain("DocDB");
expect(spyArgs.data).toContain("Test error");
}); });
it("should log warnings", () => { it("should log warnings", () => {
Logger.logWarning("Test warning", "DocDB"); Logger.logWarning("Test warning", "DocDB");
expect(sendMessage).toBeCalled(); const spyArgs = sendMessageSpy.calls.mostRecent().args[0];
expect(spyArgs.type).toBe(MessageTypes.LogInfo);
expect(spyArgs.data).toContain(LogEntryLevel.Warning);
expect(spyArgs.data).toContain("DocDB");
expect(spyArgs.data).toContain("Test warning");
}); });
}); });

View File

@@ -1,4 +1,4 @@
import { sendMessage } from "./MessageHandler"; import { MessageHandler } from "./MessageHandler";
import { Diagnostics, MessageTypes } from "../Contracts/ExplorerContracts"; import { Diagnostics, MessageTypes } from "../Contracts/ExplorerContracts";
import { appInsights } from "../Shared/appInsights"; import { appInsights } from "../Shared/appInsights";
import { SeverityLevel } from "@microsoft/applicationinsights-web"; import { SeverityLevel } from "@microsoft/applicationinsights-web";
@@ -33,7 +33,7 @@ export function logError(message: string | Error, area: string, code?: number):
} }
function _logEntry(entry: Diagnostics.LogEntry): void { function _logEntry(entry: Diagnostics.LogEntry): void {
sendMessage({ MessageHandler.sendMessage({
type: MessageTypes.LogInfo, type: MessageTypes.LogInfo,
data: JSON.stringify(entry) data: JSON.stringify(entry)
}); });

View File

@@ -1,29 +1,65 @@
import Q from "q"; import Q from "q";
import * as MessageHandler from "./MessageHandler"; import { CachedDataPromise, MessageHandler } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts"; import { MessageTypes } from "../Contracts/ExplorerContracts";
class MockMessageHandler extends MessageHandler {
public static addToMap(key: string, value: CachedDataPromise<any>): void {
MessageHandler.RequestMap[key] = value;
}
public static mapContainsKey(key: string): boolean {
return MessageHandler.RequestMap[key] != null;
}
public static clearAllEntries(): void {
MessageHandler.RequestMap = {};
}
public static runGarbageCollector(): void {
MessageHandler.runGarbageCollector();
}
}
describe("Message Handler", () => { describe("Message Handler", () => {
it("should handle cached message", async () => { beforeEach(() => {
let mockPromise = { MockMessageHandler.clearAllEntries();
});
xit("should send cached data message", (done: any) => {
const testValidationCallback = (e: MessageEvent) => {
expect(e.data.data).toEqual(
jasmine.objectContaining({ type: MessageTypes.AllDatabases, params: ["some param"] })
);
e.currentTarget.removeEventListener(e.type, testValidationCallback);
done();
};
window.parent.addEventListener("message", testValidationCallback);
MockMessageHandler.sendCachedDataMessage(MessageTypes.AllDatabases, ["some param"]);
});
it("should handle cached message", () => {
let mockPromise: CachedDataPromise<any> = {
id: "123", id: "123",
startTime: new Date(), startTime: new Date(),
deferred: Q.defer<any>() deferred: Q.defer<any>()
}; };
let mockMessage = { message: { id: "123", data: "{}" } }; let mockMessage = { message: { id: "123", data: "{}" } };
MessageHandler.RequestMap[mockPromise.id] = mockPromise;
MessageHandler.handleCachedDataMessage(mockMessage); MockMessageHandler.addToMap(mockPromise.id, mockPromise);
MockMessageHandler.handleCachedDataMessage(mockMessage);
expect(mockPromise.deferred.promise.isFulfilled()).toBe(true); expect(mockPromise.deferred.promise.isFulfilled()).toBe(true);
}); });
it("should delete fulfilled promises on running the garbage collector", async () => { it("should delete fulfilled promises on running the garbage collector", () => {
let message = { let mockPromise: CachedDataPromise<any> = {
id: "123", id: "123",
startTime: new Date(), startTime: new Date(),
deferred: Q.defer<any>() deferred: Q.defer<any>()
}; };
MessageHandler.handleCachedDataMessage(message); MockMessageHandler.addToMap(mockPromise.id, mockPromise);
MessageHandler.runGarbageCollector(); mockPromise.deferred.reject("some error");
expect(MessageHandler.RequestMap["123"]).toBeUndefined(); MockMessageHandler.runGarbageCollector();
expect(MockMessageHandler.mapContainsKey(mockPromise.id)).toBe(false);
}); });
}); });

View File

@@ -9,24 +9,36 @@ export interface CachedDataPromise<T> {
id: string; id: string;
} }
export const RequestMap: Record<string, CachedDataPromise<any>> = {}; /**
* For some reason, typescript emits a Map() in the compiled js output(despite the target being set to ES5) forcing us to define our own polyfill,
* so we define our own custom implementation of the ES6 Map to work around it.
*/
type Map = { [key: string]: CachedDataPromise<any> };
export function handleCachedDataMessage(message: any): void { export class MessageHandler {
protected static RequestMap: Map = {};
public static handleCachedDataMessage(message: any): void {
const messageContent = message && message.message; const messageContent = message && message.message;
if (message == null || messageContent == null || messageContent.id == null || !RequestMap[messageContent.id]) { if (
message == null ||
messageContent == null ||
messageContent.id == null ||
!MessageHandler.RequestMap[messageContent.id]
) {
return; return;
} }
const cachedDataPromise = RequestMap[messageContent.id]; const cachedDataPromise = MessageHandler.RequestMap[messageContent.id];
if (messageContent.error != null) { if (messageContent.error != null) {
cachedDataPromise.deferred.reject(messageContent.error); cachedDataPromise.deferred.reject(messageContent.error);
} else { } else {
cachedDataPromise.deferred.resolve(JSON.parse(messageContent.data)); cachedDataPromise.deferred.resolve(JSON.parse(messageContent.data));
} }
runGarbageCollector(); MessageHandler.runGarbageCollector();
} }
export function sendCachedDataMessage<TResponseDataModel>( public static sendCachedDataMessage<TResponseDataModel>(
messageType: MessageTypes, messageType: MessageTypes,
params: Object[], params: Object[],
timeoutInMs?: number timeoutInMs?: number
@@ -36,8 +48,8 @@ export function sendCachedDataMessage<TResponseDataModel>(
startTime: new Date(), startTime: new Date(),
id: _.uniqueId() id: _.uniqueId()
}; };
RequestMap[cachedDataPromise.id] = cachedDataPromise; MessageHandler.RequestMap[cachedDataPromise.id] = cachedDataPromise;
sendMessage({ type: messageType, params: params, id: cachedDataPromise.id }); MessageHandler.sendMessage({ type: messageType, params: params, id: cachedDataPromise.id });
//TODO: Use telemetry to measure optimal time to resolve/reject promises //TODO: Use telemetry to measure optimal time to resolve/reject promises
return cachedDataPromise.deferred.promise.timeout( return cachedDataPromise.deferred.promise.timeout(
@@ -46,8 +58,8 @@ export function sendCachedDataMessage<TResponseDataModel>(
); );
} }
export function sendMessage(data: any): void { public static sendMessage(data: any): void {
if (canSendMessage()) { if (MessageHandler.canSendMessage()) {
window.parent.postMessage( window.parent.postMessage(
{ {
signature: "pcIframe", signature: "pcIframe",
@@ -58,16 +70,16 @@ export function sendMessage(data: any): void {
} }
} }
export function canSendMessage(): boolean { public static canSendMessage(): boolean {
return window.parent !== window; return window.parent !== window;
} }
// TODO: This is exported just for testing. It should not be. protected static runGarbageCollector() {
export function runGarbageCollector() { Object.keys(MessageHandler.RequestMap).forEach((key: string) => {
Object.keys(RequestMap).forEach((key: string) => { const promise: Q.Promise<any> = MessageHandler.RequestMap[key].deferred.promise;
const promise: Q.Promise<any> = RequestMap[key].deferred.promise;
if (promise.isFulfilled() || promise.isRejected()) { if (promise.isFulfilled() || promise.isRejected()) {
delete RequestMap[key]; delete MessageHandler.RequestMap[key];
} }
}); });
} }
}

View File

@@ -1,18 +1,16 @@
import { AuthType } from "../AuthType";
import { configContext, resetConfigContext, updateConfigContext } from "../ConfigContext";
import { DatabaseAccount } from "../Contracts/DataModels";
import { Collection } from "../Contracts/ViewModels";
import DocumentId from "../Explorer/Tree/DocumentId";
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
import { updateUserContext } from "../UserContext";
import { import {
_createMongoCollectionWithARM,
deleteDocument, deleteDocument,
getEndpoint, getEndpoint,
queryDocuments, queryDocuments,
readDocument, readDocument,
updateDocument, updateDocument
_createMongoCollectionWithARM
} from "./MongoProxyClient"; } from "./MongoProxyClient";
import { AuthType } from "../AuthType";
import { Collection, DatabaseAccount, DocumentId } from "../Contracts/ViewModels";
import { config } from "../Config";
import { CosmosClient } from "./CosmosClient";
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
jest.mock("../ResourceProvider/ResourceProviderClient.ts"); jest.mock("../ResourceProvider/ResourceProviderClient.ts");
const databaseId = "testDB"; const databaseId = "testDB";
@@ -62,15 +60,13 @@ const databaseAccount = {
tableEndpoint: "foo", tableEndpoint: "foo",
cassandraEndpoint: "foo" cassandraEndpoint: "foo"
} }
} as DatabaseAccount; };
describe("MongoProxyClient", () => { describe("MongoProxyClient", () => {
describe("queryDocuments", () => { describe("queryDocuments", () => {
beforeEach(() => { beforeEach(() => {
resetConfigContext(); delete config.BACKEND_ENDPOINT;
updateUserContext({ CosmosClient.databaseAccount(databaseAccount as any);
databaseAccount
});
window.dataExplorer = { window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => "" serverId: () => ""
@@ -90,7 +86,7 @@ describe("MongoProxyClient", () => {
}); });
it("builds the correct proxy URL in development", () => { it("builds the correct proxy URL in development", () => {
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" }); config.MONGO_BACKEND_ENDPOINT = "https://localhost:1234";
queryDocuments(databaseId, collection, true, "{}"); queryDocuments(databaseId, collection, true, "{}");
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
"https://localhost:1234/api/mongo/explorer/resourcelist?db=testDB&coll=testCollection&resourceUrl=bardbs%2FtestDB%2Fcolls%2FtestCollection%2Fdocs%2F&rid=testCollectionrid&rtype=docs&sid=&rg=&dba=foo&pk=pk", "https://localhost:1234/api/mongo/explorer/resourcelist?db=testDB&coll=testCollection&resourceUrl=bardbs%2FtestDB%2Fcolls%2FtestCollection%2Fdocs%2F&rid=testCollectionrid&rtype=docs&sid=&rg=&dba=foo&pk=pk",
@@ -100,10 +96,8 @@ describe("MongoProxyClient", () => {
}); });
describe("readDocument", () => { describe("readDocument", () => {
beforeEach(() => { beforeEach(() => {
resetConfigContext(); delete config.MONGO_BACKEND_ENDPOINT;
updateUserContext({ CosmosClient.databaseAccount(databaseAccount as any);
databaseAccount
});
window.dataExplorer = { window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => "" serverId: () => ""
@@ -123,7 +117,7 @@ describe("MongoProxyClient", () => {
}); });
it("builds the correct proxy URL in development", () => { it("builds the correct proxy URL in development", () => {
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" }); config.MONGO_BACKEND_ENDPOINT = "https://localhost:1234";
readDocument(databaseId, collection, documentId); readDocument(databaseId, collection, documentId);
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
"https://localhost:1234/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk", "https://localhost:1234/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk",
@@ -133,10 +127,8 @@ describe("MongoProxyClient", () => {
}); });
describe("createDocument", () => { describe("createDocument", () => {
beforeEach(() => { beforeEach(() => {
resetConfigContext(); delete config.MONGO_BACKEND_ENDPOINT;
updateUserContext({ CosmosClient.databaseAccount(databaseAccount as any);
databaseAccount
});
window.dataExplorer = { window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => "" serverId: () => ""
@@ -156,7 +148,7 @@ describe("MongoProxyClient", () => {
}); });
it("builds the correct proxy URL in development", () => { it("builds the correct proxy URL in development", () => {
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" }); config.MONGO_BACKEND_ENDPOINT = "https://localhost:1234";
readDocument(databaseId, collection, documentId); readDocument(databaseId, collection, documentId);
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
"https://localhost:1234/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk", "https://localhost:1234/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk",
@@ -166,10 +158,8 @@ describe("MongoProxyClient", () => {
}); });
describe("updateDocument", () => { describe("updateDocument", () => {
beforeEach(() => { beforeEach(() => {
resetConfigContext(); delete config.MONGO_BACKEND_ENDPOINT;
updateUserContext({ CosmosClient.databaseAccount(databaseAccount as any);
databaseAccount
});
window.dataExplorer = { window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => "" serverId: () => ""
@@ -181,7 +171,7 @@ describe("MongoProxyClient", () => {
}); });
it("builds the correct URL", () => { it("builds the correct URL", () => {
updateDocument(databaseId, collection, documentId, "{}"); updateDocument(databaseId, collection, documentId, {});
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
"https://main.documentdb.ext.azure.com/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2Fdocs%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk", "https://main.documentdb.ext.azure.com/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2Fdocs%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk",
expect.any(Object) expect.any(Object)
@@ -189,8 +179,8 @@ describe("MongoProxyClient", () => {
}); });
it("builds the correct proxy URL in development", () => { it("builds the correct proxy URL in development", () => {
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" }); config.MONGO_BACKEND_ENDPOINT = "https://localhost:1234";
updateDocument(databaseId, collection, documentId, "{}"); updateDocument(databaseId, collection, documentId, {});
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
"https://localhost:1234/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2Fdocs%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk", "https://localhost:1234/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2Fdocs%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk",
expect.any(Object) expect.any(Object)
@@ -199,10 +189,8 @@ describe("MongoProxyClient", () => {
}); });
describe("deleteDocument", () => { describe("deleteDocument", () => {
beforeEach(() => { beforeEach(() => {
resetConfigContext(); delete config.MONGO_BACKEND_ENDPOINT;
updateUserContext({ CosmosClient.databaseAccount(databaseAccount as any);
databaseAccount
});
window.dataExplorer = { window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => "" serverId: () => ""
@@ -222,7 +210,7 @@ describe("MongoProxyClient", () => {
}); });
it("builds the correct proxy URL in development", () => { it("builds the correct proxy URL in development", () => {
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" }); config.MONGO_BACKEND_ENDPOINT = "https://localhost:1234";
deleteDocument(databaseId, collection, documentId); deleteDocument(databaseId, collection, documentId);
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
"https://localhost:1234/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2Fdocs%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk", "https://localhost:1234/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2Fdocs%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk",
@@ -232,11 +220,9 @@ describe("MongoProxyClient", () => {
}); });
describe("getEndpoint", () => { describe("getEndpoint", () => {
beforeEach(() => { beforeEach(() => {
resetConfigContext(); delete config.MONGO_BACKEND_ENDPOINT;
delete window.authType; delete window.authType;
updateUserContext({ CosmosClient.databaseAccount(databaseAccount as any);
databaseAccount
});
window.dataExplorer = { window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => "" serverId: () => ""
@@ -249,7 +235,7 @@ describe("MongoProxyClient", () => {
}); });
it("returns a development endpoint", () => { it("returns a development endpoint", () => {
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" }); config.MONGO_BACKEND_ENDPOINT = "https://localhost:1234";
const endpoint = getEndpoint(databaseAccount as DatabaseAccount); const endpoint = getEndpoint(databaseAccount as DatabaseAccount);
expect(endpoint).toEqual("https://localhost:1234/api/mongo/explorer"); expect(endpoint).toEqual("https://localhost:1234/api/mongo/explorer");
}); });

View File

@@ -1,21 +1,22 @@
import { Constants as CosmosSDKConstants } from "@azure/cosmos"; import * as Constants from "../Common/Constants";
import queryString from "querystring";
import { AuthType } from "../AuthType";
import * as DataExplorerConstants from "../Common/Constants"; import * as DataExplorerConstants from "../Common/Constants";
import { configContext } from "../ConfigContext";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import { MessageTypes } from "../Contracts/ExplorerContracts"; import * as ViewModels from "../Contracts/ViewModels";
import { Collection } from "../Contracts/ViewModels";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import DocumentId from "../Explorer/Tree/DocumentId";
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
import { AddDbUtilities } from "../Shared/AddDatabaseUtility";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
import { userContext } from "../UserContext";
import EnvironmentUtility from "./EnvironmentUtility"; import EnvironmentUtility from "./EnvironmentUtility";
import queryString from "querystring";
import { AddDbUtilities } from "../Shared/AddDatabaseUtility";
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
import { AuthType } from "../AuthType";
import { Collection } from "../Contracts/ViewModels";
import { config } from "../Config";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
import { CosmosClient } from "./CosmosClient";
import { MessageHandler } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils";
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
import { MinimalQueryIterator } from "./IteratorUtilities"; import { MinimalQueryIterator } from "./IteratorUtilities";
import { sendMessage } from "./MessageHandler";
const defaultHeaders = { const defaultHeaders = {
[HttpHeaders.apiType]: ApiType.MongoDB.toString(), [HttpHeaders.apiType]: ApiType.MongoDB.toString(),
@@ -25,9 +26,9 @@ const defaultHeaders = {
function authHeaders() { function authHeaders() {
if (window.authType === AuthType.EncryptedToken) { if (window.authType === AuthType.EncryptedToken) {
return { [HttpHeaders.guestAccessToken]: userContext.accessToken }; return { [HttpHeaders.guestAccessToken]: CosmosClient.accessToken() };
} else { } else {
return { [HttpHeaders.authorization]: userContext.authorizationToken }; return { [HttpHeaders.authorization]: CosmosClient.authorizationToken() };
} }
} }
@@ -66,7 +67,7 @@ export function queryDocuments(
query: string, query: string,
continuationToken?: string continuationToken?: string
): Promise<QueryResponse> { ): Promise<QueryResponse> {
const databaseAccount = userContext.databaseAccount; const databaseAccount = CosmosClient.databaseAccount();
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const params = { const params = {
db: databaseId, db: databaseId,
@@ -74,8 +75,8 @@ export function queryDocuments(
resourceUrl: `${resourceEndpoint}dbs/${databaseId}/colls/${collection.id()}/docs/`, resourceUrl: `${resourceEndpoint}dbs/${databaseId}/colls/${collection.id()}/docs/`,
rid: collection.rid, rid: collection.rid,
rtype: "docs", rtype: "docs",
sid: userContext.subscriptionId, sid: CosmosClient.subscriptionId(),
rg: userContext.resourceGroup, rg: CosmosClient.resourceGroup(),
dba: databaseAccount.name, dba: databaseAccount.name,
pk: pk:
collection && collection.partitionKey && !collection.partitionKey.systemKey ? collection.partitionKeyProperty : "" collection && collection.partitionKey && !collection.partitionKey.systemKey ? collection.partitionKeyProperty : ""
@@ -122,9 +123,9 @@ export function queryDocuments(
export function readDocument( export function readDocument(
databaseId: string, databaseId: string,
collection: Collection, collection: Collection,
documentId: DocumentId documentId: ViewModels.DocumentId
): Promise<DataModels.DocumentId> { ): Promise<DataModels.DocumentId> {
const databaseAccount = userContext.databaseAccount; const databaseAccount = CosmosClient.databaseAccount();
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const idComponents = documentId.self.split("/"); const idComponents = documentId.self.split("/");
const path = idComponents.slice(0, 4).join("/"); const path = idComponents.slice(0, 4).join("/");
@@ -135,8 +136,8 @@ export function readDocument(
resourceUrl: `${resourceEndpoint}${path}/${rid}`, resourceUrl: `${resourceEndpoint}${path}/${rid}`,
rid, rid,
rtype: "docs", rtype: "docs",
sid: userContext.subscriptionId, sid: CosmosClient.subscriptionId(),
rg: userContext.resourceGroup, rg: CosmosClient.resourceGroup(),
dba: databaseAccount.name, dba: databaseAccount.name,
pk: pk:
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : "" documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : ""
@@ -168,7 +169,7 @@ export function createDocument(
partitionKeyProperty: string, partitionKeyProperty: string,
documentContent: unknown documentContent: unknown
): Promise<DataModels.DocumentId> { ): Promise<DataModels.DocumentId> {
const databaseAccount = userContext.databaseAccount; const databaseAccount = CosmosClient.databaseAccount();
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const params = { const params = {
db: databaseId, db: databaseId,
@@ -176,8 +177,8 @@ export function createDocument(
resourceUrl: `${resourceEndpoint}dbs/${databaseId}/colls/${collection.id()}/docs/`, resourceUrl: `${resourceEndpoint}dbs/${databaseId}/colls/${collection.id()}/docs/`,
rid: collection.rid, rid: collection.rid,
rtype: "docs", rtype: "docs",
sid: userContext.subscriptionId, sid: CosmosClient.subscriptionId(),
rg: userContext.resourceGroup, rg: CosmosClient.resourceGroup(),
dba: databaseAccount.name, dba: databaseAccount.name,
pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "" pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : ""
}; };
@@ -204,10 +205,10 @@ export function createDocument(
export function updateDocument( export function updateDocument(
databaseId: string, databaseId: string,
collection: Collection, collection: Collection,
documentId: DocumentId, documentId: ViewModels.DocumentId,
documentContent: string documentContent: unknown
): Promise<DataModels.DocumentId> { ): Promise<DataModels.DocumentId> {
const databaseAccount = userContext.databaseAccount; const databaseAccount = CosmosClient.databaseAccount();
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const idComponents = documentId.self.split("/"); const idComponents = documentId.self.split("/");
const path = idComponents.slice(0, 5).join("/"); const path = idComponents.slice(0, 5).join("/");
@@ -218,8 +219,8 @@ export function updateDocument(
resourceUrl: `${resourceEndpoint}${path}/${rid}`, resourceUrl: `${resourceEndpoint}${path}/${rid}`,
rid, rid,
rtype: "docs", rtype: "docs",
sid: userContext.subscriptionId, sid: CosmosClient.subscriptionId(),
rg: userContext.resourceGroup, rg: CosmosClient.resourceGroup(),
dba: databaseAccount.name, dba: databaseAccount.name,
pk: pk:
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : "" documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : ""
@@ -229,7 +230,7 @@ export function updateDocument(
return window return window
.fetch(`${endpoint}?${queryString.stringify(params)}`, { .fetch(`${endpoint}?${queryString.stringify(params)}`, {
method: "PUT", method: "PUT",
body: documentContent, body: JSON.stringify(documentContent),
headers: { headers: {
...defaultHeaders, ...defaultHeaders,
...authHeaders(), ...authHeaders(),
@@ -245,8 +246,12 @@ export function updateDocument(
}); });
} }
export function deleteDocument(databaseId: string, collection: Collection, documentId: DocumentId): Promise<void> { export function deleteDocument(
const databaseAccount = userContext.databaseAccount; databaseId: string,
collection: Collection,
documentId: ViewModels.DocumentId
): Promise<void> {
const databaseAccount = CosmosClient.databaseAccount();
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const idComponents = documentId.self.split("/"); const idComponents = documentId.self.split("/");
const path = idComponents.slice(0, 5).join("/"); const path = idComponents.slice(0, 5).join("/");
@@ -257,8 +262,8 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
resourceUrl: `${resourceEndpoint}${path}/${rid}`, resourceUrl: `${resourceEndpoint}${path}/${rid}`,
rid, rid,
rtype: "docs", rtype: "docs",
sid: userContext.subscriptionId, sid: CosmosClient.subscriptionId(),
rg: userContext.resourceGroup, rg: CosmosClient.resourceGroup(),
dba: databaseAccount.name, dba: databaseAccount.name,
pk: pk:
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : "" documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : ""
@@ -284,35 +289,43 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
} }
export function createMongoCollectionWithProxy( export function createMongoCollectionWithProxy(
params: DataModels.CreateCollectionParams databaseId: string,
collectionId: string,
offerThroughput: number,
shardKey: string,
createDatabase: boolean,
sharedThroughput: boolean,
isSharded: boolean,
autopilotOptions?: DataModels.RpOptions
): Promise<DataModels.Collection> { ): Promise<DataModels.Collection> {
const databaseAccount = userContext.databaseAccount; const databaseAccount = CosmosClient.databaseAccount();
const shardKey: string = params.partitionKey?.paths[0]; const params: DataModels.MongoParameters = {
const mongoParams: DataModels.MongoParameters = {
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint, resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
db: params.databaseId, db: databaseId,
coll: params.collectionId, coll: collectionId,
pk: shardKey, pk: shardKey,
offerThroughput: params.offerThroughput, offerThroughput,
cd: params.createNewDatabase, cd: createDatabase,
st: params.databaseLevelThroughput, st: sharedThroughput,
is: !!shardKey, is: isSharded,
rid: "", rid: "",
rtype: "colls", rtype: "colls",
sid: userContext.subscriptionId, sid: CosmosClient.subscriptionId(),
rg: userContext.resourceGroup, rg: CosmosClient.resourceGroup(),
dba: databaseAccount.name, dba: databaseAccount.name,
isAutoPilot: !!params.autoPilotMaxThroughput, isAutoPilot: false
autoPilotThroughput: params.autoPilotMaxThroughput?.toString()
}; };
if (autopilotOptions) {
params.isAutoPilot = true;
params.autoPilotTier = autopilotOptions[Constants.HttpHeaders.autoPilotTier] as string;
}
const endpoint = getEndpoint(databaseAccount); const endpoint = getEndpoint(databaseAccount);
return window return window
.fetch( .fetch(
`${endpoint}/createCollection?${queryString.stringify( `${endpoint}/createCollection?${queryString.stringify((params as unknown) as queryString.ParsedUrlQueryInput)}`,
(mongoParams as unknown) as queryString.ParsedUrlQueryInput
)}`,
{ {
method: "POST", method: "POST",
headers: { headers: {
@@ -326,7 +339,7 @@ export function createMongoCollectionWithProxy(
if (response.ok) { if (response.ok) {
return response.json(); return response.json();
} }
return errorHandling(response, "creating collection", mongoParams); return errorHandling(response, "creating collection", params);
}); });
} }
@@ -342,7 +355,7 @@ export function createMongoCollectionWithARM(
isSharded: boolean, isSharded: boolean,
additionalOptions?: DataModels.RpOptions additionalOptions?: DataModels.RpOptions
): Promise<DataModels.CreateCollectionWithRpResponse> { ): Promise<DataModels.CreateCollectionWithRpResponse> {
const databaseAccount = userContext.databaseAccount; const databaseAccount = CosmosClient.databaseAccount();
const params: DataModels.MongoParameters = { const params: DataModels.MongoParameters = {
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint, resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
db: databaseId, db: databaseId,
@@ -354,8 +367,8 @@ export function createMongoCollectionWithARM(
is: isSharded, is: isSharded,
rid: "", rid: "",
rtype: "colls", rtype: "colls",
sid: userContext.subscriptionId, sid: CosmosClient.subscriptionId(),
rg: userContext.resourceGroup, rg: CosmosClient.resourceGroup(),
dba: databaseAccount.name, dba: databaseAccount.name,
analyticalStorageTtl analyticalStorageTtl
}; };
@@ -372,11 +385,11 @@ export function createMongoCollectionWithARM(
return _createMongoCollectionWithARM(armEndpoint, params, additionalOptions); return _createMongoCollectionWithARM(armEndpoint, params, additionalOptions);
} }
export function getEndpoint(databaseAccount: DataModels.DatabaseAccount): string { export function getEndpoint(databaseAccount: ViewModels.DatabaseAccount): string {
const serverId = window.dataExplorer.serverId(); const serverId = window.dataExplorer.serverId();
const extensionEndpoint = window.dataExplorer.extensionEndpoint(); const extensionEndpoint = window.dataExplorer.extensionEndpoint();
let url = configContext.MONGO_BACKEND_ENDPOINT let url = config.MONGO_BACKEND_ENDPOINT
? configContext.MONGO_BACKEND_ENDPOINT + "/api/mongo/explorer" ? config.MONGO_BACKEND_ENDPOINT + "/api/mongo/explorer"
: EnvironmentUtility.getMongoBackendEndpoint(serverId, databaseAccount.location, extensionEndpoint); : EnvironmentUtility.getMongoBackendEndpoint(serverId, databaseAccount.location, extensionEndpoint);
if (window.authType === AuthType.EncryptedToken) { if (window.authType === AuthType.EncryptedToken) {
@@ -395,14 +408,16 @@ async function errorHandling(response: Response, action: string, params: unknown
`Error ${action}: ${errorMessage}, Payload: ${JSON.stringify(params)}` `Error ${action}: ${errorMessage}, Payload: ${JSON.stringify(params)}`
); );
if (response.status === HttpStatusCodes.Forbidden) { if (response.status === HttpStatusCodes.Forbidden) {
sendMessage({ type: MessageTypes.ForbiddenError, reason: errorMessage }); MessageHandler.sendMessage({ type: MessageTypes.ForbiddenError, reason: errorMessage });
return; return;
} }
throw new Error(errorMessage); throw new Error(errorMessage);
} }
export function getARMCreateCollectionEndpoint(params: DataModels.MongoParameters): string { export function getARMCreateCollectionEndpoint(params: DataModels.MongoParameters): string {
return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${userContext.databaseAccount.name}/mongodbDatabases/${params.db}/collections/${params.coll}`; return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${
CosmosClient.databaseAccount().name
}/mongodbDatabases/${params.db}/collections/${params.coll}`;
} }
export async function _createMongoCollectionWithARM( export async function _createMongoCollectionWithARM(

View File

@@ -2,10 +2,11 @@ import "jquery";
import * as Q from "q"; import * as Q from "q";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { userContext } from "../UserContext";
export class NotificationsClientBase { import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { CosmosClient } from "./CosmosClient";
export class NotificationsClientBase implements ViewModels.NotificationsClient {
private _extensionEndpoint: string; private _extensionEndpoint: string;
private _notificationsApiSuffix: string; private _notificationsApiSuffix: string;
@@ -15,10 +16,10 @@ export class NotificationsClientBase {
public fetchNotifications(): Q.Promise<DataModels.Notification[]> { public fetchNotifications(): Q.Promise<DataModels.Notification[]> {
const deferred: Q.Deferred<DataModels.Notification[]> = Q.defer<DataModels.Notification[]>(); const deferred: Q.Deferred<DataModels.Notification[]> = Q.defer<DataModels.Notification[]>();
const databaseAccount = userContext.databaseAccount; const databaseAccount: ViewModels.DatabaseAccount = CosmosClient.databaseAccount();
const subscriptionId = userContext.subscriptionId; const subscriptionId: string = CosmosClient.subscriptionId();
const resourceGroup = userContext.resourceGroup; const resourceGroup: string = CosmosClient.resourceGroup();
const url = `${this._extensionEndpoint}${this._notificationsApiSuffix}?accountName=${databaseAccount.name}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`; const url: string = `${this._extensionEndpoint}${this._notificationsApiSuffix}?accountName=${databaseAccount.name}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;
const authorizationHeader: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader(); const authorizationHeader: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
const headers: any = {}; const headers: any = {};
headers[authorizationHeader.header] = authorizationHeader.token; headers[authorizationHeader.header] = authorizationHeader.token;

View File

@@ -1,21 +1,18 @@
import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import * as _ from "underscore"; import * as _ from "underscore";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import Explorer from "../Explorer/Explorer";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
import DocumentId from "../Explorer/Tree/DocumentId"; import DocumentId from "../Explorer/Tree/DocumentId";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { QueryUtils } from "../Utils/QueryUtils";
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
import { userContext } from "../UserContext";
import { createDocument, deleteDocument, queryDocuments, queryDocumentsPage } from "./DocumentClientUtilityBase";
import { createCollection } from "./dataAccess/createCollection";
import * as ErrorParserUtility from "./ErrorParserUtility"; import * as ErrorParserUtility from "./ErrorParserUtility";
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { CosmosClient } from "./CosmosClient";
import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import * as Logger from "./Logger"; import * as Logger from "./Logger";
import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils";
import { QueryUtils } from "../Utils/QueryUtils";
import Explorer from "../Explorer/Explorer";
export class QueriesClient { export class QueriesClient implements ViewModels.QueriesClient {
private static readonly PartitionKey: DataModels.PartitionKey = { private static readonly PartitionKey: DataModels.PartitionKey = {
paths: [`/${SavedQueries.PartitionKeyProperty}`], paths: [`/${SavedQueries.PartitionKeyProperty}`],
kind: BackendDefaults.partitionKeyKind, kind: BackendDefaults.partitionKeyKind,
@@ -36,13 +33,13 @@ export class QueriesClient {
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
"Setting up account for saving queries" "Setting up account for saving queries"
); );
return createCollection({ return this.container.documentClientUtility
.getOrCreateDatabaseAndCollection({
collectionId: SavedQueries.CollectionName, collectionId: SavedQueries.CollectionName,
createNewDatabase: true,
databaseId: SavedQueries.DatabaseName, databaseId: SavedQueries.DatabaseName,
partitionKey: QueriesClient.PartitionKey, partitionKey: QueriesClient.PartitionKey,
offerThroughput: SavedQueries.OfferThroughput, offerThroughput: SavedQueries.OfferThroughput,
databaseLevelThroughput: false databaseLevelThroughput: undefined
}) })
.then( .then(
(collection: DataModels.Collection) => { (collection: DataModels.Collection) => {
@@ -92,7 +89,8 @@ export class QueriesClient {
`Saving query ${query.queryName}` `Saving query ${query.queryName}`
); );
query.id = query.queryName; query.id = query.queryName;
return createDocument(queriesCollection, query) return this.container.documentClientUtility
.createDocument(queriesCollection, query)
.then( .then(
(savedQuery: DataModels.Query) => { (savedQuery: DataModels.Query) => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
@@ -133,11 +131,17 @@ export class QueriesClient {
const options: any = { enableCrossPartitionQuery: true }; const options: any = { enableCrossPartitionQuery: true };
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Fetching saved queries"); const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Fetching saved queries");
return queryDocuments(SavedQueries.DatabaseName, SavedQueries.CollectionName, this.fetchQueriesQuery(), options) return this.container.documentClientUtility
.queryDocuments(SavedQueries.DatabaseName, SavedQueries.CollectionName, this.fetchQueriesQuery(), options)
.then( .then(
(queryIterator: QueryIterator<ItemDefinition & Resource>) => { (queryIterator: QueryIterator<ItemDefinition & Resource>) => {
const fetchQueries = (firstItemIndex: number): Q.Promise<ViewModels.QueryResults> => const fetchQueries = (firstItemIndex: number): Q.Promise<ViewModels.QueryResults> =>
queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex, options); this.container.documentClientUtility.queryDocumentsPage(
queriesCollection.id(),
queryIterator,
firstItemIndex,
options
);
return QueryUtils.queryAllPages(fetchQueries).then( return QueryUtils.queryAllPages(fetchQueries).then(
(results: ViewModels.QueryResults) => { (results: ViewModels.QueryResults) => {
let queries: DataModels.Query[] = _.map(results.documents, (document: DataModels.Query) => { let queries: DataModels.Query[] = _.map(results.documents, (document: DataModels.Query) => {
@@ -213,16 +217,17 @@ export class QueriesClient {
`Deleting query ${query.queryName}` `Deleting query ${query.queryName}`
); );
query.id = query.queryName; query.id = query.queryName;
const documentId = new DocumentId( const documentId: ViewModels.DocumentId = new DocumentId(
{ {
partitionKey: QueriesClient.PartitionKey, partitionKey: QueriesClient.PartitionKey,
partitionKeyProperty: "id" partitionKeyProperty: "id"
} as DocumentsTab, } as ViewModels.DocumentsTab,
query, query,
query.queryName query.queryName
); // TODO: Remove DocumentId's dependency on DocumentsTab ); // TODO: Remove DocumentId's dependency on DocumentsTab
const options: any = { partitionKey: query.resourceId }; const options: any = { partitionKey: query.resourceId };
return deleteDocument(queriesCollection, documentId) return this.container.documentClientUtility
.deleteDocument(queriesCollection, documentId)
.then( .then(
() => { () => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
@@ -245,10 +250,10 @@ export class QueriesClient {
} }
public getResourceId(): string { public getResourceId(): string {
const databaseAccount = userContext.databaseAccount; const databaseAccount: ViewModels.DatabaseAccount = CosmosClient.databaseAccount();
const databaseAccountName = (databaseAccount && databaseAccount.name) || ""; const databaseAccountName: string = (databaseAccount && databaseAccount.name) || "";
const subscriptionId = userContext.subscriptionId || ""; const subscriptionId: string = CosmosClient.subscriptionId() || "";
const resourceGroup = userContext.resourceGroup || ""; const resourceGroup: string = CosmosClient.resourceGroup() || "";
return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDb/databaseAccounts/${databaseAccountName}`; return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDb/databaseAccounts/${databaseAccountName}`;
} }

View File

@@ -1,81 +0,0 @@
jest.mock("../../Utils/arm/request");
jest.mock("../CosmosClient");
jest.mock("../DataAccessUtilityBase");
import { AuthType } from "../../AuthType";
import { CreateCollectionParams, DatabaseAccount } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { armRequest } from "../../Utils/arm/request";
import { client } from "../CosmosClient";
import { createCollection, constructRpOptions } from "./createCollection";
import { updateUserContext } from "../../UserContext";
describe("createCollection", () => {
const createCollectionParams: CreateCollectionParams = {
createNewDatabase: false,
collectionId: "testContainer",
databaseId: "testDatabase",
databaseLevelThroughput: true,
offerThroughput: 400
};
beforeAll(() => {
updateUserContext({
databaseAccount: {
name: "test"
} as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB
});
});
it("should call ARM if logged in with AAD", async () => {
window.authType = AuthType.AAD;
await createCollection(createCollectionParams);
expect(armRequest).toHaveBeenCalled();
});
it("should call SDK if not logged in with non-AAD method", async () => {
window.authType = AuthType.MasterKey;
(client as jest.Mock).mockReturnValue({
databases: {
createIfNotExists: () => {
return {
database: {
containers: {
create: () => ({})
}
}
};
}
}
});
await createCollection(createCollectionParams);
expect(client).toHaveBeenCalled();
});
it("constructRpOptions should return the correct options", () => {
expect(constructRpOptions(createCollectionParams)).toEqual({});
const manualThroughputParams: CreateCollectionParams = {
createNewDatabase: false,
collectionId: "testContainer",
databaseId: "testDatabase",
databaseLevelThroughput: false,
offerThroughput: 400
};
expect(constructRpOptions(manualThroughputParams)).toEqual({ throughput: 400 });
const autoPilotThroughputParams: CreateCollectionParams = {
createNewDatabase: false,
collectionId: "testContainer",
databaseId: "testDatabase",
databaseLevelThroughput: false,
offerThroughput: 400,
autoPilotMaxThroughput: 4000
};
expect(constructRpOptions(autoPilotThroughputParams)).toEqual({
autoscaleSettings: {
maxThroughput: 4000
}
});
});
});

View File

@@ -1,371 +0,0 @@
import * as DataModels from "../../Contracts/DataModels";
import * as ErrorParserUtility from "../ErrorParserUtility";
import { AuthType } from "../../AuthType";
import { ContainerResponse, DatabaseResponse } from "@azure/cosmos";
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
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 {
createUpdateMongoDBCollection,
getMongoDBCollection
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import {
createUpdateGremlinGraph,
getGremlinGraph
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { logConsoleProgress, logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { refreshCachedResources } from "../DataAccessUtilityBase";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
import { createDatabase } from "./createDatabase";
export const createCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
let collection: DataModels.Collection;
const clearMessage = logConsoleProgress(
`Creating a new container ${params.collectionId} for database ${params.databaseId}`
);
try {
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
if (params.createNewDatabase) {
const createDatabaseParams: DataModels.CreateDatabaseParams = {
autoPilotMaxThroughput: params.autoPilotMaxThroughput,
databaseId: params.databaseId,
databaseLevelThroughput: params.databaseLevelThroughput,
offerThroughput: params.offerThroughput
};
await createDatabase(createDatabaseParams);
}
collection = await createCollectionWithARM(params);
} else if (userContext.defaultExperience === DefaultAccountExperienceType.MongoDB) {
collection = await createMongoCollectionWithProxy(params);
} else {
collection = await createCollectionWithSDK(params);
}
} catch (error) {
const sanitizedError = ErrorParserUtility.replaceKnownError(JSON.stringify(error));
logConsoleError(`Error while creating container ${params.collectionId}:\n ${sanitizedError}`);
logError(JSON.stringify(error), "CreateCollection", error.code);
sendNotificationForError(error);
clearMessage();
throw error;
}
logConsoleInfo(`Successfully created container ${params.collectionId}`);
await refreshCachedResources();
clearMessage();
return collection;
};
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
return createSqlContainer(params);
case DefaultAccountExperienceType.MongoDB:
return createMongoCollection(params);
case DefaultAccountExperienceType.Cassandra:
return createCassandraTable(params);
case DefaultAccountExperienceType.Graph:
return createGraph(params);
case DefaultAccountExperienceType.Table:
return createTable(params);
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
};
const createSqlContainer = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
try {
const getResponse = await getSqlContainer(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
params.collectionId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create container failed: container with id ${params.collectionId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
const resource: ARMTypes.SqlContainerResource = {
id: params.collectionId
};
if (params.analyticalStorageTtl) {
resource.analyticalStorageTtl = params.analyticalStorageTtl;
}
if (params.indexingPolicy) {
resource.indexingPolicy = params.indexingPolicy;
}
if (params.partitionKey) {
resource.partitionKey = params.partitionKey;
}
if (params.uniqueKeyPolicy) {
resource.uniqueKeyPolicy = params.uniqueKeyPolicy;
}
const rpPayload: ARMTypes.SqlDatabaseCreateUpdateParameters = {
properties: {
resource,
options
}
};
const createResponse = await createUpdateSqlContainer(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
params.collectionId,
rpPayload
);
return createResponse && (createResponse.properties.resource as DataModels.Collection);
};
const createMongoCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
try {
const getResponse = await getMongoDBCollection(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
params.collectionId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create collection failed: collection with id ${params.collectionId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
const resource: ARMTypes.MongoDBCollectionResource = {
id: params.collectionId
};
if (params.analyticalStorageTtl) {
resource.analyticalStorageTtl = params.analyticalStorageTtl;
}
if (params.partitionKey) {
const partitionKeyPath: string = params.partitionKey.paths[0];
resource.shardKey = { [partitionKeyPath]: "Hash" };
}
const rpPayload: ARMTypes.MongoDBCollectionCreateUpdateParameters = {
properties: {
resource,
options
}
};
const createResponse = await createUpdateMongoDBCollection(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
params.collectionId,
rpPayload
);
return createResponse && (createResponse.properties.resource as DataModels.Collection);
};
const createCassandraTable = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
try {
const getResponse = await getCassandraTable(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
params.collectionId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create table failed: table with id ${params.collectionId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
const resource: ARMTypes.CassandraTableResource = {
id: params.collectionId
};
if (params.analyticalStorageTtl) {
resource.analyticalStorageTtl = params.analyticalStorageTtl;
}
const rpPayload: ARMTypes.CassandraTableCreateUpdateParameters = {
properties: {
resource,
options
}
};
const createResponse = await createUpdateCassandraTable(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
params.collectionId,
rpPayload
);
return createResponse && (createResponse.properties.resource as DataModels.Collection);
};
const createGraph = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
try {
const getResponse = await getGremlinGraph(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
params.collectionId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create graph failed: graph with id ${params.collectionId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
const resource: ARMTypes.GremlinGraphResource = {
id: params.collectionId
};
if (params.indexingPolicy) {
resource.indexingPolicy = params.indexingPolicy;
}
if (params.partitionKey) {
resource.partitionKey = params.partitionKey;
}
if (params.uniqueKeyPolicy) {
resource.uniqueKeyPolicy = params.uniqueKeyPolicy;
}
const rpPayload: ARMTypes.GremlinGraphCreateUpdateParameters = {
properties: {
resource,
options
}
};
const createResponse = await createUpdateGremlinGraph(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
params.collectionId,
rpPayload
);
return createResponse && (createResponse.properties.resource as DataModels.Collection);
};
const createTable = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
try {
const getResponse = await getTable(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.collectionId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create table failed: table with id ${params.collectionId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
const resource: ARMTypes.TableResource = {
id: params.collectionId
};
const rpPayload: ARMTypes.TableCreateUpdateParameters = {
properties: {
resource,
options
}
};
const createResponse = await createUpdateTable(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.collectionId,
rpPayload
);
return createResponse && (createResponse.properties.resource as DataModels.Collection);
};
export const constructRpOptions = (params: DataModels.CreateDatabaseParams): ARMTypes.CreateUpdateOptions => {
if (params.databaseLevelThroughput) {
return {};
}
if (params.autoPilotMaxThroughput) {
return {
autoscaleSettings: {
maxThroughput: params.autoPilotMaxThroughput
}
};
}
return {
throughput: params.offerThroughput
};
};
const createCollectionWithSDK = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
const createCollectionBody: ContainerRequest = {
id: params.collectionId,
partitionKey: params.partitionKey || undefined,
indexingPolicy: params.indexingPolicy || undefined,
uniqueKeyPolicy: params.uniqueKeyPolicy || undefined,
analyticalStorageTtl: params.analyticalStorageTtl
} as ContainerRequest; // TODO: remove cast when https://github.com/Azure/azure-cosmos-js/issues/423 is fixed
const collectionOptions: RequestOptions = {};
const createDatabaseBody: DatabaseRequest = { id: params.databaseId };
if (params.databaseLevelThroughput) {
if (params.autoPilotMaxThroughput) {
createDatabaseBody.maxThroughput = params.autoPilotMaxThroughput;
} else {
createDatabaseBody.throughput = params.offerThroughput;
}
} else {
if (params.autoPilotMaxThroughput) {
createCollectionBody.maxThroughput = params.autoPilotMaxThroughput;
} else {
createCollectionBody.throughput = params.offerThroughput;
}
}
const databaseResponse: DatabaseResponse = await client().databases.createIfNotExists(createDatabaseBody);
const collectionResponse: ContainerResponse = await databaseResponse?.database.containers.create(
createCollectionBody,
collectionOptions
);
return collectionResponse?.resource as DataModels.Collection;
};

View File

@@ -1,251 +0,0 @@
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 { 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 {
createUpdateMongoDBDatabase,
getMongoDBDatabase
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import {
createUpdateGremlinDatabase,
getGremlinDatabase
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { logConsoleProgress, logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { refreshCachedOffers, refreshCachedResources } from "../DataAccessUtilityBase";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
export async function createDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
let database: DataModels.Database;
const clearMessage = logConsoleProgress(`Creating a new database ${params.databaseId}`);
try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
database = await createDatabaseWithARM(params);
} else {
database = await createDatabaseWithSDK(params);
}
} catch (error) {
logConsoleError(`Error while creating database ${params.databaseId}:\n ${error.message}`);
logError(JSON.stringify(error), "CreateDatabase", error.code);
sendNotificationForError(error);
clearMessage();
throw error;
}
logConsoleInfo(`Successfully created database ${params.databaseId}`);
await refreshCachedResources();
await refreshCachedOffers();
clearMessage();
return database;
}
async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
return createSqlDatabase(params);
case DefaultAccountExperienceType.MongoDB:
return createMongoDatabase(params);
case DefaultAccountExperienceType.Cassandra:
return createCassandraKeyspace(params);
case DefaultAccountExperienceType.Graph:
return createGremlineDatabase(params);
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
}
async function createSqlDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
try {
const getResponse = await getSqlDatabase(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: CreateUpdateOptions = constructRpOptions(params);
const rpPayload: SqlDatabaseCreateUpdateParameters = {
properties: {
resource: {
id: params.databaseId
},
options
}
};
const createResponse = await createUpdateSqlDatabase(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
rpPayload
);
return createResponse && (createResponse.properties.resource as DataModels.Database);
}
async function createMongoDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
try {
const getResponse = await getMongoDBDatabase(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: CreateUpdateOptions = constructRpOptions(params);
const rpPayload: MongoDBDatabaseCreateUpdateParameters = {
properties: {
resource: {
id: params.databaseId
},
options
}
};
const createResponse = await createUpdateMongoDBDatabase(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
rpPayload
);
return createResponse && (createResponse.properties.resource as DataModels.Database);
}
async function createCassandraKeyspace(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
try {
const getResponse = await getCassandraKeyspace(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: CreateUpdateOptions = constructRpOptions(params);
const rpPayload: CassandraKeyspaceCreateUpdateParameters = {
properties: {
resource: {
id: params.databaseId
},
options
}
};
const createResponse = await createUpdateCassandraKeyspace(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
rpPayload
);
return createResponse && (createResponse.properties.resource as DataModels.Database);
}
async function createGremlineDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
try {
const getResponse = await getGremlinDatabase(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: CreateUpdateOptions = constructRpOptions(params);
const rpPayload: GremlinDatabaseCreateUpdateParameters = {
properties: {
resource: {
id: params.databaseId
},
options
}
};
const createResponse = await createUpdateGremlinDatabase(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
rpPayload
);
return createResponse && (createResponse.properties.resource as DataModels.Database);
}
async function createDatabaseWithSDK(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
const createBody: DatabaseRequest = { id: params.databaseId };
if (params.databaseLevelThroughput) {
if (params.autoPilotMaxThroughput) {
createBody.maxThroughput = params.autoPilotMaxThroughput;
} else {
createBody.throughput = params.offerThroughput;
}
}
const response: DatabaseResponse = await client().databases.create(createBody);
return response.resource;
}
function constructRpOptions(params: DataModels.CreateDatabaseParams): CreateUpdateOptions {
if (!params.databaseLevelThroughput) {
return {};
}
if (params.autoPilotMaxThroughput) {
return {
autoscaleSettings: {
maxThroughput: params.autoPilotMaxThroughput
}
};
}
return {
throughput: params.offerThroughput
};
}

View File

@@ -1,46 +0,0 @@
jest.mock("../../Utils/arm/request");
jest.mock("../MessageHandler");
jest.mock("../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 { sendCachedDataMessage } from "../MessageHandler";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
describe("deleteCollection", () => {
beforeAll(() => {
updateUserContext({
databaseAccount: {
name: "test"
} as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB
});
(sendCachedDataMessage as jest.Mock).mockResolvedValue(undefined);
});
it("should call ARM if logged in with AAD", async () => {
window.authType = AuthType.AAD;
await deleteCollection("database", "collection");
expect(armRequest).toHaveBeenCalled();
});
it("should call SDK if not logged in with non-AAD method", async () => {
window.authType = AuthType.MasterKey;
(client as jest.Mock).mockReturnValue({
database: () => {
return {
container: () => {
return {
delete: (): unknown => undefined
};
}
};
}
});
await deleteCollection("database", "collection");
expect(client).toHaveBeenCalled();
});
});

View File

@@ -1,57 +0,0 @@
import { AuthType } from "../../AuthType";
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 { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
import { client } from "../CosmosClient";
import { refreshCachedResources } from "../DataAccessUtilityBase";
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
try {
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
await deleteCollectionWithARM(databaseId, collectionId);
} else {
await client()
.database(databaseId)
.container(collectionId)
.delete();
}
} catch (error) {
logConsoleError(`Error while deleting container ${collectionId}:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "DeleteCollection", error.code);
sendNotificationForError(error);
throw error;
}
logConsoleInfo(`Successfully deleted container ${collectionId}`);
clearMessage();
await refreshCachedResources();
}
function deleteCollectionWithARM(databaseId: string, collectionId: string): Promise<void> {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
return deleteSqlContainer(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
case DefaultAccountExperienceType.MongoDB:
return deleteMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
case DefaultAccountExperienceType.Cassandra:
return deleteCassandraTable(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
case DefaultAccountExperienceType.Graph:
return deleteGremlinGraph(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
case DefaultAccountExperienceType.Table:
return deleteTable(subscriptionId, resourceGroup, accountName, collectionId);
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
}

View File

@@ -1,42 +0,0 @@
jest.mock("../../Utils/arm/request");
jest.mock("../MessageHandler");
jest.mock("../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 { sendCachedDataMessage } from "../MessageHandler";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
describe("deleteDatabase", () => {
beforeAll(() => {
updateUserContext({
databaseAccount: {
name: "test"
} as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB
});
(sendCachedDataMessage as jest.Mock).mockResolvedValue(undefined);
});
it("should call ARM if logged in with AAD", async () => {
window.authType = AuthType.AAD;
await deleteDatabase("database");
expect(armRequest).toHaveBeenCalled();
});
it("should call SDK if not logged in with non-AAD method", async () => {
window.authType = AuthType.MasterKey;
(client as jest.Mock).mockReturnValue({
database: () => {
return {
delete: (): unknown => undefined
};
}
});
await deleteDatabase("database");
expect(client).toHaveBeenCalled();
});
});

View File

@@ -1,58 +0,0 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { deleteSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
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 { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
import { client } from "../CosmosClient";
import { refreshCachedResources } from "../DataAccessUtilityBase";
import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
export async function deleteDatabase(databaseId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
try {
if (
window.authType === AuthType.AAD &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table &&
!userContext.useSDKOperations
) {
await deleteDatabaseWithARM(databaseId);
} else {
await client()
.database(databaseId)
.delete();
}
} catch (error) {
logConsoleError(`Error while deleting database ${databaseId}:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "DeleteDatabase", error.code);
sendNotificationForError(error);
throw error;
}
logConsoleInfo(`Successfully deleted database ${databaseId}`);
clearMessage();
await refreshCachedResources();
}
function deleteDatabaseWithARM(databaseId: string): Promise<void> {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
return deleteSqlDatabase(subscriptionId, resourceGroup, accountName, databaseId);
case DefaultAccountExperienceType.MongoDB:
return deleteMongoDBDatabase(subscriptionId, resourceGroup, accountName, databaseId);
case DefaultAccountExperienceType.Cassandra:
return deleteCassandraKeyspace(subscriptionId, resourceGroup, accountName, databaseId);
case DefaultAccountExperienceType.Graph:
return deleteGremlinDatabase(subscriptionId, resourceGroup, accountName, databaseId);
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
}

View File

@@ -1,35 +0,0 @@
jest.mock("../CosmosClient");
import { AuthType } from "../../AuthType";
import { DatabaseAccount } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient";
import { readCollection } from "./readCollection";
import { updateUserContext } from "../../UserContext";
describe("readCollection", () => {
beforeAll(() => {
updateUserContext({
databaseAccount: {
name: "test"
} as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB
});
});
it("should call SDK if logged in with resource token", async () => {
window.authType = AuthType.ResourceToken;
(client as jest.Mock).mockReturnValue({
database: () => {
return {
container: () => {
return {
read: (): unknown => ({})
};
}
};
}
});
await readCollection("database", "collection");
expect(client).toHaveBeenCalled();
});
});

View File

@@ -1,24 +0,0 @@
import * as DataModels from "../../Contracts/DataModels";
import { client } from "../CosmosClient";
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
export async function readCollection(databaseId: string, collectionId: string): Promise<DataModels.Collection> {
let collection: DataModels.Collection;
const clearMessage = logConsoleProgress(`Querying container ${collectionId}`);
try {
const response = await client()
.database(databaseId)
.container(collectionId)
.read();
collection = response.resource as DataModels.Collection;
} catch (error) {
logConsoleError(`Error while querying container ${collectionId}:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "ReadCollection", error.code);
sendNotificationForError(error);
throw error;
}
clearMessage();
return collection;
}

View File

@@ -1,45 +0,0 @@
jest.mock("../../Utils/arm/request");
jest.mock("../CosmosClient");
import { AuthType } from "../../AuthType";
import { DatabaseAccount } from "../../Contracts/DataModels";
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(() => {
updateUserContext({
databaseAccount: {
name: "test"
} as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB
});
});
it("should call ARM if logged in with AAD", async () => {
window.authType = AuthType.AAD;
await readCollections("database");
expect(armRequest).toHaveBeenCalled();
});
it("should call SDK if not logged in with non-AAD method", async () => {
window.authType = AuthType.MasterKey;
(client as jest.Mock).mockReturnValue({
database: () => {
return {
containers: {
readAll: () => {
return {
fetchAll: (): unknown => []
};
}
}
};
}
});
await readCollections("database");
expect(client).toHaveBeenCalled();
});
});

View File

@@ -1,71 +0,0 @@
import * as DataModels from "../../Contracts/DataModels";
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient";
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, logConsoleError } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
let collections: DataModels.Collection[];
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
collections = await readCollectionsWithARM(databaseId);
} else {
const sdkResponse = await client()
.database(databaseId)
.containers.readAll()
.fetchAll();
collections = sdkResponse.resources as DataModels.Collection[];
}
} catch (error) {
logConsoleError(`Error while querying containers for database ${databaseId}:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "ReadCollections", error.code);
sendNotificationForError(error);
throw error;
}
clearMessage();
return collections;
}
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;
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
rpResponse = await listSqlContainers(subscriptionId, resourceGroup, accountName, databaseId);
break;
case DefaultAccountExperienceType.MongoDB:
rpResponse = await listMongoDBCollections(subscriptionId, resourceGroup, accountName, databaseId);
break;
case DefaultAccountExperienceType.Cassandra:
rpResponse = await listCassandraTables(subscriptionId, resourceGroup, accountName, databaseId);
break;
case DefaultAccountExperienceType.Graph:
rpResponse = await listGremlinGraphs(subscriptionId, resourceGroup, accountName, databaseId);
break;
case DefaultAccountExperienceType.Table:
rpResponse = await listTables(subscriptionId, resourceGroup, accountName);
break;
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
return rpResponse?.value?.map(collection => collection.properties?.resource as DataModels.Collection);
}

View File

@@ -1,41 +0,0 @@
jest.mock("../../Utils/arm/request");
jest.mock("../CosmosClient");
import { AuthType } from "../../AuthType";
import { DatabaseAccount } from "../../Contracts/DataModels";
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(() => {
updateUserContext({
databaseAccount: {
name: "test"
} as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB
});
});
it("should call ARM if logged in with AAD", async () => {
window.authType = AuthType.AAD;
await readDatabases();
expect(armRequest).toHaveBeenCalled();
});
it("should call SDK if not logged in with non-AAD method", async () => {
window.authType = AuthType.MasterKey;
(client as jest.Mock).mockReturnValue({
databases: {
readAll: () => {
return {
fetchAll: (): unknown => []
};
}
}
});
await readDatabases();
expect(client).toHaveBeenCalled();
});
});

View File

@@ -1,67 +0,0 @@
import * as DataModels from "../../Contracts/DataModels";
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient";
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, logConsoleError } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
export async function readDatabases(): Promise<DataModels.Database[]> {
let databases: DataModels.Database[];
const clearMessage = logConsoleProgress(`Querying databases`);
try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table &&
userContext.defaultExperience !== DefaultAccountExperienceType.Cassandra
) {
databases = await readDatabasesWithARM();
} else {
const sdkResponse = await client()
.databases.readAll()
.fetchAll();
databases = sdkResponse.resources as DataModels.Database[];
}
} catch (error) {
logConsoleError(`Error while querying databases:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "ReadDatabases", error.code);
sendNotificationForError(error);
throw error;
}
clearMessage();
return databases;
}
async function readDatabasesWithARM(): Promise<DataModels.Database[]> {
let rpResponse;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
rpResponse = await listSqlDatabases(subscriptionId, resourceGroup, accountName);
break;
case DefaultAccountExperienceType.MongoDB:
rpResponse = await listMongoDBDatabases(subscriptionId, resourceGroup, accountName);
break;
case DefaultAccountExperienceType.Cassandra:
rpResponse = await listCassandraKeyspaces(subscriptionId, resourceGroup, accountName);
break;
case DefaultAccountExperienceType.Graph:
rpResponse = await listGremlinDatabases(subscriptionId, resourceGroup, accountName);
break;
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
return rpResponse?.value?.map(database => database.properties?.resource as DataModels.Database);
}

View File

@@ -1,20 +0,0 @@
import * as Constants from "../Constants";
import { sendMessage } from "../MessageHandler";
import { MessageTypes } from "../../Contracts/ExplorerContracts";
interface CosmosError {
code: number;
message?: string;
}
export function sendNotificationForError(error: CosmosError): void {
if (error && error.code === Constants.HttpStatusCodes.Forbidden) {
if (error.message && error.message.toLowerCase().indexOf("sharedoffer is disabled for your account") > 0) {
return;
}
sendMessage({
type: MessageTypes.ForbiddenError,
reason: error && error.message ? error.message : error
});
}
}

View File

@@ -1,225 +0,0 @@
import { AuthType } from "../../AuthType";
import { Collection } from "../../Contracts/DataModels";
import { ContainerDefinition } from "@azure/cosmos";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import {
ExtendedResourceProperties,
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 {
createUpdateMongoDBCollection,
getMongoDBCollection
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import {
createUpdateGremlinGraph,
getGremlinGraph
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { refreshCachedResources } from "../DataAccessUtilityBase";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
export async function updateCollection(
databaseId: string,
collectionId: string,
newCollection: Collection,
options: RequestOptions = {}
): Promise<Collection> {
let collection: Collection;
const clearMessage = logConsoleProgress(`Updating container ${collectionId}`);
try {
if (
window.authType === AuthType.AAD &&
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
collection = await updateCollectionWithARM(databaseId, collectionId, newCollection);
} else {
const sdkResponse = await client()
.database(databaseId)
.container(collectionId)
.replace(newCollection as ContainerDefinition, options);
collection = sdkResponse.resource as Collection;
}
} catch (error) {
logConsoleError(`Failed to update container ${collectionId}: ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "UpdateCollection", error.code);
sendNotificationForError(error);
throw error;
}
logConsoleInfo(`Successfully updated container ${collectionId}`);
clearMessage();
await refreshCachedResources();
return collection;
}
async function updateCollectionWithARM(
databaseId: string,
collectionId: string,
newCollection: Collection
): Promise<Collection> {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
return updateSqlContainer(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case DefaultAccountExperienceType.MongoDB:
return updateMongoDBCollection(
databaseId,
collectionId,
subscriptionId,
resourceGroup,
accountName,
newCollection
);
case DefaultAccountExperienceType.Cassandra:
return updateCassandraTable(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case DefaultAccountExperienceType.Graph:
return updateGremlinGraph(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case DefaultAccountExperienceType.Table:
return updateTable(collectionId, subscriptionId, resourceGroup, accountName, newCollection);
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
}
async function updateSqlContainer(
databaseId: string,
collectionId: string,
subscriptionId: string,
resourceGroup: string,
accountName: string,
newCollection: Collection
): Promise<Collection> {
const getResponse = await getSqlContainer(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) {
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
const updateResponse = await createUpdateSqlContainer(
subscriptionId,
resourceGroup,
accountName,
databaseId,
collectionId,
getResponse as SqlContainerCreateUpdateParameters
);
return updateResponse && (updateResponse.properties.resource as Collection);
}
throw new Error(`Sql container to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
}
async function updateMongoDBCollection(
databaseId: string,
collectionId: string,
subscriptionId: string,
resourceGroup: string,
accountName: string,
newCollection: Collection
): Promise<Collection> {
const getResponse = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) {
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
const updateResponse = await createUpdateMongoDBCollection(
subscriptionId,
resourceGroup,
accountName,
databaseId,
collectionId,
getResponse as SqlContainerCreateUpdateParameters
);
return updateResponse && (updateResponse.properties.resource as Collection);
}
throw new Error(
`MongoDB collection to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`
);
}
async function updateCassandraTable(
databaseId: string,
collectionId: string,
subscriptionId: string,
resourceGroup: string,
accountName: string,
newCollection: Collection
): Promise<Collection> {
const getResponse = await getCassandraTable(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) {
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
const updateResponse = await createUpdateCassandraTable(
subscriptionId,
resourceGroup,
accountName,
databaseId,
collectionId,
getResponse as SqlContainerCreateUpdateParameters
);
return updateResponse && (updateResponse.properties.resource as Collection);
}
throw new Error(
`Cassandra table to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`
);
}
async function updateGremlinGraph(
databaseId: string,
collectionId: string,
subscriptionId: string,
resourceGroup: string,
accountName: string,
newCollection: Collection
): Promise<Collection> {
const getResponse = await getGremlinGraph(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) {
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
const updateResponse = await createUpdateGremlinGraph(
subscriptionId,
resourceGroup,
accountName,
databaseId,
collectionId,
getResponse as SqlContainerCreateUpdateParameters
);
return updateResponse && (updateResponse.properties.resource as Collection);
}
throw new Error(`Gremlin graph to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
}
async function updateTable(
collectionId: string,
subscriptionId: string,
resourceGroup: string,
accountName: string,
newCollection: Collection
): Promise<Collection> {
const getResponse = await getTable(subscriptionId, resourceGroup, accountName, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) {
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
const updateResponse = await createUpdateTable(
subscriptionId,
resourceGroup,
accountName,
collectionId,
getResponse as SqlContainerCreateUpdateParameters
);
return updateResponse && (updateResponse.properties.resource as Collection);
}
throw new Error(`Table to update does not exist. Table id: ${collectionId}`);
}

View File

@@ -1,26 +0,0 @@
import { updateOfferThroughputBeyondLimit } from "./updateOfferThroughputBeyondLimit";
describe("updateOfferThroughputBeyondLimit", () => {
it("should call fetch", async () => {
window.fetch = jest.fn(() => {
return {
ok: true
};
});
window.dataExplorer = {
logConsoleData: jest.fn(),
deleteInProgressConsoleDataWithId: jest.fn(),
extensionEndpoint: jest.fn()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any;
await updateOfferThroughputBeyondLimit({
subscriptionId: "foo",
resourceGroup: "foo",
databaseAccountName: "foo",
databaseName: "foo",
throughput: 1000000000,
offerIsRUPerMinuteThroughputEnabled: false
});
expect(window.fetch).toHaveBeenCalled();
});
});

View File

@@ -1,52 +0,0 @@
import { Platform, configContext } from "../../ConfigContext";
import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
import { AutoPilotOfferSettings } from "../../Contracts/DataModels";
import { logConsoleProgress, logConsoleInfo, logConsoleError } from "../../Utils/NotificationConsoleUtils";
import { HttpHeaders } from "../Constants";
interface UpdateOfferThroughputRequest {
subscriptionId: string;
resourceGroup: string;
databaseAccountName: string;
databaseName: string;
collectionName?: string;
throughput: number;
offerIsRUPerMinuteThroughputEnabled: boolean;
offerAutopilotSettings?: AutoPilotOfferSettings;
}
export async function updateOfferThroughputBeyondLimit(request: UpdateOfferThroughputRequest): Promise<void> {
if (configContext.platform !== Platform.Portal) {
throw new Error("Updating throughput beyond specified limit is not supported on this platform");
}
const resourceDescriptionInfo = request.collectionName
? `database ${request.databaseName} and container ${request.collectionName}`
: `database ${request.databaseName}`;
const clearMessage = logConsoleProgress(
`Requesting increase in throughput to ${request.throughput} for ${resourceDescriptionInfo}`
);
const explorer = window.dataExplorer;
const url = `${explorer.extensionEndpoint()}/api/offerthroughputrequest/updatebeyondspecifiedlimit`;
const authorizationHeader = getAuthorizationHeader();
const response = await fetch(url, {
method: "POST",
body: JSON.stringify(request),
headers: { [authorizationHeader.header]: authorizationHeader.token, [HttpHeaders.contentType]: "application/json" }
});
if (response.ok) {
logConsoleInfo(
`Successfully requested an increase in throughput to ${request.throughput} for ${resourceDescriptionInfo}`
);
clearMessage();
return undefined;
}
const error = await response.json();
logConsoleError(`Failed to request an increase in throughput for ${request.throughput}: ${error.message}`);
clearMessage();
throw new Error(error.message);
}

View File

@@ -4,7 +4,7 @@ export enum Platform {
Emulator = "Emulator" Emulator = "Emulator"
} }
interface ConfigContext { interface Config {
platform: Platform; platform: Platform;
allowedParentFrameOrigins: RegExp; allowedParentFrameOrigins: RegExp;
gitSha?: string; gitSha?: string;
@@ -28,7 +28,7 @@ interface ConfigContext {
} }
// Default configuration // Default configuration
let configContext: Readonly<ConfigContext> = { let config: Config = {
platform: Platform.Portal, platform: Platform.Portal,
allowedParentFrameOrigins: /^https:\/\/portal\.azure\.com$|^https:\/\/portal\.azure\.us$|^https:\/\/portal\.azure\.cn$|^https:\/\/portal\.microsoftazure\.de$|^https:\/\/.+\.portal\.azure\.com$|^https:\/\/.+\.portal\.azure\.us$|^https:\/\/.+\.portal\.azure\.cn$|^https:\/\/.+\.portal\.microsoftazure\.de$|^https:\/\/main\.documentdb\.ext\.azure\.com$|^https:\/\/main\.documentdb\.ext\.microsoftazure\.de$|^https:\/\/main\.documentdb\.ext\.azure\.cn$|^https:\/\/main\.documentdb\.ext\.azure\.us$/, allowedParentFrameOrigins: /^https:\/\/portal\.azure\.com$|^https:\/\/portal\.azure\.us$|^https:\/\/portal\.azure\.cn$|^https:\/\/portal\.microsoftazure\.de$|^https:\/\/.+\.portal\.azure\.com$|^https:\/\/.+\.portal\.azure\.us$|^https:\/\/.+\.portal\.azure\.cn$|^https:\/\/.+\.portal\.microsoftazure\.de$|^https:\/\/main\.documentdb\.ext\.azure\.com$|^https:\/\/main\.documentdb\.ext\.microsoftazure\.de$|^https:\/\/main\.documentdb\.ext\.azure\.cn$|^https:\/\/main\.documentdb\.ext\.azure\.us$/,
// Webpack injects this at build time // Webpack injects this at build time
@@ -46,58 +46,36 @@ let configContext: Readonly<ConfigContext> = {
JUNO_ENDPOINT: "https://tools.cosmos.azure.com" JUNO_ENDPOINT: "https://tools.cosmos.azure.com"
}; };
export function resetConfigContext(): void {
if (process.env.NODE_ENV !== "test") {
throw new Error("resetConfigContext can only becalled in a test environment");
}
configContext = {} as ConfigContext;
}
export function updateConfigContext(newContext: Partial<ConfigContext>): void {
Object.assign(configContext, newContext);
}
// Injected for local develpment. These will be removed in the production bundle by webpack // Injected for local develpment. These will be removed in the production bundle by webpack
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === "development") {
const port: string = process.env.PORT || "1234"; const port: string = process.env.PORT || "1234";
updateConfigContext({ config.BACKEND_ENDPOINT = "https://localhost:" + port;
BACKEND_ENDPOINT: "https://localhost:" + port, config.MONGO_BACKEND_ENDPOINT = "https://localhost:" + port;
MONGO_BACKEND_ENDPOINT: "https://localhost:" + port, config.PROXY_PATH = "/proxy";
PROXY_PATH: "/proxy", config.EMULATOR_ENDPOINT = "https://localhost:8081";
EMULATOR_ENDPOINT: "https://localhost:8081"
});
} }
export async function initializeConfiguration(): Promise<ConfigContext> { export async function initializeConfiguration(): Promise<Config> {
try { try {
const response = await fetch("./config.json"); const response = await fetch("./config.json");
if (response.status === 200) { if (response.status === 200) {
try { try {
const externalConfig = await response.json(); const externalConfig = await response.json();
Object.assign(configContext, externalConfig); config = Object.assign({}, config, externalConfig);
} catch (error) { } catch (error) {
console.error("Unable to parse json in config file"); console.error("Unable to parse json in config file");
console.error(error); console.error(error);
} }
} }
// Allow override of platform value with URL query parameter // Allow override of any config value with URL query parameters
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
if (params.has("platform")) { params.forEach((value, key) => {
const platform = params.get("platform"); (config as any)[key] = value;
switch (platform) { });
default:
console.log("Invalid platform query parameter given, ignoring");
break;
case Platform.Portal:
case Platform.Hosted:
case Platform.Emulator:
updateConfigContext({ platform });
}
}
} catch (error) { } catch (error) {
console.log("No configuration file found using defaults"); console.log("No configuration file found using defaults");
} }
return configContext; return config;
} }
export { configContext }; export { config };

View File

@@ -153,14 +153,7 @@ export interface KeyResource {
Token: string; Token: string;
} }
export interface IndexingPolicy { export interface IndexingPolicy {}
automatic: boolean;
indexingMode: string;
includedPaths: any;
excludedPaths: any;
compositeIndexes?: any;
spatialIndexes?: any;
}
export interface PartitionKey { export interface PartitionKey {
paths: string[]; paths: string[];
@@ -319,6 +312,17 @@ export interface Query {
query: string; query: string;
} }
export interface UpdateOfferThroughputRequest {
subscriptionId: string;
resourceGroup: string;
databaseAccountName: string;
databaseName: string;
collectionName: string;
throughput: number;
offerIsRUPerMinuteThroughputEnabled: boolean;
offerAutopilotSettings?: AutoPilotOfferSettings;
}
export interface AutoPilotOfferSettings { export interface AutoPilotOfferSettings {
tier?: AutopilotTier; tier?: AutopilotTier;
maximumTierThroughput?: number; maximumTierThroughput?: number;
@@ -327,24 +331,12 @@ export interface AutoPilotOfferSettings {
targetMaxThroughput?: number; targetMaxThroughput?: number;
} }
export interface CreateDatabaseParams { export interface CreateDatabaseRequest {
autoPilotMaxThroughput?: number;
databaseId: string; databaseId: string;
databaseLevelThroughput?: boolean; databaseLevelThroughput?: boolean;
offerThroughput?: number; offerThroughput?: number;
} autoPilot?: AutoPilotCreationSettings;
hasAutoPilotV2FeatureFlag?: boolean;
export interface CreateCollectionParams {
createNewDatabase: boolean;
collectionId: string;
databaseId: string;
databaseLevelThroughput: boolean;
offerThroughput: number;
analyticalStorageTtl?: number;
autoPilotMaxThroughput?: number;
indexingPolicy?: IndexingPolicy;
partitionKey?: PartitionKey;
uniqueKeyPolicy?: UniqueKeyPolicy;
} }
export interface SharedThroughputRange { export interface SharedThroughputRange {

View File

@@ -1,16 +1,41 @@
import * as DataModels from "./DataModels"; import * as DataModels from "./DataModels";
import * as monaco from "monaco-editor";
import DocumentClientUtilityBase from "../Common/DocumentClientUtilityBase";
import Q from "q"; import Q from "q";
import { AccessibleVerticalList } from "../Explorer/Tree/AccessibleVerticalList";
import { CassandraTableKey, CassandraTableKeys } from "../Explorer/Tables/TableDataClient"; import { CassandraTableKey, CassandraTableKeys } from "../Explorer/Tables/TableDataClient";
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { GitHubClient } from "../GitHub/GitHubClient";
import { JunoClient, IGalleryItem } from "../Juno/JunoClient";
import { NotebookContentItem } from "../Explorer/Notebook/NotebookContentItem";
import { QueryMetrics } from "@azure/cosmos"; import { QueryMetrics } from "@azure/cosmos";
import { UploadDetails } from "../workers/upload/definitions"; import { UploadDetails } from "../workers/upload/definitions";
import Explorer from "../Explorer/Explorer"; import Explorer from "../Explorer/Explorer";
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction"; import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
import StoredProcedure from "../Explorer/Tree/StoredProcedure"; import StoredProcedure from "../Explorer/Tree/StoredProcedure";
import ConflictsTab from "../Explorer/Tabs/ConflictsTab";
import Trigger from "../Explorer/Tree/Trigger"; import Trigger from "../Explorer/Tree/Trigger";
import DocumentId from "../Explorer/Tree/DocumentId";
import ConflictId from "../Explorer/Tree/ConflictId"; export interface ExplorerOptions {
documentClientUtility: DocumentClientUtilityBase;
notificationsClient: NotificationsClient;
isEmulator: boolean;
}
export interface Capability extends DataModels.Capability {}
export interface ConfigurationOverrides extends DataModels.ConfigurationOverrides {}
export interface NavbarButtonConfig extends CommandButtonComponentProps {}
export interface DatabaseAccount extends DataModels.DatabaseAccount {}
export interface KernelConnectionMetadata {
name: string;
configurationEndpoints: DataModels.NotebookConfigurationEndpoints;
notebookConnectionInfo: DataModels.NotebookWorkspaceConnectionInfo;
}
export interface TokenProvider { export interface TokenProvider {
getAuthHeader(): Promise<Headers>; getAuthHeader(): Promise<Headers>;
@@ -50,6 +75,11 @@ export interface WaitsForTemplate {
isTemplateReady: ko.Observable<boolean>; isTemplateReady: ko.Observable<boolean>;
} }
export interface AdHocAccessData {
readWriteUrl: string;
readUrl: string;
}
export interface TreeNode { export interface TreeNode {
nodeKind: string; nodeKind: string;
rid: string; rid: string;
@@ -171,15 +201,118 @@ export interface Collection extends CollectionBase {
getLabel(): string; getLabel(): string;
} }
export interface DocumentId {
container: DocumentsTab;
rid: string;
self: string;
ts: string;
partitionKeyValue: any;
partitionKeyProperty: string;
partitionKey: DataModels.PartitionKey;
stringPartitionKeyValue: string;
id: ko.Observable<string>;
isDirty: ko.Observable<boolean>;
click(): void;
getPartitionKeyValueAsString(): string;
loadDocument(): Q.Promise<any>;
partitionKeyHeader(): Object;
}
export interface ConflictId {
container: ConflictsTab;
rid: string;
self: string;
ts: string;
partitionKeyValue: any;
partitionKeyProperty: string;
partitionKey: DataModels.PartitionKey;
stringPartitionKeyValue: string;
id: ko.Observable<string>;
operationType: string;
resourceId: string;
resourceType: string;
isDirty: ko.Observable<boolean>;
click(): void;
buildDocumentIdFromConflict(partitionKeyValue: any): DocumentId;
getPartitionKeyValueAsString(): string;
loadConflict(): Q.Promise<any>;
}
/** /**
* Options used to initialize pane * Options used to initialize pane
*/ */
export interface PaneOptions { export interface PaneOptions {
id: string; id: string;
documentClientUtility: DocumentClientUtilityBase;
visible: ko.Observable<boolean>; visible: ko.Observable<boolean>;
container?: Explorer; container?: Explorer;
} }
export interface ContextualPane {
documentClientUtility: DocumentClientUtilityBase;
formErrors: ko.Observable<string>;
formErrorsDetails: ko.Observable<string>;
id: string;
title: ko.Observable<string>;
visible: ko.Observable<boolean>;
firstFieldHasFocus: ko.Observable<boolean>;
isExecuting: ko.Observable<boolean>;
submit: () => void;
cancel: () => void;
open: () => void;
close: () => void;
resetData: () => void;
showErrorDetails: () => void;
onCloseKeyPress(source: any, event: KeyboardEvent): void;
onPaneKeyDown(source: any, event: KeyboardEvent): boolean;
}
export interface GitHubReposPaneOptions extends PaneOptions {
gitHubClient: GitHubClient;
junoClient: JunoClient;
}
export interface PublishNotebookPaneOptions extends PaneOptions {
junoClient: JunoClient;
}
export interface PublishNotebookPaneOpenOptions {
name: string;
author: string;
content: string;
}
export interface AddCollectionPaneOptions extends PaneOptions {
isPreferredApiTable: ko.Computed<boolean>;
databaseId?: string;
databaseSelfLink?: string;
}
export interface UploadFilePaneOpenOptions {
paneTitle: string;
selectFileInputLabel: string;
errorMessage: string; // Could not upload notebook
inProgressMessage: string; // Uploading notebook
successMessage: string; // Successfully uploaded notebook
onSubmit: (file: File) => Promise<any>;
extensions?: string; // input accept field. E.g: .ipynb
submitButtonLabel?: string;
}
export interface StringInputPaneOpenOptions {
paneTitle: string;
inputLabel: string;
errorMessage: string;
inProgressMessage: string;
successMessage: string;
onSubmit: (input: string) => Promise<any>;
submitButtonLabel: string;
defaultInput?: string;
}
/** /**
* Graph configuration * Graph configuration
*/ */
@@ -249,6 +382,19 @@ export interface DocumentRequestContainer {
resourceName?: string; resourceName?: string;
} }
export interface NotificationsClient {
fetchNotifications(): Q.Promise<DataModels.Notification[]>;
setExtensionEndpoint(extensionEndpoint: string): void;
}
export interface QueriesClient {
setupQueriesCollection(): Promise<DataModels.Collection>;
saveQuery(query: DataModels.Query): Promise<void>;
getQueries(): Promise<DataModels.Query[]>;
deleteQuery(query: DataModels.Query): Promise<void>;
getResourceId(): string;
}
export interface DocumentClientOption { export interface DocumentClientOption {
endpoint?: string; endpoint?: string;
masterKey?: string; masterKey?: string;
@@ -260,10 +406,11 @@ export interface TabOptions {
tabKind: CollectionTabKind; tabKind: CollectionTabKind;
title: string; title: string;
tabPath: string; tabPath: string;
documentClientUtility: DocumentClientUtilityBase;
selfLink: string; selfLink: string;
isActive: ko.Observable<boolean>; isActive: ko.Observable<boolean>;
hashLocation: string; hashLocation: string;
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]) => void; onUpdateTabsButtons: (buttons: NavbarButtonConfig[]) => void;
isTabsContentExpanded?: ko.Observable<boolean>; isTabsContentExpanded?: ko.Observable<boolean>;
onLoadStartKey?: number; onLoadStartKey?: number;
@@ -276,6 +423,47 @@ export interface TabOptions {
theme?: string; theme?: string;
} }
export interface SparkMasterTabOptions extends TabOptions {
clusterConnectionInfo: DataModels.SparkClusterConnectionInfo;
container: Explorer;
}
export interface GraphTabOptions extends TabOptions {
account: DatabaseAccount;
masterKey: string;
collectionId: string;
databaseId: string;
collectionPartitionKeyProperty: string;
}
export interface NotebookTabOptions extends TabOptions {
account: DatabaseAccount;
masterKey: string;
container: Explorer;
notebookContentItem: NotebookContentItem;
}
export interface TerminalTabOptions extends TabOptions {
account: DatabaseAccount;
container: Explorer;
kind: TerminalKind;
}
export interface GalleryTabOptions extends TabOptions {
account: DatabaseAccount;
container: Explorer;
junoClient: JunoClient;
notebookUrl?: string;
galleryItem?: IGalleryItem;
isFavorite?: boolean;
}
export interface NotebookViewerTabOptions extends TabOptions {
account: DatabaseAccount;
container: Explorer;
notebookUrl: string;
}
export interface DocumentsTabOptions extends TabOptions { export interface DocumentsTabOptions extends TabOptions {
partitionKey: DataModels.PartitionKey; partitionKey: DataModels.PartitionKey;
documentIds: ko.ObservableArray<DocumentId>; documentIds: ko.ObservableArray<DocumentId>;
@@ -303,15 +491,157 @@ export interface ScriptTabOption extends TabOptions {
partitionKey?: DataModels.PartitionKey; partitionKey?: DataModels.PartitionKey;
} }
// Tabs
export interface Tab {
documentClientUtility: DocumentClientUtilityBase;
node: TreeNode; // Can be null
collection: CollectionBase;
rid: string;
tabKind: CollectionTabKind;
tabId: string;
isActive: ko.Observable<boolean>;
isMouseOver: ko.Observable<boolean>;
tabPath: ko.Observable<string>;
tabTitle: ko.Observable<string>;
hashLocation: ko.Observable<string>;
closeTabButton: Button;
onCloseTabButtonClick(): void;
onTabClick(): Q.Promise<any>;
onKeyPressActivate(source: any, event: KeyboardEvent): void;
onKeyPressClose(source: any, event: KeyboardEvent): void;
onActivate(): Q.Promise<any>;
refresh(): void;
closeButtonTabIndex: ko.Computed<number>;
isExecutionError: ko.Observable<boolean>;
isExecuting: ko.Observable<boolean>;
}
export interface DocumentsTab extends Tab {
/* Documents Grid */
selectDocument(documentId: DocumentId): Q.Promise<any>;
selectedDocumentId: ko.Observable<DocumentId>;
selectedDocumentContent: Editable<any>;
onDocumentIdClick(documentId: DocumentId): Q.Promise<any>;
dataContentsGridScrollHeight: ko.Observable<string>;
accessibleDocumentList: AccessibleVerticalList;
documentContentsGridId: string;
partitionKey: DataModels.PartitionKey;
idHeader: string;
partitionKeyPropertyHeader: string;
partitionKeyProperty: string;
documentIds: ko.ObservableArray<DocumentId>;
/* Documents Filter */
filterContent: ko.Observable<string>;
appliedFilter: ko.Observable<string>;
lastFilterContents: ko.ObservableArray<string>;
isFilterExpanded: ko.Observable<boolean>;
applyFilterButton: Button;
onShowFilterClick(): Q.Promise<any>;
onHideFilterClick(): Q.Promise<any>;
onApplyFilterClick(): Q.Promise<any>;
/* Document Editor */
isEditorDirty: ko.Computed<boolean>;
editorState: ko.Observable<DocumentExplorerState>;
onValidDocumentEdit(content: any): Q.Promise<any>;
onInvalidDocumentEdit(content: any): Q.Promise<any>;
onNewDocumentClick(): Q.Promise<any>;
onSaveNewDocumentClick(): Q.Promise<any>;
onRevertNewDocumentClick(): Q.Promise<any>;
onSaveExisitingDocumentClick(): Q.Promise<any>;
onRevertExisitingDocumentClick(): Q.Promise<any>;
onDeleteExisitingDocumentClick(): Q.Promise<any>;
/* Errors */
displayedError: ko.Observable<string>;
initDocumentEditor(documentId: DocumentId, content: any): Q.Promise<any>;
loadNextPage(): Q.Promise<any>;
}
export interface WaitsForTemplate { export interface WaitsForTemplate {
isTemplateReady: ko.Observable<boolean>; isTemplateReady: ko.Observable<boolean>;
} }
export interface QueryTab extends Tab {
queryEditorId: string;
isQueryMetricsEnabled: ko.Computed<boolean>;
activityId: ko.Observable<string>;
/* Command Bar */
executeQueryButton: Button;
fetchNextPageButton: Button;
saveQueryButton: Button;
onExecuteQueryClick(): Q.Promise<any>;
onFetchNextPageClick(): Q.Promise<any>;
/*Query Editor*/
initialEditorContent: ko.Observable<string>;
sqlQueryEditorContent: ko.Observable<string>;
sqlStatementToExecute: ko.Observable<string>;
/* Results */
allResultsMetadata: ko.ObservableArray<QueryResultsMetadata>;
/* Errors */
errors: ko.ObservableArray<QueryError>;
/* Status */
statusMessge: ko.Observable<string>;
statusIcon: ko.Observable<string>;
}
export interface ScriptTab extends Tab {
id: Editable<string>;
editorId: string;
saveButton: Button;
updateButton: Button;
discardButton: Button;
deleteButton: Button;
editorState: ko.Observable<ScriptEditorState>;
editorContent: ko.Observable<string>;
editor: ko.Observable<monaco.editor.IStandaloneCodeEditor>;
errors: ko.ObservableArray<QueryError>;
statusMessge: ko.Observable<string>;
statusIcon: ko.Observable<string>;
formFields: ko.ObservableArray<Editable<any>>;
formIsValid: ko.Computed<boolean>;
formIsDirty: ko.Computed<boolean>;
isNew: ko.Observable<boolean>;
resource: ko.Observable<DataModels.Resource>;
setBaselines(): void;
}
export interface StoredProcedureTab extends ScriptTab {
onExecuteSprocsResult(result: any, logsData: any): void;
onExecuteSprocsError(error: string): void;
}
export interface UserDefinedFunctionTab extends ScriptTab {}
export interface TriggerTab extends ScriptTab {
triggerType: Editable<string>;
triggerOperation: Editable<string>;
}
export interface GraphTab extends Tab {}
export interface EditorPosition { export interface EditorPosition {
line: number; line: number;
column: number; column: number;
} }
export interface MongoShellTab extends Tab {}
export enum DocumentExplorerState { export enum DocumentExplorerState {
noDocumentSelected, noDocumentSelected,
newDocumentValid, newDocumentValid,
@@ -429,8 +759,40 @@ export interface AuthorizationTokenHeaderMetadata {
token: string; token: string;
} }
export interface TelemetryActions {
sendEvent(name: string, telemetryProperties?: { [propertyName: string]: string }): Q.Promise<any>;
sendError(errorInfo: DataModels.ITelemetryError): Q.Promise<any>;
sendMetric(
name: string,
metricNumber: number,
telemetryProperties?: { [propertyName: string]: string }
): Q.Promise<any>;
}
export interface ConfigurationOverrides {
EnableBsonSchema: string;
}
export interface CosmosDbApi {
isSystemDatabasePredicate: (database: Database) => boolean;
}
export interface DropdownOption<T> { export interface DropdownOption<T> {
text: string; text: string;
value: T; value: T;
disable?: boolean; disable?: boolean;
} }
export interface INotebookContainerClient {
resetWorkspace: () => Promise<void>;
}
export interface INotebookContentClient {
updateItemChildren: (item: NotebookContentItem) => Promise<void>;
createNewNotebookFile: (parent: NotebookContentItem) => Promise<NotebookContentItem>;
deleteContentItem: (item: NotebookContentItem) => Promise<void>;
uploadFileAsync: (name: string, content: string, parent: NotebookContentItem) => Promise<NotebookContentItem>;
renameNotebook: (item: NotebookContentItem, targetName: string) => Promise<NotebookContentItem>;
createDirectory: (parent: NotebookContentItem, newDirectoryName: string) => Promise<NotebookContentItem>;
readFileContent: (filePath: string) => Promise<string>;
}

View File

@@ -12,7 +12,7 @@ import {
PortalTheme PortalTheme
} from "./HeatmapDatatypes"; } from "./HeatmapDatatypes";
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation"; import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
import { sendCachedDataMessage, sendMessage } from "../../Common/MessageHandler"; import { MessageHandler } from "../../Common/MessageHandler";
import { MessageTypes } from "../../Contracts/ExplorerContracts"; import { MessageTypes } from "../../Contracts/ExplorerContracts";
import { StyleConstants } from "../../Common/Constants"; import { StyleConstants } from "../../Common/Constants";
import "./Heatmap.less"; import "./Heatmap.less";
@@ -209,7 +209,7 @@ export class Heatmap {
for (let i = 0; i < this._chartData.dataPoints.length; i++) { for (let i = 0; i < this._chartData.dataPoints.length; i++) {
output.push(this._chartData.dataPoints[i][xAxisIndex]); output.push(this._chartData.dataPoints[i][xAxisIndex]);
} }
sendCachedDataMessage(MessageTypes.LogInfo, output); MessageHandler.sendCachedDataMessage(MessageTypes.LogInfo, output);
}); });
} }
} }
@@ -266,4 +266,4 @@ export function handleMessage(event: MessageEvent) {
} }
window.addEventListener("message", handleMessage, false); window.addEventListener("message", handleMessage, false);
sendMessage("ready"); MessageHandler.sendMessage("ready");

View File

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

View File

@@ -13,7 +13,9 @@ import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponen
import { TabsManagerKOComponent } from "./Tabs/TabsManager"; import { TabsManagerKOComponent } from "./Tabs/TabsManager";
import { ThroughputInputComponent } from "./Controls/ThroughputInput/ThroughputInputComponent"; import { ThroughputInputComponent } from "./Controls/ThroughputInput/ThroughputInputComponent";
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3"; import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
import { ToolbarComponent } from "./Controls/Toolbar/Toolbar";
ko.components.register("toolbar", new ToolbarComponent());
ko.components.register("input-typeahead", new InputTypeaheadComponent()); ko.components.register("input-typeahead", new InputTypeaheadComponent());
ko.components.register("new-vertex-form", NewVertexComponent); ko.components.register("new-vertex-form", NewVertexComponent);
ko.components.register("error-display", new ErrorDisplayComponent()); ko.components.register("error-display", new ErrorDisplayComponent());

View File

@@ -49,12 +49,6 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
{ key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" }, { key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" },
{ key: "feature.enablettl", label: "Enable TTL", value: "true" }, { key: "feature.enablettl", label: "Enable TTL", value: "true" },
{ key: "feature.enablegallerypublish", label: "Enable Notebook Gallery Publishing", value: "true" }, { key: "feature.enablegallerypublish", label: "Enable Notebook Gallery Publishing", value: "true" },
{ key: "feature.enablecodeofconduct", label: "Enable Code Of Conduct Acknowledgement", value: "true" },
{
key: "feature.enableLinkInjection",
label: "Enable Injecting Notebook Viewer Link into the first cell",
value: "true"
},
{ key: "feature.canexceedmaximumvalue", label: "Can exceed max value", value: "true" }, { key: "feature.canexceedmaximumvalue", label: "Can exceed max value", value: "true" },
{ {
key: "feature.enablefixedcollectionwithsharedthroughput", key: "feature.enablefixedcollectionwithsharedthroughput",

View File

@@ -163,14 +163,8 @@ exports[`Feature panel renders all flags 1`] = `
/> />
<StyledCheckboxBase <StyledCheckboxBase
checked={false} checked={false}
key="feature.enablecodeofconduct" key="feature.canexceedmaximumvalue"
label="Enable Code Of Conduct Acknowledgement" label="Can exceed max value"
onChange={[Function]}
/>
<StyledCheckboxBase
checked={false}
key="feature.enableLinkInjection"
label="Enable Injecting Notebook Viewer Link into the first cell"
onChange={[Function]} onChange={[Function]}
/> />
</Stack> </Stack>
@@ -178,12 +172,6 @@ exports[`Feature panel renders all flags 1`] = `
className="checkboxRow" className="checkboxRow"
horizontalAlign="space-between" horizontalAlign="space-between"
> >
<StyledCheckboxBase
checked={false}
key="feature.canexceedmaximumvalue"
label="Can exceed max value"
onChange={[Function]}
/>
<StyledCheckboxBase <StyledCheckboxBase
checked={false} checked={false}
key="feature.enablefixedcollectionwithsharedthroughput" key="feature.enablefixedcollectionwithsharedthroughput"

View File

@@ -5,7 +5,7 @@
import * as React from "react"; import * as React from "react";
import * as DataModels from "../../../Contracts/DataModels"; import * as DataModels from "../../../Contracts/DataModels";
import * as Logger from "../../../Common/Logger"; import * as Logger from "../../../Common/Logger";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; import { NotificationConsoleUtils } from "../../../Utils/NotificationConsoleUtils";
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
import { StringUtils } from "../../../Utils/StringUtils"; import { StringUtils } from "../../../Utils/StringUtils";

View File

@@ -17,8 +17,7 @@ describe("GalleryCardComponent", () => {
isSample: false, isSample: false,
downloads: 0, downloads: 0,
favorites: 0, favorites: 0,
views: 0, views: 0
newCellId: undefined
}, },
isFavorite: false, isFavorite: false,
showDownload: true, showDownload: true,

View File

@@ -36,8 +36,6 @@ export interface GalleryCardComponentProps {
export class GalleryCardComponent extends React.Component<GalleryCardComponentProps> { export class GalleryCardComponent extends React.Component<GalleryCardComponentProps> {
public static readonly CARD_WIDTH = 256; public static readonly CARD_WIDTH = 256;
private static readonly cardImageHeight = 144; private static readonly cardImageHeight = 144;
public static readonly cardHeightToWidthRatio =
GalleryCardComponent.cardImageHeight / GalleryCardComponent.CARD_WIDTH;
private static readonly cardDescriptionMaxChars = 88; private static readonly cardDescriptionMaxChars = 88;
private static readonly cardItemGapBig = 10; private static readonly cardItemGapBig = 10;
private static readonly cardItemGapSmall = 8; private static readonly cardItemGapSmall = 8;

View File

@@ -1,43 +0,0 @@
import { shallow } from "enzyme";
import * as sinon from "sinon";
import React from "react";
import { CodeOfConductComponent, CodeOfConductComponentProps } from "./CodeOfConductComponent";
import { IJunoResponse, JunoClient } from "../../../Juno/JunoClient";
import { HttpStatusCodes } from "../../../Common/Constants";
describe("CodeOfConductComponent", () => {
let sandbox: sinon.SinonSandbox;
let codeOfConductProps: CodeOfConductComponentProps;
beforeEach(() => {
sandbox = sinon.sandbox.create();
sandbox.stub(JunoClient.prototype, "acceptCodeOfConduct").returns({
status: HttpStatusCodes.OK,
data: true
} as IJunoResponse<boolean>);
const junoClient = new JunoClient(undefined);
codeOfConductProps = {
junoClient: junoClient,
onAcceptCodeOfConduct: jest.fn()
};
});
afterEach(() => {
sandbox.restore();
});
it("renders", () => {
const wrapper = shallow(<CodeOfConductComponent {...codeOfConductProps} />);
expect(wrapper).toMatchSnapshot();
});
it("onAcceptedCodeOfConductCalled", async () => {
const wrapper = shallow(<CodeOfConductComponent {...codeOfConductProps} />);
wrapper
.find(".genericPaneSubmitBtn")
.first()
.simulate("click");
await Promise.resolve();
expect(codeOfConductProps.onAcceptCodeOfConduct).toBeCalled();
});
});

View File

@@ -1,112 +0,0 @@
import * as React from "react";
import { JunoClient } from "../../../Juno/JunoClient";
import { HttpStatusCodes, CodeOfConductEndpoints } from "../../../Common/Constants";
import * as Logger from "../../../Common/Logger";
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
import { Stack, Text, Checkbox, PrimaryButton, Link } from "office-ui-fabric-react";
export interface CodeOfConductComponentProps {
junoClient: JunoClient;
onAcceptCodeOfConduct: (result: boolean) => void;
}
interface CodeOfConductComponentState {
readCodeOfConduct: boolean;
}
export class CodeOfConductComponent extends React.Component<CodeOfConductComponentProps, CodeOfConductComponentState> {
private descriptionPara1: string;
private descriptionPara2: string;
private descriptionPara3: string;
private link1: { label: string; url: string };
private link2: { label: string; url: string };
constructor(props: CodeOfConductComponentProps) {
super(props);
this.state = {
readCodeOfConduct: false
};
this.descriptionPara1 = "Azure CosmosDB Notebook Gallery - Code of Conduct and Privacy Statement";
this.descriptionPara2 =
"Azure Cosmos DB Notebook Public Gallery contains notebook samples shared by users of Cosmos DB.";
this.descriptionPara3 = "In order to access Azure Cosmos DB Notebook Gallery resources, you must accept the ";
this.link1 = { label: "code of conduct", url: CodeOfConductEndpoints.codeOfConduct };
this.link2 = { label: "privacy statement", url: CodeOfConductEndpoints.privacyStatement };
}
private async acceptCodeOfConduct(): Promise<void> {
try {
const response = await this.props.junoClient.acceptCodeOfConduct();
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
throw new Error(`Received HTTP ${response.status} when accepting code of conduct`);
}
this.props.onAcceptCodeOfConduct(response.data);
} catch (error) {
const message = `Failed to accept code of conduct: ${error}`;
Logger.logError(message, "CodeOfConductComponent/acceptCodeOfConduct");
logConsoleError(message);
}
}
private onChangeCheckbox = (): void => {
this.setState({ readCodeOfConduct: !this.state.readCodeOfConduct });
};
public render(): JSX.Element {
return (
<Stack tokens={{ childrenGap: 20 }}>
<Stack.Item>
<Text style={{ fontWeight: 500, fontSize: "20px" }}>{this.descriptionPara1}</Text>
</Stack.Item>
<Stack.Item>
<Text>{this.descriptionPara2}</Text>
</Stack.Item>
<Stack.Item>
<Text>
{this.descriptionPara3}
<Link href={this.link1.url} target="_blank">
{this.link1.label}
</Link>
{" and "}
<Link href={this.link2.url} target="_blank">
{this.link2.label}
</Link>
</Text>
</Stack.Item>
<Stack.Item>
<Checkbox
styles={{
label: {
margin: 0,
padding: "2 0 2 0"
},
text: {
fontSize: 12
}
}}
label="I have read and accepted the code of conduct and privacy statement"
onChange={this.onChangeCheckbox}
/>
</Stack.Item>
<Stack.Item>
<PrimaryButton
ariaLabel="Continue"
title="Continue"
onClick={async () => await this.acceptCodeOfConduct()}
tabIndex={0}
className="genericPaneSubmitBtn"
text="Continue"
disabled={!this.state.readCodeOfConduct}
/>
</Stack.Item>
</Stack>
);
}
}

View File

@@ -15,17 +15,15 @@ import {
} from "office-ui-fabric-react"; } from "office-ui-fabric-react";
import * as React from "react"; import * as React from "react";
import * as Logger from "../../../Common/Logger"; import * as Logger from "../../../Common/Logger";
import { IGalleryItem, JunoClient, IJunoResponse, IPublicGalleryData } from "../../../Juno/JunoClient"; import { IGalleryItem, JunoClient } from "../../../Juno/JunoClient";
import * as GalleryUtils from "../../../Utils/GalleryUtils"; import * as GalleryUtils from "../../../Utils/GalleryUtils";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; import { NotificationConsoleUtils } from "../../../Utils/NotificationConsoleUtils";
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent"; import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent";
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent"; import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
import "./GalleryViewerComponent.less"; import "./GalleryViewerComponent.less";
import { HttpStatusCodes } from "../../../Common/Constants"; import { HttpStatusCodes } from "../../../Common/Constants";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { CodeOfConductComponent } from "./CodeOfConductComponent";
import { InfoComponent } from "./InfoComponent/InfoComponent";
export interface GalleryViewerComponentProps { export interface GalleryViewerComponentProps {
container?: Explorer; container?: Explorer;
@@ -62,7 +60,6 @@ interface GalleryViewerComponentState {
sortBy: SortBy; sortBy: SortBy;
searchText: string; searchText: string;
dialogProps: DialogProps; dialogProps: DialogProps;
isCodeOfConductAccepted: boolean;
} }
interface GalleryTabInfo { interface GalleryTabInfo {
@@ -89,7 +86,6 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
private publicNotebooks: IGalleryItem[]; private publicNotebooks: IGalleryItem[];
private favoriteNotebooks: IGalleryItem[]; private favoriteNotebooks: IGalleryItem[];
private publishedNotebooks: IGalleryItem[]; private publishedNotebooks: IGalleryItem[];
private isCodeOfConductAccepted: boolean;
private columnCount: number; private columnCount: number;
private rowCount: number; private rowCount: number;
@@ -104,8 +100,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
selectedTab: props.selectedTab, selectedTab: props.selectedTab,
sortBy: props.sortBy, sortBy: props.sortBy,
searchText: props.searchText, searchText: props.searchText,
dialogProps: undefined, dialogProps: undefined
isCodeOfConductAccepted: undefined
}; };
this.sortingOptions = [ this.sortingOptions = [
@@ -139,21 +134,10 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
const tabs: GalleryTabInfo[] = [this.createTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)]; const tabs: GalleryTabInfo[] = [this.createTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)];
if (this.props.container?.isGalleryPublishEnabled()) { if (this.props.container?.isGalleryPublishEnabled()) {
tabs.push( tabs.push(this.createTab(GalleryTab.PublicGallery, this.state.publicNotebooks));
this.createPublicGalleryTab(
GalleryTab.PublicGallery,
this.state.publicNotebooks,
this.state.isCodeOfConductAccepted
)
);
tabs.push(this.createTab(GalleryTab.Favorites, this.state.favoriteNotebooks)); tabs.push(this.createTab(GalleryTab.Favorites, this.state.favoriteNotebooks));
// explicitly checking if isCodeOfConductAccepted is not false, as it is initially undefined.
// Displaying code of conduct component on gallery load should not be the default behavior.
if (this.state.isCodeOfConductAccepted !== false) {
tabs.push(this.createTab(GalleryTab.Published, this.state.publishedNotebooks)); tabs.push(this.createTab(GalleryTab.Published, this.state.publishedNotebooks));
} }
}
const pivotProps: IPivotProps = { const pivotProps: IPivotProps = {
onLinkClick: this.onPivotChange, onLinkClick: this.onPivotChange,
@@ -183,17 +167,6 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
); );
} }
private createPublicGalleryTab(
tab: GalleryTab,
data: IGalleryItem[],
acceptedCodeOfConduct: boolean
): GalleryTabInfo {
return {
tab,
content: this.createPublicGalleryTabContent(data, acceptedCodeOfConduct)
};
}
private createTab(tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo { private createTab(tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo {
return { return {
tab, tab,
@@ -201,19 +174,6 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
}; };
} }
private createPublicGalleryTabContent(data: IGalleryItem[], acceptedCodeOfConduct: boolean): JSX.Element {
return acceptedCodeOfConduct === false ? (
<CodeOfConductComponent
junoClient={this.props.junoClient}
onAcceptCodeOfConduct={(result: boolean) => {
this.setState({ isCodeOfConductAccepted: result });
}}
/>
) : (
this.createTabContent(data)
);
}
private createTabContent(data: IGalleryItem[]): JSX.Element { private createTabContent(data: IGalleryItem[]): JSX.Element {
return ( return (
<Stack tokens={{ childrenGap: 10 }}> <Stack tokens={{ childrenGap: 10 }}>
@@ -227,12 +187,8 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
<Stack.Item styles={{ root: { minWidth: 200 } }}> <Stack.Item styles={{ root: { minWidth: 200 } }}>
<Dropdown options={this.sortingOptions} selectedKey={this.state.sortBy} onChange={this.onDropdownChange} /> <Dropdown options={this.sortingOptions} selectedKey={this.state.sortBy} onChange={this.onDropdownChange} />
</Stack.Item> </Stack.Item>
{this.props.container?.isGalleryPublishEnabled() && (
<Stack.Item>
<InfoComponent />
</Stack.Item>
)}
</Stack> </Stack>
{data && this.createCardsTabContent(data)} {data && this.createCardsTabContent(data)}
</Stack> </Stack>
); );
@@ -298,19 +254,12 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
private async loadPublicNotebooks(searchText: string, sortBy: SortBy, offline: boolean): Promise<void> { private async loadPublicNotebooks(searchText: string, sortBy: SortBy, offline: boolean): Promise<void> {
if (!offline) { if (!offline) {
try { try {
let response: IJunoResponse<IPublicGalleryData> | IJunoResponse<IGalleryItem[]>; const response = await this.props.junoClient.getPublicNotebooks();
if (this.props.container.isCodeOfConductEnabled()) {
response = await this.props.junoClient.fetchPublicNotebooks();
this.isCodeOfConductAccepted = response.data?.metadata.acceptedCodeOfConduct;
this.publicNotebooks = response.data?.notebooksData;
} else {
response = await this.props.junoClient.getPublicNotebooks();
this.publicNotebooks = response.data;
}
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) { if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
throw new Error(`Received HTTP ${response.status} when loading public notebooks`); throw new Error(`Received HTTP ${response.status} when loading public notebooks`);
} }
this.publicNotebooks = response.data;
} catch (error) { } catch (error) {
const message = `Failed to load public notebooks: ${error}`; const message = `Failed to load public notebooks: ${error}`;
Logger.logError(message, "GalleryViewerComponent/loadPublicNotebooks"); Logger.logError(message, "GalleryViewerComponent/loadPublicNotebooks");
@@ -319,8 +268,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
} }
this.setState({ this.setState({
publicNotebooks: this.publicNotebooks && [...this.sort(sortBy, this.search(searchText, this.publicNotebooks))], publicNotebooks: this.publicNotebooks && [...this.sort(sortBy, this.search(searchText, this.publicNotebooks))]
isCodeOfConductAccepted: this.isCodeOfConductAccepted
}); });
} }
@@ -385,11 +333,12 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
private isGalleryItemPresent(searchText: string, item: IGalleryItem): boolean { private isGalleryItemPresent(searchText: string, item: IGalleryItem): boolean {
const toSearch = searchText.trim().toUpperCase(); const toSearch = searchText.trim().toUpperCase();
const searchData: string[] = [item.author.toUpperCase(), item.description.toUpperCase(), item.name.toUpperCase()]; const searchData: string[] = [
item.author.toUpperCase(),
if (item.tags) { item.description.toUpperCase(),
searchData.push(...item.tags.map(tag => tag.toUpperCase())); item.name.toUpperCase(),
} ...item.tags?.map(tag => tag.toUpperCase())
];
for (const data of searchData) { for (const data of searchData) {
if (data?.indexOf(toSearch) !== -1) { if (data?.indexOf(toSearch) !== -1) {

View File

@@ -1,26 +0,0 @@
@import "../../../../../less/Common/Constants.less";
.infoPanel, .infoPanelMain {
display: flex;
align-items: center;
}
.infoPanel {
padding-left: 5px;
padding-right: 5px;
}
.infoLabel, .infoLabelMain {
padding-left: 5px
}
.infoLabel {
font-weight: 400
}
.infoIconMain {
color: @AccentMedium
}
.infoIconMain:hover {
color: @BaseMedium
}

View File

@@ -1,10 +0,0 @@
import { shallow } from "enzyme";
import React from "react";
import { InfoComponent } from "./InfoComponent";
describe("InfoComponent", () => {
it("renders", () => {
const wrapper = shallow(<InfoComponent />);
expect(wrapper).toMatchSnapshot();
});
});

View File

@@ -1,42 +0,0 @@
import * as React from "react";
import { Icon, Label, Stack, HoverCard, HoverCardType, Link } from "office-ui-fabric-react";
import { CodeOfConductEndpoints } from "../../../../Common/Constants";
import "./InfoComponent.less";
export class InfoComponent extends React.Component {
private getInfoPanel = (iconName: string, labelText: string, url: string): JSX.Element => {
return (
<Link href={url} target="_blank">
<div className="infoPanel">
<Icon iconName={iconName} styles={{ root: { verticalAlign: "middle" } }} />
<Label className="infoLabel">{labelText}</Label>
</div>
</Link>
);
};
private onHover = (): JSX.Element => {
return (
<Stack tokens={{ childrenGap: 5, padding: 5 }}>
<Stack.Item>{this.getInfoPanel("Script", "Code of Conduct", CodeOfConductEndpoints.codeOfConduct)}</Stack.Item>
<Stack.Item>
{this.getInfoPanel("RedEye", "Privacy Statement", CodeOfConductEndpoints.privacyStatement)}
</Stack.Item>
<Stack.Item>
{this.getInfoPanel("KnowledgeArticle", "Microsoft Terms of Use", CodeOfConductEndpoints.termsOfUse)}
</Stack.Item>
</Stack>
);
};
public render(): JSX.Element {
return (
<HoverCard plainCardProps={{ onRenderPlainCard: this.onHover }} instantOpenOnClick type={HoverCardType.plain}>
<div className="infoPanelMain">
<Icon className="infoIconMain" iconName="Help" styles={{ root: { verticalAlign: "middle" } }} />
<Label className="infoLabelMain">Help</Label>
</div>
</HoverCard>
);
}
}

View File

@@ -1,34 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`InfoComponent renders 1`] = `
<StyledHoverCardBase
instantOpenOnClick={true}
plainCardProps={
Object {
"onRenderPlainCard": [Function],
}
}
type="PlainCard"
>
<div
className="infoPanelMain"
>
<Memo(StyledIconBase)
className="infoIconMain"
iconName="Help"
styles={
Object {
"root": Object {
"verticalAlign": "middle",
},
}
}
/>
<StyledLabelBase
className="infoLabelMain"
>
Help
</StyledLabelBase>
</div>
</StyledHoverCardBase>
`;

View File

@@ -1,75 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CodeOfConductComponent renders 1`] = `
<Stack
tokens={
Object {
"childrenGap": 20,
}
}
>
<StackItem>
<Text
style={
Object {
"fontSize": "20px",
"fontWeight": 500,
}
}
>
Azure CosmosDB Notebook Gallery - Code of Conduct and Privacy Statement
</Text>
</StackItem>
<StackItem>
<Text>
Azure Cosmos DB Notebook Public Gallery contains notebook samples shared by users of Cosmos DB.
</Text>
</StackItem>
<StackItem>
<Text>
In order to access Azure Cosmos DB Notebook Gallery resources, you must accept the
<StyledLinkBase
href="https://aka.ms/cosmos-code-of-conduct"
target="_blank"
>
code of conduct
</StyledLinkBase>
and
<StyledLinkBase
href="https://aka.ms/ms-privacy-policy"
target="_blank"
>
privacy statement
</StyledLinkBase>
</Text>
</StackItem>
<StackItem>
<StyledCheckboxBase
label="I have read and accepted the code of conduct and privacy statement"
onChange={[Function]}
styles={
Object {
"label": Object {
"margin": 0,
"padding": "2 0 2 0",
},
"text": Object {
"fontSize": 12,
},
}
}
/>
</StackItem>
<StackItem>
<CustomizedPrimaryButton
ariaLabel="Continue"
className="genericPaneSubmitBtn"
disabled={true}
onClick={[Function]}
tabIndex={0}
text="Continue"
title="Continue"
/>
</StackItem>
</Stack>
`;

View File

@@ -17,8 +17,7 @@ describe("NotebookMetadataComponent", () => {
isSample: false, isSample: false,
downloads: 0, downloads: 0,
favorites: 0, favorites: 0,
views: 0, views: 0
newCellId: undefined
}, },
isFavorite: false, isFavorite: false,
downloadButtonText: "Download", downloadButtonText: "Download",
@@ -46,8 +45,7 @@ describe("NotebookMetadataComponent", () => {
isSample: false, isSample: false,
downloads: 0, downloads: 0,
favorites: 0, favorites: 0,
views: 0, views: 0
newCellId: undefined
}, },
isFavorite: true, isFavorite: true,
downloadButtonText: "Download", downloadButtonText: "Download",

View File

@@ -10,7 +10,7 @@ import * as Logger from "../../../Common/Logger";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { IGalleryItem, JunoClient } from "../../../Juno/JunoClient"; import { IGalleryItem, JunoClient } from "../../../Juno/JunoClient";
import * as GalleryUtils from "../../../Utils/GalleryUtils"; import * as GalleryUtils from "../../../Utils/GalleryUtils";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; import { NotificationConsoleUtils } from "../../../Utils/NotificationConsoleUtils";
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2"; import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper"; import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
@@ -19,8 +19,6 @@ import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComp
import { NotebookMetadataComponent } from "./NotebookMetadataComponent"; import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
import "./NotebookViewerComponent.less"; import "./NotebookViewerComponent.less";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { NotebookV4 } from "@nteract/commutable/lib/v4";
import { SessionStorageUtility } from "../../../Shared/StorageUtility";
export interface NotebookViewerComponentProps { export interface NotebookViewerComponentProps {
container?: Explorer; container?: Explorer;
@@ -87,17 +85,16 @@ export class NotebookViewerComponent extends React.Component<
} }
const notebook: Notebook = await response.json(); const notebook: Notebook = await response.json();
this.removeNotebookViewerLink(notebook, this.props.galleryItem?.newCellId);
this.notebookComponentBootstrapper.setContent("json", notebook); this.notebookComponentBootstrapper.setContent("json", notebook);
this.setState({ content: notebook, showProgressBar: false }); this.setState({ content: notebook, showProgressBar: false });
if (this.props.galleryItem && !SessionStorageUtility.getEntry(this.props.galleryItem.id)) { if (this.props.galleryItem) {
const response = await this.props.junoClient.increaseNotebookViews(this.props.galleryItem.id); const response = await this.props.junoClient.increaseNotebookViews(this.props.galleryItem.id);
if (!response.data) { if (!response.data) {
throw new Error(`Received HTTP ${response.status} while increasing notebook views`); throw new Error(`Received HTTP ${response.status} while increasing notebook views`);
} }
this.setState({ galleryItem: response.data }); this.setState({ galleryItem: response.data });
SessionStorageUtility.setEntry(this.props.galleryItem?.id, "true");
} }
} catch (error) { } catch (error) {
this.setState({ showProgressBar: false }); this.setState({ showProgressBar: false });
@@ -107,21 +104,10 @@ export class NotebookViewerComponent extends React.Component<
} }
} }
private removeNotebookViewerLink = (notebook: Notebook, newCellId: string): void => {
if (!newCellId) {
return;
}
const notebookV4 = notebook as NotebookV4;
if (notebookV4 && notebookV4.cells[0].source[0].search(newCellId)) {
delete notebookV4.cells[0];
notebook = notebookV4;
}
};
public render(): JSX.Element { public render(): JSX.Element {
return ( return (
<div className="notebookViewerContainer"> <div className="notebookViewerContainer">
{this.props.backNavigationText !== undefined ? ( {this.props.backNavigationText ? (
<Link onClick={this.props.onBackClick}> <Link onClick={this.props.onBackClick}>
<Icon iconName="Back" /> {this.props.backNavigationText} <Icon iconName="Back" /> {this.props.backNavigationText}
</Link> </Link>

View File

@@ -27,10 +27,9 @@ import { TextField, ITextFieldProps, ITextField } from "office-ui-fabric-react/l
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import SaveQueryBannerIcon from "../../../../images/save_query_banner.png"; import SaveQueryBannerIcon from "../../../../images/save_query_banner.png";
import { QueriesClient } from "../../../Common/QueriesClient";
export interface QueriesGridComponentProps { export interface QueriesGridComponentProps {
queriesClient: QueriesClient; queriesClient: ViewModels.QueriesClient;
onQuerySelect: (query: DataModels.Query) => void; onQuerySelect: (query: DataModels.Query) => void;
containerVisible: boolean; containerVisible: boolean;
saveQueryEnabled: boolean; saveQueryEnabled: boolean;

View File

@@ -0,0 +1,12 @@
/*!---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*----------------------------------------------------------*/
import IToolbarDisplayable from "./IToolbarDisplayable";
interface IToolbarAction extends IToolbarDisplayable {
type: "action";
action: () => void;
}
export default IToolbarAction;

View File

@@ -0,0 +1,18 @@
/*!---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*----------------------------------------------------------*/
interface IToolbarDisplayable {
id: string;
title: ko.Subscribable<string>;
displayName: ko.Subscribable<string>;
enabled: ko.Subscribable<boolean>;
visible: ko.Observable<boolean>;
focused: ko.Observable<boolean>;
icon: string;
mouseDown: (data: any, event: MouseEvent) => any;
keyUp: (data: any, event: KeyboardEvent) => any;
keyDown: (data: any, event: KeyboardEvent) => any;
}
export default IToolbarDisplayable;

View File

@@ -0,0 +1,56 @@
/*!---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*----------------------------------------------------------*/
import IToolbarDisplayable from "./IToolbarDisplayable";
interface IToolbarDropDown extends IToolbarDisplayable {
type: "dropdown";
subgroup: IActionConfigItem[];
expanded: ko.Observable<boolean>;
open: () => void;
}
export interface IDropdown {
type: "dropdown";
title: string;
displayName: string;
id: string;
enabled: ko.Observable<boolean>;
visible?: ko.Observable<boolean>;
icon?: string;
subgroup?: IActionConfigItem[];
}
export interface ISeperator {
type: "separator";
visible?: ko.Observable<boolean>;
}
export interface IToggle {
type: "toggle";
title: string;
displayName: string;
checkedTitle: string;
checkedDisplayName: string;
id: string;
checked: ko.Observable<boolean>;
enabled: ko.Observable<boolean>;
visible?: ko.Observable<boolean>;
icon?: string;
}
export interface IAction {
type: "action";
title: string;
displayName: string;
id: string;
action: () => any;
enabled: ko.Subscribable<boolean>;
visible?: ko.Observable<boolean>;
icon?: string;
}
export type IActionConfigItem = ISeperator | IAction | IToggle | IDropdown;
export default IToolbarDropDown;

View File

@@ -0,0 +1,12 @@
/*!---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*----------------------------------------------------------*/
import IToolbarAction from "./IToolbarAction";
import IToolbarToggle from "./IToolbarToggle";
import IToolbarSeperator from "./IToolbarSeperator";
import IToolbarDropDown from "./IToolbarDropDown";
type IToolbarItem = IToolbarAction | IToolbarToggle | IToolbarSeperator | IToolbarDropDown;
export default IToolbarItem;

View File

@@ -0,0 +1,10 @@
/*!---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*----------------------------------------------------------*/
interface IToolbarSeperator {
type: "separator";
visible: ko.Observable<boolean>;
}
export default IToolbarSeperator;

View File

@@ -0,0 +1,12 @@
/*!---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*----------------------------------------------------------*/
import IToolbarDisplayable from "./IToolbarDisplayable";
interface IToolbarToggle extends IToolbarDisplayable {
type: "toggle";
checked: ko.Observable<boolean>;
toggle: () => void;
}
export default IToolbarToggle;

View File

@@ -0,0 +1,58 @@
/*!---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*----------------------------------------------------------*/
var keyCodes = {
RightClick: 3,
Enter: 13,
Esc: 27,
Tab: 9,
LeftArrow: 37,
UpArrow: 38,
RightArrow: 39,
DownArrow: 40,
Delete: 46,
A: 65,
B: 66,
C: 67,
D: 68,
E: 69,
F: 70,
G: 71,
H: 72,
I: 73,
J: 74,
K: 75,
L: 76,
M: 77,
N: 78,
O: 79,
P: 80,
Q: 81,
R: 82,
S: 83,
T: 84,
U: 85,
V: 86,
W: 87,
X: 88,
Y: 89,
Z: 90,
Period: 190,
DecimalPoint: 110,
F1: 112,
F2: 113,
F3: 114,
F4: 115,
F5: 116,
F6: 117,
F7: 118,
F8: 119,
F9: 120,
F10: 121,
F11: 122,
F12: 123,
Dash: 189
};
export default keyCodes;

View File

@@ -0,0 +1,145 @@
/*!---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*----------------------------------------------------------*/
import { IDropdown } from "./IToolbarDropDown";
import { IActionConfigItem } from "./IToolbarDropDown";
import IToolbarItem from "./IToolbarItem";
import * as ko from "knockout";
import ToolbarDropDown from "./ToolbarDropDown";
import ToolbarAction from "./ToolbarAction";
import ToolbarToggle from "./ToolbarToggle";
import template from "./toolbar.html";
export default class Toolbar {
private _toolbarWidth = ko.observable<number>();
private _actionConfigs: IActionConfigItem[];
private _afterExecute: (id: string) => void;
private _hasFocus: boolean = false;
private _focusedSubscription: ko.Subscription;
constructor(actionItems: IActionConfigItem[], afterExecute?: (id: string) => void) {
this._actionConfigs = actionItems;
this._afterExecute = afterExecute;
this.toolbarItems.subscribe(this._focusFirstEnabledItem);
$(window).resize(() => {
this._toolbarWidth($(".toolbar").width());
});
setTimeout(() => {
this._toolbarWidth($(".toolbar").width());
}, 500);
}
public toolbarItems: ko.PureComputed<IToolbarItem[]> = ko.pureComputed(() => {
var remainingToolbarSpace = this._toolbarWidth();
var toolbarItems: IToolbarItem[] = [];
var moreItem: IDropdown = {
type: "dropdown",
title: "More",
displayName: "More",
id: "more-actions-toggle",
enabled: ko.observable(true),
visible: ko.observable(true),
icon: "images/ASX_More.svg",
subgroup: []
};
var showHasMoreItem = false;
var addSeparator = false;
this._actionConfigs.forEach(actionConfig => {
if (actionConfig.type === "separator") {
addSeparator = true;
} else if (remainingToolbarSpace / 60 > 2) {
if (addSeparator) {
addSeparator = false;
toolbarItems.push(Toolbar._createToolbarItemFromConfig({ type: "separator" }));
remainingToolbarSpace -= 10;
}
toolbarItems.push(Toolbar._createToolbarItemFromConfig(actionConfig));
remainingToolbarSpace -= 60;
} else {
showHasMoreItem = true;
if (addSeparator) {
addSeparator = false;
moreItem.subgroup.push({
type: "separator"
});
}
if (!!actionConfig) {
moreItem.subgroup.push(actionConfig);
}
}
});
if (showHasMoreItem) {
toolbarItems.push(
Toolbar._createToolbarItemFromConfig({ type: "separator" }),
Toolbar._createToolbarItemFromConfig(moreItem)
);
}
return toolbarItems;
});
public focus() {
this._hasFocus = true;
this._focusFirstEnabledItem(this.toolbarItems());
}
private _focusFirstEnabledItem = (items: IToolbarItem[]) => {
if (!!this._focusedSubscription) {
// no memory leaks! :D
this._focusedSubscription.dispose();
}
if (this._hasFocus) {
for (var i = 0; i < items.length; i++) {
if (items[i].type !== "separator" && (<any>items[i]).enabled()) {
(<any>items[i]).focused(true);
this._focusedSubscription = (<any>items[i]).focused.subscribe((newValue: any) => {
if (!newValue) {
this._hasFocus = false;
this._focusedSubscription.dispose();
}
});
break;
}
}
}
};
private static _createToolbarItemFromConfig(
configItem: IActionConfigItem,
afterExecute?: (id: string) => void
): IToolbarItem {
switch (configItem.type) {
case "dropdown":
return new ToolbarDropDown(configItem, afterExecute);
case "action":
return new ToolbarAction(configItem, afterExecute);
case "toggle":
return new ToolbarToggle(configItem, afterExecute);
case "separator":
return {
type: "separator",
visible: ko.observable(true)
};
}
}
}
/**
* Helper class for ko component registration
*/
export class ToolbarComponent {
constructor() {
return {
viewModel: Toolbar,
template
};
}
}

View File

@@ -0,0 +1,86 @@
/*!---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*----------------------------------------------------------*/
import * as ko from "knockout";
import { IAction } from "./IToolbarDropDown";
import IToolbarAction from "./IToolbarAction";
import KeyCodes from "./KeyCodes";
import Utilities from "./Utilities";
export default class ToolbarAction implements IToolbarAction {
public type: "action" = "action";
public id: string;
public icon: string;
public title: ko.Observable<string>;
public displayName: ko.Observable<string>;
public enabled: ko.Subscribable<boolean>;
public visible: ko.Observable<boolean>;
public focused: ko.Observable<boolean>;
public action: () => void;
private _afterExecute: (id: string) => void;
constructor(actionItem: IAction, afterExecute?: (id: string) => void) {
this.action = actionItem.action;
this.title = ko.observable(actionItem.title);
this.displayName = ko.observable(actionItem.displayName);
this.id = actionItem.id;
this.enabled = actionItem.enabled;
this.visible = actionItem.visible ? actionItem.visible : ko.observable(true);
this.focused = ko.observable(false);
this.icon = actionItem.icon;
this._afterExecute = afterExecute;
}
private _executeAction = () => {
this.action();
if (!!this._afterExecute) {
this._afterExecute(this.id);
}
};
public mouseDown = (data: any, event: MouseEvent): boolean => {
this._executeAction();
return false;
};
public keyUp = (data: any, event: KeyboardEvent): boolean => {
var handled: boolean = false;
handled = Utilities.onEnter(event, ($sourceElement: JQuery) => {
this._executeAction();
if ($sourceElement.hasClass("active")) {
$sourceElement.removeClass("active");
}
return true;
});
return !handled;
};
public keyDown = (data: any, event: KeyboardEvent): boolean => {
var handled: boolean = false;
handled = Utilities.onEnter(event, ($sourceElement: JQuery) => {
if ($sourceElement.hasClass("active")) {
$sourceElement.removeClass("active");
}
return true;
});
if (!handled) {
// Reset color if [shift-] tabbing, 'up/down arrowing', or 'esc'-aping away from button while holding down 'enter'
Utilities.onKeys(
event,
[KeyCodes.Tab, KeyCodes.UpArrow, KeyCodes.DownArrow, KeyCodes.Esc],
($sourceElement: JQuery) => {
if ($sourceElement.hasClass("active")) {
$sourceElement.removeClass("active");
}
}
);
}
return !handled;
};
}

View File

@@ -0,0 +1,167 @@
/*!---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*----------------------------------------------------------*/
import * as ko from "knockout";
import { IDropdown } from "./IToolbarDropDown";
import { IActionConfigItem } from "./IToolbarDropDown";
import IToolbarDropDown from "./IToolbarDropDown";
import KeyCodes from "./KeyCodes";
import Utilities from "./Utilities";
interface IMenuItem {
id?: string;
type: "normal" | "separator" | "submenu";
label?: string;
enabled?: boolean;
visible?: boolean;
submenu?: IMenuItem[];
}
export default class ToolbarDropDown implements IToolbarDropDown {
public type: "dropdown" = "dropdown";
public title: ko.Observable<string>;
public displayName: ko.Observable<string>;
public id: string;
public enabled: ko.Observable<boolean>;
public visible: ko.Observable<boolean>;
public focused: ko.Observable<boolean>;
public icon: string;
public subgroup: IActionConfigItem[] = [];
public expanded: ko.Observable<boolean> = ko.observable(false);
private _afterExecute: (id: string) => void;
constructor(dropdown: IDropdown, afterExecute?: (id: string) => void) {
this.subgroup = dropdown.subgroup;
this.title = ko.observable(dropdown.title);
this.displayName = ko.observable(dropdown.displayName);
this.id = dropdown.id;
this.enabled = dropdown.enabled;
this.visible = dropdown.visible ? dropdown.visible : ko.observable(true);
this.focused = ko.observable(false);
this.icon = dropdown.icon;
this._afterExecute = afterExecute;
}
private static _convertToMenuItem = (
actionConfigs: IActionConfigItem[],
actionMap: { [id: string]: () => void } = {}
): { menuItems: IMenuItem[]; actionMap: { [id: string]: () => void } } => {
var returnValue = {
menuItems: actionConfigs.map<IMenuItem>((actionConfig: IActionConfigItem, index, array) => {
var menuItem: IMenuItem;
switch (actionConfig.type) {
case "action":
menuItem = <IMenuItem>{
id: actionConfig.id,
type: "normal",
label: actionConfig.displayName,
enabled: actionConfig.enabled(),
visible: actionConfig.visible ? actionConfig.visible() : true
};
actionMap[actionConfig.id] = actionConfig.action;
break;
case "dropdown":
menuItem = <IMenuItem>{
id: actionConfig.id,
type: "submenu",
label: actionConfig.displayName,
enabled: actionConfig.enabled(),
visible: actionConfig.visible ? actionConfig.visible() : true,
submenu: ToolbarDropDown._convertToMenuItem(actionConfig.subgroup, actionMap).menuItems
};
break;
case "toggle":
menuItem = <IMenuItem>{
id: actionConfig.id,
type: "normal",
label: actionConfig.checked() ? actionConfig.checkedDisplayName : actionConfig.displayName,
enabled: actionConfig.enabled(),
visible: actionConfig.visible ? actionConfig.visible() : true
};
actionMap[actionConfig.id] = () => {
actionConfig.checked(!actionConfig.checked());
};
break;
case "separator":
menuItem = <IMenuItem>{
type: "separator",
visible: true
};
break;
}
return menuItem;
}),
actionMap: actionMap
};
return returnValue;
};
public open = () => {
if (!!(<any>window).host) {
var convertedMenuItem = ToolbarDropDown._convertToMenuItem(this.subgroup);
(<any>window).host
.executeProviderOperation("MenuManager.showMenu", {
iFrameStack: [`#${window.frameElement.id}`],
anchor: `#${this.id}`,
menuItems: convertedMenuItem.menuItems
})
.then((id?: string) => {
if (!!id && !!convertedMenuItem.actionMap[id]) {
convertedMenuItem.actionMap[id]();
}
});
if (!!this._afterExecute) {
this._afterExecute(this.id);
}
}
};
public mouseDown = (data: any, event: MouseEvent): boolean => {
this.open();
return false;
};
public keyUp = (data: any, event: KeyboardEvent): boolean => {
var handled: boolean = false;
handled = Utilities.onEnter(event, ($sourceElement: JQuery) => {
this.open();
if ($sourceElement.hasClass("active")) {
$sourceElement.removeClass("active");
}
return true;
});
return !handled;
};
public keyDown = (data: any, event: KeyboardEvent): boolean => {
var handled: boolean = false;
handled = Utilities.onEnter(event, ($sourceElement: JQuery) => {
if ($sourceElement.hasClass("active")) {
$sourceElement.removeClass("active");
}
return true;
});
if (!handled) {
// Reset color if [shift-] tabbing, 'up/down arrowing', or 'esc'-aping away from button while holding down 'enter'
Utilities.onKeys(
event,
[KeyCodes.Tab, KeyCodes.UpArrow, KeyCodes.DownArrow, KeyCodes.Esc],
($sourceElement: JQuery) => {
if ($sourceElement.hasClass("active")) {
$sourceElement.removeClass("active");
}
}
);
}
return !handled;
};
}

View File

@@ -0,0 +1,109 @@
/*!---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*----------------------------------------------------------*/
import * as ko from "knockout";
import { IToggle } from "./IToolbarDropDown";
import IToolbarToggle from "./IToolbarToggle";
import KeyCodes from "./KeyCodes";
import Utilities from "./Utilities";
export default class ToolbarToggle implements IToolbarToggle {
public type: "toggle" = "toggle";
public checked: ko.Observable<boolean>;
public id: string;
public enabled: ko.Observable<boolean>;
public visible: ko.Observable<boolean>;
public focused: ko.Observable<boolean>;
public icon: string;
private _title: string;
private _displayName: string;
private _checkedTitle: string;
private _checkedDisplayName: string;
private _afterExecute: (id: string) => void;
constructor(toggleItem: IToggle, afterExecute?: (id: string) => void) {
this._title = toggleItem.title;
this._displayName = toggleItem.displayName;
this.id = toggleItem.id;
this.enabled = toggleItem.enabled;
this.visible = toggleItem.visible ? toggleItem.visible : ko.observable(true);
this.focused = ko.observable(false);
this.icon = toggleItem.icon;
this.checked = toggleItem.checked;
this._checkedTitle = toggleItem.checkedTitle;
this._checkedDisplayName = toggleItem.checkedDisplayName;
this._afterExecute = afterExecute;
}
public title = ko.pureComputed(() => {
if (this.checked()) {
return this._checkedTitle;
} else {
return this._title;
}
});
public displayName = ko.pureComputed(() => {
if (this.checked()) {
return this._checkedDisplayName;
} else {
return this._displayName;
}
});
public toggle = () => {
this.checked(!this.checked());
if (this.checked() && !!this._afterExecute) {
this._afterExecute(this.id);
}
};
public mouseDown = (data: any, event: MouseEvent): boolean => {
this.toggle();
return false;
};
public keyUp = (data: any, event: KeyboardEvent): boolean => {
var handled: boolean = false;
handled = Utilities.onEnter(event, ($sourceElement: JQuery) => {
this.toggle();
if ($sourceElement.hasClass("active")) {
$sourceElement.removeClass("active");
}
return true;
});
return !handled;
};
public keyDown = (data: any, event: KeyboardEvent): boolean => {
var handled: boolean = false;
handled = Utilities.onEnter(event, ($sourceElement: JQuery) => {
if ($sourceElement.hasClass("active")) {
$sourceElement.removeClass("active");
}
return true;
});
if (!handled) {
// Reset color if [shift-] tabbing, 'up/down arrowing', or 'esc'-aping away from button while holding down 'enter'
Utilities.onKeys(
event,
[KeyCodes.Tab, KeyCodes.UpArrow, KeyCodes.DownArrow, KeyCodes.Esc],
($sourceElement: JQuery) => {
if ($sourceElement.hasClass("active")) {
$sourceElement.removeClass("active");
}
}
);
}
return !handled;
};
}

View File

@@ -0,0 +1,166 @@
/*!---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*----------------------------------------------------------*/
import KeyCodes from "./KeyCodes";
export default class Utilities {
/**
* Executes an action on a keyboard event.
* Modifiers: ctrlKey - control/command key, shiftKey - shift key, altKey - alt/option key;
* pass on 'null' to ignore the modifier (default).
*/
public static onKey(
event: any,
eventKeyCode: number,
action: ($sourceElement: JQuery) => void,
metaKey: boolean = null,
shiftKey: boolean = null,
altKey: boolean = null
): boolean {
var source: any = event.target || event.srcElement,
keyCode: number = event.keyCode,
$sourceElement = $(source),
handled: boolean = false;
if (
$sourceElement.length &&
keyCode === eventKeyCode &&
$.isFunction(action) &&
(metaKey === null || metaKey === event.metaKey) &&
(shiftKey === null || shiftKey === event.shiftKey) &&
(altKey === null || altKey === event.altKey)
) {
action($sourceElement);
handled = true;
}
return handled;
}
/**
* Executes an action on the first matched keyboard event.
*/
public static onKeys(
event: any,
eventKeyCodes: number[],
action: ($sourceElement: JQuery) => void,
metaKey: boolean = null,
shiftKey: boolean = null,
altKey: boolean = null
): boolean {
var handled: boolean = false,
keyCount: number,
i: number;
if ($.isArray(eventKeyCodes)) {
keyCount = eventKeyCodes.length;
for (i = 0; i < keyCount; ++i) {
handled = Utilities.onKey(event, eventKeyCodes[i], action, metaKey, shiftKey, altKey);
if (handled) {
break;
}
}
}
return handled;
}
/**
* Executes an action on an 'enter' keyboard event.
*/
public static onEnter(
event: any,
action: ($sourceElement: JQuery) => void,
metaKey: boolean = null,
shiftKey: boolean = null,
altKey: boolean = null
): boolean {
return Utilities.onKey(event, KeyCodes.Enter, action, metaKey, shiftKey, altKey);
}
/**
* Executes an action on a 'tab' keyboard event.
*/
public static onTab(
event: any,
action: ($sourceElement: JQuery) => void,
metaKey: boolean = null,
shiftKey: boolean = null,
altKey: boolean = null
): boolean {
return Utilities.onKey(event, KeyCodes.Tab, action, metaKey, shiftKey, altKey);
}
/**
* Executes an action on an 'Esc' keyboard event.
*/
public static onEsc(
event: any,
action: ($sourceElement: JQuery) => void,
metaKey: boolean = null,
shiftKey: boolean = null,
altKey: boolean = null
): boolean {
return Utilities.onKey(event, KeyCodes.Esc, action, metaKey, shiftKey, altKey);
}
/**
* Executes an action on an 'UpArrow' keyboard event.
*/
public static onUpArrow(
event: any,
action: ($sourceElement: JQuery) => void,
metaKey: boolean = null,
shiftKey: boolean = null,
altKey: boolean = null
): boolean {
return Utilities.onKey(event, KeyCodes.UpArrow, action, metaKey, shiftKey, altKey);
}
/**
* Executes an action on a 'DownArrow' keyboard event.
*/
public static onDownArrow(
event: any,
action: ($sourceElement: JQuery) => void,
metaKey: boolean = null,
shiftKey: boolean = null,
altKey: boolean = null
): boolean {
return Utilities.onKey(event, KeyCodes.DownArrow, action, metaKey, shiftKey, altKey);
}
/**
* Executes an action on a mouse event.
*/
public static onButton(event: any, eventButtonCode: number, action: ($sourceElement: JQuery) => void): boolean {
var source: any = event.currentTarget;
var buttonCode: number = event.button;
var $sourceElement = $(source);
var handled: boolean = false;
if ($sourceElement.length && buttonCode === eventButtonCode && $.isFunction(action)) {
action($sourceElement);
handled = true;
}
return handled;
}
/**
* Executes an action on a 'left' mouse event.
*/
public static onLeftButton(event: any, action: ($sourceElement: JQuery) => void): boolean {
return Utilities.onButton(event, buttonCodes.Left, action);
}
}
var buttonCodes = {
None: -1,
Left: 0,
Middle: 1,
Right: 2
};

View File

@@ -0,0 +1,44 @@
<div class="toolbar">
<!-- ko template: { name: 'toolbarItemTemplate', foreach: toolbarItems } -->
<!-- /ko -->
</div>
<script type="text/html" id="toolbarItemTemplate">
<!-- ko if: type === "action" -->
<div class="toolbar-group" data-bind="visible: visible">
<button class="toolbar-group-button" data-bind="hasFocus: focused, attr: {id: id, title: title, 'aria-label': displayName}, event: { mousedown: mouseDown, keydown: keyDown, keyup: keyUp }, enable: enabled">
<div class="toolbar-group-button-icon">
<div class="toolbar_icon" data-bind="icon: icon"></div>
</div>
<span data-bind="text: displayName"></span>
</button>
</div>
<!-- /ko -->
<!-- ko if: type === "toggle" -->
<div class="toolbar-group" data-bind="visible: visible">
<button class="toolbar-group-button toggle-button" data-bind="hasFocus: focused, attr: {id: id, title: title}, event: { mousedown: mouseDown, keydown: keyDown, keyup: keyUp }, enable: enabled">
<div class="toolbar-group-button-icon" data-bind="css: { 'toggle-checked': checked }">
<div class="toolbar_icon" data-bind="icon: icon"></div>
</div>
<span data-bind="text: displayName"></span>
</button>
</div>
<!-- /ko -->
<!-- ko if: type === "dropdown" -->
<div class="toolbar-group" data-bind="visible: visible">
<div class="dropdown" data-bind="attr: {id: (id + '-dropdown')}">
<button role="menu" class="toolbar-group-button" data-bind="hasFocus: focused, attr: {id: id, title: title, 'aria-label': displayName}, event: { mousedown: mouseDown, keydown: keyDown, keyup: keyUp }, enable: enabled">
<div class="toolbar-group-button-icon">
<div class="toolbar_icon" data-bind="icon: icon"></div>
</div>
<span data-bind="text: displayName"></span>
</button>
</div>
</div>
<!-- /ko -->
<!-- ko if: type === "separator" -->
<div class="toolbar-group vertical-separator" data-bind="visible: visible"></div>
<!-- /ko -->
</script>

View File

@@ -1,14 +1,12 @@
jest.mock("../../Common/DocumentClientUtilityBase");
jest.mock("../../Common/dataAccess/createCollection");
import * as ko from "knockout"; import * as ko from "knockout";
import * as sinon from "sinon"; import * as sinon from "sinon";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import DocumentClientUtilityBase from "../../Common/DocumentClientUtilityBase";
import Q from "q"; import Q from "q";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator"; import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
import * as DocumentClientUtility from "../../Common/DocumentClientUtilityBase"; import { CosmosClient } from "../../Common/CosmosClient";
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient"; import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { updateUserContext } from "../../UserContext";
describe("ContainerSampleGenerator", () => { describe("ContainerSampleGenerator", () => {
const createExplorerStub = (database: ViewModels.Database): Explorer => { const createExplorerStub = (database: ViewModels.Database): Explorer => {
@@ -34,8 +32,8 @@ describe("ContainerSampleGenerator", () => {
databaseId: sampleDatabaseId, databaseId: sampleDatabaseId,
offerThroughput: 400, offerThroughput: 400,
databaseLevelThroughput: false, databaseLevelThroughput: false,
createNewDatabase: true,
collectionId: sampleCollectionId, collectionId: sampleCollectionId,
rupmEnabled: false,
data: [ data: [
{ {
firstname: "Eva", firstname: "Eva",
@@ -64,33 +62,27 @@ describe("ContainerSampleGenerator", () => {
const explorerStub = createExplorerStub(database); const explorerStub = createExplorerStub(database);
explorerStub.isPreferredApiDocumentDB = ko.computed<boolean>(() => true); explorerStub.isPreferredApiDocumentDB = ko.computed<boolean>(() => true);
const fakeDocumentClientUtility = sinon.createStubInstance(DocumentClientUtilityBase);
fakeDocumentClientUtility.getOrCreateDatabaseAndCollection.returns(Q.resolve(collection));
fakeDocumentClientUtility.createDocument.returns(Q.resolve());
explorerStub.documentClientUtility = fakeDocumentClientUtility;
const generator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub); const generator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub);
generator.setData(sampleData); generator.setData(sampleData);
await generator.createSampleContainerAsync(); await generator.createSampleContainerAsync();
expect(DocumentClientUtility.createDocument).toHaveBeenCalled(); expect(fakeDocumentClientUtility.createDocument.called).toBe(true);
}); });
it("should send gremlin queries for Graph API account", async () => { it("should send gremlin queries for Graph API account", async () => {
sinon.stub(GremlinClient.prototype, "initialize").callsFake(() => {}); sinon.stub(GremlinClient.prototype, "initialize").callsFake(() => {});
const executeStub = sinon.stub(GremlinClient.prototype, "execute").returns(Q.resolve()); const executeStub = sinon.stub(GremlinClient.prototype, "execute").returns(Q.resolve());
updateUserContext({ sinon.stub(CosmosClient, "databaseAccount").returns({
databaseAccount: { properties: {}
id: "foo",
name: "foo",
location: "foo",
type: "foo",
kind: "foo",
tags: [],
properties: {
documentEndpoint: "bar",
gremlinEndpoint: "foo",
tableEndpoint: "foo",
cassandraEndpoint: "foo"
}
}
}); });
const sampleCollectionId = "SampleCollection"; const sampleCollectionId = "SampleCollection";
@@ -100,8 +92,8 @@ describe("ContainerSampleGenerator", () => {
databaseId: sampleDatabaseId, databaseId: sampleDatabaseId,
offerThroughput: 400, offerThroughput: 400,
databaseLevelThroughput: false, databaseLevelThroughput: false,
createNewDatabase: true,
collectionId: sampleCollectionId, collectionId: sampleCollectionId,
rupmEnabled: false,
data: [ data: [
"g.addV('person').property(id, '1').property('_partitionKey','pk').property('name', 'Eva').property('age', 44)" "g.addV('person').property(id, '1').property('_partitionKey','pk').property('name', 'Eva').property('age', 44)"
] ]
@@ -117,12 +109,18 @@ describe("ContainerSampleGenerator", () => {
const explorerStub = createExplorerStub(database); const explorerStub = createExplorerStub(database);
explorerStub.isPreferredApiGraph = ko.computed<boolean>(() => true); explorerStub.isPreferredApiGraph = ko.computed<boolean>(() => true);
const fakeDocumentClientUtility = sinon.createStubInstance(DocumentClientUtilityBase);
fakeDocumentClientUtility.getOrCreateDatabaseAndCollection.returns(Q.resolve(collection));
fakeDocumentClientUtility.createDocument.returns(Q.resolve());
explorerStub.documentClientUtility = fakeDocumentClientUtility;
const generator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub); const generator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub);
generator.setData(sampleData); generator.setData(sampleData);
await generator.createSampleContainerAsync(); await generator.createSampleContainerAsync();
expect(DocumentClientUtility.createDocument).toHaveBeenCalled(); expect(fakeDocumentClientUtility.createDocument.called).toBe(false);
expect(executeStub.called).toBe(true); expect(executeStub.called).toBe(true);
}); });

View File

@@ -1,15 +1,14 @@
import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import GraphTab from ".././Tabs/GraphTab"; import GraphTab from ".././Tabs/GraphTab";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { CosmosClient } from "../../Common/CosmosClient";
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient"; import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { createDocument } from "../../Common/DocumentClientUtilityBase";
import { createCollection } from "../../Common/dataAccess/createCollection";
import { userContext } from "../../UserContext";
interface SampleDataFile extends DataModels.CreateCollectionParams { interface SampleDataFile extends DataModels.CreateDatabaseAndCollectionRequest {
data: any[]; data: any[];
} }
@@ -54,11 +53,18 @@ export class ContainerSampleGenerator {
} }
private async createContainerAsync(): Promise<ViewModels.Collection> { private async createContainerAsync(): Promise<ViewModels.Collection> {
const createRequest: DataModels.CreateCollectionParams = { const createRequest: DataModels.CreateDatabaseAndCollectionRequest = {
...this.sampleDataFile ...this.sampleDataFile
}; };
await createCollection(createRequest); const options: any = {};
if (this.container.isPreferredApiMongoDB()) {
options.initialHeaders = options.initialHeaders || {};
options.initialHeaders[Constants.HttpHeaders.supportSpatialLegacyCoordinates] = true;
options.initialHeaders[Constants.HttpHeaders.usePolygonsSmallerThanAHemisphere] = true;
}
await this.container.documentClientUtility.getOrCreateDatabaseAndCollection(createRequest, options);
await this.container.refreshAllDatabases(); await this.container.refreshAllDatabases();
const database = this.container.findDatabaseWithId(this.sampleDataFile.databaseId); const database = this.container.findDatabaseWithId(this.sampleDataFile.databaseId);
if (!database) { if (!database) {
@@ -80,14 +86,14 @@ export class ContainerSampleGenerator {
if (!queries || queries.length < 1) { if (!queries || queries.length < 1) {
return; return;
} }
const account = userContext.databaseAccount; const account = CosmosClient.databaseAccount();
const databaseId = collection.databaseId; const databaseId = collection.databaseId;
const gremlinClient = new GremlinClient(); const gremlinClient = new GremlinClient();
gremlinClient.initialize({ gremlinClient.initialize({
endpoint: `wss://${GraphTab.getGremlinEndpoint(account)}`, endpoint: `wss://${GraphTab.getGremlinEndpoint(account)}`,
databaseId: databaseId, databaseId: databaseId,
collectionId: collection.id(), collectionId: collection.id(),
masterKey: userContext.masterKey || "", masterKey: CosmosClient.masterKey() || "",
maxResultSize: 100 maxResultSize: 100
}); });
@@ -97,7 +103,7 @@ export class ContainerSampleGenerator {
} else { } else {
// For SQL all queries are executed at the same time // For SQL all queries are executed at the same time
this.sampleDataFile.data.forEach(doc => { this.sampleDataFile.data.forEach(doc => {
const subPromise = createDocument(collection, doc); const subPromise = this.container.documentClientUtility.createDocument(collection, doc);
subPromise.catch(reason => NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, reason)); subPromise.catch(reason => NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, reason));
promises.push(subPromise); promises.push(subPromise);
}); });

View File

@@ -1,6 +1,6 @@
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator"; import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import Explorer from "../Explorer"; import Explorer from "../Explorer";

View File

@@ -15,15 +15,13 @@ import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
import Database from "./Tree/Database"; import Database from "./Tree/Database";
import DeleteCollectionConfirmationPane from "./Panes/DeleteCollectionConfirmationPane"; import DeleteCollectionConfirmationPane from "./Panes/DeleteCollectionConfirmationPane";
import DeleteDatabaseConfirmationPane from "./Panes/DeleteDatabaseConfirmationPane"; import DeleteDatabaseConfirmationPane from "./Panes/DeleteDatabaseConfirmationPane";
import { readOffers, refreshCachedResources } from "../Common/DocumentClientUtilityBase"; import DocumentClientUtilityBase from "../Common/DocumentClientUtilityBase";
import { readCollection } from "../Common/dataAccess/readCollection";
import { readDatabases } from "../Common/dataAccess/readDatabases";
import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane"; import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane";
import EnvironmentUtility from "../Common/EnvironmentUtility"; import EnvironmentUtility from "../Common/EnvironmentUtility";
import GraphStylingPane from "./Panes/GraphStylingPane"; import GraphStylingPane from "./Panes/GraphStylingPane";
import hasher from "hasher"; import hasher from "hasher";
import NewVertexPane from "./Panes/NewVertexPane"; import NewVertexPane from "./Panes/NewVertexPane";
import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab"; import NotebookV2Tab from "./Tabs/NotebookV2Tab";
import Q from "q"; import Q from "q";
import ResourceTokenCollection from "./Tree/ResourceTokenCollection"; import ResourceTokenCollection from "./Tree/ResourceTokenCollection";
import TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor"; import TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
@@ -35,10 +33,12 @@ import { ArcadiaWorkspaceItem } from "./Controls/Arcadia/ArcadiaMenuPicker";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer"; import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane"; import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane";
import { CassandraApi } from "../Api/Apis";
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient"; import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter"; import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter";
import { configContext, updateConfigContext } from "../ConfigContext"; import { config } from "../Config";
import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent";
import { CosmosClient } from "../Common/CosmosClient";
import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils"; import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility"; import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
import { DialogComponentAdapter } from "./Controls/DialogReactComponent/DialogComponentAdapter"; import { DialogComponentAdapter } from "./Controls/DialogReactComponent/DialogComponentAdapter";
@@ -52,12 +52,12 @@ import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation";
import { IGalleryItem } from "../Juno/JunoClient"; import { IGalleryItem } from "../Juno/JunoClient";
import { LoadQueryPane } from "./Panes/LoadQueryPane"; import { LoadQueryPane } from "./Panes/LoadQueryPane";
import * as Logger from "../Common/Logger"; import * as Logger from "../Common/Logger";
import { sendMessage, sendCachedDataMessage, handleCachedDataMessage } from "../Common/MessageHandler"; import { MessageHandler } from "../Common/MessageHandler";
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem"; import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";
import { NotebookUtil } from "./Notebook/NotebookUtil"; import { NotebookUtil } from "./Notebook/NotebookUtil";
import { NotebookWorkspaceManager } from "../NotebookWorkspaceManager/NotebookWorkspaceManager"; import { NotebookWorkspaceManager } from "../NotebookWorkspaceManager/NotebookWorkspaceManager";
import { NotificationConsoleComponentAdapter } from "./Menus/NotificationConsole/NotificationConsoleComponentAdapter"; import { NotificationConsoleComponentAdapter } from "./Menus/NotificationConsole/NotificationConsoleComponentAdapter";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils";
import { PlatformType } from "../PlatformType"; import { PlatformType } from "../PlatformType";
import { QueriesClient } from "../Common/QueriesClient"; import { QueriesClient } from "../Common/QueriesClient";
import { QuerySelectPane } from "./Panes/Tables/QuerySelectPane"; import { QuerySelectPane } from "./Panes/Tables/QuerySelectPane";
@@ -82,11 +82,6 @@ import { toRawContentUri, fromContentUri } from "../Utils/GitHubUtils";
import UserDefinedFunction from "./Tree/UserDefinedFunction"; import UserDefinedFunction from "./Tree/UserDefinedFunction";
import StoredProcedure from "./Tree/StoredProcedure"; import StoredProcedure from "./Tree/StoredProcedure";
import Trigger from "./Tree/Trigger"; import Trigger from "./Tree/Trigger";
import { NotificationsClientBase } from "../Common/NotificationsClientBase";
import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
import TabsBase from "./Tabs/TabsBase";
import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandButtonComponent";
import { updateUserContext, userContext } from "../UserContext";
BindingHandlersRegisterer.registerBindingHandlers(); BindingHandlersRegisterer.registerBindingHandlers();
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import // Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
@@ -97,15 +92,6 @@ enum ShareAccessToggleState {
Read Read
} }
interface ExplorerOptions {
notificationsClient: NotificationsClientBase;
isEmulator: boolean;
}
interface AdHocAccessData {
readWriteUrl: string;
readUrl: string;
}
export default class Explorer { export default class Explorer {
public flight: ko.Observable<string> = ko.observable<string>( public flight: ko.Observable<string> = ko.observable<string>(
SharedConstants.CollectionCreation.DefaultAddCollectionDefaultFlight SharedConstants.CollectionCreation.DefaultAddCollectionDefaultFlight
@@ -121,7 +107,7 @@ export default class Explorer {
public hasWriteAccess: ko.Observable<boolean>; public hasWriteAccess: ko.Observable<boolean>;
public collapsedResourceTreeWidth: number = ExplorerMetrics.CollapsedResourceTreeWidth; public collapsedResourceTreeWidth: number = ExplorerMetrics.CollapsedResourceTreeWidth;
public databaseAccount: ko.Observable<DataModels.DatabaseAccount>; public databaseAccount: ko.Observable<ViewModels.DatabaseAccount>;
public collectionCreationDefaults: ViewModels.CollectionCreationDefaults = SharedConstants.CollectionCreationDefaults; public collectionCreationDefaults: ViewModels.CollectionCreationDefaults = SharedConstants.CollectionCreationDefaults;
public subscriptionType: ko.Observable<ViewModels.SubscriptionType>; public subscriptionType: ko.Observable<ViewModels.SubscriptionType>;
public quotaId: ko.Observable<string>; public quotaId: ko.Observable<string>;
@@ -132,7 +118,6 @@ export default class Explorer {
public isPreferredApiGraph: ko.Computed<boolean>; public isPreferredApiGraph: ko.Computed<boolean>;
public isPreferredApiTable: ko.Computed<boolean>; public isPreferredApiTable: ko.Computed<boolean>;
public isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>; public isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>;
public isServerlessEnabled: ko.Computed<boolean>;
public isEmulator: boolean; public isEmulator: boolean;
public isAccountReady: ko.Observable<boolean>; public isAccountReady: ko.Observable<boolean>;
public canSaveQueries: ko.Computed<boolean>; public canSaveQueries: ko.Computed<boolean>;
@@ -141,8 +126,9 @@ export default class Explorer {
public extensionEndpoint: ko.Observable<string>; public extensionEndpoint: ko.Observable<string>;
public armEndpoint: ko.Observable<string>; public armEndpoint: ko.Observable<string>;
public isTryCosmosDBSubscription: ko.Observable<boolean>; public isTryCosmosDBSubscription: ko.Observable<boolean>;
public notificationsClient: NotificationsClientBase; public documentClientUtility: DocumentClientUtilityBase;
public queriesClient: QueriesClient; public notificationsClient: ViewModels.NotificationsClient;
public queriesClient: ViewModels.QueriesClient;
public tableDataClient: TableDataClient; public tableDataClient: TableDataClient;
public splitter: Splitter; public splitter: Splitter;
public parentFrameDataExplorerVersion: ko.Observable<string> = ko.observable<string>(""); public parentFrameDataExplorerVersion: ko.Observable<string> = ko.observable<string>("");
@@ -153,7 +139,7 @@ export default class Explorer {
public isNotificationConsoleExpanded: ko.Observable<boolean>; public isNotificationConsoleExpanded: ko.Observable<boolean>;
// Panes // Panes
public contextPanes: ContextualPaneBase[]; public contextPanes: ViewModels.ContextualPane[];
// Resource Tree // Resource Tree
public databases: ko.ObservableArray<ViewModels.Database>; public databases: ko.ObservableArray<ViewModels.Database>;
@@ -198,29 +184,25 @@ export default class Explorer {
public uploadItemsPane: UploadItemsPane; public uploadItemsPane: UploadItemsPane;
public uploadItemsPaneAdapter: UploadItemsPaneAdapter; public uploadItemsPaneAdapter: UploadItemsPaneAdapter;
public loadQueryPane: LoadQueryPane; public loadQueryPane: LoadQueryPane;
public saveQueryPane: ContextualPaneBase; public saveQueryPane: ViewModels.ContextualPane;
public browseQueriesPane: BrowseQueriesPane; public browseQueriesPane: BrowseQueriesPane;
public uploadFilePane: UploadFilePane; public uploadFilePane: UploadFilePane;
public stringInputPane: StringInputPane; public stringInputPane: StringInputPane;
public setupNotebooksPane: SetupNotebooksPane; public setupNotebooksPane: SetupNotebooksPane;
public gitHubReposPane: ContextualPaneBase; public gitHubReposPane: ViewModels.ContextualPane;
public publishNotebookPaneAdapter: ReactAdapter; public publishNotebookPaneAdapter: ReactAdapter;
public copyNotebookPaneAdapter: ReactAdapter;
// features // features
public isGalleryPublishEnabled: ko.Computed<boolean>; public isGalleryPublishEnabled: ko.Computed<boolean>;
public isCodeOfConductEnabled: ko.Computed<boolean>;
public isLinkInjectionEnabled: ko.Computed<boolean>;
public isGitHubPaneEnabled: ko.Observable<boolean>; public isGitHubPaneEnabled: ko.Observable<boolean>;
public isPublishNotebookPaneEnabled: ko.Observable<boolean>; public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
public isCopyNotebookPaneEnabled: ko.Observable<boolean>;
public isHostedDataExplorerEnabled: ko.Computed<boolean>; public isHostedDataExplorerEnabled: ko.Computed<boolean>;
public isRightPanelV2Enabled: ko.Computed<boolean>; public isRightPanelV2Enabled: ko.Computed<boolean>;
public canExceedMaximumValue: ko.Computed<boolean>; public canExceedMaximumValue: ko.Computed<boolean>;
public hasAutoPilotV2FeatureFlag: ko.Computed<boolean>; public hasAutoPilotV2FeatureFlag: ko.Computed<boolean>;
public shouldShowShareDialogContents: ko.Observable<boolean>; public shouldShowShareDialogContents: ko.Observable<boolean>;
public shareAccessData: ko.Observable<AdHocAccessData>; public shareAccessData: ko.Observable<ViewModels.AdHocAccessData>;
public renewExplorerShareAccess: (explorer: Explorer, token: string) => Q.Promise<void>; public renewExplorerShareAccess: (explorer: Explorer, token: string) => Q.Promise<void>;
public renewTokenError: ko.Observable<string>; public renewTokenError: ko.Observable<string>;
public tokenForRenewal: ko.Observable<string>; public tokenForRenewal: ko.Observable<string>;
@@ -246,7 +228,7 @@ export default class Explorer {
public memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>; public memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>;
public notebookManager?: any; // This is dynamically loaded public notebookManager?: any; // This is dynamically loaded
private _panes: ContextualPaneBase[] = []; private _panes: ViewModels.ContextualPane[] = [];
private _importExplorerConfigComplete: boolean = false; private _importExplorerConfigComplete: boolean = false;
private _isSystemDatabasePredicate: (database: ViewModels.Database) => boolean = database => false; private _isSystemDatabasePredicate: (database: ViewModels.Database) => boolean = database => false;
private _isInitializingNotebooks: boolean; private _isInitializingNotebooks: boolean;
@@ -269,7 +251,7 @@ export default class Explorer {
private static readonly MaxNbDatabasesToAutoExpand = 5; private static readonly MaxNbDatabasesToAutoExpand = 5;
constructor(options: ExplorerOptions) { constructor(options: ViewModels.ExplorerOptions) {
const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, { const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, {
dataExplorerArea: Constants.Areas.ResourceTree dataExplorerArea: Constants.Areas.ResourceTree
}); });
@@ -282,7 +264,7 @@ export default class Explorer {
this.deleteDatabaseText = ko.observable<string>("Delete Database"); this.deleteDatabaseText = ko.observable<string>("Delete Database");
this.refreshTreeTitle = ko.observable<string>("Refresh collections"); this.refreshTreeTitle = ko.observable<string>("Refresh collections");
this.databaseAccount = ko.observable<DataModels.DatabaseAccount>(); this.databaseAccount = ko.observable<ViewModels.DatabaseAccount>();
this.subscriptionType = ko.observable<ViewModels.SubscriptionType>( this.subscriptionType = ko.observable<ViewModels.SubscriptionType>(
SharedConstants.CollectionCreation.DefaultSubscriptionType SharedConstants.CollectionCreation.DefaultSubscriptionType
); );
@@ -375,6 +357,7 @@ export default class Explorer {
} }
}); });
this.memoryUsageInfo = ko.observable<DataModels.MemoryUsageInfo>(); this.memoryUsageInfo = ko.observable<DataModels.MemoryUsageInfo>();
this.documentClientUtility = options.documentClientUtility;
this.notificationsClient = options.notificationsClient; this.notificationsClient = options.notificationsClient;
this.isEmulator = options.isEmulator; this.isEmulator = options.isEmulator;
@@ -391,7 +374,7 @@ export default class Explorer {
this.resourceTokenPartitionKey = ko.observable<string>(); this.resourceTokenPartitionKey = ko.observable<string>();
this.isAuthWithResourceToken = ko.observable<boolean>(false); this.isAuthWithResourceToken = ko.observable<boolean>(false);
this.shareAccessData = ko.observable<AdHocAccessData>({ this.shareAccessData = ko.observable<ViewModels.AdHocAccessData>({
readWriteUrl: undefined, readWriteUrl: undefined,
readUrl: undefined readUrl: undefined
}); });
@@ -414,15 +397,8 @@ export default class Explorer {
this.isGalleryPublishEnabled = ko.computed<boolean>(() => this.isGalleryPublishEnabled = ko.computed<boolean>(() =>
this.isFeatureEnabled(Constants.Features.enableGalleryPublish) this.isFeatureEnabled(Constants.Features.enableGalleryPublish)
); );
this.isCodeOfConductEnabled = ko.computed<boolean>(() =>
this.isFeatureEnabled(Constants.Features.enableCodeOfConduct)
);
this.isLinkInjectionEnabled = ko.computed<boolean>(() =>
this.isFeatureEnabled(Constants.Features.enableLinkInjection)
);
this.isGitHubPaneEnabled = ko.observable<boolean>(false); this.isGitHubPaneEnabled = ko.observable<boolean>(false);
this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false); this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false);
this.isCopyNotebookPaneEnabled = ko.observable<boolean>(false);
this.canExceedMaximumValue = ko.computed<boolean>(() => this.canExceedMaximumValue = ko.computed<boolean>(() =>
this.isFeatureEnabled(Constants.Features.canExceedMaximumValue) this.isFeatureEnabled(Constants.Features.canExceedMaximumValue)
@@ -483,14 +459,8 @@ export default class Explorer {
}); });
this.notificationConsoleData = ko.observableArray<ConsoleData>([]); this.notificationConsoleData = ko.observableArray<ConsoleData>([]);
this.defaultExperience = ko.observable<string>(); this.defaultExperience = ko.observable<string>();
this.databaseAccount.subscribe(databaseAccount => { this.databaseAccount.subscribe((databaseAccount: ViewModels.DatabaseAccount) => {
const defaultExperience: string = DefaultExperienceUtility.getDefaultExperienceFromDatabaseAccount( this.defaultExperience(DefaultExperienceUtility.getDefaultExperienceFromDatabaseAccount(databaseAccount));
databaseAccount
);
this.defaultExperience(defaultExperience);
updateUserContext({
defaultExperience: DefaultExperienceUtility.mapDefaultExperienceStringToEnum(defaultExperience)
});
}); });
this.isPreferredApiDocumentDB = ko.computed(() => { this.isPreferredApiDocumentDB = ko.computed(() => {
@@ -539,14 +509,6 @@ export default class Explorer {
return false; return false;
}); });
this.isServerlessEnabled = ko.computed(
() =>
this.databaseAccount &&
this.databaseAccount()?.properties?.capabilities?.find(
item => item.name === Constants.CapabilityNames.EnableServerless
) !== undefined
);
this.isPreferredApiMongoDB = ko.computed(() => { this.isPreferredApiMongoDB = ko.computed(() => {
const defaultExperience = (this.defaultExperience && this.defaultExperience()) || ""; const defaultExperience = (this.defaultExperience && this.defaultExperience()) || "";
if (defaultExperience.toLowerCase() === Constants.DefaultAccountExperience.MongoDB.toLowerCase()) { if (defaultExperience.toLowerCase() === Constants.DefaultAccountExperience.MongoDB.toLowerCase()) {
@@ -582,9 +544,8 @@ export default class Explorer {
defaultExperience && defaultExperience &&
defaultExperience.toLowerCase() === Constants.DefaultAccountExperience.Cassandra.toLowerCase() defaultExperience.toLowerCase() === Constants.DefaultAccountExperience.Cassandra.toLowerCase()
) { ) {
this._isSystemDatabasePredicate = (database: ViewModels.Database): boolean => { const api = new CassandraApi();
return database.id() === "system"; this._isSystemDatabasePredicate = api.isSystemDatabasePredicate;
};
} }
}); });
@@ -614,6 +575,7 @@ export default class Explorer {
}); });
this.addDatabasePane = new AddDatabasePane({ this.addDatabasePane = new AddDatabasePane({
documentClientUtility: this.documentClientUtility,
id: "adddatabasepane", id: "adddatabasepane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -622,6 +584,7 @@ export default class Explorer {
this.addCollectionPane = new AddCollectionPane({ this.addCollectionPane = new AddCollectionPane({
isPreferredApiTable: ko.computed(() => this.isPreferredApiTable()), isPreferredApiTable: ko.computed(() => this.isPreferredApiTable()),
documentClientUtility: this.documentClientUtility,
id: "addcollectionpane", id: "addcollectionpane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -629,6 +592,7 @@ export default class Explorer {
}); });
this.deleteCollectionConfirmationPane = new DeleteCollectionConfirmationPane({ this.deleteCollectionConfirmationPane = new DeleteCollectionConfirmationPane({
documentClientUtility: this.documentClientUtility,
id: "deletecollectionconfirmationpane", id: "deletecollectionconfirmationpane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -636,6 +600,7 @@ export default class Explorer {
}); });
this.deleteDatabaseConfirmationPane = new DeleteDatabaseConfirmationPane({ this.deleteDatabaseConfirmationPane = new DeleteDatabaseConfirmationPane({
documentClientUtility: this.documentClientUtility,
id: "deletedatabaseconfirmationpane", id: "deletedatabaseconfirmationpane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -643,6 +608,7 @@ export default class Explorer {
}); });
this.graphStylingPane = new GraphStylingPane({ this.graphStylingPane = new GraphStylingPane({
documentClientUtility: this.documentClientUtility,
id: "graphstylingpane", id: "graphstylingpane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -650,6 +616,7 @@ export default class Explorer {
}); });
this.addTableEntityPane = new AddTableEntityPane({ this.addTableEntityPane = new AddTableEntityPane({
documentClientUtility: this.documentClientUtility,
id: "addtableentitypane", id: "addtableentitypane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -657,6 +624,7 @@ export default class Explorer {
}); });
this.editTableEntityPane = new EditTableEntityPane({ this.editTableEntityPane = new EditTableEntityPane({
documentClientUtility: this.documentClientUtility,
id: "edittableentitypane", id: "edittableentitypane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -664,6 +632,7 @@ export default class Explorer {
}); });
this.tableColumnOptionsPane = new TableColumnOptionsPane({ this.tableColumnOptionsPane = new TableColumnOptionsPane({
documentClientUtility: this.documentClientUtility,
id: "tablecolumnoptionspane", id: "tablecolumnoptionspane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -671,6 +640,7 @@ export default class Explorer {
}); });
this.querySelectPane = new QuerySelectPane({ this.querySelectPane = new QuerySelectPane({
documentClientUtility: this.documentClientUtility,
id: "queryselectpane", id: "queryselectpane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -678,6 +648,7 @@ export default class Explorer {
}); });
this.newVertexPane = new NewVertexPane({ this.newVertexPane = new NewVertexPane({
documentClientUtility: this.documentClientUtility,
id: "newvertexpane", id: "newvertexpane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -685,6 +656,7 @@ export default class Explorer {
}); });
this.cassandraAddCollectionPane = new CassandraAddCollectionPane({ this.cassandraAddCollectionPane = new CassandraAddCollectionPane({
documentClientUtility: this.documentClientUtility,
id: "cassandraaddcollectionpane", id: "cassandraaddcollectionpane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -692,6 +664,7 @@ export default class Explorer {
}); });
this.settingsPane = new SettingsPane({ this.settingsPane = new SettingsPane({
documentClientUtility: this.documentClientUtility,
id: "settingspane", id: "settingspane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -699,6 +672,7 @@ export default class Explorer {
}); });
this.executeSprocParamsPane = new ExecuteSprocParamsPane({ this.executeSprocParamsPane = new ExecuteSprocParamsPane({
documentClientUtility: this.documentClientUtility,
id: "executesprocparamspane", id: "executesprocparamspane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -706,6 +680,7 @@ export default class Explorer {
}); });
this.renewAdHocAccessPane = new RenewAdHocAccessPane({ this.renewAdHocAccessPane = new RenewAdHocAccessPane({
documentClientUtility: this.documentClientUtility,
id: "renewadhocaccesspane", id: "renewadhocaccesspane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -713,6 +688,7 @@ export default class Explorer {
}); });
this.uploadItemsPane = new UploadItemsPane({ this.uploadItemsPane = new UploadItemsPane({
documentClientUtility: this.documentClientUtility,
id: "uploaditemspane", id: "uploaditemspane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -722,6 +698,7 @@ export default class Explorer {
this.uploadItemsPaneAdapter = new UploadItemsPaneAdapter(this); this.uploadItemsPaneAdapter = new UploadItemsPaneAdapter(this);
this.loadQueryPane = new LoadQueryPane({ this.loadQueryPane = new LoadQueryPane({
documentClientUtility: this.documentClientUtility,
id: "loadquerypane", id: "loadquerypane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -729,6 +706,7 @@ export default class Explorer {
}); });
this.saveQueryPane = new SaveQueryPane({ this.saveQueryPane = new SaveQueryPane({
documentClientUtility: this.documentClientUtility,
id: "savequerypane", id: "savequerypane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -736,6 +714,7 @@ export default class Explorer {
}); });
this.browseQueriesPane = new BrowseQueriesPane({ this.browseQueriesPane = new BrowseQueriesPane({
documentClientUtility: this.documentClientUtility,
id: "browsequeriespane", id: "browsequeriespane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -743,6 +722,7 @@ export default class Explorer {
}); });
this.uploadFilePane = new UploadFilePane({ this.uploadFilePane = new UploadFilePane({
documentClientUtility: this.documentClientUtility,
id: "uploadfilepane", id: "uploadfilepane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -750,6 +730,7 @@ export default class Explorer {
}); });
this.stringInputPane = new StringInputPane({ this.stringInputPane = new StringInputPane({
documentClientUtility: this.documentClientUtility,
id: "stringinputpane", id: "stringinputpane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -757,6 +738,7 @@ export default class Explorer {
}); });
this.setupNotebooksPane = new SetupNotebooksPane({ this.setupNotebooksPane = new SetupNotebooksPane({
documentClientUtility: this.documentClientUtility,
id: "setupnotebookspane", id: "setupnotebookspane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -789,6 +771,7 @@ export default class Explorer {
this.setupNotebooksPane this.setupNotebooksPane
]; ];
this.addDatabaseText.subscribe((addDatabaseText: string) => this.addDatabasePane.title(addDatabaseText)); this.addDatabaseText.subscribe((addDatabaseText: string) => this.addDatabasePane.title(addDatabaseText));
this.rebindDocumentClientUtility.bind(this);
this.isTabsContentExpanded = ko.observable(false); this.isTabsContentExpanded = ko.observable(false);
document.addEventListener( document.addEventListener(
@@ -870,7 +853,7 @@ export default class Explorer {
this.editTableEntityPane.title("Edit Table Entity"); this.editTableEntityPane.title("Edit Table Entity");
this.deleteCollectionConfirmationPane.title("Delete Table"); this.deleteCollectionConfirmationPane.title("Delete Table");
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the table id"); this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the table id");
this.tableDataClient = new TablesAPIDataClient(); this.tableDataClient = new TablesAPIDataClient(this.documentClientUtility);
break; break;
case Constants.DefaultAccountExperience.Cassandra.toLowerCase(): case Constants.DefaultAccountExperience.Cassandra.toLowerCase():
this.addCollectionText("New Table"); this.addCollectionText("New Table");
@@ -889,7 +872,7 @@ export default class Explorer {
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the table id"); this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the table id");
this.deleteDatabaseConfirmationPane.title("Delete Keyspace"); this.deleteDatabaseConfirmationPane.title("Delete Keyspace");
this.deleteDatabaseConfirmationPane.databaseIdConfirmationText("Confirm by typing the keyspace id"); this.deleteDatabaseConfirmationPane.databaseIdConfirmationText("Confirm by typing the keyspace id");
this.tableDataClient = new CassandraAPIDataClient(); this.tableDataClient = new CassandraAPIDataClient(this.documentClientUtility);
break; break;
} }
}); });
@@ -975,10 +958,6 @@ export default class Explorer {
this.sparkClusterConnectionInfo.valueHasMutated(); this.sparkClusterConnectionInfo.valueHasMutated();
} }
if (this.isFeatureEnabled(Constants.Features.enableSDKoperations)) {
updateUserContext({ useSDKOperations: true });
}
featureSubcription.dispose(); featureSubcription.dispose();
}); });
@@ -1039,7 +1018,7 @@ export default class Explorer {
); );
try { try {
const databaseAccount: DataModels.DatabaseAccount = await resourceProviderClient.patchAsync( const databaseAccount: ViewModels.DatabaseAccount = await resourceProviderClient.patchAsync(
this.databaseAccount().id, this.databaseAccount().id,
"2019-12-12", "2019-12-12",
{ {
@@ -1078,6 +1057,13 @@ export default class Explorer {
// TODO: return result // TODO: return result
} }
public rebindDocumentClientUtility(documentClientUtility: DocumentClientUtilityBase): void {
this.documentClientUtility = documentClientUtility;
this._panes.forEach((pane: ViewModels.ContextualPane) => {
pane.documentClientUtility = documentClientUtility;
});
}
public copyUrlLink(src: any, event: MouseEvent): void { public copyUrlLink(src: any, event: MouseEvent): void {
const urlLinkInput: HTMLInputElement = document.getElementById("shareUrlLink") as HTMLInputElement; const urlLinkInput: HTMLInputElement = document.getElementById("shareUrlLink") as HTMLInputElement;
urlLinkInput && urlLinkInput.select(); urlLinkInput && urlLinkInput.select();
@@ -1396,7 +1382,7 @@ export default class Explorer {
} }
const deferred: Q.Deferred<void> = Q.defer(); const deferred: Q.Deferred<void> = Q.defer();
readCollection(databaseId, collectionId).then((collection: DataModels.Collection) => { this.documentClientUtility.readCollection(databaseId, collectionId).then((collection: DataModels.Collection) => {
this.resourceTokenCollection(new ResourceTokenCollection(this, databaseId, collection)); this.resourceTokenCollection(new ResourceTokenCollection(this, databaseId, collection));
this.selectedNode(this.resourceTokenCollection()); this.selectedNode(this.resourceTokenCollection());
deferred.resolve(); deferred.resolve();
@@ -1420,13 +1406,16 @@ export default class Explorer {
dataExplorerArea: Constants.Areas.ResourceTree dataExplorerArea: Constants.Areas.ResourceTree
}); });
} }
// TODO: Refactor // TODO: Refactor
const deferred: Q.Deferred<any> = Q.defer(); const deferred: Q.Deferred<any> = Q.defer();
const offerPromise: Q.Promise<DataModels.Offer[]> = this.documentClientUtility.readOffers();
this._setLoadingStatusText("Fetching offers...");
const refreshDatabases = (offers?: DataModels.Offer[]) => { offerPromise.then(
(offers: DataModels.Offer[]) => {
this._setLoadingStatusText("Successfully fetched offers.");
this._setLoadingStatusText("Fetching databases..."); this._setLoadingStatusText("Fetching databases...");
readDatabases().then( this.documentClientUtility.readDatabases(null /*options*/).then(
(databases: DataModels.Database[]) => { (databases: DataModels.Database[]) => {
this._setLoadingStatusText("Successfully fetched databases."); this._setLoadingStatusText("Successfully fetched databases.");
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
@@ -1477,14 +1466,6 @@ export default class Explorer {
); );
} }
); );
};
const offerPromise: Q.Promise<DataModels.Offer[]> = readOffers({ isServerless: this.isServerlessEnabled() });
this._setLoadingStatusText("Fetching offers...");
offerPromise.then(
(offers: DataModels.Offer[]) => {
this._setLoadingStatusText("Successfully fetched offers.");
refreshDatabases(offers);
}, },
error => { error => {
this._setLoadingStatusText("Failed to fetch offers."); this._setLoadingStatusText("Failed to fetch offers.");
@@ -1554,7 +1535,7 @@ export default class Explorer {
dataExplorerArea: Constants.Areas.ResourceTree dataExplorerArea: Constants.Areas.ResourceTree
}); });
this.isRefreshingExplorer(true); this.isRefreshingExplorer(true);
refreshCachedResources().then( this.documentClientUtility.refreshCachedResources().then(
() => { () => {
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.LoadDatabases, Action.LoadDatabases,
@@ -1607,7 +1588,7 @@ export default class Explorer {
public async getArcadiaToken(): Promise<string> { public async getArcadiaToken(): Promise<string> {
return new Promise<string>((resolve: (token: string) => void, reject: (error: any) => void) => { return new Promise<string>((resolve: (token: string) => void, reject: (error: any) => void) => {
sendCachedDataMessage<string>(MessageTypes.GetArcadiaToken, undefined /** params **/).then( MessageHandler.sendCachedDataMessage<string>(MessageTypes.GetArcadiaToken, undefined /** params **/).then(
(token: string) => { (token: string) => {
resolve(token); resolve(token);
}, },
@@ -1621,7 +1602,7 @@ export default class Explorer {
private async _getArcadiaWorkspaces(): Promise<ArcadiaWorkspaceItem[]> { private async _getArcadiaWorkspaces(): Promise<ArcadiaWorkspaceItem[]> {
try { try {
const workspaces = await this._arcadiaManager.listWorkspacesAsync([userContext.subscriptionId]); const workspaces = await this._arcadiaManager.listWorkspacesAsync([CosmosClient.subscriptionId()]);
let workspaceItems: ArcadiaWorkspaceItem[] = new Array(workspaces.length); let workspaceItems: ArcadiaWorkspaceItem[] = new Array(workspaces.length);
const sparkPromises: Promise<void>[] = []; const sparkPromises: Promise<void>[] = [];
workspaces.forEach((workspace, i) => { workspaces.forEach((workspace, i) => {
@@ -1645,11 +1626,11 @@ export default class Explorer {
} }
public async createWorkspace(): Promise<string> { public async createWorkspace(): Promise<string> {
return sendCachedDataMessage(MessageTypes.CreateWorkspace, undefined /** params **/); return MessageHandler.sendCachedDataMessage(MessageTypes.CreateWorkspace, undefined /** params **/);
} }
public async createSparkPool(workspaceId: string): Promise<string> { public async createSparkPool(workspaceId: string): Promise<string> {
return sendCachedDataMessage(MessageTypes.CreateSparkPool, [workspaceId]); return MessageHandler.sendCachedDataMessage(MessageTypes.CreateSparkPool, [workspaceId]);
} }
public async initNotebooks(databaseAccount: DataModels.DatabaseAccount): Promise<void> { public async initNotebooks(databaseAccount: DataModels.DatabaseAccount): Promise<void> {
@@ -1724,7 +1705,7 @@ export default class Explorer {
} }
try { try {
const workspaces = await this.notebookWorkspaceManager.getNotebookWorkspacesAsync(databaseAccount?.id); const workspaces = await this.notebookWorkspaceManager.getNotebookWorkspacesAsync(databaseAccount.id);
return workspaces && workspaces.length > 0 && workspaces.some(workspace => workspace.name === "default"); return workspaces && workspaces.length > 0 && workspaces.some(workspace => workspace.name === "default");
} catch (error) { } catch (error) {
Logger.logError(error, "Explorer/_containsDefaultNotebookWorkspace"); Logger.logError(error, "Explorer/_containsDefaultNotebookWorkspace");
@@ -1737,7 +1718,6 @@ export default class Explorer {
return; return;
} }
let clearMessage;
try { try {
const notebookWorkspace = await this.notebookWorkspaceManager.getNotebookWorkspaceAsync( const notebookWorkspace = await this.notebookWorkspaceManager.getNotebookWorkspaceAsync(
this.databaseAccount().id, this.databaseAccount().id,
@@ -1749,14 +1729,10 @@ export default class Explorer {
notebookWorkspace.properties.status && notebookWorkspace.properties.status &&
notebookWorkspace.properties.status.toLowerCase() === "stopped" notebookWorkspace.properties.status.toLowerCase() === "stopped"
) { ) {
clearMessage = NotificationConsoleUtils.logConsoleProgress("Initializing notebook workspace");
await this.notebookWorkspaceManager.startNotebookWorkspaceAsync(this.databaseAccount().id, "default"); await this.notebookWorkspaceManager.startNotebookWorkspaceAsync(this.databaseAccount().id, "default");
} }
} catch (error) { } catch (error) {
Logger.logError(error, "Explorer/ensureNotebookWorkspaceRunning"); Logger.logError(error, "Explorer/ensureNotebookWorkspaceRunning");
NotificationConsoleUtils.logConsoleError(`Failed to initialize notebook workspace: ${JSON.stringify(error)}`);
} finally {
clearMessage && clearMessage();
} }
} }
@@ -1831,8 +1807,8 @@ export default class Explorer {
const isRunningInPortal = window.dataExplorerPlatform == PlatformType.Portal; const isRunningInPortal = window.dataExplorerPlatform == PlatformType.Portal;
const isRunningInDevMode = process.env.NODE_ENV === "development"; const isRunningInDevMode = process.env.NODE_ENV === "development";
if (inputs && configContext.BACKEND_ENDPOINT && isRunningInPortal && isRunningInDevMode) { if (inputs && config.BACKEND_ENDPOINT && isRunningInPortal && isRunningInDevMode) {
inputs.extensionEndpoint = configContext.PROXY_PATH; inputs.extensionEndpoint = config.PROXY_PATH;
} }
const initPromise: Q.Promise<void> = inputs ? this.initDataExplorerWithFrameInputs(inputs) : Q(); const initPromise: Q.Promise<void> = inputs ? this.initDataExplorerWithFrameInputs(inputs) : Q();
@@ -1850,7 +1826,7 @@ export default class Explorer {
} }
} }
if (message.actionType === ActionContracts.ActionType.TransmitCachedData) { if (message.actionType === ActionContracts.ActionType.TransmitCachedData) {
handleCachedDataMessage(message); MessageHandler.handleCachedDataMessage(message);
return; return;
} }
if (message.type) { if (message.type) {
@@ -1937,7 +1913,7 @@ export default class Explorer {
this.features(inputs.features); this.features(inputs.features);
this.serverId(inputs.serverId); this.serverId(inputs.serverId);
this.extensionEndpoint(inputs.extensionEndpoint || ""); this.extensionEndpoint(inputs.extensionEndpoint || "");
this.armEndpoint(EnvironmentUtility.normalizeArmEndpointUri(inputs.csmEndpoint || configContext.ARM_ENDPOINT)); this.armEndpoint(EnvironmentUtility.normalizeArmEndpointUri(inputs.csmEndpoint || config.ARM_ENDPOINT));
this.notificationsClient.setExtensionEndpoint(this.extensionEndpoint()); this.notificationsClient.setExtensionEndpoint(this.extensionEndpoint());
this.databaseAccount(databaseAccount); this.databaseAccount(databaseAccount);
this.subscriptionType(inputs.subscriptionType); this.subscriptionType(inputs.subscriptionType);
@@ -1953,17 +1929,11 @@ export default class Explorer {
this._importExplorerConfigComplete = true; this._importExplorerConfigComplete = true;
updateConfigContext({ CosmosClient.authorizationToken(authorizationToken);
ARM_ENDPOINT: this.armEndpoint() CosmosClient.masterKey(masterKey);
}); CosmosClient.databaseAccount(databaseAccount);
CosmosClient.subscriptionId(inputs.subscriptionId);
updateUserContext({ CosmosClient.resourceGroup(inputs.resourceGroup);
authorizationToken,
masterKey,
databaseAccount,
resourceGroup: inputs.resourceGroup,
subscriptionId: inputs.subscriptionId
});
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.LoadDatabaseAccount, Action.LoadDatabaseAccount,
{ {
@@ -1993,7 +1963,7 @@ export default class Explorer {
return _.find(selectedCollection.storedProcedures(), (storedProcedure: StoredProcedure) => { return _.find(selectedCollection.storedProcedures(), (storedProcedure: StoredProcedure) => {
const openedSprocTab = this.tabsManager.getTabs( const openedSprocTab = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.StoredProcedures, ViewModels.CollectionTabKind.StoredProcedures,
tab => tab.node && tab.node.rid === storedProcedure.rid (tab: ViewModels.Tab) => tab.node && tab.node.rid === storedProcedure.rid
); );
return ( return (
storedProcedure.rid === this.selectedNode().rid || storedProcedure.rid === this.selectedNode().rid ||
@@ -2007,7 +1977,7 @@ export default class Explorer {
return _.find(selectedCollection.userDefinedFunctions(), (userDefinedFunction: UserDefinedFunction) => { return _.find(selectedCollection.userDefinedFunctions(), (userDefinedFunction: UserDefinedFunction) => {
const openedUdfTab = this.tabsManager.getTabs( const openedUdfTab = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.UserDefinedFunctions, ViewModels.CollectionTabKind.UserDefinedFunctions,
tab => tab.node && tab.node.rid === userDefinedFunction.rid (tab: ViewModels.Tab) => tab.node && tab.node.rid === userDefinedFunction.rid
); );
return ( return (
userDefinedFunction.rid === this.selectedNode().rid || userDefinedFunction.rid === this.selectedNode().rid ||
@@ -2021,7 +1991,7 @@ export default class Explorer {
return _.find(selectedCollection.triggers(), (trigger: Trigger) => { return _.find(selectedCollection.triggers(), (trigger: Trigger) => {
const openedTriggerTab = this.tabsManager.getTabs( const openedTriggerTab = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.Triggers, ViewModels.CollectionTabKind.Triggers,
tab => tab.node && tab.node.rid === trigger.rid (tab: ViewModels.Tab) => tab.node && tab.node.rid === trigger.rid
); );
return ( return (
trigger.rid === this.selectedNode().rid || trigger.rid === this.selectedNode().rid ||
@@ -2031,7 +2001,7 @@ export default class Explorer {
} }
public closeAllPanes(): void { public closeAllPanes(): void {
this._panes.forEach((pane: ContextualPaneBase) => pane.close()); this._panes.forEach((pane: ViewModels.ContextualPane) => pane.close());
} }
public getPlatformType(): PlatformType { public getPlatformType(): PlatformType {
@@ -2046,13 +2016,13 @@ export default class Explorer {
); );
} }
public onUpdateTabsButtons(buttons: CommandButtonComponentProps[]): void { public onUpdateTabsButtons(buttons: ViewModels.NavbarButtonConfig[]): void {
this.commandBarComponentAdapter.onUpdateTabsButtons(buttons); this.commandBarComponentAdapter.onUpdateTabsButtons(buttons);
} }
public signInAad = () => { public signInAad = () => {
TelemetryProcessor.trace(Action.SignInAad, undefined, { area: "Explorer" }); TelemetryProcessor.trace(Action.SignInAad, undefined, { area: "Explorer" });
sendMessage({ MessageHandler.sendMessage({
type: MessageTypes.AadSignIn type: MessageTypes.AadSignIn
}); });
}; };
@@ -2063,21 +2033,21 @@ export default class Explorer {
}; };
public clickHostedAccountSwitch = () => { public clickHostedAccountSwitch = () => {
sendMessage({ MessageHandler.sendMessage({
type: MessageTypes.UpdateAccountSwitch, type: MessageTypes.UpdateAccountSwitch,
click: true click: true
}); });
}; };
public clickHostedDirectorySwitch = () => { public clickHostedDirectorySwitch = () => {
sendMessage({ MessageHandler.sendMessage({
type: MessageTypes.UpdateDirectoryControl, type: MessageTypes.UpdateDirectoryControl,
click: true click: true
}); });
}; };
public refreshDatabaseAccount = () => { public refreshDatabaseAccount = () => {
sendMessage({ MessageHandler.sendMessage({
type: MessageTypes.RefreshDatabaseAccount type: MessageTypes.RefreshDatabaseAccount
}); });
}; };
@@ -2106,7 +2076,9 @@ export default class Explorer {
if (isNewDatabase) { if (isNewDatabase) {
database.expandDatabase(); database.expandDatabase();
} }
this.tabsManager.refreshActiveTab(tab => tab.collection && tab.collection.getDatabase().rid === database.rid); this.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.getDatabase().rid === database.rid
);
}) })
); );
}); });
@@ -2208,8 +2180,8 @@ export default class Explorer {
return undefined; return undefined;
} }
const urlPrefixWithKeyParam: string = `${configContext.hostedExplorerURL}?key=`; const urlPrefixWithKeyParam: string = `${config.hostedExplorerURL}?key=`;
const currentActiveTab = this.tabsManager.activeTab(); const currentActiveTab: ViewModels.Tab = this.tabsManager.activeTab();
return `${urlPrefixWithKeyParam}${token}#/${(currentActiveTab && currentActiveTab.hashLocation()) || ""}`; return `${urlPrefixWithKeyParam}${token}#/${(currentActiveTab && currentActiveTab.hashLocation()) || ""}`;
} }
@@ -2320,7 +2292,7 @@ export default class Explorer {
return _.find(offers, (offer: DataModels.Offer) => offer.resource === resourceId); return _.find(offers, (offer: DataModels.Offer) => offer.resource === resourceId);
} }
public uploadFile(name: string, content: string, parent: NotebookContentItem): Promise<NotebookContentItem> { private uploadFile(name: string, content: string, parent: NotebookContentItem): Promise<NotebookContentItem> {
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) { if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to upload notebook, but notebook is not enabled"; const error = "Attempt to upload notebook, but notebook is not enabled";
Logger.logError(error, "Explorer/uploadFile"); Logger.logError(error, "Explorer/uploadFile");
@@ -2375,28 +2347,14 @@ export default class Explorer {
return Promise.resolve(false); return Promise.resolve(false);
} }
public async publishNotebook(name: string, content: string | unknown, parentDomElement: HTMLElement): Promise<void> { public publishNotebook(name: string, content: string): void {
if (this.notebookManager) { if (this.notebookManager) {
await this.notebookManager.openPublishNotebookPane( this.notebookManager.openPublishNotebookPane(name, content);
name,
content,
parentDomElement,
this.isCodeOfConductEnabled(),
this.isLinkInjectionEnabled()
);
this.publishNotebookPaneAdapter = this.notebookManager.publishNotebookPaneAdapter; this.publishNotebookPaneAdapter = this.notebookManager.publishNotebookPaneAdapter;
this.isPublishNotebookPaneEnabled(true); this.isPublishNotebookPaneEnabled(true);
} }
} }
public copyNotebook(name: string, content: string): void {
if (this.notebookManager) {
this.notebookManager.openCopyNotebookPane(name, content);
this.copyNotebookPaneAdapter = this.notebookManager.copyNotebookPaneAdapter;
this.isCopyNotebookPaneEnabled(true);
}
}
public showOkModalDialog(title: string, msg: string): void { public showOkModalDialog(title: string, msg: string): void {
this._dialogProps({ this._dialogProps({
isModal: true, isModal: true,
@@ -2486,26 +2444,28 @@ export default class Explorer {
throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`); throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`);
} }
const notebookTabs = this.tabsManager.getTabs( const notebookTabs: NotebookV2Tab[] = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.NotebookV2, ViewModels.CollectionTabKind.NotebookV2,
tab => (tab: ViewModels.Tab) =>
(tab as NotebookV2Tab).notebookPath && (tab as NotebookV2Tab).notebookPath &&
FileSystemUtil.isPathEqual((tab as NotebookV2Tab).notebookPath(), notebookContentItem.path) FileSystemUtil.isPathEqual((tab as NotebookV2Tab).notebookPath(), notebookContentItem.path)
) as NotebookV2Tab[]; ) as NotebookV2Tab[];
let notebookTab = notebookTabs && notebookTabs[0]; let notebookTab: NotebookV2Tab = notebookTabs && notebookTabs[0];
if (notebookTab) { if (notebookTab) {
this.tabsManager.activateTab(notebookTab); this.tabsManager.activateTab(notebookTab);
} else { } else {
const options: NotebookTabOptions = { const options: ViewModels.NotebookTabOptions = {
account: userContext.databaseAccount, account: CosmosClient.databaseAccount(),
tabKind: ViewModels.CollectionTabKind.NotebookV2, tabKind: ViewModels.CollectionTabKind.NotebookV2,
node: null, node: null,
title: notebookContentItem.name, title: notebookContentItem.name,
tabPath: notebookContentItem.path, tabPath: notebookContentItem.path,
documentClientUtility: null,
collection: null, collection: null,
selfLink: null, selfLink: null,
masterKey: userContext.masterKey || "", masterKey: CosmosClient.masterKey() || "",
hashLocation: "notebooks", hashLocation: "notebooks",
isActive: ko.observable(false), isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true), isTabsContentExpanded: ko.observable(true),
@@ -2561,7 +2521,7 @@ export default class Explorer {
onSubmit: (input: string) => this.notebookManager?.notebookContentClient.renameNotebook(notebookFile, input) onSubmit: (input: string) => this.notebookManager?.notebookContentClient.renameNotebook(notebookFile, input)
}) })
.then(newNotebookFile => { .then(newNotebookFile => {
const notebookTabs = this.tabsManager.getTabs( const notebookTabs: ViewModels.Tab[] = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.NotebookV2, ViewModels.CollectionTabKind.NotebookV2,
(tab: NotebookV2Tab) => tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), originalPath) (tab: NotebookV2Tab) => tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), originalPath)
); );
@@ -2649,11 +2609,7 @@ export default class Explorer {
private async _refreshNotebooksEnabledStateForAccount(): Promise<void> { private async _refreshNotebooksEnabledStateForAccount(): Promise<void> {
const authType = window.authType as AuthType; const authType = window.authType as AuthType;
if ( if (authType === AuthType.EncryptedToken || authType === AuthType.ResourceToken) {
authType === AuthType.EncryptedToken ||
authType === AuthType.ResourceToken ||
authType === AuthType.MasterKey
) {
this.isNotebooksEnabledForAccount(false); this.isNotebooksEnabledForAccount(false);
return; return;
} }
@@ -2695,7 +2651,7 @@ export default class Explorer {
} }
public _refreshSparkEnabledStateForAccount = async (): Promise<void> => { public _refreshSparkEnabledStateForAccount = async (): Promise<void> => {
const subscriptionId = userContext.subscriptionId; const subscriptionId = CosmosClient.subscriptionId();
const armEndpoint = this.armEndpoint(); const armEndpoint = this.armEndpoint();
const authType = window.authType as AuthType; const authType = window.authType as AuthType;
if (!subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) { if (!subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) {
@@ -2724,7 +2680,7 @@ export default class Explorer {
}; };
public _isAfecFeatureRegistered = async (featureName: string): Promise<boolean> => { public _isAfecFeatureRegistered = async (featureName: string): Promise<boolean> => {
const subscriptionId = userContext.subscriptionId; const subscriptionId = CosmosClient.subscriptionId();
const armEndpoint = this.armEndpoint(); const armEndpoint = this.armEndpoint();
const authType = window.authType as AuthType; const authType = window.authType as AuthType;
if (!featureName || !subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) { if (!featureName || !subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) {
@@ -2753,7 +2709,6 @@ export default class Explorer {
} }
await this.resourceTree.initialize(); await this.resourceTree.initialize();
this.notebookManager?.refreshPinnedRepos();
if (this.notebookToImport) { if (this.notebookToImport) {
this.importAndOpenContent(this.notebookToImport.name, this.notebookToImport.content); this.importAndOpenContent(this.notebookToImport.name, this.notebookToImport.content);
} }
@@ -2936,7 +2891,7 @@ export default class Explorer {
const terminalTabs: TerminalTab[] = this.tabsManager.getTabs( const terminalTabs: TerminalTab[] = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.Terminal, ViewModels.CollectionTabKind.Terminal,
tab => tab.hashLocation() == hashLocation (tab: ViewModels.Tab) => tab.hashLocation() == hashLocation
) as TerminalTab[]; ) as TerminalTab[];
let terminalTab: TerminalTab = terminalTabs && terminalTabs[0]; let terminalTab: TerminalTab = terminalTabs && terminalTabs[0];
@@ -2944,11 +2899,13 @@ export default class Explorer {
this.tabsManager.activateTab(terminalTab); this.tabsManager.activateTab(terminalTab);
} else { } else {
const newTab = new TerminalTab({ const newTab = new TerminalTab({
account: userContext.databaseAccount, account: CosmosClient.databaseAccount(),
tabKind: ViewModels.CollectionTabKind.Terminal, tabKind: ViewModels.CollectionTabKind.Terminal,
node: null, node: null,
title: title, title: title,
tabPath: title, tabPath: title,
documentClientUtility: null,
collection: null, collection: null,
selfLink: null, selfLink: null,
hashLocation: hashLocation, hashLocation: hashLocation,
@@ -2970,7 +2927,7 @@ export default class Explorer {
const galleryTabs = this.tabsManager.getTabs( const galleryTabs = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.Gallery, ViewModels.CollectionTabKind.Gallery,
tab => tab.hashLocation() == hashLocation (tab: ViewModels.Tab) => tab.hashLocation() == hashLocation
); );
let galleryTab = galleryTabs && galleryTabs[0]; let galleryTab = galleryTabs && galleryTabs[0];
@@ -2983,7 +2940,7 @@ export default class Explorer {
const newTab = new this.galleryTab.default({ const newTab = new this.galleryTab.default({
// GalleryTabOptions // GalleryTabOptions
account: userContext.databaseAccount, account: CosmosClient.databaseAccount(),
container: this, container: this,
junoClient: this.notebookManager?.junoClient, junoClient: this.notebookManager?.junoClient,
notebookUrl, notebookUrl,
@@ -3016,21 +2973,24 @@ export default class Explorer {
const notebookViewerTabModule = this.notebookViewerTab; const notebookViewerTabModule = this.notebookViewerTab;
let isNotebookViewerOpen = (tab: TabsBase) => { let isNotebookViewerOpen = (tab: ViewModels.Tab) => {
const notebookViewerTab = tab as typeof notebookViewerTabModule.default; const notebookViewerTab = tab as typeof notebookViewerTabModule.default;
return notebookViewerTab.notebookUrl === notebookUrl; return notebookViewerTab.notebookUrl === notebookUrl;
}; };
const notebookViewerTabs = this.tabsManager.getTabs(ViewModels.CollectionTabKind.NotebookV2, tab => { const notebookViewerTabs = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.NotebookV2,
(tab: ViewModels.Tab) => {
return tab.hashLocation() == hashLocation && isNotebookViewerOpen(tab); return tab.hashLocation() == hashLocation && isNotebookViewerOpen(tab);
}); }
);
let notebookViewerTab = notebookViewerTabs && notebookViewerTabs[0]; let notebookViewerTab = notebookViewerTabs && notebookViewerTabs[0];
if (notebookViewerTab) { if (notebookViewerTab) {
this.tabsManager.activateNewTab(notebookViewerTab); this.tabsManager.activateNewTab(notebookViewerTab);
} else { } else {
notebookViewerTab = new this.notebookViewerTab.default({ notebookViewerTab = new this.notebookViewerTab.default({
account: userContext.databaseAccount, account: CosmosClient.databaseAccount(),
tabKind: ViewModels.CollectionTabKind.NotebookViewer, tabKind: ViewModels.CollectionTabKind.NotebookViewer,
node: null, node: null,
title: title, title: title,

View File

@@ -15,7 +15,7 @@ import { GraphData, D3Node, D3Link } from "./GraphData";
import { HashMap } from "../../../Common/HashMap"; import { HashMap } from "../../../Common/HashMap";
import { BaseType } from "d3"; import { BaseType } from "d3";
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; import { NotificationConsoleUtils } from "../../../Utils/NotificationConsoleUtils";
import { GraphConfig } from "../../Tabs/GraphTab"; import { GraphConfig } from "../../Tabs/GraphTab";
import { GraphExplorer } from "./GraphExplorer"; import { GraphExplorer } from "./GraphExplorer";
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";

View File

@@ -1,4 +1,3 @@
jest.mock("../../../Common/DocumentClientUtilityBase");
import React from "react"; import React from "react";
import * as sinon from "sinon"; import * as sinon from "sinon";
import { mount, ReactWrapper } from "enzyme"; import { mount, ReactWrapper } from "enzyme";
@@ -8,11 +7,11 @@ import { GraphExplorer, GraphExplorerProps, GraphAccessor, GraphHighlightedNodeD
import * as D3ForceGraph from "./D3ForceGraph"; import * as D3ForceGraph from "./D3ForceGraph";
import { GraphData } from "./GraphData"; import { GraphData } from "./GraphData";
import { TabComponent } from "../../Controls/Tabs/TabComponent"; import { TabComponent } from "../../Controls/Tabs/TabComponent";
import * as ViewModels from "../../../Contracts/ViewModels";
import * as DataModels from "../../../Contracts/DataModels"; import * as DataModels from "../../../Contracts/DataModels";
import * as StorageUtility from "../../../Shared/StorageUtility"; import * as StorageUtility from "../../../Shared/StorageUtility";
import GraphTab from "../../Tabs/GraphTab"; import GraphTab from "../../Tabs/GraphTab";
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
import { queryDocuments, queryDocumentsPage } from "../../../Common/DocumentClientUtilityBase";
describe("Check whether query result is vertex array", () => { describe("Check whether query result is vertex array", () => {
it("should reject null as vertex array", () => { it("should reject null as vertex array", () => {
@@ -87,31 +86,13 @@ describe("getPkIdFromDocumentId", () => {
expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("['pkvalue', 'id']"); expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("['pkvalue', 'id']");
}); });
it("should create pkid pair from partitioned graph (pk as number)", () => {
const doc = createFakeDoc({ id: "id", mypk: 234 });
expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("[234, 'id']");
});
it("should create pkid pair from partitioned graph (pk as boolean)", () => {
const doc = createFakeDoc({ id: "id", mypk: true });
expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("[true, 'id']");
});
it("should create pkid pair from partitioned graph (pk as valid array value)", () => { it("should create pkid pair from partitioned graph (pk as valid array value)", () => {
const doc = createFakeDoc({ id: "id", mypk: [{ id: "someid", _value: "pkvalue" }] }); const doc = createFakeDoc({ id: "id", mypk: [{ id: "someid", _value: "pkvalue" }] });
expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("['pkvalue', 'id']"); expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("['pkvalue', 'id']");
}); });
it("should error if id is not a string or number", () => { it("should error if id is not a string", () => {
let doc = createFakeDoc({ id: { foo: 1 } }); const doc = createFakeDoc({ id: { foo: 1 } });
try {
GraphExplorer.getPkIdFromDocumentId(doc, undefined);
expect(true).toBe(false);
} catch (e) {
expect(true).toBe(true);
}
doc = createFakeDoc({ id: true });
try { try {
GraphExplorer.getPkIdFromDocumentId(doc, undefined); GraphExplorer.getPkIdFromDocumentId(doc, undefined);
expect(true).toBe(false); expect(true).toBe(false);
@@ -120,8 +101,16 @@ describe("getPkIdFromDocumentId", () => {
} }
}); });
it("should error if pk is empty array", () => { it("should error if pk not string nor non-empty array", () => {
let doc = createFakeDoc({ mypk: [] }); let doc = createFakeDoc({ mypk: { foo: 1 } });
try {
GraphExplorer.getPkIdFromDocumentId(doc, "mypk");
} catch (e) {
expect(true).toBe(true);
}
doc = createFakeDoc({ mypk: [] });
try { try {
GraphExplorer.getPkIdFromDocumentId(doc, "mypk"); GraphExplorer.getPkIdFromDocumentId(doc, "mypk");
expect(true).toBe(false); expect(true).toBe(false);
@@ -145,7 +134,7 @@ describe("GraphExplorer", () => {
const COLLECTION_SELF_LINK = "collectionSelfLink"; const COLLECTION_SELF_LINK = "collectionSelfLink";
const gremlinRU = 789.12; const gremlinRU = 789.12;
const createMockProps = (): GraphExplorerProps => { const createMockProps = (documentClientUtility?: any): GraphExplorerProps => {
const graphConfig = GraphTab.createGraphConfig(); const graphConfig = GraphTab.createGraphConfig();
const graphConfigUi = GraphTab.createGraphConfigUiData(graphConfig); const graphConfigUi = GraphTab.createGraphConfigUiData(graphConfig);
@@ -160,6 +149,7 @@ describe("GraphExplorer", () => {
onIsValidQueryChange: (isValidQuery: boolean): void => {}, onIsValidQueryChange: (isValidQuery: boolean): void => {},
collectionPartitionKeyProperty: "collectionPartitionKeyProperty", collectionPartitionKeyProperty: "collectionPartitionKeyProperty",
documentClientUtility: documentClientUtility,
collectionRid: COLLECTION_RID, collectionRid: COLLECTION_RID,
collectionSelfLink: COLLECTION_SELF_LINK, collectionSelfLink: COLLECTION_SELF_LINK,
graphBackendEndpoint: "graphBackendEndpoint", graphBackendEndpoint: "graphBackendEndpoint",
@@ -198,6 +188,7 @@ describe("GraphExplorer", () => {
let wrapper: ReactWrapper; let wrapper: ReactWrapper;
let connectStub: sinon.SinonSpy; let connectStub: sinon.SinonSpy;
let queryDocStub: sinon.SinonSpy;
let submitToBackendSpy: sinon.SinonSpy; let submitToBackendSpy: sinon.SinonSpy;
let renderResultAsJsonStub: sinon.SinonSpy; let renderResultAsJsonStub: sinon.SinonSpy;
let onMiddlePaneInitializedStub: sinon.SinonSpy; let onMiddlePaneInitializedStub: sinon.SinonSpy;
@@ -224,6 +215,46 @@ describe("GraphExplorer", () => {
[query: string]: AjaxResponse; [query: string]: AjaxResponse;
} }
const createDocumentClientUtilityMock = (docDBResponse: AjaxResponse) => {
const mock = {
queryDocuments: () => {},
queryDocumentsPage: (
rid: string,
iterator: any,
firstItemIndex: number,
options: any
): Q.Promise<ViewModels.QueryResults> => {
const qresult = {
hasMoreResults: false,
firstItemIndex: firstItemIndex,
lastItemIndex: 0,
itemCount: 0,
documents: docDBResponse.response,
activityId: "",
headers: [] as any[],
requestCharge: gVRU
};
return Q.resolve(qresult);
}
};
const fakeIterator: any = {
nextItem: (callback: (error: any, document: DataModels.DocumentId) => void): void => {},
hasMoreResults: () => false,
executeNext: (callback: (error: any, documents: DataModels.DocumentId[], headers: any) => void): void => {}
};
queryDocStub = sinon.stub(mock, "queryDocuments").callsFake(
(container: ViewModels.DocumentRequestContainer, query: string, options: any): Q.Promise<any> => {
(fakeIterator as any)._query = query;
return Q.resolve(fakeIterator);
}
);
return mock;
};
const setupMocks = ( const setupMocks = (
graphExplorer: GraphExplorer, graphExplorer: GraphExplorer,
backendResponses: BackendResponses, backendResponses: BackendResponses,
@@ -302,29 +333,7 @@ describe("GraphExplorer", () => {
done: any, done: any,
ignoreD3Update: boolean ignoreD3Update: boolean
): GraphExplorer => { ): GraphExplorer => {
(queryDocuments as jest.Mock).mockImplementation((container: any, query: string, options: any) => { const props: GraphExplorerProps = createMockProps(createDocumentClientUtilityMock(docDBResponse));
return Q.resolve({
_query: query,
nextItem: (callback: (error: any, document: DataModels.DocumentId) => void): void => {},
hasMoreResults: () => false,
executeNext: (callback: (error: any, documents: DataModels.DocumentId[], headers: any) => void): void => {}
});
});
(queryDocumentsPage as jest.Mock).mockImplementation(
(rid: string, iterator: any, firstItemIndex: number, options: any) => {
return Q.resolve({
hasMoreResults: false,
firstItemIndex: firstItemIndex,
lastItemIndex: 0,
itemCount: 0,
documents: docDBResponse.response,
activityId: "",
headers: [] as any[],
requestCharge: gVRU
});
}
);
const props: GraphExplorerProps = createMockProps();
wrapper = mount(<GraphExplorer {...props} />); wrapper = mount(<GraphExplorer {...props} />);
graphExplorerInstance = wrapper.instance() as GraphExplorer; graphExplorerInstance = wrapper.instance() as GraphExplorer;
setupMocks(graphExplorerInstance, backendResponses, done, ignoreD3Update); setupMocks(graphExplorerInstance, backendResponses, done, ignoreD3Update);
@@ -332,7 +341,7 @@ describe("GraphExplorer", () => {
}; };
const cleanUpStubsWrapper = () => { const cleanUpStubsWrapper = () => {
jest.resetAllMocks(); queryDocStub.restore();
connectStub.restore(); connectStub.restore();
submitToBackendSpy.restore(); submitToBackendSpy.restore();
renderResultAsJsonStub.restore(); renderResultAsJsonStub.restore();
@@ -369,11 +378,22 @@ describe("GraphExplorer", () => {
expect((graphExplorerInstance.submitToBackend as sinon.SinonSpy).calledWith("g.V()")).toBe(false); expect((graphExplorerInstance.submitToBackend as sinon.SinonSpy).calledWith("g.V()")).toBe(false);
}); });
it("should submit g.V() as docdb query with proper parameters", () => { it("should submit g.V() as docdb query with proper query", () => {
expect(queryDocuments).toBeCalledWith("databaseId", "collectionId", DOCDB_G_DOT_V_QUERY, { expect(
maxItemCount: GraphExplorer.ROOT_LIST_PAGE_SIZE, (graphExplorerInstance.props.documentClientUtility.queryDocuments as sinon.SinonSpy).getCall(0).args[2]
enableCrossPartitionQuery: true ).toBe(DOCDB_G_DOT_V_QUERY);
}); });
it("should submit g.V() as docdb query with proper parameters", () => {
expect(
(graphExplorerInstance.props.documentClientUtility.queryDocuments as sinon.SinonSpy).getCall(0).args[0]
).toEqual("databaseId");
expect(
(graphExplorerInstance.props.documentClientUtility.queryDocuments as sinon.SinonSpy).getCall(0).args[1]
).toEqual("collectionId");
expect(
(graphExplorerInstance.props.documentClientUtility.queryDocuments as sinon.SinonSpy).getCall(0).args[3]
).toEqual({ maxItemCount: GraphExplorer.ROOT_LIST_PAGE_SIZE, enableCrossPartitionQuery: true });
}); });
it("should call backend thrice (user query, fetch outE, then fetch inE)", () => { it("should call backend thrice (user query, fetch outE, then fetch inE)", () => {
@@ -406,11 +426,22 @@ describe("GraphExplorer", () => {
expect((graphExplorerInstance.submitToBackend as sinon.SinonSpy).calledWith("g.V()")).toBe(false); expect((graphExplorerInstance.submitToBackend as sinon.SinonSpy).calledWith("g.V()")).toBe(false);
}); });
it("should submit g.V() as docdb query with proper parameters", () => { it("should submit g.V() as docdb query with proper query", () => {
expect(queryDocuments).toBeCalledWith("databaseId", "collectionId", DOCDB_G_DOT_V_QUERY, { expect(
maxItemCount: GraphExplorer.ROOT_LIST_PAGE_SIZE, (graphExplorerInstance.props.documentClientUtility.queryDocuments as sinon.SinonSpy).getCall(0).args[2]
enableCrossPartitionQuery: true ).toBe(DOCDB_G_DOT_V_QUERY);
}); });
it("should submit g.V() as docdb query with proper parameters", () => {
expect(
(graphExplorerInstance.props.documentClientUtility.queryDocuments as sinon.SinonSpy).getCall(0).args[0]
).toEqual("databaseId");
expect(
(graphExplorerInstance.props.documentClientUtility.queryDocuments as sinon.SinonSpy).getCall(0).args[1]
).toEqual("collectionId");
expect(
(graphExplorerInstance.props.documentClientUtility.queryDocuments as sinon.SinonSpy).getCall(0).args[3]
).toEqual({ maxItemCount: GraphExplorer.ROOT_LIST_PAGE_SIZE, enableCrossPartitionQuery: true });
}); });
it("should call backend thrice (user query, fetch outE, then fetch inE)", () => { it("should call backend thrice (user query, fetch outE, then fetch inE)", () => {

View File

@@ -8,7 +8,7 @@ import * as D3ForceGraph from "./D3ForceGraph";
import { GraphVizComponentProps } from "./GraphVizComponent"; import { GraphVizComponentProps } from "./GraphVizComponent";
import * as GraphData from "./GraphData"; import * as GraphData from "./GraphData";
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; import { NotificationConsoleUtils } from "../../../Utils/NotificationConsoleUtils";
import { GraphUtil } from "./GraphUtil"; import { GraphUtil } from "./GraphUtil";
import * as DataModels from "../../../Contracts/DataModels"; import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
@@ -28,7 +28,7 @@ import * as Constants from "../../../Common/Constants";
import { InputProperty } from "../../../Contracts/ViewModels"; import { InputProperty } from "../../../Contracts/ViewModels";
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos"; import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif"; import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
import { queryDocuments, queryDocumentsPage } from "../../../Common/DocumentClientUtilityBase"; import DocumentClientUtilityBase from "../../../Common/DocumentClientUtilityBase";
export interface GraphAccessor { export interface GraphAccessor {
applyFilter: () => void; applyFilter: () => void;
@@ -47,6 +47,7 @@ export interface GraphExplorerProps {
onIsValidQueryChange: (isValidQuery: boolean) => void; onIsValidQueryChange: (isValidQuery: boolean) => void;
collectionPartitionKeyProperty: string; collectionPartitionKeyProperty: string;
documentClientUtility: DocumentClientUtilityBase;
collectionRid: string; collectionRid: string;
collectionSelfLink: string; collectionSelfLink: string;
graphBackendEndpoint: string; graphBackendEndpoint: string;
@@ -696,6 +697,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
* @param cmd * @param cmd
*/ */
public submitToBackend(cmd: string): Q.Promise<GremlinClient.GremlinRequestResult> { public submitToBackend(cmd: string): Q.Promise<GremlinClient.GremlinRequestResult> {
console.log("submit:", cmd);
const id = GraphExplorer.reportToConsole(ConsoleDataType.InProgress, `Executing: ${cmd}`); const id = GraphExplorer.reportToConsole(ConsoleDataType.InProgress, `Executing: ${cmd}`);
this.setExecuteCounter(this.executeCounter + 1); this.setExecuteCounter(this.executeCounter + 1);
@@ -728,12 +730,14 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
*/ */
public executeNonPagedDocDbQuery(query: string): Q.Promise<DataModels.DocumentId[]> { public executeNonPagedDocDbQuery(query: string): Q.Promise<DataModels.DocumentId[]> {
// TODO maxItemCount: this reduces throttling, but won't cap the # of results // TODO maxItemCount: this reduces throttling, but won't cap the # of results
return queryDocuments(this.props.databaseId, this.props.collectionId, query, { return this.props.documentClientUtility
.queryDocuments(this.props.databaseId, this.props.collectionId, query, {
maxItemCount: GraphExplorer.PAGE_ALL, maxItemCount: GraphExplorer.PAGE_ALL,
enableCrossPartitionQuery: enableCrossPartitionQuery:
StorageUtility.LocalStorageUtility.getEntryString(StorageUtility.StorageKey.IsCrossPartitionQueryEnabled) === StorageUtility.LocalStorageUtility.getEntryString(StorageUtility.StorageKey.IsCrossPartitionQueryEnabled) ===
"true" "true"
}).then( })
.then(
(iterator: QueryIterator<ItemDefinition & Resource>) => { (iterator: QueryIterator<ItemDefinition & Resource>) => {
return iterator.fetchNext().then(response => response.resources); return iterator.fetchNext().then(response => response.resources);
}, },
@@ -1371,7 +1375,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
if (collectionPartitionKeyProperty && d.hasOwnProperty(collectionPartitionKeyProperty)) { if (collectionPartitionKeyProperty && d.hasOwnProperty(collectionPartitionKeyProperty)) {
let pk = (d as any)[collectionPartitionKeyProperty]; let pk = (d as any)[collectionPartitionKeyProperty];
if (typeof pk !== "string" && typeof pk !== "number" && typeof pk !== "boolean") { if (typeof pk !== "string") {
if (Array.isArray(pk) && pk.length > 0) { if (Array.isArray(pk) && pk.length > 0) {
// pk is [{ id: 'id', _value: 'value' }] // pk is [{ id: 'id', _value: 'value' }]
pk = pk[0]["_value"]; pk = pk[0]["_value"];
@@ -1728,9 +1732,11 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
query = `select root.id, root.${this.props.collectionPartitionKeyProperty} from root where IS_DEFINED(root._isEdge) = false order by root._ts asc`; query = `select root.id, root.${this.props.collectionPartitionKeyProperty} from root where IS_DEFINED(root._isEdge) = false order by root._ts asc`;
} }
return queryDocuments(this.props.databaseId, this.props.collectionId, query, { return this.props.documentClientUtility
.queryDocuments(this.props.databaseId, this.props.collectionId, query, {
maxItemCount: GraphExplorer.ROOT_LIST_PAGE_SIZE, maxItemCount: GraphExplorer.ROOT_LIST_PAGE_SIZE,
enableCrossPartitionQuery: LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true" enableCrossPartitionQuery:
LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true"
}) })
.then( .then(
(iterator: QueryIterator<ItemDefinition & Resource>) => { (iterator: QueryIterator<ItemDefinition & Resource>) => {
@@ -1760,7 +1766,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
.currentDocDBQueryInfo.index + GraphExplorer.ROOT_LIST_PAGE_SIZE})`; .currentDocDBQueryInfo.index + GraphExplorer.ROOT_LIST_PAGE_SIZE})`;
const id = GraphExplorer.reportToConsole(ConsoleDataType.InProgress, `Executing: ${queryInfoStr}`); const id = GraphExplorer.reportToConsole(ConsoleDataType.InProgress, `Executing: ${queryInfoStr}`);
return queryDocumentsPage( return this.props.documentClientUtility
.queryDocumentsPage(
this.props.collectionRid, this.props.collectionRid,
this.currentDocDBQueryInfo.iterator, this.currentDocDBQueryInfo.iterator,
this.currentDocDBQueryInfo.index, this.currentDocDBQueryInfo.index,

View File

@@ -3,6 +3,7 @@ import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import { GraphConfig } from "../../Tabs/GraphTab"; import { GraphConfig } from "../../Tabs/GraphTab";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { GraphExplorer, GraphAccessor } from "./GraphExplorer"; import { GraphExplorer, GraphAccessor } from "./GraphExplorer";
import DocumentClientUtilityBase from "../../../Common/DocumentClientUtilityBase";
interface Parameter { interface Parameter {
onIsNewVertexDisabledChange: (isEnabled: boolean) => void; onIsNewVertexDisabledChange: (isEnabled: boolean) => void;
@@ -17,6 +18,7 @@ interface Parameter {
graphConfig?: GraphConfig; graphConfig?: GraphConfig;
collectionPartitionKeyProperty: string; collectionPartitionKeyProperty: string;
documentClientUtility: DocumentClientUtilityBase;
collectionRid: string; collectionRid: string;
collectionSelfLink: string; collectionSelfLink: string;
graphBackendEndpoint: string; graphBackendEndpoint: string;
@@ -49,6 +51,7 @@ export class GraphExplorerAdapter implements ReactAdapter {
onIsGraphDisplayed={this.params.onIsGraphDisplayed} onIsGraphDisplayed={this.params.onIsGraphDisplayed}
onResetDefaultGraphConfigValues={this.params.onResetDefaultGraphConfigValues} onResetDefaultGraphConfigValues={this.params.onResetDefaultGraphConfigValues}
collectionPartitionKeyProperty={this.params.collectionPartitionKeyProperty} collectionPartitionKeyProperty={this.params.collectionPartitionKeyProperty}
documentClientUtility={this.params.documentClientUtility}
collectionRid={this.params.collectionRid} collectionRid={this.params.collectionRid}
collectionSelfLink={this.params.collectionSelfLink} collectionSelfLink={this.params.collectionSelfLink}
graphBackendEndpoint={this.params.graphBackendEndpoint} graphBackendEndpoint={this.params.graphBackendEndpoint}

View File

@@ -1,6 +1,6 @@
import * as sinon from "sinon"; import * as sinon from "sinon";
import { GremlinClient, GremlinClientParameters } from "./GremlinClient"; import { GremlinClient, GremlinClientParameters } from "./GremlinClient";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; import { NotificationConsoleUtils } from "../../../Utils/NotificationConsoleUtils";
import * as Logger from "../../../Common/Logger"; import * as Logger from "../../../Common/Logger";
describe("Gremlin Client", () => { describe("Gremlin Client", () => {

View File

@@ -4,7 +4,7 @@
import * as Q from "q"; import * as Q from "q";
import { GremlinSimpleClient, Result } from "./GremlinSimpleClient"; import { GremlinSimpleClient, Result } from "./GremlinSimpleClient";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; import { NotificationConsoleUtils } from "../../../Utils/NotificationConsoleUtils";
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
import { HashMap } from "../../../Common/HashMap"; import { HashMap } from "../../../Common/HashMap";
import * as Logger from "../../../Common/Logger"; import * as Logger from "../../../Common/Logger";

View File

@@ -12,12 +12,11 @@ import { CommandBar, ICommandBarItemProps } from "office-ui-fabric-react/lib/Com
import { StyleConstants } from "../../../Common/Constants"; import { StyleConstants } from "../../../Common/Constants";
import { CommandBarUtil } from "./CommandBarUtil"; import { CommandBarUtil } from "./CommandBarUtil";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
export class CommandBarComponentAdapter implements ReactAdapter { export class CommandBarComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>; public parameters: ko.Observable<number>;
public container: Explorer; public container: Explorer;
private tabsButtons: CommandButtonComponentProps[]; private tabsButtons: ViewModels.NavbarButtonConfig[];
private isNotebookTabActive: ko.Computed<boolean>; private isNotebookTabActive: ko.Computed<boolean>;
constructor(container: Explorer) { constructor(container: Explorer) {
@@ -45,15 +44,14 @@ export class CommandBarComponentAdapter implements ReactAdapter {
container.isHostedDataExplorerEnabled, container.isHostedDataExplorerEnabled,
container.isSynapseLinkUpdating, container.isSynapseLinkUpdating,
container.databaseAccount, container.databaseAccount,
this.isNotebookTabActive, this.isNotebookTabActive
container.isServerlessEnabled
]; ];
ko.computed(() => ko.toJSON(toWatch)).subscribe(() => this.triggerRender()); ko.computed(() => ko.toJSON(toWatch)).subscribe(() => this.triggerRender());
this.parameters = ko.observable(Date.now()); this.parameters = ko.observable(Date.now());
} }
public onUpdateTabsButtons(buttons: CommandButtonComponentProps[]): void { public onUpdateTabsButtons(buttons: ViewModels.NavbarButtonConfig[]): void {
this.tabsButtons = buttons; this.tabsButtons = buttons;
this.triggerRender(); this.triggerRender();
} }

View File

@@ -7,47 +7,6 @@ import Explorer from "../../Explorer";
describe("CommandBarComponentButtonFactory tests", () => { describe("CommandBarComponentButtonFactory tests", () => {
let mockExplorer: Explorer; let mockExplorer: Explorer;
describe("Enable Azure Synapse Link Button", () => {
const enableAzureSynapseLinkBtnLabel = "Enable Azure Synapse Link (Preview)";
beforeAll(() => {
mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(false);
mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isNotebookEnabled = ko.observable(false);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
mockExplorer.isRunningOnNationalCloud = () => false;
});
it("Account is not serverless - button should be visible", () => {
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const enableAzureSynapseLinkBtn = buttons.find(
button => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
);
expect(enableAzureSynapseLinkBtn).toBeDefined();
});
it("Account is serverless - button should be hidden", () => {
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => true);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const enableAzureSynapseLinkBtn = buttons.find(
button => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
);
expect(enableAzureSynapseLinkBtn).toBeUndefined();
});
});
describe("Enable notebook button", () => { describe("Enable notebook button", () => {
const enableNotebookBtnLabel = "Enable Notebooks (Preview)"; const enableNotebookBtnLabel = "Enable Notebooks (Preview)";
@@ -64,7 +23,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false); mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true); mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
}); });
it("Notebooks is already enabled - button should be hidden", () => { it("Notebooks is already enabled - button should be hidden", () => {
@@ -128,7 +86,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false); mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true); mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
}); });
beforeEach(() => { beforeEach(() => {
@@ -210,7 +167,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false); mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true); mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
}); });
beforeEach(() => { beforeEach(() => {
@@ -298,7 +254,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false); mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.notebookManager = new NotebookManager(); mockExplorer.notebookManager = new NotebookManager();
mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined); mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined);
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
}); });
beforeEach(() => { beforeEach(() => {
@@ -350,7 +305,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true); mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isResourceTokenCollectionNodeSelected = ko.computed(() => true); mockExplorer.isResourceTokenCollectionNodeSelected = ko.computed(() => true);
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
}); });
it("should only show New SQL Query and Open Query buttons", () => { it("should only show New SQL Query and Open Query buttons", () => {

View File

@@ -24,20 +24,19 @@ import NewNotebookIcon from "../../../../images/notebook/Notebook-new.svg";
import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-workspace.svg"; import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-workspace.svg";
import GitHubIcon from "../../../../images/github.svg"; import GitHubIcon from "../../../../images/github.svg";
import SynapseIcon from "../../../../images/synapse-link.svg"; import SynapseIcon from "../../../../images/synapse-link.svg";
import { configContext, Platform } from "../../../ConfigContext"; import { config, Platform } from "../../../Config";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
export class CommandBarComponentButtonFactory { export class CommandBarComponentButtonFactory {
private static counter: number = 0; private static counter: number = 0;
public static createStaticCommandBarButtons(container: Explorer): CommandButtonComponentProps[] { public static createStaticCommandBarButtons(container: Explorer): ViewModels.NavbarButtonConfig[] {
if (container.isAuthWithResourceToken()) { if (container.isAuthWithResourceToken()) {
return CommandBarComponentButtonFactory.createStaticCommandBarButtonsForResourceToken(container); return CommandBarComponentButtonFactory.createStaticCommandBarButtonsForResourceToken(container);
} }
const newCollectionBtn = CommandBarComponentButtonFactory.createNewCollectionGroup(container); const newCollectionBtn = CommandBarComponentButtonFactory.createNewCollectionGroup(container);
const buttons: CommandButtonComponentProps[] = [newCollectionBtn]; const buttons: ViewModels.NavbarButtonConfig[] = [newCollectionBtn];
const addSynapseLink = CommandBarComponentButtonFactory.createOpenSynapseLinkDialogButton(container); const addSynapseLink = CommandBarComponentButtonFactory.createOpenSynapseLinkDialogButton(container);
if (addSynapseLink) { if (addSynapseLink) {
@@ -113,7 +112,7 @@ export class CommandBarComponentButtonFactory {
if (CommandBarComponentButtonFactory.areScriptsSupported(container)) { if (CommandBarComponentButtonFactory.areScriptsSupported(container)) {
const label = "New Stored Procedure"; const label = "New Stored Procedure";
const newStoredProcedureBtn: CommandButtonComponentProps = { const newStoredProcedureBtn: ViewModels.NavbarButtonConfig = {
iconSrc: AddStoredProcedureIcon, iconSrc: AddStoredProcedureIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
@@ -134,12 +133,12 @@ export class CommandBarComponentButtonFactory {
return buttons; return buttons;
} }
public static createContextCommandBarButtons(container: Explorer): CommandButtonComponentProps[] { public static createContextCommandBarButtons(container: Explorer): ViewModels.NavbarButtonConfig[] {
const buttons: CommandButtonComponentProps[] = []; const buttons: ViewModels.NavbarButtonConfig[] = [];
if (!container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB()) { if (!container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB()) {
const label = "New Shell"; const label = "New Shell";
const newMongoShellBtn: CommandButtonComponentProps = { const newMongoShellBtn: ViewModels.NavbarButtonConfig = {
iconSrc: HostedTerminalIcon, iconSrc: HostedTerminalIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
@@ -157,15 +156,15 @@ export class CommandBarComponentButtonFactory {
return buttons; return buttons;
} }
public static createControlCommandBarButtons(container: Explorer): CommandButtonComponentProps[] { public static createControlCommandBarButtons(container: Explorer): ViewModels.NavbarButtonConfig[] {
const buttons: CommandButtonComponentProps[] = []; const buttons: ViewModels.NavbarButtonConfig[] = [];
if (window.dataExplorerPlatform === PlatformType.Hosted) { if (window.dataExplorerPlatform === PlatformType.Hosted) {
return buttons; return buttons;
} }
if (!container.isPreferredApiCassandra()) { if (!container.isPreferredApiCassandra()) {
const label = "Settings"; const label = "Settings";
const settingsPaneButton: CommandButtonComponentProps = { const settingsPaneButton: ViewModels.NavbarButtonConfig = {
iconSrc: SettingsIcon, iconSrc: SettingsIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => container.settingsPane.open(), onCommandClick: () => container.settingsPane.open(),
@@ -180,7 +179,7 @@ export class CommandBarComponentButtonFactory {
if (container.isHostedDataExplorerEnabled()) { if (container.isHostedDataExplorerEnabled()) {
const label = "Open Full Screen"; const label = "Open Full Screen";
const fullScreenButton: CommandButtonComponentProps = { const fullScreenButton: ViewModels.NavbarButtonConfig = {
iconSrc: OpenInTabIcon, iconSrc: OpenInTabIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => container.generateSharedAccessData(), onCommandClick: () => container.generateSharedAccessData(),
@@ -196,7 +195,7 @@ export class CommandBarComponentButtonFactory {
if (!container.hasOwnProperty("isEmulator") || !container.isEmulator) { if (!container.hasOwnProperty("isEmulator") || !container.isEmulator) {
const label = "Feedback"; const label = "Feedback";
const feedbackButtonOptions: CommandButtonComponentProps = { const feedbackButtonOptions: ViewModels.NavbarButtonConfig = {
iconSrc: FeedbackIcon, iconSrc: FeedbackIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => container.provideFeedbackEmail(), onCommandClick: () => container.provideFeedbackEmail(),
@@ -212,7 +211,7 @@ export class CommandBarComponentButtonFactory {
return buttons; return buttons;
} }
public static createDivider(): CommandButtonComponentProps { public static createDivider(): ViewModels.NavbarButtonConfig {
const label = `divider${CommandBarComponentButtonFactory.counter++}`; const label = `divider${CommandBarComponentButtonFactory.counter++}`;
return { return {
isDivider: true, isDivider: true,
@@ -229,7 +228,7 @@ export class CommandBarComponentButtonFactory {
return container.isPreferredApiDocumentDB() || container.isPreferredApiGraph(); return container.isPreferredApiDocumentDB() || container.isPreferredApiGraph();
} }
private static createNewCollectionGroup(container: Explorer): CommandButtonComponentProps { private static createNewCollectionGroup(container: Explorer): ViewModels.NavbarButtonConfig {
const label = container.addCollectionText(); const label = container.addCollectionText();
return { return {
iconSrc: AddCollectionIcon, iconSrc: AddCollectionIcon,
@@ -242,15 +241,10 @@ export class CommandBarComponentButtonFactory {
}; };
} }
private static createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonComponentProps { private static createOpenSynapseLinkDialogButton(container: Explorer): ViewModels.NavbarButtonConfig {
if (configContext.platform === Platform.Emulator) { if (config.platform === Platform.Emulator) {
return null; return null;
} }
if (container.isServerlessEnabled()) {
return null;
}
if ( if (
container.databaseAccount && container.databaseAccount &&
container.databaseAccount() && container.databaseAccount() &&
@@ -282,7 +276,7 @@ export class CommandBarComponentButtonFactory {
}; };
} }
private static createNewDatabase(container: Explorer): CommandButtonComponentProps { private static createNewDatabase(container: Explorer): ViewModels.NavbarButtonConfig {
const label = container.addDatabaseText(); const label = container.addDatabaseText();
return { return {
iconSrc: AddDatabaseIcon, iconSrc: AddDatabaseIcon,
@@ -297,7 +291,7 @@ export class CommandBarComponentButtonFactory {
}; };
} }
private static createNewSQLQueryButton(container: Explorer): CommandButtonComponentProps { private static createNewSQLQueryButton(container: Explorer): ViewModels.NavbarButtonConfig {
if (container.isPreferredApiDocumentDB() || container.isPreferredApiGraph()) { if (container.isPreferredApiDocumentDB() || container.isPreferredApiGraph()) {
const label = "New SQL Query"; const label = "New SQL Query";
return { return {
@@ -331,15 +325,15 @@ export class CommandBarComponentButtonFactory {
return null; return null;
} }
public static createScriptCommandButtons(container: Explorer): CommandButtonComponentProps[] { public static createScriptCommandButtons(container: Explorer): ViewModels.NavbarButtonConfig[] {
const buttons: CommandButtonComponentProps[] = []; const buttons: ViewModels.NavbarButtonConfig[] = [];
const shouldEnableScriptsCommands: boolean = const shouldEnableScriptsCommands: boolean =
!container.isDatabaseNodeOrNoneSelected() && CommandBarComponentButtonFactory.areScriptsSupported(container); !container.isDatabaseNodeOrNoneSelected() && CommandBarComponentButtonFactory.areScriptsSupported(container);
if (shouldEnableScriptsCommands) { if (shouldEnableScriptsCommands) {
const label = "New Stored Procedure"; const label = "New Stored Procedure";
const newStoredProcedureBtn: CommandButtonComponentProps = { const newStoredProcedureBtn: ViewModels.NavbarButtonConfig = {
iconSrc: AddStoredProcedureIcon, iconSrc: AddStoredProcedureIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
@@ -356,7 +350,7 @@ export class CommandBarComponentButtonFactory {
if (shouldEnableScriptsCommands) { if (shouldEnableScriptsCommands) {
const label = "New UDF"; const label = "New UDF";
const newUserDefinedFunctionBtn: CommandButtonComponentProps = { const newUserDefinedFunctionBtn: ViewModels.NavbarButtonConfig = {
iconSrc: AddUdfIcon, iconSrc: AddUdfIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
@@ -373,7 +367,7 @@ export class CommandBarComponentButtonFactory {
if (shouldEnableScriptsCommands) { if (shouldEnableScriptsCommands) {
const label = "New Trigger"; const label = "New Trigger";
const newTriggerBtn: CommandButtonComponentProps = { const newTriggerBtn: ViewModels.NavbarButtonConfig = {
iconSrc: AddTriggerIcon, iconSrc: AddTriggerIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () => {
@@ -391,7 +385,7 @@ export class CommandBarComponentButtonFactory {
return buttons; return buttons;
} }
private static createScaleAndSettingsButton(container: Explorer): CommandButtonComponentProps { private static createScaleAndSettingsButton(container: Explorer): ViewModels.NavbarButtonConfig {
let isShared = false; let isShared = false;
if (container.isDatabaseNodeSelected()) { if (container.isDatabaseNodeSelected()) {
isShared = container.findSelectedDatabase().isDatabaseShared(); isShared = container.findSelectedDatabase().isDatabaseShared();
@@ -416,7 +410,7 @@ export class CommandBarComponentButtonFactory {
}; };
} }
private static createNewNotebookButton(container: Explorer): CommandButtonComponentProps { private static createNewNotebookButton(container: Explorer): ViewModels.NavbarButtonConfig {
const label = "New Notebook"; const label = "New Notebook";
return { return {
iconSrc: NewNotebookIcon, iconSrc: NewNotebookIcon,
@@ -429,7 +423,7 @@ export class CommandBarComponentButtonFactory {
}; };
} }
private static createuploadNotebookButton(container: Explorer): CommandButtonComponentProps { private static createuploadNotebookButton(container: Explorer): ViewModels.NavbarButtonConfig {
const label = "Upload to Notebook Server"; const label = "Upload to Notebook Server";
return { return {
iconSrc: NewNotebookIcon, iconSrc: NewNotebookIcon,
@@ -442,7 +436,7 @@ export class CommandBarComponentButtonFactory {
}; };
} }
private static createOpenQueryButton(container: Explorer): CommandButtonComponentProps { private static createOpenQueryButton(container: Explorer): ViewModels.NavbarButtonConfig {
const label = "Open Query"; const label = "Open Query";
return { return {
iconSrc: BrowseQueriesIcon, iconSrc: BrowseQueriesIcon,
@@ -455,7 +449,7 @@ export class CommandBarComponentButtonFactory {
}; };
} }
private static createOpenQueryFromDiskButton(container: Explorer): CommandButtonComponentProps { private static createOpenQueryFromDiskButton(container: Explorer): ViewModels.NavbarButtonConfig {
const label = "Open Query From Disk"; const label = "Open Query From Disk";
return { return {
iconSrc: OpenQueryFromDiskIcon, iconSrc: OpenQueryFromDiskIcon,
@@ -468,8 +462,8 @@ export class CommandBarComponentButtonFactory {
}; };
} }
private static createEnableNotebooksButton(container: Explorer): CommandButtonComponentProps { private static createEnableNotebooksButton(container: Explorer): ViewModels.NavbarButtonConfig {
if (configContext.platform === Platform.Emulator) { if (config.platform === Platform.Emulator) {
return null; return null;
} }
const label = "Enable Notebooks (Preview)"; const label = "Enable Notebooks (Preview)";
@@ -489,7 +483,7 @@ export class CommandBarComponentButtonFactory {
}; };
} }
private static createOpenTerminalButton(container: Explorer): CommandButtonComponentProps { private static createOpenTerminalButton(container: Explorer): ViewModels.NavbarButtonConfig {
const label = "Open Terminal"; const label = "Open Terminal";
return { return {
iconSrc: CosmosTerminalIcon, iconSrc: CosmosTerminalIcon,
@@ -502,7 +496,7 @@ export class CommandBarComponentButtonFactory {
}; };
} }
private static createOpenMongoTerminalButton(container: Explorer): CommandButtonComponentProps { private static createOpenMongoTerminalButton(container: Explorer): ViewModels.NavbarButtonConfig {
const label = "Open Mongo Shell"; const label = "Open Mongo Shell";
const tooltip = const tooltip =
"This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."; "This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
@@ -528,7 +522,7 @@ export class CommandBarComponentButtonFactory {
}; };
} }
private static createOpenCassandraTerminalButton(container: Explorer): CommandButtonComponentProps { private static createOpenCassandraTerminalButton(container: Explorer): ViewModels.NavbarButtonConfig {
const label = "Open Cassandra Shell"; const label = "Open Cassandra Shell";
const tooltip = const tooltip =
"This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."; "This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
@@ -554,7 +548,7 @@ export class CommandBarComponentButtonFactory {
}; };
} }
private static createNotebookWorkspaceResetButton(container: Explorer): CommandButtonComponentProps { private static createNotebookWorkspaceResetButton(container: Explorer): ViewModels.NavbarButtonConfig {
const label = "Reset Workspace"; const label = "Reset Workspace";
return { return {
iconSrc: ResetWorkspaceIcon, iconSrc: ResetWorkspaceIcon,
@@ -567,7 +561,7 @@ export class CommandBarComponentButtonFactory {
}; };
} }
private static createManageGitHubAccountButton(container: Explorer): CommandButtonComponentProps { private static createManageGitHubAccountButton(container: Explorer): ViewModels.NavbarButtonConfig {
let connectedToGitHub: boolean = container.notebookManager?.gitHubOAuthService.isLoggedIn(); let connectedToGitHub: boolean = container.notebookManager?.gitHubOAuthService.isLoggedIn();
const label = connectedToGitHub ? "Manage GitHub settings" : "Connect to GitHub"; const label = connectedToGitHub ? "Manage GitHub settings" : "Connect to GitHub";
return { return {
@@ -590,7 +584,7 @@ export class CommandBarComponentButtonFactory {
}; };
} }
private static createStaticCommandBarButtonsForResourceToken(container: Explorer): CommandButtonComponentProps[] { private static createStaticCommandBarButtonsForResourceToken(container: Explorer): ViewModels.NavbarButtonConfig[] {
const newSqlQueryBtn = CommandBarComponentButtonFactory.createNewSQLQueryButton(container); const newSqlQueryBtn = CommandBarComponentButtonFactory.createNewSQLQueryButton(container);
const openQueryBtn = CommandBarComponentButtonFactory.createOpenQueryButton(container); const openQueryBtn = CommandBarComponentButtonFactory.createOpenQueryButton(container);

View File

@@ -1,10 +1,9 @@
import { CommandBarUtil } from "./CommandBarUtil"; import { CommandBarUtil } from "./CommandBarUtil";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar"; import { ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
describe("CommandBarUtil tests", () => { describe("CommandBarUtil tests", () => {
const createButton = (): CommandButtonComponentProps => { const createButton = (): ViewModels.NavbarButtonConfig => {
return { return {
iconSrc: "icon", iconSrc: "icon",
iconAlt: "label", iconAlt: "label",
@@ -55,7 +54,7 @@ describe("CommandBarUtil tests", () => {
}); });
it("should create buttons with unique keys", () => { it("should create buttons with unique keys", () => {
const btns: CommandButtonComponentProps[] = []; const btns: ViewModels.NavbarButtonConfig[] = [];
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
btns.push(createButton()); btns.push(createButton());
} }

View File

@@ -1,11 +1,12 @@
import _ from "underscore"; import _ from "underscore";
import * as React from "react"; import * as React from "react";
import * as ViewModels from "../../../Contracts/ViewModels";
import { Observable } from "knockout"; import { Observable } from "knockout";
import { IconType } from "office-ui-fabric-react/lib/Icon"; import { IconType } from "office-ui-fabric-react/lib/Icon";
import { IComponentAsProps } from "office-ui-fabric-react/lib/Utilities"; import { IComponentAsProps } from "office-ui-fabric-react/lib/Utilities";
import { StyleConstants } from "../../../Common/Constants"; import { KeyCodes, StyleConstants } from "../../../Common/Constants";
import { ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar"; import { ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar";
import { Dropdown, IDropdownStyles, IDropdownOption } from "office-ui-fabric-react/lib/Dropdown"; import { Dropdown, DropdownMenuItemType, IDropdownStyles, IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import ChevronDownIcon from "../../../../images/Chevron_down.svg"; import ChevronDownIcon from "../../../../images/Chevron_down.svg";
import { ArcadiaMenuPicker } from "../../Controls/Arcadia/ArcadiaMenuPicker"; import { ArcadiaMenuPicker } from "../../Controls/Arcadia/ArcadiaMenuPicker";
@@ -20,13 +21,13 @@ export class CommandBarUtil {
* Convert our NavbarButtonConfig to UI Fabric buttons * Convert our NavbarButtonConfig to UI Fabric buttons
* @param btns * @param btns
*/ */
public static convertButton(btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] { public static convertButton(btns: ViewModels.NavbarButtonConfig[], backgroundColor: string): ICommandBarItemProps[] {
const buttonHeightPx = StyleConstants.CommandBarButtonHeight; const buttonHeightPx = StyleConstants.CommandBarButtonHeight;
return btns return btns
.filter(btn => btn) .filter(btn => btn)
.map( .map(
(btn: CommandButtonComponentProps, index: number): ICommandBarItemProps => { (btn: ViewModels.NavbarButtonConfig, index: number): ICommandBarItemProps => {
if (btn.isDivider) { if (btn.isDivider) {
return CommandBarUtil.createDivider(btn.commandButtonLabel); return CommandBarUtil.createDivider(btn.commandButtonLabel);
} }

View File

@@ -3,19 +3,17 @@
*/ */
import * as React from "react"; import * as React from "react";
import { import * as ViewModels from "../../../Contracts/ViewModels";
CommandButtonComponent, import { CommandButtonComponent } from "../../Controls/CommandButton/CommandButtonComponent";
CommandButtonComponentProps
} from "../../Controls/CommandButton/CommandButtonComponent";
export interface ControlBarComponentProps { export interface ControlBarComponentProps {
buttons: CommandButtonComponentProps[]; buttons: ViewModels.NavbarButtonConfig[];
} }
export class ControlBarComponent extends React.Component<ControlBarComponentProps> { export class ControlBarComponent extends React.Component<ControlBarComponentProps> {
private static renderButtons(commandButtonOptions: CommandButtonComponentProps[]): JSX.Element[] { private static renderButtons(commandButtonOptions: ViewModels.NavbarButtonConfig[]): JSX.Element[] {
return commandButtonOptions.map( return commandButtonOptions.map(
(btn: CommandButtonComponentProps, index: number): JSX.Element => { (btn: ViewModels.NavbarButtonConfig, index: number): JSX.Element => {
// Remove label // Remove label
btn.commandButtonLabel = null; btn.commandButtonLabel = null;
return CommandButtonComponent.renderButton(btn, `${index}`); return CommandButtonComponent.renderButton(btn, `${index}`);

View File

@@ -8,12 +8,12 @@ import * as ko from "knockout";
import * as React from "react"; import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler"; import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import { ControlBarComponent } from "./ControlBarComponent"; import { ControlBarComponent } from "./ControlBarComponent";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import * as ViewModels from "../../../Contracts/ViewModels";
export class ControlBarComponentAdapter implements ReactAdapter { export class ControlBarComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>; public parameters: ko.Observable<number>;
constructor(private buttons: ko.ObservableArray<CommandButtonComponentProps>) { constructor(private buttons: ko.ObservableArray<ViewModels.NavbarButtonConfig>) {
this.buttons.subscribe(() => this.forceRender()); this.buttons.subscribe(() => this.forceRender());
this.parameters = ko.observable<number>(Date.now()); this.parameters = ko.observable<number>(Date.now());
} }

View File

@@ -8,7 +8,6 @@ import { actions, createContentRef, createKernelRef, selectors } from "@nteract/
import VirtualCommandBarComponent from "./VirtualCommandBarComponent"; import VirtualCommandBarComponent from "./VirtualCommandBarComponent";
import { NotebookContentItem } from "../NotebookContentItem"; import { NotebookContentItem } from "../NotebookContentItem";
import { NotebookComponentBootstrapper } from "./NotebookComponentBootstrapper"; import { NotebookComponentBootstrapper } from "./NotebookComponentBootstrapper";
import { CdbAppState } from "./types";
export interface NotebookComponentAdapterOptions { export interface NotebookComponentAdapterOptions {
contentItem: NotebookContentItem; contentItem: NotebookContentItem;
@@ -19,7 +18,6 @@ export interface NotebookComponentAdapterOptions {
export class NotebookComponentAdapter extends NotebookComponentBootstrapper implements ReactAdapter { export class NotebookComponentAdapter extends NotebookComponentBootstrapper implements ReactAdapter {
private onUpdateKernelInfo: () => void; private onUpdateKernelInfo: () => void;
public getNotebookParentElement: () => HTMLElement;
public parameters: any; public parameters: any;
constructor(options: NotebookComponentAdapterOptions) { constructor(options: NotebookComponentAdapterOptions) {
@@ -46,11 +44,6 @@ export class NotebookComponentAdapter extends NotebookComponentBootstrapper impl
}) })
); );
} }
this.getNotebookParentElement = () => {
const cdbAppState = this.getStore().getState() as CdbAppState;
return cdbAppState.cdb.currentNotebookParentElements.get(this.contentRef);
};
} }
protected renderExtraComponent = (): JSX.Element => { protected renderExtraComponent = (): JSX.Element => {

View File

@@ -17,7 +17,7 @@ import {
} from "@nteract/core"; } from "@nteract/core";
import * as Immutable from "immutable"; import * as Immutable from "immutable";
import { Provider } from "react-redux"; import { Provider } from "react-redux";
import { CellType, CellId, ImmutableNotebook } from "@nteract/commutable"; import { CellType, CellId, toJS } from "@nteract/commutable";
import { Store, AnyAction } from "redux"; import { Store, AnyAction } from "redux";
import "./NotebookComponent.less"; import "./NotebookComponent.less";
@@ -71,14 +71,14 @@ export class NotebookComponentBootstrapper {
); );
} }
public getContent(): { name: string; content: string | ImmutableNotebook } { public getContent(): { name: string; content: string } {
const record = this.getStore() const record = this.getStore()
.getState() .getState()
.core.entities.contents.byRef.get(this.contentRef); .core.entities.contents.byRef.get(this.contentRef);
let content: string | ImmutableNotebook; let content: string;
switch (record.model.type) { switch (record.model.type) {
case "notebook": case "notebook":
content = record.model.notebook; content = JSON.stringify(toJS(record.model.notebook));
break; break;
case "file": case "file":
content = record.model.text; content = record.model.text;
@@ -98,7 +98,7 @@ export class NotebookComponentBootstrapper {
actions.fetchContentFulfilled({ actions.fetchContentFulfilled({
filepath: undefined, filepath: undefined,
model: NotebookComponentBootstrapper.wrapModelIntoContent(name, undefined, content), model: NotebookComponentBootstrapper.wrapModelIntoContent(name, undefined, content),
kernelRef: undefined, // must be undefined or it will be auto-started by the epic kernelRef: createKernelRef(),
contentRef: this.contentRef contentRef: this.contentRef
}) })
); );

View File

@@ -84,22 +84,3 @@ export const traceNotebookTelemetry = (payload: {
payload payload
}; };
}; };
export const UPDATE_NOTEBOOK_PARENT_DOM_ELTS = "UPDATE_NOTEBOOK_PARENT_DOM_ELTS";
export interface UpdateNotebookParentDomEltAction {
type: "UPDATE_NOTEBOOK_PARENT_DOM_ELTS";
payload: {
contentRef: ContentRef;
parentElt: HTMLElement;
};
}
export const UpdateNotebookParentDomElt = (payload: {
contentRef: ContentRef;
parentElt: HTMLElement;
}): UpdateNotebookParentDomEltAction => {
return {
type: UPDATE_NOTEBOOK_PARENT_DOM_ELTS,
payload
};
};

View File

@@ -1,13 +1,13 @@
import * as Immutable from "immutable"; import * as Immutable from "immutable";
import { ActionsObservable, StateObservable } from "redux-observable"; import { ActionsObservable, StateObservable } from "redux-observable";
import { Subject, empty } from "rxjs"; import { Subject } from "rxjs";
import { toArray } from "rxjs/operators"; import { toArray } from "rxjs/operators";
import { makeNotebookRecord } from "@nteract/commutable"; import { makeNotebookRecord } from "@nteract/commutable";
import { actions, state } from "@nteract/core"; import { actions, state } from "@nteract/core";
import * as sinon from "sinon"; import * as sinon from "sinon";
import { CdbAppState, makeCdbRecord } from "./types"; import { CdbAppState, makeCdbRecord } from "./types";
import { launchWebSocketKernelEpic, autoStartKernelEpic } from "./epics"; import { launchWebSocketKernelEpic } from "./epics";
import { NotebookUtil } from "../NotebookUtil"; import { NotebookUtil } from "../NotebookUtil";
import { sessions } from "rx-jupyter"; import { sessions } from "rx-jupyter";
@@ -74,6 +74,11 @@ describe("Extract kernel from notebook", () => {
}); });
}); });
describe("launchWebSocketKernelEpic", () => {
const createSpy = sinon.spy(sessions, "create");
const contentRef = "fakeContentRef";
const kernelRef = "fake";
const initialState = { const initialState = {
app: state.makeAppRecord({ app: state.makeAppRecord({
host: state.makeJupyterHostRecord({ host: state.makeJupyterHostRecord({
@@ -110,12 +115,6 @@ const initialState = {
}) })
}; };
describe("launchWebSocketKernelEpic", () => {
const createSpy = sinon.spy(sessions, "create");
const contentRef = "fakeContentRef";
const kernelRef = "fake";
it("launches remote kernels", async () => { it("launches remote kernels", async () => {
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState); const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
@@ -491,55 +490,3 @@ describe("launchWebSocketKernelEpic", () => {
}); });
}); });
}); });
describe("autoStartKernelEpic", () => {
const contentRef = "fakeContentRef";
const kernelRef = "fake";
it("automatically starts kernel when content fetch is successful if kernelRef is defined", async () => {
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
const action$ = ActionsObservable.of(
actions.fetchContentFulfilled({
contentRef,
kernelRef,
filepath: "filepath",
model: {}
})
);
const responseActions = await autoStartKernelEpic(action$, state$)
.pipe(toArray())
.toPromise();
expect(responseActions).toMatchObject([
{
type: actions.RESTART_KERNEL,
payload: {
contentRef,
kernelRef,
outputHandling: "None"
}
}
]);
});
it("Don't start kernel when content fetch is successful if kernelRef is not defined", async () => {
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
const action$ = ActionsObservable.of(
actions.fetchContentFulfilled({
contentRef,
kernelRef: undefined,
filepath: "filepath",
model: {}
})
);
const responseActions = await autoStartKernelEpic(action$, state$)
.pipe(toArray())
.toPromise();
expect(responseActions).toMatchObject([]);
});
});

View File

@@ -1,4 +1,4 @@
import { EMPTY, merge, of, timer, concat, Subject, Subscriber, Observable, Observer } from "rxjs"; import { empty, merge, of, timer, concat, Subject, Subscriber, Observable, Observer } from "rxjs";
import { webSocket } from "rxjs/webSocket"; import { webSocket } from "rxjs/webSocket";
import { ActionsObservable, StateObservable } from "redux-observable"; import { ActionsObservable, StateObservable } from "redux-observable";
import { ofType } from "redux-observable"; import { ofType } from "redux-observable";
@@ -34,7 +34,7 @@ import { sessions, kernels } from "rx-jupyter";
import { RecordOf } from "immutable"; import { RecordOf } from "immutable";
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; import { NotificationConsoleUtils } from "../../../Utils/NotificationConsoleUtils";
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
import * as CdbActions from "./actions"; import * as CdbActions from "./actions";
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
@@ -77,7 +77,7 @@ const addInitialCodeCellEpic = (
// If it's not a notebook, we shouldn't be here // If it's not a notebook, we shouldn't be here
if (!model || model.type !== "notebook") { if (!model || model.type !== "notebook") {
return EMPTY; return empty();
} }
const cellOrder = selectors.notebook.cellOrder(model); const cellOrder = selectors.notebook.cellOrder(model);
@@ -90,40 +90,7 @@ const addInitialCodeCellEpic = (
); );
} }
return EMPTY; return empty();
})
);
};
/**
* Automatically start kernel if kernelRef is present.
* The kernel is normally lazy-started when a cell is being executed, but a running kernel is
* required for code completion to work.
* For notebook viewer, there is no kernel
* @param action$
* @param state$
*/
export const autoStartKernelEpic = (
action$: ActionsObservable<actions.FetchContentFulfilled>,
state$: StateObservable<AppState>
): Observable<{} | actions.CreateCellBelow> => {
return action$.pipe(
ofType(actions.FETCH_CONTENT_FULFILLED),
mergeMap(action => {
const state = state$.value;
const { contentRef, kernelRef } = action.payload;
if (!kernelRef) {
return EMPTY;
}
return of(
actions.restartKernel({
contentRef,
kernelRef,
outputHandling: "None"
})
);
}) })
); );
}; };
@@ -321,7 +288,7 @@ export const launchWebSocketKernelEpic = (
const state = state$.value; const state = state$.value;
const host = selectors.currentHost(state); const host = selectors.currentHost(state);
if (host.type !== "jupyter") { if (host.type !== "jupyter") {
return EMPTY; return empty();
} }
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host); const serverConfig: NotebookServiceConfig = selectors.serverConfig(host);
serverConfig.userPuid = getUserPuid(); serverConfig.userPuid = getUserPuid();
@@ -332,7 +299,7 @@ export const launchWebSocketKernelEpic = (
const content = selectors.content(state, { contentRef }); const content = selectors.content(state, { contentRef });
if (!content || content.type !== "notebook") { if (!content || content.type !== "notebook") {
return EMPTY; return empty();
} }
let kernelSpecToLaunch = kernelSpecName; let kernelSpecToLaunch = kernelSpecName;
@@ -546,26 +513,26 @@ const changeWebSocketKernelEpic = (
const state = state$.value; const state = state$.value;
const host = selectors.currentHost(state); const host = selectors.currentHost(state);
if (host.type !== "jupyter") { if (host.type !== "jupyter") {
return EMPTY; return empty();
} }
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host); const serverConfig: NotebookServiceConfig = selectors.serverConfig(host);
if (!oldKernelRef) { if (!oldKernelRef) {
return EMPTY; return empty();
} }
const oldKernel = selectors.kernel(state, { kernelRef: oldKernelRef }); const oldKernel = selectors.kernel(state, { kernelRef: oldKernelRef });
if (!oldKernel || oldKernel.type !== "websocket") { if (!oldKernel || oldKernel.type !== "websocket") {
return EMPTY; return empty();
} }
const { sessionId } = oldKernel; const { sessionId } = oldKernel;
if (!sessionId) { if (!sessionId) {
return EMPTY; return empty();
} }
const content = selectors.content(state, { contentRef }); const content = selectors.content(state, { contentRef });
if (!content || content.type !== "notebook") { if (!content || content.type !== "notebook") {
return EMPTY; return empty();
} }
const { const {
filepath, filepath,
@@ -626,7 +593,7 @@ const focusInitialCodeCellEpic = (
// If it's not a notebook, we shouldn't be here // If it's not a notebook, we shouldn't be here
if (!model || model.type !== "notebook") { if (!model || model.type !== "notebook") {
return EMPTY; return empty();
} }
const cellOrder = selectors.notebook.cellOrder(model); const cellOrder = selectors.notebook.cellOrder(model);
@@ -641,7 +608,7 @@ const focusInitialCodeCellEpic = (
); );
} }
return EMPTY; return empty();
}) })
); );
}; };
@@ -694,7 +661,7 @@ const notificationsToUserEpic = (
break; break;
} }
} }
return EMPTY; return empty();
}) })
); );
}; };
@@ -734,7 +701,7 @@ const handleKernelConnectionLostEpic = (
if (explorer) { if (explorer) {
explorer.showOkModalDialog("kernel restarts", msg); explorer.showOkModalDialog("kernel restarts", msg);
} }
return of(EMPTY); return of(empty());
} }
return concat( return concat(
@@ -847,7 +814,7 @@ const closeUnsupportedMimetypesEpic = (
explorer.showOkModalDialog("File cannot be rendered", msg); explorer.showOkModalDialog("File cannot be rendered", msg);
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
} }
return EMPTY; return empty();
}) })
); );
}; };
@@ -875,14 +842,13 @@ const closeContentFailedToFetchEpic = (
explorer.showOkModalDialog("Failure to load", msg); explorer.showOkModalDialog("Failure to load", msg);
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
} }
return EMPTY; return empty();
}) })
); );
}; };
export const allEpics = [ export const allEpics = [
addInitialCodeCellEpic, addInitialCodeCellEpic,
autoStartKernelEpic,
focusInitialCodeCellEpic, focusInitialCodeCellEpic,
notificationsToUserEpic, notificationsToUserEpic,
launchWebSocketKernelEpic, launchWebSocketKernelEpic,

View File

@@ -82,19 +82,6 @@ export const cdbReducer = (state: CdbRecord, action: Action) => {
}); });
return state; return state;
} }
case cdbActions.UPDATE_NOTEBOOK_PARENT_DOM_ELTS: {
const typedAction = action as cdbActions.UpdateNotebookParentDomEltAction;
var parentEltsMap = state.get("currentNotebookParentElements");
const contentRef = typedAction.payload.contentRef;
const parentElt = typedAction.payload.parentElt;
if (parentElt) {
parentEltsMap.set(contentRef, parentElt);
} else {
parentEltsMap.delete(contentRef);
}
return state.set("currentNotebookParentElements", parentEltsMap);
}
} }
return state; return state;
}; };

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