mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-26 20:31:33 +00:00
Compare commits
61 Commits
remove-exp
...
test
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cc291a04f | ||
|
|
92c4440d38 | ||
|
|
1ccffab911 | ||
|
|
dc56f7e154 | ||
|
|
e62184a1f2 | ||
|
|
26c832437b | ||
|
|
832f8d560d | ||
|
|
d85c96d408 | ||
|
|
bad6a60d07 | ||
|
|
b690fe18e6 | ||
|
|
1bbe08378c | ||
|
|
9b021b29b9 | ||
|
|
562ac38ff1 | ||
|
|
949f9203b8 | ||
|
|
de7761ba4b | ||
|
|
40f4efab7c | ||
|
|
34c41e1557 | ||
|
|
03b19fc875 | ||
|
|
d6a4924710 | ||
|
|
5ccf26e403 | ||
|
|
ef7da10b6e | ||
|
|
dfd18152ca | ||
|
|
e22675bc40 | ||
|
|
c4257bf4a9 | ||
|
|
728eeefa17 | ||
|
|
83b13de685 | ||
|
|
c401f88aae | ||
|
|
af820c0fbf | ||
|
|
a2845a0102 | ||
|
|
ed9b443bf6 | ||
|
|
3fe63e88cb | ||
|
|
2de3c07f76 | ||
|
|
53bedb1641 | ||
|
|
e6ac5a7043 | ||
|
|
faf923f647 | ||
|
|
d471cff77c | ||
|
|
0a24a0b73e | ||
|
|
ab4753fd1d | ||
|
|
6bc506b81f | ||
|
|
efff26dbe7 | ||
|
|
fae59d8754 | ||
|
|
c2cd383ece | ||
|
|
83c120a549 | ||
|
|
a28dede88d | ||
|
|
92073a5646 | ||
|
|
5c84b3a7d4 | ||
|
|
3223ff7685 | ||
|
|
38732af907 | ||
|
|
e837f574a8 | ||
|
|
47a5c315b5 | ||
|
|
1c80ced259 | ||
|
|
5e6ac78b7d | ||
|
|
999196193f | ||
|
|
951289e190 | ||
|
|
3279460cfd | ||
|
|
07b9c1d1b7 | ||
|
|
dde2ca75c4 | ||
|
|
f44a3da568 | ||
|
|
22b2e1df48 | ||
|
|
2752d6af00 | ||
|
|
cb5fe5316e |
@@ -4,3 +4,4 @@ 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,10 +266,6 @@ 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
|
||||||
@@ -279,8 +275,6 @@ 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
|
||||||
@@ -418,6 +412,5 @@ 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
|
||||||
@@ -41,6 +41,7 @@ module.exports = {
|
|||||||
"@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 }]
|
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
|
||||||
|
eqeqeq: "error"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
20
.github/workflows/ci.yml
vendored
20
.github/workflows/ci.yml
vendored
@@ -196,6 +196,26 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||||
|
endtoendpuppeteer:
|
||||||
|
name: "End to end puppeteer tests"
|
||||||
|
needs: [lint, format, compile, unittest]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Use Node.js 12.x
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 12.x
|
||||||
|
- name: End to End Puppeteer Tests
|
||||||
|
run: |
|
||||||
|
npm ci
|
||||||
|
npm start &
|
||||||
|
npm run wait-for-server
|
||||||
|
npm run test:e2e
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||||
|
PORTAL_RUNNER_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }}
|
||||||
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' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
export class AddDbUtilities {
|
|
||||||
createGremlinDatabase(params: any) {
|
|
||||||
return Promise.resolve(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
"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 --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 chrome --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 edge --headless",
|
||||||
"test:debug": "cypress open"
|
"test:debug": "cypress open"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ const isCI = require("is-ci");
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
launch: {
|
launch: {
|
||||||
headless: isCI,
|
headless: isCI,
|
||||||
slowMo: isCI ? null : 20,
|
slowMo: 50,
|
||||||
defaultViewport: null
|
defaultViewport: null,
|
||||||
|
ignoreHTTPSErrors: true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
|
|
||||||
@SelectionColor: #3074B0;
|
@SelectionColor: #3074B0;
|
||||||
|
|
||||||
@FocusColor: #00bcf2;
|
@FocusColor: #605e5c;
|
||||||
|
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
METRICS
|
METRICS
|
||||||
|
|||||||
@@ -1522,6 +1522,10 @@ p {
|
|||||||
.tooltipVisible();
|
.tooltipVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.infoTooltip a {
|
||||||
|
color: @AccentHigh;
|
||||||
|
}
|
||||||
|
|
||||||
.nowrap {
|
.nowrap {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
@@ -1646,7 +1650,7 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.contextual-pane .collid {
|
.contextual-pane .collid {
|
||||||
border: 1px solid #bbbbbb;
|
border: 1px solid #605e5c;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
color: #000;
|
color: #000;
|
||||||
@@ -1739,7 +1743,7 @@ input::-webkit-calendar-picker-indicator {
|
|||||||
padding-right: 34px;
|
padding-right: 34px;
|
||||||
color: @BaseDark;
|
color: @BaseDark;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: auto;
|
||||||
margin: (2 * @MediumSpace) 0px;
|
margin: (2 * @MediumSpace) 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2423,22 +2427,6 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -2988,6 +2976,10 @@ settings-pane {
|
|||||||
.enableAnalyticalStorageRadio:nth-child(n+2) {
|
.enableAnalyticalStorageRadio:nth-child(n+2) {
|
||||||
margin-left: @LargeSpace;
|
margin-left: @LargeSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.enableAnalyticalStorageRadioLabel {
|
||||||
|
padding: 0px
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.addCollectionLabel {
|
.addCollectionLabel {
|
||||||
@@ -3017,4 +3009,12 @@ settings-pane {
|
|||||||
|
|
||||||
.italic {
|
.italic {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.warningErrorContent a {
|
||||||
|
color: @AccentMediumHigh
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoBoxContent a {
|
||||||
|
color: @AccentMediumHigh
|
||||||
|
}
|
||||||
|
|||||||
884
package-lock.json
generated
884
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@@ -4,11 +4,11 @@
|
|||||||
"description": "Cosmos Explorer",
|
"description": "Cosmos Explorer",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/cosmos": "3.7.4",
|
"@azure/cosmos": "3.9.0",
|
||||||
"@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.4",
|
"@microsoft/applicationinsights-web": "2.5.8",
|
||||||
"@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",
|
||||||
@@ -66,7 +66,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.121.10",
|
"office-ui-fabric-react": "7.134.1",
|
||||||
"p-retry": "4.2.0",
|
"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",
|
||||||
@@ -102,12 +102,15 @@
|
|||||||
"@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",
|
||||||
@@ -118,8 +121,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": "3.2.0",
|
"@typescript-eslint/eslint-plugin": "4.0.1",
|
||||||
"@typescript-eslint/parser": "3.2.0",
|
"@typescript-eslint/parser": "4.0.1",
|
||||||
"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",
|
||||||
@@ -132,7 +135,7 @@
|
|||||||
"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.3.1",
|
"eslint": "7.8.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-prefer-arrow": "1.2.2",
|
||||||
@@ -164,8 +167,9 @@
|
|||||||
"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": "3.9.6",
|
"typescript": "4.0.2",
|
||||||
"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",
|
||||||
@@ -184,6 +188,7 @@
|
|||||||
"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",
|
||||||
"rupmEnabled": false,
|
"createNewDatabase": true,
|
||||||
"partitionKey": { "kind": "Hash", "paths": ["/firstname"] },
|
"partitionKey": { "kind": "Hash", "paths": ["/firstname"], "version": 1 },
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{
|
||||||
"firstname": "Eva",
|
"firstname": "Eva",
|
||||||
@@ -23,4 +23,4 @@
|
|||||||
"age": 23
|
"age": 23
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ 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 {
|
||||||
|
|||||||
@@ -1,33 +1,26 @@
|
|||||||
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,
|
||||||
PartitionKeyDefinition,
|
OfferDefinition,
|
||||||
QueryIterator,
|
QueryIterator,
|
||||||
Resource,
|
Resource
|
||||||
TriggerDefinition
|
|
||||||
} from "@azure/cosmos";
|
} from "@azure/cosmos";
|
||||||
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
|
|
||||||
import { client } from "./CosmosClient";
|
|
||||||
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
|
|
||||||
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
|
|
||||||
import { sendCachedDataMessage } from "./MessageHandler";
|
|
||||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
|
||||||
import { OfferUtils } from "../Utils/OfferUtils";
|
|
||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
import Q from "q";
|
||||||
import { Platform, configContext } from "../ConfigContext";
|
import { configContext, Platform } from "../ConfigContext";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
|
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import ConflictId from "../Explorer/Tree/ConflictId";
|
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 { OfferUtils } from "../Utils/OfferUtils";
|
||||||
|
import * as Constants from "./Constants";
|
||||||
|
import { client } from "./CosmosClient";
|
||||||
|
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);
|
||||||
@@ -60,85 +53,55 @@ export function queryDocuments(
|
|||||||
return Q(documentsIterator);
|
return Q(documentsIterator);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readStoredProcedures(
|
export function getPartitionKeyHeaderForConflict(conflictId: ConflictId): Object {
|
||||||
collection: ViewModels.Collection,
|
const partitionKeyDefinition: DataModels.PartitionKey = conflictId.partitionKey;
|
||||||
options?: any
|
const partitionKeyValue: any = conflictId.partitionKeyValue;
|
||||||
): Q.Promise<DataModels.StoredProcedure[]> {
|
|
||||||
|
return getPartitionKeyHeader(partitionKeyDefinition, partitionKeyValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPartitionKeyHeader(partitionKeyDefinition: DataModels.PartitionKey, partitionKeyValue: any): Object {
|
||||||
|
if (!partitionKeyDefinition) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (partitionKeyValue === undefined) {
|
||||||
|
return [{}];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [partitionKeyValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateOffer(
|
||||||
|
offer: DataModels.Offer,
|
||||||
|
newOffer: DataModels.Offer,
|
||||||
|
options?: RequestOptions
|
||||||
|
): Q.Promise<DataModels.Offer> {
|
||||||
return Q(
|
return Q(
|
||||||
client()
|
client()
|
||||||
.database(collection.databaseId)
|
.offer(offer.id)
|
||||||
.container(collection.id())
|
// TODO Remove casting when SDK types are fixed (https://github.com/Azure/azure-sdk-for-js/issues/10660)
|
||||||
.scripts.storedProcedures.readAll(options)
|
.replace((newOffer as unknown) as OfferDefinition, options)
|
||||||
.fetchAll()
|
.then(response => {
|
||||||
.then(response => response.resources as DataModels.StoredProcedure[])
|
return Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => response.resource);
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readStoredProcedure(
|
export function updateDocument(
|
||||||
collection: ViewModels.Collection,
|
collection: ViewModels.CollectionBase,
|
||||||
requestedResource: DataModels.Resource,
|
documentId: DocumentId,
|
||||||
options?: any
|
newDocument: any
|
||||||
): Q.Promise<DataModels.StoredProcedure> {
|
): Q.Promise<any> {
|
||||||
return Q(
|
const partitionKey = documentId.partitionKeyValue;
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.scripts.storedProcedure(requestedResource.id)
|
|
||||||
.read(options)
|
|
||||||
.then(response => response.resource as DataModels.StoredProcedure)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
export function readUserDefinedFunctions(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
options: any
|
|
||||||
): Q.Promise<DataModels.UserDefinedFunction[]> {
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.scripts.userDefinedFunctions.readAll(options)
|
|
||||||
.fetchAll()
|
|
||||||
.then(response => response.resources as DataModels.UserDefinedFunction[])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
export function readUserDefinedFunction(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
requestedResource: DataModels.Resource,
|
|
||||||
options?: any
|
|
||||||
): Q.Promise<DataModels.UserDefinedFunction> {
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.scripts.userDefinedFunction(requestedResource.id)
|
|
||||||
.read(options)
|
|
||||||
.then(response => response.resource as DataModels.UserDefinedFunction)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readTriggers(collection: ViewModels.Collection, options: any): Q.Promise<DataModels.Trigger[]> {
|
|
||||||
return Q(
|
return Q(
|
||||||
client()
|
client()
|
||||||
.database(collection.databaseId)
|
.database(collection.databaseId)
|
||||||
.container(collection.id())
|
.container(collection.id())
|
||||||
.scripts.triggers.readAll(options)
|
.item(documentId.id(), partitionKey)
|
||||||
.fetchAll()
|
.replace(newDocument)
|
||||||
.then(response => response.resources as DataModels.Trigger[])
|
.then(response => response.resource)
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readTrigger(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
requestedResource: DataModels.Resource,
|
|
||||||
options?: any
|
|
||||||
): Q.Promise<DataModels.Trigger> {
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.scripts.trigger(requestedResource.id)
|
|
||||||
.read(options)
|
|
||||||
.then(response => response.resource as DataModels.Trigger)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +133,16 @@ export function executeStoredProcedure(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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> {
|
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
||||||
const partitionKey = documentId.partitionKeyValue;
|
const partitionKey = documentId.partitionKeyValue;
|
||||||
|
|
||||||
@@ -183,171 +156,6 @@ export function readDocument(collection: ViewModels.CollectionBase, documentId:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
|
||||||
return [{}];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [partitionKeyValue];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateCollection(
|
|
||||||
databaseId: string,
|
|
||||||
collectionId: string,
|
|
||||||
newCollection: DataModels.Collection,
|
|
||||||
options: any = {}
|
|
||||||
): Q.Promise<DataModels.Collection> {
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(databaseId)
|
|
||||||
.container(collectionId)
|
|
||||||
.replace(newCollection as ContainerDefinition, options)
|
|
||||||
.then(async (response: ContainerResponse) => {
|
|
||||||
return refreshCachedResources().then(() => response.resource as DataModels.Collection);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateDocument(
|
|
||||||
collection: ViewModels.CollectionBase,
|
|
||||||
documentId: DocumentId,
|
|
||||||
newDocument: any
|
|
||||||
): Q.Promise<any> {
|
|
||||||
const partitionKey = documentId.partitionKeyValue;
|
|
||||||
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.item(documentId.id(), partitionKey)
|
|
||||||
.replace(newDocument)
|
|
||||||
.then(response => response.resource)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateOffer(
|
|
||||||
offer: DataModels.Offer,
|
|
||||||
newOffer: DataModels.Offer,
|
|
||||||
options?: RequestOptions
|
|
||||||
): Q.Promise<DataModels.Offer> {
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.offer(offer.id)
|
|
||||||
.replace(newOffer, options)
|
|
||||||
.then(response => {
|
|
||||||
return Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => response.resource);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateStoredProcedure(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
storedProcedure: DataModels.StoredProcedure,
|
|
||||||
options: any
|
|
||||||
): Q.Promise<DataModels.StoredProcedure> {
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.scripts.storedProcedure(storedProcedure.id)
|
|
||||||
.replace(storedProcedure, options)
|
|
||||||
.then(response => response.resource as DataModels.StoredProcedure)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateUserDefinedFunction(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
userDefinedFunction: DataModels.UserDefinedFunction,
|
|
||||||
options?: any
|
|
||||||
): Q.Promise<DataModels.UserDefinedFunction> {
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.scripts.userDefinedFunction(userDefinedFunction.id)
|
|
||||||
.replace(userDefinedFunction, options)
|
|
||||||
.then(response => response.resource as DataModels.StoredProcedure)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateTrigger(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
trigger: DataModels.Trigger,
|
|
||||||
options?: any
|
|
||||||
): Q.Promise<DataModels.Trigger> {
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.scripts.trigger(trigger.id)
|
|
||||||
.replace(trigger as TriggerDefinition, options)
|
|
||||||
.then(response => response.resource as DataModels.Trigger)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 as DataModels.StoredProcedure)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createStoredProcedure(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
newStoredProcedure: DataModels.StoredProcedure,
|
|
||||||
options?: any
|
|
||||||
): Q.Promise<DataModels.StoredProcedure> {
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.scripts.storedProcedures.create(newStoredProcedure, options)
|
|
||||||
.then(response => response.resource as DataModels.StoredProcedure)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createUserDefinedFunction(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
newUserDefinedFunction: DataModels.UserDefinedFunction,
|
|
||||||
options: any
|
|
||||||
): Q.Promise<DataModels.UserDefinedFunction> {
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.scripts.userDefinedFunctions.create(newUserDefinedFunction, options)
|
|
||||||
.then(response => response.resource as DataModels.UserDefinedFunction)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createTrigger(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
newTrigger: DataModels.Trigger,
|
|
||||||
options?: any
|
|
||||||
): Q.Promise<DataModels.Trigger> {
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.scripts.triggers.create(newTrigger as TriggerDefinition, options)
|
|
||||||
.then(response => response.resource as DataModels.Trigger)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
||||||
const partitionKey = documentId.partitionKeyValue;
|
const partitionKey = documentId.partitionKeyValue;
|
||||||
|
|
||||||
@@ -376,48 +184,6 @@ export function deleteConflict(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteStoredProcedure(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
storedProcedure: DataModels.StoredProcedure,
|
|
||||||
options: any
|
|
||||||
): Q.Promise<any> {
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.scripts.storedProcedure(storedProcedure.id)
|
|
||||||
.delete()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteUserDefinedFunction(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
userDefinedFunction: DataModels.UserDefinedFunction,
|
|
||||||
options: any
|
|
||||||
): Q.Promise<any> {
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.scripts.userDefinedFunction(userDefinedFunction.id)
|
|
||||||
.delete()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteTrigger(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
trigger: DataModels.Trigger,
|
|
||||||
options: any
|
|
||||||
): Q.Promise<any> {
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.scripts.trigger(trigger.id)
|
|
||||||
.delete()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readCollectionQuotaInfo(
|
export function readCollectionQuotaInfo(
|
||||||
collection: ViewModels.Collection,
|
collection: ViewModels.Collection,
|
||||||
options: any
|
options: any
|
||||||
@@ -454,6 +220,10 @@ export function readCollectionQuotaInfo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function readOffers(options: any): Q.Promise<DataModels.Offer[]> {
|
export function readOffers(options: any): Q.Promise<DataModels.Offer[]> {
|
||||||
|
if (options.isServerless) {
|
||||||
|
return Q([]); // Reading offers is not supported for serverless accounts
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (configContext.platform === Platform.Portal) {
|
if (configContext.platform === Platform.Portal) {
|
||||||
return sendCachedDataMessage<DataModels.Offer[]>(MessageTypes.AllOffers, [
|
return sendCachedDataMessage<DataModels.Offer[]>(MessageTypes.AllOffers, [
|
||||||
@@ -469,6 +239,13 @@ export function readOffers(options: any): Q.Promise<DataModels.Offer[]> {
|
|||||||
.offers.readAll()
|
.offers.readAll()
|
||||||
.fetchAll()
|
.fetchAll()
|
||||||
.then(response => response.resources)
|
.then(response => response.resources)
|
||||||
|
.catch(error => {
|
||||||
|
// This should be removed when we can correctly identify if an account is serverless when connected using connection string too.
|
||||||
|
if (error.message.includes("Reading or replacing offers is not supported for serverless accounts")) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,89 +264,6 @@ export function readOffer(requestedResource: DataModels.Offer, options: any): Q.
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function 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(
|
|
||||||
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(() => refreshCachedResources(options))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createDatabase(
|
|
||||||
request: DataModels.CreateDatabaseRequest,
|
|
||||||
options: any
|
|
||||||
): Q.Promise<DataModels.Database> {
|
|
||||||
var deferred = Q.defer<DataModels.Database>();
|
|
||||||
|
|
||||||
_createDatabase(request, options).then(
|
|
||||||
(createdDatabase: DataModels.Database) => {
|
|
||||||
refreshCachedOffers().then(() => {
|
|
||||||
deferred.resolve(createdDatabase);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
_createDatabaseError => {
|
|
||||||
deferred.reject(_createDatabaseError);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function refreshCachedOffers(): Q.Promise<void> {
|
export function refreshCachedOffers(): Q.Promise<void> {
|
||||||
if (configContext.platform === Platform.Portal) {
|
if (configContext.platform === Platform.Portal) {
|
||||||
return sendCachedDataMessage(MessageTypes.RefreshOffers, []);
|
return sendCachedDataMessage(MessageTypes.RefreshOffers, []);
|
||||||
@@ -598,33 +292,3 @@ export function queryConflicts(
|
|||||||
.conflicts.query(query, options);
|
.conflicts.query(query, options);
|
||||||
return Q(documentsIterator);
|
return Q(documentsIterator);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _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(
|
|
||||||
client()
|
|
||||||
.databases.create(createBody, databaseOptions)
|
|
||||||
.then((response: DatabaseResponse) => {
|
|
||||||
return refreshCachedResources(databaseOptions).then(() => response.resource);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
import * as Constants from "./Constants";
|
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
|
||||||
import * as ErrorParserUtility from "./ErrorParserUtility";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
|
||||||
import Q from "q";
|
|
||||||
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||||
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
|
||||||
import * as DataAccessUtilityBase from "./DataAccessUtilityBase";
|
|
||||||
import * as Logger from "./Logger";
|
|
||||||
import { MinimalQueryIterator, nextPage } from "./IteratorUtilities";
|
|
||||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
|
||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
import Q from "q";
|
||||||
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import ConflictId from "../Explorer/Tree/ConflictId";
|
import ConflictId from "../Explorer/Tree/ConflictId";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
|
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
||||||
|
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
|
||||||
|
import * as Constants from "./Constants";
|
||||||
import { sendNotificationForError } from "./dataAccess/sendNotificationForError";
|
import { sendNotificationForError } from "./dataAccess/sendNotificationForError";
|
||||||
|
import * as DataAccessUtilityBase from "./DataAccessUtilityBase";
|
||||||
|
import { MinimalQueryIterator, nextPage } from "./IteratorUtilities";
|
||||||
|
import * as Logger from "./Logger";
|
||||||
|
|
||||||
// TODO: Log all promise resolutions and errors with verbosity levels
|
// TODO: Log all promise resolutions and errors with verbosity levels
|
||||||
export function queryDocuments(
|
export function queryDocuments(
|
||||||
@@ -43,121 +41,6 @@ export function getEntityName() {
|
|||||||
return "item";
|
return "item";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readStoredProcedures(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
options: any = {}
|
|
||||||
): Q.Promise<DataModels.StoredProcedure[]> {
|
|
||||||
var deferred = Q.defer<DataModels.StoredProcedure[]>();
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Querying stored procedures for container ${collection.id()}`
|
|
||||||
);
|
|
||||||
DataAccessUtilityBase.readStoredProcedures(collection, options)
|
|
||||||
.then(
|
|
||||||
(storedProcedures: DataModels.StoredProcedure[]) => {
|
|
||||||
deferred.resolve(storedProcedures);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to query stored procedures for container ${collection.id()}: ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "ReadStoredProcedures", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readStoredProcedure(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
requestedResource: DataModels.Resource,
|
|
||||||
options?: any
|
|
||||||
): Q.Promise<DataModels.StoredProcedure> {
|
|
||||||
return DataAccessUtilityBase.readStoredProcedure(collection, requestedResource, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readUserDefinedFunctions(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
options: any = {}
|
|
||||||
): Q.Promise<DataModels.UserDefinedFunction[]> {
|
|
||||||
var deferred = Q.defer<DataModels.UserDefinedFunction[]>();
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Querying user defined functions for collection ${collection.id()}`
|
|
||||||
);
|
|
||||||
DataAccessUtilityBase.readUserDefinedFunctions(collection, options)
|
|
||||||
.then(
|
|
||||||
(userDefinedFunctions: DataModels.UserDefinedFunction[]) => {
|
|
||||||
deferred.resolve(userDefinedFunctions);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to query user defined functions for container ${collection.id()}: ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "ReadUDFs", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readUserDefinedFunction(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
requestedResource: DataModels.Resource,
|
|
||||||
options: any
|
|
||||||
): Q.Promise<DataModels.UserDefinedFunction> {
|
|
||||||
return DataAccessUtilityBase.readUserDefinedFunction(collection, requestedResource, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readTriggers(collection: ViewModels.Collection, options: any): Q.Promise<DataModels.Trigger[]> {
|
|
||||||
var deferred = Q.defer<DataModels.Trigger[]>();
|
|
||||||
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Querying triggers for container ${collection.id()}`
|
|
||||||
);
|
|
||||||
DataAccessUtilityBase.readTriggers(collection, options)
|
|
||||||
.then(
|
|
||||||
(triggers: DataModels.Trigger[]) => {
|
|
||||||
deferred.resolve(triggers);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to query triggers for container ${collection.id()}: ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "ReadTriggers", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readTrigger(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
requestedResource: DataModels.Resource,
|
|
||||||
options?: any
|
|
||||||
): Q.Promise<DataModels.Trigger> {
|
|
||||||
return DataAccessUtilityBase.readTrigger(collection, requestedResource, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function executeStoredProcedure(
|
export function executeStoredProcedure(
|
||||||
collection: ViewModels.Collection,
|
collection: ViewModels.Collection,
|
||||||
storedProcedure: StoredProcedure,
|
storedProcedure: StoredProcedure,
|
||||||
@@ -166,22 +49,17 @@ export function executeStoredProcedure(
|
|||||||
): Q.Promise<any> {
|
): Q.Promise<any> {
|
||||||
var deferred = Q.defer<any>();
|
var deferred = Q.defer<any>();
|
||||||
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
const clearMessage = logConsoleProgress(`Executing stored procedure ${storedProcedure.id()}`);
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Executing stored procedure ${storedProcedure.id()}`
|
|
||||||
);
|
|
||||||
DataAccessUtilityBase.executeStoredProcedure(collection, storedProcedure, partitionKeyValue, params)
|
DataAccessUtilityBase.executeStoredProcedure(collection, storedProcedure, partitionKeyValue, params)
|
||||||
.then(
|
.then(
|
||||||
(response: any) => {
|
(response: any) => {
|
||||||
deferred.resolve(response);
|
deferred.resolve(response);
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
logConsoleInfo(
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Finished executing stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
|
`Finished executing stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
logConsoleError(
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}: ${JSON.stringify(
|
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}: ${JSON.stringify(
|
||||||
error
|
error
|
||||||
)}`
|
)}`
|
||||||
@@ -192,7 +70,7 @@ export function executeStoredProcedure(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
clearMessage();
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
@@ -206,32 +84,23 @@ export function queryDocumentsPage(
|
|||||||
): Q.Promise<ViewModels.QueryResults> {
|
): Q.Promise<ViewModels.QueryResults> {
|
||||||
var deferred = Q.defer<ViewModels.QueryResults>();
|
var deferred = Q.defer<ViewModels.QueryResults>();
|
||||||
const entityName = getEntityName();
|
const entityName = getEntityName();
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
const clearMessage = logConsoleProgress(`Querying ${entityName} for container ${resourceName}`);
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Querying ${entityName} for container ${resourceName}`
|
|
||||||
);
|
|
||||||
Q(nextPage(documentsIterator, firstItemIndex))
|
Q(nextPage(documentsIterator, firstItemIndex))
|
||||||
.then(
|
.then(
|
||||||
(result: ViewModels.QueryResults) => {
|
(result: ViewModels.QueryResults) => {
|
||||||
const itemCount = (result.documents && result.documents.length) || 0;
|
const itemCount = (result.documents && result.documents.length) || 0;
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
logConsoleInfo(`Successfully fetched ${itemCount} ${entityName} for container ${resourceName}`);
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully fetched ${itemCount} ${entityName} for container ${resourceName}`
|
|
||||||
);
|
|
||||||
deferred.resolve(result);
|
deferred.resolve(result);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
logConsoleError(`Failed to query ${entityName} for container ${resourceName}: ${JSON.stringify(error)}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to query ${entityName} for container ${resourceName}: ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "QueryDocumentsPage", error.code);
|
Logger.logError(JSON.stringify(error), "QueryDocumentsPage", error.code);
|
||||||
sendNotificationForError(error);
|
sendNotificationForError(error);
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
clearMessage();
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
@@ -240,63 +109,21 @@ export function queryDocumentsPage(
|
|||||||
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
||||||
var deferred = Q.defer<any>();
|
var deferred = Q.defer<any>();
|
||||||
const entityName = getEntityName();
|
const entityName = getEntityName();
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
const clearMessage = logConsoleProgress(`Reading ${entityName} ${documentId.id()}`);
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Reading ${entityName} ${documentId.id()}`
|
|
||||||
);
|
|
||||||
DataAccessUtilityBase.readDocument(collection, documentId)
|
DataAccessUtilityBase.readDocument(collection, documentId)
|
||||||
.then(
|
.then(
|
||||||
(document: any) => {
|
(document: any) => {
|
||||||
deferred.resolve(document);
|
deferred.resolve(document);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
logConsoleError(`Failed to read ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to read ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "ReadDocument", error.code);
|
Logger.logError(JSON.stringify(error), "ReadDocument", error.code);
|
||||||
sendNotificationForError(error);
|
sendNotificationForError(error);
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
clearMessage();
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateCollection(
|
|
||||||
databaseId: string,
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
newCollection: DataModels.Collection
|
|
||||||
): Q.Promise<DataModels.Collection> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Updating container ${collection.id()}`
|
|
||||||
);
|
|
||||||
DataAccessUtilityBase.updateCollection(databaseId, collection.id(), newCollection)
|
|
||||||
.then(
|
|
||||||
(replacedCollection: DataModels.Collection) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully updated container ${collection.id()}`
|
|
||||||
);
|
|
||||||
deferred.resolve(replacedCollection);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to update container ${collection.id()}: ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "UpdateCollection", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
@@ -309,31 +136,22 @@ export function updateDocument(
|
|||||||
): Q.Promise<any> {
|
): Q.Promise<any> {
|
||||||
var deferred = Q.defer<any>();
|
var deferred = Q.defer<any>();
|
||||||
const entityName = getEntityName();
|
const entityName = getEntityName();
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
const clearMessage = logConsoleProgress(`Updating ${entityName} ${documentId.id()}`);
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Updating ${entityName} ${documentId.id()}`
|
|
||||||
);
|
|
||||||
DataAccessUtilityBase.updateDocument(collection, documentId, newDocument)
|
DataAccessUtilityBase.updateDocument(collection, documentId, newDocument)
|
||||||
.then(
|
.then(
|
||||||
(updatedDocument: any) => {
|
(updatedDocument: any) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully updated ${entityName} ${documentId.id()}`
|
|
||||||
);
|
|
||||||
deferred.resolve(updatedDocument);
|
deferred.resolve(updatedDocument);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
logConsoleError(`Failed to update ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to update ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "UpdateDocument", error.code);
|
Logger.logError(JSON.stringify(error), "UpdateDocument", error.code);
|
||||||
sendNotificationForError(error);
|
sendNotificationForError(error);
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
clearMessage();
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
@@ -345,24 +163,15 @@ export function updateOffer(
|
|||||||
options: RequestOptions
|
options: RequestOptions
|
||||||
): Q.Promise<DataModels.Offer> {
|
): Q.Promise<DataModels.Offer> {
|
||||||
var deferred = Q.defer<any>();
|
var deferred = Q.defer<any>();
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
const clearMessage = logConsoleProgress(`Updating offer for resource ${offer.resource}`);
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Updating offer for resource ${offer.resource}`
|
|
||||||
);
|
|
||||||
DataAccessUtilityBase.updateOffer(offer, newOffer, options)
|
DataAccessUtilityBase.updateOffer(offer, newOffer, options)
|
||||||
.then(
|
.then(
|
||||||
(replacedOffer: DataModels.Offer) => {
|
(replacedOffer: DataModels.Offer) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
logConsoleInfo(`Successfully updated offer for resource ${offer.resource}`);
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully updated offer for resource ${offer.resource}`
|
|
||||||
);
|
|
||||||
deferred.resolve(replacedOffer);
|
deferred.resolve(replacedOffer);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
logConsoleError(`Error updating offer for resource ${offer.resource}: ${JSON.stringify(error)}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error updating offer for resource ${offer.resource}: ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(
|
Logger.logError(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
oldOffer: offer,
|
oldOffer: offer,
|
||||||
@@ -377,108 +186,7 @@ export function updateOffer(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
clearMessage();
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateStoredProcedure(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
storedProcedure: DataModels.StoredProcedure,
|
|
||||||
options?: any
|
|
||||||
): Q.Promise<DataModels.StoredProcedure> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Updating stored procedure ${storedProcedure.id}`
|
|
||||||
);
|
|
||||||
DataAccessUtilityBase.updateStoredProcedure(collection, storedProcedure, options)
|
|
||||||
.then(
|
|
||||||
(updatedStoredProcedure: DataModels.StoredProcedure) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully updated stored procedure ${storedProcedure.id}`
|
|
||||||
);
|
|
||||||
deferred.resolve(updatedStoredProcedure);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error while updating stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "UpdateStoredProcedure", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateUserDefinedFunction(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
userDefinedFunction: DataModels.UserDefinedFunction,
|
|
||||||
options: any = {}
|
|
||||||
): Q.Promise<DataModels.UserDefinedFunction> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Updating user defined function ${userDefinedFunction.id}`
|
|
||||||
);
|
|
||||||
DataAccessUtilityBase.updateUserDefinedFunction(collection, userDefinedFunction, options)
|
|
||||||
.then(
|
|
||||||
(updatedUserDefinedFunction: DataModels.UserDefinedFunction) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully updated user defined function ${userDefinedFunction.id}`
|
|
||||||
);
|
|
||||||
deferred.resolve(updatedUserDefinedFunction);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error while updating user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "UpdateUDF", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateTrigger(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
trigger: DataModels.Trigger
|
|
||||||
): Q.Promise<DataModels.Trigger> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, `Updating trigger ${trigger.id}`);
|
|
||||||
DataAccessUtilityBase.updateTrigger(collection, trigger)
|
|
||||||
.then(
|
|
||||||
(updatedTrigger: DataModels.Trigger) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, `Updated trigger ${trigger.id}`);
|
|
||||||
deferred.resolve(updatedTrigger);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error while updating trigger ${trigger.id}:\n ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "UpdateTrigger", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
@@ -487,22 +195,15 @@ export function updateTrigger(
|
|||||||
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
|
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
|
||||||
var deferred = Q.defer<any>();
|
var deferred = Q.defer<any>();
|
||||||
const entityName = getEntityName();
|
const entityName = getEntityName();
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
const clearMessage = logConsoleProgress(`Creating new ${entityName} for container ${collection.id()}`);
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Creating new ${entityName} for container ${collection.id()}`
|
|
||||||
);
|
|
||||||
DataAccessUtilityBase.createDocument(collection, newDocument)
|
DataAccessUtilityBase.createDocument(collection, newDocument)
|
||||||
.then(
|
.then(
|
||||||
(savedDocument: any) => {
|
(savedDocument: any) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
logConsoleInfo(`Successfully created new ${entityName} for container ${collection.id()}`);
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully created new ${entityName} for container ${collection.id()}`
|
|
||||||
);
|
|
||||||
deferred.resolve(savedDocument);
|
deferred.resolve(savedDocument);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
logConsoleError(
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error while creating new ${entityName} for container ${collection.id()}:\n ${JSON.stringify(error)}`
|
`Error while creating new ${entityName} for container ${collection.id()}:\n ${JSON.stringify(error)}`
|
||||||
);
|
);
|
||||||
Logger.logError(JSON.stringify(error), "CreateDocument", error.code);
|
Logger.logError(JSON.stringify(error), "CreateDocument", error.code);
|
||||||
@@ -511,115 +212,7 @@ export function createDocument(collection: ViewModels.CollectionBase, newDocumen
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
clearMessage();
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createStoredProcedure(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
newStoredProcedure: DataModels.StoredProcedure,
|
|
||||||
options?: any
|
|
||||||
): Q.Promise<DataModels.StoredProcedure> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Creating stored procedure for container ${collection.id()}`
|
|
||||||
);
|
|
||||||
DataAccessUtilityBase.createStoredProcedure(collection, newStoredProcedure, options)
|
|
||||||
.then(
|
|
||||||
(createdStoredProcedure: DataModels.StoredProcedure) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully created stored procedure for container ${collection.id()}`
|
|
||||||
);
|
|
||||||
deferred.resolve(createdStoredProcedure);
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error while creating stored procedure for container ${collection.id()}:\n ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "CreateStoredProcedure", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createUserDefinedFunction(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
newUserDefinedFunction: DataModels.UserDefinedFunction,
|
|
||||||
options?: any
|
|
||||||
): Q.Promise<DataModels.UserDefinedFunction> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Creating user defined function for container ${collection.id()}`
|
|
||||||
);
|
|
||||||
DataAccessUtilityBase.createUserDefinedFunction(collection, newUserDefinedFunction, options)
|
|
||||||
.then(
|
|
||||||
(createdUserDefinedFunction: DataModels.UserDefinedFunction) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully created user defined function for container ${collection.id()}`
|
|
||||||
);
|
|
||||||
deferred.resolve(createdUserDefinedFunction);
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error while creating user defined function for container ${collection.id()}:\n ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "CreateUDF", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createTrigger(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
newTrigger: DataModels.Trigger,
|
|
||||||
options: any = {}
|
|
||||||
): Q.Promise<DataModels.Trigger> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Creating trigger for container ${collection.id()}`
|
|
||||||
);
|
|
||||||
DataAccessUtilityBase.createTrigger(collection, newTrigger, options)
|
|
||||||
.then(
|
|
||||||
(createdTrigger: DataModels.Trigger) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully created trigger for container ${collection.id()}`
|
|
||||||
);
|
|
||||||
deferred.resolve(createdTrigger);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error while creating trigger for container ${collection.id()}:\n ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "CreateTrigger", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
@@ -628,31 +221,22 @@ export function createTrigger(
|
|||||||
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
||||||
var deferred = Q.defer<any>();
|
var deferred = Q.defer<any>();
|
||||||
const entityName = getEntityName();
|
const entityName = getEntityName();
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
const clearMessage = logConsoleProgress(`Deleting ${entityName} ${documentId.id()}`);
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Deleting ${entityName} ${documentId.id()}`
|
|
||||||
);
|
|
||||||
DataAccessUtilityBase.deleteDocument(collection, documentId)
|
DataAccessUtilityBase.deleteDocument(collection, documentId)
|
||||||
.then(
|
.then(
|
||||||
(response: any) => {
|
(response: any) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`);
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully deleted ${entityName} ${documentId.id()}`
|
|
||||||
);
|
|
||||||
deferred.resolve(response);
|
deferred.resolve(response);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
logConsoleError(`Error while deleting ${entityName} ${documentId.id()}:\n ${JSON.stringify(error)}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error while deleting ${entityName} ${documentId.id()}:\n ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "DeleteDocument", error.code);
|
Logger.logError(JSON.stringify(error), "DeleteDocument", error.code);
|
||||||
sendNotificationForError(error);
|
sendNotificationForError(error);
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
clearMessage();
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
@@ -665,134 +249,22 @@ export function deleteConflict(
|
|||||||
): Q.Promise<any> {
|
): Q.Promise<any> {
|
||||||
var deferred = Q.defer<any>();
|
var deferred = Q.defer<any>();
|
||||||
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
const clearMessage = logConsoleProgress(`Deleting conflict ${conflictId.id()}`);
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Deleting conflict ${conflictId.id()}`
|
|
||||||
);
|
|
||||||
DataAccessUtilityBase.deleteConflict(collection, conflictId, options)
|
DataAccessUtilityBase.deleteConflict(collection, conflictId, options)
|
||||||
.then(
|
.then(
|
||||||
(response: any) => {
|
(response: any) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
logConsoleInfo(`Successfully deleted conflict ${conflictId.id()}`);
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully deleted conflict ${conflictId.id()}`
|
|
||||||
);
|
|
||||||
deferred.resolve(response);
|
deferred.resolve(response);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
logConsoleError(`Error while deleting conflict ${conflictId.id()}:\n ${JSON.stringify(error)}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error while deleting conflict ${conflictId.id()}:\n ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "DeleteConflict", error.code);
|
Logger.logError(JSON.stringify(error), "DeleteConflict", error.code);
|
||||||
sendNotificationForError(error);
|
sendNotificationForError(error);
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
clearMessage();
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteStoredProcedure(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
storedProcedure: DataModels.StoredProcedure,
|
|
||||||
options: any = {}
|
|
||||||
): Q.Promise<DataModels.StoredProcedure> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Deleting stored procedure ${storedProcedure.id}`
|
|
||||||
);
|
|
||||||
DataAccessUtilityBase.deleteStoredProcedure(collection, storedProcedure, options)
|
|
||||||
.then(
|
|
||||||
(response: any) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully deleted stored procedure ${storedProcedure.id}`
|
|
||||||
);
|
|
||||||
deferred.resolve(response);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error while deleting stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "DeleteStoredProcedure", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteUserDefinedFunction(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
userDefinedFunction: DataModels.UserDefinedFunction,
|
|
||||||
options: any = {}
|
|
||||||
): Q.Promise<DataModels.UserDefinedFunction> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Deleting user defined function ${userDefinedFunction.id}`
|
|
||||||
);
|
|
||||||
DataAccessUtilityBase.deleteUserDefinedFunction(collection, userDefinedFunction, options)
|
|
||||||
.then(
|
|
||||||
(response: any) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully deleted user defined function ${userDefinedFunction.id}`
|
|
||||||
);
|
|
||||||
deferred.resolve(response);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error while deleting user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "DeleteUDF", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteTrigger(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
trigger: DataModels.Trigger,
|
|
||||||
options: any = {}
|
|
||||||
): Q.Promise<DataModels.Trigger> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, `Deleting trigger ${trigger.id}`);
|
|
||||||
DataAccessUtilityBase.deleteTrigger(collection, trigger, options)
|
|
||||||
.then(
|
|
||||||
(response: any) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, `Successfully deleted trigger ${trigger.id}`);
|
|
||||||
deferred.resolve(response);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error while deleting trigger ${trigger.id}:\n ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "DeleteTrigger", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
@@ -812,27 +284,21 @@ export function readCollectionQuotaInfo(
|
|||||||
): Q.Promise<DataModels.CollectionQuotaInfo> {
|
): Q.Promise<DataModels.CollectionQuotaInfo> {
|
||||||
var deferred = Q.defer<DataModels.CollectionQuotaInfo>();
|
var deferred = Q.defer<DataModels.CollectionQuotaInfo>();
|
||||||
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
const clearMessage = logConsoleProgress(`Querying quota info for container ${collection.id}`);
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Querying quota info for container ${collection.id}`
|
|
||||||
);
|
|
||||||
DataAccessUtilityBase.readCollectionQuotaInfo(collection, options)
|
DataAccessUtilityBase.readCollectionQuotaInfo(collection, options)
|
||||||
.then(
|
.then(
|
||||||
(quota: DataModels.CollectionQuotaInfo) => {
|
(quota: DataModels.CollectionQuotaInfo) => {
|
||||||
deferred.resolve(quota);
|
deferred.resolve(quota);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
logConsoleError(`Error while querying quota info for container ${collection.id}:\n ${JSON.stringify(error)}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error while querying quota info for container ${collection.id}:\n ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "ReadCollectionQuotaInfo", error.code);
|
Logger.logError(JSON.stringify(error), "ReadCollectionQuotaInfo", error.code);
|
||||||
sendNotificationForError(error);
|
sendNotificationForError(error);
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
clearMessage();
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
@@ -841,24 +307,21 @@ export function readCollectionQuotaInfo(
|
|||||||
export function readOffers(options: any = {}): Q.Promise<DataModels.Offer[]> {
|
export function readOffers(options: any = {}): Q.Promise<DataModels.Offer[]> {
|
||||||
var deferred = Q.defer<DataModels.Offer[]>();
|
var deferred = Q.defer<DataModels.Offer[]>();
|
||||||
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Querying offers");
|
const clearMessage = logConsoleProgress("Querying offers");
|
||||||
DataAccessUtilityBase.readOffers(options)
|
DataAccessUtilityBase.readOffers(options)
|
||||||
.then(
|
.then(
|
||||||
(offers: DataModels.Offer[]) => {
|
(offers: DataModels.Offer[]) => {
|
||||||
deferred.resolve(offers);
|
deferred.resolve(offers);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
logConsoleError(`Error while querying offers:\n ${JSON.stringify(error)}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error while querying offers:\n ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "ReadOffers", error.code);
|
Logger.logError(JSON.stringify(error), "ReadOffers", error.code);
|
||||||
sendNotificationForError(error);
|
sendNotificationForError(error);
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
clearMessage();
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
@@ -870,92 +333,22 @@ export function readOffer(
|
|||||||
): Q.Promise<DataModels.OfferWithHeaders> {
|
): Q.Promise<DataModels.OfferWithHeaders> {
|
||||||
var deferred = Q.defer<DataModels.OfferWithHeaders>();
|
var deferred = Q.defer<DataModels.OfferWithHeaders>();
|
||||||
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Querying offer");
|
const clearMessage = logConsoleProgress("Querying offer");
|
||||||
DataAccessUtilityBase.readOffer(requestedResource, options)
|
DataAccessUtilityBase.readOffer(requestedResource, options)
|
||||||
.then(
|
.then(
|
||||||
(offer: DataModels.OfferWithHeaders) => {
|
(offer: DataModels.OfferWithHeaders) => {
|
||||||
deferred.resolve(offer);
|
deferred.resolve(offer);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
logConsoleError(`Error while querying offer:\n ${JSON.stringify(error)}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error while querying offer:\n ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "ReadOffer", error.code);
|
Logger.logError(JSON.stringify(error), "ReadOffer", error.code);
|
||||||
sendNotificationForError(error);
|
sendNotificationForError(error);
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
clearMessage();
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getOrCreateDatabaseAndCollection(
|
|
||||||
request: DataModels.CreateDatabaseAndCollectionRequest,
|
|
||||||
options: any = {}
|
|
||||||
): Q.Promise<DataModels.Collection> {
|
|
||||||
const deferred: Q.Deferred<DataModels.Collection> = Q.defer<DataModels.Collection>();
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Creating a new container ${request.collectionId} for database ${request.databaseId}`
|
|
||||||
);
|
|
||||||
|
|
||||||
DataAccessUtilityBase.getOrCreateDatabaseAndCollection(request, options)
|
|
||||||
.then(
|
|
||||||
(collection: DataModels.Collection) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully created container ${request.collectionId}`
|
|
||||||
);
|
|
||||||
deferred.resolve(collection);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
const sanitizedError = ErrorParserUtility.replaceKnownError(JSON.stringify(error));
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error while creating container ${request.collectionId}:\n ${sanitizedError}`
|
|
||||||
);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createDatabase(
|
|
||||||
request: DataModels.CreateDatabaseRequest,
|
|
||||||
options: any = {}
|
|
||||||
): Q.Promise<DataModels.Database> {
|
|
||||||
const deferred: Q.Deferred<DataModels.Database> = Q.defer<DataModels.Database>();
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Creating a new database ${request.databaseId}`
|
|
||||||
);
|
|
||||||
|
|
||||||
DataAccessUtilityBase.createDatabase(request, options)
|
|
||||||
.then(
|
|
||||||
(database: DataModels.Database) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully created database ${request.databaseId}`
|
|
||||||
);
|
|
||||||
deferred.resolve(database);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error while creating database ${request.databaseId}:\n ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import Q from "q";
|
import Q from "q";
|
||||||
import * as MessageHandler from "./MessageHandler";
|
import * as MessageHandler from "./MessageHandler";
|
||||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
|
||||||
|
|
||||||
describe("Message Handler", () => {
|
describe("Message Handler", () => {
|
||||||
it("should handle cached message", async () => {
|
it("should handle cached message", async () => {
|
||||||
@@ -26,4 +25,34 @@ describe("Message Handler", () => {
|
|||||||
MessageHandler.runGarbageCollector();
|
MessageHandler.runGarbageCollector();
|
||||||
expect(MessageHandler.RequestMap["123"]).toBeUndefined();
|
expect(MessageHandler.RequestMap["123"]).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("getDataExplorerWindow", () => {
|
||||||
|
it("should return current window if current window has dataExplorerPlatform property", () => {
|
||||||
|
const currentWindow: Window = { dataExplorerPlatform: 0 } as any;
|
||||||
|
|
||||||
|
expect(MessageHandler.getDataExplorerWindow(currentWindow)).toEqual(currentWindow);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return current window's parent if current window's parent has dataExplorerPlatform property", () => {
|
||||||
|
const parentWindow: Window = { dataExplorerPlatform: 0 } as any;
|
||||||
|
const currentWindow: Window = { parent: parentWindow } as any;
|
||||||
|
|
||||||
|
expect(MessageHandler.getDataExplorerWindow(currentWindow)).toEqual(parentWindow);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return undefined if none of the windows in the hierarchy have dataExplorerPlatform property and window's parent is reference to itself", () => {
|
||||||
|
const parentWindow: Window = {} as any;
|
||||||
|
(parentWindow as any).parent = parentWindow; // If a window does not have a parent, its parent property is a reference to itself.
|
||||||
|
const currentWindow: Window = { parent: parentWindow } as any;
|
||||||
|
|
||||||
|
expect(MessageHandler.getDataExplorerWindow(currentWindow)).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return undefined if none of the windows in the hierarchy have dataExplorerPlatform property and window's parent is not defined", () => {
|
||||||
|
const parentWindow: Window = {} as any;
|
||||||
|
const currentWindow: Window = { parent: parentWindow } as any;
|
||||||
|
|
||||||
|
expect(MessageHandler.getDataExplorerWindow(currentWindow)).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -48,16 +48,38 @@ export function sendCachedDataMessage<TResponseDataModel>(
|
|||||||
|
|
||||||
export function sendMessage(data: any): void {
|
export function sendMessage(data: any): void {
|
||||||
if (canSendMessage()) {
|
if (canSendMessage()) {
|
||||||
window.parent.postMessage(
|
const dataExplorerWindow = getDataExplorerWindow(window);
|
||||||
{
|
if (dataExplorerWindow) {
|
||||||
signature: "pcIframe",
|
dataExplorerWindow.parent.postMessage(
|
||||||
data: data
|
{
|
||||||
},
|
signature: "pcIframe",
|
||||||
window.document.referrer
|
data: data
|
||||||
);
|
},
|
||||||
|
dataExplorerWindow.document.referrer
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only exported for unit tests
|
||||||
|
export const getDataExplorerWindow = (currentWindow: Window): Window | undefined => {
|
||||||
|
// Start with the current window and traverse up the parent hierarchy to find a window
|
||||||
|
// with `dataExplorerPlatform` property
|
||||||
|
let dataExplorerWindow: Window | undefined = currentWindow;
|
||||||
|
// TODO: Need to `any` here since the window imports Explorer which can't be in strict mode yet
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
while (dataExplorerWindow && (dataExplorerWindow as any).dataExplorerPlatform == undefined) {
|
||||||
|
// If a window does not have a parent, its parent property is a reference to itself.
|
||||||
|
if (dataExplorerWindow.parent == dataExplorerWindow) {
|
||||||
|
dataExplorerWindow = undefined;
|
||||||
|
} else {
|
||||||
|
dataExplorerWindow = dataExplorerWindow.parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataExplorerWindow;
|
||||||
|
};
|
||||||
|
|
||||||
export function canSendMessage(): boolean {
|
export function canSendMessage(): boolean {
|
||||||
return window.parent !== window;
|
return window.parent !== window;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,7 @@ import { Collection } from "../Contracts/ViewModels";
|
|||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
|
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
|
||||||
import { updateUserContext } from "../UserContext";
|
import { updateUserContext } from "../UserContext";
|
||||||
import {
|
import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
|
||||||
deleteDocument,
|
|
||||||
getEndpoint,
|
|
||||||
queryDocuments,
|
|
||||||
readDocument,
|
|
||||||
updateDocument,
|
|
||||||
_createMongoCollectionWithARM
|
|
||||||
} from "./MongoProxyClient";
|
|
||||||
jest.mock("../ResourceProvider/ResourceProviderClient.ts");
|
jest.mock("../ResourceProvider/ResourceProviderClient.ts");
|
||||||
|
|
||||||
const databaseId = "testDB";
|
const databaseId = "testDB";
|
||||||
@@ -260,58 +253,4 @@ describe("MongoProxyClient", () => {
|
|||||||
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,16 +1,12 @@
|
|||||||
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
|
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
|
||||||
import queryString from "querystring";
|
import queryString from "querystring";
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import * as Constants from "../Common/Constants";
|
|
||||||
import * as DataExplorerConstants from "../Common/Constants";
|
|
||||||
import { configContext } from "../ConfigContext";
|
import { configContext } from "../ConfigContext";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
import { Collection } from "../Contracts/ViewModels";
|
import { Collection } from "../Contracts/ViewModels";
|
||||||
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
|
|
||||||
import { AddDbUtilities } from "../Shared/AddDatabaseUtility";
|
|
||||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||||
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
@@ -285,43 +281,35 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createMongoCollectionWithProxy(
|
export function createMongoCollectionWithProxy(
|
||||||
databaseId: string,
|
params: DataModels.CreateCollectionParams
|
||||||
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 = userContext.databaseAccount;
|
||||||
const params: DataModels.MongoParameters = {
|
const shardKey: string = params.partitionKey?.paths[0];
|
||||||
|
const mongoParams: DataModels.MongoParameters = {
|
||||||
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
|
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
|
||||||
db: databaseId,
|
db: params.databaseId,
|
||||||
coll: collectionId,
|
coll: params.collectionId,
|
||||||
pk: shardKey,
|
pk: shardKey,
|
||||||
offerThroughput,
|
offerThroughput: params.offerThroughput,
|
||||||
cd: createDatabase,
|
cd: params.createNewDatabase,
|
||||||
st: sharedThroughput,
|
st: params.databaseLevelThroughput,
|
||||||
is: isSharded,
|
is: !!shardKey,
|
||||||
rid: "",
|
rid: "",
|
||||||
rtype: "colls",
|
rtype: "colls",
|
||||||
sid: userContext.subscriptionId,
|
sid: userContext.subscriptionId,
|
||||||
rg: userContext.resourceGroup,
|
rg: userContext.resourceGroup,
|
||||||
dba: databaseAccount.name,
|
dba: databaseAccount.name,
|
||||||
isAutoPilot: false
|
isAutoPilot: !!params.autoPilotMaxThroughput,
|
||||||
|
autoPilotThroughput: params.autoPilotMaxThroughput?.toString()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (autopilotOptions) {
|
|
||||||
params.isAutoPilot = true;
|
|
||||||
params.autoPilotTier = autopilotOptions[Constants.HttpHeaders.autoPilotTier] as string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const endpoint = getEndpoint(databaseAccount);
|
const endpoint = getEndpoint(databaseAccount);
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(
|
.fetch(
|
||||||
`${endpoint}/createCollection?${queryString.stringify((params as unknown) as queryString.ParsedUrlQueryInput)}`,
|
`${endpoint}/createCollection?${queryString.stringify(
|
||||||
|
(mongoParams as unknown) as queryString.ParsedUrlQueryInput
|
||||||
|
)}`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -335,52 +323,10 @@ export function createMongoCollectionWithProxy(
|
|||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
return errorHandling(response, "creating collection", params);
|
return errorHandling(response, "creating collection", mongoParams);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = userContext.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: userContext.subscriptionId,
|
|
||||||
rg: userContext.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: DataModels.DatabaseAccount): string {
|
export function getEndpoint(databaseAccount: DataModels.DatabaseAccount): string {
|
||||||
const serverId = window.dataExplorer.serverId();
|
const serverId = window.dataExplorer.serverId();
|
||||||
const extensionEndpoint = window.dataExplorer.extensionEndpoint();
|
const extensionEndpoint = window.dataExplorer.extensionEndpoint();
|
||||||
@@ -413,46 +359,3 @@ async function errorHandling(response: Response, action: string, params: unknown
|
|||||||
export function getARMCreateCollectionEndpoint(params: DataModels.MongoParameters): string {
|
export function getARMCreateCollectionEndpoint(params: DataModels.MongoParameters): string {
|
||||||
return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${userContext.databaseAccount.name}/mongodbDatabases/${params.db}/collections/${params.coll}`;
|
return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${userContext.databaseAccount.name}/mongodbDatabases/${params.db}/collections/${params.coll}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,13 +10,8 @@ import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
|||||||
import { QueryUtils } from "../Utils/QueryUtils";
|
import { QueryUtils } from "../Utils/QueryUtils";
|
||||||
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import {
|
import { createDocument, deleteDocument, queryDocuments, queryDocumentsPage } from "./DocumentClientUtilityBase";
|
||||||
createDocument,
|
import { createCollection } from "./dataAccess/createCollection";
|
||||||
deleteDocument,
|
|
||||||
getOrCreateDatabaseAndCollection,
|
|
||||||
queryDocuments,
|
|
||||||
queryDocumentsPage
|
|
||||||
} from "./DocumentClientUtilityBase";
|
|
||||||
import * as ErrorParserUtility from "./ErrorParserUtility";
|
import * as ErrorParserUtility from "./ErrorParserUtility";
|
||||||
import * as Logger from "./Logger";
|
import * as Logger from "./Logger";
|
||||||
|
|
||||||
@@ -41,12 +36,13 @@ export class QueriesClient {
|
|||||||
ConsoleDataType.InProgress,
|
ConsoleDataType.InProgress,
|
||||||
"Setting up account for saving queries"
|
"Setting up account for saving queries"
|
||||||
);
|
);
|
||||||
return getOrCreateDatabaseAndCollection({
|
return createCollection({
|
||||||
collectionId: SavedQueries.CollectionName,
|
collectionId: SavedQueries.CollectionName,
|
||||||
|
createNewDatabase: true,
|
||||||
databaseId: SavedQueries.DatabaseName,
|
databaseId: SavedQueries.DatabaseName,
|
||||||
partitionKey: QueriesClient.PartitionKey,
|
partitionKey: QueriesClient.PartitionKey,
|
||||||
offerThroughput: SavedQueries.OfferThroughput,
|
offerThroughput: SavedQueries.OfferThroughput,
|
||||||
databaseLevelThroughput: undefined
|
databaseLevelThroughput: false
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
(collection: DataModels.Collection) => {
|
(collection: DataModels.Collection) => {
|
||||||
|
|||||||
81
src/Common/dataAccess/createCollection.test.ts
Normal file
81
src/Common/dataAccess/createCollection.test.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
371
src/Common/dataAccess/createCollection.ts
Normal file
371
src/Common/dataAccess/createCollection.ts
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
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;
|
||||||
|
};
|
||||||
251
src/Common/dataAccess/createDatabase.ts
Normal file
251
src/Common/dataAccess/createDatabase.ts
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
28
src/Common/dataAccess/createStoredProcedure.ts
Normal file
28
src/Common/dataAccess/createStoredProcedure.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
28
src/Common/dataAccess/createTrigger.ts
Normal file
28
src/Common/dataAccess/createTrigger.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
28
src/Common/dataAccess/createUserDefinedFunction.ts
Normal file
28
src/Common/dataAccess/createUserDefinedFunction.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ import { refreshCachedResources } from "../DataAccessUtilityBase";
|
|||||||
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
|
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
|
||||||
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
|
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
|
||||||
try {
|
try {
|
||||||
if (window.authType === AuthType.AAD) {
|
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||||
await deleteCollectionWithARM(databaseId, collectionId);
|
await deleteCollectionWithARM(databaseId, collectionId);
|
||||||
} else {
|
} else {
|
||||||
await client()
|
await client()
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ export async function deleteDatabase(databaseId: string): Promise<void> {
|
|||||||
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
|
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (window.authType === AuthType.AAD) {
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
userContext.defaultExperience !== DefaultAccountExperienceType.Table &&
|
||||||
|
!userContext.useSDKOperations
|
||||||
|
) {
|
||||||
await deleteDatabaseWithARM(databaseId);
|
await deleteDatabaseWithARM(databaseId);
|
||||||
} else {
|
} else {
|
||||||
await client()
|
await client()
|
||||||
|
|||||||
26
src/Common/dataAccess/deleteStoredProcedure.ts
Normal file
26
src/Common/dataAccess/deleteStoredProcedure.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
22
src/Common/dataAccess/deleteTrigger.ts
Normal file
22
src/Common/dataAccess/deleteTrigger.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
22
src/Common/dataAccess/deleteUserDefinedFunction.ts
Normal file
22
src/Common/dataAccess/deleteUserDefinedFunction.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -16,7 +16,12 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
|
|||||||
let collections: DataModels.Collection[];
|
let collections: DataModels.Collection[];
|
||||||
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
|
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
|
||||||
try {
|
try {
|
||||||
if (window.authType === AuthType.AAD) {
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
|
||||||
|
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
||||||
|
) {
|
||||||
collections = await readCollectionsWithARM(databaseId);
|
collections = await readCollectionsWithARM(databaseId);
|
||||||
} else {
|
} else {
|
||||||
const sdkResponse = await client()
|
const sdkResponse = await client()
|
||||||
|
|||||||
83
src/Common/dataAccess/readDatabaseOffer.ts
Normal file
83
src/Common/dataAccess/readDatabaseOffer.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
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) {
|
||||||
|
try {
|
||||||
|
offerId = await getDatabaseOfferIdWithARM(params.databaseId);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== "NotFound") {
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
offerId = await getDatabaseOfferIdWithSDK(params.databaseResourceId, params.isServerless);
|
||||||
|
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, isServerless: boolean): Promise<string> => {
|
||||||
|
const offers = await readOffers(isServerless);
|
||||||
|
const offer = offers.find(offer => offer.resource === databaseResourceId);
|
||||||
|
return offer?.id;
|
||||||
|
};
|
||||||
@@ -15,7 +15,13 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
|
|||||||
let databases: DataModels.Database[];
|
let databases: DataModels.Database[];
|
||||||
const clearMessage = logConsoleProgress(`Querying databases`);
|
const clearMessage = logConsoleProgress(`Querying databases`);
|
||||||
try {
|
try {
|
||||||
if (window.authType === AuthType.AAD) {
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
|
||||||
|
userContext.defaultExperience !== DefaultAccountExperienceType.Table &&
|
||||||
|
userContext.defaultExperience !== DefaultAccountExperienceType.Cassandra
|
||||||
|
) {
|
||||||
databases = await readDatabasesWithARM();
|
databases = await readDatabasesWithARM();
|
||||||
} else {
|
} else {
|
||||||
const sdkResponse = await client()
|
const sdkResponse = await client()
|
||||||
|
|||||||
36
src/Common/dataAccess/readOffers.ts
Normal file
36
src/Common/dataAccess/readOffers.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
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 (isServerless?: boolean): Promise<Offer[]> => {
|
||||||
|
if (isServerless) {
|
||||||
|
return []; // Reading offers is not supported for serverless accounts
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
};
|
||||||
27
src/Common/dataAccess/readStoredProcedures.ts
Normal file
27
src/Common/dataAccess/readStoredProcedures.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
27
src/Common/dataAccess/readTriggers.ts
Normal file
27
src/Common/dataAccess/readTriggers.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
27
src/Common/dataAccess/readUserDefinedFunctions.ts
Normal file
27
src/Common/dataAccess/readUserDefinedFunctions.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
225
src/Common/dataAccess/updateCollection.ts
Normal file
225
src/Common/dataAccess/updateCollection.ts
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
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}`);
|
||||||
|
}
|
||||||
29
src/Common/dataAccess/updateStoredProcedure.ts
Normal file
29
src/Common/dataAccess/updateStoredProcedure.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
29
src/Common/dataAccess/updateTrigger.ts
Normal file
29
src/Common/dataAccess/updateTrigger.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
29
src/Common/dataAccess/updateUserDefinedFunction.ts
Normal file
29
src/Common/dataAccess/updateUserDefinedFunction.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ export enum Platform {
|
|||||||
|
|
||||||
interface ConfigContext {
|
interface ConfigContext {
|
||||||
platform: Platform;
|
platform: Platform;
|
||||||
allowedParentFrameOrigins: RegExp;
|
allowedParentFrameOrigins: string[];
|
||||||
gitSha?: string;
|
gitSha?: string;
|
||||||
proxyPath?: string;
|
proxyPath?: string;
|
||||||
AAD_ENDPOINT: string;
|
AAD_ENDPOINT: string;
|
||||||
@@ -30,7 +30,12 @@ interface ConfigContext {
|
|||||||
// Default configuration
|
// Default configuration
|
||||||
let configContext: Readonly<ConfigContext> = {
|
let configContext: Readonly<ConfigContext> = {
|
||||||
platform: Platform.Portal,
|
platform: Platform.Portal,
|
||||||
allowedParentFrameOrigins: /^https:\/\/portal\.azure\.com$|^https:\/\/portal\.azure\.us$|^https:\/\/portal\.azure\.cn$|^https:\/\/portal\.microsoftazure\.de$|^https:\/\/.+\.portal\.azure\.com$|^https:\/\/.+\.portal\.azure\.us$|^https:\/\/.+\.portal\.azure\.cn$|^https:\/\/.+\.portal\.microsoftazure\.de$|^https:\/\/main\.documentdb\.ext\.azure\.com$|^https:\/\/main\.documentdb\.ext\.microsoftazure\.de$|^https:\/\/main\.documentdb\.ext\.azure\.cn$|^https:\/\/main\.documentdb\.ext\.azure\.us$/,
|
allowedParentFrameOrigins: [
|
||||||
|
`^https:\\/\\/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/",
|
||||||
@@ -73,19 +78,32 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
|
|||||||
const response = await fetch("./config.json");
|
const response = await fetch("./config.json");
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
try {
|
try {
|
||||||
const externalConfig = await response.json();
|
const { allowedParentFrameOrigins, ...externalConfig } = await response.json();
|
||||||
Object.assign(configContext, externalConfig);
|
Object.assign(configContext, 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 any config value with URL query parameters
|
// Allow override of platform value with URL query parameter
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
params.forEach((value, key) => {
|
if (params.has("platform")) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
const platform = params.get("platform");
|
||||||
(configContext 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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,10 +88,6 @@ 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;
|
||||||
@@ -104,39 +100,12 @@ 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;
|
||||||
@@ -153,7 +122,14 @@ 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[];
|
||||||
@@ -252,28 +228,6 @@ 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;
|
||||||
@@ -300,11 +254,6 @@ 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;
|
||||||
@@ -320,18 +269,31 @@ export interface AutoPilotOfferSettings {
|
|||||||
targetMaxThroughput?: number;
|
targetMaxThroughput?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateDatabaseRequest {
|
export interface CreateDatabaseParams {
|
||||||
|
autoPilotMaxThroughput?: number;
|
||||||
databaseId: string;
|
databaseId: string;
|
||||||
databaseLevelThroughput?: boolean;
|
databaseLevelThroughput?: boolean;
|
||||||
offerThroughput?: number;
|
offerThroughput?: number;
|
||||||
autoPilot?: AutoPilotCreationSettings;
|
|
||||||
hasAutoPilotV2FeatureFlag?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SharedThroughputRange {
|
export interface CreateCollectionParams {
|
||||||
minimumRU: number;
|
createNewDatabase: boolean;
|
||||||
maximumRU: number;
|
collectionId: string;
|
||||||
defaultRU: number;
|
databaseId: string;
|
||||||
|
databaseLevelThroughput: boolean;
|
||||||
|
offerThroughput: number;
|
||||||
|
analyticalStorageTtl?: number;
|
||||||
|
autoPilotMaxThroughput?: number;
|
||||||
|
indexingPolicy?: IndexingPolicy;
|
||||||
|
partitionKey?: PartitionKey;
|
||||||
|
uniqueKeyPolicy?: UniqueKeyPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReadDatabaseOfferParams {
|
||||||
|
databaseId: string;
|
||||||
|
databaseResourceId?: string;
|
||||||
|
isServerless?: boolean;
|
||||||
|
offerId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Notification {
|
export interface Notification {
|
||||||
@@ -476,25 +438,6 @@ 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;
|
||||||
@@ -536,79 +479,10 @@ 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,16 +1,22 @@
|
|||||||
import * as DataModels from "./DataModels";
|
import {
|
||||||
|
QueryMetrics,
|
||||||
|
Resource,
|
||||||
|
StoredProcedureDefinition,
|
||||||
|
TriggerDefinition,
|
||||||
|
UserDefinedFunctionDefinition
|
||||||
|
} from "@azure/cosmos";
|
||||||
import Q from "q";
|
import Q from "q";
|
||||||
import { CassandraTableKey, CassandraTableKeys } from "../Explorer/Tables/TableDataClient";
|
|
||||||
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
|
||||||
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
|
||||||
import { QueryMetrics } from "@azure/cosmos";
|
|
||||||
import { UploadDetails } from "../workers/upload/definitions";
|
|
||||||
import Explorer from "../Explorer/Explorer";
|
import Explorer from "../Explorer/Explorer";
|
||||||
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
|
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 StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
||||||
import Trigger from "../Explorer/Tree/Trigger";
|
import Trigger from "../Explorer/Tree/Trigger";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
|
||||||
import ConflictId from "../Explorer/Tree/ConflictId";
|
import { UploadDetails } from "../workers/upload/definitions";
|
||||||
|
import * as DataModels from "./DataModels";
|
||||||
|
|
||||||
export interface TokenProvider {
|
export interface TokenProvider {
|
||||||
getAuthHeader(): Promise<Headers>;
|
getAuthHeader(): Promise<Headers>;
|
||||||
@@ -75,15 +81,15 @@ export interface Database extends TreeNode {
|
|||||||
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
|
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
|
||||||
|
|
||||||
selectDatabase(): void;
|
selectDatabase(): void;
|
||||||
expandDatabase(): void;
|
expandDatabase(): Promise<void>;
|
||||||
collapseDatabase(): void;
|
collapseDatabase(): void;
|
||||||
|
|
||||||
loadCollections(): Q.Promise<void>;
|
loadCollections(): 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 {
|
||||||
@@ -153,13 +159,13 @@ export interface Collection extends CollectionBase {
|
|||||||
collapseUserDefinedFunctions(): void;
|
collapseUserDefinedFunctions(): void;
|
||||||
collapseTriggers(): void;
|
collapseTriggers(): void;
|
||||||
|
|
||||||
loadUserDefinedFunctions(): Q.Promise<any>;
|
loadUserDefinedFunctions(): Promise<any>;
|
||||||
loadStoredProcedures(): Q.Promise<any>;
|
loadStoredProcedures(): Promise<any>;
|
||||||
loadTriggers(): Q.Promise<any>;
|
loadTriggers(): Promise<any>;
|
||||||
|
|
||||||
createStoredProcedureNode(data: DataModels.StoredProcedure): StoredProcedure;
|
createStoredProcedureNode(data: StoredProcedureDefinition & Resource): StoredProcedure;
|
||||||
createUserDefinedFunctionNode(data: DataModels.UserDefinedFunction): UserDefinedFunction;
|
createUserDefinedFunctionNode(data: UserDefinedFunctionDefinition & Resource): UserDefinedFunction;
|
||||||
createTriggerNode(data: DataModels.Trigger): Trigger;
|
createTriggerNode(data: TriggerDefinition & Resource): 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;
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ 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];
|
||||||
}
|
}
|
||||||
@@ -112,7 +113,8 @@ 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 TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as 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,6 +265,9 @@ 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",
|
||||||
@@ -604,6 +607,9 @@ 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",
|
||||||
@@ -997,6 +1003,9 @@ 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",
|
||||||
@@ -1113,6 +1122,11 @@ 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 {
|
||||||
@@ -1134,6 +1148,11 @@ 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 {
|
||||||
@@ -1150,7 +1169,6 @@ 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",
|
||||||
@@ -1230,7 +1248,6 @@ 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",
|
||||||
@@ -1259,10 +1276,6 @@ 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 {
|
||||||
@@ -1362,13 +1375,21 @@ 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": "WindowText",
|
"backgroundColor": "Window",
|
||||||
"color": "Window",
|
"border": "1px solid WindowText",
|
||||||
|
"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",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1408,6 +1429,9 @@ 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 {
|
||||||
@@ -1554,6 +1578,13 @@ 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",
|
||||||
},
|
},
|
||||||
@@ -1775,6 +1806,9 @@ 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,6 +86,7 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -94,7 +95,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.getElementById("uniqueKeyItems").focus();
|
(document.querySelector(".dynamicListItem:last-of-type input") as HTMLElement).focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onAddItemKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
public onAddItemKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||||
|
|||||||
@@ -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 TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import UrlUtility from "../../../Common/UrlUtility";
|
import UrlUtility from "../../../Common/UrlUtility";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,9 @@ export class GitHubReposComponent extends React.Component<GitHubReposComponentPr
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={"firstdivbg headerline"}>{header}</div>
|
<div className={"firstdivbg headerline"} role="heading" aria-level={2}>
|
||||||
|
{header}
|
||||||
|
</div>
|
||||||
<div className={"paneMainContent"}>{content}</div>
|
<div className={"paneMainContent"}>{content}</div>
|
||||||
{!this.props.showAuthorizeAccess && (
|
{!this.props.showAuthorizeAccess && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -148,7 +149,8 @@ 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,7 +24,7 @@ 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 TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as 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";
|
import { QueriesClient } from "../../../Common/QueriesClient";
|
||||||
|
|||||||
@@ -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': ariaLabel,
|
'aria-label': 'Max request units per second',
|
||||||
type: isAutoscaleThroughputInputFieldRequired() ? 'number' : 'hidden',
|
type: isAutoscaleThroughputInputFieldRequired() ? 'number' : 'hidden',
|
||||||
css: {
|
css: {
|
||||||
dirty: maxAutoPilotThroughputSet.editableIsDirty
|
dirty: maxAutoPilotThroughputSet.editableIsDirty
|
||||||
|
|||||||
@@ -159,4 +159,20 @@ describe("TreeNodeComponent", () => {
|
|||||||
const wrapper = shallow(<TreeNodeComponent {...props} />);
|
const wrapper = shallow(<TreeNodeComponent {...props} />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders loading icon", () => {
|
||||||
|
const node: TreeNode = {
|
||||||
|
label: "label",
|
||||||
|
children: [],
|
||||||
|
isExpanded: true
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
node,
|
||||||
|
generation: 2,
|
||||||
|
paddingLeft: 9
|
||||||
|
};
|
||||||
|
const wrapper = shallow(<TreeNodeComponent {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,12 +17,14 @@ import {
|
|||||||
|
|
||||||
import TriangleDownIcon from "../../../../images/Triangle-down.svg";
|
import TriangleDownIcon from "../../../../images/Triangle-down.svg";
|
||||||
import TriangleRightIcon from "../../../../images/Triangle-right.svg";
|
import TriangleRightIcon from "../../../../images/Triangle-right.svg";
|
||||||
|
import LoadingIndicator_3Squares from "../../../../images/LoadingIndicator_3Squares.gif";
|
||||||
|
|
||||||
export interface TreeNodeMenuItem {
|
export interface TreeNodeMenuItem {
|
||||||
label: string;
|
label: string;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
iconSrc?: string;
|
iconSrc?: string;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
|
styleClass?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TreeNode {
|
export interface TreeNode {
|
||||||
@@ -37,6 +39,7 @@ export interface TreeNode {
|
|||||||
data?: any; // Piece of data corresponding to this node
|
data?: any; // Piece of data corresponding to this node
|
||||||
timestamp?: number;
|
timestamp?: number;
|
||||||
isLeavesParentsSeparate?: boolean; // Display parents together first, then leaves
|
isLeavesParentsSeparate?: boolean; // Display parents together first, then leaves
|
||||||
|
isLoading?: boolean;
|
||||||
isSelected?: () => boolean;
|
isSelected?: () => boolean;
|
||||||
onClick?: (isExpanded: boolean) => void; // Only if a leaf, other click will expand/collapse
|
onClick?: (isExpanded: boolean) => void; // Only if a leaf, other click will expand/collapse
|
||||||
onExpanded?: () => void;
|
onExpanded?: () => void;
|
||||||
@@ -183,6 +186,9 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
|||||||
)}
|
)}
|
||||||
{node.contextMenu && this.renderContextMenuButton(node)}
|
{node.contextMenu && this.renderContextMenuButton(node)}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="loadingIconContainer">
|
||||||
|
<img className="loadingIcon" src={LoadingIndicator_3Squares} hidden={!this.props.node.isLoading} />
|
||||||
|
</div>
|
||||||
{node.children && (
|
{node.children && (
|
||||||
<AnimateHeight duration={TreeNodeComponent.transitionDurationMS} height={this.state.isExpanded ? "auto" : 0}>
|
<AnimateHeight duration={TreeNodeComponent.transitionDurationMS} height={this.state.isExpanded ? "auto" : 0}>
|
||||||
<div className="nodeChildren" data-test={node.label}>
|
<div className="nodeChildren" data-test={node.label}>
|
||||||
@@ -256,13 +262,20 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
|||||||
onContextMenu={e => e.target.dispatchEvent(TreeNodeComponent.createClickEvent())}
|
onContextMenu={e => e.target.dispatchEvent(TreeNodeComponent.createClickEvent())}
|
||||||
>
|
>
|
||||||
{props.item.onRenderIcon()}
|
{props.item.onRenderIcon()}
|
||||||
<span className="treeComponentMenuItemLabel">{props.item.text}</span>
|
<span
|
||||||
|
className={
|
||||||
|
"treeComponentMenuItemLabel" + (props.item.className ? ` ${props.item.className}Label` : "")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{props.item.text}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
items: node.contextMenu.map((menuItem: TreeNodeMenuItem) => ({
|
items: node.contextMenu.map((menuItem: TreeNodeMenuItem) => ({
|
||||||
key: menuItem.label,
|
key: menuItem.label,
|
||||||
text: menuItem.label,
|
text: menuItem.label,
|
||||||
disabled: menuItem.isDisabled,
|
disabled: menuItem.isDisabled,
|
||||||
|
className: menuItem.styleClass,
|
||||||
onClick: menuItem.onClick,
|
onClick: menuItem.onClick,
|
||||||
onRenderIcon: (props: any) => <img src={menuItem.iconSrc} alt="" />
|
onRenderIcon: (props: any) => <img src={menuItem.iconSrc} alt="" />
|
||||||
}))
|
}))
|
||||||
@@ -282,7 +295,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
|||||||
<img
|
<img
|
||||||
className="expandCollapseIcon"
|
className="expandCollapseIcon"
|
||||||
src={this.state.isExpanded ? TriangleDownIcon : TriangleRightIcon}
|
src={this.state.isExpanded ? TriangleDownIcon : TriangleRightIcon}
|
||||||
alt={this.state.isExpanded ? "Branch is expanded" : "Branch is collapsed"}
|
alt={this.state.isExpanded ? `${node.label} branch is expanded` : `${node.label} branch is collapsed`}
|
||||||
onKeyPress={(event: React.KeyboardEvent<HTMLDivElement>) => this.onCollapseExpandIconKeyPress(event, node)}
|
onKeyPress={(event: React.KeyboardEvent<HTMLDivElement>) => this.onCollapseExpandIconKeyPress(event, node)}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role="button"
|
role="button"
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
|
|||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Branch is collapsed"
|
alt="label branch is collapsed"
|
||||||
className="expandCollapseIcon"
|
className="expandCollapseIcon"
|
||||||
onKeyPress={[Function]}
|
onKeyPress={[Function]}
|
||||||
role="button"
|
role="button"
|
||||||
@@ -63,6 +63,15 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
|
|||||||
label
|
label
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className="loadingIconContainer"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className="loadingIcon"
|
||||||
|
hidden={true}
|
||||||
|
src=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<AnimateHeight
|
<AnimateHeight
|
||||||
animateOpacity={false}
|
animateOpacity={false}
|
||||||
animationStateClasses={
|
animationStateClasses={
|
||||||
@@ -140,7 +149,7 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
|
|||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Branch is expanded"
|
alt="label branch is expanded"
|
||||||
className="expandCollapseIcon"
|
className="expandCollapseIcon"
|
||||||
onKeyPress={[Function]}
|
onKeyPress={[Function]}
|
||||||
role="button"
|
role="button"
|
||||||
@@ -179,6 +188,7 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
|
|||||||
"isBeakVisible": false,
|
"isBeakVisible": false,
|
||||||
"items": Array [
|
"items": Array [
|
||||||
Object {
|
Object {
|
||||||
|
"className": undefined,
|
||||||
"disabled": true,
|
"disabled": true,
|
||||||
"key": "menuLabel",
|
"key": "menuLabel",
|
||||||
"onClick": undefined,
|
"onClick": undefined,
|
||||||
@@ -201,6 +211,15 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className="loadingIconContainer"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className="loadingIcon"
|
||||||
|
hidden={true}
|
||||||
|
src=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<AnimateHeight
|
<AnimateHeight
|
||||||
animateOpacity={false}
|
animateOpacity={false}
|
||||||
animationStateClasses={
|
animationStateClasses={
|
||||||
@@ -261,6 +280,77 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`TreeNodeComponent renders loading icon 1`] = `
|
||||||
|
<div
|
||||||
|
className=" main2 nodeItem "
|
||||||
|
onClick={[Function]}
|
||||||
|
onKeyPress={[Function]}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="treeNodeHeader "
|
||||||
|
data-test="label"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"paddingLeft": 9,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tabIndex={-1}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt="label branch is expanded"
|
||||||
|
className="expandCollapseIcon"
|
||||||
|
onKeyPress={[Function]}
|
||||||
|
role="button"
|
||||||
|
src=""
|
||||||
|
tabIndex={0}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="nodeLabel"
|
||||||
|
title="label"
|
||||||
|
>
|
||||||
|
label
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="loadingIconContainer"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className="loadingIcon"
|
||||||
|
hidden={true}
|
||||||
|
src=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<AnimateHeight
|
||||||
|
animateOpacity={false}
|
||||||
|
animationStateClasses={
|
||||||
|
Object {
|
||||||
|
"animating": "rah-animating",
|
||||||
|
"animatingDown": "rah-animating--down",
|
||||||
|
"animatingToHeightAuto": "rah-animating--to-height-auto",
|
||||||
|
"animatingToHeightSpecific": "rah-animating--to-height-specific",
|
||||||
|
"animatingToHeightZero": "rah-animating--to-height-zero",
|
||||||
|
"animatingUp": "rah-animating--up",
|
||||||
|
"static": "rah-static",
|
||||||
|
"staticHeightAuto": "rah-static--height-auto",
|
||||||
|
"staticHeightSpecific": "rah-static--height-specific",
|
||||||
|
"staticHeightZero": "rah-static--height-zero",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
applyInlineTransitions={true}
|
||||||
|
delay={0}
|
||||||
|
duration={200}
|
||||||
|
easing="ease"
|
||||||
|
height="auto"
|
||||||
|
style={Object {}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="nodeChildren"
|
||||||
|
data-test="label"
|
||||||
|
/>
|
||||||
|
</AnimateHeight>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents separated 1`] = `
|
exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents separated 1`] = `
|
||||||
<div
|
<div
|
||||||
className="nodeClassname main12 nodeItem "
|
className="nodeClassname main12 nodeItem "
|
||||||
@@ -278,7 +368,7 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
|
|||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Branch is expanded"
|
alt="label branch is expanded"
|
||||||
className="expandCollapseIcon"
|
className="expandCollapseIcon"
|
||||||
onKeyPress={[Function]}
|
onKeyPress={[Function]}
|
||||||
role="button"
|
role="button"
|
||||||
@@ -331,6 +421,15 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className="loadingIconContainer"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className="loadingIcon"
|
||||||
|
hidden={true}
|
||||||
|
src=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<AnimateHeight
|
<AnimateHeight
|
||||||
animateOpacity={false}
|
animateOpacity={false}
|
||||||
animationStateClasses={
|
animationStateClasses={
|
||||||
@@ -436,7 +535,7 @@ exports[`TreeNodeComponent renders unsorted children by default 1`] = `
|
|||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Branch is expanded"
|
alt="label branch is expanded"
|
||||||
className="expandCollapseIcon"
|
className="expandCollapseIcon"
|
||||||
onKeyPress={[Function]}
|
onKeyPress={[Function]}
|
||||||
role="button"
|
role="button"
|
||||||
@@ -450,6 +549,15 @@ exports[`TreeNodeComponent renders unsorted children by default 1`] = `
|
|||||||
label
|
label
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className="loadingIconContainer"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className="loadingIcon"
|
||||||
|
hidden={true}
|
||||||
|
src=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<AnimateHeight
|
<AnimateHeight
|
||||||
animateOpacity={false}
|
animateOpacity={false}
|
||||||
animationStateClasses={
|
animationStateClasses={
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.showingMenu {
|
&.showingMenu {
|
||||||
background-color: #EEE;
|
background-color: #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
.treeMenuEllipsis {
|
.treeMenuEllipsis {
|
||||||
@@ -78,3 +78,12 @@
|
|||||||
vertical-align: text-bottom;
|
vertical-align: text-bottom;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loadingIconContainer {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.loadingIcon {
|
||||||
|
height: 6px;
|
||||||
|
margin-left: 38px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
jest.mock("../../Common/DocumentClientUtilityBase");
|
jest.mock("../../Common/DocumentClientUtilityBase");
|
||||||
|
jest.mock("../../Common/dataAccess/createCollection");
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as sinon from "sinon";
|
import * as sinon from "sinon";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
@@ -33,8 +34,8 @@ describe("ContainerSampleGenerator", () => {
|
|||||||
databaseId: sampleDatabaseId,
|
databaseId: sampleDatabaseId,
|
||||||
offerThroughput: 400,
|
offerThroughput: 400,
|
||||||
databaseLevelThroughput: false,
|
databaseLevelThroughput: false,
|
||||||
|
createNewDatabase: true,
|
||||||
collectionId: sampleCollectionId,
|
collectionId: sampleCollectionId,
|
||||||
rupmEnabled: false,
|
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
firstname: "Eva",
|
firstname: "Eva",
|
||||||
@@ -99,8 +100,8 @@ describe("ContainerSampleGenerator", () => {
|
|||||||
databaseId: sampleDatabaseId,
|
databaseId: sampleDatabaseId,
|
||||||
offerThroughput: 400,
|
offerThroughput: 400,
|
||||||
databaseLevelThroughput: false,
|
databaseLevelThroughput: false,
|
||||||
|
createNewDatabase: true,
|
||||||
collectionId: sampleCollectionId,
|
collectionId: sampleCollectionId,
|
||||||
rupmEnabled: false,
|
|
||||||
data: [
|
data: [
|
||||||
"g.addV('person').property(id, '1').property('_partitionKey','pk').property('name', 'Eva').property('age', 44)"
|
"g.addV('person').property(id, '1').property('_partitionKey','pk').property('name', 'Eva').property('age', 44)"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as Constants from "../../Common/Constants";
|
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import GraphTab from ".././Tabs/GraphTab";
|
import GraphTab from ".././Tabs/GraphTab";
|
||||||
@@ -6,10 +5,11 @@ import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsol
|
|||||||
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
|
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { createDocument, getOrCreateDatabaseAndCollection } from "../../Common/DocumentClientUtilityBase";
|
import { createDocument } from "../../Common/DocumentClientUtilityBase";
|
||||||
|
import { createCollection } from "../../Common/dataAccess/createCollection";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
interface SampleDataFile extends DataModels.CreateDatabaseAndCollectionRequest {
|
interface SampleDataFile extends DataModels.CreateCollectionParams {
|
||||||
data: any[];
|
data: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,18 +54,11 @@ export class ContainerSampleGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async createContainerAsync(): Promise<ViewModels.Collection> {
|
private async createContainerAsync(): Promise<ViewModels.Collection> {
|
||||||
const createRequest: DataModels.CreateDatabaseAndCollectionRequest = {
|
const createRequest: DataModels.CreateCollectionParams = {
|
||||||
...this.sampleDataFile
|
...this.sampleDataFile
|
||||||
};
|
};
|
||||||
|
|
||||||
const options: any = {};
|
await createCollection(createRequest);
|
||||||
if (this.container.isPreferredApiMongoDB()) {
|
|
||||||
options.initialHeaders = options.initialHeaders || {};
|
|
||||||
options.initialHeaders[Constants.HttpHeaders.supportSpatialLegacyCoordinates] = true;
|
|
||||||
options.initialHeaders[Constants.HttpHeaders.usePolygonsSmallerThanAHemisphere] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
await getOrCreateDatabaseAndCollection(createRequest, options);
|
|
||||||
await this.container.refreshAllDatabases();
|
await this.container.refreshAllDatabases();
|
||||||
const database = this.container.findDatabaseWithId(this.sampleDataFile.databaseId);
|
const database = this.container.findDatabaseWithId(this.sampleDataFile.databaseId);
|
||||||
if (!database) {
|
if (!database) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
|
|||||||
import Database from "./Tree/Database";
|
import Database from "./Tree/Database";
|
||||||
import DeleteCollectionConfirmationPane from "./Panes/DeleteCollectionConfirmationPane";
|
import DeleteCollectionConfirmationPane from "./Panes/DeleteCollectionConfirmationPane";
|
||||||
import DeleteDatabaseConfirmationPane from "./Panes/DeleteDatabaseConfirmationPane";
|
import DeleteDatabaseConfirmationPane from "./Panes/DeleteDatabaseConfirmationPane";
|
||||||
import { readOffers, refreshCachedResources } from "../Common/DocumentClientUtilityBase";
|
import { refreshCachedResources } from "../Common/DocumentClientUtilityBase";
|
||||||
import { readCollection } from "../Common/dataAccess/readCollection";
|
import { readCollection } from "../Common/dataAccess/readCollection";
|
||||||
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
||||||
import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane";
|
import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane";
|
||||||
@@ -26,7 +26,7 @@ import NewVertexPane from "./Panes/NewVertexPane";
|
|||||||
import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab";
|
import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab";
|
||||||
import Q from "q";
|
import Q from "q";
|
||||||
import ResourceTokenCollection from "./Tree/ResourceTokenCollection";
|
import ResourceTokenCollection from "./Tree/ResourceTokenCollection";
|
||||||
import TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
||||||
import TerminalTab from "./Tabs/TerminalTab";
|
import TerminalTab from "./Tabs/TerminalTab";
|
||||||
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
||||||
import { ActionContracts, MessageTypes } from "../Contracts/ExplorerContracts";
|
import { ActionContracts, MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
@@ -37,7 +37,7 @@ import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer
|
|||||||
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane";
|
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane";
|
||||||
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
|
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
|
||||||
import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter";
|
import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import { configContext } from "../ConfigContext";
|
import { configContext, updateConfigContext } from "../ConfigContext";
|
||||||
import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||||
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
|
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
|
||||||
@@ -87,6 +87,7 @@ import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
|
|||||||
import TabsBase from "./Tabs/TabsBase";
|
import TabsBase from "./Tabs/TabsBase";
|
||||||
import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandButtonComponent";
|
||||||
import { updateUserContext, userContext } from "../UserContext";
|
import { updateUserContext, userContext } from "../UserContext";
|
||||||
|
import { stringToBlob } from "../Utils/BlobUtils";
|
||||||
|
|
||||||
BindingHandlersRegisterer.registerBindingHandlers();
|
BindingHandlersRegisterer.registerBindingHandlers();
|
||||||
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
|
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
|
||||||
@@ -975,6 +976,10 @@ export default class Explorer {
|
|||||||
this.sparkClusterConnectionInfo.valueHasMutated();
|
this.sparkClusterConnectionInfo.valueHasMutated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isFeatureEnabled(Constants.Features.enableSDKoperations)) {
|
||||||
|
updateUserContext({ useSDKOperations: true });
|
||||||
|
}
|
||||||
|
|
||||||
featureSubcription.dispose();
|
featureSubcription.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1419,94 +1424,58 @@ export default class Explorer {
|
|||||||
|
|
||||||
// TODO: Refactor
|
// TODO: Refactor
|
||||||
const deferred: Q.Deferred<any> = Q.defer();
|
const deferred: Q.Deferred<any> = Q.defer();
|
||||||
|
this._setLoadingStatusText("Fetching databases...");
|
||||||
const refreshDatabases = (offers?: DataModels.Offer[]) => {
|
readDatabases().then(
|
||||||
this._setLoadingStatusText("Fetching databases...");
|
(databases: DataModels.Database[]) => {
|
||||||
readDatabases().then(
|
this._setLoadingStatusText("Successfully fetched databases.");
|
||||||
(databases: DataModels.Database[]) => {
|
TelemetryProcessor.traceSuccess(
|
||||||
this._setLoadingStatusText("Successfully fetched databases.");
|
Action.LoadDatabases,
|
||||||
TelemetryProcessor.traceSuccess(
|
{
|
||||||
Action.LoadDatabases,
|
databaseAccountName: this.databaseAccount().name,
|
||||||
{
|
defaultExperience: this.defaultExperience(),
|
||||||
databaseAccountName: this.databaseAccount().name,
|
dataExplorerArea: Constants.Areas.ResourceTree
|
||||||
defaultExperience: this.defaultExperience(),
|
},
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree
|
startKey
|
||||||
|
);
|
||||||
|
const currentlySelectedNode: ViewModels.TreeNode = this.selectedNode();
|
||||||
|
const deltaDatabases = this.getDeltaDatabases(databases);
|
||||||
|
this.addDatabasesToList(deltaDatabases.toAdd);
|
||||||
|
this.deleteDatabasesFromList(deltaDatabases.toDelete);
|
||||||
|
this.selectedNode(currentlySelectedNode);
|
||||||
|
this._setLoadingStatusText("Fetching containers...");
|
||||||
|
this.refreshAndExpandNewDatabases(deltaDatabases.toAdd)
|
||||||
|
.then(
|
||||||
|
() => {
|
||||||
|
this._setLoadingStatusText("Successfully fetched containers.");
|
||||||
|
deferred.resolve();
|
||||||
},
|
},
|
||||||
startKey
|
reason => {
|
||||||
);
|
this._setLoadingStatusText("Failed to fetch containers.");
|
||||||
const currentlySelectedNode: ViewModels.TreeNode = this.selectedNode();
|
deferred.reject(reason);
|
||||||
const deltaDatabases = this.getDeltaDatabases(databases, offers);
|
}
|
||||||
this.addDatabasesToList(deltaDatabases.toAdd);
|
)
|
||||||
this.deleteDatabasesFromList(deltaDatabases.toDelete);
|
.finally(() => this.isRefreshingExplorer(false));
|
||||||
this.selectedNode(currentlySelectedNode);
|
},
|
||||||
this._setLoadingStatusText("Fetching containers...");
|
error => {
|
||||||
this.refreshAndExpandNewDatabases(deltaDatabases.toAdd)
|
this._setLoadingStatusText("Failed to fetch databases.");
|
||||||
.then(
|
this.isRefreshingExplorer(false);
|
||||||
() => {
|
deferred.reject(error);
|
||||||
this._setLoadingStatusText("Successfully fetched containers.");
|
TelemetryProcessor.traceFailure(
|
||||||
deferred.resolve();
|
Action.LoadDatabases,
|
||||||
},
|
{
|
||||||
reason => {
|
databaseAccountName: this.databaseAccount().name,
|
||||||
this._setLoadingStatusText("Failed to fetch containers.");
|
defaultExperience: this.defaultExperience(),
|
||||||
deferred.reject(reason);
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
}
|
error: JSON.stringify(error)
|
||||||
)
|
},
|
||||||
.finally(() => this.isRefreshingExplorer(false));
|
startKey
|
||||||
},
|
);
|
||||||
error => {
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
this._setLoadingStatusText("Failed to fetch databases.");
|
ConsoleDataType.Error,
|
||||||
this.isRefreshingExplorer(false);
|
`Error while refreshing databases: ${JSON.stringify(error)}`
|
||||||
deferred.reject(error);
|
);
|
||||||
TelemetryProcessor.traceFailure(
|
}
|
||||||
Action.LoadDatabases,
|
);
|
||||||
{
|
|
||||||
databaseAccountName: this.databaseAccount().name,
|
|
||||||
defaultExperience: this.defaultExperience(),
|
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
|
||||||
error: JSON.stringify(error)
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error while refreshing databases: ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.isServerlessEnabled()) {
|
|
||||||
// Serverless accounts don't support offers call
|
|
||||||
refreshDatabases();
|
|
||||||
} else {
|
|
||||||
const offerPromise: Q.Promise<DataModels.Offer[]> = readOffers();
|
|
||||||
this._setLoadingStatusText("Fetching offers...");
|
|
||||||
offerPromise.then(
|
|
||||||
(offers: DataModels.Offer[]) => {
|
|
||||||
this._setLoadingStatusText("Successfully fetched offers.");
|
|
||||||
refreshDatabases(offers);
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
this._setLoadingStatusText("Failed to fetch offers.");
|
|
||||||
this.isRefreshingExplorer(false);
|
|
||||||
deferred.reject(error);
|
|
||||||
TelemetryProcessor.traceFailure(
|
|
||||||
Action.LoadDatabases,
|
|
||||||
{
|
|
||||||
databaseAccountName: this.databaseAccount().name,
|
|
||||||
defaultExperience: this.defaultExperience(),
|
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
|
||||||
error: JSON.stringify(error)
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error while refreshing databases: ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return deferred.promise.then(
|
return deferred.promise.then(
|
||||||
() => {
|
() => {
|
||||||
@@ -1894,6 +1863,9 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public findSelectedDatabase(): ViewModels.Database {
|
public findSelectedDatabase(): ViewModels.Database {
|
||||||
|
if (!this.selectedNode()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (this.selectedNode().nodeKind === "Database") {
|
if (this.selectedNode().nodeKind === "Database") {
|
||||||
return _.find(this.databases(), (database: ViewModels.Database) => database.rid === this.selectedNode().rid);
|
return _.find(this.databases(), (database: ViewModels.Database) => database.rid === this.selectedNode().rid);
|
||||||
}
|
}
|
||||||
@@ -1954,12 +1926,17 @@ export default class Explorer {
|
|||||||
|
|
||||||
this._importExplorerConfigComplete = true;
|
this._importExplorerConfigComplete = true;
|
||||||
|
|
||||||
|
updateConfigContext({
|
||||||
|
ARM_ENDPOINT: this.armEndpoint()
|
||||||
|
});
|
||||||
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
authorizationToken,
|
authorizationToken,
|
||||||
masterKey,
|
masterKey,
|
||||||
databaseAccount
|
databaseAccount,
|
||||||
|
resourceGroup: inputs.resourceGroup,
|
||||||
|
subscriptionId: inputs.subscriptionId
|
||||||
});
|
});
|
||||||
updateUserContext({ resourceGroup: inputs.resourceGroup, subscriptionId: inputs.subscriptionId });
|
|
||||||
TelemetryProcessor.traceSuccess(
|
TelemetryProcessor.traceSuccess(
|
||||||
Action.LoadDatabaseAccount,
|
Action.LoadDatabaseAccount,
|
||||||
{
|
{
|
||||||
@@ -2095,16 +2072,13 @@ export default class Explorer {
|
|||||||
defaultExperience: this.defaultExperience && this.defaultExperience(),
|
defaultExperience: this.defaultExperience && this.defaultExperience(),
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree
|
dataExplorerArea: Constants.Areas.ResourceTree
|
||||||
});
|
});
|
||||||
databasesToLoad.forEach((database: ViewModels.Database) => {
|
databasesToLoad.forEach(async (database: ViewModels.Database) => {
|
||||||
loadCollectionPromises.push(
|
await database.loadCollections();
|
||||||
database.loadCollections().finally(() => {
|
const isNewDatabase: boolean = _.some(newDatabases, (db: ViewModels.Database) => db.rid === database.rid);
|
||||||
const isNewDatabase: boolean = _.some(newDatabases, (db: ViewModels.Database) => db.rid === database.rid);
|
if (isNewDatabase) {
|
||||||
if (isNewDatabase) {
|
database.expandDatabase();
|
||||||
database.expandDatabase();
|
}
|
||||||
}
|
this.tabsManager.refreshActiveTab(tab => tab.collection && tab.collection.getDatabase().rid === database.rid);
|
||||||
this.tabsManager.refreshActiveTab(tab => tab.collection && tab.collection.getDatabase().rid === database.rid);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Q.all(loadCollectionPromises).done(
|
Q.all(loadCollectionPromises).done(
|
||||||
@@ -2249,8 +2223,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getDeltaDatabases(
|
private getDeltaDatabases(
|
||||||
updatedDatabaseList: DataModels.Database[],
|
updatedDatabaseList: DataModels.Database[]
|
||||||
updatedOffersList: DataModels.Offer[]
|
|
||||||
): { toAdd: ViewModels.Database[]; toDelete: ViewModels.Database[] } {
|
): { toAdd: ViewModels.Database[]; toDelete: ViewModels.Database[] } {
|
||||||
const newDatabases: DataModels.Database[] = _.filter(updatedDatabaseList, (database: DataModels.Database) => {
|
const newDatabases: DataModels.Database[] = _.filter(updatedDatabaseList, (database: DataModels.Database) => {
|
||||||
const databaseExists = _.some(
|
const databaseExists = _.some(
|
||||||
@@ -2259,10 +2232,9 @@ export default class Explorer {
|
|||||||
);
|
);
|
||||||
return !databaseExists;
|
return !databaseExists;
|
||||||
});
|
});
|
||||||
const databasesToAdd: ViewModels.Database[] = _.map(newDatabases, (newDatabase: DataModels.Database) => {
|
const databasesToAdd: ViewModels.Database[] = newDatabases.map(
|
||||||
const databaseOffer: DataModels.Offer = this.getOfferForResource(updatedOffersList, newDatabase._self);
|
(newDatabase: DataModels.Database) => new Database(this, newDatabase)
|
||||||
return new Database(this, newDatabase, databaseOffer);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
let databasesToDelete: ViewModels.Database[] = [];
|
let databasesToDelete: ViewModels.Database[] = [];
|
||||||
ko.utils.arrayForEach(this.databases(), (database: ViewModels.Database) => {
|
ko.utils.arrayForEach(this.databases(), (database: ViewModels.Database) => {
|
||||||
@@ -2312,10 +2284,6 @@ export default class Explorer {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getOfferForResource(offers: DataModels.Offer[], resourceId: string): DataModels.Offer {
|
|
||||||
return _.find(offers, (offer: DataModels.Offer) => offer.resource === resourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public uploadFile(name: string, content: string, parent: NotebookContentItem): Promise<NotebookContentItem> {
|
public uploadFile(name: string, content: string, parent: NotebookContentItem): Promise<NotebookContentItem> {
|
||||||
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
|
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
|
||||||
const error = "Attempt to upload notebook, but notebook is not enabled";
|
const error = "Attempt to upload notebook, but notebook is not enabled";
|
||||||
@@ -2614,9 +2582,11 @@ export default class Explorer {
|
|||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const clearMessage = NotificationConsoleUtils.logConsoleProgress(`Downloading ${notebookFile.path}`);
|
||||||
|
|
||||||
return this.notebookManager?.notebookContentClient.readFileContent(notebookFile.path).then(
|
return this.notebookManager?.notebookContentClient.readFileContent(notebookFile.path).then(
|
||||||
(content: string) => {
|
(content: string) => {
|
||||||
const blob = new Blob([content], { type: "octet/stream" });
|
const blob = stringToBlob(content, "text/plain");
|
||||||
if (navigator.msSaveBlob) {
|
if (navigator.msSaveBlob) {
|
||||||
// for IE and Edge
|
// for IE and Edge
|
||||||
navigator.msSaveBlob(blob, notebookFile.name);
|
navigator.msSaveBlob(blob, notebookFile.name);
|
||||||
@@ -2633,12 +2603,16 @@ export default class Explorer {
|
|||||||
downloadLink.click();
|
downloadLink.click();
|
||||||
downloadLink.remove();
|
downloadLink.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearMessage();
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
ConsoleDataType.Error,
|
ConsoleDataType.Error,
|
||||||
`Could not download notebook ${JSON.stringify(error)}`
|
`Could not download notebook ${JSON.stringify(error)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -3113,12 +3087,6 @@ export default class Explorer {
|
|||||||
} else {
|
} else {
|
||||||
loadingTitle.innerHTML = title;
|
loadingTitle.innerHTML = title;
|
||||||
}
|
}
|
||||||
|
|
||||||
TelemetryProcessor.trace(
|
|
||||||
Action.LoadingStatus,
|
|
||||||
ActionModifiers.Mark,
|
|
||||||
title !== "Welcome to Azure Cosmos DB" ? `Title: ${title}, Text: ${text}` : text
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _openSetupNotebooksPaneForQuickstart(): void {
|
private _openSetupNotebooksPaneForQuickstart(): void {
|
||||||
@@ -3152,4 +3120,15 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async loadSelectedDatabaseOffer(): Promise<void> {
|
||||||
|
const database = this.findSelectedDatabase();
|
||||||
|
await database?.loadOffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async loadDatabaseOffers(): Promise<void> {
|
||||||
|
this.databases()?.forEach(async (database: ViewModels.Database) => {
|
||||||
|
await database.loadOffer();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,13 +87,31 @@ describe("getPkIdFromDocumentId", () => {
|
|||||||
expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("['pkvalue', 'id']");
|
expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("['pkvalue', 'id']");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should create pkid pair from partitioned graph (pk as number)", () => {
|
||||||
|
const doc = createFakeDoc({ id: "id", mypk: 234 });
|
||||||
|
expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("[234, 'id']");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create pkid pair from partitioned graph (pk as boolean)", () => {
|
||||||
|
const doc = createFakeDoc({ id: "id", mypk: true });
|
||||||
|
expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("[true, 'id']");
|
||||||
|
});
|
||||||
|
|
||||||
it("should create pkid pair from partitioned graph (pk as valid array value)", () => {
|
it("should create pkid pair from partitioned graph (pk as valid array value)", () => {
|
||||||
const doc = createFakeDoc({ id: "id", mypk: [{ id: "someid", _value: "pkvalue" }] });
|
const doc = createFakeDoc({ id: "id", mypk: [{ id: "someid", _value: "pkvalue" }] });
|
||||||
expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("['pkvalue', 'id']");
|
expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("['pkvalue', 'id']");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should error if id is not a string", () => {
|
it("should error if id is not a string or number", () => {
|
||||||
const doc = createFakeDoc({ id: { foo: 1 } });
|
let doc = createFakeDoc({ id: { foo: 1 } });
|
||||||
|
try {
|
||||||
|
GraphExplorer.getPkIdFromDocumentId(doc, undefined);
|
||||||
|
expect(true).toBe(false);
|
||||||
|
} catch (e) {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
doc = createFakeDoc({ id: true });
|
||||||
try {
|
try {
|
||||||
GraphExplorer.getPkIdFromDocumentId(doc, undefined);
|
GraphExplorer.getPkIdFromDocumentId(doc, undefined);
|
||||||
expect(true).toBe(false);
|
expect(true).toBe(false);
|
||||||
@@ -102,16 +120,8 @@ describe("getPkIdFromDocumentId", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should error if pk not string nor non-empty array", () => {
|
it("should error if pk is empty array", () => {
|
||||||
let doc = createFakeDoc({ mypk: { foo: 1 } });
|
let doc = createFakeDoc({ mypk: [] });
|
||||||
|
|
||||||
try {
|
|
||||||
GraphExplorer.getPkIdFromDocumentId(doc, "mypk");
|
|
||||||
} catch (e) {
|
|
||||||
expect(true).toBe(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
doc = createFakeDoc({ mypk: [] });
|
|
||||||
try {
|
try {
|
||||||
GraphExplorer.getPkIdFromDocumentId(doc, "mypk");
|
GraphExplorer.getPkIdFromDocumentId(doc, "mypk");
|
||||||
expect(true).toBe(false);
|
expect(true).toBe(false);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { GraphConfig } from "../../Tabs/GraphTab";
|
|||||||
import { EditorReact } from "../../Controls/Editor/EditorReact";
|
import { EditorReact } from "../../Controls/Editor/EditorReact";
|
||||||
import LoadGraphIcon from "../../../../images/LoadGraph.png";
|
import LoadGraphIcon from "../../../../images/LoadGraph.png";
|
||||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import { InputProperty } from "../../../Contracts/ViewModels";
|
import { InputProperty } from "../../../Contracts/ViewModels";
|
||||||
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
|
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
|
||||||
@@ -1371,7 +1371,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
|
|
||||||
if (collectionPartitionKeyProperty && d.hasOwnProperty(collectionPartitionKeyProperty)) {
|
if (collectionPartitionKeyProperty && d.hasOwnProperty(collectionPartitionKeyProperty)) {
|
||||||
let pk = (d as any)[collectionPartitionKeyProperty];
|
let pk = (d as any)[collectionPartitionKeyProperty];
|
||||||
if (typeof pk !== "string") {
|
if (typeof pk !== "string" && typeof pk !== "number" && typeof pk !== "boolean") {
|
||||||
if (Array.isArray(pk) && pk.length > 0) {
|
if (Array.isArray(pk) && pk.length > 0) {
|
||||||
// pk is [{ id: 'id', _value: 'value' }]
|
// pk is [{ id: 'id', _value: 'value' }]
|
||||||
pk = pk[0]["_value"];
|
pk = pk[0]["_value"];
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as ViewModels from "../../../Contracts/ViewModels";
|
|||||||
import { PlatformType } from "../../../PlatformType";
|
import { PlatformType } from "../../../PlatformType";
|
||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { Areas } from "../../../Common/Constants";
|
import { Areas } from "../../../Common/Constants";
|
||||||
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
|
||||||
import AddDatabaseIcon from "../../../../images/AddDatabase.svg";
|
import AddDatabaseIcon from "../../../../images/AddDatabase.svg";
|
||||||
import AddCollectionIcon from "../../../../images/AddCollection.svg";
|
import AddCollectionIcon from "../../../../images/AddCollection.svg";
|
||||||
@@ -391,31 +391,6 @@ export class CommandBarComponentButtonFactory {
|
|||||||
return buttons;
|
return buttons;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static createScaleAndSettingsButton(container: Explorer): CommandButtonComponentProps {
|
|
||||||
let isShared = false;
|
|
||||||
if (container.isDatabaseNodeSelected()) {
|
|
||||||
isShared = container.findSelectedDatabase().isDatabaseShared();
|
|
||||||
} else if (container.isNodeKindSelected("Collection")) {
|
|
||||||
const database: ViewModels.Database = container.findSelectedCollection().getDatabase();
|
|
||||||
isShared = database && database.isDatabaseShared();
|
|
||||||
}
|
|
||||||
|
|
||||||
const label = isShared ? "Settings" : "Scale & Settings";
|
|
||||||
|
|
||||||
return {
|
|
||||||
iconSrc: ScaleIcon,
|
|
||||||
iconAlt: label,
|
|
||||||
onCommandClick: () => {
|
|
||||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
|
||||||
selectedCollection && (<any>selectedCollection).onSettingsClick();
|
|
||||||
},
|
|
||||||
commandButtonLabel: label,
|
|
||||||
ariaLabel: label,
|
|
||||||
hasPopup: true,
|
|
||||||
disabled: container.isDatabaseNodeOrNoneSelected()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static createNewNotebookButton(container: Explorer): CommandButtonComponentProps {
|
private static createNewNotebookButton(container: Explorer): CommandButtonComponentProps {
|
||||||
const label = "New Notebook";
|
const label = "New Notebook";
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -130,11 +130,14 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
<span className="headerStatusEllipsis">{this.state.headerStatus}</span>
|
<span className="headerStatusEllipsis">{this.state.headerStatus}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="expandCollapseButton" role="button" tabIndex={0}>
|
<div
|
||||||
<img
|
className="expandCollapseButton"
|
||||||
src={this.state.isExpanded ? ChevronDownIcon : ChevronUpIcon}
|
role="button"
|
||||||
alt={this.state.isExpanded ? "collapse console" : "expand console"}
|
tabIndex={0}
|
||||||
/>
|
aria-label={this.state.isExpanded ? "collapse console" : "expand console"}
|
||||||
|
aria-expanded={this.state.isExpanded}
|
||||||
|
>
|
||||||
|
<img src={this.state.isExpanded ? ChevronDownIcon : ChevronUpIcon} alt="" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AnimateHeight
|
<AnimateHeight
|
||||||
|
|||||||
@@ -68,12 +68,14 @@ exports[`NotificationConsoleComponent renders the console (expanded) 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
aria-expanded={true}
|
||||||
|
aria-label="collapse console"
|
||||||
className="expandCollapseButton"
|
className="expandCollapseButton"
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="collapse console"
|
alt=""
|
||||||
src=""
|
src=""
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import { Store, AnyAction, MiddlewareAPI, Middleware, Dispatch } from "redux";
|
|||||||
import configureStore from "./NotebookComponent/store";
|
import configureStore from "./NotebookComponent/store";
|
||||||
|
|
||||||
import { Notification } from "react-notification-system";
|
import { Notification } from "react-notification-system";
|
||||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
|
||||||
export type KernelSpecsDisplay = { name: string; displayName: string };
|
export type KernelSpecsDisplay = { name: string; displayName: string };
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export class NotebookComponentBootstrapper {
|
|||||||
actions.fetchContentFulfilled({
|
actions.fetchContentFulfilled({
|
||||||
filepath: undefined,
|
filepath: undefined,
|
||||||
model: NotebookComponentBootstrapper.wrapModelIntoContent(name, undefined, content),
|
model: NotebookComponentBootstrapper.wrapModelIntoContent(name, undefined, content),
|
||||||
kernelRef: createKernelRef(),
|
kernelRef: undefined, // must be undefined or it will be auto-started by the epic
|
||||||
contentRef: this.contentRef
|
contentRef: this.contentRef
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import * as Immutable from "immutable";
|
import * as Immutable from "immutable";
|
||||||
import { ActionsObservable, StateObservable } from "redux-observable";
|
import { ActionsObservable, StateObservable } from "redux-observable";
|
||||||
import { Subject } from "rxjs";
|
import { Subject, empty } from "rxjs";
|
||||||
import { toArray } from "rxjs/operators";
|
import { toArray } from "rxjs/operators";
|
||||||
import { makeNotebookRecord } from "@nteract/commutable";
|
import { makeNotebookRecord } from "@nteract/commutable";
|
||||||
import { actions, state } from "@nteract/core";
|
import { actions, state } from "@nteract/core";
|
||||||
import * as sinon from "sinon";
|
import * as sinon from "sinon";
|
||||||
|
|
||||||
import { CdbAppState, makeCdbRecord } from "./types";
|
import { CdbAppState, makeCdbRecord } from "./types";
|
||||||
import { launchWebSocketKernelEpic } from "./epics";
|
import { launchWebSocketKernelEpic, autoStartKernelEpic } from "./epics";
|
||||||
import { NotebookUtil } from "../NotebookUtil";
|
import { NotebookUtil } from "../NotebookUtil";
|
||||||
|
|
||||||
import { sessions } from "rx-jupyter";
|
import { sessions } from "rx-jupyter";
|
||||||
@@ -74,46 +74,47 @@ describe("Extract kernel from notebook", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
app: state.makeAppRecord({
|
||||||
|
host: state.makeJupyterHostRecord({
|
||||||
|
type: "jupyter",
|
||||||
|
token: "eh",
|
||||||
|
basePath: "/"
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
comms: state.makeCommsRecord(),
|
||||||
|
config: Immutable.Map({}),
|
||||||
|
core: state.makeStateRecord({
|
||||||
|
kernelRef: "fake",
|
||||||
|
entities: state.makeEntitiesRecord({
|
||||||
|
contents: state.makeContentsRecord({
|
||||||
|
byRef: Immutable.Map({
|
||||||
|
fakeContentRef: state.makeNotebookContentRecord()
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
kernels: state.makeKernelsRecord({
|
||||||
|
byRef: Immutable.Map({
|
||||||
|
fake: state.makeRemoteKernelRecord({
|
||||||
|
type: "websocket",
|
||||||
|
channels: new Subject<any>(),
|
||||||
|
kernelSpecName: "fancy",
|
||||||
|
id: "0"
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
cdb: makeCdbRecord({
|
||||||
|
databaseAccountName: "dbAccountName",
|
||||||
|
defaultExperience: "defaultExperience"
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
describe("launchWebSocketKernelEpic", () => {
|
describe("launchWebSocketKernelEpic", () => {
|
||||||
const createSpy = sinon.spy(sessions, "create");
|
const createSpy = sinon.spy(sessions, "create");
|
||||||
|
|
||||||
const contentRef = "fakeContentRef";
|
const contentRef = "fakeContentRef";
|
||||||
const kernelRef = "fake";
|
const kernelRef = "fake";
|
||||||
const initialState = {
|
|
||||||
app: state.makeAppRecord({
|
|
||||||
host: state.makeJupyterHostRecord({
|
|
||||||
type: "jupyter",
|
|
||||||
token: "eh",
|
|
||||||
basePath: "/"
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
comms: state.makeCommsRecord(),
|
|
||||||
config: Immutable.Map({}),
|
|
||||||
core: state.makeStateRecord({
|
|
||||||
kernelRef: "fake",
|
|
||||||
entities: state.makeEntitiesRecord({
|
|
||||||
contents: state.makeContentsRecord({
|
|
||||||
byRef: Immutable.Map({
|
|
||||||
fakeContentRef: state.makeNotebookContentRecord()
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
kernels: state.makeKernelsRecord({
|
|
||||||
byRef: Immutable.Map({
|
|
||||||
fake: state.makeRemoteKernelRecord({
|
|
||||||
type: "websocket",
|
|
||||||
channels: new Subject<any>(),
|
|
||||||
kernelSpecName: "fancy",
|
|
||||||
id: "0"
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
cdb: makeCdbRecord({
|
|
||||||
databaseAccountName: "dbAccountName",
|
|
||||||
defaultExperience: "defaultExperience"
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
it("launches remote kernels", async () => {
|
it("launches remote kernels", async () => {
|
||||||
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
|
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
|
||||||
@@ -490,3 +491,55 @@ describe("launchWebSocketKernelEpic", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("autoStartKernelEpic", () => {
|
||||||
|
const contentRef = "fakeContentRef";
|
||||||
|
const kernelRef = "fake";
|
||||||
|
|
||||||
|
it("automatically starts kernel when content fetch is successful if kernelRef is defined", async () => {
|
||||||
|
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
|
||||||
|
|
||||||
|
const action$ = ActionsObservable.of(
|
||||||
|
actions.fetchContentFulfilled({
|
||||||
|
contentRef,
|
||||||
|
kernelRef,
|
||||||
|
filepath: "filepath",
|
||||||
|
model: {}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const responseActions = await autoStartKernelEpic(action$, state$)
|
||||||
|
.pipe(toArray())
|
||||||
|
.toPromise();
|
||||||
|
|
||||||
|
expect(responseActions).toMatchObject([
|
||||||
|
{
|
||||||
|
type: actions.RESTART_KERNEL,
|
||||||
|
payload: {
|
||||||
|
contentRef,
|
||||||
|
kernelRef,
|
||||||
|
outputHandling: "None"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Don't start kernel when content fetch is successful if kernelRef is not defined", async () => {
|
||||||
|
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
|
||||||
|
|
||||||
|
const action$ = ActionsObservable.of(
|
||||||
|
actions.fetchContentFulfilled({
|
||||||
|
contentRef,
|
||||||
|
kernelRef: undefined,
|
||||||
|
filepath: "filepath",
|
||||||
|
model: {}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const responseActions = await autoStartKernelEpic(action$, state$)
|
||||||
|
.pipe(toArray())
|
||||||
|
.toPromise();
|
||||||
|
|
||||||
|
expect(responseActions).toMatchObject([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { empty, merge, of, timer, concat, Subject, Subscriber, Observable, Observer } from "rxjs";
|
import { EMPTY, merge, of, timer, concat, Subject, Subscriber, Observable, Observer } from "rxjs";
|
||||||
import { webSocket } from "rxjs/webSocket";
|
import { webSocket } from "rxjs/webSocket";
|
||||||
import { ActionsObservable, StateObservable } from "redux-observable";
|
import { ActionsObservable, StateObservable } from "redux-observable";
|
||||||
import { ofType } from "redux-observable";
|
import { ofType } from "redux-observable";
|
||||||
@@ -37,7 +37,7 @@ import * as Constants from "../../../Common/Constants";
|
|||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import * as CdbActions from "./actions";
|
import * as CdbActions from "./actions";
|
||||||
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action as TelemetryAction } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action as TelemetryAction } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { CdbAppState } from "./types";
|
import { CdbAppState } from "./types";
|
||||||
import { decryptJWTToken } from "../../../Utils/AuthorizationUtils";
|
import { decryptJWTToken } from "../../../Utils/AuthorizationUtils";
|
||||||
@@ -77,7 +77,7 @@ const addInitialCodeCellEpic = (
|
|||||||
|
|
||||||
// If it's not a notebook, we shouldn't be here
|
// If it's not a notebook, we shouldn't be here
|
||||||
if (!model || model.type !== "notebook") {
|
if (!model || model.type !== "notebook") {
|
||||||
return empty();
|
return EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cellOrder = selectors.notebook.cellOrder(model);
|
const cellOrder = selectors.notebook.cellOrder(model);
|
||||||
@@ -90,7 +90,40 @@ const addInitialCodeCellEpic = (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return empty();
|
return EMPTY;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically start kernel if kernelRef is present.
|
||||||
|
* The kernel is normally lazy-started when a cell is being executed, but a running kernel is
|
||||||
|
* required for code completion to work.
|
||||||
|
* For notebook viewer, there is no kernel
|
||||||
|
* @param action$
|
||||||
|
* @param state$
|
||||||
|
*/
|
||||||
|
export const autoStartKernelEpic = (
|
||||||
|
action$: ActionsObservable<actions.FetchContentFulfilled>,
|
||||||
|
state$: StateObservable<AppState>
|
||||||
|
): Observable<{} | actions.CreateCellBelow> => {
|
||||||
|
return action$.pipe(
|
||||||
|
ofType(actions.FETCH_CONTENT_FULFILLED),
|
||||||
|
mergeMap(action => {
|
||||||
|
const state = state$.value;
|
||||||
|
const { contentRef, kernelRef } = action.payload;
|
||||||
|
|
||||||
|
if (!kernelRef) {
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return of(
|
||||||
|
actions.restartKernel({
|
||||||
|
contentRef,
|
||||||
|
kernelRef,
|
||||||
|
outputHandling: "None"
|
||||||
|
})
|
||||||
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -288,7 +321,7 @@ export const launchWebSocketKernelEpic = (
|
|||||||
const state = state$.value;
|
const state = state$.value;
|
||||||
const host = selectors.currentHost(state);
|
const host = selectors.currentHost(state);
|
||||||
if (host.type !== "jupyter") {
|
if (host.type !== "jupyter") {
|
||||||
return empty();
|
return EMPTY;
|
||||||
}
|
}
|
||||||
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host);
|
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host);
|
||||||
serverConfig.userPuid = getUserPuid();
|
serverConfig.userPuid = getUserPuid();
|
||||||
@@ -299,7 +332,7 @@ export const launchWebSocketKernelEpic = (
|
|||||||
|
|
||||||
const content = selectors.content(state, { contentRef });
|
const content = selectors.content(state, { contentRef });
|
||||||
if (!content || content.type !== "notebook") {
|
if (!content || content.type !== "notebook") {
|
||||||
return empty();
|
return EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
let kernelSpecToLaunch = kernelSpecName;
|
let kernelSpecToLaunch = kernelSpecName;
|
||||||
@@ -513,26 +546,26 @@ const changeWebSocketKernelEpic = (
|
|||||||
const state = state$.value;
|
const state = state$.value;
|
||||||
const host = selectors.currentHost(state);
|
const host = selectors.currentHost(state);
|
||||||
if (host.type !== "jupyter") {
|
if (host.type !== "jupyter") {
|
||||||
return empty();
|
return EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host);
|
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host);
|
||||||
if (!oldKernelRef) {
|
if (!oldKernelRef) {
|
||||||
return empty();
|
return EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldKernel = selectors.kernel(state, { kernelRef: oldKernelRef });
|
const oldKernel = selectors.kernel(state, { kernelRef: oldKernelRef });
|
||||||
if (!oldKernel || oldKernel.type !== "websocket") {
|
if (!oldKernel || oldKernel.type !== "websocket") {
|
||||||
return empty();
|
return EMPTY;
|
||||||
}
|
}
|
||||||
const { sessionId } = oldKernel;
|
const { sessionId } = oldKernel;
|
||||||
if (!sessionId) {
|
if (!sessionId) {
|
||||||
return empty();
|
return EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = selectors.content(state, { contentRef });
|
const content = selectors.content(state, { contentRef });
|
||||||
if (!content || content.type !== "notebook") {
|
if (!content || content.type !== "notebook") {
|
||||||
return empty();
|
return EMPTY;
|
||||||
}
|
}
|
||||||
const {
|
const {
|
||||||
filepath,
|
filepath,
|
||||||
@@ -593,7 +626,7 @@ const focusInitialCodeCellEpic = (
|
|||||||
|
|
||||||
// If it's not a notebook, we shouldn't be here
|
// If it's not a notebook, we shouldn't be here
|
||||||
if (!model || model.type !== "notebook") {
|
if (!model || model.type !== "notebook") {
|
||||||
return empty();
|
return EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cellOrder = selectors.notebook.cellOrder(model);
|
const cellOrder = selectors.notebook.cellOrder(model);
|
||||||
@@ -608,7 +641,7 @@ const focusInitialCodeCellEpic = (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return empty();
|
return EMPTY;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -661,7 +694,7 @@ const notificationsToUserEpic = (
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return empty();
|
return EMPTY;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -701,7 +734,7 @@ const handleKernelConnectionLostEpic = (
|
|||||||
if (explorer) {
|
if (explorer) {
|
||||||
explorer.showOkModalDialog("kernel restarts", msg);
|
explorer.showOkModalDialog("kernel restarts", msg);
|
||||||
}
|
}
|
||||||
return of(empty());
|
return of(EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
return concat(
|
return concat(
|
||||||
@@ -814,7 +847,7 @@ const closeUnsupportedMimetypesEpic = (
|
|||||||
explorer.showOkModalDialog("File cannot be rendered", msg);
|
explorer.showOkModalDialog("File cannot be rendered", msg);
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
||||||
}
|
}
|
||||||
return empty();
|
return EMPTY;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -842,13 +875,14 @@ const closeContentFailedToFetchEpic = (
|
|||||||
explorer.showOkModalDialog("Failure to load", msg);
|
explorer.showOkModalDialog("Failure to load", msg);
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
||||||
}
|
}
|
||||||
return empty();
|
return EMPTY;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const allEpics = [
|
export const allEpics = [
|
||||||
addInitialCodeCellEpic,
|
addInitialCodeCellEpic,
|
||||||
|
autoStartKernelEpic,
|
||||||
focusInitialCodeCellEpic,
|
focusInitialCodeCellEpic,
|
||||||
notificationsToUserEpic,
|
notificationsToUserEpic,
|
||||||
launchWebSocketKernelEpic,
|
launchWebSocketKernelEpic,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { actions, CoreRecord, reducers as nteractReducers } from "@nteract/core";
|
import { actions, CoreRecord, reducers as nteractReducers } from "@nteract/core";
|
||||||
import { Action } from "redux";
|
import { Action } from "redux";
|
||||||
import { Areas } from "../../../Common/Constants";
|
import { Areas } from "../../../Common/Constants";
|
||||||
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import * as cdbActions from "./actions";
|
import * as cdbActions from "./actions";
|
||||||
import { CdbRecord } from "./types";
|
import { CdbRecord } from "./types";
|
||||||
|
|
||||||
|
|||||||
@@ -194,17 +194,24 @@ export class NotebookContentClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public readFileContent(filePath: string): Promise<string> {
|
public async readFileContent(filePath: string): Promise<string> {
|
||||||
return this.contentProvider
|
const xhr = await this.contentProvider.get(this.getServerConfig(), filePath, { content: 1 }).toPromise();
|
||||||
.get(this.getServerConfig(), filePath, { type: "notebook", format: "text", content: 1 })
|
const content = (xhr.response as any).content;
|
||||||
.toPromise()
|
if (!content) {
|
||||||
.then(xhr => {
|
throw new Error("No content read");
|
||||||
const content = (xhr.response as any).content;
|
}
|
||||||
if (!content) {
|
|
||||||
throw new Error("No content read");
|
const format = (xhr.response as any).format;
|
||||||
}
|
switch (format) {
|
||||||
|
case "text":
|
||||||
|
return content;
|
||||||
|
case "base64":
|
||||||
|
return atob(content);
|
||||||
|
case "json":
|
||||||
return stringifyNotebook(content);
|
return stringifyNotebook(content);
|
||||||
});
|
default:
|
||||||
|
throw new Error(`Unsupported content format ${format}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private deleteNotebookFile(path: string): Promise<string> {
|
private deleteNotebookFile(path: string): Promise<string> {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import * as Logger from "../../Common/Logger";
|
|||||||
import { HttpStatusCodes, Areas } from "../../Common/Constants";
|
import { HttpStatusCodes, Areas } from "../../Common/Constants";
|
||||||
import { GitHubReposPane } from "../Panes/GitHubReposPane";
|
import { GitHubReposPane } from "../Panes/GitHubReposPane";
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { IContentProvider } from "@nteract/core";
|
import { IContentProvider } from "@nteract/core";
|
||||||
import { NotebookContentProvider } from "./NotebookComponent/NotebookContentProvider";
|
import { NotebookContentProvider } from "./NotebookComponent/NotebookContentProvider";
|
||||||
@@ -113,11 +113,14 @@ export default class NotebookManager {
|
|||||||
this.params.resourceTree.initializeGitHubRepos(pinnedRepos);
|
this.params.resourceTree.initializeGitHubRepos(pinnedRepos);
|
||||||
this.params.resourceTree.triggerRender();
|
this.params.resourceTree.triggerRender();
|
||||||
});
|
});
|
||||||
this.junoClient.getPinnedRepos(this.gitHubOAuthService.getTokenObservable()()?.scope);
|
this.refreshPinnedRepos();
|
||||||
}
|
}
|
||||||
|
|
||||||
public refreshPinnedRepos(): void {
|
public refreshPinnedRepos(): void {
|
||||||
this.junoClient.getPinnedRepos(this.gitHubOAuthService.getTokenObservable()()?.scope);
|
const token = this.gitHubOAuthService.getTokenObservable()();
|
||||||
|
if (token) {
|
||||||
|
this.junoClient.getPinnedRepos(token.scope);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async openPublishNotebookPane(
|
public async openPublishNotebookPane(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import "./base.css";
|
|||||||
import "./default.css";
|
import "./default.css";
|
||||||
|
|
||||||
import { CodeCell, RawCell, Cells, MarkdownCell } from "@nteract/stateful-components";
|
import { CodeCell, RawCell, Cells, MarkdownCell } from "@nteract/stateful-components";
|
||||||
|
import Prompt, { PassedPromptProps } from "@nteract/stateful-components/lib/inputs/prompt";
|
||||||
import { AzureTheme } from "./AzureTheme";
|
import { AzureTheme } from "./AzureTheme";
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -15,6 +16,7 @@ import "./NotebookReadOnlyRenderer.less";
|
|||||||
export interface NotebookRendererProps {
|
export interface NotebookRendererProps {
|
||||||
contentRef: any;
|
contentRef: any;
|
||||||
hideInputs?: boolean;
|
hideInputs?: boolean;
|
||||||
|
hidePrompts?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PassedEditorProps {
|
interface PassedEditorProps {
|
||||||
@@ -38,6 +40,29 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
|||||||
loadTransform(this.props as any);
|
loadTransform(this.props as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderPrompt(id: string, contentRef: string): JSX.Element {
|
||||||
|
if (this.props.hidePrompts) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Prompt id={id} contentRef={contentRef}>
|
||||||
|
{(props: PassedPromptProps) => {
|
||||||
|
if (props.status === "busy") {
|
||||||
|
return <React.Fragment>{"[*]"}</React.Fragment>;
|
||||||
|
}
|
||||||
|
if (props.status === "queued") {
|
||||||
|
return <React.Fragment>{"[…]"}</React.Fragment>;
|
||||||
|
}
|
||||||
|
if (typeof props.executionCount === "number") {
|
||||||
|
return <React.Fragment>{`[${props.executionCount}]`}</React.Fragment>;
|
||||||
|
}
|
||||||
|
return <React.Fragment>{"[ ]"}</React.Fragment>;
|
||||||
|
}}
|
||||||
|
</Prompt>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className="NotebookReadOnlyRender">
|
<div className="NotebookReadOnlyRender">
|
||||||
@@ -46,6 +71,7 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
|||||||
code: ({ id, contentRef }: { id: any; contentRef: ContentRef }) => (
|
code: ({ id, contentRef }: { id: any; contentRef: ContentRef }) => (
|
||||||
<CodeCell id={id} contentRef={contentRef}>
|
<CodeCell id={id} contentRef={contentRef}>
|
||||||
{{
|
{{
|
||||||
|
prompt: (props: { id: string; contentRef: string }) => this.renderPrompt(props.id, props.contentRef),
|
||||||
editor: {
|
editor: {
|
||||||
codemirror: (props: PassedEditorProps) =>
|
codemirror: (props: PassedEditorProps) =>
|
||||||
this.props.hideInputs ? <></> : <CodeMirrorEditor {...props} readOnly={"nocursor"} />
|
this.props.hideInputs ? <></> : <CodeMirrorEditor {...props} readOnly={"nocursor"} />
|
||||||
|
|||||||
@@ -24,8 +24,8 @@
|
|||||||
<script type="text/html" id="add-collection-inputs">
|
<script type="text/html" id="add-collection-inputs">
|
||||||
<!-- Add collection header - Start -->
|
<!-- Add collection header - Start -->
|
||||||
<div class="firstdivbg headerline">
|
<div class="firstdivbg headerline">
|
||||||
<span id="containerTitle" data-bind="text: title"></span>
|
<span id="containerTitle" role="heading" aria-level="2" data-bind="text: title" ></span>
|
||||||
<div class="closeImg" id="closeBtnAddCollection" role="button" aria-label="Close pane"
|
<div class="closeImg" id="closeBtnAddCollection" role="button" aria-label="Add collection close pane"
|
||||||
data-bind="click: cancel, event: { keypress: onCloseKeyPress }" tabindex="0">
|
data-bind="click: cancel, event: { keypress: onCloseKeyPress }" tabindex="0">
|
||||||
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
|
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
|
||||||
</div>
|
</div>
|
||||||
@@ -115,10 +115,10 @@
|
|||||||
|
|
||||||
<!-- Database provisioned throughput - Start -->
|
<!-- Database provisioned throughput - Start -->
|
||||||
<!-- ko if: canConfigureThroughput -->
|
<!-- ko if: canConfigureThroughput -->
|
||||||
<div class="databaseProvision" aria-label="New database provision support"
|
<div class="databaseProvision" aria-label="Provision database throughput"
|
||||||
data-bind="visible: databaseCreateNew">
|
data-bind="visible: databaseCreateNew">
|
||||||
<input tabindex="0" type="checkbox" data-test="addCollectionPane-databaseSharedThroughput"
|
<input tabindex="0" type="checkbox" data-test="addCollectionPane-databaseSharedThroughput"
|
||||||
id="addCollection-databaseSharedThroughput" title="Provision shared throughput"
|
id="addCollection-databaseSharedThroughput" title="Provision database throughput"
|
||||||
data-bind="checked: databaseCreateNewShared" />
|
data-bind="checked: databaseCreateNewShared" />
|
||||||
<span class="databaseProvisionText" for="databaseSharedThroughput">Provision database throughput</span>
|
<span class="databaseProvisionText" for="databaseSharedThroughput">Provision database throughput</span>
|
||||||
<span class="infoTooltip" role="tooltip" tabindex="0">
|
<span class="infoTooltip" role="tooltip" tabindex="0">
|
||||||
@@ -517,13 +517,13 @@
|
|||||||
<div>
|
<div>
|
||||||
<span class="mandatoryStar">*</span>
|
<span class="mandatoryStar">*</span>
|
||||||
<span class="addCollectionLabel">Analytical store</span>
|
<span class="addCollectionLabel">Analytical store</span>
|
||||||
<span class="infoTooltip" role="tooltip" tabindex="0">
|
<span class="infoTooltip" role="tooltip" tabindex="0" data-bind="event: { focus: function(data, event) { transferFocus('tooltip1', 'link1') } }">
|
||||||
<img class="infoImg" src="/info-bubble.svg" alt="More information">
|
<img class="infoImg" src="/info-bubble.svg" alt="More information">
|
||||||
<span class="tooltiptext infoTooltipWidth">
|
<span id="tooltip1" class="tooltiptext infoTooltipWidth" data-bind="event: { mouseout: onMouseOut }">
|
||||||
Enable analytical store capability to perform near real-time analytics on your operational
|
Enable analytical store capability to perform near real-time analytics on your operational
|
||||||
data, without impacting the performance of transactional workloads.
|
data, without impacting the performance of transactional workloads.
|
||||||
Learn more <a class="errorLink" href="https://aka.ms/analytical-store-overview"
|
Learn more <a id="link1" class="errorLink" href="https://aka.ms/analytical-store-overview"
|
||||||
target="_blank">here</a>
|
target="_blank" data-bind="event: { focusout: onFocusOut, keydown: onKeyDown.bind($data, 'largePartitionKey') }">here</a>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -537,9 +537,11 @@
|
|||||||
attr: {
|
attr: {
|
||||||
'aria-checked': isAnalyticalStorageOn() ? 'true' : 'false'
|
'aria-checked': isAnalyticalStorageOn() ? 'true' : 'false'
|
||||||
}" />
|
}" />
|
||||||
<span for="enableAnalyticalStorageRadioOn" data-bind="disable: showEnableSynapseLink">
|
<label for="enableAnalyticalStorageRadioOn" class="enableAnalyticalStorageRadioLabel">
|
||||||
On
|
<span data-bind="disable: showEnableSynapseLink">
|
||||||
</span>
|
On
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
<input class="enableAnalyticalStorageRadio" id="enableAnalyticalStorageRadioOff"
|
<input class="enableAnalyticalStorageRadio" id="enableAnalyticalStorageRadioOff"
|
||||||
name="analyticalStore" type="radio" role="radio" tabindex="0" data-bind="
|
name="analyticalStore" type="radio" role="radio" tabindex="0" data-bind="
|
||||||
@@ -549,9 +551,11 @@
|
|||||||
attr: {
|
attr: {
|
||||||
'aria-checked': isAnalyticalStorageOn() ? 'false' : 'true'
|
'aria-checked': isAnalyticalStorageOn() ? 'false' : 'true'
|
||||||
}" />
|
}" />
|
||||||
<span for="enableAnalyticalStorageRadioOff" data-bind="disable: showEnableSynapseLink">
|
<label for="enableAnalyticalStorageRadioOff" class="enableAnalyticalStorageRadioLabel">
|
||||||
Off
|
<span data-bind="disable: showEnableSynapseLink">
|
||||||
</span>
|
Off
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="paragraph italic" data-bind="visible: ttl90DaysEnabled() && isAnalyticalStorageOn()">
|
<div class="paragraph italic" data-bind="visible: ttl90DaysEnabled() && isAnalyticalStorageOn()">
|
||||||
|
|||||||
@@ -9,18 +9,15 @@ import * as PricingUtils from "../../Utils/PricingUtils";
|
|||||||
import * as SharedConstants from "../../Shared/Constants";
|
import * as SharedConstants from "../../Shared/Constants";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import editable from "../../Common/EditableUtility";
|
import editable from "../../Common/EditableUtility";
|
||||||
import EnvironmentUtility from "../../Common/EnvironmentUtility";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import Q from "q";
|
|
||||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { configContext, Platform } from "../../ConfigContext";
|
import { configContext, Platform } from "../../ConfigContext";
|
||||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||||
import { createMongoCollectionWithARM, createMongoCollectionWithProxy } from "../../Common/MongoProxyClient";
|
|
||||||
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
|
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
|
||||||
import { HashMap } from "../../Common/HashMap";
|
import { HashMap } from "../../Common/HashMap";
|
||||||
import { PlatformType } from "../../PlatformType";
|
import { PlatformType } from "../../PlatformType";
|
||||||
import { refreshCachedResources, getOrCreateDatabaseAndCollection } from "../../Common/DocumentClientUtilityBase";
|
import { refreshCachedResources } from "../../Common/DocumentClientUtilityBase";
|
||||||
import { userContext } from "../../UserContext";
|
import { createCollection } from "../../Common/dataAccess/createCollection";
|
||||||
|
|
||||||
export interface AddCollectionPaneOptions extends ViewModels.PaneOptions {
|
export interface AddCollectionPaneOptions extends ViewModels.PaneOptions {
|
||||||
isPreferredApiTable: ko.Computed<boolean>;
|
isPreferredApiTable: ko.Computed<boolean>;
|
||||||
@@ -684,7 +681,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
public open(databaseId?: string) {
|
public async open(databaseId?: string) {
|
||||||
super.open();
|
super.open();
|
||||||
// TODO: Figure out if a database level partition split is about to happen once shared throughput read is available
|
// TODO: Figure out if a database level partition split is about to happen once shared throughput read is available
|
||||||
this.formWarnings("");
|
this.formWarnings("");
|
||||||
@@ -718,18 +715,40 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
dataExplorerArea: Constants.Areas.ContextualPane
|
dataExplorerArea: Constants.Areas.ContextualPane
|
||||||
};
|
};
|
||||||
|
|
||||||
|
await this.container.loadDatabaseOffers();
|
||||||
this._onDatabasesChange(this.container.databases());
|
this._onDatabasesChange(this.container.databases());
|
||||||
this._setFocus();
|
this._setFocus();
|
||||||
|
|
||||||
TelemetryProcessor.trace(Action.CreateCollection, ActionModifiers.Open, addCollectionPaneOpenMessage);
|
TelemetryProcessor.trace(Action.CreateCollection, ActionModifiers.Open, addCollectionPaneOpenMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private transferFocus(elementIdToKeepVisible: string, elementIdToFocus: string): void {
|
||||||
|
document.getElementById(elementIdToKeepVisible).style.visibility = "visible";
|
||||||
|
document.getElementById(elementIdToFocus).focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onFocusOut(_: any, event: any): void {
|
||||||
|
event.target.parentElement.style.visibility = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private onMouseOut(_: any, event: any): void {
|
||||||
|
event.target.style.visibility = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private onKeyDown(previousActiveElementId: string, _: any, event: KeyboardEvent): boolean {
|
||||||
|
if (event.shiftKey && event.keyCode == Constants.KeyCodes.Tab) {
|
||||||
|
document.getElementById(previousActiveElementId).focus();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
// Execute default action
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _onDatabasesChange(newDatabaseIds: ViewModels.Database[]) {
|
private _onDatabasesChange(newDatabaseIds: ViewModels.Database[]) {
|
||||||
const cachedDatabaseIdsList = _.map(newDatabaseIds, (database: ViewModels.Database) => {
|
const cachedDatabaseIdsList = _.map(newDatabaseIds, (database: ViewModels.Database) => {
|
||||||
if (database && database.offer && database.offer()) {
|
if (database && database.offer && database.offer()) {
|
||||||
this._databaseOffers.set(database.id(), database.offer());
|
this._databaseOffers.set(database.id(), database.offer());
|
||||||
} else if (database && database.isDatabaseShared && database.isDatabaseShared()) {
|
|
||||||
database.readSettings();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return database.id();
|
return database.id();
|
||||||
@@ -811,7 +830,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
|
|
||||||
let databaseId: string = this.databaseCreateNew() ? this.databaseId().trim() : this.databaseId();
|
let databaseId: string = this.databaseCreateNew() ? this.databaseId().trim() : this.databaseId();
|
||||||
let collectionId: string = this.collectionId().trim();
|
let collectionId: string = this.collectionId().trim();
|
||||||
let rupm: boolean = this.rupm() === Constants.RUPMStates.on;
|
|
||||||
|
|
||||||
let indexingPolicy: DataModels.IndexingPolicy;
|
let indexingPolicy: DataModels.IndexingPolicy;
|
||||||
// todo - remove mongo indexing policy ticket # 616274
|
// todo - remove mongo indexing policy ticket # 616274
|
||||||
@@ -828,130 +846,28 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.formErrors("");
|
this.formErrors("");
|
||||||
|
|
||||||
this.isExecuting(true);
|
this.isExecuting(true);
|
||||||
|
|
||||||
const createRequest: DataModels.CreateDatabaseAndCollectionRequest = {
|
const databaseLevelThroughput: boolean = this.databaseCreateNew()
|
||||||
|
? this.databaseCreateNewShared()
|
||||||
|
: this.databaseHasSharedOffer() && !this.collectionWithThroughputInShared();
|
||||||
|
const autoPilotMaxThroughput: number = databaseLevelThroughput
|
||||||
|
? this.isSharedAutoPilotSelected() && this.sharedAutoPilotThroughput()
|
||||||
|
: this.isAutoPilotSelected() && this.autoPilotThroughput();
|
||||||
|
const createCollectionParams: DataModels.CreateCollectionParams = {
|
||||||
|
createNewDatabase: this.databaseCreateNew(),
|
||||||
collectionId,
|
collectionId,
|
||||||
databaseId,
|
databaseId,
|
||||||
|
databaseLevelThroughput,
|
||||||
offerThroughput,
|
offerThroughput,
|
||||||
databaseLevelThroughput: this.databaseHasSharedOffer() && !this.collectionWithThroughputInShared(),
|
|
||||||
rupmEnabled: rupm,
|
|
||||||
partitionKey,
|
|
||||||
indexingPolicy,
|
|
||||||
uniqueKeyPolicy,
|
|
||||||
autoPilot,
|
|
||||||
analyticalStorageTtl: this._getAnalyticalStorageTtl(),
|
analyticalStorageTtl: this._getAnalyticalStorageTtl(),
|
||||||
hasAutoPilotV2FeatureFlag: this.hasAutoPilotV2FeatureFlag()
|
autoPilotMaxThroughput,
|
||||||
|
indexingPolicy,
|
||||||
|
partitionKey,
|
||||||
|
uniqueKeyPolicy
|
||||||
};
|
};
|
||||||
|
|
||||||
const options: any = {};
|
createCollection(createCollectionParams).then(
|
||||||
if (this.container.isPreferredApiMongoDB()) {
|
|
||||||
options.initialHeaders = options.initialHeaders || {};
|
|
||||||
options.initialHeaders[Constants.HttpHeaders.supportSpatialLegacyCoordinates] = true;
|
|
||||||
options.initialHeaders[Constants.HttpHeaders.usePolygonsSmallerThanAHemisphere] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const databaseCreateNew = this.databaseCreateNew();
|
|
||||||
const useDatabaseSharedOffer = this.shouldUseDatabaseThroughput();
|
|
||||||
const isSharded: boolean = !!partitionKeyPath;
|
|
||||||
const autopilotSettings: DataModels.RpOptions = this._getAutopilotSettings();
|
|
||||||
|
|
||||||
let createCollectionFunc: () => Q.Promise<DataModels.Collection | DataModels.CreateCollectionWithRpResponse>;
|
|
||||||
|
|
||||||
if (this.container.isPreferredApiMongoDB()) {
|
|
||||||
const isFixedCollectionWithSharedThroughputBeingCreated =
|
|
||||||
this.container.isFixedCollectionWithSharedThroughputSupported() &&
|
|
||||||
!this.isUnlimitedStorageSelected() &&
|
|
||||||
this.databaseHasSharedOffer();
|
|
||||||
const isAadUser = EnvironmentUtility.isAadUser();
|
|
||||||
|
|
||||||
// note: v3 autopilot not supported yet for Mongo fixed collections (only tier supported)
|
|
||||||
if (!isAadUser || isFixedCollectionWithSharedThroughputBeingCreated) {
|
|
||||||
createCollectionFunc = () =>
|
|
||||||
Q(
|
|
||||||
createMongoCollectionWithProxy(
|
|
||||||
databaseId,
|
|
||||||
collectionId,
|
|
||||||
offerThroughput,
|
|
||||||
partitionKeyPath,
|
|
||||||
databaseCreateNew,
|
|
||||||
useDatabaseSharedOffer,
|
|
||||||
isSharded,
|
|
||||||
autopilotSettings
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
createCollectionFunc = () =>
|
|
||||||
Q(
|
|
||||||
createMongoCollectionWithARM(
|
|
||||||
this.container.armEndpoint(),
|
|
||||||
databaseId,
|
|
||||||
this._getAnalyticalStorageTtl(),
|
|
||||||
collectionId,
|
|
||||||
offerThroughput,
|
|
||||||
partitionKeyPath,
|
|
||||||
databaseCreateNew,
|
|
||||||
useDatabaseSharedOffer,
|
|
||||||
isSharded,
|
|
||||||
autopilotSettings
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (this.container.isPreferredApiTable() && EnvironmentUtility.isAadUser()) {
|
|
||||||
createCollectionFunc = () =>
|
|
||||||
Q(
|
|
||||||
AddCollectionUtility.Utilities.createAzureTableWithARM(
|
|
||||||
this.container.armEndpoint(),
|
|
||||||
createRequest,
|
|
||||||
autopilotSettings
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else if (this.container.isPreferredApiGraph() && EnvironmentUtility.isAadUser()) {
|
|
||||||
createCollectionFunc = () =>
|
|
||||||
Q(
|
|
||||||
AddCollectionUtility.CreateCollectionUtilities.createGremlinGraph(
|
|
||||||
this.container.armEndpoint(),
|
|
||||||
databaseId,
|
|
||||||
collectionId,
|
|
||||||
indexingPolicy,
|
|
||||||
offerThroughput,
|
|
||||||
partitionKeyPath,
|
|
||||||
partitionKey.version,
|
|
||||||
databaseCreateNew,
|
|
||||||
useDatabaseSharedOffer,
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
autopilotSettings
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else if (this.container.isPreferredApiDocumentDB() && EnvironmentUtility.isAadUser()) {
|
|
||||||
createCollectionFunc = () =>
|
|
||||||
Q(
|
|
||||||
AddCollectionUtility.CreateSqlCollectionUtilities.createSqlCollection(
|
|
||||||
this.container.armEndpoint(),
|
|
||||||
databaseId,
|
|
||||||
this._getAnalyticalStorageTtl(),
|
|
||||||
collectionId,
|
|
||||||
indexingPolicy,
|
|
||||||
offerThroughput,
|
|
||||||
partitionKeyPath,
|
|
||||||
partitionKey.version,
|
|
||||||
databaseCreateNew,
|
|
||||||
useDatabaseSharedOffer,
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
uniqueKeyPolicy,
|
|
||||||
autopilotSettings
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
createCollectionFunc = () => getOrCreateDatabaseAndCollection(createRequest, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
createCollectionFunc().then(
|
|
||||||
() => {
|
() => {
|
||||||
this.isExecuting(false);
|
this.isExecuting(false);
|
||||||
this.close();
|
this.close();
|
||||||
@@ -1049,7 +965,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
const defaultThroughput = this.container.collectionCreationDefaults.throughput;
|
const defaultThroughput = this.container.collectionCreationDefaults.throughput;
|
||||||
this.throughputSinglePartition(defaultThroughput.fixed);
|
this.throughputSinglePartition(defaultThroughput.fixed);
|
||||||
this.throughputMultiPartition(
|
this.throughputMultiPartition(
|
||||||
AddCollectionUtility.Utilities.getMaxThroughput(this.container.collectionCreationDefaults, this.container)
|
AddCollectionUtility.getMaxThroughput(this.container.collectionCreationDefaults, this.container)
|
||||||
);
|
);
|
||||||
|
|
||||||
this.throughputDatabase(defaultThroughput.shared);
|
this.throughputDatabase(defaultThroughput.shared);
|
||||||
@@ -1234,35 +1150,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
private _getAutopilotSettings(): DataModels.RpOptions {
|
|
||||||
if (
|
|
||||||
(!this.hasAutoPilotV2FeatureFlag() &&
|
|
||||||
this.databaseCreateNewShared() &&
|
|
||||||
this.isSharedAutoPilotSelected() &&
|
|
||||||
this.sharedAutoPilotThroughput()) ||
|
|
||||||
(this.hasAutoPilotV2FeatureFlag() &&
|
|
||||||
this.databaseCreateNewShared() &&
|
|
||||||
this.isSharedAutoPilotSelected() &&
|
|
||||||
this.selectedSharedAutoPilotTier())
|
|
||||||
) {
|
|
||||||
return !this.hasAutoPilotV2FeatureFlag()
|
|
||||||
? {
|
|
||||||
[Constants.HttpHeaders.autoPilotThroughput]: { maxThroughput: this.sharedAutoPilotThroughput() * 1 }
|
|
||||||
}
|
|
||||||
: { [Constants.HttpHeaders.autoPilotTier]: this.selectedSharedAutoPilotTier().toString() };
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.autoPilotThroughput()) ||
|
|
||||||
(this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.selectedAutoPilotTier())
|
|
||||||
) {
|
|
||||||
return !this.hasAutoPilotV2FeatureFlag()
|
|
||||||
? {
|
|
||||||
[Constants.HttpHeaders.autoPilotThroughput]: { maxThroughput: this.autoPilotThroughput() * 1 }
|
|
||||||
}
|
|
||||||
: { [Constants.HttpHeaders.autoPilotTier]: this.selectedAutoPilotTier().toString() };
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _calculateNumberOfPartitions(): number {
|
private _calculateNumberOfPartitions(): number {
|
||||||
// Note: this will not validate properly on accounts that have been set up for custom partitioning,
|
// Note: this will not validate properly on accounts that have been set up for custom partitioning,
|
||||||
@@ -1302,17 +1189,19 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
|
|
||||||
private _updateThroughputLimitByCollectionStorage() {
|
private _updateThroughputLimitByCollectionStorage() {
|
||||||
const storage = this.storage();
|
const storage = this.storage();
|
||||||
const minThroughputRU = AddCollectionUtility.Utilities.getMinRUForStorageOption(
|
const minThroughputRU =
|
||||||
this.container.collectionCreationDefaults,
|
storage === SharedConstants.CollectionCreation.storage10Gb
|
||||||
storage
|
? SharedConstants.CollectionCreation.DefaultCollectionRUs400
|
||||||
);
|
: this.container.collectionCreationDefaults.throughput.unlimitedmin;
|
||||||
|
|
||||||
let maxThroughputRU = AddCollectionUtility.Utilities.getMaxRUForStorageOption(
|
let maxThroughputRU;
|
||||||
this.container.collectionCreationDefaults,
|
|
||||||
storage
|
|
||||||
);
|
|
||||||
if (this.isTryCosmosDBSubscription()) {
|
if (this.isTryCosmosDBSubscription()) {
|
||||||
maxThroughputRU = Constants.TryCosmosExperience.maxRU;
|
maxThroughputRU = Constants.TryCosmosExperience.maxRU;
|
||||||
|
} else {
|
||||||
|
maxThroughputRU =
|
||||||
|
storage === SharedConstants.CollectionCreation.storage10Gb
|
||||||
|
? SharedConstants.CollectionCreation.DefaultCollectionRUs10K
|
||||||
|
: this.container.collectionCreationDefaults.throughput.unlimitedmax;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.minThroughputRU(minThroughputRU);
|
this.minThroughputRU(minThroughputRU);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<script type="text/html" id="add-database-inputs">
|
<script type="text/html" id="add-database-inputs">
|
||||||
<!-- Add database header - Start -->
|
<!-- Add database header - Start -->
|
||||||
<div class="firstdivbg headerline">
|
<div class="firstdivbg headerline">
|
||||||
<span id="databaseTitle" data-bind="text: title"></span>
|
<span id="databaseTitle" role="heading" aria-level="2" data-bind="text: title"></span>
|
||||||
<div class="closeImg" role="button" aria-label="Close pane"
|
<div class="closeImg" role="button" aria-label="Close pane"
|
||||||
data-bind="click: cancel, event: { keypress: onCloseKeyPress }" tabindex="0">
|
data-bind="click: cancel, event: { keypress: onCloseKeyPress }" tabindex="0">
|
||||||
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
|
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
|
||||||
@@ -73,8 +73,8 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<input id="database-id" type="text" aria-required="true" autocomplete="off" pattern="[^/?#\\]*[^/?# \\]"
|
<input id="database-id" type="text" aria-required="true" autocomplete="off" pattern="[^/?#\\]*[^/?# \\]"
|
||||||
title="May not end with space nor contain characters '\' '/' '#' '?'" placeholder="Type a new database id"
|
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
||||||
size="40" class="collid" data-bind="textInput: databaseId, hasFocus: firstFieldHasFocus"
|
size="40" class="collid" data-bind="textInput: databaseId, hasFocus: firstFieldHasFocus, attr: { placeholder: databaseIdPlaceHolder }"
|
||||||
aria-label="Database id" autofocus>
|
aria-label="Database id" autofocus>
|
||||||
|
|
||||||
<!-- Database provisioned throughput - Start -->
|
<!-- Database provisioned throughput - Start -->
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as AddCollectionUtility from "../../Shared/AddCollectionUtility";
|
|
||||||
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
|
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
@@ -8,19 +7,16 @@ import * as PricingUtils from "../../Utils/PricingUtils";
|
|||||||
import * as SharedConstants from "../../Shared/Constants";
|
import * as SharedConstants from "../../Shared/Constants";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import editable from "../../Common/EditableUtility";
|
import editable from "../../Common/EditableUtility";
|
||||||
import EnvironmentUtility from "../../Common/EnvironmentUtility";
|
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 { AddDbUtilities } from "../../Shared/AddDatabaseUtility";
|
|
||||||
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
|
|
||||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||||
|
import { createDatabase } from "../../Common/dataAccess/createDatabase";
|
||||||
import { PlatformType } from "../../PlatformType";
|
import { PlatformType } from "../../PlatformType";
|
||||||
import { refreshCachedOffers, refreshCachedResources, createDatabase } from "../../Common/DocumentClientUtilityBase";
|
|
||||||
import { userContext } from "../../UserContext";
|
|
||||||
|
|
||||||
export default class AddDatabasePane extends ContextualPaneBase {
|
export default class AddDatabasePane extends ContextualPaneBase {
|
||||||
public defaultExperience: ko.Computed<string>;
|
public defaultExperience: ko.Computed<string>;
|
||||||
public databaseIdLabel: ko.Computed<string>;
|
public databaseIdLabel: ko.Computed<string>;
|
||||||
|
public databaseIdPlaceHolder: ko.Computed<string>;
|
||||||
public databaseId: ko.Observable<string>;
|
public databaseId: ko.Observable<string>;
|
||||||
public databaseIdTooltipText: ko.Computed<string>;
|
public databaseIdTooltipText: ko.Computed<string>;
|
||||||
public databaseLevelThroughputTooltipText: ko.Computed<string>;
|
public databaseLevelThroughputTooltipText: ko.Computed<string>;
|
||||||
@@ -75,6 +71,11 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
this.databaseIdLabel = ko.computed<string>(() =>
|
this.databaseIdLabel = ko.computed<string>(() =>
|
||||||
this.container.isPreferredApiCassandra() ? "Keyspace id" : "Database id"
|
this.container.isPreferredApiCassandra() ? "Keyspace id" : "Database id"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.databaseIdPlaceHolder = ko.computed<string>(() =>
|
||||||
|
this.container.isPreferredApiCassandra() ? "Type a new keyspace id" : "Type a new database id"
|
||||||
|
);
|
||||||
|
|
||||||
this.databaseIdTooltipText = ko.computed<string>(() => {
|
this.databaseIdTooltipText = ko.computed<string>(() => {
|
||||||
const isCassandraAccount: boolean = this.container.isPreferredApiCassandra();
|
const isCassandraAccount: boolean = this.container.isPreferredApiCassandra();
|
||||||
return `A ${isCassandraAccount ? "keyspace" : "database"} is a logical container of one or more ${
|
return `A ${isCassandraAccount ? "keyspace" : "database"} is a logical container of one or more ${
|
||||||
@@ -304,76 +305,23 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
this.formErrors("");
|
this.formErrors("");
|
||||||
this.isExecuting(true);
|
this.isExecuting(true);
|
||||||
|
|
||||||
const createDatabaseParameters: DataModels.RpParameters = {
|
const createDatabaseParams: DataModels.CreateDatabaseParams = {
|
||||||
db: addDatabasePaneStartMessage.database.id,
|
autoPilotMaxThroughput: this.maxAutoPilotThroughputSet(),
|
||||||
st: addDatabasePaneStartMessage.database.shared,
|
databaseId: addDatabasePaneStartMessage.database.id,
|
||||||
offerThroughput: addDatabasePaneStartMessage.offerThroughput,
|
databaseLevelThroughput: addDatabasePaneStartMessage.database.shared,
|
||||||
sid: userContext.subscriptionId,
|
offerThroughput: addDatabasePaneStartMessage.offerThroughput
|
||||||
rg: userContext.resourceGroup,
|
|
||||||
dba: addDatabasePaneStartMessage.databaseAccountName
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const autopilotSettings = this._getAutopilotSettings();
|
createDatabase(createDatabaseParams).then(
|
||||||
|
(database: DataModels.Database) => {
|
||||||
if (this.container.isPreferredApiCassandra()) {
|
this._onCreateDatabaseSuccess(offerThroughput, startKey);
|
||||||
this._createKeyspace(createDatabaseParameters, autopilotSettings, startKey);
|
},
|
||||||
} else if (this.container.isPreferredApiMongoDB() && EnvironmentUtility.isAadUser()) {
|
(reason: any) => {
|
||||||
this._createMongoDatabase(createDatabaseParameters, autopilotSettings, startKey);
|
this._onCreateDatabaseFailure(reason, offerThroughput, reason);
|
||||||
} else if (this.container.isPreferredApiGraph() && EnvironmentUtility.isAadUser()) {
|
|
||||||
this._createGremlinDatabase(createDatabaseParameters, autopilotSettings, startKey);
|
|
||||||
} else if (this.container.isPreferredApiDocumentDB() && EnvironmentUtility.isAadUser()) {
|
|
||||||
this._createSqlDatabase(createDatabaseParameters, autopilotSettings, startKey);
|
|
||||||
} else {
|
|
||||||
this._createDatabase(offerThroughput, startKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _createSqlDatabase(
|
|
||||||
createDatabaseParameters: DataModels.RpParameters,
|
|
||||||
autoPilotSettings: DataModels.RpOptions,
|
|
||||||
startKey: number
|
|
||||||
) {
|
|
||||||
AddDbUtilities.createSqlDatabase(this.container.armEndpoint(), createDatabaseParameters, autoPilotSettings).then(
|
|
||||||
() => {
|
|
||||||
Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => {
|
|
||||||
this._onCreateDatabaseSuccess(createDatabaseParameters.offerThroughput, startKey);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createMongoDatabase(
|
|
||||||
createDatabaseParameters: DataModels.RpParameters,
|
|
||||||
autoPilotSettings: DataModels.RpOptions,
|
|
||||||
startKey: number
|
|
||||||
) {
|
|
||||||
AddDbUtilities.createMongoDatabaseWithARM(
|
|
||||||
this.container.armEndpoint(),
|
|
||||||
createDatabaseParameters,
|
|
||||||
autoPilotSettings
|
|
||||||
).then(() => {
|
|
||||||
Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => {
|
|
||||||
this._onCreateDatabaseSuccess(createDatabaseParameters.offerThroughput, startKey);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _createGremlinDatabase(
|
|
||||||
createDatabaseParameters: DataModels.RpParameters,
|
|
||||||
autoPilotSettings: DataModels.RpOptions,
|
|
||||||
startKey: number
|
|
||||||
) {
|
|
||||||
AddDbUtilities.createGremlinDatabase(
|
|
||||||
this.container.armEndpoint(),
|
|
||||||
createDatabaseParameters,
|
|
||||||
autoPilotSettings
|
|
||||||
).then(() => {
|
|
||||||
Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => {
|
|
||||||
this._onCreateDatabaseSuccess(createDatabaseParameters.offerThroughput, startKey);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public resetData() {
|
public resetData() {
|
||||||
this.databaseId("");
|
this.databaseId("");
|
||||||
this.databaseCreateNewShared(this.getSharedThroughputDefault());
|
this.databaseCreateNewShared(this.getSharedThroughputDefault());
|
||||||
@@ -396,72 +344,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createDatabase(offerThroughput: number, telemetryStartKey: number): void {
|
|
||||||
const autoPilot: DataModels.AutoPilotCreationSettings = this._isAutoPilotSelectedAndWhatTier();
|
|
||||||
const createRequest: DataModels.CreateDatabaseRequest = {
|
|
||||||
databaseId: this.databaseId().trim(),
|
|
||||||
offerThroughput,
|
|
||||||
databaseLevelThroughput: this.databaseCreateNewShared(),
|
|
||||||
autoPilot,
|
|
||||||
hasAutoPilotV2FeatureFlag: this.hasAutoPilotV2FeatureFlag()
|
|
||||||
};
|
|
||||||
createDatabase(createRequest).then(
|
|
||||||
(database: DataModels.Database) => {
|
|
||||||
this._onCreateDatabaseSuccess(offerThroughput, telemetryStartKey);
|
|
||||||
},
|
|
||||||
(reason: any) => {
|
|
||||||
this._onCreateDatabaseFailure(reason, offerThroughput, reason);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _createKeyspace(
|
|
||||||
createDatabaseParameters: DataModels.RpParameters,
|
|
||||||
autoPilotSettings: DataModels.RpOptions,
|
|
||||||
startKey: number
|
|
||||||
): void {
|
|
||||||
if (EnvironmentUtility.isAadUser()) {
|
|
||||||
this._createKeyspaceUsingRP(this.container.armEndpoint(), createDatabaseParameters, autoPilotSettings, startKey);
|
|
||||||
} else {
|
|
||||||
this._createKeyspaceUsingProxy(createDatabaseParameters.offerThroughput, startKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _createKeyspaceUsingProxy(offerThroughput: number, telemetryStartKey: number): void {
|
|
||||||
const provisionThroughputQueryPart: string = this.databaseCreateNewShared()
|
|
||||||
? `AND cosmosdb_provisioned_throughput=${offerThroughput}`
|
|
||||||
: "";
|
|
||||||
const createKeyspaceQuery: string = `CREATE KEYSPACE ${this.databaseId().trim()} WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 } ${provisionThroughputQueryPart};`;
|
|
||||||
(this.container.tableDataClient as CassandraAPIDataClient)
|
|
||||||
.createKeyspace(
|
|
||||||
this.container.databaseAccount().properties.cassandraEndpoint,
|
|
||||||
this.container.databaseAccount().id,
|
|
||||||
this.container,
|
|
||||||
createKeyspaceQuery
|
|
||||||
)
|
|
||||||
.then(
|
|
||||||
() => {
|
|
||||||
this._onCreateDatabaseSuccess(offerThroughput, telemetryStartKey);
|
|
||||||
},
|
|
||||||
(reason: any) => {
|
|
||||||
this._onCreateDatabaseFailure(reason, offerThroughput, telemetryStartKey);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _createKeyspaceUsingRP(
|
|
||||||
armEndpoint: string,
|
|
||||||
createKeyspaceParameters: DataModels.RpParameters,
|
|
||||||
autoPilotSettings: DataModels.RpOptions,
|
|
||||||
startKey: number
|
|
||||||
): void {
|
|
||||||
AddDbUtilities.createCassandraKeyspace(armEndpoint, createKeyspaceParameters, autoPilotSettings).then(() => {
|
|
||||||
Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => {
|
|
||||||
this._onCreateDatabaseSuccess(createKeyspaceParameters.offerThroughput, startKey);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _onCreateDatabaseSuccess(offerThroughput: number, startKey: number): void {
|
private _onCreateDatabaseSuccess(offerThroughput: number, startKey: number): void {
|
||||||
this.isExecuting(false);
|
this.isExecuting(false);
|
||||||
this.close();
|
this.close();
|
||||||
@@ -582,20 +464,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getAutopilotSettings(): DataModels.RpOptions {
|
|
||||||
if (
|
|
||||||
(!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.maxAutoPilotThroughputSet()) ||
|
|
||||||
(this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.selectedAutoPilotTier())
|
|
||||||
) {
|
|
||||||
return !this.hasAutoPilotV2FeatureFlag()
|
|
||||||
? {
|
|
||||||
[Constants.HttpHeaders.autoPilotThroughput]: { maxThroughput: this.maxAutoPilotThroughputSet() * 1 }
|
|
||||||
}
|
|
||||||
: { [Constants.HttpHeaders.autoPilotTier]: this.selectedAutoPilotTier().toString() };
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _updateThroughputLimitByDatabase() {
|
private _updateThroughputLimitByDatabase() {
|
||||||
const throughputDefaults = this.container.collectionCreationDefaults.throughput;
|
const throughputDefaults = this.container.collectionCreationDefaults.throughput;
|
||||||
this.throughput(throughputDefaults.shared);
|
this.throughput(throughputDefaults.shared);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<div class="paneContentContainer">
|
<div class="paneContentContainer">
|
||||||
<!-- Save Query header - Start -->
|
<!-- Save Query header - Start -->
|
||||||
<div class="firstdivbg headerline">
|
<div class="firstdivbg headerline">
|
||||||
<span data-bind="text: title"></span>
|
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||||
<div
|
<div
|
||||||
class="closeImg"
|
class="closeImg"
|
||||||
role="button"
|
role="button"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Areas } from "../../Common/Constants";
|
|||||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||||
import * as Logger from "../../Common/Logger";
|
import * as Logger from "../../Common/Logger";
|
||||||
import { QueriesGridComponentAdapter } from "../Controls/QueriesGridReactComponent/QueriesGridComponentAdapter";
|
import { QueriesGridComponentAdapter } from "../Controls/QueriesGridReactComponent/QueriesGridComponentAdapter";
|
||||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import QueryTab from "../Tabs/QueryTab";
|
import QueryTab from "../Tabs/QueryTab";
|
||||||
|
|
||||||
export class BrowseQueriesPane extends ContextualPaneBase {
|
export class BrowseQueriesPane extends ContextualPaneBase {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
>
|
>
|
||||||
<!-- Add Cassandra collection header - Start -->
|
<!-- Add Cassandra collection header - Start -->
|
||||||
<div class="firstdivbg headerline">
|
<div class="firstdivbg headerline">
|
||||||
<span data-bind="text: title"></span>
|
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||||
<div
|
<div
|
||||||
class="closeImg"
|
class="closeImg"
|
||||||
role="button"
|
role="button"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import * as ko from "knockout";
|
|||||||
import * as PricingUtils from "../../Utils/PricingUtils";
|
import * as PricingUtils from "../../Utils/PricingUtils";
|
||||||
import * as SharedConstants from "../../Shared/Constants";
|
import * as SharedConstants from "../../Shared/Constants";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
|
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
|
||||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||||
@@ -268,8 +268,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
const cachedKeyspaceIdsList = _.map(newKeyspaceIds, (keyspace: ViewModels.Database) => {
|
const cachedKeyspaceIdsList = _.map(newKeyspaceIds, (keyspace: ViewModels.Database) => {
|
||||||
if (keyspace && keyspace.offer && !!keyspace.offer()) {
|
if (keyspace && keyspace.offer && !!keyspace.offer()) {
|
||||||
this.keyspaceOffers.set(keyspace.id(), keyspace.offer());
|
this.keyspaceOffers.set(keyspace.id(), keyspace.offer());
|
||||||
} else if (keyspace && keyspace.isDatabaseShared && keyspace.isDatabaseShared()) {
|
|
||||||
keyspace.readSettings();
|
|
||||||
}
|
}
|
||||||
return keyspace.id();
|
return keyspace.id();
|
||||||
});
|
});
|
||||||
@@ -494,9 +492,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
this.selectedSharedAutoPilotTier(null);
|
this.selectedSharedAutoPilotTier(null);
|
||||||
this.selectedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
this.selectedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||||
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||||
this.throughput(
|
this.throughput(AddCollectionUtility.getMaxThroughput(this.container.collectionCreationDefaults, this.container));
|
||||||
AddCollectionUtility.Utilities.getMaxThroughput(this.container.collectionCreationDefaults, this.container)
|
|
||||||
);
|
|
||||||
this.keyspaceThroughput(throughputDefaults.shared);
|
this.keyspaceThroughput(throughputDefaults.shared);
|
||||||
this.maxThroughputRU(throughputDefaults.unlimitedmax);
|
this.maxThroughputRU(throughputDefaults.unlimitedmax);
|
||||||
this.minThroughputRU(throughputDefaults.unlimitedmin);
|
this.minThroughputRU(throughputDefaults.unlimitedmin);
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import * as Constants from "../../Common/Constants";
|
|||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { KeyCodes } from "../../Common/Constants";
|
import { KeyCodes } from "../../Common/Constants";
|
||||||
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
|
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
|
||||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
|
|
||||||
// TODO: Use specific actions for logging telemetry data
|
// TODO: Use specific actions for logging telemetry data
|
||||||
export abstract class ContextualPaneBase extends WaitsForTemplateViewModel {
|
export abstract class ContextualPaneBase extends WaitsForTemplateViewModel {
|
||||||
|
private initalFocusedElement: HTMLElement | undefined;
|
||||||
public id: string;
|
public id: string;
|
||||||
public container: Explorer;
|
public container: Explorer;
|
||||||
public firstFieldHasFocus: ko.Observable<boolean>;
|
public firstFieldHasFocus: ko.Observable<boolean>;
|
||||||
@@ -49,9 +50,11 @@ export abstract class ContextualPaneBase extends WaitsForTemplateViewModel {
|
|||||||
this.visible(false);
|
this.visible(false);
|
||||||
this.isExecuting(false);
|
this.isExecuting(false);
|
||||||
this.resetData();
|
this.resetData();
|
||||||
|
this.resetFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public open() {
|
public open() {
|
||||||
|
this.initalFocusedElement = document.activeElement as HTMLElement;
|
||||||
this.visible(true);
|
this.visible(true);
|
||||||
this.firstFieldHasFocus(true);
|
this.firstFieldHasFocus(true);
|
||||||
this.resizePane();
|
this.resizePane();
|
||||||
@@ -123,4 +126,11 @@ export abstract class ContextualPaneBase extends WaitsForTemplateViewModel {
|
|||||||
|
|
||||||
$(paneElement).height(newPaneElementHeight);
|
$(paneElement).height(newPaneElementHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private resetFocus(): void {
|
||||||
|
if (this.initalFocusedElement) {
|
||||||
|
this.initalFocusedElement.focus();
|
||||||
|
this.initalFocusedElement = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
>
|
>
|
||||||
<!-- Delete Collection Confirmation header - Start -->
|
<!-- Delete Collection Confirmation header - Start -->
|
||||||
<div class="firstdivbg headerline">
|
<div class="firstdivbg headerline">
|
||||||
<span data-bind="text: title"></span>
|
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||||
<div
|
<div
|
||||||
class="closeImg"
|
class="closeImg"
|
||||||
role="button"
|
role="button"
|
||||||
@@ -67,8 +67,7 @@
|
|||||||
name="collectionIdConfirmation"
|
name="collectionIdConfirmation"
|
||||||
required
|
required
|
||||||
class="collid"
|
class="collid"
|
||||||
data-bind="value: collectionIdConfirmation, hasFocus: firstFieldHasFocus"
|
data-bind="value: collectionIdConfirmation, hasFocus: firstFieldHasFocus, attr: { 'aria-label': collectionIdConfirmationText }"
|
||||||
aria-label="Confirm by typing the collection id"
|
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
|
|||||||
import DeleteCollectionConfirmationPane from "./DeleteCollectionConfirmationPane";
|
import DeleteCollectionConfirmationPane from "./DeleteCollectionConfirmationPane";
|
||||||
import DeleteFeedback from "../../Common/DeleteFeedback";
|
import DeleteFeedback from "../../Common/DeleteFeedback";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { TreeNode } from "../../Contracts/ViewModels";
|
import { TreeNode } from "../../Contracts/ViewModels";
|
||||||
import { deleteCollection } from "../../Common/dataAccess/deleteCollection";
|
import { deleteCollection } from "../../Common/dataAccess/deleteCollection";
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { ContextualPaneBase } from "./ContextualPaneBase";
|
|||||||
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
||||||
import DeleteFeedback from "../../Common/DeleteFeedback";
|
import DeleteFeedback from "../../Common/DeleteFeedback";
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { deleteCollection } from "../../Common/dataAccess/deleteCollection";
|
import { deleteCollection } from "../../Common/dataAccess/deleteCollection";
|
||||||
|
|
||||||
export default class DeleteCollectionConfirmationPane extends ContextualPaneBase {
|
export default class DeleteCollectionConfirmationPane extends ContextualPaneBase {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
>
|
>
|
||||||
<!-- Delete Database Confirmation header - Start -->
|
<!-- Delete Database Confirmation header - Start -->
|
||||||
<div class="firstdivbg headerline">
|
<div class="firstdivbg headerline">
|
||||||
<span data-bind="text: title"></span>
|
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||||
<div
|
<div
|
||||||
class="closeImg"
|
class="closeImg"
|
||||||
role="button"
|
role="button"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import * as ViewModels from "../../Contracts/ViewModels";
|
|||||||
import DeleteDatabaseConfirmationPane from "./DeleteDatabaseConfirmationPane";
|
import DeleteDatabaseConfirmationPane from "./DeleteDatabaseConfirmationPane";
|
||||||
import DeleteFeedback from "../../Common/DeleteFeedback";
|
import DeleteFeedback from "../../Common/DeleteFeedback";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { TreeNode } from "../../Contracts/ViewModels";
|
import { TreeNode } from "../../Contracts/ViewModels";
|
||||||
import { TabsManager } from "../Tabs/TabsManager";
|
import { TabsManager } from "../Tabs/TabsManager";
|
||||||
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
|
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility"
|
|||||||
import DeleteFeedback from "../../Common/DeleteFeedback";
|
import DeleteFeedback from "../../Common/DeleteFeedback";
|
||||||
|
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
|
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
|
||||||
|
import { ARMError } from "../../Utils/arm/request";
|
||||||
|
|
||||||
export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
|
export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
|
||||||
public databaseIdConfirmationText: ko.Observable<string>;
|
public databaseIdConfirmationText: ko.Observable<string>;
|
||||||
@@ -105,11 +106,12 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
|
|||||||
this.databaseDeleteFeedback("");
|
this.databaseDeleteFeedback("");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(reason: any) => {
|
(reason: unknown) => {
|
||||||
this.isExecuting(false);
|
this.isExecuting(false);
|
||||||
const message = ErrorParserUtility.parse(reason);
|
|
||||||
this.formErrors(message[0].message);
|
const message = reason instanceof ARMError ? reason.message : ErrorParserUtility.parse(reason)[0].message;
|
||||||
this.formErrorsDetails(message[0].message);
|
this.formErrors(message);
|
||||||
|
this.formErrorsDetails(message);
|
||||||
TelemetryProcessor.traceFailure(
|
TelemetryProcessor.traceFailure(
|
||||||
Action.DeleteDatabase,
|
Action.DeleteDatabase,
|
||||||
{
|
{
|
||||||
@@ -130,7 +132,8 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
|
|||||||
super.resetData();
|
super.resetData();
|
||||||
}
|
}
|
||||||
|
|
||||||
public open() {
|
public async open() {
|
||||||
|
await this.container.loadSelectedDatabaseOffer();
|
||||||
this.recordDeleteFeedback(this.shouldRecordFeedback());
|
this.recordDeleteFeedback(this.shouldRecordFeedback());
|
||||||
super.open();
|
super.open();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<form class="paneContentContainer" data-bind="submit: execute">
|
<form class="paneContentContainer" data-bind="submit: execute">
|
||||||
<!-- Input params header - Start -->
|
<!-- Input params header - Start -->
|
||||||
<div class="firstdivbg headerline">
|
<div class="firstdivbg headerline">
|
||||||
<span data-bind="text: title"></span>
|
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||||
<div
|
<div
|
||||||
class="closeImg"
|
class="closeImg"
|
||||||
role="button"
|
role="button"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export interface GenericRightPaneProps {
|
|||||||
onSubmit: () => void;
|
onSubmit: () => void;
|
||||||
submitButtonText: string;
|
submitButtonText: string;
|
||||||
title: string;
|
title: string;
|
||||||
isSubmitButtonVisible?: boolean;
|
isSubmitButtonHidden?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenericRightPaneState {
|
export interface GenericRightPaneState {
|
||||||
@@ -70,7 +70,9 @@ export class GenericRightPaneComponent extends React.Component<GenericRightPaneP
|
|||||||
private renderPanelHeader = (): JSX.Element => {
|
private renderPanelHeader = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<div className="firstdivbg headerline">
|
<div className="firstdivbg headerline">
|
||||||
<span id="databaseTitle">{this.props.title}</span>
|
<span id="databaseTitle" role="heading" aria-level={2}>
|
||||||
|
{this.props.title}
|
||||||
|
</span>
|
||||||
<IconButton
|
<IconButton
|
||||||
ariaLabel="Close pane"
|
ariaLabel="Close pane"
|
||||||
title="Close pane"
|
title="Close pane"
|
||||||
@@ -108,7 +110,7 @@ export class GenericRightPaneComponent extends React.Component<GenericRightPaneP
|
|||||||
<div className="paneFooter">
|
<div className="paneFooter">
|
||||||
<div className="leftpanel-okbut">
|
<div className="leftpanel-okbut">
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
style={{ visibility: this.props.isSubmitButtonVisible ? "visible" : "hidden" }}
|
style={{ visibility: this.props.isSubmitButtonHidden ? "hidden" : "visible" }}
|
||||||
ariaLabel="Submit"
|
ariaLabel="Submit"
|
||||||
title="Submit"
|
title="Submit"
|
||||||
onClick={this.props.onSubmit}
|
onClick={this.props.onSubmit}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import * as ViewModels from "../../Contracts/ViewModels";
|
|||||||
import { GitHubClient, IGitHubPageInfo, IGitHubRepo } from "../../GitHub/GitHubClient";
|
import { GitHubClient, IGitHubPageInfo, IGitHubRepo } from "../../GitHub/GitHubClient";
|
||||||
import { IPinnedRepo, JunoClient } from "../../Juno/JunoClient";
|
import { IPinnedRepo, JunoClient } from "../../Juno/JunoClient";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||||
import { JunoUtils } from "../../Utils/JunoUtils";
|
import { JunoUtils } from "../../Utils/JunoUtils";
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<form class="paneContentContainer" data-bind="submit: submit">
|
<form class="paneContentContainer" data-bind="submit: submit">
|
||||||
<!-- New Vertex header - Start -->
|
<!-- New Vertex header - Start -->
|
||||||
<div class="firstdivbg headerline">
|
<div class="firstdivbg headerline">
|
||||||
<span>New Vertex</span>
|
<span role="heading" aria-level="2">New Vertex</span>
|
||||||
<div
|
<div
|
||||||
class="closeImg"
|
class="closeImg"
|
||||||
role="button"
|
role="button"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<form class="paneContentContainer" data-bind="submit: submit">
|
<form class="paneContentContainer" data-bind="submit: submit">
|
||||||
<!-- Graph Styling header - Start -->
|
<!-- Graph Styling header - Start -->
|
||||||
<div class="firstdivbg headerline">
|
<div class="firstdivbg headerline">
|
||||||
<span>Graph Styling</span>
|
<span role="heading" aria-level="2">Graph Styling</span>
|
||||||
<div
|
<div
|
||||||
class="closeImg"
|
class="closeImg"
|
||||||
role="button"
|
role="button"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<form class="paneContentContainer" data-bind="submit: submit">
|
<form class="paneContentContainer" data-bind="submit: submit">
|
||||||
<!-- Load Query header - Start -->
|
<!-- Load Query header - Start -->
|
||||||
<div class="firstdivbg headerline">
|
<div class="firstdivbg headerline">
|
||||||
<span data-bind="text: title"></span>
|
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||||
<div
|
<div
|
||||||
class="closeImg"
|
class="closeImg"
|
||||||
role="button"
|
role="button"
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
|
|||||||
submitButtonText: "Publish",
|
submitButtonText: "Publish",
|
||||||
onClose: () => this.close(),
|
onClose: () => this.close(),
|
||||||
onSubmit: () => this.submit(),
|
onSubmit: () => this.submit(),
|
||||||
isSubmitButtonVisible: this.isCodeOfConductAccepted
|
isSubmitButtonHidden: !this.isCodeOfConductAccepted
|
||||||
};
|
};
|
||||||
|
|
||||||
const publishNotebookPaneProps: PublishNotebookPaneProps = {
|
const publishNotebookPaneProps: PublishNotebookPaneProps = {
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ export class PublishNotebookPaneComponent extends React.Component<PublishNoteboo
|
|||||||
<GalleryCardComponent
|
<GalleryCardComponent
|
||||||
data={{
|
data={{
|
||||||
id: undefined,
|
id: undefined,
|
||||||
name: this.props.notebookName,
|
name: this.state.notebookName,
|
||||||
description: this.state.notebookDescription,
|
description: this.state.notebookDescription,
|
||||||
gitSha: undefined,
|
gitSha: undefined,
|
||||||
tags: this.state.notebookTags.split(","),
|
tags: this.state.notebookTags.split(","),
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<form class="paneContentContainer" data-bind="submit: submit">
|
<form class="paneContentContainer" data-bind="submit: submit">
|
||||||
<!-- Renew ad-hoc access header - Start -->
|
<!-- Renew ad-hoc access header - Start -->
|
||||||
<div class="firstdivbg headerline">
|
<div class="firstdivbg headerline">
|
||||||
<span data-bind="text: title"></span>
|
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||||
<div
|
<div
|
||||||
class="closeImg"
|
class="closeImg"
|
||||||
role="button"
|
role="button"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<form class="paneContentContainer" data-bind="submit: submit">
|
<form class="paneContentContainer" data-bind="submit: submit">
|
||||||
<!-- Save Query header - Start -->
|
<!-- Save Query header - Start -->
|
||||||
<div class="firstdivbg headerline">
|
<div class="firstdivbg headerline">
|
||||||
<span data-bind="text: title"></span>
|
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||||
<div class="closeImg" role="button" aria-label="Close pane" tabindex="0" data-bind="click: cancel">
|
<div class="closeImg" role="button" aria-label="Close pane" tabindex="0" data-bind="click: cancel">
|
||||||
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
|
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
|||||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import QueryTab from "../Tabs/QueryTab";
|
import QueryTab from "../Tabs/QueryTab";
|
||||||
|
|
||||||
export class SaveQueryPane extends ContextualPaneBase {
|
export class SaveQueryPane extends ContextualPaneBase {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user