mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-23 10:51:30 +00:00
Compare commits
8 Commits
users/srna
...
replace-co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77f895d343 | ||
|
|
3bc2701356 | ||
|
|
35dbaeea96 | ||
|
|
18745a9ae6 | ||
|
|
5be6f982f9 | ||
|
|
4fc9393b76 | ||
|
|
ee51e873b8 | ||
|
|
206a8ef93b |
@@ -4,4 +4,3 @@ PORTAL_RUNNER_PASSWORD=
|
||||
PORTAL_RUNNER_SUBSCRIPTION=
|
||||
PORTAL_RUNNER_RESOURCE_GROUP=
|
||||
PORTAL_RUNNER_DATABASE_ACCOUNT=
|
||||
PORTAL_RUNNER_CONNECTION_STRING=
|
||||
@@ -266,6 +266,10 @@ src/ResourceProvider/ResourceProviderClientFactory.ts
|
||||
src/RouteHandlers/RouteHandler.ts
|
||||
src/RouteHandlers/TabRouteHandler.test.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/DefaultExperienceUtility.test.ts
|
||||
src/Shared/DefaultExperienceUtility.ts
|
||||
@@ -275,6 +279,8 @@ src/Shared/StorageUtility.test.ts
|
||||
src/Shared/StorageUtility.ts
|
||||
src/Shared/StringUtility.test.ts
|
||||
src/Shared/StringUtility.ts
|
||||
src/Shared/Telemetry/TelemetryConstants.ts
|
||||
src/Shared/Telemetry/TelemetryProcessor.ts
|
||||
src/Shared/appInsights.ts
|
||||
src/SparkClusterManager/ArcadiaResourceManager.ts
|
||||
src/SparkClusterManager/SparkClusterManager.ts
|
||||
@@ -412,5 +418,6 @@ cypress/integration/dataexplorer/SQL/addCollection.spec.ts
|
||||
cypress/integration/dataexplorer/TABLE/addCollection.spec.ts
|
||||
cypress/integration/notebook/newNotebook.spec.ts
|
||||
cypress/integration/notebook/resourceTree.spec.ts
|
||||
__mocks__/AddDatabaseUtility.ts
|
||||
__mocks__/monaco-editor.ts
|
||||
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx
|
||||
20
.github/workflows/ci.yml
vendored
20
.github/workflows/ci.yml
vendored
@@ -196,26 +196,6 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
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:
|
||||
name: Publish Nuget
|
||||
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
||||
|
||||
5
__mocks__/AddDatabaseUtility.ts
Normal file
5
__mocks__/AddDatabaseUtility.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export class AddDbUtilities {
|
||||
createGremlinDatabase(params: any) {
|
||||
return Promise.resolve(1)
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
"test": "cypress run",
|
||||
"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:ci": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/ https-get://0.0.0.0:8081/_explorer/index.html && cypress run --browser edge --headless",
|
||||
"test:ci": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/ https-get://0.0.0.0:8081/_explorer/index.html && cypress run --browser chrome --headless",
|
||||
"test:debug": "cypress open"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -3,8 +3,7 @@ const isCI = require("is-ci");
|
||||
module.exports = {
|
||||
launch: {
|
||||
headless: isCI,
|
||||
slowMo: 50,
|
||||
defaultViewport: null,
|
||||
ignoreHTTPSErrors: true
|
||||
slowMo: isCI ? null : 20,
|
||||
defaultViewport: null
|
||||
}
|
||||
};
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
@SelectionColor: #3074B0;
|
||||
|
||||
@FocusColor: #605e5c;
|
||||
@FocusColor: #00bcf2;
|
||||
|
||||
/******************************************************************************
|
||||
METRICS
|
||||
|
||||
@@ -1522,10 +1522,6 @@ p {
|
||||
.tooltipVisible();
|
||||
}
|
||||
|
||||
.infoTooltip a {
|
||||
color: @AccentHigh;
|
||||
}
|
||||
|
||||
.nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -1650,7 +1646,7 @@ p {
|
||||
}
|
||||
|
||||
.contextual-pane .collid {
|
||||
border: 1px solid #605e5c;
|
||||
border: 1px solid #bbbbbb;
|
||||
font-size: 10px;
|
||||
padding: 5px 10px;
|
||||
color: #000;
|
||||
@@ -2427,6 +2423,22 @@ a:link {
|
||||
display: none;
|
||||
}
|
||||
|
||||
::-webkit-input-placeholder {
|
||||
color: #969696;
|
||||
}
|
||||
|
||||
::-moz-placeholder {
|
||||
color: #969696;
|
||||
}
|
||||
|
||||
:-ms-input-placeholder {
|
||||
color: #969696;
|
||||
}
|
||||
|
||||
:-moz-placeholder {
|
||||
color: #969696;
|
||||
}
|
||||
|
||||
::-ms-expand {
|
||||
color: #969696;
|
||||
}
|
||||
@@ -2976,10 +2988,6 @@ settings-pane {
|
||||
.enableAnalyticalStorageRadio:nth-child(n+2) {
|
||||
margin-left: @LargeSpace;
|
||||
}
|
||||
|
||||
.enableAnalyticalStorageRadioLabel {
|
||||
padding: 0px
|
||||
}
|
||||
}
|
||||
|
||||
.addCollectionLabel {
|
||||
@@ -3010,11 +3018,3 @@ settings-pane {
|
||||
.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.warningErrorContent a {
|
||||
color: @AccentMediumHigh
|
||||
}
|
||||
|
||||
.infoBoxContent a {
|
||||
color: @AccentMediumHigh
|
||||
}
|
||||
|
||||
858
package-lock.json
generated
858
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -8,7 +8,7 @@
|
||||
"@azure/cosmos-language-service": "0.0.4",
|
||||
"@jupyterlab/services": "4.2.0",
|
||||
"@jupyterlab/terminal": "1.2.1",
|
||||
"@microsoft/applicationinsights-web": "2.5.8",
|
||||
"@microsoft/applicationinsights-web": "2.5.4",
|
||||
"@nteract/commutable": "7.1.4",
|
||||
"@nteract/connected-components": "6.7.8",
|
||||
"@nteract/core": "13.0.0",
|
||||
@@ -66,7 +66,7 @@
|
||||
"mkdirp": "1.0.4",
|
||||
"monaco-editor": "0.15.6",
|
||||
"object.entries": "1.1.0",
|
||||
"office-ui-fabric-react": "7.134.1",
|
||||
"office-ui-fabric-react": "7.121.10",
|
||||
"p-retry": "4.2.0",
|
||||
"plotly.js-cartesian-dist-min": "1.52.3",
|
||||
"promise-polyfill": "8.1.0",
|
||||
@@ -102,15 +102,12 @@
|
||||
"@types/d3": "4.13.2",
|
||||
"@types/enzyme": "3.10.3",
|
||||
"@types/enzyme-adapter-react-16": "1.0.5",
|
||||
"@types/expect-puppeteer": "4.4.3",
|
||||
"@types/hasher": "0.0.31",
|
||||
"@types/jest": "23.3.10",
|
||||
"@types/jest-environment-puppeteer": "4.3.2",
|
||||
"@types/memoize-one": "4.1.1",
|
||||
"@types/node": "12.11.1",
|
||||
"@types/promise.prototype.finally": "2.0.3",
|
||||
"@types/prop-types": "15.5.8",
|
||||
"@types/puppeteer": "3.0.1",
|
||||
"@types/q": "1.5.1",
|
||||
"@types/react": "16.8.25",
|
||||
"@types/react-dom": "16.0.7",
|
||||
@@ -121,8 +118,8 @@
|
||||
"@types/text-encoding": "0.0.33",
|
||||
"@types/underscore": "1.7.36",
|
||||
"@types/webfontloader": "1.6.29",
|
||||
"@typescript-eslint/eslint-plugin": "4.0.1",
|
||||
"@typescript-eslint/parser": "4.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "3.2.0",
|
||||
"@typescript-eslint/parser": "3.2.0",
|
||||
"adal-angular": "1.0.15",
|
||||
"axe-puppeteer": "1.1.0",
|
||||
"babel-jest": "24.9.0",
|
||||
@@ -135,7 +132,7 @@
|
||||
"enzyme": "3.10.0",
|
||||
"enzyme-adapter-react-16": "1.15.1",
|
||||
"enzyme-to-json": "3.4.3",
|
||||
"eslint": "7.8.1",
|
||||
"eslint": "7.3.1",
|
||||
"eslint-cli": "1.1.1",
|
||||
"eslint-plugin-no-null": "1.0.2",
|
||||
"eslint-plugin-prefer-arrow": "1.2.2",
|
||||
@@ -167,9 +164,8 @@
|
||||
"ts-loader": "6.2.2",
|
||||
"tslint": "5.11.0",
|
||||
"tslint-microsoft-contrib": "6.0.0",
|
||||
"typescript": "4.0.2",
|
||||
"typescript": "3.9.6",
|
||||
"url-loader": "1.1.1",
|
||||
"wait-on": "4.0.2",
|
||||
"webpack": "4.43.0",
|
||||
"webpack-bundle-analyzer": "3.6.1",
|
||||
"webpack-cli": "3.3.10",
|
||||
@@ -188,7 +184,6 @@
|
||||
"test": "rimraf coverage && jest",
|
||||
"test:e2e": "jest -c ./jest.config.e2e.js --detectOpenHandles",
|
||||
"watch": "npm run start",
|
||||
"wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/",
|
||||
"build:ase": "gulp build:ase",
|
||||
"compile": "tsc",
|
||||
"compile:contracts": "tsc -p ./tsconfig.contracts.json",
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
"offerThroughput": 400,
|
||||
"databaseLevelThroughput": false,
|
||||
"collectionId": "Persons",
|
||||
"createNewDatabase": true,
|
||||
"partitionKey": { "kind": "Hash", "paths": ["/firstname"], "version": 1 },
|
||||
"rupmEnabled": false,
|
||||
"partitionKey": { "kind": "Hash", "paths": ["/firstname"] },
|
||||
"data": [
|
||||
{
|
||||
"firstname": "Eva",
|
||||
|
||||
@@ -1,26 +1,31 @@
|
||||
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 {
|
||||
ConflictDefinition,
|
||||
FeedOptions,
|
||||
ItemDefinition,
|
||||
OfferDefinition,
|
||||
PartitionKeyDefinition,
|
||||
QueryIterator,
|
||||
Resource
|
||||
Resource,
|
||||
TriggerDefinition,
|
||||
OfferDefinition
|
||||
} from "@azure/cosmos";
|
||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||
import Q from "q";
|
||||
import { configContext, Platform } from "../ConfigContext";
|
||||
import * as DataModels from "../Contracts/DataModels";
|
||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import ConflictId from "../Explorer/Tree/ConflictId";
|
||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
||||
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
|
||||
import { OfferUtils } from "../Utils/OfferUtils";
|
||||
import * as Constants from "./Constants";
|
||||
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
|
||||
import { client } from "./CosmosClient";
|
||||
import * as HeadersUtility from "./HeadersUtility";
|
||||
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 StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
||||
import { Platform, configContext } from "../ConfigContext";
|
||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||
import ConflictId from "../Explorer/Tree/ConflictId";
|
||||
|
||||
export function getCommonQueryOptions(options: FeedOptions): any {
|
||||
const storedItemPerPageSetting: number = LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage);
|
||||
@@ -53,55 +58,85 @@ export function queryDocuments(
|
||||
return Q(documentsIterator);
|
||||
}
|
||||
|
||||
export function getPartitionKeyHeaderForConflict(conflictId: ConflictId): Object {
|
||||
const partitionKeyDefinition: DataModels.PartitionKey = conflictId.partitionKey;
|
||||
const partitionKeyValue: any = conflictId.partitionKeyValue;
|
||||
|
||||
return getPartitionKeyHeader(partitionKeyDefinition, partitionKeyValue);
|
||||
}
|
||||
|
||||
export function getPartitionKeyHeader(partitionKeyDefinition: DataModels.PartitionKey, partitionKeyValue: any): Object {
|
||||
if (!partitionKeyDefinition) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (partitionKeyValue === undefined) {
|
||||
return [{}];
|
||||
}
|
||||
|
||||
return [partitionKeyValue];
|
||||
}
|
||||
|
||||
export function updateOffer(
|
||||
offer: DataModels.Offer,
|
||||
newOffer: DataModels.Offer,
|
||||
options?: RequestOptions
|
||||
): Q.Promise<DataModels.Offer> {
|
||||
return Q(
|
||||
client()
|
||||
.offer(offer.id)
|
||||
// TODO Remove casting when SDK types are fixed (https://github.com/Azure/azure-sdk-for-js/issues/10660)
|
||||
.replace((newOffer as unknown) as OfferDefinition, options)
|
||||
.then(response => {
|
||||
return Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => response.resource);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function updateDocument(
|
||||
collection: ViewModels.CollectionBase,
|
||||
documentId: DocumentId,
|
||||
newDocument: any
|
||||
): Q.Promise<any> {
|
||||
const partitionKey = documentId.partitionKeyValue;
|
||||
|
||||
export function readStoredProcedures(
|
||||
collection: ViewModels.Collection,
|
||||
options?: any
|
||||
): Q.Promise<DataModels.StoredProcedure[]> {
|
||||
return Q(
|
||||
client()
|
||||
.database(collection.databaseId)
|
||||
.container(collection.id())
|
||||
.item(documentId.id(), partitionKey)
|
||||
.replace(newDocument)
|
||||
.then(response => response.resource)
|
||||
.scripts.storedProcedures.readAll(options)
|
||||
.fetchAll()
|
||||
.then(response => response.resources as DataModels.StoredProcedure[])
|
||||
);
|
||||
}
|
||||
|
||||
export function readStoredProcedure(
|
||||
collection: ViewModels.Collection,
|
||||
requestedResource: DataModels.Resource,
|
||||
options?: any
|
||||
): Q.Promise<DataModels.StoredProcedure> {
|
||||
return Q(
|
||||
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(
|
||||
client()
|
||||
.database(collection.databaseId)
|
||||
.container(collection.id())
|
||||
.scripts.triggers.readAll(options)
|
||||
.fetchAll()
|
||||
.then(response => response.resources as DataModels.Trigger[])
|
||||
);
|
||||
}
|
||||
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -133,16 +168,6 @@ 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> {
|
||||
const partitionKey = documentId.partitionKeyValue;
|
||||
|
||||
@@ -156,6 +181,155 @@ 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 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)
|
||||
// TODO Remove casting when SDK types are fixed (https://github.com/Azure/azure-sdk-for-js/issues/10660)
|
||||
.replace((newOffer as unknown) as OfferDefinition, 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> {
|
||||
const partitionKey = documentId.partitionKeyValue;
|
||||
|
||||
@@ -184,6 +358,48 @@ 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(
|
||||
collection: ViewModels.Collection,
|
||||
options: any
|
||||
@@ -264,6 +480,69 @@ 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 refreshCachedOffers(): Q.Promise<void> {
|
||||
if (configContext.platform === Platform.Portal) {
|
||||
return sendCachedDataMessage(MessageTypes.RefreshOffers, []);
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||
import Q from "q";
|
||||
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 { 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 StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
||||
import ConflictId from "../Explorer/Tree/ConflictId";
|
||||
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 * as DataAccessUtilityBase from "./DataAccessUtilityBase";
|
||||
import { MinimalQueryIterator, nextPage } from "./IteratorUtilities";
|
||||
import * as Logger from "./Logger";
|
||||
|
||||
// TODO: Log all promise resolutions and errors with verbosity levels
|
||||
export function queryDocuments(
|
||||
@@ -41,6 +43,121 @@ export function getEntityName() {
|
||||
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(
|
||||
collection: ViewModels.Collection,
|
||||
storedProcedure: StoredProcedure,
|
||||
@@ -49,17 +166,22 @@ export function executeStoredProcedure(
|
||||
): Q.Promise<any> {
|
||||
var deferred = Q.defer<any>();
|
||||
|
||||
const clearMessage = logConsoleProgress(`Executing stored procedure ${storedProcedure.id()}`);
|
||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.InProgress,
|
||||
`Executing stored procedure ${storedProcedure.id()}`
|
||||
);
|
||||
DataAccessUtilityBase.executeStoredProcedure(collection, storedProcedure, partitionKeyValue, params)
|
||||
.then(
|
||||
(response: any) => {
|
||||
deferred.resolve(response);
|
||||
logConsoleInfo(
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Info,
|
||||
`Finished executing stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
|
||||
);
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}: ${JSON.stringify(
|
||||
error
|
||||
)}`
|
||||
@@ -70,7 +192,7 @@ export function executeStoredProcedure(
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
clearMessage();
|
||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
@@ -84,23 +206,32 @@ export function queryDocumentsPage(
|
||||
): Q.Promise<ViewModels.QueryResults> {
|
||||
var deferred = Q.defer<ViewModels.QueryResults>();
|
||||
const entityName = getEntityName();
|
||||
const clearMessage = logConsoleProgress(`Querying ${entityName} for container ${resourceName}`);
|
||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.InProgress,
|
||||
`Querying ${entityName} for container ${resourceName}`
|
||||
);
|
||||
Q(nextPage(documentsIterator, firstItemIndex))
|
||||
.then(
|
||||
(result: ViewModels.QueryResults) => {
|
||||
const itemCount = (result.documents && result.documents.length) || 0;
|
||||
logConsoleInfo(`Successfully fetched ${itemCount} ${entityName} for container ${resourceName}`);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Info,
|
||||
`Successfully fetched ${itemCount} ${entityName} for container ${resourceName}`
|
||||
);
|
||||
deferred.resolve(result);
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(`Failed to query ${entityName} for container ${resourceName}: ${JSON.stringify(error)}`);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to query ${entityName} for container ${resourceName}: ${JSON.stringify(error)}`
|
||||
);
|
||||
Logger.logError(JSON.stringify(error), "QueryDocumentsPage", error.code);
|
||||
sendNotificationForError(error);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
clearMessage();
|
||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
@@ -109,21 +240,27 @@ export function queryDocumentsPage(
|
||||
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
||||
var deferred = Q.defer<any>();
|
||||
const entityName = getEntityName();
|
||||
const clearMessage = logConsoleProgress(`Reading ${entityName} ${documentId.id()}`);
|
||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.InProgress,
|
||||
`Reading ${entityName} ${documentId.id()}`
|
||||
);
|
||||
DataAccessUtilityBase.readDocument(collection, documentId)
|
||||
.then(
|
||||
(document: any) => {
|
||||
deferred.resolve(document);
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(`Failed to read ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to read ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`
|
||||
);
|
||||
Logger.logError(JSON.stringify(error), "ReadDocument", error.code);
|
||||
sendNotificationForError(error);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
clearMessage();
|
||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
@@ -136,22 +273,31 @@ export function updateDocument(
|
||||
): Q.Promise<any> {
|
||||
var deferred = Q.defer<any>();
|
||||
const entityName = getEntityName();
|
||||
const clearMessage = logConsoleProgress(`Updating ${entityName} ${documentId.id()}`);
|
||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.InProgress,
|
||||
`Updating ${entityName} ${documentId.id()}`
|
||||
);
|
||||
DataAccessUtilityBase.updateDocument(collection, documentId, newDocument)
|
||||
.then(
|
||||
(updatedDocument: any) => {
|
||||
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Info,
|
||||
`Successfully updated ${entityName} ${documentId.id()}`
|
||||
);
|
||||
deferred.resolve(updatedDocument);
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(`Failed to update ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to update ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`
|
||||
);
|
||||
Logger.logError(JSON.stringify(error), "UpdateDocument", error.code);
|
||||
sendNotificationForError(error);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
clearMessage();
|
||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
@@ -163,15 +309,24 @@ export function updateOffer(
|
||||
options: RequestOptions
|
||||
): Q.Promise<DataModels.Offer> {
|
||||
var deferred = Q.defer<any>();
|
||||
const clearMessage = logConsoleProgress(`Updating offer for resource ${offer.resource}`);
|
||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.InProgress,
|
||||
`Updating offer for resource ${offer.resource}`
|
||||
);
|
||||
DataAccessUtilityBase.updateOffer(offer, newOffer, options)
|
||||
.then(
|
||||
(replacedOffer: DataModels.Offer) => {
|
||||
logConsoleInfo(`Successfully updated offer for resource ${offer.resource}`);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Info,
|
||||
`Successfully updated offer for resource ${offer.resource}`
|
||||
);
|
||||
deferred.resolve(replacedOffer);
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(`Error updating offer for resource ${offer.resource}: ${JSON.stringify(error)}`);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error updating offer for resource ${offer.resource}: ${JSON.stringify(error)}`
|
||||
);
|
||||
Logger.logError(
|
||||
JSON.stringify({
|
||||
oldOffer: offer,
|
||||
@@ -186,7 +341,108 @@ export function updateOffer(
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
clearMessage();
|
||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
||||
});
|
||||
|
||||
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;
|
||||
@@ -195,15 +451,22 @@ export function updateOffer(
|
||||
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
|
||||
var deferred = Q.defer<any>();
|
||||
const entityName = getEntityName();
|
||||
const clearMessage = logConsoleProgress(`Creating new ${entityName} for container ${collection.id()}`);
|
||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.InProgress,
|
||||
`Creating new ${entityName} for container ${collection.id()}`
|
||||
);
|
||||
DataAccessUtilityBase.createDocument(collection, newDocument)
|
||||
.then(
|
||||
(savedDocument: any) => {
|
||||
logConsoleInfo(`Successfully created new ${entityName} for container ${collection.id()}`);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Info,
|
||||
`Successfully created new ${entityName} for container ${collection.id()}`
|
||||
);
|
||||
deferred.resolve(savedDocument);
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while creating new ${entityName} for container ${collection.id()}:\n ${JSON.stringify(error)}`
|
||||
);
|
||||
Logger.logError(JSON.stringify(error), "CreateDocument", error.code);
|
||||
@@ -212,7 +475,115 @@ export function createDocument(collection: ViewModels.CollectionBase, newDocumen
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
clearMessage();
|
||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
||||
});
|
||||
|
||||
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;
|
||||
@@ -221,22 +592,31 @@ export function createDocument(collection: ViewModels.CollectionBase, newDocumen
|
||||
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
||||
var deferred = Q.defer<any>();
|
||||
const entityName = getEntityName();
|
||||
const clearMessage = logConsoleProgress(`Deleting ${entityName} ${documentId.id()}`);
|
||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.InProgress,
|
||||
`Deleting ${entityName} ${documentId.id()}`
|
||||
);
|
||||
DataAccessUtilityBase.deleteDocument(collection, documentId)
|
||||
.then(
|
||||
(response: any) => {
|
||||
logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Info,
|
||||
`Successfully deleted ${entityName} ${documentId.id()}`
|
||||
);
|
||||
deferred.resolve(response);
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(`Error while deleting ${entityName} ${documentId.id()}:\n ${JSON.stringify(error)}`);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while deleting ${entityName} ${documentId.id()}:\n ${JSON.stringify(error)}`
|
||||
);
|
||||
Logger.logError(JSON.stringify(error), "DeleteDocument", error.code);
|
||||
sendNotificationForError(error);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
clearMessage();
|
||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
@@ -249,22 +629,134 @@ export function deleteConflict(
|
||||
): Q.Promise<any> {
|
||||
var deferred = Q.defer<any>();
|
||||
|
||||
const clearMessage = logConsoleProgress(`Deleting conflict ${conflictId.id()}`);
|
||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.InProgress,
|
||||
`Deleting conflict ${conflictId.id()}`
|
||||
);
|
||||
DataAccessUtilityBase.deleteConflict(collection, conflictId, options)
|
||||
.then(
|
||||
(response: any) => {
|
||||
logConsoleInfo(`Successfully deleted conflict ${conflictId.id()}`);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Info,
|
||||
`Successfully deleted conflict ${conflictId.id()}`
|
||||
);
|
||||
deferred.resolve(response);
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(`Error while deleting conflict ${conflictId.id()}:\n ${JSON.stringify(error)}`);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while deleting conflict ${conflictId.id()}:\n ${JSON.stringify(error)}`
|
||||
);
|
||||
Logger.logError(JSON.stringify(error), "DeleteConflict", error.code);
|
||||
sendNotificationForError(error);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
clearMessage();
|
||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
||||
});
|
||||
|
||||
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;
|
||||
@@ -284,21 +776,27 @@ export function readCollectionQuotaInfo(
|
||||
): Q.Promise<DataModels.CollectionQuotaInfo> {
|
||||
var deferred = Q.defer<DataModels.CollectionQuotaInfo>();
|
||||
|
||||
const clearMessage = logConsoleProgress(`Querying quota info for container ${collection.id}`);
|
||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.InProgress,
|
||||
`Querying quota info for container ${collection.id}`
|
||||
);
|
||||
DataAccessUtilityBase.readCollectionQuotaInfo(collection, options)
|
||||
.then(
|
||||
(quota: DataModels.CollectionQuotaInfo) => {
|
||||
deferred.resolve(quota);
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(`Error while querying quota info for container ${collection.id}:\n ${JSON.stringify(error)}`);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while querying quota info for container ${collection.id}:\n ${JSON.stringify(error)}`
|
||||
);
|
||||
Logger.logError(JSON.stringify(error), "ReadCollectionQuotaInfo", error.code);
|
||||
sendNotificationForError(error);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
clearMessage();
|
||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
@@ -307,21 +805,24 @@ export function readCollectionQuotaInfo(
|
||||
export function readOffers(options: any = {}): Q.Promise<DataModels.Offer[]> {
|
||||
var deferred = Q.defer<DataModels.Offer[]>();
|
||||
|
||||
const clearMessage = logConsoleProgress("Querying offers");
|
||||
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Querying offers");
|
||||
DataAccessUtilityBase.readOffers(options)
|
||||
.then(
|
||||
(offers: DataModels.Offer[]) => {
|
||||
deferred.resolve(offers);
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(`Error while querying offers:\n ${JSON.stringify(error)}`);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while querying offers:\n ${JSON.stringify(error)}`
|
||||
);
|
||||
Logger.logError(JSON.stringify(error), "ReadOffers", error.code);
|
||||
sendNotificationForError(error);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
clearMessage();
|
||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
@@ -333,22 +834,59 @@ export function readOffer(
|
||||
): Q.Promise<DataModels.OfferWithHeaders> {
|
||||
var deferred = Q.defer<DataModels.OfferWithHeaders>();
|
||||
|
||||
const clearMessage = logConsoleProgress("Querying offer");
|
||||
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Querying offer");
|
||||
DataAccessUtilityBase.readOffer(requestedResource, options)
|
||||
.then(
|
||||
(offer: DataModels.OfferWithHeaders) => {
|
||||
deferred.resolve(offer);
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(`Error while querying offer:\n ${JSON.stringify(error)}`);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while querying offer:\n ${JSON.stringify(error)}`
|
||||
);
|
||||
Logger.logError(JSON.stringify(error), "ReadOffer", error.code);
|
||||
sendNotificationForError(error);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
clearMessage();
|
||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Q from "q";
|
||||
import * as MessageHandler from "./MessageHandler";
|
||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||
|
||||
describe("Message Handler", () => {
|
||||
it("should handle cached message", async () => {
|
||||
@@ -25,34 +26,4 @@ describe("Message Handler", () => {
|
||||
MessageHandler.runGarbageCollector();
|
||||
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,37 +48,15 @@ export function sendCachedDataMessage<TResponseDataModel>(
|
||||
|
||||
export function sendMessage(data: any): void {
|
||||
if (canSendMessage()) {
|
||||
const dataExplorerWindow = getDataExplorerWindow(window);
|
||||
if (dataExplorerWindow) {
|
||||
dataExplorerWindow.parent.postMessage(
|
||||
window.parent.postMessage(
|
||||
{
|
||||
signature: "pcIframe",
|
||||
data: data
|
||||
},
|
||||
dataExplorerWindow.document.referrer
|
||||
window.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 {
|
||||
return window.parent !== window;
|
||||
|
||||
@@ -5,7 +5,14 @@ import { Collection } from "../Contracts/ViewModels";
|
||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
|
||||
import { updateUserContext } from "../UserContext";
|
||||
import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
|
||||
import {
|
||||
deleteDocument,
|
||||
getEndpoint,
|
||||
queryDocuments,
|
||||
readDocument,
|
||||
updateDocument,
|
||||
_createMongoCollectionWithARM
|
||||
} from "./MongoProxyClient";
|
||||
jest.mock("../ResourceProvider/ResourceProviderClient.ts");
|
||||
|
||||
const databaseId = "testDB";
|
||||
@@ -253,4 +260,58 @@ describe("MongoProxyClient", () => {
|
||||
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,12 +1,16 @@
|
||||
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
|
||||
import queryString from "querystring";
|
||||
import { AuthType } from "../AuthType";
|
||||
import * as Constants from "../Common/Constants";
|
||||
import * as DataExplorerConstants from "../Common/Constants";
|
||||
import { configContext } from "../ConfigContext";
|
||||
import * as DataModels from "../Contracts/DataModels";
|
||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||
import { Collection } from "../Contracts/ViewModels";
|
||||
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
|
||||
import { AddDbUtilities } from "../Shared/AddDatabaseUtility";
|
||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
||||
import { userContext } from "../UserContext";
|
||||
@@ -281,35 +285,43 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
|
||||
}
|
||||
|
||||
export function createMongoCollectionWithProxy(
|
||||
params: DataModels.CreateCollectionParams
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
offerThroughput: number,
|
||||
shardKey: string,
|
||||
createDatabase: boolean,
|
||||
sharedThroughput: boolean,
|
||||
isSharded: boolean,
|
||||
autopilotOptions?: DataModels.RpOptions
|
||||
): Promise<DataModels.Collection> {
|
||||
const databaseAccount = userContext.databaseAccount;
|
||||
const shardKey: string = params.partitionKey?.paths[0];
|
||||
const mongoParams: DataModels.MongoParameters = {
|
||||
const params: DataModels.MongoParameters = {
|
||||
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
|
||||
db: params.databaseId,
|
||||
coll: params.collectionId,
|
||||
db: databaseId,
|
||||
coll: collectionId,
|
||||
pk: shardKey,
|
||||
offerThroughput: params.offerThroughput,
|
||||
cd: params.createNewDatabase,
|
||||
st: params.databaseLevelThroughput,
|
||||
is: !!shardKey,
|
||||
offerThroughput,
|
||||
cd: createDatabase,
|
||||
st: sharedThroughput,
|
||||
is: isSharded,
|
||||
rid: "",
|
||||
rtype: "colls",
|
||||
sid: userContext.subscriptionId,
|
||||
rg: userContext.resourceGroup,
|
||||
dba: databaseAccount.name,
|
||||
isAutoPilot: !!params.autoPilotMaxThroughput,
|
||||
autoPilotThroughput: params.autoPilotMaxThroughput?.toString()
|
||||
isAutoPilot: false
|
||||
};
|
||||
|
||||
if (autopilotOptions) {
|
||||
params.isAutoPilot = true;
|
||||
params.autoPilotTier = autopilotOptions[Constants.HttpHeaders.autoPilotTier] as string;
|
||||
}
|
||||
|
||||
const endpoint = getEndpoint(databaseAccount);
|
||||
|
||||
return window
|
||||
.fetch(
|
||||
`${endpoint}/createCollection?${queryString.stringify(
|
||||
(mongoParams as unknown) as queryString.ParsedUrlQueryInput
|
||||
)}`,
|
||||
`${endpoint}/createCollection?${queryString.stringify((params as unknown) as queryString.ParsedUrlQueryInput)}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -323,10 +335,52 @@ export function createMongoCollectionWithProxy(
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
return errorHandling(response, "creating collection", mongoParams);
|
||||
return errorHandling(response, "creating collection", params);
|
||||
});
|
||||
}
|
||||
|
||||
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 {
|
||||
const serverId = window.dataExplorer.serverId();
|
||||
const extensionEndpoint = window.dataExplorer.extensionEndpoint();
|
||||
@@ -359,3 +413,46 @@ async function errorHandling(response: Response, action: string, params: unknown
|
||||
export function getARMCreateCollectionEndpoint(params: DataModels.MongoParameters): string {
|
||||
return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${userContext.databaseAccount.name}/mongodbDatabases/${params.db}/collections/${params.coll}`;
|
||||
}
|
||||
|
||||
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,8 +10,13 @@ import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||
import { QueryUtils } from "../Utils/QueryUtils";
|
||||
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
||||
import { userContext } from "../UserContext";
|
||||
import { createDocument, deleteDocument, queryDocuments, queryDocumentsPage } from "./DocumentClientUtilityBase";
|
||||
import { createCollection } from "./dataAccess/createCollection";
|
||||
import {
|
||||
createDocument,
|
||||
deleteDocument,
|
||||
getOrCreateDatabaseAndCollection,
|
||||
queryDocuments,
|
||||
queryDocumentsPage
|
||||
} from "./DocumentClientUtilityBase";
|
||||
import * as ErrorParserUtility from "./ErrorParserUtility";
|
||||
import * as Logger from "./Logger";
|
||||
|
||||
@@ -36,13 +41,12 @@ export class QueriesClient {
|
||||
ConsoleDataType.InProgress,
|
||||
"Setting up account for saving queries"
|
||||
);
|
||||
return createCollection({
|
||||
return getOrCreateDatabaseAndCollection({
|
||||
collectionId: SavedQueries.CollectionName,
|
||||
createNewDatabase: true,
|
||||
databaseId: SavedQueries.DatabaseName,
|
||||
partitionKey: QueriesClient.PartitionKey,
|
||||
offerThroughput: SavedQueries.OfferThroughput,
|
||||
databaseLevelThroughput: false
|
||||
databaseLevelThroughput: undefined
|
||||
})
|
||||
.then(
|
||||
(collection: DataModels.Collection) => {
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
jest.mock("../../Utils/arm/request");
|
||||
jest.mock("../CosmosClient");
|
||||
jest.mock("../DataAccessUtilityBase");
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { CreateCollectionParams, DatabaseAccount } from "../../Contracts/DataModels";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { armRequest } from "../../Utils/arm/request";
|
||||
import { client } from "../CosmosClient";
|
||||
import { createCollection, constructRpOptions } from "./createCollection";
|
||||
import { updateUserContext } from "../../UserContext";
|
||||
|
||||
describe("createCollection", () => {
|
||||
const createCollectionParams: CreateCollectionParams = {
|
||||
createNewDatabase: false,
|
||||
collectionId: "testContainer",
|
||||
databaseId: "testDatabase",
|
||||
databaseLevelThroughput: true,
|
||||
offerThroughput: 400
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
updateUserContext({
|
||||
databaseAccount: {
|
||||
name: "test"
|
||||
} as DatabaseAccount,
|
||||
defaultExperience: DefaultAccountExperienceType.DocumentDB
|
||||
});
|
||||
});
|
||||
|
||||
it("should call ARM if logged in with AAD", async () => {
|
||||
window.authType = AuthType.AAD;
|
||||
await createCollection(createCollectionParams);
|
||||
expect(armRequest).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call SDK if not logged in with non-AAD method", async () => {
|
||||
window.authType = AuthType.MasterKey;
|
||||
(client as jest.Mock).mockReturnValue({
|
||||
databases: {
|
||||
createIfNotExists: () => {
|
||||
return {
|
||||
database: {
|
||||
containers: {
|
||||
create: () => ({})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
await createCollection(createCollectionParams);
|
||||
expect(client).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("constructRpOptions should return the correct options", () => {
|
||||
expect(constructRpOptions(createCollectionParams)).toEqual({});
|
||||
|
||||
const manualThroughputParams: CreateCollectionParams = {
|
||||
createNewDatabase: false,
|
||||
collectionId: "testContainer",
|
||||
databaseId: "testDatabase",
|
||||
databaseLevelThroughput: false,
|
||||
offerThroughput: 400
|
||||
};
|
||||
expect(constructRpOptions(manualThroughputParams)).toEqual({ throughput: 400 });
|
||||
|
||||
const autoPilotThroughputParams: CreateCollectionParams = {
|
||||
createNewDatabase: false,
|
||||
collectionId: "testContainer",
|
||||
databaseId: "testDatabase",
|
||||
databaseLevelThroughput: false,
|
||||
offerThroughput: 400,
|
||||
autoPilotMaxThroughput: 4000
|
||||
};
|
||||
expect(constructRpOptions(autoPilotThroughputParams)).toEqual({
|
||||
autoscaleSettings: {
|
||||
maxThroughput: 4000
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,371 +0,0 @@
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ErrorParserUtility from "../ErrorParserUtility";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { ContainerResponse, DatabaseResponse } from "@azure/cosmos";
|
||||
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
|
||||
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||
import * as ARMTypes from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import { client } from "../CosmosClient";
|
||||
import { createMongoCollectionWithProxy } from "../MongoProxyClient";
|
||||
import { createUpdateSqlContainer, getSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import {
|
||||
createUpdateCassandraTable,
|
||||
getCassandraTable
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||
import {
|
||||
createUpdateMongoDBCollection,
|
||||
getMongoDBCollection
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||
import {
|
||||
createUpdateGremlinGraph,
|
||||
getGremlinGraph
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||
import { logConsoleProgress, logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { createDatabase } from "./createDatabase";
|
||||
|
||||
export const createCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||
let collection: DataModels.Collection;
|
||||
const clearMessage = logConsoleProgress(
|
||||
`Creating a new container ${params.collectionId} for database ${params.databaseId}`
|
||||
);
|
||||
try {
|
||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||
if (params.createNewDatabase) {
|
||||
const createDatabaseParams: DataModels.CreateDatabaseParams = {
|
||||
autoPilotMaxThroughput: params.autoPilotMaxThroughput,
|
||||
databaseId: params.databaseId,
|
||||
databaseLevelThroughput: params.databaseLevelThroughput,
|
||||
offerThroughput: params.offerThroughput
|
||||
};
|
||||
await createDatabase(createDatabaseParams);
|
||||
}
|
||||
collection = await createCollectionWithARM(params);
|
||||
} else if (userContext.defaultExperience === DefaultAccountExperienceType.MongoDB) {
|
||||
collection = await createMongoCollectionWithProxy(params);
|
||||
} else {
|
||||
collection = await createCollectionWithSDK(params);
|
||||
}
|
||||
} catch (error) {
|
||||
const sanitizedError = ErrorParserUtility.replaceKnownError(JSON.stringify(error));
|
||||
logConsoleError(`Error while creating container ${params.collectionId}:\n ${sanitizedError}`);
|
||||
logError(JSON.stringify(error), "CreateCollection", error.code);
|
||||
sendNotificationForError(error);
|
||||
clearMessage();
|
||||
throw error;
|
||||
}
|
||||
logConsoleInfo(`Successfully created container ${params.collectionId}`);
|
||||
await refreshCachedResources();
|
||||
clearMessage();
|
||||
return collection;
|
||||
};
|
||||
|
||||
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||
const defaultExperience = userContext.defaultExperience;
|
||||
switch (defaultExperience) {
|
||||
case DefaultAccountExperienceType.DocumentDB:
|
||||
return createSqlContainer(params);
|
||||
case DefaultAccountExperienceType.MongoDB:
|
||||
return createMongoCollection(params);
|
||||
case DefaultAccountExperienceType.Cassandra:
|
||||
return createCassandraTable(params);
|
||||
case DefaultAccountExperienceType.Graph:
|
||||
return createGraph(params);
|
||||
case DefaultAccountExperienceType.Table:
|
||||
return createTable(params);
|
||||
default:
|
||||
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
|
||||
}
|
||||
};
|
||||
|
||||
const createSqlContainer = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||
try {
|
||||
const getResponse = await getSqlContainer(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId,
|
||||
params.collectionId
|
||||
);
|
||||
if (getResponse?.properties?.resource) {
|
||||
throw new Error(`Create container failed: container with id ${params.collectionId} already exists`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== "NotFound") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
||||
const resource: ARMTypes.SqlContainerResource = {
|
||||
id: params.collectionId
|
||||
};
|
||||
if (params.analyticalStorageTtl) {
|
||||
resource.analyticalStorageTtl = params.analyticalStorageTtl;
|
||||
}
|
||||
if (params.indexingPolicy) {
|
||||
resource.indexingPolicy = params.indexingPolicy;
|
||||
}
|
||||
if (params.partitionKey) {
|
||||
resource.partitionKey = params.partitionKey;
|
||||
}
|
||||
if (params.uniqueKeyPolicy) {
|
||||
resource.uniqueKeyPolicy = params.uniqueKeyPolicy;
|
||||
}
|
||||
|
||||
const rpPayload: ARMTypes.SqlDatabaseCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource,
|
||||
options
|
||||
}
|
||||
};
|
||||
|
||||
const createResponse = await createUpdateSqlContainer(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId,
|
||||
params.collectionId,
|
||||
rpPayload
|
||||
);
|
||||
return createResponse && (createResponse.properties.resource as DataModels.Collection);
|
||||
};
|
||||
|
||||
const createMongoCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||
try {
|
||||
const getResponse = await getMongoDBCollection(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId,
|
||||
params.collectionId
|
||||
);
|
||||
if (getResponse?.properties?.resource) {
|
||||
throw new Error(`Create collection failed: collection with id ${params.collectionId} already exists`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== "NotFound") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
||||
const resource: ARMTypes.MongoDBCollectionResource = {
|
||||
id: params.collectionId
|
||||
};
|
||||
if (params.analyticalStorageTtl) {
|
||||
resource.analyticalStorageTtl = params.analyticalStorageTtl;
|
||||
}
|
||||
if (params.partitionKey) {
|
||||
const partitionKeyPath: string = params.partitionKey.paths[0];
|
||||
resource.shardKey = { [partitionKeyPath]: "Hash" };
|
||||
}
|
||||
|
||||
const rpPayload: ARMTypes.MongoDBCollectionCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource,
|
||||
options
|
||||
}
|
||||
};
|
||||
|
||||
const createResponse = await createUpdateMongoDBCollection(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId,
|
||||
params.collectionId,
|
||||
rpPayload
|
||||
);
|
||||
return createResponse && (createResponse.properties.resource as DataModels.Collection);
|
||||
};
|
||||
|
||||
const createCassandraTable = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||
try {
|
||||
const getResponse = await getCassandraTable(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId,
|
||||
params.collectionId
|
||||
);
|
||||
if (getResponse?.properties?.resource) {
|
||||
throw new Error(`Create table failed: table with id ${params.collectionId} already exists`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== "NotFound") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
||||
const resource: ARMTypes.CassandraTableResource = {
|
||||
id: params.collectionId
|
||||
};
|
||||
if (params.analyticalStorageTtl) {
|
||||
resource.analyticalStorageTtl = params.analyticalStorageTtl;
|
||||
}
|
||||
|
||||
const rpPayload: ARMTypes.CassandraTableCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource,
|
||||
options
|
||||
}
|
||||
};
|
||||
|
||||
const createResponse = await createUpdateCassandraTable(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId,
|
||||
params.collectionId,
|
||||
rpPayload
|
||||
);
|
||||
return createResponse && (createResponse.properties.resource as DataModels.Collection);
|
||||
};
|
||||
|
||||
const createGraph = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||
try {
|
||||
const getResponse = await getGremlinGraph(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId,
|
||||
params.collectionId
|
||||
);
|
||||
if (getResponse?.properties?.resource) {
|
||||
throw new Error(`Create graph failed: graph with id ${params.collectionId} already exists`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== "NotFound") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
||||
const resource: ARMTypes.GremlinGraphResource = {
|
||||
id: params.collectionId
|
||||
};
|
||||
|
||||
if (params.indexingPolicy) {
|
||||
resource.indexingPolicy = params.indexingPolicy;
|
||||
}
|
||||
if (params.partitionKey) {
|
||||
resource.partitionKey = params.partitionKey;
|
||||
}
|
||||
if (params.uniqueKeyPolicy) {
|
||||
resource.uniqueKeyPolicy = params.uniqueKeyPolicy;
|
||||
}
|
||||
|
||||
const rpPayload: ARMTypes.GremlinGraphCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource,
|
||||
options
|
||||
}
|
||||
};
|
||||
|
||||
const createResponse = await createUpdateGremlinGraph(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId,
|
||||
params.collectionId,
|
||||
rpPayload
|
||||
);
|
||||
return createResponse && (createResponse.properties.resource as DataModels.Collection);
|
||||
};
|
||||
|
||||
const createTable = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||
try {
|
||||
const getResponse = await getTable(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.collectionId
|
||||
);
|
||||
if (getResponse?.properties?.resource) {
|
||||
throw new Error(`Create table failed: table with id ${params.collectionId} already exists`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== "NotFound") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
||||
const resource: ARMTypes.TableResource = {
|
||||
id: params.collectionId
|
||||
};
|
||||
|
||||
const rpPayload: ARMTypes.TableCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource,
|
||||
options
|
||||
}
|
||||
};
|
||||
|
||||
const createResponse = await createUpdateTable(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.collectionId,
|
||||
rpPayload
|
||||
);
|
||||
return createResponse && (createResponse.properties.resource as DataModels.Collection);
|
||||
};
|
||||
|
||||
export const constructRpOptions = (params: DataModels.CreateDatabaseParams): ARMTypes.CreateUpdateOptions => {
|
||||
if (params.databaseLevelThroughput) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (params.autoPilotMaxThroughput) {
|
||||
return {
|
||||
autoscaleSettings: {
|
||||
maxThroughput: params.autoPilotMaxThroughput
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
throughput: params.offerThroughput
|
||||
};
|
||||
};
|
||||
|
||||
const createCollectionWithSDK = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||
const createCollectionBody: ContainerRequest = {
|
||||
id: params.collectionId,
|
||||
partitionKey: params.partitionKey || undefined,
|
||||
indexingPolicy: params.indexingPolicy || undefined,
|
||||
uniqueKeyPolicy: params.uniqueKeyPolicy || undefined,
|
||||
analyticalStorageTtl: params.analyticalStorageTtl
|
||||
} as ContainerRequest; // TODO: remove cast when https://github.com/Azure/azure-cosmos-js/issues/423 is fixed
|
||||
const collectionOptions: RequestOptions = {};
|
||||
const createDatabaseBody: DatabaseRequest = { id: params.databaseId };
|
||||
|
||||
if (params.databaseLevelThroughput) {
|
||||
if (params.autoPilotMaxThroughput) {
|
||||
createDatabaseBody.maxThroughput = params.autoPilotMaxThroughput;
|
||||
} else {
|
||||
createDatabaseBody.throughput = params.offerThroughput;
|
||||
}
|
||||
} else {
|
||||
if (params.autoPilotMaxThroughput) {
|
||||
createCollectionBody.maxThroughput = params.autoPilotMaxThroughput;
|
||||
} else {
|
||||
createCollectionBody.throughput = params.offerThroughput;
|
||||
}
|
||||
}
|
||||
|
||||
const databaseResponse: DatabaseResponse = await client().databases.createIfNotExists(createDatabaseBody);
|
||||
const collectionResponse: ContainerResponse = await databaseResponse?.database.containers.create(
|
||||
createCollectionBody,
|
||||
collectionOptions
|
||||
);
|
||||
return collectionResponse?.resource as DataModels.Collection;
|
||||
};
|
||||
@@ -3,10 +3,8 @@ import { AuthType } from "../../AuthType";
|
||||
import { DatabaseResponse } from "@azure/cosmos";
|
||||
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||
import {
|
||||
CassandraKeyspaceCreateUpdateParameters,
|
||||
GremlinDatabaseCreateUpdateParameters,
|
||||
MongoDBDatabaseCreateUpdateParameters,
|
||||
SqlDatabaseCreateUpdateParameters,
|
||||
CreateUpdateOptions
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
@@ -81,7 +79,7 @@ async function createSqlDatabase(params: DataModels.CreateDatabaseParams): Promi
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId
|
||||
);
|
||||
if (getResponse?.properties?.resource) {
|
||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
||||
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -117,7 +115,7 @@ async function createMongoDatabase(params: DataModels.CreateDatabaseParams): Pro
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId
|
||||
);
|
||||
if (getResponse?.properties?.resource) {
|
||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
||||
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -127,7 +125,7 @@ async function createMongoDatabase(params: DataModels.CreateDatabaseParams): Pro
|
||||
}
|
||||
|
||||
const options: CreateUpdateOptions = constructRpOptions(params);
|
||||
const rpPayload: MongoDBDatabaseCreateUpdateParameters = {
|
||||
const rpPayload: SqlDatabaseCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource: {
|
||||
id: params.databaseId
|
||||
@@ -163,7 +161,7 @@ async function createCassandraKeyspace(params: DataModels.CreateDatabaseParams):
|
||||
}
|
||||
|
||||
const options: CreateUpdateOptions = constructRpOptions(params);
|
||||
const rpPayload: CassandraKeyspaceCreateUpdateParameters = {
|
||||
const rpPayload: SqlDatabaseCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource: {
|
||||
id: params.databaseId
|
||||
@@ -189,7 +187,7 @@ async function createGremlineDatabase(params: DataModels.CreateDatabaseParams):
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId
|
||||
);
|
||||
if (getResponse?.properties?.resource) {
|
||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
||||
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -199,7 +197,7 @@ async function createGremlineDatabase(params: DataModels.CreateDatabaseParams):
|
||||
}
|
||||
|
||||
const options: CreateUpdateOptions = constructRpOptions(params);
|
||||
const rpPayload: GremlinDatabaseCreateUpdateParameters = {
|
||||
const rpPayload: SqlDatabaseCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource: {
|
||||
id: params.databaseId
|
||||
@@ -219,7 +217,8 @@ async function createGremlineDatabase(params: DataModels.CreateDatabaseParams):
|
||||
|
||||
async function createDatabaseWithSDK(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||
const createBody: DatabaseRequest = { id: params.databaseId };
|
||||
|
||||
const databaseOptions: RequestOptions = {};
|
||||
// TODO: replace when SDK support autopilot
|
||||
if (params.databaseLevelThroughput) {
|
||||
if (params.autoPilotMaxThroughput) {
|
||||
createBody.maxThroughput = params.autoPilotMaxThroughput;
|
||||
@@ -228,7 +227,7 @@ async function createDatabaseWithSDK(params: DataModels.CreateDatabaseParams): P
|
||||
}
|
||||
}
|
||||
|
||||
const response: DatabaseResponse = await client().databases.create(createBody);
|
||||
const response: DatabaseResponse = await client().databases.create(createBody, databaseOptions);
|
||||
return response.resource;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
|
||||
export async function createStoredProcedure(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
storedProcedure: StoredProcedureDefinition
|
||||
): Promise<StoredProcedureDefinition & Resource> {
|
||||
let createdStoredProcedure: StoredProcedureDefinition & Resource;
|
||||
const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`);
|
||||
try {
|
||||
const response = await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.storedProcedures.create(storedProcedure);
|
||||
createdStoredProcedure = response.resource;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while creating stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "CreateStoredProcedure", error.code);
|
||||
sendNotificationForError(error);
|
||||
}
|
||||
|
||||
clearMessage();
|
||||
return createdStoredProcedure;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { Resource, TriggerDefinition } from "@azure/cosmos";
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
|
||||
export async function createTrigger(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
trigger: TriggerDefinition
|
||||
): Promise<TriggerDefinition & Resource> {
|
||||
let createdTrigger: TriggerDefinition & Resource;
|
||||
const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`);
|
||||
try {
|
||||
const response = await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.triggers.create(trigger);
|
||||
createdTrigger = response.resource;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while creating trigger ${trigger.id}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "CreateTrigger", error.code);
|
||||
sendNotificationForError(error);
|
||||
}
|
||||
|
||||
clearMessage();
|
||||
return createdTrigger;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
|
||||
export async function createUserDefinedFunction(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
userDefinedFunction: UserDefinedFunctionDefinition
|
||||
): Promise<UserDefinedFunctionDefinition & Resource> {
|
||||
let createdUserDefinedFunction: UserDefinedFunctionDefinition & Resource;
|
||||
const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`);
|
||||
try {
|
||||
const response = await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.userDefinedFunctions.create(userDefinedFunction);
|
||||
createdUserDefinedFunction = response.resource;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while creating user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "CreateUserupdateUserDefinedFunction", error.code);
|
||||
sendNotificationForError(error);
|
||||
}
|
||||
|
||||
clearMessage();
|
||||
return createdUserDefinedFunction;
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
|
||||
export async function deleteStoredProcedure(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
storedProcedureId: string
|
||||
): Promise<void> {
|
||||
const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`);
|
||||
try {
|
||||
await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.storedProcedure(storedProcedureId)
|
||||
.delete();
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while deleting stored procedure ${storedProcedureId}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "DeleteStoredProcedure", error.code);
|
||||
sendNotificationForError(error);
|
||||
}
|
||||
|
||||
clearMessage();
|
||||
return undefined;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
|
||||
export async function deleteTrigger(databaseId: string, collectionId: string, triggerId: string): Promise<void> {
|
||||
const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`);
|
||||
try {
|
||||
await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.trigger(triggerId)
|
||||
.delete();
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while deleting trigger ${triggerId}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "DeleteTrigger", error.code);
|
||||
sendNotificationForError(error);
|
||||
}
|
||||
|
||||
clearMessage();
|
||||
return undefined;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
|
||||
export async function deleteUserDefinedFunction(databaseId: string, collectionId: string, id: string): Promise<void> {
|
||||
const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`);
|
||||
try {
|
||||
await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.userDefinedFunction(id)
|
||||
.delete();
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while deleting user defined function ${id}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "DeleteUserDefinedFunction", error.code);
|
||||
sendNotificationForError(error);
|
||||
}
|
||||
|
||||
clearMessage();
|
||||
return undefined;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
|
||||
export async function readStoredProcedures(
|
||||
databaseId: string,
|
||||
collectionId: string
|
||||
): Promise<(StoredProcedureDefinition & Resource)[]> {
|
||||
let sprocs: (StoredProcedureDefinition & Resource)[];
|
||||
const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`);
|
||||
try {
|
||||
const response = await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.storedProcedures.readAll()
|
||||
.fetchAll();
|
||||
sprocs = response.resources;
|
||||
} catch (error) {
|
||||
logConsoleError(`Failed to query stored procedures for container ${collectionId}: ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadStoredProcedures", error.code);
|
||||
sendNotificationForError(error);
|
||||
}
|
||||
clearMessage();
|
||||
return sprocs;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Resource, TriggerDefinition } from "@azure/cosmos";
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
|
||||
export async function readTriggers(
|
||||
databaseId: string,
|
||||
collectionId: string
|
||||
): Promise<(TriggerDefinition & Resource)[]> {
|
||||
let triggers: (TriggerDefinition & Resource)[];
|
||||
const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`);
|
||||
try {
|
||||
const response = await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.triggers.readAll()
|
||||
.fetchAll();
|
||||
triggers = response.resources;
|
||||
} catch (error) {
|
||||
logConsoleError(`Failed to query triggers for container ${collectionId}: ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadTriggers", error.code);
|
||||
sendNotificationForError(error);
|
||||
}
|
||||
clearMessage();
|
||||
return triggers;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
|
||||
export async function readUserDefinedFunctions(
|
||||
databaseId: string,
|
||||
collectionId: string
|
||||
): Promise<(UserDefinedFunctionDefinition & Resource)[]> {
|
||||
let udfs: (UserDefinedFunctionDefinition & Resource)[];
|
||||
const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`);
|
||||
try {
|
||||
const response = await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.userDefinedFunctions.readAll()
|
||||
.fetchAll();
|
||||
udfs = response.resources;
|
||||
} catch (error) {
|
||||
logConsoleError(`Failed to query user defined functions for container ${collectionId}: ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadUserDefinedFunctions", error.code);
|
||||
sendNotificationForError(error);
|
||||
}
|
||||
clearMessage();
|
||||
return udfs;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
|
||||
export async function updateStoredProcedure(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
storedProcedure: StoredProcedureDefinition
|
||||
): Promise<StoredProcedureDefinition & Resource> {
|
||||
let updatedStoredProcedure: StoredProcedureDefinition & Resource;
|
||||
const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`);
|
||||
try {
|
||||
const response = await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.storedProcedure(storedProcedure.id)
|
||||
.replace(storedProcedure);
|
||||
updatedStoredProcedure = response.resource;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while updating stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "UpdateStoredProcedure", error.code);
|
||||
sendNotificationForError(error);
|
||||
}
|
||||
|
||||
clearMessage();
|
||||
return updatedStoredProcedure;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { TriggerDefinition } from "@azure/cosmos";
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
|
||||
export async function updateTrigger(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
trigger: TriggerDefinition
|
||||
): Promise<TriggerDefinition> {
|
||||
let updatedTrigger: TriggerDefinition;
|
||||
const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`);
|
||||
try {
|
||||
const response = await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.trigger(trigger.id)
|
||||
.replace(trigger);
|
||||
updatedTrigger = response.resource;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while updating trigger ${trigger.id}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "UpdateTrigger", error.code);
|
||||
sendNotificationForError(error);
|
||||
}
|
||||
|
||||
clearMessage();
|
||||
return updatedTrigger;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
|
||||
export async function updateUserDefinedFunction(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
userDefinedFunction: UserDefinedFunctionDefinition
|
||||
): Promise<UserDefinedFunctionDefinition & Resource> {
|
||||
let updatedUserDefinedFunction: UserDefinedFunctionDefinition & Resource;
|
||||
const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`);
|
||||
try {
|
||||
const response = await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.userDefinedFunction(userDefinedFunction.id)
|
||||
.replace(userDefinedFunction);
|
||||
updatedUserDefinedFunction = response.resource;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while updating user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "UpdateUserupdateUserDefinedFunction", error.code);
|
||||
sendNotificationForError(error);
|
||||
}
|
||||
|
||||
clearMessage();
|
||||
return updatedUserDefinedFunction;
|
||||
}
|
||||
@@ -6,7 +6,7 @@ export enum Platform {
|
||||
|
||||
interface ConfigContext {
|
||||
platform: Platform;
|
||||
allowedParentFrameOrigins: string[];
|
||||
allowedParentFrameOrigins: RegExp;
|
||||
gitSha?: string;
|
||||
proxyPath?: string;
|
||||
AAD_ENDPOINT: string;
|
||||
@@ -30,12 +30,7 @@ interface ConfigContext {
|
||||
// Default configuration
|
||||
let configContext: Readonly<ConfigContext> = {
|
||||
platform: Platform.Portal,
|
||||
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$`
|
||||
],
|
||||
allowedParentFrameOrigins: /^https:\/\/portal\.azure\.com$|^https:\/\/portal\.azure\.us$|^https:\/\/portal\.azure\.cn$|^https:\/\/portal\.microsoftazure\.de$|^https:\/\/.+\.portal\.azure\.com$|^https:\/\/.+\.portal\.azure\.us$|^https:\/\/.+\.portal\.azure\.cn$|^https:\/\/.+\.portal\.microsoftazure\.de$|^https:\/\/main\.documentdb\.ext\.azure\.com$|^https:\/\/main\.documentdb\.ext\.microsoftazure\.de$|^https:\/\/main\.documentdb\.ext\.azure\.cn$|^https:\/\/main\.documentdb\.ext\.azure\.us$/,
|
||||
// Webpack injects this at build time
|
||||
gitSha: process.env.GIT_SHA,
|
||||
hostedExplorerURL: "https://cosmos.azure.com/",
|
||||
@@ -78,13 +73,8 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
|
||||
const response = await fetch("./config.json");
|
||||
if (response.status === 200) {
|
||||
try {
|
||||
const { allowedParentFrameOrigins, ...externalConfig } = await response.json();
|
||||
const externalConfig = await response.json();
|
||||
Object.assign(configContext, externalConfig);
|
||||
if (allowedParentFrameOrigins && allowedParentFrameOrigins.length > 0) {
|
||||
updateConfigContext({
|
||||
allowedParentFrameOrigins: [...configContext.allowedParentFrameOrigins, ...allowedParentFrameOrigins]
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Unable to parse json in config file");
|
||||
console.error(error);
|
||||
|
||||
@@ -88,6 +88,10 @@ export interface Resource {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface ResourceRequest {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface Collection extends Resource {
|
||||
defaultTtl?: number;
|
||||
indexingPolicy?: IndexingPolicy;
|
||||
@@ -100,12 +104,39 @@ export interface Collection extends Resource {
|
||||
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 {
|
||||
collections?: Collection[];
|
||||
}
|
||||
|
||||
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 {
|
||||
resourceId?: string;
|
||||
resourceType?: string;
|
||||
@@ -228,6 +259,28 @@ export interface ErrorDataModel {
|
||||
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 {
|
||||
databaseId: string;
|
||||
collectionId: string;
|
||||
@@ -254,6 +307,11 @@ export enum AutopilotTier {
|
||||
Tier4 = 4
|
||||
}
|
||||
|
||||
export interface RpOptions {
|
||||
// tier is sent as string, autoscale as object (AutoPilotCreationSettings)
|
||||
[key: string]: string | AutoPilotCreationSettings;
|
||||
}
|
||||
|
||||
export interface Query {
|
||||
id: string;
|
||||
resourceId: string;
|
||||
@@ -276,17 +334,10 @@ export interface CreateDatabaseParams {
|
||||
offerThroughput?: number;
|
||||
}
|
||||
|
||||
export interface CreateCollectionParams {
|
||||
createNewDatabase: boolean;
|
||||
collectionId: string;
|
||||
databaseId: string;
|
||||
databaseLevelThroughput: boolean;
|
||||
offerThroughput: number;
|
||||
analyticalStorageTtl?: number;
|
||||
autoPilotMaxThroughput?: number;
|
||||
indexingPolicy?: IndexingPolicy;
|
||||
partitionKey?: PartitionKey;
|
||||
uniqueKeyPolicy?: UniqueKeyPolicy;
|
||||
export interface SharedThroughputRange {
|
||||
minimumRU: number;
|
||||
maximumRU: number;
|
||||
defaultRU: number;
|
||||
}
|
||||
|
||||
export interface Notification {
|
||||
@@ -431,6 +482,25 @@ export interface NotebookConfigurationEndpointInfo {
|
||||
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 {
|
||||
userName: string;
|
||||
password: string;
|
||||
@@ -472,10 +542,79 @@ export interface MongoParameters extends RpParameters {
|
||||
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 {
|
||||
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 {
|
||||
properties: {
|
||||
kind: "Jar";
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
import {
|
||||
QueryMetrics,
|
||||
Resource,
|
||||
StoredProcedureDefinition,
|
||||
TriggerDefinition,
|
||||
UserDefinedFunctionDefinition
|
||||
} from "@azure/cosmos";
|
||||
import * as DataModels from "./DataModels";
|
||||
import Q from "q";
|
||||
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
|
||||
import Explorer from "../Explorer/Explorer";
|
||||
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import { CassandraTableKey, CassandraTableKeys } from "../Explorer/Tables/TableDataClient";
|
||||
import ConflictId from "../Explorer/Tree/ConflictId";
|
||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||
import { 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 UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
|
||||
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
||||
import Trigger from "../Explorer/Tree/Trigger";
|
||||
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
|
||||
import { UploadDetails } from "../workers/upload/definitions";
|
||||
import * as DataModels from "./DataModels";
|
||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||
import ConflictId from "../Explorer/Tree/ConflictId";
|
||||
|
||||
export interface TokenProvider {
|
||||
getAuthHeader(): Promise<Headers>;
|
||||
@@ -159,13 +153,13 @@ export interface Collection extends CollectionBase {
|
||||
collapseUserDefinedFunctions(): void;
|
||||
collapseTriggers(): void;
|
||||
|
||||
loadUserDefinedFunctions(): Promise<any>;
|
||||
loadStoredProcedures(): Promise<any>;
|
||||
loadTriggers(): Promise<any>;
|
||||
loadUserDefinedFunctions(): Q.Promise<any>;
|
||||
loadStoredProcedures(): Q.Promise<any>;
|
||||
loadTriggers(): Q.Promise<any>;
|
||||
|
||||
createStoredProcedureNode(data: StoredProcedureDefinition & Resource): StoredProcedure;
|
||||
createUserDefinedFunctionNode(data: UserDefinedFunctionDefinition & Resource): UserDefinedFunction;
|
||||
createTriggerNode(data: TriggerDefinition & Resource): Trigger;
|
||||
createStoredProcedureNode(data: DataModels.StoredProcedure): StoredProcedure;
|
||||
createUserDefinedFunctionNode(data: DataModels.UserDefinedFunction): UserDefinedFunction;
|
||||
createTriggerNode(data: DataModels.Trigger): Trigger;
|
||||
findStoredProcedureWithId(sprocRid: string): StoredProcedure;
|
||||
findTriggerWithId(triggerRid: string): Trigger;
|
||||
findUserDefinedFunctionWithId(udfRid: string): UserDefinedFunction;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { StringUtils } from "../../../Utils/StringUtils";
|
||||
import { KeyCodes } from "../../../Common/Constants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
|
||||
|
||||
|
||||
@@ -265,9 +265,6 @@ exports[`test render renders with filters 1`] = `
|
||||
"buttonTextDisabled": "#a19f9d",
|
||||
"buttonTextHovered": "#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",
|
||||
"disabledBackground": "#f3f2f1",
|
||||
"disabledBodySubtext": "#c8c6c4",
|
||||
@@ -607,9 +604,6 @@ exports[`test render renders with filters 1`] = `
|
||||
"buttonTextDisabled": "#a19f9d",
|
||||
"buttonTextHovered": "#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",
|
||||
"disabledBackground": "#f3f2f1",
|
||||
"disabledBodySubtext": "#c8c6c4",
|
||||
@@ -1003,9 +997,6 @@ exports[`test render renders with filters 1`] = `
|
||||
"buttonTextDisabled": "#a19f9d",
|
||||
"buttonTextHovered": "#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",
|
||||
"disabledBackground": "#f3f2f1",
|
||||
"disabledBodySubtext": "#c8c6c4",
|
||||
@@ -1122,11 +1113,6 @@ exports[`test render renders with filters 1`] = `
|
||||
},
|
||||
"iconDisabled": Object {
|
||||
"color": "#a19f9d",
|
||||
"selectors": Object {
|
||||
"@media screen and (-ms-high-contrast: active)": Object {
|
||||
"color": "GrayText",
|
||||
},
|
||||
},
|
||||
},
|
||||
"label": Array [
|
||||
Object {
|
||||
@@ -1148,11 +1134,6 @@ exports[`test render renders with filters 1`] = `
|
||||
},
|
||||
"menuIconDisabled": Object {
|
||||
"color": "#a19f9d",
|
||||
"selectors": Object {
|
||||
"@media screen and (-ms-high-contrast: active)": Object {
|
||||
"color": "GrayText",
|
||||
},
|
||||
},
|
||||
},
|
||||
"root": Array [
|
||||
Object {
|
||||
@@ -1169,6 +1150,7 @@ exports[`test render renders with filters 1`] = `
|
||||
"right": 2,
|
||||
"selectors": Object {
|
||||
"@media screen and (-ms-high-contrast: active)": Object {
|
||||
"border": "none",
|
||||
"bottom": -2,
|
||||
"left": -2,
|
||||
"outlineColor": "ButtonText",
|
||||
@@ -1248,6 +1230,7 @@ exports[`test render renders with filters 1`] = `
|
||||
"right": 2,
|
||||
"selectors": Object {
|
||||
"@media screen and (-ms-high-contrast: active)": Object {
|
||||
"border": "none",
|
||||
"bottom": -2,
|
||||
"left": -2,
|
||||
"outlineColor": "ButtonText",
|
||||
@@ -1276,6 +1259,10 @@ exports[`test render renders with filters 1`] = `
|
||||
":hover": Object {
|
||||
"outline": 0,
|
||||
},
|
||||
"@media screen and (-ms-high-contrast: active)": Object {
|
||||
"borderColor": "grayText",
|
||||
"color": "grayText",
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
@@ -1375,21 +1362,13 @@ exports[`test render renders with filters 1`] = `
|
||||
"selectors": Object {
|
||||
"@media screen and (-ms-high-contrast: active)": Object {
|
||||
"MsHighContrastAdjust": "none",
|
||||
"backgroundColor": "Window",
|
||||
"border": "1px solid WindowText",
|
||||
"borderRightWidth": "0",
|
||||
"color": "WindowText",
|
||||
"backgroundColor": "WindowText",
|
||||
"color": "Window",
|
||||
},
|
||||
},
|
||||
},
|
||||
".ms-Button--primary + .ms-Button": Object {
|
||||
"border": "none",
|
||||
"selectors": Object {
|
||||
"@media screen and (-ms-high-contrast: active)": Object {
|
||||
"border": "1px solid WindowText",
|
||||
"borderLeftWidth": "0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1429,9 +1408,6 @@ exports[`test render renders with filters 1`] = `
|
||||
"borderColor": "GrayText",
|
||||
"color": "GrayText",
|
||||
},
|
||||
"@media screen and (forced-colors: active)": Object {
|
||||
"forcedColorAdjust": "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
"splitButtonContainerFocused": Object {
|
||||
@@ -1578,13 +1554,6 @@ exports[`test render renders with filters 1`] = `
|
||||
},
|
||||
},
|
||||
},
|
||||
".ms-Button-menuIcon": Object {
|
||||
"selectors": Object {
|
||||
"@media screen and (-ms-high-contrast: active)": Object {
|
||||
"color": "GrayText",
|
||||
},
|
||||
},
|
||||
},
|
||||
":hover": Object {
|
||||
"cursor": "default",
|
||||
},
|
||||
@@ -1806,9 +1775,6 @@ exports[`test render renders with filters 1`] = `
|
||||
"buttonTextDisabled": "#a19f9d",
|
||||
"buttonTextHovered": "#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",
|
||||
"disabledBackground": "#f3f2f1",
|
||||
"disabledBodySubtext": "#c8c6c4",
|
||||
|
||||
@@ -86,7 +86,6 @@ export class DynamicListViewModel extends WaitsForTemplateViewModel {
|
||||
public onRemoveItemKeyPress = (data: any, event: KeyboardEvent, source: any): boolean => {
|
||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
||||
this.removeItem(data, event);
|
||||
(document.querySelector(".dynamicListItem:last-of-type input") as HTMLElement).focus();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
@@ -95,7 +94,7 @@ export class DynamicListViewModel extends WaitsForTemplateViewModel {
|
||||
|
||||
public addItem(): void {
|
||||
this.listItems.push({ value: ko.observable("") });
|
||||
(document.querySelector(".dynamicListItem:last-of-type input") as HTMLElement).focus();
|
||||
document.getElementById("uniqueKeyItems").focus();
|
||||
}
|
||||
|
||||
public onAddItemKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { RepoListItem } from "./GitHubReposComponent";
|
||||
import { ChildrenMargin } from "./GitHubStyleConstants";
|
||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||
import { IGitHubRepo } from "../../../GitHub/GitHubClient";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import UrlUtility from "../../../Common/UrlUtility";
|
||||
import Explorer from "../../Explorer";
|
||||
|
||||
|
||||
@@ -66,9 +66,7 @@ export class GitHubReposComponent extends React.Component<GitHubReposComponentPr
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={"firstdivbg headerline"} role="heading" aria-level={2}>
|
||||
{header}
|
||||
</div>
|
||||
<div className={"firstdivbg headerline"}>{header}</div>
|
||||
<div className={"paneMainContent"}>{content}</div>
|
||||
{!this.props.showAuthorizeAccess && (
|
||||
<>
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
} from "office-ui-fabric-react/lib/utilities/selection/index";
|
||||
import { StyleConstants } from "../../../Common/Constants";
|
||||
import { TextField, ITextFieldProps, ITextField } from "office-ui-fabric-react/lib/TextField";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
|
||||
import SaveQueryBannerIcon from "../../../../images/save_query_banner.png";
|
||||
import { QueriesClient } from "../../../Common/QueriesClient";
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
step: step,
|
||||
'class':'migration collid select-font-size',
|
||||
min: minAutoPilotThroughput,
|
||||
'aria-label': 'Max request units per second',
|
||||
'aria-label': ariaLabel,
|
||||
type: isAutoscaleThroughputInputFieldRequired() ? 'number' : 'hidden',
|
||||
css: {
|
||||
dirty: maxAutoPilotThroughputSet.editableIsDirty
|
||||
|
||||
@@ -282,7 +282,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
||||
<img
|
||||
className="expandCollapseIcon"
|
||||
src={this.state.isExpanded ? TriangleDownIcon : TriangleRightIcon}
|
||||
alt={this.state.isExpanded ? `${node.label} branch is expanded` : `${node.label} branch is collapsed`}
|
||||
alt={this.state.isExpanded ? "Branch is expanded" : "Branch is collapsed"}
|
||||
onKeyPress={(event: React.KeyboardEvent<HTMLDivElement>) => this.onCollapseExpandIconKeyPress(event, node)}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
|
||||
@@ -49,7 +49,7 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
|
||||
tabIndex={-1}
|
||||
>
|
||||
<img
|
||||
alt="label branch is collapsed"
|
||||
alt="Branch is collapsed"
|
||||
className="expandCollapseIcon"
|
||||
onKeyPress={[Function]}
|
||||
role="button"
|
||||
@@ -140,7 +140,7 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
|
||||
tabIndex={-1}
|
||||
>
|
||||
<img
|
||||
alt="label branch is expanded"
|
||||
alt="Branch is expanded"
|
||||
className="expandCollapseIcon"
|
||||
onKeyPress={[Function]}
|
||||
role="button"
|
||||
@@ -278,7 +278,7 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
|
||||
tabIndex={-1}
|
||||
>
|
||||
<img
|
||||
alt="label branch is expanded"
|
||||
alt="Branch is expanded"
|
||||
className="expandCollapseIcon"
|
||||
onKeyPress={[Function]}
|
||||
role="button"
|
||||
@@ -436,7 +436,7 @@ exports[`TreeNodeComponent renders unsorted children by default 1`] = `
|
||||
tabIndex={-1}
|
||||
>
|
||||
<img
|
||||
alt="label branch is expanded"
|
||||
alt="Branch is expanded"
|
||||
className="expandCollapseIcon"
|
||||
onKeyPress={[Function]}
|
||||
role="button"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
jest.mock("../../Common/DocumentClientUtilityBase");
|
||||
jest.mock("../../Common/dataAccess/createCollection");
|
||||
import * as ko from "knockout";
|
||||
import * as sinon from "sinon";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
@@ -34,8 +33,8 @@ describe("ContainerSampleGenerator", () => {
|
||||
databaseId: sampleDatabaseId,
|
||||
offerThroughput: 400,
|
||||
databaseLevelThroughput: false,
|
||||
createNewDatabase: true,
|
||||
collectionId: sampleCollectionId,
|
||||
rupmEnabled: false,
|
||||
data: [
|
||||
{
|
||||
firstname: "Eva",
|
||||
@@ -100,8 +99,8 @@ describe("ContainerSampleGenerator", () => {
|
||||
databaseId: sampleDatabaseId,
|
||||
offerThroughput: 400,
|
||||
databaseLevelThroughput: false,
|
||||
createNewDatabase: true,
|
||||
collectionId: sampleCollectionId,
|
||||
rupmEnabled: false,
|
||||
data: [
|
||||
"g.addV('person').property(id, '1').property('_partitionKey','pk').property('name', 'Eva').property('age', 44)"
|
||||
]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import GraphTab from ".././Tabs/GraphTab";
|
||||
@@ -5,11 +6,10 @@ import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsol
|
||||
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
|
||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||
import Explorer from "../Explorer";
|
||||
import { createDocument } from "../../Common/DocumentClientUtilityBase";
|
||||
import { createCollection } from "../../Common/dataAccess/createCollection";
|
||||
import { createDocument, getOrCreateDatabaseAndCollection } from "../../Common/DocumentClientUtilityBase";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
interface SampleDataFile extends DataModels.CreateCollectionParams {
|
||||
interface SampleDataFile extends DataModels.CreateDatabaseAndCollectionRequest {
|
||||
data: any[];
|
||||
}
|
||||
|
||||
@@ -54,11 +54,18 @@ export class ContainerSampleGenerator {
|
||||
}
|
||||
|
||||
private async createContainerAsync(): Promise<ViewModels.Collection> {
|
||||
const createRequest: DataModels.CreateCollectionParams = {
|
||||
const createRequest: DataModels.CreateDatabaseAndCollectionRequest = {
|
||||
...this.sampleDataFile
|
||||
};
|
||||
|
||||
await createCollection(createRequest);
|
||||
const options: any = {};
|
||||
if (this.container.isPreferredApiMongoDB()) {
|
||||
options.initialHeaders = options.initialHeaders || {};
|
||||
options.initialHeaders[Constants.HttpHeaders.supportSpatialLegacyCoordinates] = true;
|
||||
options.initialHeaders[Constants.HttpHeaders.usePolygonsSmallerThanAHemisphere] = true;
|
||||
}
|
||||
|
||||
await getOrCreateDatabaseAndCollection(createRequest, options);
|
||||
await this.container.refreshAllDatabases();
|
||||
const database = this.container.findDatabaseWithId(this.sampleDataFile.databaseId);
|
||||
if (!database) {
|
||||
|
||||
@@ -26,7 +26,7 @@ import NewVertexPane from "./Panes/NewVertexPane";
|
||||
import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab";
|
||||
import Q from "q";
|
||||
import ResourceTokenCollection from "./Tree/ResourceTokenCollection";
|
||||
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
||||
import TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
||||
import TerminalTab from "./Tabs/TerminalTab";
|
||||
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
||||
import { ActionContracts, MessageTypes } from "../Contracts/ExplorerContracts";
|
||||
@@ -87,7 +87,6 @@ import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
|
||||
import TabsBase from "./Tabs/TabsBase";
|
||||
import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandButtonComponent";
|
||||
import { updateUserContext, userContext } from "../UserContext";
|
||||
import { stringToBlob } from "../Utils/BlobUtils";
|
||||
|
||||
BindingHandlersRegisterer.registerBindingHandlers();
|
||||
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
|
||||
@@ -1894,9 +1893,6 @@ export default class Explorer {
|
||||
}
|
||||
|
||||
public findSelectedDatabase(): ViewModels.Database {
|
||||
if (!this.selectedNode()) {
|
||||
return null;
|
||||
}
|
||||
if (this.selectedNode().nodeKind === "Database") {
|
||||
return _.find(this.databases(), (database: ViewModels.Database) => database.rid === this.selectedNode().rid);
|
||||
}
|
||||
@@ -2622,11 +2618,9 @@ export default class Explorer {
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
const clearMessage = NotificationConsoleUtils.logConsoleProgress(`Downloading ${notebookFile.path}`);
|
||||
|
||||
return this.notebookManager?.notebookContentClient.readFileContent(notebookFile.path).then(
|
||||
(content: string) => {
|
||||
const blob = stringToBlob(content, "text/plain");
|
||||
const blob = new Blob([content], { type: "octet/stream" });
|
||||
if (navigator.msSaveBlob) {
|
||||
// for IE and Edge
|
||||
navigator.msSaveBlob(blob, notebookFile.name);
|
||||
@@ -2643,16 +2637,12 @@ export default class Explorer {
|
||||
downloadLink.click();
|
||||
downloadLink.remove();
|
||||
}
|
||||
|
||||
clearMessage();
|
||||
},
|
||||
(error: any) => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Could not download notebook ${JSON.stringify(error)}`
|
||||
);
|
||||
|
||||
clearMessage();
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -3127,6 +3117,12 @@ export default class Explorer {
|
||||
} else {
|
||||
loadingTitle.innerHTML = title;
|
||||
}
|
||||
|
||||
TelemetryProcessor.trace(
|
||||
Action.LoadingStatus,
|
||||
ActionModifiers.Mark,
|
||||
title !== "Welcome to Azure Cosmos DB" ? `Title: ${title}, Text: ${text}` : text
|
||||
);
|
||||
}
|
||||
|
||||
private _openSetupNotebooksPaneForQuickstart(): void {
|
||||
|
||||
@@ -23,7 +23,7 @@ import { GraphConfig } from "../../Tabs/GraphTab";
|
||||
import { EditorReact } from "../../Controls/Editor/EditorReact";
|
||||
import LoadGraphIcon from "../../../../images/LoadGraph.png";
|
||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import { InputProperty } from "../../../Contracts/ViewModels";
|
||||
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { PlatformType } from "../../../PlatformType";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import { Areas } from "../../../Common/Constants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
|
||||
import AddDatabaseIcon from "../../../../images/AddDatabase.svg";
|
||||
import AddCollectionIcon from "../../../../images/AddCollection.svg";
|
||||
|
||||
@@ -130,14 +130,11 @@ export class NotificationConsoleComponent extends React.Component<
|
||||
<span className="headerStatusEllipsis">{this.state.headerStatus}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="expandCollapseButton"
|
||||
role="button"
|
||||
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 className="expandCollapseButton" role="button" tabIndex={0}>
|
||||
<img
|
||||
src={this.state.isExpanded ? ChevronDownIcon : ChevronUpIcon}
|
||||
alt={this.state.isExpanded ? "collapse console" : "expand console"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<AnimateHeight
|
||||
|
||||
@@ -68,14 +68,12 @@ exports[`NotificationConsoleComponent renders the console (expanded) 1`] = `
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
aria-expanded={true}
|
||||
aria-label="collapse console"
|
||||
className="expandCollapseButton"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
alt="collapse console"
|
||||
src=""
|
||||
/>
|
||||
</div>
|
||||
|
||||
464
src/Explorer/Notebook/MonacoEditor/MonacoEditor.tsx
Normal file
464
src/Explorer/Notebook/MonacoEditor/MonacoEditor.tsx
Normal file
@@ -0,0 +1,464 @@
|
||||
import { Channels } from "@nteract/messaging";
|
||||
import * as monaco from "./monaco";
|
||||
import * as React from "react";
|
||||
import { completionProvider } from "./completions/completionItemProvider";
|
||||
import { AppState, ContentRef } from "@nteract/core";
|
||||
import { connect } from "react-redux";
|
||||
import "./styles.css";
|
||||
import { LightThemeName, HCLightThemeName, DarkThemeName } from "./theme";
|
||||
// import { logger } from "src/common/localLogger";
|
||||
import { getCellMonacoLanguage } from "./selectors";
|
||||
// import { DocumentUri } from "./documentUri";
|
||||
|
||||
export type IModelContentChangedEvent = monaco.editor.IModelContentChangedEvent;
|
||||
|
||||
/**
|
||||
* Initial props for Monaco received from agnostic component
|
||||
*/
|
||||
export interface IMonacoProps {
|
||||
id: string;
|
||||
contentRef: ContentRef;
|
||||
modelUri?: monaco.Uri;
|
||||
theme: monaco.editor.IStandaloneThemeData | monaco.editor.BuiltinTheme | string;
|
||||
cellLanguageOverride?: string;
|
||||
notebookLanguageOverride?: string;
|
||||
readOnly?: boolean;
|
||||
channels: Channels | undefined;
|
||||
enableCompletion: boolean;
|
||||
shouldRegisterDefaultCompletion?: boolean;
|
||||
onChange: (value: string, event?: unknown) => void;
|
||||
onFocusChange: (focus: boolean) => void;
|
||||
onCursorPositionChange?: (selection: monaco.ISelection | null) => void;
|
||||
onRegisterCompletionProvider?: (languageId: string) => void;
|
||||
value: string;
|
||||
editorFocused: boolean;
|
||||
lineNumbers: boolean;
|
||||
|
||||
/** set height of editor to fit the specified number of lines in display */
|
||||
numberOfLines?: number;
|
||||
|
||||
options?: monaco.editor.IEditorOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Monaco specific props derived from State
|
||||
*/
|
||||
interface IMonacoStateProps {
|
||||
language: string;
|
||||
}
|
||||
|
||||
// Cache the custom theme data to avoid repeatly defining the custom theme
|
||||
let customThemeData: monaco.editor.IStandaloneThemeData;
|
||||
|
||||
function getMonacoTheme(theme: monaco.editor.IStandaloneThemeData | monaco.editor.BuiltinTheme | string) {
|
||||
if (typeof theme === "string") {
|
||||
switch (theme) {
|
||||
case "vs-dark":
|
||||
return DarkThemeName;
|
||||
case "hc-black":
|
||||
return "hc-black";
|
||||
case "vs":
|
||||
return LightThemeName;
|
||||
case "hc-light":
|
||||
return HCLightThemeName;
|
||||
default:
|
||||
return LightThemeName;
|
||||
}
|
||||
} else if (theme === undefined || typeof theme === "undefined") {
|
||||
return LightThemeName;
|
||||
} else {
|
||||
const themeName = "custom-vs";
|
||||
|
||||
// Skip redefining the same custom theme if it is the same theme data.
|
||||
if (customThemeData !== theme) {
|
||||
monaco.editor.defineTheme(themeName, theme);
|
||||
customThemeData = theme;
|
||||
}
|
||||
|
||||
return themeName;
|
||||
}
|
||||
}
|
||||
|
||||
const makeMapStateToProps = (initialState: AppState, initialProps: IMonacoProps) => {
|
||||
const { id, contentRef } = initialProps;
|
||||
const mapStateToProps = (state: AppState, ownProps: IMonacoProps & IMonacoStateProps) => {
|
||||
return {
|
||||
language: getCellMonacoLanguage(
|
||||
state,
|
||||
contentRef,
|
||||
id,
|
||||
ownProps.cellLanguageOverride,
|
||||
ownProps.notebookLanguageOverride
|
||||
)
|
||||
};
|
||||
};
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a MonacoEditor instance within the MonacoContainer div
|
||||
*/
|
||||
export class MonacoEditor extends React.Component<IMonacoProps & IMonacoStateProps> {
|
||||
editor?: monaco.editor.IStandaloneCodeEditor;
|
||||
editorContainerRef = React.createRef<HTMLDivElement>();
|
||||
contentHeight?: number;
|
||||
private cursorPositionListener?: monaco.IDisposable;
|
||||
|
||||
constructor(props: IMonacoProps & IMonacoStateProps) {
|
||||
super(props);
|
||||
this.onFocus = this.onFocus.bind(this);
|
||||
this.onBlur = this.onBlur.bind(this);
|
||||
this.calculateHeight = this.calculateHeight.bind(this);
|
||||
}
|
||||
|
||||
onDidChangeModelContent(e: monaco.editor.IModelContentChangedEvent): void {
|
||||
if (this.editor) {
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(this.editor.getValue(), e);
|
||||
}
|
||||
|
||||
this.calculateHeight();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the height of editor
|
||||
*
|
||||
* @remarks
|
||||
* The way to determine how many lines we should display in editor:
|
||||
* If numberOfLines is not set or set to 0, we adjust the height to fit the content
|
||||
* If numberOfLines is specified we respect that setting
|
||||
*/
|
||||
calculateHeight(): void {
|
||||
// Make sure we have an editor
|
||||
if (!this.editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we have a model
|
||||
const model = this.editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.editorContainerRef && this.editorContainerRef.current) {
|
||||
const expectedLines = this.props.numberOfLines || model.getLineCount();
|
||||
// The find & replace menu takes up 2 lines, that is why 2 line is set as the minimum number of lines
|
||||
// TODO: we should either disable the find/replace menu or auto expand the editor when find/replace is triggerred.
|
||||
const finalizedLines = Math.max(expectedLines, 1) + 1;
|
||||
const lineHeight = this.editor.getConfiguration().lineHeight;
|
||||
|
||||
const contentHeight = finalizedLines * lineHeight;
|
||||
if (this.contentHeight !== contentHeight) {
|
||||
this.editorContainerRef.current.style.height = contentHeight + "px";
|
||||
this.editor.layout();
|
||||
this.contentHeight = contentHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
if (this.editorContainerRef && this.editorContainerRef.current) {
|
||||
// Register Jupyter completion provider if needed
|
||||
this.registerCompletionProvider();
|
||||
|
||||
// Use Monaco model uri if provided. Otherwise, create a new model uri using editor id.
|
||||
const uri = this.props.modelUri ? this.props.modelUri : monaco.Uri.file(this.props.id);
|
||||
|
||||
// Only create a new model if it does not exist. For example, when we double click on a markdown cell,
|
||||
// an editor model is created for it. Once we go back to markdown preview mode that doesn't use the editor,
|
||||
// double clicking on the markdown cell will again instantiate a monaco editor. In that case, we should
|
||||
// rebind the previously created editor model for the markdown instead of recreating one. Monaco does not
|
||||
// allow models to be recreated with the same uri.
|
||||
let model = monaco.editor.getModel(uri);
|
||||
if (!model) {
|
||||
model = monaco.editor.createModel(this.props.value, this.props.language, uri);
|
||||
}
|
||||
|
||||
// Create Monaco editor backed by a Monaco model.
|
||||
this.editor = monaco.editor.create(this.editorContainerRef.current, {
|
||||
// Following are the default settings
|
||||
minimap: {
|
||||
enabled: false
|
||||
},
|
||||
autoIndent: true,
|
||||
overviewRulerLanes: 1,
|
||||
scrollbar: {
|
||||
useShadows: false,
|
||||
verticalHasArrows: false,
|
||||
horizontalHasArrows: false,
|
||||
vertical: "hidden",
|
||||
horizontal: "hidden",
|
||||
verticalScrollbarSize: 0,
|
||||
horizontalScrollbarSize: 0,
|
||||
arrowSize: 30
|
||||
},
|
||||
scrollBeyondLastLine: false,
|
||||
find: {
|
||||
// TODO Need this?
|
||||
// addExtraSpaceOnTop: false, // pops the editor out of alignment if turned on
|
||||
seedSearchStringFromSelection: true, // default is true
|
||||
autoFindInSelection: false // default is false
|
||||
},
|
||||
// Disable highlight current line, too much visual noise with it on.
|
||||
// VS Code also has it disabled for their notebook experience.
|
||||
renderLineHighlight: "none",
|
||||
|
||||
// Allow editor pop up widgets such as context menus, signature help, hover tips to be able to be
|
||||
// displayed outside of the editor. Without this, the pop up widgets can be clipped.
|
||||
fixedOverflowWidgets: true,
|
||||
|
||||
// Apply custom settings from configuration
|
||||
...this.props.options,
|
||||
|
||||
// Apply specific settings passed-in as direct props
|
||||
model,
|
||||
value: this.props.value,
|
||||
language: this.props.language,
|
||||
readOnly: this.props.readOnly,
|
||||
lineNumbers: this.props.lineNumbers ? "on" : "off",
|
||||
theme: getMonacoTheme(this.props.theme)
|
||||
});
|
||||
|
||||
this.addEditorTopMargin();
|
||||
|
||||
// Ignore Ctrl + Enter
|
||||
// tslint:disable-next-line no-bitwise
|
||||
this.editor.addCommand(
|
||||
monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter,
|
||||
() => {
|
||||
// Do nothing. This is handled elsewhere, we just don't want the editor to put the newline.
|
||||
},
|
||||
undefined
|
||||
);
|
||||
// TODO Add right context
|
||||
|
||||
this.toggleEditorOptions(this.props.editorFocused);
|
||||
|
||||
if (this.props.editorFocused) {
|
||||
if (!this.editor.hasTextFocus()) {
|
||||
// Bring browser focus to the editor if text not already in focus
|
||||
this.editor.focus();
|
||||
}
|
||||
this.registerCursorListener();
|
||||
}
|
||||
|
||||
// TODO: Need to remove the event listener when the editor is disposed, or we have a memory leak here.
|
||||
// The same applies to the other event listeners below
|
||||
// Adds listener under the resize window event which calls the resize method
|
||||
window.addEventListener("resize", this.resize.bind(this));
|
||||
|
||||
// Adds listeners for undo and redo actions emitted from the toolbar
|
||||
this.editorContainerRef.current.addEventListener("undo", () => {
|
||||
if (this.editor) {
|
||||
this.editor.trigger("undo-event", "undo", {});
|
||||
}
|
||||
});
|
||||
this.editorContainerRef.current.addEventListener("redo", () => {
|
||||
if (this.editor) {
|
||||
this.editor.trigger("redo-event", "redo", {});
|
||||
}
|
||||
});
|
||||
|
||||
this.editor.onDidChangeModelContent(this.onDidChangeModelContent.bind(this));
|
||||
this.editor.onDidFocusEditorText(this.onFocus);
|
||||
this.editor.onDidBlurEditorText(this.onBlur);
|
||||
this.calculateHeight();
|
||||
|
||||
// FIXME: This might need further investigation as the props value should be respected in construction
|
||||
// The following is a mitigation measure till that time
|
||||
// Ensures that the source contents of the editor (value) is consistent with the state of the editor
|
||||
this.editor.setValue(this.props.value);
|
||||
}
|
||||
}
|
||||
|
||||
addEditorTopMargin(): void {
|
||||
if (this.editor) {
|
||||
// Monaco editor doesn't have margins
|
||||
// https://github.com/notable/notable/issues/551
|
||||
// This is a workaround to add an editor area 12px padding at the top
|
||||
// so that cursors rendered by collab decorators could be visible without being cut.
|
||||
this.editor.changeViewZones(changeAccessor => {
|
||||
const domNode = document.createElement("div");
|
||||
changeAccessor.addZone({
|
||||
afterLineNumber: 0,
|
||||
heightInPx: 12,
|
||||
domNode
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells editor to check the surrounding container size and resize itself appropriately
|
||||
*/
|
||||
resize(): void {
|
||||
if (this.editor && this.props.editorFocused) {
|
||||
this.editor.layout();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(): void {
|
||||
if (!this.editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { value, channels, /* language, contentRef, id,*/ editorFocused, theme } = this.props;
|
||||
|
||||
// Ensures that the source contents of the editor (value) is consistent with the state of the editor
|
||||
if (this.editor.getValue() !== value) {
|
||||
this.editor.setValue(value);
|
||||
}
|
||||
|
||||
completionProvider.setChannels(channels);
|
||||
|
||||
// Register Jupyter completion provider if needed
|
||||
this.registerCompletionProvider();
|
||||
|
||||
/*
|
||||
// Apply new model to the editor when the language is changed.
|
||||
const model = this.editor.getModel();
|
||||
if (model && language && model.getModeId() !== language) {
|
||||
const newUri = DocumentUri.createCellUri(contentRef, id, language);
|
||||
if (!monaco.editor.getModel(newUri)) {
|
||||
// Save the cursor position before we set new model.
|
||||
const position = this.editor.getPosition();
|
||||
|
||||
// Set new model targeting the changed language.
|
||||
this.editor.setModel(monaco.editor.createModel(value, language, newUri));
|
||||
this.addEditorTopMargin();
|
||||
|
||||
// Restore cursor position to new model.
|
||||
if (position) {
|
||||
this.editor.setPosition(position);
|
||||
}
|
||||
|
||||
// Dispose of the old model in a seperate event. We cannot dispose of the model within the
|
||||
// componentDidUpdate method or else the editor will throw an exception. Zero in the timeout field
|
||||
// means execute immediately but in a seperate next event.
|
||||
setTimeout(() => model.dispose(), 0);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if (theme) {
|
||||
monaco.editor.setTheme(getMonacoTheme(theme));
|
||||
}
|
||||
|
||||
// In the multi-tabs scenario, when the notebook is hidden by setting "display:none",
|
||||
// Any state update propagated here would cause a UI re-layout, monaco-editor will then recalculate
|
||||
// and set its height to 5px.
|
||||
// To work around that issue, we skip updating the UI when parent element's offsetParent is null (which
|
||||
// indicate an ancient element is hidden by display set to none)
|
||||
// We may revisit this when we get to refactor for multi-notebooks.
|
||||
if (!this.editorContainerRef.current?.offsetParent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set focus
|
||||
if (editorFocused && !this.editor.hasTextFocus()) {
|
||||
this.editor.focus();
|
||||
}
|
||||
|
||||
// Tells the editor pane to check if its container has changed size and fill appropriately
|
||||
this.editor.layout();
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
if (this.editor) {
|
||||
try {
|
||||
const model = this.editor.getModel();
|
||||
if (model) {
|
||||
model.dispose();
|
||||
}
|
||||
|
||||
this.editor.dispose();
|
||||
} catch (err) {
|
||||
console.error(`Error occurs in disposing editor: ${JSON.stringify(err)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
return (
|
||||
<div className="monaco-container">
|
||||
<div ref={this.editorContainerRef} id={`editor-${this.props.id}`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register default kernel-based completion provider.
|
||||
* @param language Language
|
||||
*/
|
||||
registerDefaultCompletionProvider(language: string): void {
|
||||
// onLanguage event is emitted only once per language when language is first time needed.
|
||||
monaco.languages.onLanguage(language, () => {
|
||||
monaco.languages.registerCompletionItemProvider(language, completionProvider);
|
||||
});
|
||||
}
|
||||
|
||||
private onFocus() {
|
||||
this.props.onFocusChange(true);
|
||||
this.toggleEditorOptions(true);
|
||||
this.registerCursorListener();
|
||||
}
|
||||
|
||||
private onBlur() {
|
||||
this.props.onFocusChange(false);
|
||||
this.toggleEditorOptions(false);
|
||||
this.unregisterCursorListener();
|
||||
}
|
||||
|
||||
private registerCursorListener() {
|
||||
if (this.editor && this.props.onCursorPositionChange) {
|
||||
const selection = this.editor.getSelection();
|
||||
this.props.onCursorPositionChange(selection);
|
||||
|
||||
if (!this.cursorPositionListener) {
|
||||
this.cursorPositionListener = this.editor.onDidChangeCursorSelection(event =>
|
||||
this.props.onCursorPositionChange!(event.selection)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private unregisterCursorListener() {
|
||||
if (this.cursorPositionListener) {
|
||||
this.cursorPositionListener.dispose();
|
||||
this.cursorPositionListener = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle editor options based on if the editor is in active state (i.e. focused).
|
||||
* When the editor is not active, we want to deactivate some of the visual noise.
|
||||
* @param isActive Whether editor is active.
|
||||
*/
|
||||
private toggleEditorOptions(isActive: boolean) {
|
||||
if (this.editor) {
|
||||
this.editor.updateOptions({
|
||||
matchBrackets: isActive,
|
||||
occurrencesHighlight: isActive,
|
||||
renderIndentGuides: isActive
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register language features for target language. Call before setting language type to model.
|
||||
*/
|
||||
private registerCompletionProvider() {
|
||||
const { enableCompletion, language, onRegisterCompletionProvider, shouldRegisterDefaultCompletion } = this.props;
|
||||
|
||||
if (enableCompletion && language) {
|
||||
if (onRegisterCompletionProvider) {
|
||||
onRegisterCompletionProvider(language);
|
||||
} else if (shouldRegisterDefaultCompletion) {
|
||||
this.registerDefaultCompletionProvider(language);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default connect<IMonacoStateProps, void, IMonacoProps, AppState>(makeMapStateToProps)(MonacoEditor);
|
||||
@@ -0,0 +1,239 @@
|
||||
import * as monaco from "monaco-editor/esm/vs/editor/editor.api";
|
||||
// import * as monaco from "../monaco";
|
||||
import { Observable, Observer } from "rxjs";
|
||||
import { first, map } from "rxjs/operators";
|
||||
import { childOf, JupyterMessage, ofMessageType, Channels } from "@nteract/messaging";
|
||||
|
||||
/**
|
||||
* TODO: import from nteract when the changes under editor-base.ts are ported to nteract.
|
||||
*/
|
||||
import { CompletionResults, CompletionMatch, completionRequest, js_idx_to_char_idx } from "../editor-base";
|
||||
|
||||
/**
|
||||
* Jupyter to Monaco completion item kinds.
|
||||
*/
|
||||
const unknownJupyterKind = "<unknown>";
|
||||
const jupyterToMonacoCompletionItemKind = {
|
||||
[unknownJupyterKind]: monaco.languages.CompletionItemKind.Field,
|
||||
class: monaco.languages.CompletionItemKind.Class,
|
||||
function: monaco.languages.CompletionItemKind.Function,
|
||||
keyword: monaco.languages.CompletionItemKind.Keyword,
|
||||
instance: monaco.languages.CompletionItemKind.Variable,
|
||||
statement: monaco.languages.CompletionItemKind.Variable
|
||||
};
|
||||
|
||||
/**
|
||||
* Completion item provider.
|
||||
*/
|
||||
class CompletionItemProvider implements monaco.languages.CompletionItemProvider {
|
||||
private channels: Channels | undefined;
|
||||
|
||||
/**
|
||||
* Set Channels of Jupyter kernel.
|
||||
* @param channels Channels of Jupyter kernel.
|
||||
*/
|
||||
setChannels(channels: Channels | undefined) {
|
||||
this.channels = channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether provider is connected to Jupyter kernel.
|
||||
*/
|
||||
get isConnectedToKernel() {
|
||||
return !!this.channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional characters to trigger completion other than Ctrl+Space.
|
||||
*/
|
||||
get triggerCharacters() {
|
||||
return [" ", "<", "/", ".", "="];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of completion items at position of cursor.
|
||||
* @param model Monaco editor text model.
|
||||
* @param position Position of cursor.
|
||||
*/
|
||||
async provideCompletionItems(model: monaco.editor.ITextModel, position: monaco.Position) {
|
||||
// Convert to zero-based index
|
||||
let cursorPos = model.getOffsetAt(position);
|
||||
const code = model.getValue();
|
||||
cursorPos = js_idx_to_char_idx(cursorPos, code);
|
||||
|
||||
// Get completions from Jupyter kernel if its Channels is connected
|
||||
let items = [];
|
||||
if (this.channels) {
|
||||
try {
|
||||
const message = completionRequest(code, cursorPos);
|
||||
items = await this.codeCompleteObservable(this.channels, message, model).toPromise();
|
||||
} catch (error) {
|
||||
// Temporary log error to console until we settle on how we log in V3
|
||||
// tslint:disable-next-line
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve<monaco.languages.CompletionList>({
|
||||
suggestions: items,
|
||||
incomplete: false
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of completion items from Jupyter kernel.
|
||||
* @param channels Channels of Jupyter kernel.
|
||||
* @param message Jupyter message for completion request.
|
||||
* @param model Text model.
|
||||
*/
|
||||
private codeCompleteObservable(channels: Channels, message: JupyterMessage, model: monaco.editor.ITextModel) {
|
||||
// Process completion response
|
||||
const completion$ = channels.pipe(
|
||||
childOf(message),
|
||||
ofMessageType("complete_reply"),
|
||||
map(entry => entry.content),
|
||||
first(),
|
||||
map(results => this.adaptToMonacoCompletions(results, model))
|
||||
);
|
||||
|
||||
// Subscribe and send completion request message
|
||||
return Observable.create((observer: Observer<unknown>) => {
|
||||
const subscription = completion$.subscribe(observer);
|
||||
channels.next(message);
|
||||
return subscription;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Jupyter completion result to list of Monaco completion items.
|
||||
*/
|
||||
private adaptToMonacoCompletions(results: CompletionResults, model: monaco.editor.ITextModel) {
|
||||
let range: monaco.IRange;
|
||||
let percentCount = 0;
|
||||
let matches = results ? results.matches : [];
|
||||
if (results.metadata && results.metadata._jupyter_types_experimental) {
|
||||
matches = results.metadata._jupyter_types_experimental as CompletionMatch[];
|
||||
}
|
||||
return matches.map((match: CompletionMatch, index: number) => {
|
||||
if (typeof match === "string") {
|
||||
const text = this.sanitizeText(match);
|
||||
const filtered = this.getFilterText(text);
|
||||
return {
|
||||
kind: this.adaptToMonacoCompletionItemKind(unknownJupyterKind),
|
||||
label: text,
|
||||
insertText: text,
|
||||
filterText: filtered,
|
||||
sortText: this.getSortText(index)
|
||||
} as monaco.languages.CompletionItem;
|
||||
} else {
|
||||
// We only need to get the range once as the range is the same for all completion items in the list.
|
||||
if (!range) {
|
||||
const start = model.getPositionAt(match.start);
|
||||
const end = model.getPositionAt(match.end);
|
||||
range = {
|
||||
startLineNumber: start.lineNumber,
|
||||
startColumn: start.column,
|
||||
endLineNumber: end.lineNumber,
|
||||
endColumn: end.column
|
||||
};
|
||||
|
||||
// Get the range representing the text before the completion action was invoked.
|
||||
// If the text starts with magics % indicator, we need to track how many of these indicators exist
|
||||
// so that we ensure the insertion text only inserts the delta between what the user typed versus
|
||||
// what is recommended by the completion. Without this, there will be extra % insertions.
|
||||
// Example:
|
||||
// User types %%p then suggestion list will recommend %%python, if we now commit the item then the
|
||||
// final text in the editor becomes %%p%%python instead of %%python. This is why the tracking code
|
||||
// below is needed. This behavior is only specific to the magics % indicators as Monaco does not
|
||||
// handle % characters in their completion list well.
|
||||
const rangeText = model.getValueInRange(range);
|
||||
if (rangeText.startsWith("%%")) {
|
||||
percentCount = 2;
|
||||
} else if (rangeText.startsWith("%")) {
|
||||
percentCount = 1;
|
||||
}
|
||||
}
|
||||
|
||||
const text = this.sanitizeText(match.text);
|
||||
const filtered = this.getFilterText(text);
|
||||
const insert = this.getInsertText(text, percentCount);
|
||||
return {
|
||||
kind: this.adaptToMonacoCompletionItemKind(match.type as keyof typeof jupyterToMonacoCompletionItemKind),
|
||||
label: text,
|
||||
insertText: percentCount > 0 ? insert : text,
|
||||
filterText: filtered,
|
||||
sortText: this.getSortText(index)
|
||||
} as monaco.languages.CompletionItem;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Jupyter completion item kind to Monaco completion item kind.
|
||||
* @param kind Jupyter completion item kind.
|
||||
*/
|
||||
private adaptToMonacoCompletionItemKind(kind: keyof typeof jupyterToMonacoCompletionItemKind) {
|
||||
const result = jupyterToMonacoCompletionItemKind[kind];
|
||||
return result ? result : jupyterToMonacoCompletionItemKind[unknownJupyterKind];
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove everything before a dot. Jupyter completion results like to include all characters before
|
||||
* the trigger character. For example, if user types "myarray.", we expect the completion results to
|
||||
* show "append", "pop", etc. but for the actual case, it will show "myarray.append", "myarray.pop",
|
||||
* etc. so we are going to sanitize the text.
|
||||
* @param text Text of Jupyter completion item
|
||||
*/
|
||||
private sanitizeText(text: string) {
|
||||
const index = text.lastIndexOf(".");
|
||||
return index > -1 && index < text.length - 1 ? text.substring(index + 1) : text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove magics all % characters as Monaco doesn't like them for the filtering text.
|
||||
* Without this, completion won't show magics match items.
|
||||
* @param text Text of Jupyter completion item.
|
||||
*/
|
||||
private getFilterText(text: string) {
|
||||
return text.replace(/%/g, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get insertion text handling what to insert for the magics case depending on what
|
||||
* has already been typed.
|
||||
* @param text Text of Jupyter completion item.
|
||||
* @param percentCount Number of percent characters to remove
|
||||
*/
|
||||
private getInsertText(text: string, percentCount: number) {
|
||||
for (let i = 0; i < percentCount; i++) {
|
||||
text = text.replace("%", "");
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps numbers to strings, such that if a>b numerically, f(a)>f(b) lexicograhically.
|
||||
* 1 -> "za", 26 -> "zz", 27 -> "zza", 28 -> "zzb", 52 -> "zzz", 53 ->"zzza"
|
||||
* @param order Number to be converted to a sorting-string. order >= 0.
|
||||
* @returns A string representing the order.
|
||||
*/
|
||||
private getSortText(order: number): string {
|
||||
order++;
|
||||
const numCharacters = 26; // "z" - "a" + 1;
|
||||
const div = Math.floor(order / numCharacters);
|
||||
|
||||
let sortText = "z";
|
||||
for (let i = 0; i < div; i++) {
|
||||
sortText += "z";
|
||||
}
|
||||
|
||||
const remainder = order % numCharacters;
|
||||
if (remainder > 0) {
|
||||
sortText += String.fromCharCode(96 + remainder);
|
||||
}
|
||||
return sortText;
|
||||
}
|
||||
}
|
||||
|
||||
const completionProvider = new CompletionItemProvider();
|
||||
export { completionProvider };
|
||||
44
src/Explorer/Notebook/MonacoEditor/converter.ts
Normal file
44
src/Explorer/Notebook/MonacoEditor/converter.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import Immutable from "immutable";
|
||||
import * as monaco from "./monaco";
|
||||
/**
|
||||
* Code Mirror to Monaco constants.
|
||||
*/
|
||||
export enum Mode {
|
||||
markdown = "markdown",
|
||||
raw = "plaintext",
|
||||
python = "python",
|
||||
csharp = "csharp"
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps Code Mirror mode to a valid Monaco Editor supported langauge
|
||||
* defaults to plaintext if map not found.
|
||||
* @param mode Code Mirror mode
|
||||
* @returns Monaco language
|
||||
*/
|
||||
export function mapCodeMirrorModeToMonaco(mode: string | { name: string }): string {
|
||||
let language = "";
|
||||
|
||||
// Parse codemirror mode object
|
||||
if (typeof mode === "string") {
|
||||
language = mode;
|
||||
}
|
||||
// Vanilla object
|
||||
else if (typeof mode === "object" && mode.name) {
|
||||
language = mode.name;
|
||||
}
|
||||
// Immutable Map
|
||||
else if (Immutable.Map.isMap(mode) && mode.has("name")) {
|
||||
language = mode.get("name");
|
||||
}
|
||||
|
||||
// Need to handle "ipython" as a special case since it is not a registered language
|
||||
if (language === "ipython") {
|
||||
return Mode.python;
|
||||
} else if (language === "text/x-csharp") {
|
||||
return Mode.csharp;
|
||||
} else if (monaco.languages.getEncodedLanguageId(language) > 0) {
|
||||
return language;
|
||||
}
|
||||
return Mode.raw;
|
||||
}
|
||||
76
src/Explorer/Notebook/MonacoEditor/editor-base.ts
Normal file
76
src/Explorer/Notebook/MonacoEditor/editor-base.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
// Disable linting on file since we will be moving the code below to nteract which have different rules configured.
|
||||
// tslint:disable:variable-name
|
||||
// tslint:disable:interface-name
|
||||
|
||||
/**
|
||||
* TODO: Create new editor-base package in nteract repo and move all code below to new package.
|
||||
*/
|
||||
|
||||
import { createMessage } from "@nteract/messaging";
|
||||
|
||||
/**
|
||||
* Jupyter messaging protocol's _jupyter_types_experimental completion result.
|
||||
*/
|
||||
interface CompletionResult {
|
||||
end: number;
|
||||
start: number;
|
||||
type: string;
|
||||
text: string;
|
||||
displayText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Juptyer completion match item.
|
||||
*/
|
||||
export type CompletionMatch = string | CompletionResult;
|
||||
|
||||
/**
|
||||
* Jupyter messaging protocol's complete_reply response.
|
||||
*/
|
||||
export interface CompletionResults {
|
||||
status: string;
|
||||
cursor_start: number;
|
||||
cursor_end: number;
|
||||
matches: CompletionMatch[];
|
||||
metadata?: {
|
||||
_jupyter_types_experimental?: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Jupyter messaging protocol's complete_request message.
|
||||
* @param code Code of editor.
|
||||
* @param cursorPos cursor position represented in the Jupyter messaging protocol (character position)
|
||||
*/
|
||||
export const completionRequest = (code: string, cursorPos: number) =>
|
||||
createMessage("complete_request", {
|
||||
content: {
|
||||
code,
|
||||
cursor_pos: cursorPos
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* JavaScript stores text as utf16 and string indices use "code units",
|
||||
* which stores high-codepoint characters as "surrogate pairs",
|
||||
* which occupy two indices in the JavaScript string.
|
||||
* We need to translate cursor_pos in the protocol (in characters)
|
||||
* to js offset (with surrogate pairs taking two spots).
|
||||
* @param js_idx JavaScript index
|
||||
* @param text Text
|
||||
*/
|
||||
export const js_idx_to_char_idx: (js_idx: number, text: string) => number = (js_idx: number, text: string): number => {
|
||||
let char_idx: number = js_idx;
|
||||
for (let i = 0; i + 1 < text.length && i < js_idx; i++) {
|
||||
const char_code: number = text.charCodeAt(i);
|
||||
// check for surrogate pair
|
||||
if (char_code >= 0xd800 && char_code <= 0xdbff) {
|
||||
const next_char_code: number = text.charCodeAt(i + 1);
|
||||
if (next_char_code >= 0xdc00 && next_char_code <= 0xdfff) {
|
||||
char_idx--;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return char_idx;
|
||||
};
|
||||
10
src/Explorer/Notebook/MonacoEditor/monaco.ts
Normal file
10
src/Explorer/Notebook/MonacoEditor/monaco.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export * from "monaco-editor/esm/vs/editor/editor.api";
|
||||
|
||||
// /**
|
||||
// * Set the custom worker url to workaround the cross-domain issue with creating web worker
|
||||
// * See https://github.com/microsoft/monaco-editor/blob/master/docs/integrate-amd-cross.md for more details
|
||||
// * This step has to be executed after a importing of monaco-editor once per chunk to make sure
|
||||
// * the custom worker url overwrites the one from monaco-editor module itself.
|
||||
// */
|
||||
// import { setMonacoWorkerUrl } from "./workerUrl";
|
||||
// setMonacoWorkerUrl();
|
||||
67
src/Explorer/Notebook/MonacoEditor/selectors.ts
Normal file
67
src/Explorer/Notebook/MonacoEditor/selectors.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { AppState, ContentRef, selectors as nteractSelectors } from "@nteract/core";
|
||||
import { CellId } from "@nteract/commutable";
|
||||
import { Mode, mapCodeMirrorModeToMonaco } from "./converter";
|
||||
|
||||
/**
|
||||
* Returns the language to use for syntax highlighting and autocompletion in the Monaco Editor for a given cell, falling back to the notebook language if one for the cell is not defined.
|
||||
*/
|
||||
export const getCellMonacoLanguage = (
|
||||
state: AppState,
|
||||
contentRef: ContentRef,
|
||||
cellId: CellId,
|
||||
cellLanguageOverride?: string,
|
||||
notebookLanguageOverride?: string
|
||||
): string => {
|
||||
const model = nteractSelectors.model(state, { contentRef });
|
||||
if (!model || model.type !== "notebook") {
|
||||
throw new Error("Connected Editor components should not be used with non-notebook models");
|
||||
}
|
||||
|
||||
const cell = nteractSelectors.notebook.cellById(model, { id: cellId });
|
||||
if (!cell) {
|
||||
throw new Error("Invalid cell id");
|
||||
}
|
||||
|
||||
switch (cell.cell_type) {
|
||||
case "markdown":
|
||||
return Mode.markdown;
|
||||
case "raw":
|
||||
return Mode.raw;
|
||||
case "code":
|
||||
if (cellLanguageOverride) {
|
||||
return mapCodeMirrorModeToMonaco(cellLanguageOverride);
|
||||
} else {
|
||||
// Fall back to notebook language if cell language isn't present.
|
||||
return getNotebookMonacoLanguage(state, contentRef, notebookLanguageOverride);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the language to use for syntax highlighting and autocompletion in the Monaco Editor for a given notebook.
|
||||
*/
|
||||
export const getNotebookMonacoLanguage = (
|
||||
state: AppState,
|
||||
contentRef: ContentRef,
|
||||
notebookLanguageOverride?: string
|
||||
): string => {
|
||||
const model = nteractSelectors.model(state, { contentRef });
|
||||
if (!model || model.type !== "notebook") {
|
||||
throw new Error("Connected Editor components should not be used with non-notebook models");
|
||||
}
|
||||
|
||||
if (notebookLanguageOverride) {
|
||||
return mapCodeMirrorModeToMonaco(notebookLanguageOverride);
|
||||
}
|
||||
|
||||
const kernelRef = model.kernelRef;
|
||||
let codeMirrorMode;
|
||||
// Try to get the CodeMirror mode from the kernel.
|
||||
if (kernelRef) {
|
||||
codeMirrorMode = nteractSelectors.kernel(state, { kernelRef })?.info?.codemirrorMode;
|
||||
}
|
||||
// As a fallback, get the CodeMirror mode from the notebook itself.
|
||||
codeMirrorMode = codeMirrorMode ?? nteractSelectors.notebook.codeMirrorMode(model);
|
||||
|
||||
return mapCodeMirrorModeToMonaco(codeMirrorMode);
|
||||
};
|
||||
22
src/Explorer/Notebook/MonacoEditor/styles.css
Normal file
22
src/Explorer/Notebook/MonacoEditor/styles.css
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
For the following components, we use the inherited width values from monaco-container.
|
||||
On resizing the browser, the width of monaco-container will be calculated
|
||||
and we just use the calculated width for the following components
|
||||
So we don't need to use Monaco editor's layout() function which is expensive operation and causes performance issues on resizing.
|
||||
*/
|
||||
/*
|
||||
TODO: These styles below are added for resizing perf improvement.
|
||||
Once the virtualization is implemented, we will revisit this later.
|
||||
*/
|
||||
.monaco-container .monaco-editor {
|
||||
width: inherit !important;
|
||||
}
|
||||
|
||||
.monaco-container .monaco-editor .overflow-guard {
|
||||
width: inherit !important;
|
||||
}
|
||||
|
||||
/* 26px is the left margin for .monaco-scrollable-element */
|
||||
.monaco-container .monaco-editor .monaco-scrollable-element.editor-scrollable.vs {
|
||||
width: calc(100% - 26px) !important;
|
||||
}
|
||||
75
src/Explorer/Notebook/MonacoEditor/theme.ts
Normal file
75
src/Explorer/Notebook/MonacoEditor/theme.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import * as monaco from "./monaco";
|
||||
|
||||
// TODO: move defineTheme calls to an initialization function
|
||||
|
||||
/**
|
||||
* The default light theme with customized background
|
||||
*/
|
||||
export const LightThemeName = "vs-light";
|
||||
|
||||
/**
|
||||
* Default monaco theme for light theme
|
||||
*/
|
||||
export const customMonacoLightTheme: monaco.editor.IStandaloneThemeData = {
|
||||
base: "vs", // Derive from default light theme of Monaco
|
||||
inherit: true,
|
||||
rules: [],
|
||||
colors: {
|
||||
// We want Monaco background to use the same background for our themes.
|
||||
// Without this, the Monaco light theme has a yellowish tone.
|
||||
// Verified with UX that white meets all the accessbility requirements for light
|
||||
// and high contrast light theme.
|
||||
"editor.background": "#FFFFFF"
|
||||
}
|
||||
};
|
||||
|
||||
monaco.editor.defineTheme(LightThemeName, customMonacoLightTheme);
|
||||
|
||||
/**
|
||||
* The default dark theme with customized background
|
||||
*/
|
||||
export const DarkThemeName = "aznb-dark";
|
||||
|
||||
/**
|
||||
* Default monaco theme for dark theme
|
||||
*/
|
||||
export const customMonacoDarkTheme: monaco.editor.IStandaloneThemeData = {
|
||||
base: "vs-dark", // Derive from default dark theme of Monaco
|
||||
inherit: true,
|
||||
rules: [],
|
||||
colors: {
|
||||
"editor.background": "#1b1a19"
|
||||
}
|
||||
};
|
||||
|
||||
monaco.editor.defineTheme(DarkThemeName, customMonacoDarkTheme);
|
||||
|
||||
/**
|
||||
* The custom high contrast light theme with customized background
|
||||
*/
|
||||
export const HCLightThemeName = "hc-light";
|
||||
|
||||
/**
|
||||
* Default monaco theme for light high contrast mode
|
||||
*/
|
||||
export const customMonacoHCLightTheme: monaco.editor.IStandaloneThemeData = {
|
||||
base: "vs", // Derive from default light theme of Monaco; change all grey colors to black to comply with highcontrast rules
|
||||
inherit: true,
|
||||
rules: [
|
||||
{ token: "annotation", foreground: "000000" },
|
||||
{ token: "delimiter.html", foreground: "000000" },
|
||||
{ token: "operator.scss", foreground: "000000" },
|
||||
{ token: "operator.sql", foreground: "000000" },
|
||||
{ token: "operator.swift", foreground: "000000" },
|
||||
{ token: "predefined.sql", foreground: "000000" }
|
||||
],
|
||||
colors: {
|
||||
// We want Monaco background to use the same background for our themes.
|
||||
// Without this, the Monaco light theme has a yellowish tone.
|
||||
// Verified with UX that white meets all the accessbility requirements for light
|
||||
// and high contrast light theme.
|
||||
"editor.background": "#FFFFFF"
|
||||
}
|
||||
};
|
||||
|
||||
monaco.editor.defineTheme(HCLightThemeName, customMonacoHCLightTheme);
|
||||
@@ -32,7 +32,7 @@ import { Store, AnyAction, MiddlewareAPI, Middleware, Dispatch } from "redux";
|
||||
import configureStore from "./NotebookComponent/store";
|
||||
|
||||
import { Notification } from "react-notification-system";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
|
||||
export type KernelSpecsDisplay = { name: string; displayName: string };
|
||||
|
||||
@@ -37,7 +37,7 @@ import * as Constants from "../../../Common/Constants";
|
||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import * as CdbActions from "./actions";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { Action as TelemetryAction } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import { CdbAppState } from "./types";
|
||||
import { decryptJWTToken } from "../../../Utils/AuthorizationUtils";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { actions, CoreRecord, reducers as nteractReducers } from "@nteract/core";
|
||||
import { Action } from "redux";
|
||||
import { Areas } from "../../../Common/Constants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import * as cdbActions from "./actions";
|
||||
import { CdbRecord } from "./types";
|
||||
|
||||
|
||||
@@ -194,24 +194,17 @@ export class NotebookContentClient {
|
||||
});
|
||||
}
|
||||
|
||||
public async readFileContent(filePath: string): Promise<string> {
|
||||
const xhr = await this.contentProvider.get(this.getServerConfig(), filePath, { content: 1 }).toPromise();
|
||||
public readFileContent(filePath: string): Promise<string> {
|
||||
return this.contentProvider
|
||||
.get(this.getServerConfig(), filePath, { type: "notebook", format: "text", content: 1 })
|
||||
.toPromise()
|
||||
.then(xhr => {
|
||||
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);
|
||||
default:
|
||||
throw new Error(`Unsupported content format ${format}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private deleteNotebookFile(path: string): Promise<string> {
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as Logger from "../../Common/Logger";
|
||||
import { HttpStatusCodes, Areas } from "../../Common/Constants";
|
||||
import { GitHubReposPane } from "../Panes/GitHubReposPane";
|
||||
import ko from "knockout";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import { IContentProvider } from "@nteract/core";
|
||||
import { NotebookContentProvider } from "./NotebookComponent/NotebookContentProvider";
|
||||
|
||||
@@ -30,6 +30,7 @@ import { CellType } from "@nteract/commutable/src";
|
||||
import "./NotebookRenderer.less";
|
||||
import HoverableCell from "./decorators/HoverableCell";
|
||||
import CellLabeler from "./decorators/CellLabeler";
|
||||
import MonacoEditor from "../MonacoEditor/MonacoEditor";
|
||||
import * as cdbActions from "../NotebookComponent/actions";
|
||||
|
||||
export interface NotebookRendererBaseProps {
|
||||
@@ -116,7 +117,12 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
|
||||
{{
|
||||
editor: {
|
||||
codemirror: (props: PassedEditorProps) => (
|
||||
<CodeMirrorEditor {...props} lineNumbers={true} />
|
||||
<MonacoEditor
|
||||
{...props}
|
||||
lineNumbers={true}
|
||||
enableCompletion={true}
|
||||
shouldRegisterDefaultCompletion={true}
|
||||
/>
|
||||
)
|
||||
},
|
||||
prompt: ({ id, contentRef }: { id: CellId; contentRef: ContentRef }) => (
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
<script type="text/html" id="add-collection-inputs">
|
||||
<!-- Add collection header - Start -->
|
||||
<div class="firstdivbg headerline">
|
||||
<span id="containerTitle" role="heading" aria-level="2" data-bind="text: title" ></span>
|
||||
<div class="closeImg" id="closeBtnAddCollection" role="button" aria-label="Add collection close pane"
|
||||
<span id="containerTitle" data-bind="text: title"></span>
|
||||
<div class="closeImg" id="closeBtnAddCollection" role="button" aria-label="Close pane"
|
||||
data-bind="click: cancel, event: { keypress: onCloseKeyPress }" tabindex="0">
|
||||
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
|
||||
</div>
|
||||
@@ -115,10 +115,10 @@
|
||||
|
||||
<!-- Database provisioned throughput - Start -->
|
||||
<!-- ko if: canConfigureThroughput -->
|
||||
<div class="databaseProvision" aria-label="Provision database throughput"
|
||||
<div class="databaseProvision" aria-label="New database provision support"
|
||||
data-bind="visible: databaseCreateNew">
|
||||
<input tabindex="0" type="checkbox" data-test="addCollectionPane-databaseSharedThroughput"
|
||||
id="addCollection-databaseSharedThroughput" title="Provision database throughput"
|
||||
id="addCollection-databaseSharedThroughput" title="Provision shared throughput"
|
||||
data-bind="checked: databaseCreateNewShared" />
|
||||
<span class="databaseProvisionText" for="databaseSharedThroughput">Provision database throughput</span>
|
||||
<span class="infoTooltip" role="tooltip" tabindex="0">
|
||||
@@ -537,11 +537,9 @@
|
||||
attr: {
|
||||
'aria-checked': isAnalyticalStorageOn() ? 'true' : 'false'
|
||||
}" />
|
||||
<label for="enableAnalyticalStorageRadioOn" class="enableAnalyticalStorageRadioLabel">
|
||||
<span data-bind="disable: showEnableSynapseLink">
|
||||
<span for="enableAnalyticalStorageRadioOn" data-bind="disable: showEnableSynapseLink">
|
||||
On
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<input class="enableAnalyticalStorageRadio" id="enableAnalyticalStorageRadioOff"
|
||||
name="analyticalStore" type="radio" role="radio" tabindex="0" data-bind="
|
||||
@@ -551,11 +549,9 @@
|
||||
attr: {
|
||||
'aria-checked': isAnalyticalStorageOn() ? 'false' : 'true'
|
||||
}" />
|
||||
<label for="enableAnalyticalStorageRadioOff" class="enableAnalyticalStorageRadioLabel">
|
||||
<span data-bind="disable: showEnableSynapseLink">
|
||||
<span for="enableAnalyticalStorageRadioOff" data-bind="disable: showEnableSynapseLink">
|
||||
Off
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="paragraph italic" data-bind="visible: ttl90DaysEnabled() && isAnalyticalStorageOn()">
|
||||
|
||||
@@ -9,15 +9,18 @@ import * as PricingUtils from "../../Utils/PricingUtils";
|
||||
import * as SharedConstants from "../../Shared/Constants";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import editable from "../../Common/EditableUtility";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import EnvironmentUtility from "../../Common/EnvironmentUtility";
|
||||
import Q from "q";
|
||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import { configContext, Platform } from "../../ConfigContext";
|
||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
import { createMongoCollectionWithARM, createMongoCollectionWithProxy } from "../../Common/MongoProxyClient";
|
||||
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
|
||||
import { HashMap } from "../../Common/HashMap";
|
||||
import { PlatformType } from "../../PlatformType";
|
||||
import { refreshCachedResources } from "../../Common/DocumentClientUtilityBase";
|
||||
import { createCollection } from "../../Common/dataAccess/createCollection";
|
||||
import { refreshCachedResources, getOrCreateDatabaseAndCollection } from "../../Common/DocumentClientUtilityBase";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export interface AddCollectionPaneOptions extends ViewModels.PaneOptions {
|
||||
isPreferredApiTable: ko.Computed<boolean>;
|
||||
@@ -808,6 +811,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
|
||||
let databaseId: string = this.databaseCreateNew() ? this.databaseId().trim() : this.databaseId();
|
||||
let collectionId: string = this.collectionId().trim();
|
||||
let rupm: boolean = this.rupm() === Constants.RUPMStates.on;
|
||||
|
||||
let indexingPolicy: DataModels.IndexingPolicy;
|
||||
// todo - remove mongo indexing policy ticket # 616274
|
||||
@@ -824,28 +828,130 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
}
|
||||
|
||||
this.formErrors("");
|
||||
|
||||
this.isExecuting(true);
|
||||
|
||||
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(),
|
||||
const createRequest: DataModels.CreateDatabaseAndCollectionRequest = {
|
||||
collectionId,
|
||||
databaseId,
|
||||
databaseLevelThroughput,
|
||||
offerThroughput,
|
||||
analyticalStorageTtl: this._getAnalyticalStorageTtl(),
|
||||
autoPilotMaxThroughput,
|
||||
indexingPolicy,
|
||||
databaseLevelThroughput: this.databaseHasSharedOffer() && !this.collectionWithThroughputInShared(),
|
||||
rupmEnabled: rupm,
|
||||
partitionKey,
|
||||
uniqueKeyPolicy
|
||||
indexingPolicy,
|
||||
uniqueKeyPolicy,
|
||||
autoPilot,
|
||||
analyticalStorageTtl: this._getAnalyticalStorageTtl(),
|
||||
hasAutoPilotV2FeatureFlag: this.hasAutoPilotV2FeatureFlag()
|
||||
};
|
||||
|
||||
createCollection(createCollectionParams).then(
|
||||
const options: any = {};
|
||||
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.close();
|
||||
@@ -943,7 +1049,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
const defaultThroughput = this.container.collectionCreationDefaults.throughput;
|
||||
this.throughputSinglePartition(defaultThroughput.fixed);
|
||||
this.throughputMultiPartition(
|
||||
AddCollectionUtility.getMaxThroughput(this.container.collectionCreationDefaults, this.container)
|
||||
AddCollectionUtility.Utilities.getMaxThroughput(this.container.collectionCreationDefaults, this.container)
|
||||
);
|
||||
|
||||
this.throughputDatabase(defaultThroughput.shared);
|
||||
@@ -1128,6 +1234,35 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
|
||||
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 {
|
||||
// Note: this will not validate properly on accounts that have been set up for custom partitioning,
|
||||
@@ -1167,19 +1302,17 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
|
||||
private _updateThroughputLimitByCollectionStorage() {
|
||||
const storage = this.storage();
|
||||
const minThroughputRU =
|
||||
storage === SharedConstants.CollectionCreation.storage10Gb
|
||||
? SharedConstants.CollectionCreation.DefaultCollectionRUs400
|
||||
: this.container.collectionCreationDefaults.throughput.unlimitedmin;
|
||||
const minThroughputRU = AddCollectionUtility.Utilities.getMinRUForStorageOption(
|
||||
this.container.collectionCreationDefaults,
|
||||
storage
|
||||
);
|
||||
|
||||
let maxThroughputRU;
|
||||
let maxThroughputRU = AddCollectionUtility.Utilities.getMaxRUForStorageOption(
|
||||
this.container.collectionCreationDefaults,
|
||||
storage
|
||||
);
|
||||
if (this.isTryCosmosDBSubscription()) {
|
||||
maxThroughputRU = Constants.TryCosmosExperience.maxRU;
|
||||
} else {
|
||||
maxThroughputRU =
|
||||
storage === SharedConstants.CollectionCreation.storage10Gb
|
||||
? SharedConstants.CollectionCreation.DefaultCollectionRUs10K
|
||||
: this.container.collectionCreationDefaults.throughput.unlimitedmax;
|
||||
}
|
||||
|
||||
this.minThroughputRU(minThroughputRU);
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<script type="text/html" id="add-database-inputs">
|
||||
<!-- Add database header - Start -->
|
||||
<div class="firstdivbg headerline">
|
||||
<span id="databaseTitle" role="heading" aria-level="2" data-bind="text: title"></span>
|
||||
<span id="databaseTitle" data-bind="text: title"></span>
|
||||
<div class="closeImg" role="button" aria-label="Close pane"
|
||||
data-bind="click: cancel, event: { keypress: onCloseKeyPress }" tabindex="0">
|
||||
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
|
||||
@@ -73,8 +73,8 @@
|
||||
</p>
|
||||
|
||||
<input id="database-id" type="text" aria-required="true" autocomplete="off" pattern="[^/?#\\]*[^/?# \\]"
|
||||
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
||||
size="40" class="collid" data-bind="textInput: databaseId, hasFocus: firstFieldHasFocus, attr: { placeholder: databaseIdPlaceHolder }"
|
||||
title="May not end with space nor contain characters '\' '/' '#' '?'" placeholder="Type a new database id"
|
||||
size="40" class="collid" data-bind="textInput: databaseId, hasFocus: firstFieldHasFocus"
|
||||
aria-label="Database id" autofocus>
|
||||
|
||||
<!-- Database provisioned throughput - Start -->
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as AddCollectionUtility from "../../Shared/AddCollectionUtility";
|
||||
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
@@ -7,16 +8,19 @@ import * as PricingUtils from "../../Utils/PricingUtils";
|
||||
import * as SharedConstants from "../../Shared/Constants";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import editable from "../../Common/EditableUtility";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import EnvironmentUtility from "../../Common/EnvironmentUtility";
|
||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import { AddDbUtilities } from "../../Shared/AddDatabaseUtility";
|
||||
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
|
||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
import { createDatabase } from "../../Common/dataAccess/createDatabase";
|
||||
import { PlatformType } from "../../PlatformType";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export default class AddDatabasePane extends ContextualPaneBase {
|
||||
public defaultExperience: ko.Computed<string>;
|
||||
public databaseIdLabel: ko.Computed<string>;
|
||||
public databaseIdPlaceHolder: ko.Computed<string>;
|
||||
public databaseId: ko.Observable<string>;
|
||||
public databaseIdTooltipText: ko.Computed<string>;
|
||||
public databaseLevelThroughputTooltipText: ko.Computed<string>;
|
||||
@@ -71,11 +75,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
this.databaseIdLabel = ko.computed<string>(() =>
|
||||
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>(() => {
|
||||
const isCassandraAccount: boolean = this.container.isPreferredApiCassandra();
|
||||
return `A ${isCassandraAccount ? "keyspace" : "database"} is a logical container of one or more ${
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="paneContentContainer">
|
||||
<!-- Save Query header - Start -->
|
||||
<div class="firstdivbg headerline">
|
||||
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||
<span data-bind="text: title"></span>
|
||||
<div
|
||||
class="closeImg"
|
||||
role="button"
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Areas } from "../../Common/Constants";
|
||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
import * as Logger from "../../Common/Logger";
|
||||
import { QueriesGridComponentAdapter } from "../Controls/QueriesGridReactComponent/QueriesGridComponentAdapter";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import QueryTab from "../Tabs/QueryTab";
|
||||
|
||||
export class BrowseQueriesPane extends ContextualPaneBase {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
>
|
||||
<!-- Add Cassandra collection header - Start -->
|
||||
<div class="firstdivbg headerline">
|
||||
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||
<span data-bind="text: title"></span>
|
||||
<div
|
||||
class="closeImg"
|
||||
role="button"
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as ko from "knockout";
|
||||
import * as PricingUtils from "../../Utils/PricingUtils";
|
||||
import * as SharedConstants from "../../Shared/Constants";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
|
||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
@@ -494,7 +494,9 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
this.selectedSharedAutoPilotTier(null);
|
||||
this.selectedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||
this.throughput(AddCollectionUtility.getMaxThroughput(this.container.collectionCreationDefaults, this.container));
|
||||
this.throughput(
|
||||
AddCollectionUtility.Utilities.getMaxThroughput(this.container.collectionCreationDefaults, this.container)
|
||||
);
|
||||
this.keyspaceThroughput(throughputDefaults.shared);
|
||||
this.maxThroughputRU(throughputDefaults.unlimitedmax);
|
||||
this.minThroughputRU(throughputDefaults.unlimitedmin);
|
||||
|
||||
@@ -4,12 +4,11 @@ import * as Constants from "../../Common/Constants";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import { KeyCodes } from "../../Common/Constants";
|
||||
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import Explorer from "../Explorer";
|
||||
|
||||
// TODO: Use specific actions for logging telemetry data
|
||||
export abstract class ContextualPaneBase extends WaitsForTemplateViewModel {
|
||||
private initalFocusedElement: HTMLElement | undefined;
|
||||
public id: string;
|
||||
public container: Explorer;
|
||||
public firstFieldHasFocus: ko.Observable<boolean>;
|
||||
@@ -50,11 +49,9 @@ export abstract class ContextualPaneBase extends WaitsForTemplateViewModel {
|
||||
this.visible(false);
|
||||
this.isExecuting(false);
|
||||
this.resetData();
|
||||
this.resetFocus();
|
||||
}
|
||||
|
||||
public open() {
|
||||
this.initalFocusedElement = document.activeElement as HTMLElement;
|
||||
this.visible(true);
|
||||
this.firstFieldHasFocus(true);
|
||||
this.resizePane();
|
||||
@@ -126,11 +123,4 @@ export abstract class ContextualPaneBase extends WaitsForTemplateViewModel {
|
||||
|
||||
$(paneElement).height(newPaneElementHeight);
|
||||
}
|
||||
|
||||
private resetFocus(): void {
|
||||
if (this.initalFocusedElement) {
|
||||
this.initalFocusedElement.focus();
|
||||
this.initalFocusedElement = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
>
|
||||
<!-- Delete Collection Confirmation header - Start -->
|
||||
<div class="firstdivbg headerline">
|
||||
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||
<span data-bind="text: title"></span>
|
||||
<div
|
||||
class="closeImg"
|
||||
role="button"
|
||||
@@ -67,7 +67,8 @@
|
||||
name="collectionIdConfirmation"
|
||||
required
|
||||
class="collid"
|
||||
data-bind="value: collectionIdConfirmation, hasFocus: firstFieldHasFocus, attr: { 'aria-label': collectionIdConfirmationText }"
|
||||
data-bind="value: collectionIdConfirmation, hasFocus: firstFieldHasFocus"
|
||||
aria-label="Confirm by typing the collection id"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
|
||||
import DeleteCollectionConfirmationPane from "./DeleteCollectionConfirmationPane";
|
||||
import DeleteFeedback from "../../Common/DeleteFeedback";
|
||||
import Explorer from "../Explorer";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { TreeNode } from "../../Contracts/ViewModels";
|
||||
import { deleteCollection } from "../../Common/dataAccess/deleteCollection";
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
||||
import DeleteFeedback from "../../Common/DeleteFeedback";
|
||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { deleteCollection } from "../../Common/dataAccess/deleteCollection";
|
||||
|
||||
export default class DeleteCollectionConfirmationPane extends ContextualPaneBase {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
>
|
||||
<!-- Delete Database Confirmation header - Start -->
|
||||
<div class="firstdivbg headerline">
|
||||
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||
<span data-bind="text: title"></span>
|
||||
<div
|
||||
class="closeImg"
|
||||
role="button"
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import DeleteDatabaseConfirmationPane from "./DeleteDatabaseConfirmationPane";
|
||||
import DeleteFeedback from "../../Common/DeleteFeedback";
|
||||
import Explorer from "../Explorer";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { TreeNode } from "../../Contracts/ViewModels";
|
||||
import { TabsManager } from "../Tabs/TabsManager";
|
||||
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
|
||||
|
||||
@@ -11,9 +11,8 @@ import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility"
|
||||
import DeleteFeedback from "../../Common/DeleteFeedback";
|
||||
|
||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
|
||||
import { ARMError } from "../../Utils/arm/request";
|
||||
|
||||
export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
|
||||
public databaseIdConfirmationText: ko.Observable<string>;
|
||||
@@ -106,12 +105,11 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
|
||||
this.databaseDeleteFeedback("");
|
||||
}
|
||||
},
|
||||
(reason: unknown) => {
|
||||
(reason: any) => {
|
||||
this.isExecuting(false);
|
||||
|
||||
const message = reason instanceof ARMError ? reason.message : ErrorParserUtility.parse(reason)[0].message;
|
||||
this.formErrors(message);
|
||||
this.formErrorsDetails(message);
|
||||
const message = ErrorParserUtility.parse(reason);
|
||||
this.formErrors(message[0].message);
|
||||
this.formErrorsDetails(message[0].message);
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.DeleteDatabase,
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<form class="paneContentContainer" data-bind="submit: execute">
|
||||
<!-- Input params header - Start -->
|
||||
<div class="firstdivbg headerline">
|
||||
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||
<span data-bind="text: title"></span>
|
||||
<div
|
||||
class="closeImg"
|
||||
role="button"
|
||||
|
||||
@@ -70,9 +70,7 @@ export class GenericRightPaneComponent extends React.Component<GenericRightPaneP
|
||||
private renderPanelHeader = (): JSX.Element => {
|
||||
return (
|
||||
<div className="firstdivbg headerline">
|
||||
<span id="databaseTitle" role="heading" aria-level={2}>
|
||||
{this.props.title}
|
||||
</span>
|
||||
<span id="databaseTitle">{this.props.title}</span>
|
||||
<IconButton
|
||||
ariaLabel="Close pane"
|
||||
title="Close pane"
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { GitHubClient, IGitHubPageInfo, IGitHubRepo } from "../../GitHub/GitHubClient";
|
||||
import { IPinnedRepo, JunoClient } from "../../Juno/JunoClient";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||
import { JunoUtils } from "../../Utils/JunoUtils";
|
||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<form class="paneContentContainer" data-bind="submit: submit">
|
||||
<!-- New Vertex header - Start -->
|
||||
<div class="firstdivbg headerline">
|
||||
<span role="heading" aria-level="2">New Vertex</span>
|
||||
<span>New Vertex</span>
|
||||
<div
|
||||
class="closeImg"
|
||||
role="button"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<form class="paneContentContainer" data-bind="submit: submit">
|
||||
<!-- Graph Styling header - Start -->
|
||||
<div class="firstdivbg headerline">
|
||||
<span role="heading" aria-level="2">Graph Styling</span>
|
||||
<span>Graph Styling</span>
|
||||
<div
|
||||
class="closeImg"
|
||||
role="button"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<form class="paneContentContainer" data-bind="submit: submit">
|
||||
<!-- Load Query header - Start -->
|
||||
<div class="firstdivbg headerline">
|
||||
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||
<span data-bind="text: title"></span>
|
||||
<div
|
||||
class="closeImg"
|
||||
role="button"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<form class="paneContentContainer" data-bind="submit: submit">
|
||||
<!-- Renew ad-hoc access header - Start -->
|
||||
<div class="firstdivbg headerline">
|
||||
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||
<span data-bind="text: title"></span>
|
||||
<div
|
||||
class="closeImg"
|
||||
role="button"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<form class="paneContentContainer" data-bind="submit: submit">
|
||||
<!-- Save Query header - Start -->
|
||||
<div class="firstdivbg headerline">
|
||||
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||
<span data-bind="text: title"></span>
|
||||
<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" />
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import QueryTab from "../Tabs/QueryTab";
|
||||
|
||||
export class SaveQueryPane extends ContextualPaneBase {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<form class="paneContentContainer" data-bind="submit: submit">
|
||||
<!-- Settings Confirmation header - Start -->
|
||||
<div class="firstdivbg headerline">
|
||||
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||
<span data-bind="text: title"></span>
|
||||
<div
|
||||
class="closeImg"
|
||||
role="button"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="paneContentContainer">
|
||||
<!-- Setup notebooks header - Start -->
|
||||
<div class="firstdivbg headerline">
|
||||
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||
<span data-bind="text: title"></span>
|
||||
<div
|
||||
class="closeImg"
|
||||
role="button"
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Areas, KeyCodes } from "../../Common/Constants";
|
||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import * as ko from "knockout";
|
||||
|
||||
export class SetupNotebooksPane extends ContextualPaneBase {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<form class="paneContentContainer" data-bind="submit: submit">
|
||||
<!-- String Input header - Start -->
|
||||
<div class="firstdivbg headerline">
|
||||
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||
<span data-bind="text: title"></span>
|
||||
<div class="closeImg" role="button" aria-label="Close pane" tabindex="0" data-bind="click: cancel"></div>
|
||||
</div>
|
||||
<!-- String Input header - End -->
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="contextual-pane-in">
|
||||
<!-- Switch Directory header - Start -->
|
||||
<div class="firstdivbg headerline">
|
||||
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||
<span data-bind="text: title"></span>
|
||||
<div class="closeImg" role="button" aria-label="Close pane" tabindex="0" data-bind="click: close">
|
||||
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { DirectoryListProps } from "../Controls/Directory/DirectoryListComponent
|
||||
import { DefaultDirectoryDropdownProps } from "../Controls/Directory/DefaultDirectoryDropdownComponent";
|
||||
import { DirectoryComponentAdapter } from "../Controls/Directory/DirectoryComponentAdapter";
|
||||
import SwitchDirectoryPaneTemplate from "./SwitchDirectoryPane.html";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
|
||||
class PaneComponent {
|
||||
constructor(data: any) {
|
||||
|
||||
@@ -80,7 +80,7 @@ export default class AddTableEntityPane extends TableEntityPane {
|
||||
this.updateIsActionEnabled();
|
||||
super.open();
|
||||
}
|
||||
const focusElement = document.getElementById("closeAddEntityPane");
|
||||
const focusElement = document.getElementById("addTableEntityValue");
|
||||
focusElement && focusElement.focus();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
<div class="firstdivbg headerline">
|
||||
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||
<div
|
||||
id="closeAddEntityPane"
|
||||
class="closeImg"
|
||||
role="button"
|
||||
aria-label="Close pane"
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
>
|
||||
<!-- Edit table entity header - Start -->
|
||||
<div class="firstdivbg headerline">
|
||||
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||
<span data-bind="text: title"></span>
|
||||
<div
|
||||
class="closeImg"
|
||||
role="button"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<form class="paneContentContainer" data-bind="submit: submit">
|
||||
<!-- Upload File header - Start -->
|
||||
<div class="firstdivbg headerline">
|
||||
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
||||
<span data-bind="text: title"></span>
|
||||
<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" />
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user