mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-29 22:02:01 +00:00
Compare commits
14 Commits
exclude-ve
...
genereated
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6870bd9b54 | ||
|
|
8aeff8fb45 | ||
|
|
ee6f635458 | ||
|
|
ad115a2cce | ||
|
|
0c255a55c8 | ||
|
|
08e84d93b5 | ||
|
|
e2895b62b4 | ||
|
|
155aacdf63 | ||
|
|
f4f2d00d7f | ||
|
|
f1812077e9 | ||
|
|
cfe9bd8303 | ||
|
|
9db8d11801 | ||
|
|
769a2e7d1c | ||
|
|
df544f88b2 |
@@ -4,4 +4,3 @@ PORTAL_RUNNER_PASSWORD=
|
|||||||
PORTAL_RUNNER_SUBSCRIPTION=
|
PORTAL_RUNNER_SUBSCRIPTION=
|
||||||
PORTAL_RUNNER_RESOURCE_GROUP=
|
PORTAL_RUNNER_RESOURCE_GROUP=
|
||||||
PORTAL_RUNNER_DATABASE_ACCOUNT=
|
PORTAL_RUNNER_DATABASE_ACCOUNT=
|
||||||
PORTAL_RUNNER_CONNECTION_STRING=
|
|
||||||
@@ -266,6 +266,10 @@ src/ResourceProvider/ResourceProviderClientFactory.ts
|
|||||||
src/RouteHandlers/RouteHandler.ts
|
src/RouteHandlers/RouteHandler.ts
|
||||||
src/RouteHandlers/TabRouteHandler.test.ts
|
src/RouteHandlers/TabRouteHandler.test.ts
|
||||||
src/RouteHandlers/TabRouteHandler.ts
|
src/RouteHandlers/TabRouteHandler.ts
|
||||||
|
src/Shared/AddCollectionUtility.test.ts
|
||||||
|
src/Shared/AddCollectionUtility.ts
|
||||||
|
src/Shared/AddDatabaseUtility.test.ts
|
||||||
|
src/Shared/AddDatabaseUtility.ts
|
||||||
src/Shared/Constants.ts
|
src/Shared/Constants.ts
|
||||||
src/Shared/DefaultExperienceUtility.test.ts
|
src/Shared/DefaultExperienceUtility.test.ts
|
||||||
src/Shared/DefaultExperienceUtility.ts
|
src/Shared/DefaultExperienceUtility.ts
|
||||||
@@ -275,6 +279,8 @@ src/Shared/StorageUtility.test.ts
|
|||||||
src/Shared/StorageUtility.ts
|
src/Shared/StorageUtility.ts
|
||||||
src/Shared/StringUtility.test.ts
|
src/Shared/StringUtility.test.ts
|
||||||
src/Shared/StringUtility.ts
|
src/Shared/StringUtility.ts
|
||||||
|
src/Shared/Telemetry/TelemetryConstants.ts
|
||||||
|
src/Shared/Telemetry/TelemetryProcessor.ts
|
||||||
src/Shared/appInsights.ts
|
src/Shared/appInsights.ts
|
||||||
src/SparkClusterManager/ArcadiaResourceManager.ts
|
src/SparkClusterManager/ArcadiaResourceManager.ts
|
||||||
src/SparkClusterManager/SparkClusterManager.ts
|
src/SparkClusterManager/SparkClusterManager.ts
|
||||||
@@ -292,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
|
||||||
@@ -412,5 +420,6 @@ cypress/integration/dataexplorer/SQL/addCollection.spec.ts
|
|||||||
cypress/integration/dataexplorer/TABLE/addCollection.spec.ts
|
cypress/integration/dataexplorer/TABLE/addCollection.spec.ts
|
||||||
cypress/integration/notebook/newNotebook.spec.ts
|
cypress/integration/notebook/newNotebook.spec.ts
|
||||||
cypress/integration/notebook/resourceTree.spec.ts
|
cypress/integration/notebook/resourceTree.spec.ts
|
||||||
|
__mocks__/AddDatabaseUtility.ts
|
||||||
__mocks__/monaco-editor.ts
|
__mocks__/monaco-editor.ts
|
||||||
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx
|
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx
|
||||||
@@ -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
2
.github/CODEOWNERS
vendored
@@ -1 +1 @@
|
|||||||
* @Azure/cosmos-explorer-owners @Azure/azure-cosmos-explorer-developers
|
* @Azure/cosmos-explorer-owners
|
||||||
|
|||||||
27
.github/workflows/ci.yml
vendored
27
.github/workflows/ci.yml
vendored
@@ -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:
|
||||||
|
|||||||
5
__mocks__/AddDatabaseUtility.ts
Normal file
5
__mocks__/AddDatabaseUtility.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export class AddDbUtilities {
|
||||||
|
createGremlinDatabase(params: any) {
|
||||||
|
return Promise.resolve(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ const isCI = require("is-ci");
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
launch: {
|
launch: {
|
||||||
headless: isCI,
|
headless: isCI,
|
||||||
slowMo: 50,
|
slowMo: isCI ? null : 20,
|
||||||
defaultViewport: null,
|
defaultViewport: null
|
||||||
ignoreHTTPSErrors: true,
|
|
||||||
args: ["--disable-web-security"]
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
|
|
||||||
@SelectionColor: #3074B0;
|
@SelectionColor: #3074B0;
|
||||||
|
|
||||||
@FocusColor: #605e5c;
|
@FocusColor: #00bcf2;
|
||||||
|
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
METRICS
|
METRICS
|
||||||
|
|||||||
@@ -1522,10 +1522,6 @@ p {
|
|||||||
.tooltipVisible();
|
.tooltipVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
.infoTooltip a {
|
|
||||||
color: @AccentHigh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nowrap {
|
.nowrap {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
@@ -1650,7 +1646,7 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.contextual-pane .collid {
|
.contextual-pane .collid {
|
||||||
border: 1px solid #605e5c;
|
border: 1px solid #bbbbbb;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
color: #000;
|
color: #000;
|
||||||
@@ -1743,7 +1739,7 @@ input::-webkit-calendar-picker-indicator {
|
|||||||
padding-right: 34px;
|
padding-right: 34px;
|
||||||
color: @BaseDark;
|
color: @BaseDark;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: auto;
|
overflow-x: hidden;
|
||||||
margin: (2 * @MediumSpace) 0px;
|
margin: (2 * @MediumSpace) 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2078,7 +2074,7 @@ a:link {
|
|||||||
.resourceTreeAndTabs {
|
.resourceTreeAndTabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow: auto;
|
overflow: hidden;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2427,6 +2423,22 @@ a:link {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::-webkit-input-placeholder {
|
||||||
|
color: #969696;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-moz-placeholder {
|
||||||
|
color: #969696;
|
||||||
|
}
|
||||||
|
|
||||||
|
:-ms-input-placeholder {
|
||||||
|
color: #969696;
|
||||||
|
}
|
||||||
|
|
||||||
|
:-moz-placeholder {
|
||||||
|
color: #969696;
|
||||||
|
}
|
||||||
|
|
||||||
::-ms-expand {
|
::-ms-expand {
|
||||||
color: #969696;
|
color: #969696;
|
||||||
}
|
}
|
||||||
@@ -2976,10 +2988,6 @@ settings-pane {
|
|||||||
.enableAnalyticalStorageRadio:nth-child(n+2) {
|
.enableAnalyticalStorageRadio:nth-child(n+2) {
|
||||||
margin-left: @LargeSpace;
|
margin-left: @LargeSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.enableAnalyticalStorageRadioLabel {
|
|
||||||
padding: 0px
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.addCollectionLabel {
|
.addCollectionLabel {
|
||||||
@@ -3009,12 +3017,4 @@ settings-pane {
|
|||||||
|
|
||||||
.italic {
|
.italic {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warningErrorContent a {
|
|
||||||
color: @AccentMediumHigh
|
|
||||||
}
|
|
||||||
|
|
||||||
.infoBoxContent a {
|
|
||||||
color: @AccentMediumHigh
|
|
||||||
}
|
|
||||||
952
package-lock.json
generated
952
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
@@ -4,11 +4,11 @@
|
|||||||
"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",
|
||||||
"@microsoft/applicationinsights-web": "2.5.8",
|
"@microsoft/applicationinsights-web": "2.5.4",
|
||||||
"@nteract/commutable": "7.1.4",
|
"@nteract/commutable": "7.1.4",
|
||||||
"@nteract/connected-components": "6.7.8",
|
"@nteract/connected-components": "6.7.8",
|
||||||
"@nteract/core": "13.0.0",
|
"@nteract/core": "13.0.0",
|
||||||
@@ -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",
|
||||||
@@ -66,8 +65,7 @@
|
|||||||
"mkdirp": "1.0.4",
|
"mkdirp": "1.0.4",
|
||||||
"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.134.1",
|
"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",
|
||||||
@@ -102,15 +100,12 @@
|
|||||||
"@types/d3": "4.13.2",
|
"@types/d3": "4.13.2",
|
||||||
"@types/enzyme": "3.10.3",
|
"@types/enzyme": "3.10.3",
|
||||||
"@types/enzyme-adapter-react-16": "1.0.5",
|
"@types/enzyme-adapter-react-16": "1.0.5",
|
||||||
"@types/expect-puppeteer": "4.4.3",
|
|
||||||
"@types/hasher": "0.0.31",
|
"@types/hasher": "0.0.31",
|
||||||
"@types/jest": "23.3.10",
|
"@types/jest": "23.3.10",
|
||||||
"@types/jest-environment-puppeteer": "4.3.2",
|
|
||||||
"@types/memoize-one": "4.1.1",
|
"@types/memoize-one": "4.1.1",
|
||||||
"@types/node": "12.11.1",
|
"@types/node": "12.11.1",
|
||||||
"@types/promise.prototype.finally": "2.0.3",
|
"@types/promise.prototype.finally": "2.0.3",
|
||||||
"@types/prop-types": "15.5.8",
|
"@types/prop-types": "15.5.8",
|
||||||
"@types/puppeteer": "3.0.1",
|
|
||||||
"@types/q": "1.5.1",
|
"@types/q": "1.5.1",
|
||||||
"@types/react": "16.8.25",
|
"@types/react": "16.8.25",
|
||||||
"@types/react-dom": "16.0.7",
|
"@types/react-dom": "16.0.7",
|
||||||
@@ -121,8 +116,8 @@
|
|||||||
"@types/text-encoding": "0.0.33",
|
"@types/text-encoding": "0.0.33",
|
||||||
"@types/underscore": "1.7.36",
|
"@types/underscore": "1.7.36",
|
||||||
"@types/webfontloader": "1.6.29",
|
"@types/webfontloader": "1.6.29",
|
||||||
"@typescript-eslint/eslint-plugin": "4.0.1",
|
"@typescript-eslint/eslint-plugin": "3.2.0",
|
||||||
"@typescript-eslint/parser": "4.0.1",
|
"@typescript-eslint/parser": "3.2.0",
|
||||||
"adal-angular": "1.0.15",
|
"adal-angular": "1.0.15",
|
||||||
"axe-puppeteer": "1.1.0",
|
"axe-puppeteer": "1.1.0",
|
||||||
"babel-jest": "24.9.0",
|
"babel-jest": "24.9.0",
|
||||||
@@ -135,10 +130,9 @@
|
|||||||
"enzyme": "3.10.0",
|
"enzyme": "3.10.0",
|
||||||
"enzyme-adapter-react-16": "1.15.1",
|
"enzyme-adapter-react-16": "1.15.1",
|
||||||
"enzyme-to-json": "3.4.3",
|
"enzyme-to-json": "3.4.3",
|
||||||
"eslint": "7.8.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",
|
||||||
@@ -167,9 +161,8 @@
|
|||||||
"ts-loader": "6.2.2",
|
"ts-loader": "6.2.2",
|
||||||
"tslint": "5.11.0",
|
"tslint": "5.11.0",
|
||||||
"tslint-microsoft-contrib": "6.0.0",
|
"tslint-microsoft-contrib": "6.0.0",
|
||||||
"typescript": "4.0.2",
|
"typescript": "3.9.6",
|
||||||
"url-loader": "1.1.1",
|
"url-loader": "1.1.1",
|
||||||
"wait-on": "4.0.2",
|
|
||||||
"webpack": "4.43.0",
|
"webpack": "4.43.0",
|
||||||
"webpack-bundle-analyzer": "3.6.1",
|
"webpack-bundle-analyzer": "3.6.1",
|
||||||
"webpack-cli": "3.3.10",
|
"webpack-cli": "3.3.10",
|
||||||
@@ -188,7 +181,6 @@
|
|||||||
"test": "rimraf coverage && jest",
|
"test": "rimraf coverage && jest",
|
||||||
"test:e2e": "jest -c ./jest.config.e2e.js --detectOpenHandles",
|
"test:e2e": "jest -c ./jest.config.e2e.js --detectOpenHandles",
|
||||||
"watch": "npm run start",
|
"watch": "npm run start",
|
||||||
"wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/",
|
|
||||||
"build:ase": "gulp build:ase",
|
"build:ase": "gulp build:ase",
|
||||||
"compile": "tsc",
|
"compile": "tsc",
|
||||||
"compile:contracts": "tsc -p ./tsconfig.contracts.json",
|
"compile:contracts": "tsc -p ./tsconfig.contracts.json",
|
||||||
|
|||||||
@@ -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",
|
||||||
@@ -23,4 +23,4 @@
|
|||||||
"age": 23
|
"age": 23
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
13
src/Api/Apis.ts
Normal file
13
src/Api/Apis.ts
Normal 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";
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
@@ -170,8 +157,89 @@ export enum MongoBackendEndpointType {
|
|||||||
remote
|
remote
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class MongoBackend {
|
||||||
|
public static localhostEndpoint: string = "/api/mongo/explorer";
|
||||||
|
public static centralUsEndpoint: string = "https://main.documentdb.ext.azure.com/api/mongo/explorer";
|
||||||
|
public static northEuropeEndpoint: string = "https://main.documentdb.ext.azure.com/api/mongo/explorer";
|
||||||
|
public static southEastAsiaEndpoint: string = "https://main.documentdb.ext.azure.com/api/mongo/explorer";
|
||||||
|
|
||||||
|
public static endpointsByRegion: any = {
|
||||||
|
default: MongoBackend.centralUsEndpoint,
|
||||||
|
northeurope: MongoBackend.northEuropeEndpoint,
|
||||||
|
ukwest: MongoBackend.northEuropeEndpoint,
|
||||||
|
uksouth: MongoBackend.northEuropeEndpoint,
|
||||||
|
westeurope: MongoBackend.northEuropeEndpoint,
|
||||||
|
australiaeast: MongoBackend.southEastAsiaEndpoint,
|
||||||
|
australiasoutheast: MongoBackend.southEastAsiaEndpoint,
|
||||||
|
centralindia: MongoBackend.southEastAsiaEndpoint,
|
||||||
|
eastasia: MongoBackend.southEastAsiaEndpoint,
|
||||||
|
japaneast: MongoBackend.southEastAsiaEndpoint,
|
||||||
|
japanwest: MongoBackend.southEastAsiaEndpoint,
|
||||||
|
koreacentral: MongoBackend.southEastAsiaEndpoint,
|
||||||
|
koreasouth: MongoBackend.southEastAsiaEndpoint,
|
||||||
|
southeastasia: MongoBackend.southEastAsiaEndpoint,
|
||||||
|
southindia: MongoBackend.southEastAsiaEndpoint,
|
||||||
|
westindia: MongoBackend.southEastAsiaEndpoint
|
||||||
|
};
|
||||||
|
|
||||||
|
public static endpointsByEnvironment: any = {
|
||||||
|
default: MongoBackendEndpointType.local,
|
||||||
|
localhost: MongoBackendEndpointType.local,
|
||||||
|
prod1: MongoBackendEndpointType.remote,
|
||||||
|
prod2: MongoBackendEndpointType.remote
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: 435619 Add default endpoints per cloud and use regional only when available
|
// TODO: 435619 Add default endpoints per cloud and use regional only when available
|
||||||
export class CassandraBackend {
|
export class CassandraBackend {
|
||||||
|
public static readonly localhostEndpoint: string = "https://localhost:12901/";
|
||||||
|
public static readonly devEndpoint: string = "https://platformproxycassandradev.azurewebsites.net/";
|
||||||
|
|
||||||
|
public static readonly centralUsEndpoint: string = "https://main.documentdb.ext.azure.com/";
|
||||||
|
public static readonly northEuropeEndpoint: string = "https://main.documentdb.ext.azure.com/";
|
||||||
|
public static readonly southEastAsiaEndpoint: string = "https://main.documentdb.ext.azure.com/";
|
||||||
|
|
||||||
|
public static readonly bf_default: string = "https://main.documentdb.ext.microsoftazure.de/";
|
||||||
|
public static readonly mc_default: string = "https://main.documentdb.ext.azure.cn/";
|
||||||
|
public static readonly ff_default: string = "https://main.documentdb.ext.azure.us/";
|
||||||
|
|
||||||
|
public static readonly endpointsByRegion: any = {
|
||||||
|
default: CassandraBackend.centralUsEndpoint,
|
||||||
|
northeurope: CassandraBackend.northEuropeEndpoint,
|
||||||
|
ukwest: CassandraBackend.northEuropeEndpoint,
|
||||||
|
uksouth: CassandraBackend.northEuropeEndpoint,
|
||||||
|
westeurope: CassandraBackend.northEuropeEndpoint,
|
||||||
|
australiaeast: CassandraBackend.southEastAsiaEndpoint,
|
||||||
|
australiasoutheast: CassandraBackend.southEastAsiaEndpoint,
|
||||||
|
centralindia: CassandraBackend.southEastAsiaEndpoint,
|
||||||
|
eastasia: CassandraBackend.southEastAsiaEndpoint,
|
||||||
|
japaneast: CassandraBackend.southEastAsiaEndpoint,
|
||||||
|
japanwest: CassandraBackend.southEastAsiaEndpoint,
|
||||||
|
koreacentral: CassandraBackend.southEastAsiaEndpoint,
|
||||||
|
koreasouth: CassandraBackend.southEastAsiaEndpoint,
|
||||||
|
southeastasia: CassandraBackend.southEastAsiaEndpoint,
|
||||||
|
southindia: CassandraBackend.southEastAsiaEndpoint,
|
||||||
|
westindia: CassandraBackend.southEastAsiaEndpoint,
|
||||||
|
|
||||||
|
// Black Forest
|
||||||
|
germanycentral: CassandraBackend.bf_default,
|
||||||
|
germanynortheast: CassandraBackend.bf_default,
|
||||||
|
|
||||||
|
// Fairfax
|
||||||
|
usdodeast: CassandraBackend.ff_default,
|
||||||
|
usdodcentral: CassandraBackend.ff_default,
|
||||||
|
usgovarizona: CassandraBackend.ff_default,
|
||||||
|
usgoviowa: CassandraBackend.ff_default,
|
||||||
|
usgovtexas: CassandraBackend.ff_default,
|
||||||
|
usgovvirginia: CassandraBackend.ff_default,
|
||||||
|
|
||||||
|
// Mooncake
|
||||||
|
chinaeast: CassandraBackend.mc_default,
|
||||||
|
chinaeast2: CassandraBackend.mc_default,
|
||||||
|
chinanorth: CassandraBackend.mc_default,
|
||||||
|
chinanorth2: CassandraBackend.mc_default
|
||||||
|
};
|
||||||
|
|
||||||
public static readonly createOrDeleteApi: string = "api/cassandra/createordelete";
|
public static readonly createOrDeleteApi: string = "api/cassandra/createordelete";
|
||||||
public static readonly guestCreateOrDeleteApi: string = "api/guest/cassandra/createordelete";
|
public static readonly guestCreateOrDeleteApi: string = "api/guest/cassandra/createordelete";
|
||||||
public static readonly queryApi: string = "api/cassandra";
|
public static readonly queryApi: string = "api/cassandra";
|
||||||
@@ -481,11 +549,3 @@ export class AnalyticalStorageTtl {
|
|||||||
public static readonly Infinite: number = -1;
|
public static readonly Infinite: number = -1;
|
||||||
public static readonly Disabled: number = 0;
|
public static readonly Disabled: number = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TerminalQueryParams {
|
|
||||||
public static readonly Terminal = "terminal";
|
|
||||||
public static readonly Server = "server";
|
|
||||||
public static readonly Token = "token";
|
|
||||||
public static readonly SubscriptionId = "subscriptionId";
|
|
||||||
public static readonly TerminalEndpoint = "terminalEndpoint";
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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,28 +75,24 @@ 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",
|
type: "foo",
|
||||||
type: "foo",
|
kind: "foo",
|
||||||
kind: "foo",
|
tags: [],
|
||||||
tags: [],
|
properties: {
|
||||||
properties: {
|
documentEndpoint: "bar",
|
||||||
documentEndpoint: "bar",
|
gremlinEndpoint: "foo",
|
||||||
gremlinEndpoint: "foo",
|
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";
|
||||||
|
|||||||
@@ -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,25 +75,106 @@ 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 = {
|
||||||
const options: Cosmos.CosmosClientOptions = {
|
client(): Cosmos.CosmosClient {
|
||||||
endpoint: endpoint() || " ", // CosmosClient gets upset if we pass a falsy value here
|
if (_client) {
|
||||||
key: userContext.masterKey,
|
return _client;
|
||||||
tokenProvider,
|
}
|
||||||
connectionPolicy: {
|
const options: Cosmos.CosmosClientOptions = {
|
||||||
enableEndpointDiscovery: false
|
endpoint: endpoint() || " ", // CosmosClient gets upset if we pass a falsy value here
|
||||||
},
|
key: _masterKey,
|
||||||
userAgentSuffix: "Azure Portal"
|
tokenProvider,
|
||||||
};
|
connectionPolicy: {
|
||||||
|
enableEndpointDiscovery: false
|
||||||
|
},
|
||||||
|
userAgentSuffix: "Azure Portal"
|
||||||
|
};
|
||||||
|
|
||||||
// In development we proxy requests to the backend via webpack. This is removed in production bundles.
|
// In development we proxy requests to the backend via webpack. This is removed in production bundles.
|
||||||
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 }];
|
||||||
|
}
|
||||||
|
_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;
|
||||||
}
|
}
|
||||||
return new Cosmos.CosmosClient(options);
|
};
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,26 +1,30 @@
|
|||||||
|
import * as _ from "underscore";
|
||||||
|
import * as Constants from "./Constants";
|
||||||
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
|
import * as HeadersUtility from "./HeadersUtility";
|
||||||
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
|
import Q from "q";
|
||||||
import {
|
import {
|
||||||
ConflictDefinition,
|
ConflictDefinition,
|
||||||
|
ContainerDefinition,
|
||||||
|
ContainerResponse,
|
||||||
|
DatabaseResponse,
|
||||||
FeedOptions,
|
FeedOptions,
|
||||||
ItemDefinition,
|
ItemDefinition,
|
||||||
OfferDefinition,
|
PartitionKeyDefinition,
|
||||||
QueryIterator,
|
QueryIterator,
|
||||||
Resource
|
Resource,
|
||||||
|
TriggerDefinition
|
||||||
} from "@azure/cosmos";
|
} from "@azure/cosmos";
|
||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
|
||||||
import Q from "q";
|
import { CosmosClient } from "./CosmosClient";
|
||||||
import { configContext, Platform } from "../ConfigContext";
|
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
|
||||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
|
||||||
import ConflictId from "../Explorer/Tree/ConflictId";
|
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
|
||||||
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
|
||||||
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
|
||||||
|
import { MessageHandler } from "./MessageHandler";
|
||||||
|
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
import { OfferUtils } from "../Utils/OfferUtils";
|
import { OfferUtils } from "../Utils/OfferUtils";
|
||||||
import * as Constants from "./Constants";
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
import { client } from "./CosmosClient";
|
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
||||||
import * as HeadersUtility from "./HeadersUtility";
|
|
||||||
import { sendCachedDataMessage } from "./MessageHandler";
|
|
||||||
|
|
||||||
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);
|
||||||
@@ -39,256 +43,629 @@ export function getCommonQueryOptions(options: FeedOptions): any {
|
|||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function queryDocuments(
|
// TODO: Add timeout for all promises
|
||||||
databaseId: string,
|
export abstract class DataAccessUtilityBase {
|
||||||
containerId: string,
|
public queryDocuments(
|
||||||
query: string,
|
databaseId: string,
|
||||||
options: any
|
containerId: string,
|
||||||
): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
|
query: string,
|
||||||
options = getCommonQueryOptions(options);
|
options: any
|
||||||
const documentsIterator = client()
|
): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
|
||||||
.database(databaseId)
|
options = getCommonQueryOptions(options);
|
||||||
.container(containerId)
|
const documentsIterator = CosmosClient.client()
|
||||||
.items.query(query, options);
|
.database(databaseId)
|
||||||
return Q(documentsIterator);
|
.container(containerId)
|
||||||
}
|
.items.query(query, options);
|
||||||
|
return Q(documentsIterator);
|
||||||
export function getPartitionKeyHeaderForConflict(conflictId: ConflictId): Object {
|
|
||||||
const partitionKeyDefinition: DataModels.PartitionKey = conflictId.partitionKey;
|
|
||||||
const partitionKeyValue: any = conflictId.partitionKeyValue;
|
|
||||||
|
|
||||||
return getPartitionKeyHeader(partitionKeyDefinition, partitionKeyValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPartitionKeyHeader(partitionKeyDefinition: DataModels.PartitionKey, partitionKeyValue: any): Object {
|
|
||||||
if (!partitionKeyDefinition) {
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (partitionKeyValue === undefined) {
|
public readStoredProcedures(
|
||||||
return [{}];
|
collection: ViewModels.Collection,
|
||||||
|
options?: any
|
||||||
|
): Q.Promise<DataModels.StoredProcedure[]> {
|
||||||
|
return Q(
|
||||||
|
CosmosClient.client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.scripts.storedProcedures.readAll(options)
|
||||||
|
.fetchAll()
|
||||||
|
.then(response => response.resources as DataModels.StoredProcedure[])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [partitionKeyValue];
|
public readStoredProcedure(
|
||||||
}
|
collection: ViewModels.Collection,
|
||||||
|
requestedResource: DataModels.Resource,
|
||||||
export function updateOffer(
|
options?: any
|
||||||
offer: DataModels.Offer,
|
): Q.Promise<DataModels.StoredProcedure> {
|
||||||
newOffer: DataModels.Offer,
|
return Q(
|
||||||
options?: RequestOptions
|
CosmosClient.client()
|
||||||
): Q.Promise<DataModels.Offer> {
|
.database(collection.databaseId)
|
||||||
return Q(
|
.container(collection.id())
|
||||||
client()
|
.scripts.storedProcedure(requestedResource.id)
|
||||||
.offer(offer.id)
|
.read(options)
|
||||||
// TODO Remove casting when SDK types are fixed (https://github.com/Azure/azure-sdk-for-js/issues/10660)
|
.then(response => response.resource as DataModels.StoredProcedure)
|
||||||
.replace((newOffer as unknown) as OfferDefinition, options)
|
);
|
||||||
.then(response => {
|
}
|
||||||
return Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => response.resource);
|
public readUserDefinedFunctions(
|
||||||
})
|
collection: ViewModels.Collection,
|
||||||
);
|
options: any
|
||||||
}
|
): Q.Promise<DataModels.UserDefinedFunction[]> {
|
||||||
|
return Q(
|
||||||
export function updateDocument(
|
CosmosClient.client()
|
||||||
collection: ViewModels.CollectionBase,
|
.database(collection.databaseId)
|
||||||
documentId: DocumentId,
|
.container(collection.id())
|
||||||
newDocument: any
|
.scripts.userDefinedFunctions.readAll(options)
|
||||||
): Q.Promise<any> {
|
.fetchAll()
|
||||||
const partitionKey = documentId.partitionKeyValue;
|
.then(response => response.resources as DataModels.UserDefinedFunction[])
|
||||||
|
);
|
||||||
return Q(
|
}
|
||||||
client()
|
public readUserDefinedFunction(
|
||||||
.database(collection.databaseId)
|
collection: ViewModels.Collection,
|
||||||
.container(collection.id())
|
requestedResource: DataModels.Resource,
|
||||||
.item(documentId.id(), partitionKey)
|
options?: any
|
||||||
.replace(newDocument)
|
): Q.Promise<DataModels.UserDefinedFunction> {
|
||||||
.then(response => response.resource)
|
return Q(
|
||||||
);
|
CosmosClient.client()
|
||||||
}
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
export function executeStoredProcedure(
|
.scripts.userDefinedFunction(requestedResource.id)
|
||||||
collection: ViewModels.Collection,
|
.read(options)
|
||||||
storedProcedure: StoredProcedure,
|
.then(response => response.resource as DataModels.UserDefinedFunction)
|
||||||
partitionKeyValue: any,
|
);
|
||||||
params: any[]
|
|
||||||
): Q.Promise<any> {
|
|
||||||
// TODO remove this deferred. Kept it because of timeout code at bottom of function
|
|
||||||
const deferred = Q.defer<any>();
|
|
||||||
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.scripts.storedProcedure(storedProcedure.id())
|
|
||||||
.execute(partitionKeyValue, params, { enableScriptLogging: true })
|
|
||||||
.then(response =>
|
|
||||||
deferred.resolve({
|
|
||||||
result: response.resource,
|
|
||||||
scriptLogs: response.headers[Constants.HttpHeaders.scriptLogResults]
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.catch(error => deferred.reject(error));
|
|
||||||
|
|
||||||
return deferred.promise.timeout(
|
|
||||||
Constants.ClientDefaults.requestTimeoutMs,
|
|
||||||
`Request timed out while executing stored procedure ${storedProcedure.id()}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.items.create(newDocument)
|
|
||||||
.then(response => response.resource)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
|
||||||
const partitionKey = documentId.partitionKeyValue;
|
|
||||||
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.item(documentId.id(), partitionKey)
|
|
||||||
.read()
|
|
||||||
.then(response => response.resource)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
|
||||||
const partitionKey = documentId.partitionKeyValue;
|
|
||||||
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.item(documentId.id(), partitionKey)
|
|
||||||
.delete()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteConflict(
|
|
||||||
collection: ViewModels.CollectionBase,
|
|
||||||
conflictId: ConflictId,
|
|
||||||
options: any = {}
|
|
||||||
): Q.Promise<any> {
|
|
||||||
options.partitionKey = options.partitionKey || getPartitionKeyHeaderForConflict(conflictId);
|
|
||||||
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.conflict(conflictId.id())
|
|
||||||
.delete(options)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readCollectionQuotaInfo(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
options: any
|
|
||||||
): Q.Promise<DataModels.CollectionQuotaInfo> {
|
|
||||||
options = options || {};
|
|
||||||
options.populateQuotaInfo = true;
|
|
||||||
options.initialHeaders = options.initialHeaders || {};
|
|
||||||
options.initialHeaders[Constants.HttpHeaders.populatePartitionStatistics] = true;
|
|
||||||
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.read(options)
|
|
||||||
// TODO any needed because SDK does not properly type response.resource.statistics
|
|
||||||
.then((response: any) => {
|
|
||||||
let quota: DataModels.CollectionQuotaInfo = HeadersUtility.getQuota(response.headers);
|
|
||||||
quota["usageSizeInKB"] = response.resource.statistics.reduce(
|
|
||||||
(
|
|
||||||
previousValue: number,
|
|
||||||
currentValue: DataModels.Statistic,
|
|
||||||
currentIndex: number,
|
|
||||||
array: DataModels.Statistic[]
|
|
||||||
) => {
|
|
||||||
return previousValue + currentValue.sizeInKB;
|
|
||||||
},
|
|
||||||
0
|
|
||||||
);
|
|
||||||
quota["numPartitions"] = response.resource.statistics.length;
|
|
||||||
quota["uniqueKeyPolicy"] = collection.uniqueKeyPolicy; // TODO: Remove after refactoring (#119617)
|
|
||||||
return quota;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readOffers(options: any): Q.Promise<DataModels.Offer[]> {
|
|
||||||
if (options.isServerless) {
|
|
||||||
return Q([]); // Reading offers is not supported for serverless accounts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
public readTriggers(collection: ViewModels.Collection, options: any): Q.Promise<DataModels.Trigger[]> {
|
||||||
if (configContext.platform === Platform.Portal) {
|
return Q(
|
||||||
return sendCachedDataMessage<DataModels.Offer[]>(MessageTypes.AllOffers, [
|
CosmosClient.client()
|
||||||
(<any>window).dataExplorer.databaseAccount().id,
|
.database(collection.databaseId)
|
||||||
Constants.ClientDefaults.portalCacheTimeoutMs
|
.container(collection.id())
|
||||||
]);
|
.scripts.triggers.readAll(options)
|
||||||
|
.fetchAll()
|
||||||
|
.then(response => response.resources as DataModels.Trigger[])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readTrigger(
|
||||||
|
collection: ViewModels.Collection,
|
||||||
|
requestedResource: DataModels.Resource,
|
||||||
|
options?: any
|
||||||
|
): Q.Promise<DataModels.Trigger> {
|
||||||
|
return Q(
|
||||||
|
CosmosClient.client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.scripts.trigger(requestedResource.id)
|
||||||
|
.read(options)
|
||||||
|
.then(response => response.resource as DataModels.Trigger)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public executeStoredProcedure(
|
||||||
|
collection: ViewModels.Collection,
|
||||||
|
storedProcedure: StoredProcedure,
|
||||||
|
partitionKeyValue: any,
|
||||||
|
params: any[]
|
||||||
|
): Q.Promise<any> {
|
||||||
|
// TODO remove this deferred. Kept it because of timeout code at bottom of function
|
||||||
|
const deferred = Q.defer<any>();
|
||||||
|
|
||||||
|
CosmosClient.client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.scripts.storedProcedure(storedProcedure.id())
|
||||||
|
.execute(partitionKeyValue, params, { enableScriptLogging: true })
|
||||||
|
.then(response =>
|
||||||
|
deferred.resolve({
|
||||||
|
result: response.resource,
|
||||||
|
scriptLogs: response.headers[Constants.HttpHeaders.scriptLogResults]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch(error => deferred.reject(error));
|
||||||
|
|
||||||
|
return deferred.promise.timeout(
|
||||||
|
Constants.ClientDefaults.requestTimeoutMs,
|
||||||
|
`Request timed out while executing stored procedure ${storedProcedure.id()}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readDocument(collection: ViewModels.CollectionBase, documentId: ViewModels.DocumentId): Q.Promise<any> {
|
||||||
|
const partitionKey = documentId.partitionKeyValue;
|
||||||
|
|
||||||
|
return Q(
|
||||||
|
CosmosClient.client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.item(documentId.id(), partitionKey)
|
||||||
|
.read()
|
||||||
|
.then(response => response.resource)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPartitionKeyHeaderForConflict(conflictId: ViewModels.ConflictId): Object {
|
||||||
|
const partitionKeyDefinition: DataModels.PartitionKey = conflictId.partitionKey;
|
||||||
|
const partitionKeyValue: any = conflictId.partitionKeyValue;
|
||||||
|
|
||||||
|
return this.getPartitionKeyHeader(partitionKeyDefinition, partitionKeyValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPartitionKeyHeader(partitionKeyDefinition: DataModels.PartitionKey, partitionKeyValue: any): Object {
|
||||||
|
if (!partitionKeyDefinition) {
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
// If error getting cached Offers, continue on and read via SDK
|
|
||||||
}
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.offers.readAll()
|
|
||||||
.fetchAll()
|
|
||||||
.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> {
|
if (partitionKeyValue === undefined) {
|
||||||
options = options || {};
|
return [{}];
|
||||||
options.initialHeaders = options.initialHeaders || {};
|
}
|
||||||
if (!OfferUtils.isOfferV1(requestedResource)) {
|
|
||||||
options.initialHeaders[Constants.HttpHeaders.populateCollectionThroughputInfo] = true;
|
return [partitionKeyValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
return Q(
|
public updateCollection(
|
||||||
client()
|
databaseId: string,
|
||||||
.offer(requestedResource.id)
|
collectionId: string,
|
||||||
.read(options)
|
newCollection: DataModels.Collection,
|
||||||
.then(response => ({ ...response.resource, headers: response.headers }))
|
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);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function refreshCachedOffers(): Q.Promise<void> {
|
public updateDocument(
|
||||||
if (configContext.platform === Platform.Portal) {
|
collection: ViewModels.CollectionBase,
|
||||||
return sendCachedDataMessage(MessageTypes.RefreshOffers, []);
|
documentId: ViewModels.DocumentId,
|
||||||
} else {
|
newDocument: any
|
||||||
return Q();
|
): Q.Promise<any> {
|
||||||
|
const partitionKey = documentId.partitionKeyValue;
|
||||||
|
|
||||||
|
return Q(
|
||||||
|
CosmosClient.client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.item(documentId.id(), partitionKey)
|
||||||
|
.replace(newDocument)
|
||||||
|
.then(response => response.resource)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateOffer(
|
||||||
|
offer: DataModels.Offer,
|
||||||
|
newOffer: DataModels.Offer,
|
||||||
|
options?: RequestOptions
|
||||||
|
): Q.Promise<DataModels.Offer> {
|
||||||
|
return Q(
|
||||||
|
CosmosClient.client()
|
||||||
|
.offer(offer.id)
|
||||||
|
.replace(newOffer, options)
|
||||||
|
.then(response => {
|
||||||
|
return Promise.all([this.refreshCachedOffers(), this.refreshCachedResources()]).then(() => response.resource);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateStoredProcedure(
|
||||||
|
collection: ViewModels.Collection,
|
||||||
|
storedProcedure: DataModels.StoredProcedure,
|
||||||
|
options: any
|
||||||
|
): Q.Promise<DataModels.StoredProcedure> {
|
||||||
|
return Q(
|
||||||
|
CosmosClient.client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.scripts.storedProcedure(storedProcedure.id)
|
||||||
|
.replace(storedProcedure, options)
|
||||||
|
.then(response => response.resource as DataModels.StoredProcedure)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateUserDefinedFunction(
|
||||||
|
collection: ViewModels.Collection,
|
||||||
|
userDefinedFunction: DataModels.UserDefinedFunction,
|
||||||
|
options?: any
|
||||||
|
): Q.Promise<DataModels.UserDefinedFunction> {
|
||||||
|
return Q(
|
||||||
|
CosmosClient.client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.scripts.userDefinedFunction(userDefinedFunction.id)
|
||||||
|
.replace(userDefinedFunction, options)
|
||||||
|
.then(response => response.resource as DataModels.StoredProcedure)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateTrigger(
|
||||||
|
collection: ViewModels.Collection,
|
||||||
|
trigger: DataModels.Trigger,
|
||||||
|
options?: any
|
||||||
|
): Q.Promise<DataModels.Trigger> {
|
||||||
|
return Q(
|
||||||
|
CosmosClient.client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.scripts.trigger(trigger.id)
|
||||||
|
.replace(trigger as TriggerDefinition, options)
|
||||||
|
.then(response => response.resource as DataModels.Trigger)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
|
||||||
|
return Q(
|
||||||
|
CosmosClient.client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.items.create(newDocument)
|
||||||
|
.then(response => response.resource as DataModels.StoredProcedure)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public createStoredProcedure(
|
||||||
|
collection: ViewModels.Collection,
|
||||||
|
newStoredProcedure: DataModels.StoredProcedure,
|
||||||
|
options?: any
|
||||||
|
): Q.Promise<DataModels.StoredProcedure> {
|
||||||
|
return Q(
|
||||||
|
CosmosClient.client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.scripts.storedProcedures.create(newStoredProcedure, options)
|
||||||
|
.then(response => response.resource as DataModels.StoredProcedure)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public createUserDefinedFunction(
|
||||||
|
collection: ViewModels.Collection,
|
||||||
|
newUserDefinedFunction: DataModels.UserDefinedFunction,
|
||||||
|
options: any
|
||||||
|
): Q.Promise<DataModels.UserDefinedFunction> {
|
||||||
|
return Q(
|
||||||
|
CosmosClient.client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.scripts.userDefinedFunctions.create(newUserDefinedFunction, options)
|
||||||
|
.then(response => response.resource as DataModels.UserDefinedFunction)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public createTrigger(
|
||||||
|
collection: ViewModels.Collection,
|
||||||
|
newTrigger: DataModels.Trigger,
|
||||||
|
options?: any
|
||||||
|
): Q.Promise<DataModels.Trigger> {
|
||||||
|
return Q(
|
||||||
|
CosmosClient.client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.scripts.triggers.create(newTrigger as TriggerDefinition, options)
|
||||||
|
.then(response => response.resource as DataModels.Trigger)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public deleteDocument(collection: ViewModels.CollectionBase, documentId: ViewModels.DocumentId): Q.Promise<any> {
|
||||||
|
const partitionKey = documentId.partitionKeyValue;
|
||||||
|
|
||||||
|
return Q(
|
||||||
|
CosmosClient.client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.item(documentId.id(), partitionKey)
|
||||||
|
.delete()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public deleteConflict(
|
||||||
|
collection: ViewModels.CollectionBase,
|
||||||
|
conflictId: ViewModels.ConflictId,
|
||||||
|
options: any = {}
|
||||||
|
): Q.Promise<any> {
|
||||||
|
options.partitionKey = options.partitionKey || this.getPartitionKeyHeaderForConflict(conflictId);
|
||||||
|
|
||||||
|
return Q(
|
||||||
|
CosmosClient.client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.conflict(conflictId.id())
|
||||||
|
.delete(options)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
storedProcedure: DataModels.StoredProcedure,
|
||||||
|
options: any
|
||||||
|
): Q.Promise<any> {
|
||||||
|
return Q(
|
||||||
|
CosmosClient.client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.scripts.storedProcedure(storedProcedure.id)
|
||||||
|
.delete()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public deleteUserDefinedFunction(
|
||||||
|
collection: ViewModels.Collection,
|
||||||
|
userDefinedFunction: DataModels.UserDefinedFunction,
|
||||||
|
options: any
|
||||||
|
): Q.Promise<any> {
|
||||||
|
return Q(
|
||||||
|
CosmosClient.client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.scripts.userDefinedFunction(userDefinedFunction.id)
|
||||||
|
.delete()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public deleteTrigger(collection: ViewModels.Collection, trigger: DataModels.Trigger, options: any): Q.Promise<any> {
|
||||||
|
return Q(
|
||||||
|
CosmosClient.client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.scripts.trigger(trigger.id)
|
||||||
|
.delete()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
options: any
|
||||||
|
): Q.Promise<DataModels.CollectionQuotaInfo> {
|
||||||
|
options = options || {};
|
||||||
|
options.populateQuotaInfo = true;
|
||||||
|
options.initialHeaders = options.initialHeaders || {};
|
||||||
|
options.initialHeaders[Constants.HttpHeaders.populatePartitionStatistics] = true;
|
||||||
|
|
||||||
|
return Q(
|
||||||
|
CosmosClient.client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.read(options)
|
||||||
|
// TODO any needed because SDK does not properly type response.resource.statistics
|
||||||
|
.then((response: any) => {
|
||||||
|
let quota: DataModels.CollectionQuotaInfo = HeadersUtility.getQuota(response.headers);
|
||||||
|
quota["usageSizeInKB"] = response.resource.statistics.reduce(
|
||||||
|
(
|
||||||
|
previousValue: number,
|
||||||
|
currentValue: DataModels.Statistic,
|
||||||
|
currentIndex: number,
|
||||||
|
array: DataModels.Statistic[]
|
||||||
|
) => {
|
||||||
|
return previousValue + currentValue.sizeInKB;
|
||||||
|
},
|
||||||
|
0
|
||||||
|
);
|
||||||
|
quota["numPartitions"] = response.resource.statistics.length;
|
||||||
|
quota["uniqueKeyPolicy"] = collection.uniqueKeyPolicy; // TODO: Remove after refactoring (#119617)
|
||||||
|
return quota;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readOffers(options: any): Q.Promise<DataModels.Offer[]> {
|
||||||
|
return Q(
|
||||||
|
CosmosClient.client()
|
||||||
|
.offers.readAll()
|
||||||
|
.fetchAll()
|
||||||
|
.then(response => response.resources)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readOffer(requestedResource: DataModels.Offer, options: any): Q.Promise<DataModels.OfferWithHeaders> {
|
||||||
|
options = options || {};
|
||||||
|
options.initialHeaders = options.initialHeaders || {};
|
||||||
|
if (!OfferUtils.isOfferV1(requestedResource)) {
|
||||||
|
options.initialHeaders[Constants.HttpHeaders.populateCollectionThroughputInfo] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Q(
|
||||||
|
CosmosClient.client()
|
||||||
|
.offer(requestedResource.id)
|
||||||
|
.read(options)
|
||||||
|
.then(response => ({ ...response.resource, headers: response.headers }))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readDatabases(options: any): Q.Promise<DataModels.Database[]> {
|
||||||
|
return Q(
|
||||||
|
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 {
|
||||||
|
return Q();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public refreshCachedResources(options?: any): Q.Promise<void> {
|
||||||
|
if (MessageHandler.canSendMessage()) {
|
||||||
|
return MessageHandler.sendCachedDataMessage(MessageTypes.RefreshResources, []);
|
||||||
|
} else {
|
||||||
|
return Q();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public queryConflicts(
|
||||||
|
databaseId: string,
|
||||||
|
containerId: string,
|
||||||
|
query: string,
|
||||||
|
options: any
|
||||||
|
): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
|
||||||
|
const documentsIterator = CosmosClient.client()
|
||||||
|
.database(databaseId)
|
||||||
|
.container(containerId)
|
||||||
|
.conflicts.query(query, options);
|
||||||
|
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);
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function refreshCachedResources(options?: any): Q.Promise<void> {
|
|
||||||
if (configContext.platform === Platform.Portal) {
|
|
||||||
return sendCachedDataMessage(MessageTypes.RefreshResources, []);
|
|
||||||
} else {
|
|
||||||
return Q();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queryConflicts(
|
|
||||||
databaseId: string,
|
|
||||||
containerId: string,
|
|
||||||
query: string,
|
|
||||||
options: any
|
|
||||||
): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
|
|
||||||
const documentsIterator = client()
|
|
||||||
.database(databaseId)
|
|
||||||
.container(containerId)
|
|
||||||
.conflicts.query(query, options);
|
|
||||||
return Q(documentsIterator);
|
|
||||||
}
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,49 @@
|
|||||||
|
import * as Constants from "../Common/Constants";
|
||||||
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
|
import { AuthType } from "../AuthType";
|
||||||
|
import { StringUtils } from "../Utils/StringUtils";
|
||||||
|
import Explorer from "../Explorer/Explorer";
|
||||||
|
|
||||||
export default class EnvironmentUtility {
|
export default class EnvironmentUtility {
|
||||||
|
public static getMongoBackendEndpoint(serverId: string, location: string, extensionEndpoint: string = ""): string {
|
||||||
|
const defaultEnvironment: string = "default";
|
||||||
|
const defaultLocation: string = "default";
|
||||||
|
let environment: string = serverId;
|
||||||
|
const endpointType: Constants.MongoBackendEndpointType =
|
||||||
|
Constants.MongoBackend.endpointsByEnvironment[environment] ||
|
||||||
|
Constants.MongoBackend.endpointsByEnvironment[defaultEnvironment];
|
||||||
|
if (endpointType === Constants.MongoBackendEndpointType.local) {
|
||||||
|
return `${extensionEndpoint}${Constants.MongoBackend.localhostEndpoint}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedLocation = EnvironmentUtility.normalizeRegionName(location);
|
||||||
|
return (
|
||||||
|
Constants.MongoBackend.endpointsByRegion[normalizedLocation] ||
|
||||||
|
Constants.MongoBackend.endpointsByRegion[defaultLocation]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static isAadUser(): boolean {
|
||||||
|
return window.authType === AuthType.AAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getCassandraBackendEndpoint(explorer: Explorer): string {
|
||||||
|
const defaultLocation: string = "default";
|
||||||
|
const location: string = EnvironmentUtility.normalizeRegionName(explorer.databaseAccount().location);
|
||||||
|
return (
|
||||||
|
Constants.CassandraBackend.endpointsByRegion[location] ||
|
||||||
|
Constants.CassandraBackend.endpointsByRegion[defaultLocation]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public static normalizeArmEndpointUri(uri: string): string {
|
public static normalizeArmEndpointUri(uri: string): string {
|
||||||
if (uri && uri.slice(-1) !== "/") {
|
if (uri && uri.slice(-1) !== "/") {
|
||||||
return `${uri}/`;
|
return `${uri}/`;
|
||||||
}
|
}
|
||||||
return uri;
|
return uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static normalizeRegionName(region: string): string {
|
||||||
|
return region && StringUtils.stripSpacesFromString(region.toLocaleLowerCase());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,28 +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";
|
||||||
|
|
||||||
|
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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,76 +1,85 @@
|
|||||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
import Q from "q";
|
import Q from "q";
|
||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
import * as Constants from "./Constants";
|
import * as Constants from "./Constants";
|
||||||
import { getDataExplorerWindow } from "../Utils/WindowUtils";
|
|
||||||
|
export interface CachedDataPromise<T> {
|
||||||
export interface CachedDataPromise<T> {
|
deferred: Q.Deferred<T>;
|
||||||
deferred: Q.Deferred<T>;
|
startTime: Date;
|
||||||
startTime: Date;
|
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.
|
||||||
export function handleCachedDataMessage(message: any): void {
|
*/
|
||||||
const messageContent = message && message.message;
|
type Map = { [key: string]: CachedDataPromise<any> };
|
||||||
if (message == null || messageContent == null || messageContent.id == null || !RequestMap[messageContent.id]) {
|
|
||||||
return;
|
export class MessageHandler {
|
||||||
}
|
protected static RequestMap: Map = {};
|
||||||
|
|
||||||
const cachedDataPromise = RequestMap[messageContent.id];
|
public static handleCachedDataMessage(message: any): void {
|
||||||
if (messageContent.error != null) {
|
const messageContent = message && message.message;
|
||||||
cachedDataPromise.deferred.reject(messageContent.error);
|
if (
|
||||||
} else {
|
message == null ||
|
||||||
cachedDataPromise.deferred.resolve(JSON.parse(messageContent.data));
|
messageContent == null ||
|
||||||
}
|
messageContent.id == null ||
|
||||||
runGarbageCollector();
|
!MessageHandler.RequestMap[messageContent.id]
|
||||||
}
|
) {
|
||||||
|
return;
|
||||||
export function sendCachedDataMessage<TResponseDataModel>(
|
}
|
||||||
messageType: MessageTypes,
|
|
||||||
params: Object[],
|
const cachedDataPromise = MessageHandler.RequestMap[messageContent.id];
|
||||||
timeoutInMs?: number
|
if (messageContent.error != null) {
|
||||||
): Q.Promise<TResponseDataModel> {
|
cachedDataPromise.deferred.reject(messageContent.error);
|
||||||
let cachedDataPromise: CachedDataPromise<TResponseDataModel> = {
|
} else {
|
||||||
deferred: Q.defer<TResponseDataModel>(),
|
cachedDataPromise.deferred.resolve(JSON.parse(messageContent.data));
|
||||||
startTime: new Date(),
|
}
|
||||||
id: _.uniqueId()
|
MessageHandler.runGarbageCollector();
|
||||||
};
|
}
|
||||||
RequestMap[cachedDataPromise.id] = cachedDataPromise;
|
|
||||||
sendMessage({ type: messageType, params: params, id: cachedDataPromise.id });
|
public static sendCachedDataMessage<TResponseDataModel>(
|
||||||
|
messageType: MessageTypes,
|
||||||
//TODO: Use telemetry to measure optimal time to resolve/reject promises
|
params: Object[],
|
||||||
return cachedDataPromise.deferred.promise.timeout(
|
timeoutInMs?: number
|
||||||
timeoutInMs || Constants.ClientDefaults.requestTimeoutMs,
|
): Q.Promise<TResponseDataModel> {
|
||||||
"Timed out while waiting for response from portal"
|
let cachedDataPromise: CachedDataPromise<TResponseDataModel> = {
|
||||||
);
|
deferred: Q.defer<TResponseDataModel>(),
|
||||||
}
|
startTime: new Date(),
|
||||||
|
id: _.uniqueId()
|
||||||
export function sendMessage(data: any): void {
|
};
|
||||||
if (canSendMessage()) {
|
MessageHandler.RequestMap[cachedDataPromise.id] = cachedDataPromise;
|
||||||
// We try to find data explorer window first, then fallback to current window
|
MessageHandler.sendMessage({ type: messageType, params: params, id: cachedDataPromise.id });
|
||||||
const portalChildWindow = getDataExplorerWindow(window) || window;
|
|
||||||
portalChildWindow.parent.postMessage(
|
//TODO: Use telemetry to measure optimal time to resolve/reject promises
|
||||||
{
|
return cachedDataPromise.deferred.promise.timeout(
|
||||||
signature: "pcIframe",
|
timeoutInMs || Constants.ClientDefaults.requestTimeoutMs,
|
||||||
data: data
|
"Timed out while waiting for response from portal"
|
||||||
},
|
);
|
||||||
portalChildWindow.document.referrer
|
}
|
||||||
);
|
|
||||||
}
|
public static sendMessage(data: any): void {
|
||||||
}
|
if (MessageHandler.canSendMessage()) {
|
||||||
|
window.parent.postMessage(
|
||||||
export function canSendMessage(): boolean {
|
{
|
||||||
return window.parent !== window;
|
signature: "pcIframe",
|
||||||
}
|
data: data
|
||||||
|
},
|
||||||
// TODO: This is exported just for testing. It should not be.
|
window.document.referrer
|
||||||
export function runGarbageCollector() {
|
);
|
||||||
Object.keys(RequestMap).forEach((key: string) => {
|
}
|
||||||
const promise: Q.Promise<any> = RequestMap[key].deferred.promise;
|
}
|
||||||
if (promise.isFulfilled() || promise.isRejected()) {
|
|
||||||
delete RequestMap[key];
|
public static canSendMessage(): boolean {
|
||||||
}
|
return window.parent !== window;
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
protected static runGarbageCollector() {
|
||||||
|
Object.keys(MessageHandler.RequestMap).forEach((key: string) => {
|
||||||
|
const promise: Q.Promise<any> = MessageHandler.RequestMap[key].deferred.promise;
|
||||||
|
if (promise.isFulfilled() || promise.isRejected()) {
|
||||||
|
delete MessageHandler.RequestMap[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
|
import {
|
||||||
|
_createMongoCollectionWithARM,
|
||||||
|
deleteDocument,
|
||||||
|
getEndpoint,
|
||||||
|
queryDocuments,
|
||||||
|
readDocument,
|
||||||
|
updateDocument
|
||||||
|
} from "./MongoProxyClient";
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import { resetConfigContext, updateConfigContext } from "../ConfigContext";
|
import { Collection, DatabaseAccount, DocumentId } from "../Contracts/ViewModels";
|
||||||
import { DatabaseAccount } from "../Contracts/DataModels";
|
import { config } from "../Config";
|
||||||
import { Collection } from "../Contracts/ViewModels";
|
import { CosmosClient } from "./CosmosClient";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
|
||||||
import { updateUserContext } from "../UserContext";
|
|
||||||
import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
|
|
||||||
jest.mock("../ResourceProvider/ResourceProviderClient.ts");
|
jest.mock("../ResourceProvider/ResourceProviderClient.ts");
|
||||||
|
|
||||||
const databaseId = "testDB";
|
const databaseId = "testDB";
|
||||||
@@ -54,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: () => ""
|
||||||
@@ -82,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",
|
||||||
@@ -92,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: () => ""
|
||||||
@@ -115,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",
|
||||||
@@ -125,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: () => ""
|
||||||
@@ -148,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",
|
||||||
@@ -158,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: () => ""
|
||||||
@@ -173,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)
|
||||||
@@ -181,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)
|
||||||
@@ -191,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: () => ""
|
||||||
@@ -214,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",
|
||||||
@@ -224,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: () => ""
|
||||||
@@ -236,20 +230,74 @@ describe("MongoProxyClient", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("returns a production endpoint", () => {
|
it("returns a production endpoint", () => {
|
||||||
const endpoint = getEndpoint();
|
const endpoint = getEndpoint(databaseAccount as DatabaseAccount);
|
||||||
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/mongo/explorer");
|
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/mongo/explorer");
|
||||||
});
|
});
|
||||||
|
|
||||||
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();
|
const endpoint = getEndpoint(databaseAccount as DatabaseAccount);
|
||||||
expect(endpoint).toEqual("https://localhost:1234/api/mongo/explorer");
|
expect(endpoint).toEqual("https://localhost:1234/api/mongo/explorer");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns a guest endpoint", () => {
|
it("returns a guest endpoint", () => {
|
||||||
window.authType = AuthType.EncryptedToken;
|
window.authType = AuthType.EncryptedToken;
|
||||||
const endpoint = getEndpoint();
|
const endpoint = getEndpoint(databaseAccount as DatabaseAccount);
|
||||||
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer");
|
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("createMongoCollectionWithARM", () => {
|
||||||
|
it("should create a collection with autopilot when autopilot is selected + shared throughput is false", () => {
|
||||||
|
const resourceProviderClientPutAsyncSpy = jest.spyOn(ResourceProviderClient.prototype, "putAsync");
|
||||||
|
const properties = {
|
||||||
|
pk: "state",
|
||||||
|
coll: "abc-collection",
|
||||||
|
cd: true,
|
||||||
|
db: "a1-db",
|
||||||
|
st: false,
|
||||||
|
sid: "a2",
|
||||||
|
rg: "c1",
|
||||||
|
dba: "main",
|
||||||
|
is: false
|
||||||
|
};
|
||||||
|
_createMongoCollectionWithARM("management.azure.com", properties, { "x-ms-cosmos-offer-autopilot-tier": "1" });
|
||||||
|
expect(resourceProviderClientPutAsyncSpy).toHaveBeenCalledWith(
|
||||||
|
"subscriptions/a2/resourceGroups/c1/providers/Microsoft.DocumentDB/databaseAccounts/foo/mongodbDatabases/a1-db/collections/abc-collection",
|
||||||
|
"2020-04-01",
|
||||||
|
{
|
||||||
|
properties: {
|
||||||
|
options: { "x-ms-cosmos-offer-autopilot-tier": "1" },
|
||||||
|
resource: { id: "abc-collection" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("should create a collection with provisioned throughput when provisioned throughput is selected + shared throughput is false", () => {
|
||||||
|
const resourceProviderClientPutAsyncSpy = jest.spyOn(ResourceProviderClient.prototype, "putAsync");
|
||||||
|
const properties = {
|
||||||
|
pk: "state",
|
||||||
|
coll: "abc-collection",
|
||||||
|
cd: true,
|
||||||
|
db: "a1-db",
|
||||||
|
st: false,
|
||||||
|
sid: "a2",
|
||||||
|
rg: "c1",
|
||||||
|
dba: "main",
|
||||||
|
is: false,
|
||||||
|
offerThroughput: 400
|
||||||
|
};
|
||||||
|
_createMongoCollectionWithARM("management.azure.com", properties, undefined);
|
||||||
|
expect(resourceProviderClientPutAsyncSpy).toHaveBeenCalledWith(
|
||||||
|
"subscriptions/a2/resourceGroups/c1/providers/Microsoft.DocumentDB/databaseAccounts/foo/mongodbDatabases/a1-db/collections/abc-collection",
|
||||||
|
"2020-04-01",
|
||||||
|
{
|
||||||
|
properties: {
|
||||||
|
options: { throughput: "400" },
|
||||||
|
resource: { id: "abc-collection" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
|
import * as Constants from "../Common/Constants";
|
||||||
import queryString from "querystring";
|
import * as DataExplorerConstants from "../Common/Constants";
|
||||||
import { AuthType } from "../AuthType";
|
|
||||||
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 EnvironmentUtility from "./EnvironmentUtility";
|
||||||
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
import queryString from "querystring";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import { AddDbUtilities } from "../Shared/AddDatabaseUtility";
|
||||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
|
||||||
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
||||||
import { userContext } from "../UserContext";
|
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(),
|
||||||
@@ -21,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() };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,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,
|
||||||
@@ -70,14 +75,14 @@ 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 : ""
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getEndpoint() || "";
|
const endpoint = getEndpoint(databaseAccount) || "";
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
...defaultHeaders,
|
...defaultHeaders,
|
||||||
@@ -118,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("/");
|
||||||
@@ -131,14 +136,14 @@ 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 : ""
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getEndpoint();
|
const endpoint = getEndpoint(databaseAccount);
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -164,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,
|
||||||
@@ -172,13 +177,13 @@ 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 : ""
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getEndpoint();
|
const endpoint = getEndpoint(databaseAccount);
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}/resourcelist?${queryString.stringify(params)}`, {
|
.fetch(`${endpoint}/resourcelist?${queryString.stringify(params)}`, {
|
||||||
@@ -200,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("/");
|
||||||
@@ -214,18 +219,18 @@ 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 : ""
|
||||||
};
|
};
|
||||||
const endpoint = getEndpoint();
|
const endpoint = getEndpoint(databaseAccount);
|
||||||
|
|
||||||
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(),
|
||||||
@@ -241,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("/");
|
||||||
@@ -253,13 +262,13 @@ 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 : ""
|
||||||
};
|
};
|
||||||
const endpoint = getEndpoint();
|
const endpoint = getEndpoint(databaseAccount);
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
||||||
@@ -280,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()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getEndpoint();
|
if (autopilotOptions) {
|
||||||
|
params.isAutoPilot = true;
|
||||||
|
params.autoPilotTier = autopilotOptions[Constants.HttpHeaders.autoPilotTier] as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
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: {
|
||||||
@@ -322,13 +339,58 @@ 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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEndpoint(): string {
|
export function createMongoCollectionWithARM(
|
||||||
|
armEndpoint: string,
|
||||||
|
databaseId: string,
|
||||||
|
analyticalStorageTtl: number,
|
||||||
|
collectionId: string,
|
||||||
|
offerThroughput: number,
|
||||||
|
shardKey: string,
|
||||||
|
createDatabase: boolean,
|
||||||
|
sharedThroughput: boolean,
|
||||||
|
isSharded: boolean,
|
||||||
|
additionalOptions?: DataModels.RpOptions
|
||||||
|
): Promise<DataModels.CreateCollectionWithRpResponse> {
|
||||||
|
const databaseAccount = CosmosClient.databaseAccount();
|
||||||
|
const params: DataModels.MongoParameters = {
|
||||||
|
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
|
||||||
|
db: databaseId,
|
||||||
|
coll: collectionId,
|
||||||
|
pk: shardKey,
|
||||||
|
offerThroughput,
|
||||||
|
cd: createDatabase,
|
||||||
|
st: sharedThroughput,
|
||||||
|
is: isSharded,
|
||||||
|
rid: "",
|
||||||
|
rtype: "colls",
|
||||||
|
sid: CosmosClient.subscriptionId(),
|
||||||
|
rg: CosmosClient.resourceGroup(),
|
||||||
|
dba: databaseAccount.name,
|
||||||
|
analyticalStorageTtl
|
||||||
|
};
|
||||||
|
|
||||||
|
if (createDatabase) {
|
||||||
|
return AddDbUtilities.createMongoDatabaseWithARM(
|
||||||
|
armEndpoint,
|
||||||
|
params,
|
||||||
|
sharedThroughput ? additionalOptions : {}
|
||||||
|
).then(() => {
|
||||||
|
return _createMongoCollectionWithARM(armEndpoint, params, sharedThroughput ? {} : additionalOptions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return _createMongoCollectionWithARM(armEndpoint, params, additionalOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEndpoint(databaseAccount: ViewModels.DatabaseAccount): string {
|
||||||
|
const serverId = window.dataExplorer.serverId();
|
||||||
const extensionEndpoint = window.dataExplorer.extensionEndpoint();
|
const extensionEndpoint = window.dataExplorer.extensionEndpoint();
|
||||||
let url = (configContext.MONGO_BACKEND_ENDPOINT || extensionEndpoint) + "/api/mongo/explorer";
|
let url = config.MONGO_BACKEND_ENDPOINT
|
||||||
|
? config.MONGO_BACKEND_ENDPOINT + "/api/mongo/explorer"
|
||||||
|
: EnvironmentUtility.getMongoBackendEndpoint(serverId, databaseAccount.location, extensionEndpoint);
|
||||||
|
|
||||||
if (window.authType === AuthType.EncryptedToken) {
|
if (window.authType === AuthType.EncryptedToken) {
|
||||||
url = url.replace("api/mongo", "api/guest/mongo");
|
url = url.replace("api/mongo", "api/guest/mongo");
|
||||||
@@ -346,12 +408,57 @@ 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(
|
||||||
|
armEndpoint: string,
|
||||||
|
params: DataModels.MongoParameters,
|
||||||
|
rpOptions: DataModels.RpOptions
|
||||||
|
): Promise<DataModels.CreateCollectionWithRpResponse> {
|
||||||
|
const rpPayloadToCreateCollection: DataModels.MongoCreationRequest = {
|
||||||
|
properties: {
|
||||||
|
resource: {
|
||||||
|
id: params.coll
|
||||||
|
},
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (params.is) {
|
||||||
|
rpPayloadToCreateCollection.properties.resource["shardKey"] = { [params.pk]: "Hash" };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!params.st) {
|
||||||
|
if (rpOptions) {
|
||||||
|
rpPayloadToCreateCollection.properties.options = rpOptions;
|
||||||
|
} else {
|
||||||
|
rpPayloadToCreateCollection.properties.options["throughput"] =
|
||||||
|
params.offerThroughput && params.offerThroughput.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.analyticalStorageTtl) {
|
||||||
|
rpPayloadToCreateCollection.properties.resource.analyticalStorageTtl = params.analyticalStorageTtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new ResourceProviderClient<DataModels.CreateCollectionWithRpResponse>(armEndpoint).putAsync(
|
||||||
|
getARMCreateCollectionEndpoint(params),
|
||||||
|
DataExplorerConstants.ArmApiVersions.publicVersion,
|
||||||
|
rpPayloadToCreateCollection
|
||||||
|
);
|
||||||
|
} catch (response) {
|
||||||
|
errorHandling(response, "creating collection", undefined);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,14 +33,14 @@ 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
|
||||||
collectionId: SavedQueries.CollectionName,
|
.getOrCreateDatabaseAndCollection({
|
||||||
createNewDatabase: true,
|
collectionId: SavedQueries.CollectionName,
|
||||||
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) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
@@ -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}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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;
|
|
||||||
};
|
|
||||||
@@ -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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
|
|
||||||
export async function createStoredProcedure(
|
|
||||||
databaseId: string,
|
|
||||||
collectionId: string,
|
|
||||||
storedProcedure: StoredProcedureDefinition
|
|
||||||
): Promise<StoredProcedureDefinition & Resource> {
|
|
||||||
let createdStoredProcedure: StoredProcedureDefinition & Resource;
|
|
||||||
const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`);
|
|
||||||
try {
|
|
||||||
const response = await client()
|
|
||||||
.database(databaseId)
|
|
||||||
.container(collectionId)
|
|
||||||
.scripts.storedProcedures.create(storedProcedure);
|
|
||||||
createdStoredProcedure = response.resource;
|
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Error while creating stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`);
|
|
||||||
logError(JSON.stringify(error), "CreateStoredProcedure", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return createdStoredProcedure;
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { Resource, TriggerDefinition } from "@azure/cosmos";
|
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
|
|
||||||
export async function createTrigger(
|
|
||||||
databaseId: string,
|
|
||||||
collectionId: string,
|
|
||||||
trigger: TriggerDefinition
|
|
||||||
): Promise<TriggerDefinition & Resource> {
|
|
||||||
let createdTrigger: TriggerDefinition & Resource;
|
|
||||||
const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`);
|
|
||||||
try {
|
|
||||||
const response = await client()
|
|
||||||
.database(databaseId)
|
|
||||||
.container(collectionId)
|
|
||||||
.scripts.triggers.create(trigger);
|
|
||||||
createdTrigger = response.resource;
|
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Error while creating trigger ${trigger.id}:\n ${JSON.stringify(error)}`);
|
|
||||||
logError(JSON.stringify(error), "CreateTrigger", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return createdTrigger;
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
|
|
||||||
export async function createUserDefinedFunction(
|
|
||||||
databaseId: string,
|
|
||||||
collectionId: string,
|
|
||||||
userDefinedFunction: UserDefinedFunctionDefinition
|
|
||||||
): Promise<UserDefinedFunctionDefinition & Resource> {
|
|
||||||
let createdUserDefinedFunction: UserDefinedFunctionDefinition & Resource;
|
|
||||||
const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`);
|
|
||||||
try {
|
|
||||||
const response = await client()
|
|
||||||
.database(databaseId)
|
|
||||||
.container(collectionId)
|
|
||||||
.scripts.userDefinedFunctions.create(userDefinedFunction);
|
|
||||||
createdUserDefinedFunction = response.resource;
|
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Error while creating user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`);
|
|
||||||
logError(JSON.stringify(error), "CreateUserupdateUserDefinedFunction", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return createdUserDefinedFunction;
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
|
|
||||||
export async function deleteStoredProcedure(
|
|
||||||
databaseId: string,
|
|
||||||
collectionId: string,
|
|
||||||
storedProcedureId: string
|
|
||||||
): Promise<void> {
|
|
||||||
const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`);
|
|
||||||
try {
|
|
||||||
await client()
|
|
||||||
.database(databaseId)
|
|
||||||
.container(collectionId)
|
|
||||||
.scripts.storedProcedure(storedProcedureId)
|
|
||||||
.delete();
|
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Error while deleting stored procedure ${storedProcedureId}:\n ${JSON.stringify(error)}`);
|
|
||||||
logError(JSON.stringify(error), "DeleteStoredProcedure", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
|
|
||||||
export async function deleteTrigger(databaseId: string, collectionId: string, triggerId: string): Promise<void> {
|
|
||||||
const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`);
|
|
||||||
try {
|
|
||||||
await client()
|
|
||||||
.database(databaseId)
|
|
||||||
.container(collectionId)
|
|
||||||
.scripts.trigger(triggerId)
|
|
||||||
.delete();
|
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Error while deleting trigger ${triggerId}:\n ${JSON.stringify(error)}`);
|
|
||||||
logError(JSON.stringify(error), "DeleteTrigger", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
|
|
||||||
export async function deleteUserDefinedFunction(databaseId: string, collectionId: string, id: string): Promise<void> {
|
|
||||||
const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`);
|
|
||||||
try {
|
|
||||||
await client()
|
|
||||||
.database(databaseId)
|
|
||||||
.container(collectionId)
|
|
||||||
.scripts.userDefinedFunction(id)
|
|
||||||
.delete();
|
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Error while deleting user defined function ${id}:\n ${JSON.stringify(error)}`);
|
|
||||||
logError(JSON.stringify(error), "DeleteUserDefinedFunction", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
import * as DataModels from "../../Contracts/DataModels";
|
|
||||||
import { AuthType } from "../../AuthType";
|
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
|
||||||
import { HttpHeaders } from "../Constants";
|
|
||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { getSqlDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
|
||||||
import { getMongoDBDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
|
||||||
import { getCassandraKeyspaceThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
|
||||||
import { getGremlinDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
|
||||||
import { readOffers } from "./readOffers";
|
|
||||||
import { userContext } from "../../UserContext";
|
|
||||||
|
|
||||||
export const readDatabaseOffer = async (
|
|
||||||
params: DataModels.ReadDatabaseOfferParams
|
|
||||||
): Promise<DataModels.OfferWithHeaders> => {
|
|
||||||
let offerId = params.offerId;
|
|
||||||
if (!offerId) {
|
|
||||||
if (
|
|
||||||
window.authType === AuthType.AAD &&
|
|
||||||
!userContext.useSDKOperations &&
|
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
offerId = await getDatabaseOfferIdWithARM(params.databaseId);
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw new Error(error);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
offerId = await getDatabaseOfferIdWithSDK(params.databaseResourceId);
|
|
||||||
if (!offerId) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: RequestOptions = {
|
|
||||||
initialHeaders: {
|
|
||||||
[HttpHeaders.populateCollectionThroughputInfo]: true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await client()
|
|
||||||
.offer(offerId)
|
|
||||||
.read(options);
|
|
||||||
return (
|
|
||||||
response && {
|
|
||||||
...response.resource,
|
|
||||||
headers: response.headers
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDatabaseOfferIdWithARM = async (databaseId: string): Promise<string> => {
|
|
||||||
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 getSqlDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
|
|
||||||
break;
|
|
||||||
case DefaultAccountExperienceType.MongoDB:
|
|
||||||
rpResponse = await getMongoDBDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
|
|
||||||
break;
|
|
||||||
case DefaultAccountExperienceType.Cassandra:
|
|
||||||
rpResponse = await getCassandraKeyspaceThroughput(subscriptionId, resourceGroup, accountName, databaseId);
|
|
||||||
break;
|
|
||||||
case DefaultAccountExperienceType.Graph:
|
|
||||||
rpResponse = await getGremlinDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rpResponse?.name;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDatabaseOfferIdWithSDK = async (databaseResourceId: string): Promise<string> => {
|
|
||||||
const offers = await readOffers();
|
|
||||||
const offer = offers.find(offer => offer.resource === databaseResourceId);
|
|
||||||
return offer?.id;
|
|
||||||
};
|
|
||||||
@@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { Offer } from "../../Contracts/DataModels";
|
|
||||||
import { ClientDefaults } from "../Constants";
|
|
||||||
import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
|
||||||
import { Platform, configContext } from "../../ConfigContext";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { sendCachedDataMessage } from "../MessageHandler";
|
|
||||||
import { userContext } from "../../UserContext";
|
|
||||||
|
|
||||||
export const readOffers = async (): Promise<Offer[]> => {
|
|
||||||
try {
|
|
||||||
if (configContext.platform === Platform.Portal) {
|
|
||||||
return sendCachedDataMessage<Offer[]>(MessageTypes.AllOffers, [
|
|
||||||
userContext.databaseAccount.id,
|
|
||||||
ClientDefaults.portalCacheTimeoutMs
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// If error getting cached Offers, continue on and read via SDK
|
|
||||||
}
|
|
||||||
|
|
||||||
return client()
|
|
||||||
.offers.readAll()
|
|
||||||
.fetchAll()
|
|
||||||
.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;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
|
|
||||||
export async function readStoredProcedures(
|
|
||||||
databaseId: string,
|
|
||||||
collectionId: string
|
|
||||||
): Promise<(StoredProcedureDefinition & Resource)[]> {
|
|
||||||
let sprocs: (StoredProcedureDefinition & Resource)[];
|
|
||||||
const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`);
|
|
||||||
try {
|
|
||||||
const response = await client()
|
|
||||||
.database(databaseId)
|
|
||||||
.container(collectionId)
|
|
||||||
.scripts.storedProcedures.readAll()
|
|
||||||
.fetchAll();
|
|
||||||
sprocs = response.resources;
|
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Failed to query stored procedures for container ${collectionId}: ${JSON.stringify(error)}`);
|
|
||||||
logError(JSON.stringify(error), "ReadStoredProcedures", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
}
|
|
||||||
clearMessage();
|
|
||||||
return sprocs;
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { Resource, TriggerDefinition } from "@azure/cosmos";
|
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
|
|
||||||
export async function readTriggers(
|
|
||||||
databaseId: string,
|
|
||||||
collectionId: string
|
|
||||||
): Promise<(TriggerDefinition & Resource)[]> {
|
|
||||||
let triggers: (TriggerDefinition & Resource)[];
|
|
||||||
const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`);
|
|
||||||
try {
|
|
||||||
const response = await client()
|
|
||||||
.database(databaseId)
|
|
||||||
.container(collectionId)
|
|
||||||
.scripts.triggers.readAll()
|
|
||||||
.fetchAll();
|
|
||||||
triggers = response.resources;
|
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Failed to query triggers for container ${collectionId}: ${JSON.stringify(error)}`);
|
|
||||||
logError(JSON.stringify(error), "ReadTriggers", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
}
|
|
||||||
clearMessage();
|
|
||||||
return triggers;
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
|
|
||||||
export async function readUserDefinedFunctions(
|
|
||||||
databaseId: string,
|
|
||||||
collectionId: string
|
|
||||||
): Promise<(UserDefinedFunctionDefinition & Resource)[]> {
|
|
||||||
let udfs: (UserDefinedFunctionDefinition & Resource)[];
|
|
||||||
const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`);
|
|
||||||
try {
|
|
||||||
const response = await client()
|
|
||||||
.database(databaseId)
|
|
||||||
.container(collectionId)
|
|
||||||
.scripts.userDefinedFunctions.readAll()
|
|
||||||
.fetchAll();
|
|
||||||
udfs = response.resources;
|
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Failed to query user defined functions for container ${collectionId}: ${JSON.stringify(error)}`);
|
|
||||||
logError(JSON.stringify(error), "ReadUserDefinedFunctions", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
}
|
|
||||||
clearMessage();
|
|
||||||
return udfs;
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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}`);
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
|
|
||||||
export async function updateStoredProcedure(
|
|
||||||
databaseId: string,
|
|
||||||
collectionId: string,
|
|
||||||
storedProcedure: StoredProcedureDefinition
|
|
||||||
): Promise<StoredProcedureDefinition & Resource> {
|
|
||||||
let updatedStoredProcedure: StoredProcedureDefinition & Resource;
|
|
||||||
const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`);
|
|
||||||
try {
|
|
||||||
const response = await client()
|
|
||||||
.database(databaseId)
|
|
||||||
.container(collectionId)
|
|
||||||
.scripts.storedProcedure(storedProcedure.id)
|
|
||||||
.replace(storedProcedure);
|
|
||||||
updatedStoredProcedure = response.resource;
|
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Error while updating stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`);
|
|
||||||
logError(JSON.stringify(error), "UpdateStoredProcedure", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return updatedStoredProcedure;
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { TriggerDefinition } from "@azure/cosmos";
|
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
|
|
||||||
export async function updateTrigger(
|
|
||||||
databaseId: string,
|
|
||||||
collectionId: string,
|
|
||||||
trigger: TriggerDefinition
|
|
||||||
): Promise<TriggerDefinition> {
|
|
||||||
let updatedTrigger: TriggerDefinition;
|
|
||||||
const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`);
|
|
||||||
try {
|
|
||||||
const response = await client()
|
|
||||||
.database(databaseId)
|
|
||||||
.container(collectionId)
|
|
||||||
.scripts.trigger(trigger.id)
|
|
||||||
.replace(trigger);
|
|
||||||
updatedTrigger = response.resource;
|
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Error while updating trigger ${trigger.id}:\n ${JSON.stringify(error)}`);
|
|
||||||
logError(JSON.stringify(error), "UpdateTrigger", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return updatedTrigger;
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
|
|
||||||
export async function updateUserDefinedFunction(
|
|
||||||
databaseId: string,
|
|
||||||
collectionId: string,
|
|
||||||
userDefinedFunction: UserDefinedFunctionDefinition
|
|
||||||
): Promise<UserDefinedFunctionDefinition & Resource> {
|
|
||||||
let updatedUserDefinedFunction: UserDefinedFunctionDefinition & Resource;
|
|
||||||
const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`);
|
|
||||||
try {
|
|
||||||
const response = await client()
|
|
||||||
.database(databaseId)
|
|
||||||
.container(collectionId)
|
|
||||||
.scripts.userDefinedFunction(userDefinedFunction.id)
|
|
||||||
.replace(userDefinedFunction);
|
|
||||||
updatedUserDefinedFunction = response.resource;
|
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Error while updating user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`);
|
|
||||||
logError(JSON.stringify(error), "UpdateUserupdateUserDefinedFunction", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return updatedUserDefinedFunction;
|
|
||||||
}
|
|
||||||
@@ -4,9 +4,9 @@ export enum Platform {
|
|||||||
Emulator = "Emulator"
|
Emulator = "Emulator"
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ConfigContext {
|
interface Config {
|
||||||
platform: Platform;
|
platform: Platform;
|
||||||
allowedParentFrameOrigins: string[];
|
allowedParentFrameOrigins: RegExp;
|
||||||
gitSha?: string;
|
gitSha?: string;
|
||||||
proxyPath?: string;
|
proxyPath?: string;
|
||||||
AAD_ENDPOINT: string;
|
AAD_ENDPOINT: string;
|
||||||
@@ -28,14 +28,9 @@ interface ConfigContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Default configuration
|
// Default configuration
|
||||||
let configContext: Readonly<ConfigContext> = {
|
let config: Config = {
|
||||||
platform: Platform.Portal,
|
platform: Platform.Portal,
|
||||||
allowedParentFrameOrigins: [
|
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$/,
|
||||||
`^https:\\/\\/cosmos.azure.(com|cn|us)$`,
|
|
||||||
`^https:\\/\\/[\\.\\w]+.portal.azure.(com|cn|us)$`,
|
|
||||||
`^https:\\/\\/[\\.\\w]+.ext.azure.(com|cn|us)$`,
|
|
||||||
`^https:\\/\\/[\\.\\w]+microsoftazure.de$`
|
|
||||||
],
|
|
||||||
// Webpack injects this at build time
|
// Webpack injects this at build time
|
||||||
gitSha: process.env.GIT_SHA,
|
gitSha: process.env.GIT_SHA,
|
||||||
hostedExplorerURL: "https://cosmos.azure.com/",
|
hostedExplorerURL: "https://cosmos.azure.com/",
|
||||||
@@ -51,63 +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 { allowedParentFrameOrigins, ...externalConfig } = await response.json();
|
const externalConfig = await response.json();
|
||||||
Object.assign(configContext, externalConfig);
|
config = Object.assign({}, config, externalConfig);
|
||||||
if (allowedParentFrameOrigins && allowedParentFrameOrigins.length > 0) {
|
|
||||||
updateConfigContext({
|
|
||||||
allowedParentFrameOrigins: [...configContext.allowedParentFrameOrigins, ...allowedParentFrameOrigins]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} 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 };
|
||||||
@@ -88,6 +88,10 @@ export interface Resource {
|
|||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ResourceRequest {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Collection extends Resource {
|
export interface Collection extends Resource {
|
||||||
defaultTtl?: number;
|
defaultTtl?: number;
|
||||||
indexingPolicy?: IndexingPolicy;
|
indexingPolicy?: IndexingPolicy;
|
||||||
@@ -100,12 +104,39 @@ export interface Collection extends Resource {
|
|||||||
geospatialConfig?: GeospatialConfig;
|
geospatialConfig?: GeospatialConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CreateCollectionWithRpResponse extends Resource {
|
||||||
|
properties: Collection;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CollectionRequest extends ResourceRequest {
|
||||||
|
defaultTtl?: number;
|
||||||
|
indexingPolicy?: IndexingPolicy;
|
||||||
|
partitionKey?: PartitionKey;
|
||||||
|
uniqueKeyPolicy?: UniqueKeyPolicy;
|
||||||
|
conflictResolutionPolicy?: ConflictResolutionPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Database extends Resource {
|
export interface Database extends Resource {
|
||||||
collections?: Collection[];
|
collections?: Collection[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DocumentId extends Resource {}
|
export interface DocumentId extends Resource {}
|
||||||
|
|
||||||
|
export interface Script extends Resource {
|
||||||
|
body: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StoredProcedure extends Script {}
|
||||||
|
|
||||||
|
export interface UserDefinedFunction extends Script {}
|
||||||
|
|
||||||
|
export interface Trigger extends Script {
|
||||||
|
triggerType: string;
|
||||||
|
triggerOperation: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ConflictId extends Resource {
|
export interface ConflictId extends Resource {
|
||||||
resourceId?: string;
|
resourceId?: string;
|
||||||
resourceType?: string;
|
resourceType?: string;
|
||||||
@@ -122,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[];
|
||||||
@@ -228,6 +252,28 @@ export interface ErrorDataModel {
|
|||||||
code?: string;
|
code?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a property bag for telemetry e.g. see ITelemetryError.
|
||||||
|
*/
|
||||||
|
export interface ITelemetryProperties {
|
||||||
|
[propertyName: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a property bag for telemetry e.g. see ITelemetryError.
|
||||||
|
*/
|
||||||
|
export interface ITelemetryEvent {
|
||||||
|
name: string;
|
||||||
|
properties?: ITelemetryProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines an error to be logged as telemetry data.
|
||||||
|
*/
|
||||||
|
export interface ITelemetryError extends ITelemetryEvent {
|
||||||
|
error: any;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CreateDatabaseAndCollectionRequest {
|
export interface CreateDatabaseAndCollectionRequest {
|
||||||
databaseId: string;
|
databaseId: string;
|
||||||
collectionId: string;
|
collectionId: string;
|
||||||
@@ -254,6 +300,11 @@ export enum AutopilotTier {
|
|||||||
Tier4 = 4
|
Tier4 = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RpOptions {
|
||||||
|
// tier is sent as string, autoscale as object (AutoPilotCreationSettings)
|
||||||
|
[key: string]: string | AutoPilotCreationSettings;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Query {
|
export interface Query {
|
||||||
id: string;
|
id: string;
|
||||||
resourceId: string;
|
resourceId: string;
|
||||||
@@ -261,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;
|
||||||
@@ -269,30 +331,18 @@ 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 {
|
export interface SharedThroughputRange {
|
||||||
createNewDatabase: boolean;
|
minimumRU: number;
|
||||||
collectionId: string;
|
maximumRU: number;
|
||||||
databaseId: string;
|
defaultRU: number;
|
||||||
databaseLevelThroughput: boolean;
|
|
||||||
offerThroughput: number;
|
|
||||||
analyticalStorageTtl?: number;
|
|
||||||
autoPilotMaxThroughput?: number;
|
|
||||||
indexingPolicy?: IndexingPolicy;
|
|
||||||
partitionKey?: PartitionKey;
|
|
||||||
uniqueKeyPolicy?: UniqueKeyPolicy;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ReadDatabaseOfferParams {
|
|
||||||
databaseId: string;
|
|
||||||
databaseResourceId?: string;
|
|
||||||
offerId?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Notification {
|
export interface Notification {
|
||||||
@@ -437,6 +487,25 @@ export interface NotebookConfigurationEndpointInfo {
|
|||||||
token: string;
|
token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SparkCluster {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
properties: {
|
||||||
|
kind: string;
|
||||||
|
driverSize: string;
|
||||||
|
workerSize: string;
|
||||||
|
workerInstanceCount: number;
|
||||||
|
creationTime: string;
|
||||||
|
status: string;
|
||||||
|
libraries?: SparkClusterLibrary[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SparkClusterFeedResponse {
|
||||||
|
value: SparkCluster[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface SparkClusterConnectionInfo {
|
export interface SparkClusterConnectionInfo {
|
||||||
userName: string;
|
userName: string;
|
||||||
password: string;
|
password: string;
|
||||||
@@ -478,10 +547,79 @@ export interface MongoParameters extends RpParameters {
|
|||||||
analyticalStorageTtl?: number;
|
analyticalStorageTtl?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GraphParameters extends RpParameters {
|
||||||
|
pk: string;
|
||||||
|
coll: string;
|
||||||
|
cd: Boolean;
|
||||||
|
indexingPolicy?: IndexingPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreationRequest {
|
||||||
|
properties: {
|
||||||
|
resource: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
options: RpOptions;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SqlCollectionParameters extends RpParameters {
|
||||||
|
uniqueKeyPolicy?: UniqueKeyPolicy;
|
||||||
|
pk: string;
|
||||||
|
coll: string;
|
||||||
|
cd: Boolean;
|
||||||
|
analyticalStorageTtl?: number;
|
||||||
|
indexingPolicy?: IndexingPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MongoCreationRequest extends CreationRequest {
|
||||||
|
properties: {
|
||||||
|
resource: {
|
||||||
|
id: string;
|
||||||
|
analyticalStorageTtl?: number;
|
||||||
|
shardKey?: {};
|
||||||
|
};
|
||||||
|
options: RpOptions;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GraphCreationRequest extends CreationRequest {
|
||||||
|
properties: {
|
||||||
|
resource: {
|
||||||
|
id: string;
|
||||||
|
partitionKey: {};
|
||||||
|
indexingPolicy?: IndexingPolicy;
|
||||||
|
};
|
||||||
|
options: RpOptions;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateDatabaseWithRpResponse {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
properties: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface SparkClusterLibrary {
|
export interface SparkClusterLibrary {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SqlCollectionCreationRequest extends CreationRequest {
|
||||||
|
properties: {
|
||||||
|
resource: {
|
||||||
|
uniqueKeyPolicy?: UniqueKeyPolicy;
|
||||||
|
id: string;
|
||||||
|
partitionKey: {};
|
||||||
|
analyticalStorageTtl?: number;
|
||||||
|
indexingPolicy?: IndexingPolicy;
|
||||||
|
};
|
||||||
|
options: RpOptions;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface Library extends SparkClusterLibrary {
|
export interface Library extends SparkClusterLibrary {
|
||||||
properties: {
|
properties: {
|
||||||
kind: "Jar";
|
kind: "Jar";
|
||||||
|
|||||||
@@ -1,22 +1,41 @@
|
|||||||
import {
|
|
||||||
QueryMetrics,
|
|
||||||
Resource,
|
|
||||||
StoredProcedureDefinition,
|
|
||||||
TriggerDefinition,
|
|
||||||
UserDefinedFunctionDefinition
|
|
||||||
} from "@azure/cosmos";
|
|
||||||
import Q from "q";
|
|
||||||
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
|
|
||||||
import Explorer from "../Explorer/Explorer";
|
|
||||||
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
|
||||||
import { CassandraTableKey, CassandraTableKeys } from "../Explorer/Tables/TableDataClient";
|
|
||||||
import ConflictId from "../Explorer/Tree/ConflictId";
|
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
|
||||||
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
|
||||||
import Trigger from "../Explorer/Tree/Trigger";
|
|
||||||
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
|
|
||||||
import { UploadDetails } from "../workers/upload/definitions";
|
|
||||||
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 { AccessibleVerticalList } from "../Explorer/Tree/AccessibleVerticalList";
|
||||||
|
import { CassandraTableKey, CassandraTableKeys } from "../Explorer/Tables/TableDataClient";
|
||||||
|
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
|
||||||
|
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 { UploadDetails } from "../workers/upload/definitions";
|
||||||
|
import Explorer from "../Explorer/Explorer";
|
||||||
|
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
|
||||||
|
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
||||||
|
import ConflictsTab from "../Explorer/Tabs/ConflictsTab";
|
||||||
|
import Trigger from "../Explorer/Tree/Trigger";
|
||||||
|
|
||||||
|
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>;
|
||||||
@@ -56,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;
|
||||||
@@ -81,15 +105,15 @@ export interface Database extends TreeNode {
|
|||||||
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
|
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
|
||||||
|
|
||||||
selectDatabase(): void;
|
selectDatabase(): void;
|
||||||
expandDatabase(): Promise<void>;
|
expandDatabase(): void;
|
||||||
collapseDatabase(): void;
|
collapseDatabase(): void;
|
||||||
|
|
||||||
loadCollections(): Promise<void>;
|
loadCollections(): Q.Promise<void>;
|
||||||
findCollectionWithId(collectionRid: string): Collection;
|
findCollectionWithId(collectionRid: string): Collection;
|
||||||
openAddCollection(database: Database, event: MouseEvent): void;
|
openAddCollection(database: Database, event: MouseEvent): void;
|
||||||
onDeleteDatabaseContextMenuClick(source: Database, event: MouseEvent | KeyboardEvent): void;
|
onDeleteDatabaseContextMenuClick(source: Database, event: MouseEvent | KeyboardEvent): void;
|
||||||
|
readSettings(): void;
|
||||||
onSettingsClick: () => void;
|
onSettingsClick: () => void;
|
||||||
loadOffer(): Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionBase extends TreeNode {
|
export interface CollectionBase extends TreeNode {
|
||||||
@@ -159,13 +183,13 @@ export interface Collection extends CollectionBase {
|
|||||||
collapseUserDefinedFunctions(): void;
|
collapseUserDefinedFunctions(): void;
|
||||||
collapseTriggers(): void;
|
collapseTriggers(): void;
|
||||||
|
|
||||||
loadUserDefinedFunctions(): Promise<any>;
|
loadUserDefinedFunctions(): Q.Promise<any>;
|
||||||
loadStoredProcedures(): Promise<any>;
|
loadStoredProcedures(): Q.Promise<any>;
|
||||||
loadTriggers(): Promise<any>;
|
loadTriggers(): Q.Promise<any>;
|
||||||
|
|
||||||
createStoredProcedureNode(data: StoredProcedureDefinition & Resource): StoredProcedure;
|
createStoredProcedureNode(data: DataModels.StoredProcedure): StoredProcedure;
|
||||||
createUserDefinedFunctionNode(data: UserDefinedFunctionDefinition & Resource): UserDefinedFunction;
|
createUserDefinedFunctionNode(data: DataModels.UserDefinedFunction): UserDefinedFunction;
|
||||||
createTriggerNode(data: TriggerDefinition & Resource): Trigger;
|
createTriggerNode(data: DataModels.Trigger): Trigger;
|
||||||
findStoredProcedureWithId(sprocRid: string): StoredProcedure;
|
findStoredProcedureWithId(sprocRid: string): StoredProcedure;
|
||||||
findTriggerWithId(triggerRid: string): Trigger;
|
findTriggerWithId(triggerRid: string): Trigger;
|
||||||
findUserDefinedFunctionWithId(udfRid: string): UserDefinedFunction;
|
findUserDefinedFunctionWithId(udfRid: string): UserDefinedFunction;
|
||||||
@@ -177,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
|
||||||
*/
|
*/
|
||||||
@@ -255,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;
|
||||||
@@ -266,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;
|
||||||
|
|
||||||
@@ -282,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>;
|
||||||
@@ -309,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,
|
||||||
@@ -435,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>;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
export enum DefaultAccountExperienceType {
|
|
||||||
DocumentDB = "DocumentDB",
|
|
||||||
Graph = "Graph",
|
|
||||||
MongoDB = "MongoDB",
|
|
||||||
Table = "Table",
|
|
||||||
Cassandra = "Cassandra",
|
|
||||||
ApiForMongoDB = "Azure Cosmos DB for MongoDB API"
|
|
||||||
}
|
|
||||||
@@ -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());
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ export class ResourceTreeContextMenuButtonFactory {
|
|||||||
const deleteDatabaseMenuItem = {
|
const deleteDatabaseMenuItem = {
|
||||||
iconSrc: DeleteDatabaseIcon,
|
iconSrc: DeleteDatabaseIcon,
|
||||||
onClick: () => container.deleteDatabaseConfirmationPane.open(),
|
onClick: () => container.deleteDatabaseConfirmationPane.open(),
|
||||||
label: container.deleteDatabaseText(),
|
label: container.deleteDatabaseText()
|
||||||
styleClass: "deleteDatabaseMenuItem"
|
|
||||||
};
|
};
|
||||||
return [newCollectionMenuItem, deleteDatabaseMenuItem];
|
return [newCollectionMenuItem, deleteDatabaseMenuItem];
|
||||||
}
|
}
|
||||||
@@ -113,8 +112,7 @@ export class ResourceTreeContextMenuButtonFactory {
|
|||||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
||||||
selectedCollection && selectedCollection.onDeleteCollectionContextMenuClick(selectedCollection, null);
|
selectedCollection && selectedCollection.onDeleteCollectionContextMenuClick(selectedCollection, null);
|
||||||
},
|
},
|
||||||
label: container.deleteCollectionText(),
|
label: container.deleteCollectionText()
|
||||||
styleClass: "deleteCollectionMenuItem"
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { StringUtils } from "../../../Utils/StringUtils";
|
import { StringUtils } from "../../../Utils/StringUtils";
|
||||||
import { KeyCodes } from "../../../Common/Constants";
|
import { KeyCodes } from "../../../Common/Constants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
|
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
|
||||||
|
|
||||||
|
|||||||
@@ -265,9 +265,6 @@ exports[`test render renders with filters 1`] = `
|
|||||||
"buttonTextDisabled": "#a19f9d",
|
"buttonTextDisabled": "#a19f9d",
|
||||||
"buttonTextHovered": "#201f1e",
|
"buttonTextHovered": "#201f1e",
|
||||||
"buttonTextPressed": "#201f1e",
|
"buttonTextPressed": "#201f1e",
|
||||||
"cardShadow": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
|
|
||||||
"cardShadowHovered": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)",
|
|
||||||
"cardStandoutBackground": "#ffffff",
|
|
||||||
"defaultStateBackground": "#faf9f8",
|
"defaultStateBackground": "#faf9f8",
|
||||||
"disabledBackground": "#f3f2f1",
|
"disabledBackground": "#f3f2f1",
|
||||||
"disabledBodySubtext": "#c8c6c4",
|
"disabledBodySubtext": "#c8c6c4",
|
||||||
@@ -607,9 +604,6 @@ exports[`test render renders with filters 1`] = `
|
|||||||
"buttonTextDisabled": "#a19f9d",
|
"buttonTextDisabled": "#a19f9d",
|
||||||
"buttonTextHovered": "#201f1e",
|
"buttonTextHovered": "#201f1e",
|
||||||
"buttonTextPressed": "#201f1e",
|
"buttonTextPressed": "#201f1e",
|
||||||
"cardShadow": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
|
|
||||||
"cardShadowHovered": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)",
|
|
||||||
"cardStandoutBackground": "#ffffff",
|
|
||||||
"defaultStateBackground": "#faf9f8",
|
"defaultStateBackground": "#faf9f8",
|
||||||
"disabledBackground": "#f3f2f1",
|
"disabledBackground": "#f3f2f1",
|
||||||
"disabledBodySubtext": "#c8c6c4",
|
"disabledBodySubtext": "#c8c6c4",
|
||||||
@@ -1003,9 +997,6 @@ exports[`test render renders with filters 1`] = `
|
|||||||
"buttonTextDisabled": "#a19f9d",
|
"buttonTextDisabled": "#a19f9d",
|
||||||
"buttonTextHovered": "#201f1e",
|
"buttonTextHovered": "#201f1e",
|
||||||
"buttonTextPressed": "#201f1e",
|
"buttonTextPressed": "#201f1e",
|
||||||
"cardShadow": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
|
|
||||||
"cardShadowHovered": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)",
|
|
||||||
"cardStandoutBackground": "#ffffff",
|
|
||||||
"defaultStateBackground": "#faf9f8",
|
"defaultStateBackground": "#faf9f8",
|
||||||
"disabledBackground": "#f3f2f1",
|
"disabledBackground": "#f3f2f1",
|
||||||
"disabledBodySubtext": "#c8c6c4",
|
"disabledBodySubtext": "#c8c6c4",
|
||||||
@@ -1122,11 +1113,6 @@ exports[`test render renders with filters 1`] = `
|
|||||||
},
|
},
|
||||||
"iconDisabled": Object {
|
"iconDisabled": Object {
|
||||||
"color": "#a19f9d",
|
"color": "#a19f9d",
|
||||||
"selectors": Object {
|
|
||||||
"@media screen and (-ms-high-contrast: active)": Object {
|
|
||||||
"color": "GrayText",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"label": Array [
|
"label": Array [
|
||||||
Object {
|
Object {
|
||||||
@@ -1148,11 +1134,6 @@ exports[`test render renders with filters 1`] = `
|
|||||||
},
|
},
|
||||||
"menuIconDisabled": Object {
|
"menuIconDisabled": Object {
|
||||||
"color": "#a19f9d",
|
"color": "#a19f9d",
|
||||||
"selectors": Object {
|
|
||||||
"@media screen and (-ms-high-contrast: active)": Object {
|
|
||||||
"color": "GrayText",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"root": Array [
|
"root": Array [
|
||||||
Object {
|
Object {
|
||||||
@@ -1169,6 +1150,7 @@ exports[`test render renders with filters 1`] = `
|
|||||||
"right": 2,
|
"right": 2,
|
||||||
"selectors": Object {
|
"selectors": Object {
|
||||||
"@media screen and (-ms-high-contrast: active)": Object {
|
"@media screen and (-ms-high-contrast: active)": Object {
|
||||||
|
"border": "none",
|
||||||
"bottom": -2,
|
"bottom": -2,
|
||||||
"left": -2,
|
"left": -2,
|
||||||
"outlineColor": "ButtonText",
|
"outlineColor": "ButtonText",
|
||||||
@@ -1248,6 +1230,7 @@ exports[`test render renders with filters 1`] = `
|
|||||||
"right": 2,
|
"right": 2,
|
||||||
"selectors": Object {
|
"selectors": Object {
|
||||||
"@media screen and (-ms-high-contrast: active)": Object {
|
"@media screen and (-ms-high-contrast: active)": Object {
|
||||||
|
"border": "none",
|
||||||
"bottom": -2,
|
"bottom": -2,
|
||||||
"left": -2,
|
"left": -2,
|
||||||
"outlineColor": "ButtonText",
|
"outlineColor": "ButtonText",
|
||||||
@@ -1276,6 +1259,10 @@ exports[`test render renders with filters 1`] = `
|
|||||||
":hover": Object {
|
":hover": Object {
|
||||||
"outline": 0,
|
"outline": 0,
|
||||||
},
|
},
|
||||||
|
"@media screen and (-ms-high-contrast: active)": Object {
|
||||||
|
"borderColor": "grayText",
|
||||||
|
"color": "grayText",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
@@ -1375,21 +1362,13 @@ exports[`test render renders with filters 1`] = `
|
|||||||
"selectors": Object {
|
"selectors": Object {
|
||||||
"@media screen and (-ms-high-contrast: active)": Object {
|
"@media screen and (-ms-high-contrast: active)": Object {
|
||||||
"MsHighContrastAdjust": "none",
|
"MsHighContrastAdjust": "none",
|
||||||
"backgroundColor": "Window",
|
"backgroundColor": "WindowText",
|
||||||
"border": "1px solid WindowText",
|
"color": "Window",
|
||||||
"borderRightWidth": "0",
|
|
||||||
"color": "WindowText",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
".ms-Button--primary + .ms-Button": Object {
|
".ms-Button--primary + .ms-Button": Object {
|
||||||
"border": "none",
|
"border": "none",
|
||||||
"selectors": Object {
|
|
||||||
"@media screen and (-ms-high-contrast: active)": Object {
|
|
||||||
"border": "1px solid WindowText",
|
|
||||||
"borderLeftWidth": "0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1429,9 +1408,6 @@ exports[`test render renders with filters 1`] = `
|
|||||||
"borderColor": "GrayText",
|
"borderColor": "GrayText",
|
||||||
"color": "GrayText",
|
"color": "GrayText",
|
||||||
},
|
},
|
||||||
"@media screen and (forced-colors: active)": Object {
|
|
||||||
"forcedColorAdjust": "none",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"splitButtonContainerFocused": Object {
|
"splitButtonContainerFocused": Object {
|
||||||
@@ -1578,13 +1554,6 @@ exports[`test render renders with filters 1`] = `
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
".ms-Button-menuIcon": Object {
|
|
||||||
"selectors": Object {
|
|
||||||
"@media screen and (-ms-high-contrast: active)": Object {
|
|
||||||
"color": "GrayText",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
":hover": Object {
|
":hover": Object {
|
||||||
"cursor": "default",
|
"cursor": "default",
|
||||||
},
|
},
|
||||||
@@ -1806,9 +1775,6 @@ exports[`test render renders with filters 1`] = `
|
|||||||
"buttonTextDisabled": "#a19f9d",
|
"buttonTextDisabled": "#a19f9d",
|
||||||
"buttonTextHovered": "#201f1e",
|
"buttonTextHovered": "#201f1e",
|
||||||
"buttonTextPressed": "#201f1e",
|
"buttonTextPressed": "#201f1e",
|
||||||
"cardShadow": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
|
|
||||||
"cardShadowHovered": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)",
|
|
||||||
"cardStandoutBackground": "#ffffff",
|
|
||||||
"defaultStateBackground": "#faf9f8",
|
"defaultStateBackground": "#faf9f8",
|
||||||
"disabledBackground": "#f3f2f1",
|
"disabledBackground": "#f3f2f1",
|
||||||
"disabledBodySubtext": "#c8c6c4",
|
"disabledBodySubtext": "#c8c6c4",
|
||||||
|
|||||||
@@ -86,7 +86,6 @@ export class DynamicListViewModel extends WaitsForTemplateViewModel {
|
|||||||
public onRemoveItemKeyPress = (data: any, event: KeyboardEvent, source: any): boolean => {
|
public onRemoveItemKeyPress = (data: any, event: KeyboardEvent, source: any): boolean => {
|
||||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
||||||
this.removeItem(data, event);
|
this.removeItem(data, event);
|
||||||
(document.querySelector(".dynamicListItem:last-of-type input") as HTMLElement).focus();
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -95,7 +94,7 @@ export class DynamicListViewModel extends WaitsForTemplateViewModel {
|
|||||||
|
|
||||||
public addItem(): void {
|
public addItem(): void {
|
||||||
this.listItems.push({ value: ko.observable("") });
|
this.listItems.push({ value: ko.observable("") });
|
||||||
(document.querySelector(".dynamicListItem:last-of-type input") as HTMLElement).focus();
|
document.getElementById("uniqueKeyItems").focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onAddItemKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
public onAddItemKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { RepoListItem } from "./GitHubReposComponent";
|
|||||||
import { ChildrenMargin } from "./GitHubStyleConstants";
|
import { ChildrenMargin } from "./GitHubStyleConstants";
|
||||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||||
import { IGitHubRepo } from "../../../GitHub/GitHubClient";
|
import { IGitHubRepo } from "../../../GitHub/GitHubClient";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import UrlUtility from "../../../Common/UrlUtility";
|
import UrlUtility from "../../../Common/UrlUtility";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
|
||||||
|
|||||||
@@ -66,9 +66,7 @@ export class GitHubReposComponent extends React.Component<GitHubReposComponentPr
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={"firstdivbg headerline"} role="heading" aria-level={2}>
|
<div className={"firstdivbg headerline"}>{header}</div>
|
||||||
{header}
|
|
||||||
</div>
|
|
||||||
<div className={"paneMainContent"}>{content}</div>
|
<div className={"paneMainContent"}>{content}</div>
|
||||||
{!this.props.showAuthorizeAccess && (
|
{!this.props.showAuthorizeAccess && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -5,11 +5,9 @@
|
|||||||
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";
|
||||||
import { userContext } from "../../../UserContext";
|
|
||||||
import { TerminalQueryParams } from "../../../Common/Constants";
|
|
||||||
|
|
||||||
export interface NotebookTerminalComponentProps {
|
export interface NotebookTerminalComponentProps {
|
||||||
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
||||||
@@ -34,11 +32,11 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
|
|||||||
|
|
||||||
public getTerminalParams(): Map<string, string> {
|
public getTerminalParams(): Map<string, string> {
|
||||||
let params: Map<string, string> = new Map<string, string>();
|
let params: Map<string, string> = new Map<string, string>();
|
||||||
params.set(TerminalQueryParams.Terminal, "true");
|
params.set("terminal", "true");
|
||||||
|
|
||||||
const terminalEndpoint: string = this.tryGetTerminalEndpoint();
|
const terminalEndpoint: string = this.tryGetTerminalEndpoint();
|
||||||
if (terminalEndpoint) {
|
if (terminalEndpoint) {
|
||||||
params.set(TerminalQueryParams.TerminalEndpoint, terminalEndpoint);
|
params.set("terminalEndpoint", terminalEndpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
return params;
|
return params;
|
||||||
@@ -77,13 +75,11 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
params.set(TerminalQueryParams.Server, serverInfo.notebookServerEndpoint);
|
params.set("server", serverInfo.notebookServerEndpoint);
|
||||||
if (serverInfo.authToken && serverInfo.authToken.length > 0) {
|
if (serverInfo.authToken && serverInfo.authToken.length > 0) {
|
||||||
params.set(TerminalQueryParams.Token, serverInfo.authToken);
|
params.set("token", serverInfo.authToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
params.set(TerminalQueryParams.SubscriptionId, userContext.subscriptionId);
|
|
||||||
|
|
||||||
let result: string = "terminal.html?";
|
let result: string = "terminal.html?";
|
||||||
for (let key of params.keys()) {
|
for (let key of params.keys()) {
|
||||||
result += `${key}=${encodeURIComponent(params.get(key))}&`;
|
result += `${key}=${encodeURIComponent(params.get(key))}&`;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,20 +134,9 @@ 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));
|
||||||
|
tabs.push(this.createTab(GalleryTab.Published, this.state.publishedNotebooks));
|
||||||
// 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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const pivotProps: IPivotProps = {
|
const pivotProps: IPivotProps = {
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
`;
|
|
||||||
@@ -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>
|
|
||||||
`;
|
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -30,7 +28,6 @@ export interface NotebookViewerComponentProps {
|
|||||||
isFavorite?: boolean;
|
isFavorite?: boolean;
|
||||||
backNavigationText: string;
|
backNavigationText: string;
|
||||||
hideInputs?: boolean;
|
hideInputs?: boolean;
|
||||||
hidePrompts?: boolean;
|
|
||||||
onBackClick: () => void;
|
onBackClick: () => void;
|
||||||
onTagClick: (tag: string) => void;
|
onTagClick: (tag: string) => void;
|
||||||
}
|
}
|
||||||
@@ -88,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 });
|
||||||
@@ -108,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>
|
||||||
@@ -149,8 +134,7 @@ export class NotebookViewerComponent extends React.Component<
|
|||||||
{this.state.showProgressBar && <ProgressIndicator />}
|
{this.state.showProgressBar && <ProgressIndicator />}
|
||||||
|
|
||||||
{this.notebookComponentBootstrapper.renderComponent(NotebookReadOnlyRenderer, {
|
{this.notebookComponentBootstrapper.renderComponent(NotebookReadOnlyRenderer, {
|
||||||
hideInputs: this.props.hideInputs,
|
hideInputs: this.props.hideInputs
|
||||||
hidePrompts: this.props.hidePrompts
|
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{this.state.dialogProps && <DialogComponent {...this.state.dialogProps} />}
|
{this.state.dialogProps && <DialogComponent {...this.state.dialogProps} />}
|
||||||
|
|||||||
@@ -24,13 +24,12 @@ import {
|
|||||||
} from "office-ui-fabric-react/lib/utilities/selection/index";
|
} from "office-ui-fabric-react/lib/utilities/selection/index";
|
||||||
import { StyleConstants } from "../../../Common/Constants";
|
import { StyleConstants } from "../../../Common/Constants";
|
||||||
import { TextField, ITextFieldProps, ITextField } from "office-ui-fabric-react/lib/TextField";
|
import { TextField, ITextFieldProps, ITextField } from "office-ui-fabric-react/lib/TextField";
|
||||||
import * as 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;
|
||||||
|
|||||||
@@ -84,7 +84,7 @@
|
|||||||
step: step,
|
step: step,
|
||||||
'class':'migration collid select-font-size',
|
'class':'migration collid select-font-size',
|
||||||
min: minAutoPilotThroughput,
|
min: minAutoPilotThroughput,
|
||||||
'aria-label': 'Max request units per second',
|
'aria-label': ariaLabel,
|
||||||
type: isAutoscaleThroughputInputFieldRequired() ? 'number' : 'hidden',
|
type: isAutoscaleThroughputInputFieldRequired() ? 'number' : 'hidden',
|
||||||
css: {
|
css: {
|
||||||
dirty: maxAutoPilotThroughputSet.editableIsDirty
|
dirty: maxAutoPilotThroughputSet.editableIsDirty
|
||||||
|
|||||||
12
src/Explorer/Controls/Toolbar/IToolbarAction.ts
Normal file
12
src/Explorer/Controls/Toolbar/IToolbarAction.ts
Normal 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;
|
||||||
18
src/Explorer/Controls/Toolbar/IToolbarDisplayable.ts
Normal file
18
src/Explorer/Controls/Toolbar/IToolbarDisplayable.ts
Normal 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;
|
||||||
56
src/Explorer/Controls/Toolbar/IToolbarDropDown.ts
Normal file
56
src/Explorer/Controls/Toolbar/IToolbarDropDown.ts
Normal 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;
|
||||||
12
src/Explorer/Controls/Toolbar/IToolbarItem.ts
Normal file
12
src/Explorer/Controls/Toolbar/IToolbarItem.ts
Normal 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;
|
||||||
10
src/Explorer/Controls/Toolbar/IToolbarSeperator.ts
Normal file
10
src/Explorer/Controls/Toolbar/IToolbarSeperator.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/*!---------------------------------------------------------
|
||||||
|
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||||
|
*----------------------------------------------------------*/
|
||||||
|
|
||||||
|
interface IToolbarSeperator {
|
||||||
|
type: "separator";
|
||||||
|
visible: ko.Observable<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IToolbarSeperator;
|
||||||
12
src/Explorer/Controls/Toolbar/IToolbarToggle.ts
Normal file
12
src/Explorer/Controls/Toolbar/IToolbarToggle.ts
Normal 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;
|
||||||
58
src/Explorer/Controls/Toolbar/KeyCodes.ts
Normal file
58
src/Explorer/Controls/Toolbar/KeyCodes.ts
Normal 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;
|
||||||
145
src/Explorer/Controls/Toolbar/Toolbar.ts
Normal file
145
src/Explorer/Controls/Toolbar/Toolbar.ts
Normal 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
86
src/Explorer/Controls/Toolbar/ToolbarAction.ts
Normal file
86
src/Explorer/Controls/Toolbar/ToolbarAction.ts
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user