mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-31 06:41:35 +00:00
Compare commits
1 Commits
memory-swr
...
remove-exp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f738723f5a |
@@ -266,6 +266,10 @@ src/ResourceProvider/ResourceProviderClientFactory.ts
|
|||||||
src/RouteHandlers/RouteHandler.ts
|
src/RouteHandlers/RouteHandler.ts
|
||||||
src/RouteHandlers/TabRouteHandler.test.ts
|
src/RouteHandlers/TabRouteHandler.test.ts
|
||||||
src/RouteHandlers/TabRouteHandler.ts
|
src/RouteHandlers/TabRouteHandler.ts
|
||||||
|
src/Shared/AddCollectionUtility.test.ts
|
||||||
|
src/Shared/AddCollectionUtility.ts
|
||||||
|
src/Shared/AddDatabaseUtility.test.ts
|
||||||
|
src/Shared/AddDatabaseUtility.ts
|
||||||
src/Shared/Constants.ts
|
src/Shared/Constants.ts
|
||||||
src/Shared/DefaultExperienceUtility.test.ts
|
src/Shared/DefaultExperienceUtility.test.ts
|
||||||
src/Shared/DefaultExperienceUtility.ts
|
src/Shared/DefaultExperienceUtility.ts
|
||||||
@@ -275,6 +279,8 @@ src/Shared/StorageUtility.test.ts
|
|||||||
src/Shared/StorageUtility.ts
|
src/Shared/StorageUtility.ts
|
||||||
src/Shared/StringUtility.test.ts
|
src/Shared/StringUtility.test.ts
|
||||||
src/Shared/StringUtility.ts
|
src/Shared/StringUtility.ts
|
||||||
|
src/Shared/Telemetry/TelemetryConstants.ts
|
||||||
|
src/Shared/Telemetry/TelemetryProcessor.ts
|
||||||
src/Shared/appInsights.ts
|
src/Shared/appInsights.ts
|
||||||
src/SparkClusterManager/ArcadiaResourceManager.ts
|
src/SparkClusterManager/ArcadiaResourceManager.ts
|
||||||
src/SparkClusterManager/SparkClusterManager.ts
|
src/SparkClusterManager/SparkClusterManager.ts
|
||||||
@@ -412,5 +418,6 @@ cypress/integration/dataexplorer/SQL/addCollection.spec.ts
|
|||||||
cypress/integration/dataexplorer/TABLE/addCollection.spec.ts
|
cypress/integration/dataexplorer/TABLE/addCollection.spec.ts
|
||||||
cypress/integration/notebook/newNotebook.spec.ts
|
cypress/integration/notebook/newNotebook.spec.ts
|
||||||
cypress/integration/notebook/resourceTree.spec.ts
|
cypress/integration/notebook/resourceTree.spec.ts
|
||||||
|
__mocks__/AddDatabaseUtility.ts
|
||||||
__mocks__/monaco-editor.ts
|
__mocks__/monaco-editor.ts
|
||||||
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx
|
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx
|
||||||
@@ -41,7 +41,6 @@ module.exports = {
|
|||||||
"@typescript-eslint/no-extraneous-class": "error",
|
"@typescript-eslint/no-extraneous-class": "error",
|
||||||
"no-null/no-null": "error",
|
"no-null/no-null": "error",
|
||||||
"@typescript-eslint/no-explicit-any": "error",
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
|
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }]
|
||||||
eqeqeq: "error"
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
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",
|
"test": "cypress run",
|
||||||
"wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/",
|
"wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/",
|
||||||
"test:sql": "cypress run --browser chrome --spec \"./integration/dataexplorer/SQL/*\"",
|
"test:sql": "cypress run --browser chrome --spec \"./integration/dataexplorer/SQL/*\"",
|
||||||
"test:ci": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/ https-get://0.0.0.0:8081/_explorer/index.html && cypress run --browser edge --headless",
|
"test:ci": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/ https-get://0.0.0.0:8081/_explorer/index.html && cypress run --browser chrome --headless",
|
||||||
"test:debug": "cypress open"
|
"test:debug": "cypress open"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -3017,8 +3017,4 @@ settings-pane {
|
|||||||
|
|
||||||
.italic {
|
.italic {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
|
||||||
|
|
||||||
.warningErrorContent a {
|
|
||||||
color: @AccentMediumHigh
|
|
||||||
}
|
}
|
||||||
730
package-lock.json
generated
730
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -4,11 +4,11 @@
|
|||||||
"description": "Cosmos Explorer",
|
"description": "Cosmos Explorer",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/cosmos": "3.9.0",
|
"@azure/cosmos": "3.7.4",
|
||||||
"@azure/cosmos-language-service": "0.0.4",
|
"@azure/cosmos-language-service": "0.0.4",
|
||||||
"@jupyterlab/services": "4.2.0",
|
"@jupyterlab/services": "4.2.0",
|
||||||
"@jupyterlab/terminal": "1.2.1",
|
"@jupyterlab/terminal": "1.2.1",
|
||||||
"@microsoft/applicationinsights-web": "2.5.8",
|
"@microsoft/applicationinsights-web": "2.5.4",
|
||||||
"@nteract/commutable": "7.1.4",
|
"@nteract/commutable": "7.1.4",
|
||||||
"@nteract/connected-components": "6.7.8",
|
"@nteract/connected-components": "6.7.8",
|
||||||
"@nteract/core": "13.0.0",
|
"@nteract/core": "13.0.0",
|
||||||
@@ -66,13 +66,13 @@
|
|||||||
"mkdirp": "1.0.4",
|
"mkdirp": "1.0.4",
|
||||||
"monaco-editor": "0.15.6",
|
"monaco-editor": "0.15.6",
|
||||||
"object.entries": "1.1.0",
|
"object.entries": "1.1.0",
|
||||||
"office-ui-fabric-react": "7.134.1",
|
"office-ui-fabric-react": "7.121.10",
|
||||||
"p-retry": "4.2.0",
|
"p-retry": "4.2.0",
|
||||||
"plotly.js-cartesian-dist-min": "1.52.3",
|
"plotly.js-cartesian-dist-min": "1.52.3",
|
||||||
"promise-polyfill": "8.1.0",
|
"promise-polyfill": "8.1.0",
|
||||||
"promise.prototype.finally": "3.1.0",
|
"promise.prototype.finally": "3.1.0",
|
||||||
"q": "1.5.1",
|
"q": "1.5.1",
|
||||||
"react": "16.13.1",
|
"react": "16.9.0",
|
||||||
"react-animate-height": "2.0.8",
|
"react-animate-height": "2.0.8",
|
||||||
"react-dnd": "9.4.0",
|
"react-dnd": "9.4.0",
|
||||||
"react-dnd-html5-backend": "9.4.0",
|
"react-dnd-html5-backend": "9.4.0",
|
||||||
@@ -118,8 +118,8 @@
|
|||||||
"@types/text-encoding": "0.0.33",
|
"@types/text-encoding": "0.0.33",
|
||||||
"@types/underscore": "1.7.36",
|
"@types/underscore": "1.7.36",
|
||||||
"@types/webfontloader": "1.6.29",
|
"@types/webfontloader": "1.6.29",
|
||||||
"@typescript-eslint/eslint-plugin": "4.0.1",
|
"@typescript-eslint/eslint-plugin": "3.2.0",
|
||||||
"@typescript-eslint/parser": "4.0.1",
|
"@typescript-eslint/parser": "3.2.0",
|
||||||
"adal-angular": "1.0.15",
|
"adal-angular": "1.0.15",
|
||||||
"axe-puppeteer": "1.1.0",
|
"axe-puppeteer": "1.1.0",
|
||||||
"babel-jest": "24.9.0",
|
"babel-jest": "24.9.0",
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
"enzyme": "3.10.0",
|
"enzyme": "3.10.0",
|
||||||
"enzyme-adapter-react-16": "1.15.1",
|
"enzyme-adapter-react-16": "1.15.1",
|
||||||
"enzyme-to-json": "3.4.3",
|
"enzyme-to-json": "3.4.3",
|
||||||
"eslint": "7.8.1",
|
"eslint": "7.3.1",
|
||||||
"eslint-cli": "1.1.1",
|
"eslint-cli": "1.1.1",
|
||||||
"eslint-plugin-no-null": "1.0.2",
|
"eslint-plugin-no-null": "1.0.2",
|
||||||
"eslint-plugin-prefer-arrow": "1.2.2",
|
"eslint-plugin-prefer-arrow": "1.2.2",
|
||||||
@@ -160,12 +160,11 @@
|
|||||||
"rimraf": "3.0.0",
|
"rimraf": "3.0.0",
|
||||||
"sinon": "3.2.1",
|
"sinon": "3.2.1",
|
||||||
"style-loader": "0.23.0",
|
"style-loader": "0.23.0",
|
||||||
"swr": "0.3.2",
|
|
||||||
"terser-webpack-plugin": "3.0.5",
|
"terser-webpack-plugin": "3.0.5",
|
||||||
"ts-loader": "6.2.2",
|
"ts-loader": "6.2.2",
|
||||||
"tslint": "5.11.0",
|
"tslint": "5.11.0",
|
||||||
"tslint-microsoft-contrib": "6.0.0",
|
"tslint-microsoft-contrib": "6.0.0",
|
||||||
"typescript": "4.0.2",
|
"typescript": "3.9.6",
|
||||||
"url-loader": "1.1.1",
|
"url-loader": "1.1.1",
|
||||||
"webpack": "4.43.0",
|
"webpack": "4.43.0",
|
||||||
"webpack-bundle-analyzer": "3.6.1",
|
"webpack-bundle-analyzer": "3.6.1",
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
"offerThroughput": 400,
|
"offerThroughput": 400,
|
||||||
"databaseLevelThroughput": false,
|
"databaseLevelThroughput": false,
|
||||||
"collectionId": "Persons",
|
"collectionId": "Persons",
|
||||||
"createNewDatabase": true,
|
"rupmEnabled": false,
|
||||||
"partitionKey": { "kind": "Hash", "paths": ["/firstname"], "version": 1 },
|
"partitionKey": { "kind": "Hash", "paths": ["/firstname"] },
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{
|
||||||
"firstname": "Eva",
|
"firstname": "Eva",
|
||||||
@@ -23,4 +23,4 @@
|
|||||||
"age": 23
|
"age": 23
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -134,7 +134,6 @@ export class Features {
|
|||||||
public static readonly enableAutoPilotV2 = "enableautopilotv2";
|
public static readonly enableAutoPilotV2 = "enableautopilotv2";
|
||||||
public static readonly ttl90Days = "ttl90days";
|
public static readonly ttl90Days = "ttl90days";
|
||||||
public static readonly enableRightPanelV2 = "enablerightpanelv2";
|
public static readonly enableRightPanelV2 = "enablerightpanelv2";
|
||||||
public static readonly enableSDKoperations = "enablesdkoperations";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AfecFeatures {
|
export class AfecFeatures {
|
||||||
|
|||||||
@@ -6,14 +6,19 @@ import * as ViewModels from "../Contracts/ViewModels";
|
|||||||
import Q from "q";
|
import Q from "q";
|
||||||
import {
|
import {
|
||||||
ConflictDefinition,
|
ConflictDefinition,
|
||||||
|
ContainerDefinition,
|
||||||
|
ContainerResponse,
|
||||||
|
DatabaseResponse,
|
||||||
FeedOptions,
|
FeedOptions,
|
||||||
ItemDefinition,
|
ItemDefinition,
|
||||||
|
PartitionKeyDefinition,
|
||||||
QueryIterator,
|
QueryIterator,
|
||||||
Resource,
|
Resource,
|
||||||
TriggerDefinition,
|
TriggerDefinition
|
||||||
OfferDefinition
|
|
||||||
} from "@azure/cosmos";
|
} from "@azure/cosmos";
|
||||||
|
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
|
||||||
import { client } from "./CosmosClient";
|
import { client } from "./CosmosClient";
|
||||||
|
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
|
||||||
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
|
||||||
import { sendCachedDataMessage } from "./MessageHandler";
|
import { sendCachedDataMessage } from "./MessageHandler";
|
||||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
@@ -197,6 +202,23 @@ export function getPartitionKeyHeader(partitionKeyDefinition: DataModels.Partiti
|
|||||||
return [partitionKeyValue];
|
return [partitionKeyValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updateCollection(
|
||||||
|
databaseId: string,
|
||||||
|
collectionId: string,
|
||||||
|
newCollection: DataModels.Collection,
|
||||||
|
options: any = {}
|
||||||
|
): Q.Promise<DataModels.Collection> {
|
||||||
|
return Q(
|
||||||
|
client()
|
||||||
|
.database(databaseId)
|
||||||
|
.container(collectionId)
|
||||||
|
.replace(newCollection as ContainerDefinition, options)
|
||||||
|
.then(async (response: ContainerResponse) => {
|
||||||
|
return refreshCachedResources().then(() => response.resource as DataModels.Collection);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function updateDocument(
|
export function updateDocument(
|
||||||
collection: ViewModels.CollectionBase,
|
collection: ViewModels.CollectionBase,
|
||||||
documentId: DocumentId,
|
documentId: DocumentId,
|
||||||
@@ -222,8 +244,7 @@ export function updateOffer(
|
|||||||
return Q(
|
return Q(
|
||||||
client()
|
client()
|
||||||
.offer(offer.id)
|
.offer(offer.id)
|
||||||
// TODO Remove casting when SDK types are fixed (https://github.com/Azure/azure-sdk-for-js/issues/10660)
|
.replace(newOffer, options)
|
||||||
.replace((newOffer as unknown) as OfferDefinition, options)
|
|
||||||
.then(response => {
|
.then(response => {
|
||||||
return Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => response.resource);
|
return Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => response.resource);
|
||||||
})
|
})
|
||||||
@@ -433,10 +454,6 @@ export function readCollectionQuotaInfo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function readOffers(options: any): Q.Promise<DataModels.Offer[]> {
|
export function readOffers(options: any): Q.Promise<DataModels.Offer[]> {
|
||||||
if (options.isServerless) {
|
|
||||||
return Q([]); // Reading offers is not supported for serverless accounts
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (configContext.platform === Platform.Portal) {
|
if (configContext.platform === Platform.Portal) {
|
||||||
return sendCachedDataMessage<DataModels.Offer[]>(MessageTypes.AllOffers, [
|
return sendCachedDataMessage<DataModels.Offer[]>(MessageTypes.AllOffers, [
|
||||||
@@ -452,13 +469,6 @@ export function readOffers(options: any): Q.Promise<DataModels.Offer[]> {
|
|||||||
.offers.readAll()
|
.offers.readAll()
|
||||||
.fetchAll()
|
.fetchAll()
|
||||||
.then(response => response.resources)
|
.then(response => response.resources)
|
||||||
.catch(error => {
|
|
||||||
// This should be removed when we can correctly identify if an account is serverless when connected using connection string too.
|
|
||||||
if (error.message.includes("Reading or replacing offers is not supported for serverless accounts")) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,6 +487,89 @@ export function readOffer(requestedResource: DataModels.Offer, options: any): Q.
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getOrCreateDatabaseAndCollection(
|
||||||
|
request: DataModels.CreateDatabaseAndCollectionRequest,
|
||||||
|
options: any
|
||||||
|
): Q.Promise<DataModels.Collection> {
|
||||||
|
const databaseOptions: any = options && _.omit(options, "sharedOfferThroughput");
|
||||||
|
const {
|
||||||
|
databaseId,
|
||||||
|
databaseLevelThroughput,
|
||||||
|
collectionId,
|
||||||
|
partitionKey,
|
||||||
|
indexingPolicy,
|
||||||
|
uniqueKeyPolicy,
|
||||||
|
offerThroughput,
|
||||||
|
analyticalStorageTtl,
|
||||||
|
hasAutoPilotV2FeatureFlag
|
||||||
|
} = request;
|
||||||
|
|
||||||
|
const createBody: DatabaseRequest = {
|
||||||
|
id: databaseId
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: replace when SDK support autopilot
|
||||||
|
const initialHeaders = request.autoPilot
|
||||||
|
? !hasAutoPilotV2FeatureFlag
|
||||||
|
? {
|
||||||
|
[Constants.HttpHeaders.autoPilotThroughputSDK]: JSON.stringify({
|
||||||
|
maxThroughput: request.autoPilot.maxThroughput
|
||||||
|
})
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
[Constants.HttpHeaders.autoPilotTier]: request.autoPilot.autopilotTier
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
if (databaseLevelThroughput) {
|
||||||
|
if (request.autoPilot) {
|
||||||
|
databaseOptions.initialHeaders = initialHeaders;
|
||||||
|
}
|
||||||
|
createBody.throughput = offerThroughput;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Q(
|
||||||
|
client()
|
||||||
|
.databases.createIfNotExists(createBody, databaseOptions)
|
||||||
|
.then(response => {
|
||||||
|
return response.database.containers.create(
|
||||||
|
{
|
||||||
|
id: collectionId,
|
||||||
|
partitionKey: (partitionKey || undefined) as PartitionKeyDefinition,
|
||||||
|
indexingPolicy: indexingPolicy ? indexingPolicy : undefined,
|
||||||
|
uniqueKeyPolicy: uniqueKeyPolicy ? uniqueKeyPolicy : undefined,
|
||||||
|
analyticalStorageTtl: analyticalStorageTtl,
|
||||||
|
throughput: databaseLevelThroughput || request.autoPilot ? undefined : offerThroughput
|
||||||
|
} as ContainerRequest, // TODO: remove cast when https://github.com/Azure/azure-cosmos-js/issues/423 is fixed
|
||||||
|
{
|
||||||
|
initialHeaders: databaseLevelThroughput ? undefined : initialHeaders
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.then(containerResponse => containerResponse.resource as DataModels.Collection)
|
||||||
|
.finally(() => refreshCachedResources(options))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDatabase(
|
||||||
|
request: DataModels.CreateDatabaseRequest,
|
||||||
|
options: any
|
||||||
|
): Q.Promise<DataModels.Database> {
|
||||||
|
var deferred = Q.defer<DataModels.Database>();
|
||||||
|
|
||||||
|
_createDatabase(request, options).then(
|
||||||
|
(createdDatabase: DataModels.Database) => {
|
||||||
|
refreshCachedOffers().then(() => {
|
||||||
|
deferred.resolve(createdDatabase);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
_createDatabaseError => {
|
||||||
|
deferred.reject(_createDatabaseError);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
export function refreshCachedOffers(): Q.Promise<void> {
|
export function refreshCachedOffers(): Q.Promise<void> {
|
||||||
if (configContext.platform === Platform.Portal) {
|
if (configContext.platform === Platform.Portal) {
|
||||||
return sendCachedDataMessage(MessageTypes.RefreshOffers, []);
|
return sendCachedDataMessage(MessageTypes.RefreshOffers, []);
|
||||||
@@ -505,3 +598,33 @@ export function queryConflicts(
|
|||||||
.conflicts.query(query, options);
|
.conflicts.query(query, options);
|
||||||
return Q(documentsIterator);
|
return Q(documentsIterator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _createDatabase(request: DataModels.CreateDatabaseRequest, options: any = {}): Q.Promise<DataModels.Database> {
|
||||||
|
const { databaseId, databaseLevelThroughput, offerThroughput, autoPilot, hasAutoPilotV2FeatureFlag } = request;
|
||||||
|
const createBody: DatabaseRequest = { id: databaseId };
|
||||||
|
const databaseOptions: any = options && _.omit(options, "sharedOfferThroughput");
|
||||||
|
// TODO: replace when SDK support autopilot
|
||||||
|
const initialHeaders = autoPilot
|
||||||
|
? !hasAutoPilotV2FeatureFlag
|
||||||
|
? {
|
||||||
|
[Constants.HttpHeaders.autoPilotThroughputSDK]: JSON.stringify({ maxThroughput: autoPilot.maxThroughput })
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
[Constants.HttpHeaders.autoPilotTier]: autoPilot.autopilotTier
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
if (!!databaseLevelThroughput) {
|
||||||
|
if (autoPilot) {
|
||||||
|
databaseOptions.initialHeaders = initialHeaders;
|
||||||
|
}
|
||||||
|
createBody.throughput = offerThroughput;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Q(
|
||||||
|
client()
|
||||||
|
.databases.create(createBody, databaseOptions)
|
||||||
|
.then((response: DatabaseResponse) => {
|
||||||
|
return refreshCachedResources(databaseOptions).then(() => response.resource);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as Constants from "./Constants";
|
import * as Constants from "./Constants";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
|
import * as ErrorParserUtility from "./ErrorParserUtility";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import Q from "q";
|
import Q from "q";
|
||||||
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||||
@@ -265,6 +266,42 @@ export function readDocument(collection: ViewModels.CollectionBase, documentId:
|
|||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updateCollection(
|
||||||
|
databaseId: string,
|
||||||
|
collection: ViewModels.Collection,
|
||||||
|
newCollection: DataModels.Collection
|
||||||
|
): Q.Promise<DataModels.Collection> {
|
||||||
|
var deferred = Q.defer<any>();
|
||||||
|
const id = NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.InProgress,
|
||||||
|
`Updating container ${collection.id()}`
|
||||||
|
);
|
||||||
|
DataAccessUtilityBase.updateCollection(databaseId, collection.id(), newCollection)
|
||||||
|
.then(
|
||||||
|
(replacedCollection: DataModels.Collection) => {
|
||||||
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.Info,
|
||||||
|
`Successfully updated container ${collection.id()}`
|
||||||
|
);
|
||||||
|
deferred.resolve(replacedCollection);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.Error,
|
||||||
|
`Failed to update container ${collection.id()}: ${JSON.stringify(error)}`
|
||||||
|
);
|
||||||
|
Logger.logError(JSON.stringify(error), "UpdateCollection", error.code);
|
||||||
|
sendNotificationForError(error);
|
||||||
|
deferred.reject(error);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.finally(() => {
|
||||||
|
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
export function updateDocument(
|
export function updateDocument(
|
||||||
collection: ViewModels.CollectionBase,
|
collection: ViewModels.CollectionBase,
|
||||||
documentId: DocumentId,
|
documentId: DocumentId,
|
||||||
@@ -855,3 +892,70 @@ export function readOffer(
|
|||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getOrCreateDatabaseAndCollection(
|
||||||
|
request: DataModels.CreateDatabaseAndCollectionRequest,
|
||||||
|
options: any = {}
|
||||||
|
): Q.Promise<DataModels.Collection> {
|
||||||
|
const deferred: Q.Deferred<DataModels.Collection> = Q.defer<DataModels.Collection>();
|
||||||
|
const id = NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.InProgress,
|
||||||
|
`Creating a new container ${request.collectionId} for database ${request.databaseId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
DataAccessUtilityBase.getOrCreateDatabaseAndCollection(request, options)
|
||||||
|
.then(
|
||||||
|
(collection: DataModels.Collection) => {
|
||||||
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.Info,
|
||||||
|
`Successfully created container ${request.collectionId}`
|
||||||
|
);
|
||||||
|
deferred.resolve(collection);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
const sanitizedError = ErrorParserUtility.replaceKnownError(JSON.stringify(error));
|
||||||
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.Error,
|
||||||
|
`Error while creating container ${request.collectionId}:\n ${sanitizedError}`
|
||||||
|
);
|
||||||
|
sendNotificationForError(error);
|
||||||
|
deferred.reject(error);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDatabase(
|
||||||
|
request: DataModels.CreateDatabaseRequest,
|
||||||
|
options: any = {}
|
||||||
|
): Q.Promise<DataModels.Database> {
|
||||||
|
const deferred: Q.Deferred<DataModels.Database> = Q.defer<DataModels.Database>();
|
||||||
|
const id = NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.InProgress,
|
||||||
|
`Creating a new database ${request.databaseId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
DataAccessUtilityBase.createDatabase(request, options)
|
||||||
|
.then(
|
||||||
|
(database: DataModels.Database) => {
|
||||||
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.Info,
|
||||||
|
`Successfully created database ${request.databaseId}`
|
||||||
|
);
|
||||||
|
deferred.resolve(database);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.Error,
|
||||||
|
`Error while creating database ${request.databaseId}:\n ${JSON.stringify(error)}`
|
||||||
|
);
|
||||||
|
sendNotificationForError(error);
|
||||||
|
deferred.reject(error);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,14 @@ import { Collection } from "../Contracts/ViewModels";
|
|||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
|
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
|
||||||
import { updateUserContext } from "../UserContext";
|
import { updateUserContext } from "../UserContext";
|
||||||
import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
|
import {
|
||||||
|
deleteDocument,
|
||||||
|
getEndpoint,
|
||||||
|
queryDocuments,
|
||||||
|
readDocument,
|
||||||
|
updateDocument,
|
||||||
|
_createMongoCollectionWithARM
|
||||||
|
} from "./MongoProxyClient";
|
||||||
jest.mock("../ResourceProvider/ResourceProviderClient.ts");
|
jest.mock("../ResourceProvider/ResourceProviderClient.ts");
|
||||||
|
|
||||||
const databaseId = "testDB";
|
const databaseId = "testDB";
|
||||||
@@ -253,4 +260,58 @@ describe("MongoProxyClient", () => {
|
|||||||
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer");
|
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("createMongoCollectionWithARM", () => {
|
||||||
|
it("should create a collection with autopilot when autopilot is selected + shared throughput is false", () => {
|
||||||
|
const resourceProviderClientPutAsyncSpy = jest.spyOn(ResourceProviderClient.prototype, "putAsync");
|
||||||
|
const properties = {
|
||||||
|
pk: "state",
|
||||||
|
coll: "abc-collection",
|
||||||
|
cd: true,
|
||||||
|
db: "a1-db",
|
||||||
|
st: false,
|
||||||
|
sid: "a2",
|
||||||
|
rg: "c1",
|
||||||
|
dba: "main",
|
||||||
|
is: false
|
||||||
|
};
|
||||||
|
_createMongoCollectionWithARM("management.azure.com", properties, { "x-ms-cosmos-offer-autopilot-tier": "1" });
|
||||||
|
expect(resourceProviderClientPutAsyncSpy).toHaveBeenCalledWith(
|
||||||
|
"subscriptions/a2/resourceGroups/c1/providers/Microsoft.DocumentDB/databaseAccounts/foo/mongodbDatabases/a1-db/collections/abc-collection",
|
||||||
|
"2020-04-01",
|
||||||
|
{
|
||||||
|
properties: {
|
||||||
|
options: { "x-ms-cosmos-offer-autopilot-tier": "1" },
|
||||||
|
resource: { id: "abc-collection" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("should create a collection with provisioned throughput when provisioned throughput is selected + shared throughput is false", () => {
|
||||||
|
const resourceProviderClientPutAsyncSpy = jest.spyOn(ResourceProviderClient.prototype, "putAsync");
|
||||||
|
const properties = {
|
||||||
|
pk: "state",
|
||||||
|
coll: "abc-collection",
|
||||||
|
cd: true,
|
||||||
|
db: "a1-db",
|
||||||
|
st: false,
|
||||||
|
sid: "a2",
|
||||||
|
rg: "c1",
|
||||||
|
dba: "main",
|
||||||
|
is: false,
|
||||||
|
offerThroughput: 400
|
||||||
|
};
|
||||||
|
_createMongoCollectionWithARM("management.azure.com", properties, undefined);
|
||||||
|
expect(resourceProviderClientPutAsyncSpy).toHaveBeenCalledWith(
|
||||||
|
"subscriptions/a2/resourceGroups/c1/providers/Microsoft.DocumentDB/databaseAccounts/foo/mongodbDatabases/a1-db/collections/abc-collection",
|
||||||
|
"2020-04-01",
|
||||||
|
{
|
||||||
|
properties: {
|
||||||
|
options: { throughput: "400" },
|
||||||
|
resource: { id: "abc-collection" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
|
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
|
||||||
import queryString from "querystring";
|
import queryString from "querystring";
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
|
import * as Constants from "../Common/Constants";
|
||||||
|
import * as DataExplorerConstants from "../Common/Constants";
|
||||||
import { configContext } from "../ConfigContext";
|
import { configContext } from "../ConfigContext";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
import { Collection } from "../Contracts/ViewModels";
|
import { Collection } from "../Contracts/ViewModels";
|
||||||
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
|
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
|
||||||
|
import { AddDbUtilities } from "../Shared/AddDatabaseUtility";
|
||||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||||
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
@@ -281,35 +285,43 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createMongoCollectionWithProxy(
|
export function createMongoCollectionWithProxy(
|
||||||
params: DataModels.CreateCollectionParams
|
databaseId: string,
|
||||||
|
collectionId: string,
|
||||||
|
offerThroughput: number,
|
||||||
|
shardKey: string,
|
||||||
|
createDatabase: boolean,
|
||||||
|
sharedThroughput: boolean,
|
||||||
|
isSharded: boolean,
|
||||||
|
autopilotOptions?: DataModels.RpOptions
|
||||||
): Promise<DataModels.Collection> {
|
): Promise<DataModels.Collection> {
|
||||||
const databaseAccount = userContext.databaseAccount;
|
const databaseAccount = userContext.databaseAccount;
|
||||||
const shardKey: string = params.partitionKey?.paths[0];
|
const params: DataModels.MongoParameters = {
|
||||||
const mongoParams: DataModels.MongoParameters = {
|
|
||||||
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
|
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
|
||||||
db: params.databaseId,
|
db: databaseId,
|
||||||
coll: params.collectionId,
|
coll: collectionId,
|
||||||
pk: shardKey,
|
pk: shardKey,
|
||||||
offerThroughput: params.offerThroughput,
|
offerThroughput,
|
||||||
cd: params.createNewDatabase,
|
cd: createDatabase,
|
||||||
st: params.databaseLevelThroughput,
|
st: sharedThroughput,
|
||||||
is: !!shardKey,
|
is: isSharded,
|
||||||
rid: "",
|
rid: "",
|
||||||
rtype: "colls",
|
rtype: "colls",
|
||||||
sid: userContext.subscriptionId,
|
sid: userContext.subscriptionId,
|
||||||
rg: userContext.resourceGroup,
|
rg: userContext.resourceGroup,
|
||||||
dba: databaseAccount.name,
|
dba: databaseAccount.name,
|
||||||
isAutoPilot: !!params.autoPilotMaxThroughput,
|
isAutoPilot: false
|
||||||
autoPilotThroughput: params.autoPilotMaxThroughput?.toString()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (autopilotOptions) {
|
||||||
|
params.isAutoPilot = true;
|
||||||
|
params.autoPilotTier = autopilotOptions[Constants.HttpHeaders.autoPilotTier] as string;
|
||||||
|
}
|
||||||
|
|
||||||
const endpoint = getEndpoint(databaseAccount);
|
const endpoint = getEndpoint(databaseAccount);
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(
|
.fetch(
|
||||||
`${endpoint}/createCollection?${queryString.stringify(
|
`${endpoint}/createCollection?${queryString.stringify((params as unknown) as queryString.ParsedUrlQueryInput)}`,
|
||||||
(mongoParams as unknown) as queryString.ParsedUrlQueryInput
|
|
||||||
)}`,
|
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -323,10 +335,52 @@ export function createMongoCollectionWithProxy(
|
|||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
return errorHandling(response, "creating collection", mongoParams);
|
return errorHandling(response, "creating collection", params);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createMongoCollectionWithARM(
|
||||||
|
armEndpoint: string,
|
||||||
|
databaseId: string,
|
||||||
|
analyticalStorageTtl: number,
|
||||||
|
collectionId: string,
|
||||||
|
offerThroughput: number,
|
||||||
|
shardKey: string,
|
||||||
|
createDatabase: boolean,
|
||||||
|
sharedThroughput: boolean,
|
||||||
|
isSharded: boolean,
|
||||||
|
additionalOptions?: DataModels.RpOptions
|
||||||
|
): Promise<DataModels.CreateCollectionWithRpResponse> {
|
||||||
|
const databaseAccount = userContext.databaseAccount;
|
||||||
|
const params: DataModels.MongoParameters = {
|
||||||
|
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
|
||||||
|
db: databaseId,
|
||||||
|
coll: collectionId,
|
||||||
|
pk: shardKey,
|
||||||
|
offerThroughput,
|
||||||
|
cd: createDatabase,
|
||||||
|
st: sharedThroughput,
|
||||||
|
is: isSharded,
|
||||||
|
rid: "",
|
||||||
|
rtype: "colls",
|
||||||
|
sid: userContext.subscriptionId,
|
||||||
|
rg: userContext.resourceGroup,
|
||||||
|
dba: databaseAccount.name,
|
||||||
|
analyticalStorageTtl
|
||||||
|
};
|
||||||
|
|
||||||
|
if (createDatabase) {
|
||||||
|
return AddDbUtilities.createMongoDatabaseWithARM(
|
||||||
|
armEndpoint,
|
||||||
|
params,
|
||||||
|
sharedThroughput ? additionalOptions : {}
|
||||||
|
).then(() => {
|
||||||
|
return _createMongoCollectionWithARM(armEndpoint, params, sharedThroughput ? {} : additionalOptions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return _createMongoCollectionWithARM(armEndpoint, params, additionalOptions);
|
||||||
|
}
|
||||||
|
|
||||||
export function getEndpoint(databaseAccount: DataModels.DatabaseAccount): string {
|
export function getEndpoint(databaseAccount: DataModels.DatabaseAccount): string {
|
||||||
const serverId = window.dataExplorer.serverId();
|
const serverId = window.dataExplorer.serverId();
|
||||||
const extensionEndpoint = window.dataExplorer.extensionEndpoint();
|
const extensionEndpoint = window.dataExplorer.extensionEndpoint();
|
||||||
@@ -359,3 +413,46 @@ async function errorHandling(response: Response, action: string, params: unknown
|
|||||||
export function getARMCreateCollectionEndpoint(params: DataModels.MongoParameters): string {
|
export function getARMCreateCollectionEndpoint(params: DataModels.MongoParameters): string {
|
||||||
return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${userContext.databaseAccount.name}/mongodbDatabases/${params.db}/collections/${params.coll}`;
|
return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${userContext.databaseAccount.name}/mongodbDatabases/${params.db}/collections/${params.coll}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function _createMongoCollectionWithARM(
|
||||||
|
armEndpoint: string,
|
||||||
|
params: DataModels.MongoParameters,
|
||||||
|
rpOptions: DataModels.RpOptions
|
||||||
|
): Promise<DataModels.CreateCollectionWithRpResponse> {
|
||||||
|
const rpPayloadToCreateCollection: DataModels.MongoCreationRequest = {
|
||||||
|
properties: {
|
||||||
|
resource: {
|
||||||
|
id: params.coll
|
||||||
|
},
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (params.is) {
|
||||||
|
rpPayloadToCreateCollection.properties.resource["shardKey"] = { [params.pk]: "Hash" };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!params.st) {
|
||||||
|
if (rpOptions) {
|
||||||
|
rpPayloadToCreateCollection.properties.options = rpOptions;
|
||||||
|
} else {
|
||||||
|
rpPayloadToCreateCollection.properties.options["throughput"] =
|
||||||
|
params.offerThroughput && params.offerThroughput.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.analyticalStorageTtl) {
|
||||||
|
rpPayloadToCreateCollection.properties.resource.analyticalStorageTtl = params.analyticalStorageTtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new ResourceProviderClient<DataModels.CreateCollectionWithRpResponse>(armEndpoint).putAsync(
|
||||||
|
getARMCreateCollectionEndpoint(params),
|
||||||
|
DataExplorerConstants.ArmApiVersions.publicVersion,
|
||||||
|
rpPayloadToCreateCollection
|
||||||
|
);
|
||||||
|
} catch (response) {
|
||||||
|
errorHandling(response, "creating collection", undefined);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
import "jquery";
|
|
||||||
import * as Q from "q";
|
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
|
||||||
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
|
||||||
import { userContext } from "../UserContext";
|
|
||||||
|
|
||||||
export class NotificationsClientBase {
|
|
||||||
private _extensionEndpoint: string;
|
|
||||||
private _notificationsApiSuffix: string;
|
|
||||||
|
|
||||||
protected constructor(notificationsApiSuffix: string) {
|
|
||||||
this._notificationsApiSuffix = notificationsApiSuffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
public fetchNotifications(): Q.Promise<DataModels.Notification[]> {
|
|
||||||
const deferred: Q.Deferred<DataModels.Notification[]> = Q.defer<DataModels.Notification[]>();
|
|
||||||
const databaseAccount = userContext.databaseAccount;
|
|
||||||
const subscriptionId = userContext.subscriptionId;
|
|
||||||
const resourceGroup = userContext.resourceGroup;
|
|
||||||
const url = `${this._extensionEndpoint}${this._notificationsApiSuffix}?accountName=${databaseAccount.name}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;
|
|
||||||
const authorizationHeader: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
|
||||||
const headers: any = {};
|
|
||||||
headers[authorizationHeader.header] = authorizationHeader.token;
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: url,
|
|
||||||
type: "GET",
|
|
||||||
headers: headers,
|
|
||||||
cache: false
|
|
||||||
}).then(
|
|
||||||
(notifications: DataModels.Notification[], textStatus: string, xhr: JQueryXHR<any>) => {
|
|
||||||
deferred.resolve(notifications);
|
|
||||||
},
|
|
||||||
(xhr: JQueryXHR<any>, textStatus: string, error: any) => {
|
|
||||||
deferred.reject(xhr.responseText);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
public setExtensionEndpoint(extensionEndpoint: string): void {
|
|
||||||
this._extensionEndpoint = extensionEndpoint;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,8 +10,13 @@ import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
|||||||
import { QueryUtils } from "../Utils/QueryUtils";
|
import { QueryUtils } from "../Utils/QueryUtils";
|
||||||
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { createDocument, deleteDocument, queryDocuments, queryDocumentsPage } from "./DocumentClientUtilityBase";
|
import {
|
||||||
import { createCollection } from "./dataAccess/createCollection";
|
createDocument,
|
||||||
|
deleteDocument,
|
||||||
|
getOrCreateDatabaseAndCollection,
|
||||||
|
queryDocuments,
|
||||||
|
queryDocumentsPage
|
||||||
|
} from "./DocumentClientUtilityBase";
|
||||||
import * as ErrorParserUtility from "./ErrorParserUtility";
|
import * as ErrorParserUtility from "./ErrorParserUtility";
|
||||||
import * as Logger from "./Logger";
|
import * as Logger from "./Logger";
|
||||||
|
|
||||||
@@ -36,13 +41,12 @@ export class QueriesClient {
|
|||||||
ConsoleDataType.InProgress,
|
ConsoleDataType.InProgress,
|
||||||
"Setting up account for saving queries"
|
"Setting up account for saving queries"
|
||||||
);
|
);
|
||||||
return createCollection({
|
return getOrCreateDatabaseAndCollection({
|
||||||
collectionId: SavedQueries.CollectionName,
|
collectionId: SavedQueries.CollectionName,
|
||||||
createNewDatabase: true,
|
|
||||||
databaseId: SavedQueries.DatabaseName,
|
databaseId: SavedQueries.DatabaseName,
|
||||||
partitionKey: QueriesClient.PartitionKey,
|
partitionKey: QueriesClient.PartitionKey,
|
||||||
offerThroughput: SavedQueries.OfferThroughput,
|
offerThroughput: SavedQueries.OfferThroughput,
|
||||||
databaseLevelThroughput: false
|
databaseLevelThroughput: undefined
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
(collection: DataModels.Collection) => {
|
(collection: DataModels.Collection) => {
|
||||||
|
|||||||
@@ -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,374 +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-cosmos-db/types";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { createMongoCollectionWithProxy } from "../MongoProxyClient";
|
|
||||||
import {
|
|
||||||
createUpdateSqlContainer,
|
|
||||||
getSqlContainer
|
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/sqlResources";
|
|
||||||
import {
|
|
||||||
createUpdateCassandraTable,
|
|
||||||
getCassandraTable
|
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/cassandraResources";
|
|
||||||
import {
|
|
||||||
createUpdateMongoDBCollection,
|
|
||||||
getMongoDBCollection
|
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/mongoDBResources";
|
|
||||||
import {
|
|
||||||
createUpdateGremlinGraph,
|
|
||||||
getGremlinGraph
|
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/gremlinResources";
|
|
||||||
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/tableResources";
|
|
||||||
import { logConsoleProgress, logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
|
||||||
import { createDatabase } from "./createDatabase";
|
|
||||||
|
|
||||||
export const createCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
|
||||||
let collection: DataModels.Collection;
|
|
||||||
const clearMessage = logConsoleProgress(
|
|
||||||
`Creating a new container ${params.collectionId} for database ${params.databaseId}`
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
|
||||||
if (params.createNewDatabase) {
|
|
||||||
const createDatabaseParams: DataModels.CreateDatabaseParams = {
|
|
||||||
autoPilotMaxThroughput: params.autoPilotMaxThroughput,
|
|
||||||
databaseId: params.databaseId,
|
|
||||||
databaseLevelThroughput: params.databaseLevelThroughput,
|
|
||||||
offerThroughput: params.offerThroughput
|
|
||||||
};
|
|
||||||
await createDatabase(createDatabaseParams);
|
|
||||||
}
|
|
||||||
collection = await createCollectionWithARM(params);
|
|
||||||
} else if (userContext.defaultExperience === DefaultAccountExperienceType.MongoDB) {
|
|
||||||
collection = await createMongoCollectionWithProxy(params);
|
|
||||||
} else {
|
|
||||||
collection = await createCollectionWithSDK(params);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const sanitizedError = ErrorParserUtility.replaceKnownError(JSON.stringify(error));
|
|
||||||
logConsoleError(`Error while creating container ${params.collectionId}:\n ${sanitizedError}`);
|
|
||||||
logError(JSON.stringify(error), "CreateCollection", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
clearMessage();
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
logConsoleInfo(`Successfully created container ${params.collectionId}`);
|
|
||||||
await refreshCachedResources();
|
|
||||||
clearMessage();
|
|
||||||
return collection;
|
|
||||||
};
|
|
||||||
|
|
||||||
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
|
||||||
const defaultExperience = userContext.defaultExperience;
|
|
||||||
switch (defaultExperience) {
|
|
||||||
case DefaultAccountExperienceType.DocumentDB:
|
|
||||||
return createSqlContainer(params);
|
|
||||||
case DefaultAccountExperienceType.MongoDB:
|
|
||||||
return createMongoCollection(params);
|
|
||||||
case DefaultAccountExperienceType.Cassandra:
|
|
||||||
return createCassandraTable(params);
|
|
||||||
case DefaultAccountExperienceType.Graph:
|
|
||||||
return createGraph(params);
|
|
||||||
case DefaultAccountExperienceType.Table:
|
|
||||||
return createTable(params);
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const createSqlContainer = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
|
||||||
try {
|
|
||||||
const getResponse = await getSqlContainer(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId,
|
|
||||||
params.collectionId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create container failed: container with id ${params.collectionId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
|
||||||
const resource: ARMTypes.SqlContainerResource = {
|
|
||||||
id: params.collectionId
|
|
||||||
};
|
|
||||||
if (params.analyticalStorageTtl) {
|
|
||||||
resource.analyticalStorageTtl = params.analyticalStorageTtl;
|
|
||||||
}
|
|
||||||
if (params.indexingPolicy) {
|
|
||||||
resource.indexingPolicy = params.indexingPolicy;
|
|
||||||
}
|
|
||||||
if (params.partitionKey) {
|
|
||||||
resource.partitionKey = params.partitionKey;
|
|
||||||
}
|
|
||||||
if (params.uniqueKeyPolicy) {
|
|
||||||
resource.uniqueKeyPolicy = params.uniqueKeyPolicy;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rpPayload: ARMTypes.SqlDatabaseCreateUpdateParameters = {
|
|
||||||
properties: {
|
|
||||||
resource,
|
|
||||||
options
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const createResponse = await createUpdateSqlContainer(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId,
|
|
||||||
params.collectionId,
|
|
||||||
rpPayload
|
|
||||||
);
|
|
||||||
return createResponse && (createResponse.properties.resource as DataModels.Collection);
|
|
||||||
};
|
|
||||||
|
|
||||||
const createMongoCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
|
||||||
try {
|
|
||||||
const getResponse = await getMongoDBCollection(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId,
|
|
||||||
params.collectionId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create collection failed: collection with id ${params.collectionId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
|
||||||
const resource: ARMTypes.MongoDBCollectionResource = {
|
|
||||||
id: params.collectionId
|
|
||||||
};
|
|
||||||
if (params.analyticalStorageTtl) {
|
|
||||||
resource.analyticalStorageTtl = params.analyticalStorageTtl;
|
|
||||||
}
|
|
||||||
if (params.partitionKey) {
|
|
||||||
const partitionKeyPath: string = params.partitionKey.paths[0];
|
|
||||||
resource.shardKey = { [partitionKeyPath]: "Hash" };
|
|
||||||
}
|
|
||||||
|
|
||||||
const rpPayload: ARMTypes.MongoDBCollectionCreateUpdateParameters = {
|
|
||||||
properties: {
|
|
||||||
resource,
|
|
||||||
options
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const createResponse = await createUpdateMongoDBCollection(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId,
|
|
||||||
params.collectionId,
|
|
||||||
rpPayload
|
|
||||||
);
|
|
||||||
return createResponse && (createResponse.properties.resource as DataModels.Collection);
|
|
||||||
};
|
|
||||||
|
|
||||||
const createCassandraTable = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
|
||||||
try {
|
|
||||||
const getResponse = await getCassandraTable(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId,
|
|
||||||
params.collectionId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create table failed: table with id ${params.collectionId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
|
||||||
const resource: ARMTypes.CassandraTableResource = {
|
|
||||||
id: params.collectionId
|
|
||||||
};
|
|
||||||
if (params.analyticalStorageTtl) {
|
|
||||||
resource.analyticalStorageTtl = params.analyticalStorageTtl;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rpPayload: ARMTypes.CassandraTableCreateUpdateParameters = {
|
|
||||||
properties: {
|
|
||||||
resource,
|
|
||||||
options
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const createResponse = await createUpdateCassandraTable(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId,
|
|
||||||
params.collectionId,
|
|
||||||
rpPayload
|
|
||||||
);
|
|
||||||
return createResponse && (createResponse.properties.resource as DataModels.Collection);
|
|
||||||
};
|
|
||||||
|
|
||||||
const createGraph = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
|
||||||
try {
|
|
||||||
const getResponse = await getGremlinGraph(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId,
|
|
||||||
params.collectionId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create graph failed: graph with id ${params.collectionId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
|
||||||
const resource: ARMTypes.GremlinGraphResource = {
|
|
||||||
id: params.collectionId
|
|
||||||
};
|
|
||||||
|
|
||||||
if (params.indexingPolicy) {
|
|
||||||
resource.indexingPolicy = params.indexingPolicy;
|
|
||||||
}
|
|
||||||
if (params.partitionKey) {
|
|
||||||
resource.partitionKey = params.partitionKey;
|
|
||||||
}
|
|
||||||
if (params.uniqueKeyPolicy) {
|
|
||||||
resource.uniqueKeyPolicy = params.uniqueKeyPolicy;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rpPayload: ARMTypes.GremlinGraphCreateUpdateParameters = {
|
|
||||||
properties: {
|
|
||||||
resource,
|
|
||||||
options
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const createResponse = await createUpdateGremlinGraph(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId,
|
|
||||||
params.collectionId,
|
|
||||||
rpPayload
|
|
||||||
);
|
|
||||||
return createResponse && (createResponse.properties.resource as DataModels.Collection);
|
|
||||||
};
|
|
||||||
|
|
||||||
const createTable = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
|
||||||
try {
|
|
||||||
const getResponse = await getTable(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.collectionId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create table failed: table with id ${params.collectionId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
|
||||||
const resource: ARMTypes.TableResource = {
|
|
||||||
id: params.collectionId
|
|
||||||
};
|
|
||||||
|
|
||||||
const rpPayload: ARMTypes.TableCreateUpdateParameters = {
|
|
||||||
properties: {
|
|
||||||
resource,
|
|
||||||
options
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const createResponse = await createUpdateTable(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.collectionId,
|
|
||||||
rpPayload
|
|
||||||
);
|
|
||||||
return createResponse && (createResponse.properties.resource as DataModels.Collection);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const constructRpOptions = (params: DataModels.CreateDatabaseParams): ARMTypes.CreateUpdateOptions => {
|
|
||||||
if (params.databaseLevelThroughput) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.autoPilotMaxThroughput) {
|
|
||||||
return {
|
|
||||||
autoscaleSettings: {
|
|
||||||
maxThroughput: params.autoPilotMaxThroughput
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
throughput: params.offerThroughput
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const createCollectionWithSDK = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
|
||||||
const createCollectionBody: ContainerRequest = {
|
|
||||||
id: params.collectionId,
|
|
||||||
partitionKey: params.partitionKey || undefined,
|
|
||||||
indexingPolicy: params.indexingPolicy || undefined,
|
|
||||||
uniqueKeyPolicy: params.uniqueKeyPolicy || undefined,
|
|
||||||
analyticalStorageTtl: params.analyticalStorageTtl
|
|
||||||
} as ContainerRequest; // TODO: remove cast when https://github.com/Azure/azure-cosmos-js/issues/423 is fixed
|
|
||||||
const collectionOptions: RequestOptions = {};
|
|
||||||
const createDatabaseBody: DatabaseRequest = { id: params.databaseId };
|
|
||||||
|
|
||||||
if (params.databaseLevelThroughput) {
|
|
||||||
if (params.autoPilotMaxThroughput) {
|
|
||||||
createDatabaseBody.maxThroughput = params.autoPilotMaxThroughput;
|
|
||||||
} else {
|
|
||||||
createDatabaseBody.throughput = params.offerThroughput;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (params.autoPilotMaxThroughput) {
|
|
||||||
createCollectionBody.maxThroughput = params.autoPilotMaxThroughput;
|
|
||||||
} else {
|
|
||||||
createCollectionBody.throughput = params.offerThroughput;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const databaseResponse: DatabaseResponse = await client().databases.createIfNotExists(createDatabaseBody);
|
|
||||||
const collectionResponse: ContainerResponse = await databaseResponse?.database.containers.create(
|
|
||||||
createCollectionBody,
|
|
||||||
collectionOptions
|
|
||||||
);
|
|
||||||
return collectionResponse?.resource as DataModels.Collection;
|
|
||||||
};
|
|
||||||
@@ -1,254 +0,0 @@
|
|||||||
import * as DataModels from "../../Contracts/DataModels";
|
|
||||||
import { AuthType } from "../../AuthType";
|
|
||||||
import { DatabaseResponse } from "@azure/cosmos";
|
|
||||||
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
|
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
|
||||||
import {
|
|
||||||
CassandraKeyspaceCreateUpdateParameters,
|
|
||||||
GremlinDatabaseCreateUpdateParameters,
|
|
||||||
MongoDBDatabaseCreateUpdateParameters,
|
|
||||||
SqlDatabaseCreateUpdateParameters,
|
|
||||||
CreateUpdateOptions
|
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/types";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import {
|
|
||||||
createUpdateSqlDatabase,
|
|
||||||
getSqlDatabase
|
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/sqlResources";
|
|
||||||
import {
|
|
||||||
createUpdateCassandraKeyspace,
|
|
||||||
getCassandraKeyspace
|
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/cassandraResources";
|
|
||||||
import {
|
|
||||||
createUpdateMongoDBDatabase,
|
|
||||||
getMongoDBDatabase
|
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/mongoDBResources";
|
|
||||||
import {
|
|
||||||
createUpdateGremlinDatabase,
|
|
||||||
getGremlinDatabase
|
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/gremlinResources";
|
|
||||||
import { logConsoleProgress, logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { refreshCachedOffers, refreshCachedResources } from "../DataAccessUtilityBase";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
|
||||||
|
|
||||||
export async function createDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
|
||||||
let database: DataModels.Database;
|
|
||||||
const clearMessage = logConsoleProgress(`Creating a new database ${params.databaseId}`);
|
|
||||||
try {
|
|
||||||
if (
|
|
||||||
window.authType === AuthType.AAD &&
|
|
||||||
!userContext.useSDKOperations &&
|
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
|
||||||
) {
|
|
||||||
database = await createDatabaseWithARM(params);
|
|
||||||
} else {
|
|
||||||
database = await createDatabaseWithSDK(params);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Error while creating database ${params.databaseId}:\n ${error.message}`);
|
|
||||||
logError(JSON.stringify(error), "CreateDatabase", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
clearMessage();
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
logConsoleInfo(`Successfully created database ${params.databaseId}`);
|
|
||||||
await refreshCachedResources();
|
|
||||||
await refreshCachedOffers();
|
|
||||||
clearMessage();
|
|
||||||
return database;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
|
||||||
const defaultExperience = userContext.defaultExperience;
|
|
||||||
switch (defaultExperience) {
|
|
||||||
case DefaultAccountExperienceType.DocumentDB:
|
|
||||||
return createSqlDatabase(params);
|
|
||||||
case DefaultAccountExperienceType.MongoDB:
|
|
||||||
return createMongoDatabase(params);
|
|
||||||
case DefaultAccountExperienceType.Cassandra:
|
|
||||||
return createCassandraKeyspace(params);
|
|
||||||
case DefaultAccountExperienceType.Graph:
|
|
||||||
return createGremlineDatabase(params);
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createSqlDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
|
||||||
try {
|
|
||||||
const getResponse = await getSqlDatabase(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: CreateUpdateOptions = constructRpOptions(params);
|
|
||||||
const rpPayload: SqlDatabaseCreateUpdateParameters = {
|
|
||||||
properties: {
|
|
||||||
resource: {
|
|
||||||
id: params.databaseId
|
|
||||||
},
|
|
||||||
options
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const createResponse = await createUpdateSqlDatabase(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId,
|
|
||||||
rpPayload
|
|
||||||
);
|
|
||||||
return createResponse && (createResponse.properties.resource as DataModels.Database);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createMongoDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
|
||||||
try {
|
|
||||||
const getResponse = await getMongoDBDatabase(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: CreateUpdateOptions = constructRpOptions(params);
|
|
||||||
const rpPayload: MongoDBDatabaseCreateUpdateParameters = {
|
|
||||||
properties: {
|
|
||||||
resource: {
|
|
||||||
id: params.databaseId
|
|
||||||
},
|
|
||||||
options
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const createResponse = await createUpdateMongoDBDatabase(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId,
|
|
||||||
rpPayload
|
|
||||||
);
|
|
||||||
return createResponse && (createResponse.properties.resource as DataModels.Database);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createCassandraKeyspace(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
|
||||||
try {
|
|
||||||
const getResponse = await getCassandraKeyspace(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: CreateUpdateOptions = constructRpOptions(params);
|
|
||||||
const rpPayload: CassandraKeyspaceCreateUpdateParameters = {
|
|
||||||
properties: {
|
|
||||||
resource: {
|
|
||||||
id: params.databaseId
|
|
||||||
},
|
|
||||||
options
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const createResponse = await createUpdateCassandraKeyspace(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId,
|
|
||||||
rpPayload
|
|
||||||
);
|
|
||||||
return createResponse && (createResponse.properties.resource as DataModels.Database);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createGremlineDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
|
||||||
try {
|
|
||||||
const getResponse = await getGremlinDatabase(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId
|
|
||||||
);
|
|
||||||
if (getResponse?.properties?.resource) {
|
|
||||||
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: CreateUpdateOptions = constructRpOptions(params);
|
|
||||||
const rpPayload: GremlinDatabaseCreateUpdateParameters = {
|
|
||||||
properties: {
|
|
||||||
resource: {
|
|
||||||
id: params.databaseId
|
|
||||||
},
|
|
||||||
options
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const createResponse = await createUpdateGremlinDatabase(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
userContext.databaseAccount.name,
|
|
||||||
params.databaseId,
|
|
||||||
rpPayload
|
|
||||||
);
|
|
||||||
return createResponse && (createResponse.properties.resource as DataModels.Database);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createDatabaseWithSDK(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
|
||||||
const createBody: DatabaseRequest = { id: params.databaseId };
|
|
||||||
|
|
||||||
if (params.databaseLevelThroughput) {
|
|
||||||
if (params.autoPilotMaxThroughput) {
|
|
||||||
createBody.maxThroughput = params.autoPilotMaxThroughput;
|
|
||||||
} else {
|
|
||||||
createBody.throughput = params.offerThroughput;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const response: DatabaseResponse = await client().databases.create(createBody);
|
|
||||||
return response.resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
function constructRpOptions(params: DataModels.CreateDatabaseParams): CreateUpdateOptions {
|
|
||||||
if (!params.databaseLevelThroughput) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.autoPilotMaxThroughput) {
|
|
||||||
return {
|
|
||||||
autoscaleSettings: {
|
|
||||||
maxThroughput: params.autoPilotMaxThroughput
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
throughput: params.offerThroughput
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { deleteSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/sqlResources";
|
import { deleteSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { deleteCassandraTable } from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/cassandraResources";
|
import { deleteCassandraTable } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||||
import { deleteMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/mongoDBResources";
|
import { deleteMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
import { deleteGremlinGraph } from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/gremlinResources";
|
import { deleteGremlinGraph } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { deleteTable } from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/tableResources";
|
import { deleteTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { logError } from "../Logger";
|
import { logError } from "../Logger";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { sendNotificationForError } from "./sendNotificationForError";
|
||||||
@@ -15,7 +15,7 @@ import { refreshCachedResources } from "../DataAccessUtilityBase";
|
|||||||
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
|
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
|
||||||
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
|
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
|
||||||
try {
|
try {
|
||||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
if (window.authType === AuthType.AAD) {
|
||||||
await deleteCollectionWithARM(databaseId, collectionId);
|
await deleteCollectionWithARM(databaseId, collectionId);
|
||||||
} else {
|
} else {
|
||||||
await client()
|
await client()
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { deleteSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/sqlResources";
|
import { deleteSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { deleteCassandraKeyspace } from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/cassandraResources";
|
import { deleteCassandraKeyspace } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||||
import { deleteMongoDBDatabase } from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/mongoDBResources";
|
import { deleteMongoDBDatabase } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
import { deleteGremlinDatabase } from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/gremlinResources";
|
import { deleteGremlinDatabase } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
@@ -15,11 +15,7 @@ export async function deleteDatabase(databaseId: string): Promise<void> {
|
|||||||
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
|
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (
|
if (window.authType === AuthType.AAD) {
|
||||||
window.authType === AuthType.AAD &&
|
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table &&
|
|
||||||
!userContext.useSDKOperations
|
|
||||||
) {
|
|
||||||
await deleteDatabaseWithARM(databaseId);
|
await deleteDatabaseWithARM(databaseId);
|
||||||
} else {
|
} else {
|
||||||
await client()
|
await client()
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import * as DataModels from "../../Contracts/DataModels";
|
|||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { listSqlContainers } from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/sqlResources";
|
import { listSqlContainers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/cassandraResources";
|
import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||||
import { listMongoDBCollections } from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/mongoDBResources";
|
import { listMongoDBCollections } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/gremlinResources";
|
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { listTables } from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/tableResources";
|
import { listTables } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { logError } from "../Logger";
|
import { logError } from "../Logger";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { sendNotificationForError } from "./sendNotificationForError";
|
||||||
@@ -16,12 +16,7 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
|
|||||||
let collections: DataModels.Collection[];
|
let collections: DataModels.Collection[];
|
||||||
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
|
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
|
||||||
try {
|
try {
|
||||||
if (
|
if (window.authType === AuthType.AAD) {
|
||||||
window.authType === AuthType.AAD &&
|
|
||||||
!userContext.useSDKOperations &&
|
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
|
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
|
||||||
) {
|
|
||||||
collections = await readCollectionsWithARM(databaseId);
|
collections = await readCollectionsWithARM(databaseId);
|
||||||
} else {
|
} else {
|
||||||
const sdkResponse = await client()
|
const sdkResponse = await client()
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import * as DataModels from "../../Contracts/DataModels";
|
|||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { listSqlDatabases } from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/sqlResources";
|
import { listSqlDatabases } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/cassandraResources";
|
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||||
import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/mongoDBResources";
|
import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/gremlinResources";
|
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { logError } from "../Logger";
|
import { logError } from "../Logger";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { sendNotificationForError } from "./sendNotificationForError";
|
||||||
@@ -15,13 +15,7 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
|
|||||||
let databases: DataModels.Database[];
|
let databases: DataModels.Database[];
|
||||||
const clearMessage = logConsoleProgress(`Querying databases`);
|
const clearMessage = logConsoleProgress(`Querying databases`);
|
||||||
try {
|
try {
|
||||||
if (
|
if (window.authType === AuthType.AAD) {
|
||||||
window.authType === AuthType.AAD &&
|
|
||||||
!userContext.useSDKOperations &&
|
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
|
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table &&
|
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.Cassandra
|
|
||||||
) {
|
|
||||||
databases = await readDatabasesWithARM();
|
databases = await readDatabasesWithARM();
|
||||||
} else {
|
} else {
|
||||||
const sdkResponse = await client()
|
const sdkResponse = await client()
|
||||||
|
|||||||
@@ -1,228 +0,0 @@
|
|||||||
import { AuthType } from "../../AuthType";
|
|
||||||
import { Collection } from "../../Contracts/DataModels";
|
|
||||||
import { ContainerDefinition } from "@azure/cosmos";
|
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
|
||||||
import {
|
|
||||||
ExtendedResourceProperties,
|
|
||||||
SqlContainerCreateUpdateParameters,
|
|
||||||
SqlContainerResource
|
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/types";
|
|
||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import {
|
|
||||||
createUpdateSqlContainer,
|
|
||||||
getSqlContainer
|
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/sqlResources";
|
|
||||||
import {
|
|
||||||
createUpdateCassandraTable,
|
|
||||||
getCassandraTable
|
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/cassandraResources";
|
|
||||||
import {
|
|
||||||
createUpdateMongoDBCollection,
|
|
||||||
getMongoDBCollection
|
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/mongoDBResources";
|
|
||||||
import {
|
|
||||||
createUpdateGremlinGraph,
|
|
||||||
getGremlinGraph
|
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/gremlinResources";
|
|
||||||
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01-cosmos-db/tableResources";
|
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
|
||||||
|
|
||||||
export async function updateCollection(
|
|
||||||
databaseId: string,
|
|
||||||
collectionId: string,
|
|
||||||
newCollection: Collection,
|
|
||||||
options: RequestOptions = {}
|
|
||||||
): Promise<Collection> {
|
|
||||||
let collection: Collection;
|
|
||||||
const clearMessage = logConsoleProgress(`Updating container ${collectionId}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (
|
|
||||||
window.authType === AuthType.AAD &&
|
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
|
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
|
||||||
) {
|
|
||||||
collection = await updateCollectionWithARM(databaseId, collectionId, newCollection);
|
|
||||||
} else {
|
|
||||||
const sdkResponse = await client()
|
|
||||||
.database(databaseId)
|
|
||||||
.container(collectionId)
|
|
||||||
.replace(newCollection as ContainerDefinition, options);
|
|
||||||
collection = sdkResponse.resource as Collection;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Failed to update container ${collectionId}: ${JSON.stringify(error)}`);
|
|
||||||
logError(JSON.stringify(error), "UpdateCollection", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
logConsoleInfo(`Successfully updated container ${collectionId}`);
|
|
||||||
clearMessage();
|
|
||||||
await refreshCachedResources();
|
|
||||||
return collection;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateCollectionWithARM(
|
|
||||||
databaseId: string,
|
|
||||||
collectionId: string,
|
|
||||||
newCollection: Collection
|
|
||||||
): Promise<Collection> {
|
|
||||||
const subscriptionId = userContext.subscriptionId;
|
|
||||||
const resourceGroup = userContext.resourceGroup;
|
|
||||||
const accountName = userContext.databaseAccount.name;
|
|
||||||
const defaultExperience = userContext.defaultExperience;
|
|
||||||
|
|
||||||
switch (defaultExperience) {
|
|
||||||
case DefaultAccountExperienceType.DocumentDB:
|
|
||||||
return updateSqlContainer(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
|
||||||
case DefaultAccountExperienceType.MongoDB:
|
|
||||||
return updateMongoDBCollection(
|
|
||||||
databaseId,
|
|
||||||
collectionId,
|
|
||||||
subscriptionId,
|
|
||||||
resourceGroup,
|
|
||||||
accountName,
|
|
||||||
newCollection
|
|
||||||
);
|
|
||||||
case DefaultAccountExperienceType.Cassandra:
|
|
||||||
return updateCassandraTable(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
|
||||||
case DefaultAccountExperienceType.Graph:
|
|
||||||
return updateGremlinGraph(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
|
||||||
case DefaultAccountExperienceType.Table:
|
|
||||||
return updateTable(collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateSqlContainer(
|
|
||||||
databaseId: string,
|
|
||||||
collectionId: string,
|
|
||||||
subscriptionId: string,
|
|
||||||
resourceGroup: string,
|
|
||||||
accountName: string,
|
|
||||||
newCollection: Collection
|
|
||||||
): Promise<Collection> {
|
|
||||||
const getResponse = await getSqlContainer(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
|
||||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
|
||||||
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
|
|
||||||
const updateResponse = await createUpdateSqlContainer(
|
|
||||||
subscriptionId,
|
|
||||||
resourceGroup,
|
|
||||||
accountName,
|
|
||||||
databaseId,
|
|
||||||
collectionId,
|
|
||||||
getResponse as SqlContainerCreateUpdateParameters
|
|
||||||
);
|
|
||||||
return updateResponse && (updateResponse.properties.resource as Collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Sql container to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateMongoDBCollection(
|
|
||||||
databaseId: string,
|
|
||||||
collectionId: string,
|
|
||||||
subscriptionId: string,
|
|
||||||
resourceGroup: string,
|
|
||||||
accountName: string,
|
|
||||||
newCollection: Collection
|
|
||||||
): Promise<Collection> {
|
|
||||||
const getResponse = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
|
||||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
|
||||||
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
|
|
||||||
const updateResponse = await createUpdateMongoDBCollection(
|
|
||||||
subscriptionId,
|
|
||||||
resourceGroup,
|
|
||||||
accountName,
|
|
||||||
databaseId,
|
|
||||||
collectionId,
|
|
||||||
getResponse as SqlContainerCreateUpdateParameters
|
|
||||||
);
|
|
||||||
return updateResponse && (updateResponse.properties.resource as Collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(
|
|
||||||
`MongoDB collection to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateCassandraTable(
|
|
||||||
databaseId: string,
|
|
||||||
collectionId: string,
|
|
||||||
subscriptionId: string,
|
|
||||||
resourceGroup: string,
|
|
||||||
accountName: string,
|
|
||||||
newCollection: Collection
|
|
||||||
): Promise<Collection> {
|
|
||||||
const getResponse = await getCassandraTable(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
|
||||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
|
||||||
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
|
|
||||||
const updateResponse = await createUpdateCassandraTable(
|
|
||||||
subscriptionId,
|
|
||||||
resourceGroup,
|
|
||||||
accountName,
|
|
||||||
databaseId,
|
|
||||||
collectionId,
|
|
||||||
getResponse as SqlContainerCreateUpdateParameters
|
|
||||||
);
|
|
||||||
return updateResponse && (updateResponse.properties.resource as Collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(
|
|
||||||
`Cassandra table to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateGremlinGraph(
|
|
||||||
databaseId: string,
|
|
||||||
collectionId: string,
|
|
||||||
subscriptionId: string,
|
|
||||||
resourceGroup: string,
|
|
||||||
accountName: string,
|
|
||||||
newCollection: Collection
|
|
||||||
): Promise<Collection> {
|
|
||||||
const getResponse = await getGremlinGraph(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
|
||||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
|
||||||
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
|
|
||||||
const updateResponse = await createUpdateGremlinGraph(
|
|
||||||
subscriptionId,
|
|
||||||
resourceGroup,
|
|
||||||
accountName,
|
|
||||||
databaseId,
|
|
||||||
collectionId,
|
|
||||||
getResponse as SqlContainerCreateUpdateParameters
|
|
||||||
);
|
|
||||||
return updateResponse && (updateResponse.properties.resource as Collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Gremlin graph to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateTable(
|
|
||||||
collectionId: string,
|
|
||||||
subscriptionId: string,
|
|
||||||
resourceGroup: string,
|
|
||||||
accountName: string,
|
|
||||||
newCollection: Collection
|
|
||||||
): Promise<Collection> {
|
|
||||||
const getResponse = await getTable(subscriptionId, resourceGroup, accountName, collectionId);
|
|
||||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
|
||||||
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
|
|
||||||
const updateResponse = await createUpdateTable(
|
|
||||||
subscriptionId,
|
|
||||||
resourceGroup,
|
|
||||||
accountName,
|
|
||||||
collectionId,
|
|
||||||
getResponse as SqlContainerCreateUpdateParameters
|
|
||||||
);
|
|
||||||
return updateResponse && (updateResponse.properties.resource as Collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Table to update does not exist. Table id: ${collectionId}`);
|
|
||||||
}
|
|
||||||
@@ -80,20 +80,12 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Allow override of platform value with URL query parameter
|
// Allow override of any config value with URL query parameters
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
if (params.has("platform")) {
|
params.forEach((value, key) => {
|
||||||
const platform = params.get("platform");
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
switch (platform) {
|
(configContext as any)[key] = value;
|
||||||
default:
|
});
|
||||||
console.log("Invalid platform query parameter given, ignoring");
|
|
||||||
break;
|
|
||||||
case Platform.Portal:
|
|
||||||
case Platform.Hosted:
|
|
||||||
case Platform.Emulator:
|
|
||||||
updateConfigContext({ platform });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("No configuration file found using defaults");
|
console.log("No configuration file found using defaults");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,14 +153,7 @@ export interface KeyResource {
|
|||||||
Token: string;
|
Token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IndexingPolicy {
|
export interface IndexingPolicy {}
|
||||||
automatic: boolean;
|
|
||||||
indexingMode: string;
|
|
||||||
includedPaths: any;
|
|
||||||
excludedPaths: any;
|
|
||||||
compositeIndexes?: any;
|
|
||||||
spatialIndexes?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PartitionKey {
|
export interface PartitionKey {
|
||||||
paths: string[];
|
paths: string[];
|
||||||
@@ -327,24 +320,12 @@ export interface AutoPilotOfferSettings {
|
|||||||
targetMaxThroughput?: number;
|
targetMaxThroughput?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateDatabaseParams {
|
export interface CreateDatabaseRequest {
|
||||||
autoPilotMaxThroughput?: number;
|
|
||||||
databaseId: string;
|
databaseId: string;
|
||||||
databaseLevelThroughput?: boolean;
|
databaseLevelThroughput?: boolean;
|
||||||
offerThroughput?: number;
|
offerThroughput?: number;
|
||||||
}
|
autoPilot?: AutoPilotCreationSettings;
|
||||||
|
hasAutoPilotV2FeatureFlag?: boolean;
|
||||||
export interface CreateCollectionParams {
|
|
||||||
createNewDatabase: boolean;
|
|
||||||
collectionId: string;
|
|
||||||
databaseId: string;
|
|
||||||
databaseLevelThroughput: boolean;
|
|
||||||
offerThroughput: number;
|
|
||||||
analyticalStorageTtl?: number;
|
|
||||||
autoPilotMaxThroughput?: number;
|
|
||||||
indexingPolicy?: IndexingPolicy;
|
|
||||||
partitionKey?: PartitionKey;
|
|
||||||
uniqueKeyPolicy?: UniqueKeyPolicy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SharedThroughputRange {
|
export interface SharedThroughputRange {
|
||||||
@@ -709,6 +690,11 @@ export interface SparkPool extends ArmResource {
|
|||||||
properties: SparkPoolProperties;
|
properties: SparkPoolProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MemoryUsageInfo {
|
||||||
|
freeKB: number;
|
||||||
|
totalKB: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface resourceTokenConnectionStringProperties {
|
export interface resourceTokenConnectionStringProperties {
|
||||||
accountEndpoint: string;
|
accountEndpoint: string;
|
||||||
collectionId: string;
|
collectionId: string;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { StringUtils } from "../../../Utils/StringUtils";
|
import { StringUtils } from "../../../Utils/StringUtils";
|
||||||
import { KeyCodes } from "../../../Common/Constants";
|
import { KeyCodes } from "../../../Common/Constants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
|
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
|
||||||
|
|
||||||
|
|||||||
@@ -265,9 +265,6 @@ exports[`test render renders with filters 1`] = `
|
|||||||
"buttonTextDisabled": "#a19f9d",
|
"buttonTextDisabled": "#a19f9d",
|
||||||
"buttonTextHovered": "#201f1e",
|
"buttonTextHovered": "#201f1e",
|
||||||
"buttonTextPressed": "#201f1e",
|
"buttonTextPressed": "#201f1e",
|
||||||
"cardShadow": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
|
|
||||||
"cardShadowHovered": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)",
|
|
||||||
"cardStandoutBackground": "#ffffff",
|
|
||||||
"defaultStateBackground": "#faf9f8",
|
"defaultStateBackground": "#faf9f8",
|
||||||
"disabledBackground": "#f3f2f1",
|
"disabledBackground": "#f3f2f1",
|
||||||
"disabledBodySubtext": "#c8c6c4",
|
"disabledBodySubtext": "#c8c6c4",
|
||||||
@@ -607,9 +604,6 @@ exports[`test render renders with filters 1`] = `
|
|||||||
"buttonTextDisabled": "#a19f9d",
|
"buttonTextDisabled": "#a19f9d",
|
||||||
"buttonTextHovered": "#201f1e",
|
"buttonTextHovered": "#201f1e",
|
||||||
"buttonTextPressed": "#201f1e",
|
"buttonTextPressed": "#201f1e",
|
||||||
"cardShadow": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
|
|
||||||
"cardShadowHovered": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)",
|
|
||||||
"cardStandoutBackground": "#ffffff",
|
|
||||||
"defaultStateBackground": "#faf9f8",
|
"defaultStateBackground": "#faf9f8",
|
||||||
"disabledBackground": "#f3f2f1",
|
"disabledBackground": "#f3f2f1",
|
||||||
"disabledBodySubtext": "#c8c6c4",
|
"disabledBodySubtext": "#c8c6c4",
|
||||||
@@ -1003,9 +997,6 @@ exports[`test render renders with filters 1`] = `
|
|||||||
"buttonTextDisabled": "#a19f9d",
|
"buttonTextDisabled": "#a19f9d",
|
||||||
"buttonTextHovered": "#201f1e",
|
"buttonTextHovered": "#201f1e",
|
||||||
"buttonTextPressed": "#201f1e",
|
"buttonTextPressed": "#201f1e",
|
||||||
"cardShadow": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
|
|
||||||
"cardShadowHovered": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)",
|
|
||||||
"cardStandoutBackground": "#ffffff",
|
|
||||||
"defaultStateBackground": "#faf9f8",
|
"defaultStateBackground": "#faf9f8",
|
||||||
"disabledBackground": "#f3f2f1",
|
"disabledBackground": "#f3f2f1",
|
||||||
"disabledBodySubtext": "#c8c6c4",
|
"disabledBodySubtext": "#c8c6c4",
|
||||||
@@ -1122,11 +1113,6 @@ exports[`test render renders with filters 1`] = `
|
|||||||
},
|
},
|
||||||
"iconDisabled": Object {
|
"iconDisabled": Object {
|
||||||
"color": "#a19f9d",
|
"color": "#a19f9d",
|
||||||
"selectors": Object {
|
|
||||||
"@media screen and (-ms-high-contrast: active)": Object {
|
|
||||||
"color": "GrayText",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"label": Array [
|
"label": Array [
|
||||||
Object {
|
Object {
|
||||||
@@ -1148,11 +1134,6 @@ exports[`test render renders with filters 1`] = `
|
|||||||
},
|
},
|
||||||
"menuIconDisabled": Object {
|
"menuIconDisabled": Object {
|
||||||
"color": "#a19f9d",
|
"color": "#a19f9d",
|
||||||
"selectors": Object {
|
|
||||||
"@media screen and (-ms-high-contrast: active)": Object {
|
|
||||||
"color": "GrayText",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"root": Array [
|
"root": Array [
|
||||||
Object {
|
Object {
|
||||||
@@ -1169,6 +1150,7 @@ exports[`test render renders with filters 1`] = `
|
|||||||
"right": 2,
|
"right": 2,
|
||||||
"selectors": Object {
|
"selectors": Object {
|
||||||
"@media screen and (-ms-high-contrast: active)": Object {
|
"@media screen and (-ms-high-contrast: active)": Object {
|
||||||
|
"border": "none",
|
||||||
"bottom": -2,
|
"bottom": -2,
|
||||||
"left": -2,
|
"left": -2,
|
||||||
"outlineColor": "ButtonText",
|
"outlineColor": "ButtonText",
|
||||||
@@ -1248,6 +1230,7 @@ exports[`test render renders with filters 1`] = `
|
|||||||
"right": 2,
|
"right": 2,
|
||||||
"selectors": Object {
|
"selectors": Object {
|
||||||
"@media screen and (-ms-high-contrast: active)": Object {
|
"@media screen and (-ms-high-contrast: active)": Object {
|
||||||
|
"border": "none",
|
||||||
"bottom": -2,
|
"bottom": -2,
|
||||||
"left": -2,
|
"left": -2,
|
||||||
"outlineColor": "ButtonText",
|
"outlineColor": "ButtonText",
|
||||||
@@ -1276,6 +1259,10 @@ exports[`test render renders with filters 1`] = `
|
|||||||
":hover": Object {
|
":hover": Object {
|
||||||
"outline": 0,
|
"outline": 0,
|
||||||
},
|
},
|
||||||
|
"@media screen and (-ms-high-contrast: active)": Object {
|
||||||
|
"borderColor": "grayText",
|
||||||
|
"color": "grayText",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
@@ -1375,21 +1362,13 @@ exports[`test render renders with filters 1`] = `
|
|||||||
"selectors": Object {
|
"selectors": Object {
|
||||||
"@media screen and (-ms-high-contrast: active)": Object {
|
"@media screen and (-ms-high-contrast: active)": Object {
|
||||||
"MsHighContrastAdjust": "none",
|
"MsHighContrastAdjust": "none",
|
||||||
"backgroundColor": "Window",
|
"backgroundColor": "WindowText",
|
||||||
"border": "1px solid WindowText",
|
"color": "Window",
|
||||||
"borderRightWidth": "0",
|
|
||||||
"color": "WindowText",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
".ms-Button--primary + .ms-Button": Object {
|
".ms-Button--primary + .ms-Button": Object {
|
||||||
"border": "none",
|
"border": "none",
|
||||||
"selectors": Object {
|
|
||||||
"@media screen and (-ms-high-contrast: active)": Object {
|
|
||||||
"border": "1px solid WindowText",
|
|
||||||
"borderLeftWidth": "0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1429,9 +1408,6 @@ exports[`test render renders with filters 1`] = `
|
|||||||
"borderColor": "GrayText",
|
"borderColor": "GrayText",
|
||||||
"color": "GrayText",
|
"color": "GrayText",
|
||||||
},
|
},
|
||||||
"@media screen and (forced-colors: active)": Object {
|
|
||||||
"forcedColorAdjust": "none",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"splitButtonContainerFocused": Object {
|
"splitButtonContainerFocused": Object {
|
||||||
@@ -1578,13 +1554,6 @@ exports[`test render renders with filters 1`] = `
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
".ms-Button-menuIcon": Object {
|
|
||||||
"selectors": Object {
|
|
||||||
"@media screen and (-ms-high-contrast: active)": Object {
|
|
||||||
"color": "GrayText",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
":hover": Object {
|
":hover": Object {
|
||||||
"cursor": "default",
|
"cursor": "default",
|
||||||
},
|
},
|
||||||
@@ -1806,9 +1775,6 @@ exports[`test render renders with filters 1`] = `
|
|||||||
"buttonTextDisabled": "#a19f9d",
|
"buttonTextDisabled": "#a19f9d",
|
||||||
"buttonTextHovered": "#201f1e",
|
"buttonTextHovered": "#201f1e",
|
||||||
"buttonTextPressed": "#201f1e",
|
"buttonTextPressed": "#201f1e",
|
||||||
"cardShadow": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
|
|
||||||
"cardShadowHovered": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)",
|
|
||||||
"cardStandoutBackground": "#ffffff",
|
|
||||||
"defaultStateBackground": "#faf9f8",
|
"defaultStateBackground": "#faf9f8",
|
||||||
"disabledBackground": "#f3f2f1",
|
"disabledBackground": "#f3f2f1",
|
||||||
"disabledBodySubtext": "#c8c6c4",
|
"disabledBodySubtext": "#c8c6c4",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { RepoListItem } from "./GitHubReposComponent";
|
|||||||
import { ChildrenMargin } from "./GitHubStyleConstants";
|
import { ChildrenMargin } from "./GitHubStyleConstants";
|
||||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||||
import { IGitHubRepo } from "../../../GitHub/GitHubClient";
|
import { IGitHubRepo } from "../../../GitHub/GitHubClient";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import UrlUtility from "../../../Common/UrlUtility";
|
import UrlUtility from "../../../Common/UrlUtility";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
} from "office-ui-fabric-react/lib/utilities/selection/index";
|
} from "office-ui-fabric-react/lib/utilities/selection/index";
|
||||||
import { StyleConstants } from "../../../Common/Constants";
|
import { StyleConstants } from "../../../Common/Constants";
|
||||||
import { TextField, ITextFieldProps, ITextField } from "office-ui-fabric-react/lib/TextField";
|
import { TextField, ITextFieldProps, ITextField } from "office-ui-fabric-react/lib/TextField";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
|
||||||
import SaveQueryBannerIcon from "../../../../images/save_query_banner.png";
|
import SaveQueryBannerIcon from "../../../../images/save_query_banner.png";
|
||||||
import { QueriesClient } from "../../../Common/QueriesClient";
|
import { QueriesClient } from "../../../Common/QueriesClient";
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
jest.mock("../../Common/DocumentClientUtilityBase");
|
jest.mock("../../Common/DocumentClientUtilityBase");
|
||||||
jest.mock("../../Common/dataAccess/createCollection");
|
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as sinon from "sinon";
|
import * as sinon from "sinon";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
@@ -34,8 +33,8 @@ describe("ContainerSampleGenerator", () => {
|
|||||||
databaseId: sampleDatabaseId,
|
databaseId: sampleDatabaseId,
|
||||||
offerThroughput: 400,
|
offerThroughput: 400,
|
||||||
databaseLevelThroughput: false,
|
databaseLevelThroughput: false,
|
||||||
createNewDatabase: true,
|
|
||||||
collectionId: sampleCollectionId,
|
collectionId: sampleCollectionId,
|
||||||
|
rupmEnabled: false,
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
firstname: "Eva",
|
firstname: "Eva",
|
||||||
@@ -100,8 +99,8 @@ describe("ContainerSampleGenerator", () => {
|
|||||||
databaseId: sampleDatabaseId,
|
databaseId: sampleDatabaseId,
|
||||||
offerThroughput: 400,
|
offerThroughput: 400,
|
||||||
databaseLevelThroughput: false,
|
databaseLevelThroughput: false,
|
||||||
createNewDatabase: true,
|
|
||||||
collectionId: sampleCollectionId,
|
collectionId: sampleCollectionId,
|
||||||
|
rupmEnabled: false,
|
||||||
data: [
|
data: [
|
||||||
"g.addV('person').property(id, '1').property('_partitionKey','pk').property('name', 'Eva').property('age', 44)"
|
"g.addV('person').property(id, '1').property('_partitionKey','pk').property('name', 'Eva').property('age', 44)"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import * as Constants from "../../Common/Constants";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import GraphTab from ".././Tabs/GraphTab";
|
import GraphTab from ".././Tabs/GraphTab";
|
||||||
@@ -5,11 +6,10 @@ import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsol
|
|||||||
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
|
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { createDocument } from "../../Common/DocumentClientUtilityBase";
|
import { createDocument, getOrCreateDatabaseAndCollection } from "../../Common/DocumentClientUtilityBase";
|
||||||
import { createCollection } from "../../Common/dataAccess/createCollection";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
interface SampleDataFile extends DataModels.CreateCollectionParams {
|
interface SampleDataFile extends DataModels.CreateDatabaseAndCollectionRequest {
|
||||||
data: any[];
|
data: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,11 +54,18 @@ export class ContainerSampleGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async createContainerAsync(): Promise<ViewModels.Collection> {
|
private async createContainerAsync(): Promise<ViewModels.Collection> {
|
||||||
const createRequest: DataModels.CreateCollectionParams = {
|
const createRequest: DataModels.CreateDatabaseAndCollectionRequest = {
|
||||||
...this.sampleDataFile
|
...this.sampleDataFile
|
||||||
};
|
};
|
||||||
|
|
||||||
await createCollection(createRequest);
|
const options: any = {};
|
||||||
|
if (this.container.isPreferredApiMongoDB()) {
|
||||||
|
options.initialHeaders = options.initialHeaders || {};
|
||||||
|
options.initialHeaders[Constants.HttpHeaders.supportSpatialLegacyCoordinates] = true;
|
||||||
|
options.initialHeaders[Constants.HttpHeaders.usePolygonsSmallerThanAHemisphere] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await getOrCreateDatabaseAndCollection(createRequest, options);
|
||||||
await this.container.refreshAllDatabases();
|
await this.container.refreshAllDatabases();
|
||||||
const database = this.container.findDatabaseWithId(this.sampleDataFile.databaseId);
|
const database = this.container.findDatabaseWithId(this.sampleDataFile.databaseId);
|
||||||
if (!database) {
|
if (!database) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import NewVertexPane from "./Panes/NewVertexPane";
|
|||||||
import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab";
|
import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab";
|
||||||
import Q from "q";
|
import Q from "q";
|
||||||
import ResourceTokenCollection from "./Tree/ResourceTokenCollection";
|
import ResourceTokenCollection from "./Tree/ResourceTokenCollection";
|
||||||
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
||||||
import TerminalTab from "./Tabs/TerminalTab";
|
import TerminalTab from "./Tabs/TerminalTab";
|
||||||
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
||||||
import { ActionContracts, MessageTypes } from "../Contracts/ExplorerContracts";
|
import { ActionContracts, MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
@@ -37,7 +37,7 @@ import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer
|
|||||||
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane";
|
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane";
|
||||||
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
|
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
|
||||||
import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter";
|
import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import { configContext, updateConfigContext } from "../ConfigContext";
|
import { configContext } from "../ConfigContext";
|
||||||
import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||||
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
|
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
|
||||||
@@ -82,7 +82,6 @@ import { toRawContentUri, fromContentUri } from "../Utils/GitHubUtils";
|
|||||||
import UserDefinedFunction from "./Tree/UserDefinedFunction";
|
import UserDefinedFunction from "./Tree/UserDefinedFunction";
|
||||||
import StoredProcedure from "./Tree/StoredProcedure";
|
import StoredProcedure from "./Tree/StoredProcedure";
|
||||||
import Trigger from "./Tree/Trigger";
|
import Trigger from "./Tree/Trigger";
|
||||||
import { NotificationsClientBase } from "../Common/NotificationsClientBase";
|
|
||||||
import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
|
import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
|
||||||
import TabsBase from "./Tabs/TabsBase";
|
import TabsBase from "./Tabs/TabsBase";
|
||||||
import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandButtonComponent";
|
||||||
@@ -98,7 +97,6 @@ enum ShareAccessToggleState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ExplorerOptions {
|
interface ExplorerOptions {
|
||||||
notificationsClient: NotificationsClientBase;
|
|
||||||
isEmulator: boolean;
|
isEmulator: boolean;
|
||||||
}
|
}
|
||||||
interface AdHocAccessData {
|
interface AdHocAccessData {
|
||||||
@@ -133,7 +131,6 @@ export default class Explorer {
|
|||||||
public isPreferredApiTable: ko.Computed<boolean>;
|
public isPreferredApiTable: ko.Computed<boolean>;
|
||||||
public isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>;
|
public isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>;
|
||||||
public isServerlessEnabled: ko.Computed<boolean>;
|
public isServerlessEnabled: ko.Computed<boolean>;
|
||||||
public isEmulator: boolean;
|
|
||||||
public isAccountReady: ko.Observable<boolean>;
|
public isAccountReady: ko.Observable<boolean>;
|
||||||
public canSaveQueries: ko.Computed<boolean>;
|
public canSaveQueries: ko.Computed<boolean>;
|
||||||
public features: ko.Observable<any>;
|
public features: ko.Observable<any>;
|
||||||
@@ -141,7 +138,6 @@ export default class Explorer {
|
|||||||
public extensionEndpoint: ko.Observable<string>;
|
public extensionEndpoint: ko.Observable<string>;
|
||||||
public armEndpoint: ko.Observable<string>;
|
public armEndpoint: ko.Observable<string>;
|
||||||
public isTryCosmosDBSubscription: ko.Observable<boolean>;
|
public isTryCosmosDBSubscription: ko.Observable<boolean>;
|
||||||
public notificationsClient: NotificationsClientBase;
|
|
||||||
public queriesClient: QueriesClient;
|
public queriesClient: QueriesClient;
|
||||||
public tableDataClient: TableDataClient;
|
public tableDataClient: TableDataClient;
|
||||||
public splitter: Splitter;
|
public splitter: Splitter;
|
||||||
@@ -243,6 +239,7 @@ export default class Explorer {
|
|||||||
public arcadiaWorkspaces: ko.ObservableArray<ArcadiaWorkspaceItem>;
|
public arcadiaWorkspaces: ko.ObservableArray<ArcadiaWorkspaceItem>;
|
||||||
public hasStorageAnalyticsAfecFeature: ko.Observable<boolean>;
|
public hasStorageAnalyticsAfecFeature: ko.Observable<boolean>;
|
||||||
public isSynapseLinkUpdating: ko.Observable<boolean>;
|
public isSynapseLinkUpdating: ko.Observable<boolean>;
|
||||||
|
public memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>;
|
||||||
public notebookManager?: any; // This is dynamically loaded
|
public notebookManager?: any; // This is dynamically loaded
|
||||||
|
|
||||||
private _panes: ContextualPaneBase[] = [];
|
private _panes: ContextualPaneBase[] = [];
|
||||||
@@ -268,7 +265,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
private static readonly MaxNbDatabasesToAutoExpand = 5;
|
private static readonly MaxNbDatabasesToAutoExpand = 5;
|
||||||
|
|
||||||
constructor(options: ExplorerOptions) {
|
constructor() {
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, {
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree
|
dataExplorerArea: Constants.Areas.ResourceTree
|
||||||
});
|
});
|
||||||
@@ -373,8 +370,7 @@ export default class Explorer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.notificationsClient = options.notificationsClient;
|
this.memoryUsageInfo = ko.observable<DataModels.MemoryUsageInfo>();
|
||||||
this.isEmulator = options.isEmulator;
|
|
||||||
|
|
||||||
this.features = ko.observable();
|
this.features = ko.observable();
|
||||||
this.serverId = ko.observable<string>();
|
this.serverId = ko.observable<string>();
|
||||||
@@ -973,10 +969,6 @@ export default class Explorer {
|
|||||||
this.sparkClusterConnectionInfo.valueHasMutated();
|
this.sparkClusterConnectionInfo.valueHasMutated();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isFeatureEnabled(Constants.Features.enableSDKoperations)) {
|
|
||||||
updateUserContext({ useSDKOperations: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
featureSubcription.dispose();
|
featureSubcription.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1477,33 +1469,38 @@ export default class Explorer {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const offerPromise: Q.Promise<DataModels.Offer[]> = readOffers({ isServerless: this.isServerlessEnabled() });
|
if (this.isServerlessEnabled()) {
|
||||||
this._setLoadingStatusText("Fetching offers...");
|
// Serverless accounts don't support offers call
|
||||||
offerPromise.then(
|
refreshDatabases();
|
||||||
(offers: DataModels.Offer[]) => {
|
} else {
|
||||||
this._setLoadingStatusText("Successfully fetched offers.");
|
const offerPromise: Q.Promise<DataModels.Offer[]> = readOffers();
|
||||||
refreshDatabases(offers);
|
this._setLoadingStatusText("Fetching offers...");
|
||||||
},
|
offerPromise.then(
|
||||||
error => {
|
(offers: DataModels.Offer[]) => {
|
||||||
this._setLoadingStatusText("Failed to fetch offers.");
|
this._setLoadingStatusText("Successfully fetched offers.");
|
||||||
this.isRefreshingExplorer(false);
|
refreshDatabases(offers);
|
||||||
deferred.reject(error);
|
},
|
||||||
TelemetryProcessor.traceFailure(
|
error => {
|
||||||
Action.LoadDatabases,
|
this._setLoadingStatusText("Failed to fetch offers.");
|
||||||
{
|
this.isRefreshingExplorer(false);
|
||||||
databaseAccountName: this.databaseAccount().name,
|
deferred.reject(error);
|
||||||
defaultExperience: this.defaultExperience(),
|
TelemetryProcessor.traceFailure(
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
Action.LoadDatabases,
|
||||||
error: JSON.stringify(error)
|
{
|
||||||
},
|
databaseAccountName: this.databaseAccount().name,
|
||||||
startKey
|
defaultExperience: this.defaultExperience(),
|
||||||
);
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
error: JSON.stringify(error)
|
||||||
ConsoleDataType.Error,
|
},
|
||||||
`Error while refreshing databases: ${JSON.stringify(error)}`
|
startKey
|
||||||
);
|
);
|
||||||
}
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
);
|
ConsoleDataType.Error,
|
||||||
|
`Error while refreshing databases: ${JSON.stringify(error)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return deferred.promise.then(
|
return deferred.promise.then(
|
||||||
() => {
|
() => {
|
||||||
@@ -1936,7 +1933,6 @@ export default class Explorer {
|
|||||||
this.serverId(inputs.serverId);
|
this.serverId(inputs.serverId);
|
||||||
this.extensionEndpoint(inputs.extensionEndpoint || "");
|
this.extensionEndpoint(inputs.extensionEndpoint || "");
|
||||||
this.armEndpoint(EnvironmentUtility.normalizeArmEndpointUri(inputs.csmEndpoint || configContext.ARM_ENDPOINT));
|
this.armEndpoint(EnvironmentUtility.normalizeArmEndpointUri(inputs.csmEndpoint || configContext.ARM_ENDPOINT));
|
||||||
this.notificationsClient.setExtensionEndpoint(this.extensionEndpoint());
|
|
||||||
this.databaseAccount(databaseAccount);
|
this.databaseAccount(databaseAccount);
|
||||||
this.subscriptionType(inputs.subscriptionType);
|
this.subscriptionType(inputs.subscriptionType);
|
||||||
this.quotaId(inputs.quotaId);
|
this.quotaId(inputs.quotaId);
|
||||||
@@ -1951,17 +1947,12 @@ export default class Explorer {
|
|||||||
|
|
||||||
this._importExplorerConfigComplete = true;
|
this._importExplorerConfigComplete = true;
|
||||||
|
|
||||||
updateConfigContext({
|
|
||||||
ARM_ENDPOINT: this.armEndpoint()
|
|
||||||
});
|
|
||||||
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
authorizationToken,
|
authorizationToken,
|
||||||
masterKey,
|
masterKey,
|
||||||
databaseAccount,
|
databaseAccount
|
||||||
resourceGroup: inputs.resourceGroup,
|
|
||||||
subscriptionId: inputs.subscriptionId
|
|
||||||
});
|
});
|
||||||
|
updateUserContext({ resourceGroup: inputs.resourceGroup, subscriptionId: inputs.subscriptionId });
|
||||||
TelemetryProcessor.traceSuccess(
|
TelemetryProcessor.traceSuccess(
|
||||||
Action.LoadDatabaseAccount,
|
Action.LoadDatabaseAccount,
|
||||||
{
|
{
|
||||||
@@ -3115,6 +3106,12 @@ export default class Explorer {
|
|||||||
} else {
|
} else {
|
||||||
loadingTitle.innerHTML = title;
|
loadingTitle.innerHTML = title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TelemetryProcessor.trace(
|
||||||
|
Action.LoadingStatus,
|
||||||
|
ActionModifiers.Mark,
|
||||||
|
title !== "Welcome to Azure Cosmos DB" ? `Title: ${title}, Text: ${text}` : text
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _openSetupNotebooksPaneForQuickstart(): void {
|
private _openSetupNotebooksPaneForQuickstart(): void {
|
||||||
|
|||||||
@@ -87,31 +87,13 @@ describe("getPkIdFromDocumentId", () => {
|
|||||||
expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("['pkvalue', 'id']");
|
expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("['pkvalue', 'id']");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should create pkid pair from partitioned graph (pk as number)", () => {
|
|
||||||
const doc = createFakeDoc({ id: "id", mypk: 234 });
|
|
||||||
expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("[234, 'id']");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should create pkid pair from partitioned graph (pk as boolean)", () => {
|
|
||||||
const doc = createFakeDoc({ id: "id", mypk: true });
|
|
||||||
expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("[true, 'id']");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should create pkid pair from partitioned graph (pk as valid array value)", () => {
|
it("should create pkid pair from partitioned graph (pk as valid array value)", () => {
|
||||||
const doc = createFakeDoc({ id: "id", mypk: [{ id: "someid", _value: "pkvalue" }] });
|
const doc = createFakeDoc({ id: "id", mypk: [{ id: "someid", _value: "pkvalue" }] });
|
||||||
expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("['pkvalue', 'id']");
|
expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("['pkvalue', 'id']");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should error if id is not a string or number", () => {
|
it("should error if id is not a string", () => {
|
||||||
let doc = createFakeDoc({ id: { foo: 1 } });
|
const doc = createFakeDoc({ id: { foo: 1 } });
|
||||||
try {
|
|
||||||
GraphExplorer.getPkIdFromDocumentId(doc, undefined);
|
|
||||||
expect(true).toBe(false);
|
|
||||||
} catch (e) {
|
|
||||||
expect(true).toBe(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
doc = createFakeDoc({ id: true });
|
|
||||||
try {
|
try {
|
||||||
GraphExplorer.getPkIdFromDocumentId(doc, undefined);
|
GraphExplorer.getPkIdFromDocumentId(doc, undefined);
|
||||||
expect(true).toBe(false);
|
expect(true).toBe(false);
|
||||||
@@ -120,8 +102,16 @@ describe("getPkIdFromDocumentId", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should error if pk is empty array", () => {
|
it("should error if pk not string nor non-empty array", () => {
|
||||||
let doc = createFakeDoc({ mypk: [] });
|
let doc = createFakeDoc({ mypk: { foo: 1 } });
|
||||||
|
|
||||||
|
try {
|
||||||
|
GraphExplorer.getPkIdFromDocumentId(doc, "mypk");
|
||||||
|
} catch (e) {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
doc = createFakeDoc({ mypk: [] });
|
||||||
try {
|
try {
|
||||||
GraphExplorer.getPkIdFromDocumentId(doc, "mypk");
|
GraphExplorer.getPkIdFromDocumentId(doc, "mypk");
|
||||||
expect(true).toBe(false);
|
expect(true).toBe(false);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { GraphConfig } from "../../Tabs/GraphTab";
|
|||||||
import { EditorReact } from "../../Controls/Editor/EditorReact";
|
import { EditorReact } from "../../Controls/Editor/EditorReact";
|
||||||
import LoadGraphIcon from "../../../../images/LoadGraph.png";
|
import LoadGraphIcon from "../../../../images/LoadGraph.png";
|
||||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import { InputProperty } from "../../../Contracts/ViewModels";
|
import { InputProperty } from "../../../Contracts/ViewModels";
|
||||||
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
|
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
|
||||||
@@ -1371,7 +1371,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
|
|
||||||
if (collectionPartitionKeyProperty && d.hasOwnProperty(collectionPartitionKeyProperty)) {
|
if (collectionPartitionKeyProperty && d.hasOwnProperty(collectionPartitionKeyProperty)) {
|
||||||
let pk = (d as any)[collectionPartitionKeyProperty];
|
let pk = (d as any)[collectionPartitionKeyProperty];
|
||||||
if (typeof pk !== "string" && typeof pk !== "number" && typeof pk !== "boolean") {
|
if (typeof pk !== "string") {
|
||||||
if (Array.isArray(pk) && pk.length > 0) {
|
if (Array.isArray(pk) && pk.length > 0) {
|
||||||
// pk is [{ id: 'id', _value: 'value' }]
|
// pk is [{ id: 'id', _value: 'value' }]
|
||||||
pk = pk[0]["_value"];
|
pk = pk[0]["_value"];
|
||||||
|
|||||||
@@ -82,7 +82,9 @@ export class CommandBarComponentAdapter implements ReactAdapter {
|
|||||||
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
||||||
|
|
||||||
if (this.isNotebookTabActive()) {
|
if (this.isNotebookTabActive()) {
|
||||||
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker"));
|
uiFabricControlButtons.unshift(
|
||||||
|
CommandBarUtil.createMemoryTracker("memoryTracker", this.container.memoryUsageInfo)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as ViewModels from "../../../Contracts/ViewModels";
|
|||||||
import { PlatformType } from "../../../PlatformType";
|
import { PlatformType } from "../../../PlatformType";
|
||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { Areas } from "../../../Common/Constants";
|
import { Areas } from "../../../Common/Constants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
|
||||||
import AddDatabaseIcon from "../../../../images/AddDatabase.svg";
|
import AddDatabaseIcon from "../../../../images/AddDatabase.svg";
|
||||||
import AddCollectionIcon from "../../../../images/AddCollection.svg";
|
import AddCollectionIcon from "../../../../images/AddCollection.svg";
|
||||||
@@ -194,7 +194,7 @@ export class CommandBarComponentButtonFactory {
|
|||||||
buttons.push(fullScreenButton);
|
buttons.push(fullScreenButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!container.hasOwnProperty("isEmulator") || !container.isEmulator) {
|
if (configContext.platform !== Platform.Emulator) {
|
||||||
const label = "Feedback";
|
const label = "Feedback";
|
||||||
const feedbackButtonOptions: CommandButtonComponentProps = {
|
const feedbackButtonOptions: CommandButtonComponentProps = {
|
||||||
iconSrc: FeedbackIcon,
|
iconSrc: FeedbackIcon,
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import { ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar";
|
import _ from "underscore";
|
||||||
import { Dropdown, IDropdownOption, IDropdownStyles } from "office-ui-fabric-react/lib/Dropdown";
|
import * as React from "react";
|
||||||
|
import { Observable } from "knockout";
|
||||||
import { IconType } from "office-ui-fabric-react/lib/Icon";
|
import { IconType } from "office-ui-fabric-react/lib/Icon";
|
||||||
import { IComponentAsProps } from "office-ui-fabric-react/lib/Utilities";
|
import { IComponentAsProps } from "office-ui-fabric-react/lib/Utilities";
|
||||||
import * as React from "react";
|
|
||||||
import _ from "underscore";
|
|
||||||
import ChevronDownIcon from "../../../../images/Chevron_down.svg";
|
|
||||||
import { StyleConstants } from "../../../Common/Constants";
|
import { StyleConstants } from "../../../Common/Constants";
|
||||||
import { ArcadiaMenuPicker } from "../../Controls/Arcadia/ArcadiaMenuPicker";
|
import { ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar";
|
||||||
|
import { Dropdown, IDropdownStyles, IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
|
import ChevronDownIcon from "../../../../images/Chevron_down.svg";
|
||||||
|
import { ArcadiaMenuPicker } from "../../Controls/Arcadia/ArcadiaMenuPicker";
|
||||||
import { MemoryTrackerComponent } from "./MemoryTrackerComponent";
|
import { MemoryTrackerComponent } from "./MemoryTrackerComponent";
|
||||||
|
import { MemoryUsageInfo } from "../../../Contracts/DataModels";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilities for CommandBar
|
* Utilities for CommandBar
|
||||||
@@ -176,10 +178,10 @@ export class CommandBarUtil {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static createMemoryTracker(key: string): ICommandBarItemProps {
|
public static createMemoryTracker(key: string, memoryUsageInfo: Observable<MemoryUsageInfo>): ICommandBarItemProps {
|
||||||
return {
|
return {
|
||||||
key,
|
key,
|
||||||
onRender: () => <MemoryTrackerComponent />
|
onRender: () => <MemoryTrackerComponent memoryUsageInfo={memoryUsageInfo} />
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,71 +1,50 @@
|
|||||||
import React, { FunctionComponent } from "react";
|
import * as React from "react";
|
||||||
import useSWR from "swr";
|
import { Observable, Subscription } from "knockout";
|
||||||
|
import { MemoryUsageInfo } from "../../../Contracts/DataModels";
|
||||||
import { ProgressIndicator } from "office-ui-fabric-react/lib/ProgressIndicator";
|
import { ProgressIndicator } from "office-ui-fabric-react/lib/ProgressIndicator";
|
||||||
import { Spinner, SpinnerSize } from "office-ui-fabric-react/lib/Spinner";
|
import { Spinner, SpinnerSize } from "office-ui-fabric-react/lib/Spinner";
|
||||||
import { Stack } from "office-ui-fabric-react/lib/Stack";
|
import { Stack } from "office-ui-fabric-react/lib/Stack";
|
||||||
import { listConnectionInfo } from "../../../Utils/arm/generatedClients/2020-04-01-notebook/notebookWorkspaces";
|
|
||||||
import { NotebookWorkspaceConnectionInfoResult } from "../../../Utils/arm/generatedClients/2020-04-01-notebook/types";
|
|
||||||
import { userContext } from "../../../UserContext";
|
|
||||||
|
|
||||||
export interface MemoryUsageInfo {
|
interface MemoryTrackerProps {
|
||||||
total: number;
|
memoryUsageInfo: Observable<MemoryUsageInfo>;
|
||||||
free: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const kbInGB = 1048576;
|
export class MemoryTrackerComponent extends React.Component<MemoryTrackerProps> {
|
||||||
|
private memoryUsageInfoSubscription: Subscription;
|
||||||
|
|
||||||
const fetchMemoryInfo = async (_key: unknown, connectionInfo: NotebookWorkspaceConnectionInfoResult) => {
|
public componentDidMount(): void {
|
||||||
const response = await fetch(`${connectionInfo.notebookServerEndpoint}/api/metrics/memory`, {
|
this.memoryUsageInfoSubscription = this.props.memoryUsageInfo.subscribe(() => {
|
||||||
method: "GET",
|
this.forceUpdate();
|
||||||
headers: {
|
});
|
||||||
Authorization: `Token ${connectionInfo.authToken}`,
|
|
||||||
"content-type": "application/json"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(await response.text());
|
|
||||||
}
|
}
|
||||||
const memoryUsageInfo = (await response.json()) as MemoryUsageInfo;
|
|
||||||
return {
|
|
||||||
totalKB: memoryUsageInfo.total,
|
|
||||||
freeKB: memoryUsageInfo.free
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MemoryTrackerComponent: FunctionComponent = () => {
|
public componentWillUnmount(): void {
|
||||||
const { data: connectionInfo } = useSWR(
|
this.memoryUsageInfoSubscription && this.memoryUsageInfoSubscription.dispose();
|
||||||
[
|
}
|
||||||
"notebooksConnectionInfo",
|
|
||||||
userContext.subscriptionId,
|
public render(): JSX.Element {
|
||||||
userContext.resourceGroup,
|
const memoryUsageInfo: MemoryUsageInfo = this.props.memoryUsageInfo();
|
||||||
userContext.databaseAccount.name,
|
if (!memoryUsageInfo) {
|
||||||
"default"
|
return (
|
||||||
],
|
<Stack className="memoryTrackerContainer" horizontal>
|
||||||
(_key, subscriptionId, resourceGroup, accountName, workspace) =>
|
<span>Memory</span>
|
||||||
listConnectionInfo(subscriptionId, resourceGroup, accountName, workspace)
|
<Spinner size={SpinnerSize.medium} />
|
||||||
);
|
</Stack>
|
||||||
const { data } = useSWR(connectionInfo ? ["memoryUsage", connectionInfo] : null, fetchMemoryInfo, {
|
);
|
||||||
refreshInterval: 2000
|
}
|
||||||
});
|
|
||||||
|
const totalGB = memoryUsageInfo.totalKB / 1048576;
|
||||||
|
const usedGB = totalGB - memoryUsageInfo.freeKB / 1048576;
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
return (
|
return (
|
||||||
<Stack className="memoryTrackerContainer" horizontal>
|
<Stack className="memoryTrackerContainer" horizontal>
|
||||||
<span>Memory</span>
|
<span>Memory</span>
|
||||||
<Spinner size={SpinnerSize.medium} />
|
<ProgressIndicator
|
||||||
|
className={usedGB / totalGB > 0.8 ? "lowMemory" : ""}
|
||||||
|
description={usedGB.toFixed(1) + " of " + totalGB.toFixed(1) + " GB"}
|
||||||
|
percentComplete={usedGB / totalGB}
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const totalGB = data.totalKB / kbInGB;
|
}
|
||||||
const usedGB = totalGB - data.freeKB / kbInGB;
|
|
||||||
return (
|
|
||||||
<Stack className="memoryTrackerContainer" horizontal>
|
|
||||||
<span>Memory</span>
|
|
||||||
<ProgressIndicator
|
|
||||||
className={usedGB / totalGB > 0.8 ? "lowMemory" : ""}
|
|
||||||
description={usedGB.toFixed(1) + " of " + totalGB.toFixed(1) + " GB"}
|
|
||||||
percentComplete={usedGB / totalGB}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import { Store, AnyAction, MiddlewareAPI, Middleware, Dispatch } from "redux";
|
|||||||
import configureStore from "./NotebookComponent/store";
|
import configureStore from "./NotebookComponent/store";
|
||||||
|
|
||||||
import { Notification } from "react-notification-system";
|
import { Notification } from "react-notification-system";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
|
||||||
export type KernelSpecsDisplay = { name: string; displayName: string };
|
export type KernelSpecsDisplay = { name: string; displayName: string };
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export class NotebookComponentBootstrapper {
|
|||||||
actions.fetchContentFulfilled({
|
actions.fetchContentFulfilled({
|
||||||
filepath: undefined,
|
filepath: undefined,
|
||||||
model: NotebookComponentBootstrapper.wrapModelIntoContent(name, undefined, content),
|
model: NotebookComponentBootstrapper.wrapModelIntoContent(name, undefined, content),
|
||||||
kernelRef: undefined, // must be undefined or it will be auto-started by the epic
|
kernelRef: createKernelRef(),
|
||||||
contentRef: this.contentRef
|
contentRef: this.contentRef
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import * as Immutable from "immutable";
|
import * as Immutable from "immutable";
|
||||||
import { ActionsObservable, StateObservable } from "redux-observable";
|
import { ActionsObservable, StateObservable } from "redux-observable";
|
||||||
import { Subject, empty } from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
import { toArray } from "rxjs/operators";
|
import { toArray } from "rxjs/operators";
|
||||||
import { makeNotebookRecord } from "@nteract/commutable";
|
import { makeNotebookRecord } from "@nteract/commutable";
|
||||||
import { actions, state } from "@nteract/core";
|
import { actions, state } from "@nteract/core";
|
||||||
import * as sinon from "sinon";
|
import * as sinon from "sinon";
|
||||||
|
|
||||||
import { CdbAppState, makeCdbRecord } from "./types";
|
import { CdbAppState, makeCdbRecord } from "./types";
|
||||||
import { launchWebSocketKernelEpic, autoStartKernelEpic } from "./epics";
|
import { launchWebSocketKernelEpic } from "./epics";
|
||||||
import { NotebookUtil } from "../NotebookUtil";
|
import { NotebookUtil } from "../NotebookUtil";
|
||||||
|
|
||||||
import { sessions } from "rx-jupyter";
|
import { sessions } from "rx-jupyter";
|
||||||
@@ -74,47 +74,46 @@ describe("Extract kernel from notebook", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const initialState = {
|
|
||||||
app: state.makeAppRecord({
|
|
||||||
host: state.makeJupyterHostRecord({
|
|
||||||
type: "jupyter",
|
|
||||||
token: "eh",
|
|
||||||
basePath: "/"
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
comms: state.makeCommsRecord(),
|
|
||||||
config: Immutable.Map({}),
|
|
||||||
core: state.makeStateRecord({
|
|
||||||
kernelRef: "fake",
|
|
||||||
entities: state.makeEntitiesRecord({
|
|
||||||
contents: state.makeContentsRecord({
|
|
||||||
byRef: Immutable.Map({
|
|
||||||
fakeContentRef: state.makeNotebookContentRecord()
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
kernels: state.makeKernelsRecord({
|
|
||||||
byRef: Immutable.Map({
|
|
||||||
fake: state.makeRemoteKernelRecord({
|
|
||||||
type: "websocket",
|
|
||||||
channels: new Subject<any>(),
|
|
||||||
kernelSpecName: "fancy",
|
|
||||||
id: "0"
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
cdb: makeCdbRecord({
|
|
||||||
databaseAccountName: "dbAccountName",
|
|
||||||
defaultExperience: "defaultExperience"
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("launchWebSocketKernelEpic", () => {
|
describe("launchWebSocketKernelEpic", () => {
|
||||||
const createSpy = sinon.spy(sessions, "create");
|
const createSpy = sinon.spy(sessions, "create");
|
||||||
|
|
||||||
const contentRef = "fakeContentRef";
|
const contentRef = "fakeContentRef";
|
||||||
const kernelRef = "fake";
|
const kernelRef = "fake";
|
||||||
|
const initialState = {
|
||||||
|
app: state.makeAppRecord({
|
||||||
|
host: state.makeJupyterHostRecord({
|
||||||
|
type: "jupyter",
|
||||||
|
token: "eh",
|
||||||
|
basePath: "/"
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
comms: state.makeCommsRecord(),
|
||||||
|
config: Immutable.Map({}),
|
||||||
|
core: state.makeStateRecord({
|
||||||
|
kernelRef: "fake",
|
||||||
|
entities: state.makeEntitiesRecord({
|
||||||
|
contents: state.makeContentsRecord({
|
||||||
|
byRef: Immutable.Map({
|
||||||
|
fakeContentRef: state.makeNotebookContentRecord()
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
kernels: state.makeKernelsRecord({
|
||||||
|
byRef: Immutable.Map({
|
||||||
|
fake: state.makeRemoteKernelRecord({
|
||||||
|
type: "websocket",
|
||||||
|
channels: new Subject<any>(),
|
||||||
|
kernelSpecName: "fancy",
|
||||||
|
id: "0"
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
cdb: makeCdbRecord({
|
||||||
|
databaseAccountName: "dbAccountName",
|
||||||
|
defaultExperience: "defaultExperience"
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
it("launches remote kernels", async () => {
|
it("launches remote kernels", async () => {
|
||||||
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
|
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
|
||||||
@@ -491,55 +490,3 @@ describe("launchWebSocketKernelEpic", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("autoStartKernelEpic", () => {
|
|
||||||
const contentRef = "fakeContentRef";
|
|
||||||
const kernelRef = "fake";
|
|
||||||
|
|
||||||
it("automatically starts kernel when content fetch is successful if kernelRef is defined", async () => {
|
|
||||||
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
|
|
||||||
|
|
||||||
const action$ = ActionsObservable.of(
|
|
||||||
actions.fetchContentFulfilled({
|
|
||||||
contentRef,
|
|
||||||
kernelRef,
|
|
||||||
filepath: "filepath",
|
|
||||||
model: {}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const responseActions = await autoStartKernelEpic(action$, state$)
|
|
||||||
.pipe(toArray())
|
|
||||||
.toPromise();
|
|
||||||
|
|
||||||
expect(responseActions).toMatchObject([
|
|
||||||
{
|
|
||||||
type: actions.RESTART_KERNEL,
|
|
||||||
payload: {
|
|
||||||
contentRef,
|
|
||||||
kernelRef,
|
|
||||||
outputHandling: "None"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Don't start kernel when content fetch is successful if kernelRef is not defined", async () => {
|
|
||||||
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
|
|
||||||
|
|
||||||
const action$ = ActionsObservable.of(
|
|
||||||
actions.fetchContentFulfilled({
|
|
||||||
contentRef,
|
|
||||||
kernelRef: undefined,
|
|
||||||
filepath: "filepath",
|
|
||||||
model: {}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const responseActions = await autoStartKernelEpic(action$, state$)
|
|
||||||
.pipe(toArray())
|
|
||||||
.toPromise();
|
|
||||||
|
|
||||||
expect(responseActions).toMatchObject([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { EMPTY, merge, of, timer, concat, Subject, Subscriber, Observable, Observer } from "rxjs";
|
import { empty, merge, of, timer, concat, Subject, Subscriber, Observable, Observer } from "rxjs";
|
||||||
import { webSocket } from "rxjs/webSocket";
|
import { webSocket } from "rxjs/webSocket";
|
||||||
import { ActionsObservable, StateObservable } from "redux-observable";
|
import { ActionsObservable, StateObservable } from "redux-observable";
|
||||||
import { ofType } from "redux-observable";
|
import { ofType } from "redux-observable";
|
||||||
@@ -37,7 +37,7 @@ import * as Constants from "../../../Common/Constants";
|
|||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import * as CdbActions from "./actions";
|
import * as CdbActions from "./actions";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action as TelemetryAction } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action as TelemetryAction } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { CdbAppState } from "./types";
|
import { CdbAppState } from "./types";
|
||||||
import { decryptJWTToken } from "../../../Utils/AuthorizationUtils";
|
import { decryptJWTToken } from "../../../Utils/AuthorizationUtils";
|
||||||
@@ -77,7 +77,7 @@ const addInitialCodeCellEpic = (
|
|||||||
|
|
||||||
// If it's not a notebook, we shouldn't be here
|
// If it's not a notebook, we shouldn't be here
|
||||||
if (!model || model.type !== "notebook") {
|
if (!model || model.type !== "notebook") {
|
||||||
return EMPTY;
|
return empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
const cellOrder = selectors.notebook.cellOrder(model);
|
const cellOrder = selectors.notebook.cellOrder(model);
|
||||||
@@ -90,40 +90,7 @@ const addInitialCodeCellEpic = (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return EMPTY;
|
return empty();
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Automatically start kernel if kernelRef is present.
|
|
||||||
* The kernel is normally lazy-started when a cell is being executed, but a running kernel is
|
|
||||||
* required for code completion to work.
|
|
||||||
* For notebook viewer, there is no kernel
|
|
||||||
* @param action$
|
|
||||||
* @param state$
|
|
||||||
*/
|
|
||||||
export const autoStartKernelEpic = (
|
|
||||||
action$: ActionsObservable<actions.FetchContentFulfilled>,
|
|
||||||
state$: StateObservable<AppState>
|
|
||||||
): Observable<{} | actions.CreateCellBelow> => {
|
|
||||||
return action$.pipe(
|
|
||||||
ofType(actions.FETCH_CONTENT_FULFILLED),
|
|
||||||
mergeMap(action => {
|
|
||||||
const state = state$.value;
|
|
||||||
const { contentRef, kernelRef } = action.payload;
|
|
||||||
|
|
||||||
if (!kernelRef) {
|
|
||||||
return EMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
return of(
|
|
||||||
actions.restartKernel({
|
|
||||||
contentRef,
|
|
||||||
kernelRef,
|
|
||||||
outputHandling: "None"
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -321,7 +288,7 @@ export const launchWebSocketKernelEpic = (
|
|||||||
const state = state$.value;
|
const state = state$.value;
|
||||||
const host = selectors.currentHost(state);
|
const host = selectors.currentHost(state);
|
||||||
if (host.type !== "jupyter") {
|
if (host.type !== "jupyter") {
|
||||||
return EMPTY;
|
return empty();
|
||||||
}
|
}
|
||||||
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host);
|
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host);
|
||||||
serverConfig.userPuid = getUserPuid();
|
serverConfig.userPuid = getUserPuid();
|
||||||
@@ -332,7 +299,7 @@ export const launchWebSocketKernelEpic = (
|
|||||||
|
|
||||||
const content = selectors.content(state, { contentRef });
|
const content = selectors.content(state, { contentRef });
|
||||||
if (!content || content.type !== "notebook") {
|
if (!content || content.type !== "notebook") {
|
||||||
return EMPTY;
|
return empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
let kernelSpecToLaunch = kernelSpecName;
|
let kernelSpecToLaunch = kernelSpecName;
|
||||||
@@ -546,26 +513,26 @@ const changeWebSocketKernelEpic = (
|
|||||||
const state = state$.value;
|
const state = state$.value;
|
||||||
const host = selectors.currentHost(state);
|
const host = selectors.currentHost(state);
|
||||||
if (host.type !== "jupyter") {
|
if (host.type !== "jupyter") {
|
||||||
return EMPTY;
|
return empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host);
|
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host);
|
||||||
if (!oldKernelRef) {
|
if (!oldKernelRef) {
|
||||||
return EMPTY;
|
return empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldKernel = selectors.kernel(state, { kernelRef: oldKernelRef });
|
const oldKernel = selectors.kernel(state, { kernelRef: oldKernelRef });
|
||||||
if (!oldKernel || oldKernel.type !== "websocket") {
|
if (!oldKernel || oldKernel.type !== "websocket") {
|
||||||
return EMPTY;
|
return empty();
|
||||||
}
|
}
|
||||||
const { sessionId } = oldKernel;
|
const { sessionId } = oldKernel;
|
||||||
if (!sessionId) {
|
if (!sessionId) {
|
||||||
return EMPTY;
|
return empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = selectors.content(state, { contentRef });
|
const content = selectors.content(state, { contentRef });
|
||||||
if (!content || content.type !== "notebook") {
|
if (!content || content.type !== "notebook") {
|
||||||
return EMPTY;
|
return empty();
|
||||||
}
|
}
|
||||||
const {
|
const {
|
||||||
filepath,
|
filepath,
|
||||||
@@ -626,7 +593,7 @@ const focusInitialCodeCellEpic = (
|
|||||||
|
|
||||||
// If it's not a notebook, we shouldn't be here
|
// If it's not a notebook, we shouldn't be here
|
||||||
if (!model || model.type !== "notebook") {
|
if (!model || model.type !== "notebook") {
|
||||||
return EMPTY;
|
return empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
const cellOrder = selectors.notebook.cellOrder(model);
|
const cellOrder = selectors.notebook.cellOrder(model);
|
||||||
@@ -641,7 +608,7 @@ const focusInitialCodeCellEpic = (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return EMPTY;
|
return empty();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -694,7 +661,7 @@ const notificationsToUserEpic = (
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return EMPTY;
|
return empty();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -734,7 +701,7 @@ const handleKernelConnectionLostEpic = (
|
|||||||
if (explorer) {
|
if (explorer) {
|
||||||
explorer.showOkModalDialog("kernel restarts", msg);
|
explorer.showOkModalDialog("kernel restarts", msg);
|
||||||
}
|
}
|
||||||
return of(EMPTY);
|
return of(empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
return concat(
|
return concat(
|
||||||
@@ -847,7 +814,7 @@ const closeUnsupportedMimetypesEpic = (
|
|||||||
explorer.showOkModalDialog("File cannot be rendered", msg);
|
explorer.showOkModalDialog("File cannot be rendered", msg);
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
||||||
}
|
}
|
||||||
return EMPTY;
|
return empty();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -875,14 +842,13 @@ const closeContentFailedToFetchEpic = (
|
|||||||
explorer.showOkModalDialog("Failure to load", msg);
|
explorer.showOkModalDialog("Failure to load", msg);
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
||||||
}
|
}
|
||||||
return EMPTY;
|
return empty();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const allEpics = [
|
export const allEpics = [
|
||||||
addInitialCodeCellEpic,
|
addInitialCodeCellEpic,
|
||||||
autoStartKernelEpic,
|
|
||||||
focusInitialCodeCellEpic,
|
focusInitialCodeCellEpic,
|
||||||
notificationsToUserEpic,
|
notificationsToUserEpic,
|
||||||
launchWebSocketKernelEpic,
|
launchWebSocketKernelEpic,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { actions, CoreRecord, reducers as nteractReducers } from "@nteract/core";
|
import { actions, CoreRecord, reducers as nteractReducers } from "@nteract/core";
|
||||||
import { Action } from "redux";
|
import { Action } from "redux";
|
||||||
import { Areas } from "../../../Common/Constants";
|
import { Areas } from "../../../Common/Constants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import * as cdbActions from "./actions";
|
import * as cdbActions from "./actions";
|
||||||
import { CdbRecord } from "./types";
|
import { CdbRecord } from "./types";
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,89 @@
|
|||||||
* Notebook container related stuff
|
* Notebook container related stuff
|
||||||
*/
|
*/
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
|
import * as Constants from "../../Common/Constants";
|
||||||
|
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
import * as Logger from "../../Common/Logger";
|
import * as Logger from "../../Common/Logger";
|
||||||
|
|
||||||
export class NotebookContainerClient {
|
export class NotebookContainerClient {
|
||||||
private reconnectingNotificationId: string;
|
private reconnectingNotificationId: string;
|
||||||
private isResettingWorkspace: boolean;
|
private isResettingWorkspace: boolean;
|
||||||
|
|
||||||
constructor(private notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>) {}
|
constructor(
|
||||||
|
private notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>,
|
||||||
|
private onConnectionLost: () => void,
|
||||||
|
private onMemoryUsageInfoUpdate: (update: DataModels.MemoryUsageInfo) => void
|
||||||
|
) {
|
||||||
|
if (notebookServerInfo() && notebookServerInfo().notebookServerEndpoint) {
|
||||||
|
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
|
||||||
|
} else {
|
||||||
|
const subscription = notebookServerInfo.subscribe((newServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => {
|
||||||
|
if (newServerInfo && newServerInfo.notebookServerEndpoint) {
|
||||||
|
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
|
||||||
|
}
|
||||||
|
subscription.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Heartbeat: each ping schedules another ping
|
||||||
|
*/
|
||||||
|
private scheduleHeartbeat(delayMs: number): void {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.getMemoryUsage()
|
||||||
|
.then(memoryUsageInfo => this.onMemoryUsageInfoUpdate(memoryUsageInfo))
|
||||||
|
.finally(() => this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs));
|
||||||
|
}, delayMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> {
|
||||||
|
if (!this.notebookServerInfo() || !this.notebookServerInfo().notebookServerEndpoint) {
|
||||||
|
const error = "No server endpoint detected";
|
||||||
|
Logger.logError(error, "NotebookContainerClient/getMemoryUsage");
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isResettingWorkspace) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${notebookServerEndpoint}/api/metrics/memory`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
Authorization: authToken,
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
if (this.reconnectingNotificationId) {
|
||||||
|
NotificationConsoleUtils.clearInProgressMessageWithId(this.reconnectingNotificationId);
|
||||||
|
this.reconnectingNotificationId = "";
|
||||||
|
}
|
||||||
|
const memoryUsageInfo = await response.json();
|
||||||
|
if (memoryUsageInfo) {
|
||||||
|
return {
|
||||||
|
totalKB: memoryUsageInfo.total,
|
||||||
|
freeKB: memoryUsageInfo.free
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
} catch (error) {
|
||||||
|
Logger.logError(error, "NotebookContainerClient/getMemoryUsage");
|
||||||
|
if (!this.reconnectingNotificationId) {
|
||||||
|
this.reconnectingNotificationId = NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.InProgress,
|
||||||
|
"Connection lost with Notebook server. Attempting to reconnect..."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.onConnectionLost();
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async resetWorkspace(): Promise<void> {
|
public async resetWorkspace(): Promise<void> {
|
||||||
this.isResettingWorkspace = true;
|
this.isResettingWorkspace = true;
|
||||||
|
|||||||
@@ -9,13 +9,14 @@ import * as Logger from "../../Common/Logger";
|
|||||||
import { HttpStatusCodes, Areas } from "../../Common/Constants";
|
import { HttpStatusCodes, Areas } from "../../Common/Constants";
|
||||||
import { GitHubReposPane } from "../Panes/GitHubReposPane";
|
import { GitHubReposPane } from "../Panes/GitHubReposPane";
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { IContentProvider } from "@nteract/core";
|
import { IContentProvider } from "@nteract/core";
|
||||||
import { NotebookContentProvider } from "./NotebookComponent/NotebookContentProvider";
|
import { NotebookContentProvider } from "./NotebookComponent/NotebookContentProvider";
|
||||||
import { GitHubContentProvider } from "../../GitHub/GitHubContentProvider";
|
import { GitHubContentProvider } from "../../GitHub/GitHubContentProvider";
|
||||||
import { contents } from "rx-jupyter";
|
import { contents } from "rx-jupyter";
|
||||||
import { NotebookContainerClient } from "./NotebookContainerClient";
|
import { NotebookContainerClient } from "./NotebookContainerClient";
|
||||||
|
import { MemoryUsageInfo } from "../../Contracts/DataModels";
|
||||||
import { NotebookContentClient } from "./NotebookContentClient";
|
import { NotebookContentClient } from "./NotebookContentClient";
|
||||||
import { DialogProps } from "../Controls/DialogReactComponent/DialogComponent";
|
import { DialogProps } from "../Controls/DialogReactComponent/DialogComponent";
|
||||||
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
|
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
|
||||||
@@ -75,7 +76,11 @@ export default class NotebookManager {
|
|||||||
contents.JupyterContentProvider
|
contents.JupyterContentProvider
|
||||||
);
|
);
|
||||||
|
|
||||||
this.notebookClient = new NotebookContainerClient(this.params.container.notebookServerInfo);
|
this.notebookClient = new NotebookContainerClient(
|
||||||
|
this.params.container.notebookServerInfo,
|
||||||
|
() => this.params.container.initNotebooks(this.params.container.databaseAccount()),
|
||||||
|
(update: MemoryUsageInfo) => this.params.container.memoryUsageInfo(update)
|
||||||
|
);
|
||||||
|
|
||||||
this.notebookContentClient = new NotebookContentClient(
|
this.notebookContentClient = new NotebookContentClient(
|
||||||
this.params.container.notebookServerInfo,
|
this.params.container.notebookServerInfo,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<!-- Add collection header - Start -->
|
<!-- Add collection header - Start -->
|
||||||
<div class="firstdivbg headerline">
|
<div class="firstdivbg headerline">
|
||||||
<span id="containerTitle" data-bind="text: title"></span>
|
<span id="containerTitle" data-bind="text: title"></span>
|
||||||
<div class="closeImg" id="closeBtnAddCollection" role="button" aria-label="Add collection close pane"
|
<div class="closeImg" id="closeBtnAddCollection" role="button" aria-label="Close pane"
|
||||||
data-bind="click: cancel, event: { keypress: onCloseKeyPress }" tabindex="0">
|
data-bind="click: cancel, event: { keypress: onCloseKeyPress }" tabindex="0">
|
||||||
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
|
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ describe("Add Collection Pane", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
explorer = new Explorer();
|
||||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,15 +9,18 @@ import * as PricingUtils from "../../Utils/PricingUtils";
|
|||||||
import * as SharedConstants from "../../Shared/Constants";
|
import * as SharedConstants from "../../Shared/Constants";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import editable from "../../Common/EditableUtility";
|
import editable from "../../Common/EditableUtility";
|
||||||
import * 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 { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { configContext, Platform } from "../../ConfigContext";
|
import { configContext, Platform } from "../../ConfigContext";
|
||||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||||
|
import { createMongoCollectionWithARM, createMongoCollectionWithProxy } from "../../Common/MongoProxyClient";
|
||||||
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
|
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
|
||||||
import { HashMap } from "../../Common/HashMap";
|
import { HashMap } from "../../Common/HashMap";
|
||||||
import { PlatformType } from "../../PlatformType";
|
import { PlatformType } from "../../PlatformType";
|
||||||
import { refreshCachedResources } from "../../Common/DocumentClientUtilityBase";
|
import { refreshCachedResources, getOrCreateDatabaseAndCollection } from "../../Common/DocumentClientUtilityBase";
|
||||||
import { createCollection } from "../../Common/dataAccess/createCollection";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export interface AddCollectionPaneOptions extends ViewModels.PaneOptions {
|
export interface AddCollectionPaneOptions extends ViewModels.PaneOptions {
|
||||||
isPreferredApiTable: ko.Computed<boolean>;
|
isPreferredApiTable: ko.Computed<boolean>;
|
||||||
@@ -326,7 +329,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
|
|
||||||
this.canRequestSupport = ko.pureComputed(() => {
|
this.canRequestSupport = ko.pureComputed(() => {
|
||||||
if (
|
if (
|
||||||
!this.container.isEmulator &&
|
configContext.platform !== Platform.Emulator &&
|
||||||
!this.container.isTryCosmosDBSubscription() &&
|
!this.container.isTryCosmosDBSubscription() &&
|
||||||
this.container.getPlatformType() !== PlatformType.Portal
|
this.container.getPlatformType() !== PlatformType.Portal
|
||||||
) {
|
) {
|
||||||
@@ -338,7 +341,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.costsVisible = ko.pureComputed(() => {
|
this.costsVisible = ko.pureComputed(() => {
|
||||||
return !this.container.isEmulator;
|
return configContext.platform !== Platform.Emulator;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.maxCollectionsReached = ko.computed<boolean>(() => {
|
this.maxCollectionsReached = ko.computed<boolean>(() => {
|
||||||
@@ -808,6 +811,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
|
|
||||||
let databaseId: string = this.databaseCreateNew() ? this.databaseId().trim() : this.databaseId();
|
let databaseId: string = this.databaseCreateNew() ? this.databaseId().trim() : this.databaseId();
|
||||||
let collectionId: string = this.collectionId().trim();
|
let collectionId: string = this.collectionId().trim();
|
||||||
|
let rupm: boolean = this.rupm() === Constants.RUPMStates.on;
|
||||||
|
|
||||||
let indexingPolicy: DataModels.IndexingPolicy;
|
let indexingPolicy: DataModels.IndexingPolicy;
|
||||||
// todo - remove mongo indexing policy ticket # 616274
|
// todo - remove mongo indexing policy ticket # 616274
|
||||||
@@ -824,28 +828,130 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.formErrors("");
|
this.formErrors("");
|
||||||
|
|
||||||
this.isExecuting(true);
|
this.isExecuting(true);
|
||||||
|
|
||||||
const databaseLevelThroughput: boolean = this.databaseCreateNew()
|
const createRequest: DataModels.CreateDatabaseAndCollectionRequest = {
|
||||||
? this.databaseCreateNewShared()
|
|
||||||
: this.databaseHasSharedOffer() && !this.collectionWithThroughputInShared();
|
|
||||||
const autoPilotMaxThroughput: number = databaseLevelThroughput
|
|
||||||
? this.isSharedAutoPilotSelected() && this.sharedAutoPilotThroughput()
|
|
||||||
: this.isAutoPilotSelected() && this.autoPilotThroughput();
|
|
||||||
const createCollectionParams: DataModels.CreateCollectionParams = {
|
|
||||||
createNewDatabase: this.databaseCreateNew(),
|
|
||||||
collectionId,
|
collectionId,
|
||||||
databaseId,
|
databaseId,
|
||||||
databaseLevelThroughput,
|
|
||||||
offerThroughput,
|
offerThroughput,
|
||||||
analyticalStorageTtl: this._getAnalyticalStorageTtl(),
|
databaseLevelThroughput: this.databaseHasSharedOffer() && !this.collectionWithThroughputInShared(),
|
||||||
autoPilotMaxThroughput,
|
rupmEnabled: rupm,
|
||||||
indexingPolicy,
|
|
||||||
partitionKey,
|
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.isExecuting(false);
|
||||||
this.close();
|
this.close();
|
||||||
@@ -943,7 +1049,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
const defaultThroughput = this.container.collectionCreationDefaults.throughput;
|
const defaultThroughput = this.container.collectionCreationDefaults.throughput;
|
||||||
this.throughputSinglePartition(defaultThroughput.fixed);
|
this.throughputSinglePartition(defaultThroughput.fixed);
|
||||||
this.throughputMultiPartition(
|
this.throughputMultiPartition(
|
||||||
AddCollectionUtility.getMaxThroughput(this.container.collectionCreationDefaults, this.container)
|
AddCollectionUtility.Utilities.getMaxThroughput(this.container.collectionCreationDefaults, this.container)
|
||||||
);
|
);
|
||||||
|
|
||||||
this.throughputDatabase(defaultThroughput.shared);
|
this.throughputDatabase(defaultThroughput.shared);
|
||||||
@@ -1128,6 +1234,35 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
private _getAutopilotSettings(): DataModels.RpOptions {
|
||||||
|
if (
|
||||||
|
(!this.hasAutoPilotV2FeatureFlag() &&
|
||||||
|
this.databaseCreateNewShared() &&
|
||||||
|
this.isSharedAutoPilotSelected() &&
|
||||||
|
this.sharedAutoPilotThroughput()) ||
|
||||||
|
(this.hasAutoPilotV2FeatureFlag() &&
|
||||||
|
this.databaseCreateNewShared() &&
|
||||||
|
this.isSharedAutoPilotSelected() &&
|
||||||
|
this.selectedSharedAutoPilotTier())
|
||||||
|
) {
|
||||||
|
return !this.hasAutoPilotV2FeatureFlag()
|
||||||
|
? {
|
||||||
|
[Constants.HttpHeaders.autoPilotThroughput]: { maxThroughput: this.sharedAutoPilotThroughput() * 1 }
|
||||||
|
}
|
||||||
|
: { [Constants.HttpHeaders.autoPilotTier]: this.selectedSharedAutoPilotTier().toString() };
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.autoPilotThroughput()) ||
|
||||||
|
(this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.selectedAutoPilotTier())
|
||||||
|
) {
|
||||||
|
return !this.hasAutoPilotV2FeatureFlag()
|
||||||
|
? {
|
||||||
|
[Constants.HttpHeaders.autoPilotThroughput]: { maxThroughput: this.autoPilotThroughput() * 1 }
|
||||||
|
}
|
||||||
|
: { [Constants.HttpHeaders.autoPilotTier]: this.selectedAutoPilotTier().toString() };
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
private _calculateNumberOfPartitions(): number {
|
private _calculateNumberOfPartitions(): number {
|
||||||
// Note: this will not validate properly on accounts that have been set up for custom partitioning,
|
// Note: this will not validate properly on accounts that have been set up for custom partitioning,
|
||||||
@@ -1167,19 +1302,17 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
|
|
||||||
private _updateThroughputLimitByCollectionStorage() {
|
private _updateThroughputLimitByCollectionStorage() {
|
||||||
const storage = this.storage();
|
const storage = this.storage();
|
||||||
const minThroughputRU =
|
const minThroughputRU = AddCollectionUtility.Utilities.getMinRUForStorageOption(
|
||||||
storage === SharedConstants.CollectionCreation.storage10Gb
|
this.container.collectionCreationDefaults,
|
||||||
? SharedConstants.CollectionCreation.DefaultCollectionRUs400
|
storage
|
||||||
: this.container.collectionCreationDefaults.throughput.unlimitedmin;
|
);
|
||||||
|
|
||||||
let maxThroughputRU;
|
let maxThroughputRU = AddCollectionUtility.Utilities.getMaxRUForStorageOption(
|
||||||
|
this.container.collectionCreationDefaults,
|
||||||
|
storage
|
||||||
|
);
|
||||||
if (this.isTryCosmosDBSubscription()) {
|
if (this.isTryCosmosDBSubscription()) {
|
||||||
maxThroughputRU = Constants.TryCosmosExperience.maxRU;
|
maxThroughputRU = Constants.TryCosmosExperience.maxRU;
|
||||||
} else {
|
|
||||||
maxThroughputRU =
|
|
||||||
storage === SharedConstants.CollectionCreation.storage10Gb
|
|
||||||
? SharedConstants.CollectionCreation.DefaultCollectionRUs10K
|
|
||||||
: this.container.collectionCreationDefaults.throughput.unlimitedmax;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.minThroughputRU(minThroughputRU);
|
this.minThroughputRU(minThroughputRU);
|
||||||
|
|||||||
@@ -40,10 +40,7 @@ describe("Add Database Pane", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer({
|
explorer = new Explorer();
|
||||||
notificationsClient: null,
|
|
||||||
isEmulator: false
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be true if subscription type is Benefits", () => {
|
it("should be true if subscription type is Benefits", () => {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import * as AddCollectionUtility from "../../Shared/AddCollectionUtility";
|
||||||
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
|
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
@@ -7,11 +8,16 @@ import * as PricingUtils from "../../Utils/PricingUtils";
|
|||||||
import * as SharedConstants from "../../Shared/Constants";
|
import * as SharedConstants from "../../Shared/Constants";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import editable from "../../Common/EditableUtility";
|
import editable from "../../Common/EditableUtility";
|
||||||
import * 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 { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import { AddDbUtilities } from "../../Shared/AddDatabaseUtility";
|
||||||
|
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
|
||||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||||
import { createDatabase } from "../../Common/dataAccess/createDatabase";
|
|
||||||
import { PlatformType } from "../../PlatformType";
|
import { PlatformType } from "../../PlatformType";
|
||||||
|
import { refreshCachedOffers, refreshCachedResources, createDatabase } from "../../Common/DocumentClientUtilityBase";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
import { configContext, Platform } from "../../ConfigContext";
|
||||||
|
|
||||||
export default class AddDatabasePane extends ContextualPaneBase {
|
export default class AddDatabasePane extends ContextualPaneBase {
|
||||||
public defaultExperience: ko.Computed<string>;
|
public defaultExperience: ko.Computed<string>;
|
||||||
@@ -174,7 +180,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
|
|
||||||
this.canRequestSupport = ko.pureComputed(() => {
|
this.canRequestSupport = ko.pureComputed(() => {
|
||||||
if (
|
if (
|
||||||
!this.container.isEmulator &&
|
configContext.platform !== Platform.Emulator &&
|
||||||
!this.container.isTryCosmosDBSubscription() &&
|
!this.container.isTryCosmosDBSubscription() &&
|
||||||
this.container.getPlatformType() !== PlatformType.Portal
|
this.container.getPlatformType() !== PlatformType.Portal
|
||||||
) {
|
) {
|
||||||
@@ -197,7 +203,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.costsVisible = ko.pureComputed(() => {
|
this.costsVisible = ko.pureComputed(() => {
|
||||||
return !this.container.isEmulator;
|
return configContext.platform !== Platform.Emulator;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => {
|
this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => {
|
||||||
@@ -299,23 +305,76 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
this.formErrors("");
|
this.formErrors("");
|
||||||
this.isExecuting(true);
|
this.isExecuting(true);
|
||||||
|
|
||||||
const createDatabaseParams: DataModels.CreateDatabaseParams = {
|
const createDatabaseParameters: DataModels.RpParameters = {
|
||||||
autoPilotMaxThroughput: this.maxAutoPilotThroughputSet(),
|
db: addDatabasePaneStartMessage.database.id,
|
||||||
databaseId: addDatabasePaneStartMessage.database.id,
|
st: addDatabasePaneStartMessage.database.shared,
|
||||||
databaseLevelThroughput: addDatabasePaneStartMessage.database.shared,
|
offerThroughput: addDatabasePaneStartMessage.offerThroughput,
|
||||||
offerThroughput: addDatabasePaneStartMessage.offerThroughput
|
sid: userContext.subscriptionId,
|
||||||
|
rg: userContext.resourceGroup,
|
||||||
|
dba: addDatabasePaneStartMessage.databaseAccountName
|
||||||
};
|
};
|
||||||
|
|
||||||
createDatabase(createDatabaseParams).then(
|
const autopilotSettings = this._getAutopilotSettings();
|
||||||
(database: DataModels.Database) => {
|
|
||||||
this._onCreateDatabaseSuccess(offerThroughput, startKey);
|
if (this.container.isPreferredApiCassandra()) {
|
||||||
},
|
this._createKeyspace(createDatabaseParameters, autopilotSettings, startKey);
|
||||||
(reason: any) => {
|
} else if (this.container.isPreferredApiMongoDB() && EnvironmentUtility.isAadUser()) {
|
||||||
this._onCreateDatabaseFailure(reason, offerThroughput, reason);
|
this._createMongoDatabase(createDatabaseParameters, autopilotSettings, startKey);
|
||||||
|
} else if (this.container.isPreferredApiGraph() && EnvironmentUtility.isAadUser()) {
|
||||||
|
this._createGremlinDatabase(createDatabaseParameters, autopilotSettings, startKey);
|
||||||
|
} else if (this.container.isPreferredApiDocumentDB() && EnvironmentUtility.isAadUser()) {
|
||||||
|
this._createSqlDatabase(createDatabaseParameters, autopilotSettings, startKey);
|
||||||
|
} else {
|
||||||
|
this._createDatabase(offerThroughput, startKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createSqlDatabase(
|
||||||
|
createDatabaseParameters: DataModels.RpParameters,
|
||||||
|
autoPilotSettings: DataModels.RpOptions,
|
||||||
|
startKey: number
|
||||||
|
) {
|
||||||
|
AddDbUtilities.createSqlDatabase(this.container.armEndpoint(), createDatabaseParameters, autoPilotSettings).then(
|
||||||
|
() => {
|
||||||
|
Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => {
|
||||||
|
this._onCreateDatabaseSuccess(createDatabaseParameters.offerThroughput, startKey);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _createMongoDatabase(
|
||||||
|
createDatabaseParameters: DataModels.RpParameters,
|
||||||
|
autoPilotSettings: DataModels.RpOptions,
|
||||||
|
startKey: number
|
||||||
|
) {
|
||||||
|
AddDbUtilities.createMongoDatabaseWithARM(
|
||||||
|
this.container.armEndpoint(),
|
||||||
|
createDatabaseParameters,
|
||||||
|
autoPilotSettings
|
||||||
|
).then(() => {
|
||||||
|
Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => {
|
||||||
|
this._onCreateDatabaseSuccess(createDatabaseParameters.offerThroughput, startKey);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createGremlinDatabase(
|
||||||
|
createDatabaseParameters: DataModels.RpParameters,
|
||||||
|
autoPilotSettings: DataModels.RpOptions,
|
||||||
|
startKey: number
|
||||||
|
) {
|
||||||
|
AddDbUtilities.createGremlinDatabase(
|
||||||
|
this.container.armEndpoint(),
|
||||||
|
createDatabaseParameters,
|
||||||
|
autoPilotSettings
|
||||||
|
).then(() => {
|
||||||
|
Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => {
|
||||||
|
this._onCreateDatabaseSuccess(createDatabaseParameters.offerThroughput, startKey);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public resetData() {
|
public resetData() {
|
||||||
this.databaseId("");
|
this.databaseId("");
|
||||||
this.databaseCreateNewShared(this.getSharedThroughputDefault());
|
this.databaseCreateNewShared(this.getSharedThroughputDefault());
|
||||||
@@ -338,6 +397,72 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _createDatabase(offerThroughput: number, telemetryStartKey: number): void {
|
||||||
|
const autoPilot: DataModels.AutoPilotCreationSettings = this._isAutoPilotSelectedAndWhatTier();
|
||||||
|
const createRequest: DataModels.CreateDatabaseRequest = {
|
||||||
|
databaseId: this.databaseId().trim(),
|
||||||
|
offerThroughput,
|
||||||
|
databaseLevelThroughput: this.databaseCreateNewShared(),
|
||||||
|
autoPilot,
|
||||||
|
hasAutoPilotV2FeatureFlag: this.hasAutoPilotV2FeatureFlag()
|
||||||
|
};
|
||||||
|
createDatabase(createRequest).then(
|
||||||
|
(database: DataModels.Database) => {
|
||||||
|
this._onCreateDatabaseSuccess(offerThroughput, telemetryStartKey);
|
||||||
|
},
|
||||||
|
(reason: any) => {
|
||||||
|
this._onCreateDatabaseFailure(reason, offerThroughput, reason);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createKeyspace(
|
||||||
|
createDatabaseParameters: DataModels.RpParameters,
|
||||||
|
autoPilotSettings: DataModels.RpOptions,
|
||||||
|
startKey: number
|
||||||
|
): void {
|
||||||
|
if (EnvironmentUtility.isAadUser()) {
|
||||||
|
this._createKeyspaceUsingRP(this.container.armEndpoint(), createDatabaseParameters, autoPilotSettings, startKey);
|
||||||
|
} else {
|
||||||
|
this._createKeyspaceUsingProxy(createDatabaseParameters.offerThroughput, startKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createKeyspaceUsingProxy(offerThroughput: number, telemetryStartKey: number): void {
|
||||||
|
const provisionThroughputQueryPart: string = this.databaseCreateNewShared()
|
||||||
|
? `AND cosmosdb_provisioned_throughput=${offerThroughput}`
|
||||||
|
: "";
|
||||||
|
const createKeyspaceQuery: string = `CREATE KEYSPACE ${this.databaseId().trim()} WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 } ${provisionThroughputQueryPart};`;
|
||||||
|
(this.container.tableDataClient as CassandraAPIDataClient)
|
||||||
|
.createKeyspace(
|
||||||
|
this.container.databaseAccount().properties.cassandraEndpoint,
|
||||||
|
this.container.databaseAccount().id,
|
||||||
|
this.container,
|
||||||
|
createKeyspaceQuery
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
() => {
|
||||||
|
this._onCreateDatabaseSuccess(offerThroughput, telemetryStartKey);
|
||||||
|
},
|
||||||
|
(reason: any) => {
|
||||||
|
this._onCreateDatabaseFailure(reason, offerThroughput, telemetryStartKey);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createKeyspaceUsingRP(
|
||||||
|
armEndpoint: string,
|
||||||
|
createKeyspaceParameters: DataModels.RpParameters,
|
||||||
|
autoPilotSettings: DataModels.RpOptions,
|
||||||
|
startKey: number
|
||||||
|
): void {
|
||||||
|
AddDbUtilities.createCassandraKeyspace(armEndpoint, createKeyspaceParameters, autoPilotSettings).then(() => {
|
||||||
|
Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => {
|
||||||
|
this._onCreateDatabaseSuccess(createKeyspaceParameters.offerThroughput, startKey);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private _onCreateDatabaseSuccess(offerThroughput: number, startKey: number): void {
|
private _onCreateDatabaseSuccess(offerThroughput: number, startKey: number): void {
|
||||||
this.isExecuting(false);
|
this.isExecuting(false);
|
||||||
this.close();
|
this.close();
|
||||||
@@ -458,6 +583,20 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _getAutopilotSettings(): DataModels.RpOptions {
|
||||||
|
if (
|
||||||
|
(!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.maxAutoPilotThroughputSet()) ||
|
||||||
|
(this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.selectedAutoPilotTier())
|
||||||
|
) {
|
||||||
|
return !this.hasAutoPilotV2FeatureFlag()
|
||||||
|
? {
|
||||||
|
[Constants.HttpHeaders.autoPilotThroughput]: { maxThroughput: this.maxAutoPilotThroughputSet() * 1 }
|
||||||
|
}
|
||||||
|
: { [Constants.HttpHeaders.autoPilotTier]: this.selectedAutoPilotTier().toString() };
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
private _updateThroughputLimitByDatabase() {
|
private _updateThroughputLimitByDatabase() {
|
||||||
const throughputDefaults = this.container.collectionCreationDefaults.throughput;
|
const throughputDefaults = this.container.collectionCreationDefaults.throughput;
|
||||||
this.throughput(throughputDefaults.shared);
|
this.throughput(throughputDefaults.shared);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Areas } from "../../Common/Constants";
|
|||||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||||
import * as Logger from "../../Common/Logger";
|
import * as Logger from "../../Common/Logger";
|
||||||
import { QueriesGridComponentAdapter } from "../Controls/QueriesGridReactComponent/QueriesGridComponentAdapter";
|
import { QueriesGridComponentAdapter } from "../Controls/QueriesGridReactComponent/QueriesGridComponentAdapter";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import QueryTab from "../Tabs/QueryTab";
|
import QueryTab from "../Tabs/QueryTab";
|
||||||
|
|
||||||
export class BrowseQueriesPane extends ContextualPaneBase {
|
export class BrowseQueriesPane extends ContextualPaneBase {
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ import * as ko from "knockout";
|
|||||||
import * as PricingUtils from "../../Utils/PricingUtils";
|
import * as PricingUtils from "../../Utils/PricingUtils";
|
||||||
import * as SharedConstants from "../../Shared/Constants";
|
import * as SharedConstants from "../../Shared/Constants";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
|
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
|
||||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||||
import { HashMap } from "../../Common/HashMap";
|
import { HashMap } from "../../Common/HashMap";
|
||||||
|
import { configContext, Platform } from "../../ConfigContext";
|
||||||
|
|
||||||
export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||||
public createTableQuery: ko.Observable<string>;
|
public createTableQuery: ko.Observable<string>;
|
||||||
@@ -231,11 +232,11 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.costsVisible = ko.pureComputed(() => {
|
this.costsVisible = ko.pureComputed(() => {
|
||||||
return !this.container.isEmulator;
|
return configContext.platform !== Platform.Emulator;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.canRequestSupport = ko.pureComputed(() => {
|
this.canRequestSupport = ko.pureComputed(() => {
|
||||||
if (!this.container.isEmulator && !this.container.isTryCosmosDBSubscription()) {
|
if (configContext.platform !== Platform.Emulator && !this.container.isTryCosmosDBSubscription()) {
|
||||||
const offerThroughput: number = this.throughput();
|
const offerThroughput: number = this.throughput();
|
||||||
return offerThroughput <= 100000;
|
return offerThroughput <= 100000;
|
||||||
}
|
}
|
||||||
@@ -494,7 +495,9 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
this.selectedSharedAutoPilotTier(null);
|
this.selectedSharedAutoPilotTier(null);
|
||||||
this.selectedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
this.selectedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||||
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||||
this.throughput(AddCollectionUtility.getMaxThroughput(this.container.collectionCreationDefaults, this.container));
|
this.throughput(
|
||||||
|
AddCollectionUtility.Utilities.getMaxThroughput(this.container.collectionCreationDefaults, this.container)
|
||||||
|
);
|
||||||
this.keyspaceThroughput(throughputDefaults.shared);
|
this.keyspaceThroughput(throughputDefaults.shared);
|
||||||
this.maxThroughputRU(throughputDefaults.unlimitedmax);
|
this.maxThroughputRU(throughputDefaults.unlimitedmax);
|
||||||
this.minThroughputRU(throughputDefaults.unlimitedmin);
|
this.minThroughputRU(throughputDefaults.unlimitedmin);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as Constants from "../../Common/Constants";
|
|||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { KeyCodes } from "../../Common/Constants";
|
import { KeyCodes } from "../../Common/Constants";
|
||||||
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
|
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
|
|
||||||
// TODO: Use specific actions for logging telemetry data
|
// TODO: Use specific actions for logging telemetry data
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
|
|||||||
import DeleteCollectionConfirmationPane from "./DeleteCollectionConfirmationPane";
|
import DeleteCollectionConfirmationPane from "./DeleteCollectionConfirmationPane";
|
||||||
import DeleteFeedback from "../../Common/DeleteFeedback";
|
import DeleteFeedback from "../../Common/DeleteFeedback";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { TreeNode } from "../../Contracts/ViewModels";
|
import { TreeNode } from "../../Contracts/ViewModels";
|
||||||
import { deleteCollection } from "../../Common/dataAccess/deleteCollection";
|
import { deleteCollection } from "../../Common/dataAccess/deleteCollection";
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
explorer = new Explorer();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be true if 1 database and 1 collection", () => {
|
it("should be true if 1 database and 1 collection", () => {
|
||||||
@@ -56,7 +56,7 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
|
|
||||||
describe("shouldRecordFeedback()", () => {
|
describe("shouldRecordFeedback()", () => {
|
||||||
it("should return true if last collection and database does not have shared throughput else false", () => {
|
it("should return true if last collection and database does not have shared throughput else false", () => {
|
||||||
let fakeExplorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
let fakeExplorer = new Explorer();
|
||||||
fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false);
|
fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false);
|
||||||
fakeExplorer.refreshAllDatabases = () => Q.resolve();
|
fakeExplorer.refreshAllDatabases = () => Q.resolve();
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { ContextualPaneBase } from "./ContextualPaneBase";
|
|||||||
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
||||||
import DeleteFeedback from "../../Common/DeleteFeedback";
|
import DeleteFeedback from "../../Common/DeleteFeedback";
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { deleteCollection } from "../../Common/dataAccess/deleteCollection";
|
import { deleteCollection } from "../../Common/dataAccess/deleteCollection";
|
||||||
|
|
||||||
export default class DeleteCollectionConfirmationPane extends ContextualPaneBase {
|
export default class DeleteCollectionConfirmationPane extends ContextualPaneBase {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import * as ViewModels from "../../Contracts/ViewModels";
|
|||||||
import DeleteDatabaseConfirmationPane from "./DeleteDatabaseConfirmationPane";
|
import DeleteDatabaseConfirmationPane from "./DeleteDatabaseConfirmationPane";
|
||||||
import DeleteFeedback from "../../Common/DeleteFeedback";
|
import DeleteFeedback from "../../Common/DeleteFeedback";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { TreeNode } from "../../Contracts/ViewModels";
|
import { TreeNode } from "../../Contracts/ViewModels";
|
||||||
import { TabsManager } from "../Tabs/TabsManager";
|
import { TabsManager } from "../Tabs/TabsManager";
|
||||||
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
|
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
|
||||||
@@ -22,7 +22,7 @@ describe("Delete Database Confirmation Pane", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
explorer = new Explorer();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be true if only 1 database", () => {
|
it("should be true if only 1 database", () => {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility"
|
|||||||
import DeleteFeedback from "../../Common/DeleteFeedback";
|
import DeleteFeedback from "../../Common/DeleteFeedback";
|
||||||
|
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
|
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
|
||||||
|
|
||||||
export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
|
export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export interface GenericRightPaneProps {
|
|||||||
onSubmit: () => void;
|
onSubmit: () => void;
|
||||||
submitButtonText: string;
|
submitButtonText: string;
|
||||||
title: string;
|
title: string;
|
||||||
isSubmitButtonHidden?: boolean;
|
isSubmitButtonVisible?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenericRightPaneState {
|
export interface GenericRightPaneState {
|
||||||
@@ -108,7 +108,7 @@ export class GenericRightPaneComponent extends React.Component<GenericRightPaneP
|
|||||||
<div className="paneFooter">
|
<div className="paneFooter">
|
||||||
<div className="leftpanel-okbut">
|
<div className="leftpanel-okbut">
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
style={{ visibility: this.props.isSubmitButtonHidden ? "hidden" : "visible" }}
|
style={{ visibility: this.props.isSubmitButtonVisible ? "visible" : "hidden" }}
|
||||||
ariaLabel="Submit"
|
ariaLabel="Submit"
|
||||||
title="Submit"
|
title="Submit"
|
||||||
onClick={this.props.onSubmit}
|
onClick={this.props.onSubmit}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import * as ViewModels from "../../Contracts/ViewModels";
|
|||||||
import { GitHubClient, IGitHubPageInfo, IGitHubRepo } from "../../GitHub/GitHubClient";
|
import { GitHubClient, IGitHubPageInfo, IGitHubRepo } from "../../GitHub/GitHubClient";
|
||||||
import { IPinnedRepo, JunoClient } from "../../Juno/JunoClient";
|
import { IPinnedRepo, JunoClient } from "../../Juno/JunoClient";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||||
import { JunoUtils } from "../../Utils/JunoUtils";
|
import { JunoUtils } from "../../Utils/JunoUtils";
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
|
|||||||
submitButtonText: "Publish",
|
submitButtonText: "Publish",
|
||||||
onClose: () => this.close(),
|
onClose: () => this.close(),
|
||||||
onSubmit: () => this.submit(),
|
onSubmit: () => this.submit(),
|
||||||
isSubmitButtonHidden: !this.isCodeOfConductAccepted
|
isSubmitButtonVisible: this.isCodeOfConductAccepted
|
||||||
};
|
};
|
||||||
|
|
||||||
const publishNotebookPaneProps: PublishNotebookPaneProps = {
|
const publishNotebookPaneProps: PublishNotebookPaneProps = {
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ export class PublishNotebookPaneComponent extends React.Component<PublishNoteboo
|
|||||||
<GalleryCardComponent
|
<GalleryCardComponent
|
||||||
data={{
|
data={{
|
||||||
id: undefined,
|
id: undefined,
|
||||||
name: this.state.notebookName,
|
name: this.props.notebookName,
|
||||||
description: this.state.notebookDescription,
|
description: this.state.notebookDescription,
|
||||||
gitSha: undefined,
|
gitSha: undefined,
|
||||||
tags: this.state.notebookTags.split(","),
|
tags: this.state.notebookTags.split(","),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
|||||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import QueryTab from "../Tabs/QueryTab";
|
import QueryTab from "../Tabs/QueryTab";
|
||||||
|
|
||||||
export class SaveQueryPane extends ContextualPaneBase {
|
export class SaveQueryPane extends ContextualPaneBase {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ describe("Settings Pane", () => {
|
|||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
explorer = new Explorer();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be true for SQL API", () => {
|
it("should be true for SQL API", () => {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Areas, KeyCodes } from "../../Common/Constants";
|
|||||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
|
|
||||||
export class SetupNotebooksPane extends ContextualPaneBase {
|
export class SetupNotebooksPane extends ContextualPaneBase {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { DirectoryListProps } from "../Controls/Directory/DirectoryListComponent
|
|||||||
import { DefaultDirectoryDropdownProps } from "../Controls/Directory/DefaultDirectoryDropdownComponent";
|
import { DefaultDirectoryDropdownProps } from "../Controls/Directory/DefaultDirectoryDropdownComponent";
|
||||||
import { DirectoryComponentAdapter } from "../Controls/Directory/DirectoryComponentAdapter";
|
import { DirectoryComponentAdapter } from "../Controls/Directory/DirectoryComponentAdapter";
|
||||||
import SwitchDirectoryPaneTemplate from "./SwitchDirectoryPane.html";
|
import SwitchDirectoryPaneTemplate from "./SwitchDirectoryPane.html";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
|
||||||
class PaneComponent {
|
class PaneComponent {
|
||||||
constructor(data: any) {
|
constructor(data: any) {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export class UploadItemsPaneAdapter implements ReactAdapter {
|
|||||||
formErrorDetail: this.formErrorDetail,
|
formErrorDetail: this.formErrorDetail,
|
||||||
id: "uploaditemspane",
|
id: "uploaditemspane",
|
||||||
isExecuting: this.isExecuting,
|
isExecuting: this.isExecuting,
|
||||||
|
isSubmitButtonVisible: true,
|
||||||
title: "Upload Items",
|
title: "Upload Items",
|
||||||
submitButtonText: "Upload",
|
submitButtonText: "Upload",
|
||||||
onClose: () => this.close(),
|
onClose: () => this.close(),
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export class SplashScreenComponent extends React.Component<SplashScreenComponent
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role="button"
|
role="button"
|
||||||
>
|
>
|
||||||
<img src={item.iconSrc} alt="" />
|
<img src={item.iconSrc} alt={item.title} />
|
||||||
<div className="legendContainer">
|
<div className="legendContainer">
|
||||||
<div className="legend">{item.title}</div>
|
<div className="legend">{item.title}</div>
|
||||||
<div className="description">{item.description}</div>
|
<div className="description">{item.description}</div>
|
||||||
@@ -66,7 +66,7 @@ export class SplashScreenComponent extends React.Component<SplashScreenComponent
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role="button"
|
role="button"
|
||||||
>
|
>
|
||||||
<img src={item.iconSrc} alt="" />
|
<img src={item.iconSrc} alt={item.title} />
|
||||||
<span className="oneLineContent" title={item.info}>
|
<span className="oneLineContent" title={item.info}>
|
||||||
{item.title}
|
{item.title}
|
||||||
</span>
|
</span>
|
||||||
@@ -79,7 +79,7 @@ export class SplashScreenComponent extends React.Component<SplashScreenComponent
|
|||||||
<ul>
|
<ul>
|
||||||
{this.props.recentItems.map((item: SplashScreenItem, index: number) => (
|
{this.props.recentItems.map((item: SplashScreenItem, index: number) => (
|
||||||
<li key={`${item.title}${item.description}${index}`}>
|
<li key={`${item.title}${item.description}${index}`}>
|
||||||
<img src={item.iconSrc} alt="" />
|
<img src={item.iconSrc} alt={item.title} />
|
||||||
<span className="twoLineContent">
|
<span className="twoLineContent">
|
||||||
<Link onClick={item.onClick} title={item.info}>
|
<Link onClick={item.onClick} title={item.info}>
|
||||||
{item.title}
|
{item.title}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import Explorer from "../Explorer";
|
|||||||
jest.mock("../Explorer");
|
jest.mock("../Explorer");
|
||||||
|
|
||||||
const createExplorer = () => {
|
const createExplorer = () => {
|
||||||
const mock = new Explorer({} as any);
|
const mock = new Explorer();
|
||||||
mock.selectedNode = ko.observable();
|
mock.selectedNode = ko.observable();
|
||||||
mock.isNotebookEnabled = ko.observable(false);
|
mock.isNotebookEnabled = ko.observable(false);
|
||||||
mock.addCollectionText = ko.observable("add collection");
|
mock.addCollectionText = ko.observable("add collection");
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import * as CommonConstants from "../../../Common/Constants";
|
|||||||
import * as Constants from "../Constants";
|
import * as Constants from "../Constants";
|
||||||
import * as Entities from "../Entities";
|
import * as Entities from "../Entities";
|
||||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
|
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
|
||||||
|
|
||||||
// This is the format of the data we will have to pass to Datatable render callback,
|
// This is the format of the data we will have to pass to Datatable render callback,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import * as Utilities from "../Utilities";
|
|||||||
import * as Entities from "../Entities";
|
import * as Entities from "../Entities";
|
||||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
||||||
import * as TableEntityProcessor from "../TableEntityProcessor";
|
import * as TableEntityProcessor from "../TableEntityProcessor";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import * as ErrorParserUtility from "../../../Common/ErrorParserUtility";
|
import * as ErrorParserUtility from "../../../Common/ErrorParserUtility";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import * as HeadersUtility from "../../Common/HeadersUtility";
|
|||||||
import TabsBase from "./TabsBase";
|
import TabsBase from "./TabsBase";
|
||||||
import { DocumentsGridMetrics } from "../../Common/Constants";
|
import { DocumentsGridMetrics } from "../../Common/Constants";
|
||||||
import { Splitter, SplitterBounds, SplitterDirection } from "../../Common/Splitter";
|
import { Splitter, SplitterBounds, SplitterDirection } from "../../Common/Splitter";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import SaveIcon from "../../../images/save-cosmos.svg";
|
import SaveIcon from "../../../images/save-cosmos.svg";
|
||||||
import DiscardIcon from "../../../images/discard.svg";
|
import DiscardIcon from "../../../images/discard.svg";
|
||||||
import DeleteIcon from "../../../images/delete.svg";
|
import DeleteIcon from "../../../images/delete.svg";
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import editable from "../../Common/EditableUtility";
|
|||||||
import Q from "q";
|
import Q from "q";
|
||||||
import SaveIcon from "../../../images/save-cosmos.svg";
|
import SaveIcon from "../../../images/save-cosmos.svg";
|
||||||
import TabsBase from "./TabsBase";
|
import TabsBase from "./TabsBase";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { PlatformType } from "../../PlatformType";
|
import { PlatformType } from "../../PlatformType";
|
||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
@@ -20,6 +20,7 @@ import { updateOffer } from "../../Common/DocumentClientUtilityBase";
|
|||||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { updateOfferThroughputBeyondLimit } from "../../Common/dataAccess/updateOfferThroughputBeyondLimit";
|
import { updateOfferThroughputBeyondLimit } from "../../Common/dataAccess/updateOfferThroughputBeyondLimit";
|
||||||
|
import { configContext, Platform } from "../../ConfigContext";
|
||||||
|
|
||||||
const updateThroughputBeyondLimitWarningMessage: string = `
|
const updateThroughputBeyondLimitWarningMessage: string = `
|
||||||
You are about to request an increase in throughput beyond the pre-allocated capacity.
|
You are about to request an increase in throughput beyond the pre-allocated capacity.
|
||||||
@@ -196,7 +197,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.costsVisible = ko.computed(() => {
|
this.costsVisible = ko.computed(() => {
|
||||||
return !this.container.isEmulator;
|
return configContext.platform !== Platform.Emulator;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.shouldDisplayPortalUsePrompt = ko.pureComputed<boolean>(
|
this.shouldDisplayPortalUsePrompt = ko.pureComputed<boolean>(
|
||||||
@@ -207,7 +208,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
|||||||
);
|
);
|
||||||
this.canRequestSupport = ko.pureComputed(() => {
|
this.canRequestSupport = ko.pureComputed(() => {
|
||||||
if (
|
if (
|
||||||
!!this.container.isEmulator ||
|
configContext.platform === Platform.Emulator ||
|
||||||
this.container.getPlatformType() === PlatformType.Hosted ||
|
this.container.getPlatformType() === PlatformType.Hosted ||
|
||||||
this.canThroughputExceedMaximumValue()
|
this.canThroughputExceedMaximumValue()
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -27,15 +27,9 @@ describe("Documents tab", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("showPartitionKey", () => {
|
describe("showPartitionKey", () => {
|
||||||
const explorer = new Explorer({
|
const explorer = new Explorer();
|
||||||
notificationsClient: null,
|
|
||||||
isEmulator: false
|
|
||||||
});
|
|
||||||
|
|
||||||
const mongoExplorer = new Explorer({
|
const mongoExplorer = new Explorer();
|
||||||
notificationsClient: null,
|
|
||||||
isEmulator: false
|
|
||||||
});
|
|
||||||
mongoExplorer.defaultExperience(Constants.DefaultAccountExperience.MongoDB);
|
mongoExplorer.defaultExperience(Constants.DefaultAccountExperience.MongoDB);
|
||||||
|
|
||||||
const collectionWithoutPartitionKey = <ViewModels.Collection>(<unknown>{
|
const collectionWithoutPartitionKey = <ViewModels.Collection>(<unknown>{
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import TabsBase from "./TabsBase";
|
|||||||
import { DocumentsGridMetrics } from "../../Common/Constants";
|
import { DocumentsGridMetrics } from "../../Common/Constants";
|
||||||
import { QueryUtils } from "../../Utils/QueryUtils";
|
import { QueryUtils } from "../../Utils/QueryUtils";
|
||||||
import { Splitter, SplitterBounds, SplitterDirection } from "../../Common/Splitter";
|
import { Splitter, SplitterBounds, SplitterDirection } from "../../Common/Splitter";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import NewDocumentIcon from "../../../images/NewDocument.svg";
|
import NewDocumentIcon from "../../../images/NewDocument.svg";
|
||||||
import SaveIcon from "../../../images/save-cosmos.svg";
|
import SaveIcon from "../../../images/save-cosmos.svg";
|
||||||
import DiscardIcon from "../../../images/discard.svg";
|
import DiscardIcon from "../../../images/discard.svg";
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import * as ErrorParserUtility from "../../Common/ErrorParserUtility";
|
|||||||
import MongoUtility from "../../Common/MongoUtility";
|
import MongoUtility from "../../Common/MongoUtility";
|
||||||
import ObjectId from "../Tree/ObjectId";
|
import ObjectId from "../Tree/ObjectId";
|
||||||
import Q from "q";
|
import Q from "q";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import {
|
import {
|
||||||
createDocument,
|
createDocument,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import EnvironmentUtility from "../../Common/EnvironmentUtility";
|
|||||||
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
|
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
|
||||||
import Q from "q";
|
import Q from "q";
|
||||||
import TabsBase from "./TabsBase";
|
import TabsBase from "./TabsBase";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import { HashMap } from "../../Common/HashMap";
|
import { HashMap } from "../../Common/HashMap";
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import SaveIcon from "../../../images/save-cosmos.svg";
|
|||||||
import ClearAllOutputsIcon from "../../../images/notebook/Notebook-clear-all-outputs.svg";
|
import ClearAllOutputsIcon from "../../../images/notebook/Notebook-clear-all-outputs.svg";
|
||||||
import InterruptKernelIcon from "../../../images/notebook/Notebook-stop.svg";
|
import InterruptKernelIcon from "../../../images/notebook/Notebook-stop.svg";
|
||||||
import KillKernelIcon from "../../../images/notebook/Notebook-stop.svg";
|
import KillKernelIcon from "../../../images/notebook/Notebook-stop.svg";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { Areas, ArmApiVersions } from "../../Common/Constants";
|
import { Areas, ArmApiVersions } from "../../Common/Constants";
|
||||||
import { CommandBarComponentButtonFactory } from "../Menus/CommandBar/CommandBarComponentButtonFactory";
|
import { CommandBarComponentButtonFactory } from "../Menus/CommandBar/CommandBarComponentButtonFactory";
|
||||||
@@ -147,30 +147,6 @@ export default class NotebookTabV2 extends TabsBase {
|
|||||||
const cellCodeType = "code";
|
const cellCodeType = "code";
|
||||||
const cellMarkdownType = "markdown";
|
const cellMarkdownType = "markdown";
|
||||||
const cellRawType = "raw";
|
const cellRawType = "raw";
|
||||||
|
|
||||||
const saveButtonChildren = [];
|
|
||||||
if (this.container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
|
|
||||||
saveButtonChildren.push({
|
|
||||||
iconName: "Copy",
|
|
||||||
onCommandClick: () => this.copyNotebook(),
|
|
||||||
commandButtonLabel: copyToLabel,
|
|
||||||
hasPopup: false,
|
|
||||||
disabled: false,
|
|
||||||
ariaLabel: copyToLabel
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.container.isGalleryPublishEnabled()) {
|
|
||||||
saveButtonChildren.push({
|
|
||||||
iconName: "PublishContent",
|
|
||||||
onCommandClick: async () => await this.publishToGallery(),
|
|
||||||
commandButtonLabel: publishLabel,
|
|
||||||
hasPopup: false,
|
|
||||||
disabled: false,
|
|
||||||
ariaLabel: publishLabel
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let buttons: CommandButtonComponentProps[] = [
|
let buttons: CommandButtonComponentProps[] = [
|
||||||
{
|
{
|
||||||
iconSrc: SaveIcon,
|
iconSrc: SaveIcon,
|
||||||
@@ -180,17 +156,34 @@ export default class NotebookTabV2 extends TabsBase {
|
|||||||
hasPopup: false,
|
hasPopup: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
ariaLabel: saveLabel,
|
ariaLabel: saveLabel,
|
||||||
children: saveButtonChildren.length && [
|
children: this.container.isGalleryPublishEnabled()
|
||||||
{
|
? [
|
||||||
iconName: "Save",
|
{
|
||||||
onCommandClick: () => this.notebookComponentAdapter.notebookSave(),
|
iconName: "Save",
|
||||||
commandButtonLabel: saveLabel,
|
onCommandClick: () => this.notebookComponentAdapter.notebookSave(),
|
||||||
hasPopup: false,
|
commandButtonLabel: saveLabel,
|
||||||
disabled: false,
|
hasPopup: false,
|
||||||
ariaLabel: saveLabel
|
disabled: false,
|
||||||
},
|
ariaLabel: saveLabel
|
||||||
...saveButtonChildren
|
},
|
||||||
]
|
{
|
||||||
|
iconName: "Copy",
|
||||||
|
onCommandClick: () => this.copyNotebook(),
|
||||||
|
commandButtonLabel: copyToLabel,
|
||||||
|
hasPopup: false,
|
||||||
|
disabled: false,
|
||||||
|
ariaLabel: copyToLabel
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iconName: "PublishContent",
|
||||||
|
onCommandClick: async () => await this.publishToGallery(),
|
||||||
|
commandButtonLabel: publishLabel,
|
||||||
|
hasPopup: false,
|
||||||
|
disabled: false,
|
||||||
|
ariaLabel: publishLabel
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: undefined
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
iconSrc: null,
|
iconSrc: null,
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ describe("Query Tab", () => {
|
|||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
explorer = new Explorer();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be true for accounts using SQL API", () => {
|
it("should be true for accounts using SQL API", () => {
|
||||||
@@ -69,7 +69,7 @@ describe("Query Tab", () => {
|
|||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
explorer = new Explorer();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be visible when using a supported API", () => {
|
it("should be visible when using a supported API", () => {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { HashMap } from "../../Common/HashMap";
|
|||||||
import * as HeadersUtility from "../../Common/HeadersUtility";
|
import * as HeadersUtility from "../../Common/HeadersUtility";
|
||||||
import * as Logger from "../../Common/Logger";
|
import * as Logger from "../../Common/Logger";
|
||||||
import { Splitter, SplitterBounds, SplitterDirection } from "../../Common/Splitter";
|
import { Splitter, SplitterBounds, SplitterDirection } from "../../Common/Splitter";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg";
|
import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg";
|
||||||
import { QueryUtils } from "../../Utils/QueryUtils";
|
import { QueryUtils } from "../../Utils/QueryUtils";
|
||||||
import SaveQueryIcon from "../../../images/save-cosmos.svg";
|
import SaveQueryIcon from "../../../images/save-cosmos.svg";
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import Database from "../Tree/Database";
|
|||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import SettingsTab from "../Tabs/SettingsTab";
|
import SettingsTab from "../Tabs/SettingsTab";
|
||||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||||
import { IndexingPolicies } from "../../Shared/Constants";
|
|
||||||
|
|
||||||
describe("Settings tab", () => {
|
describe("Settings tab", () => {
|
||||||
const baseCollection: DataModels.Collection = {
|
const baseCollection: DataModels.Collection = {
|
||||||
@@ -17,7 +16,7 @@ describe("Settings tab", () => {
|
|||||||
mode: DataModels.ConflictResolutionMode.LastWriterWins,
|
mode: DataModels.ConflictResolutionMode.LastWriterWins,
|
||||||
conflictResolutionPath: "/_ts"
|
conflictResolutionPath: "/_ts"
|
||||||
},
|
},
|
||||||
indexingPolicy: IndexingPolicies.SharedDatabaseDefault,
|
indexingPolicy: {},
|
||||||
_rid: "",
|
_rid: "",
|
||||||
_self: "",
|
_self: "",
|
||||||
_etag: "",
|
_etag: "",
|
||||||
@@ -52,7 +51,7 @@ describe("Settings tab", () => {
|
|||||||
defaultTtl: 200,
|
defaultTtl: 200,
|
||||||
partitionKey: null,
|
partitionKey: null,
|
||||||
conflictResolutionPolicy: null,
|
conflictResolutionPolicy: null,
|
||||||
indexingPolicy: IndexingPolicies.SharedDatabaseDefault,
|
indexingPolicy: {},
|
||||||
_rid: "",
|
_rid: "",
|
||||||
_self: "",
|
_self: "",
|
||||||
_etag: "",
|
_etag: "",
|
||||||
@@ -79,7 +78,7 @@ describe("Settings tab", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
explorer = new Explorer();
|
||||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -178,7 +177,7 @@ describe("Settings tab", () => {
|
|||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
explorer = new Explorer();
|
||||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -256,7 +255,7 @@ describe("Settings tab", () => {
|
|||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
explorer = new Explorer();
|
||||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -337,15 +336,13 @@ describe("Settings tab", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getCollection(defaultApi: string, partitionKeyOption: PartitionKeyOption) {
|
function getCollection(defaultApi: string, partitionKeyOption: PartitionKeyOption) {
|
||||||
const explorer = new Explorer({
|
const explorer = new Explorer();
|
||||||
notificationsClient: null,
|
|
||||||
isEmulator: false
|
|
||||||
});
|
|
||||||
explorer.defaultExperience(defaultApi);
|
explorer.defaultExperience(defaultApi);
|
||||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||||
|
|
||||||
const offer: DataModels.Offer = null;
|
const offer: DataModels.Offer = null;
|
||||||
const defaultTtl = 200;
|
const defaultTtl = 200;
|
||||||
|
const indexingPolicy = {};
|
||||||
const database = new Database(explorer, baseDatabase, null);
|
const database = new Database(explorer, baseDatabase, null);
|
||||||
const conflictResolutionPolicy = {
|
const conflictResolutionPolicy = {
|
||||||
mode: DataModels.ConflictResolutionMode.LastWriterWins,
|
mode: DataModels.ConflictResolutionMode.LastWriterWins,
|
||||||
@@ -367,7 +364,7 @@ describe("Settings tab", () => {
|
|||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
conflictResolutionPolicy: conflictResolutionPolicy,
|
conflictResolutionPolicy: conflictResolutionPolicy,
|
||||||
indexingPolicy: IndexingPolicies.SharedDatabaseDefault,
|
indexingPolicy: indexingPolicy,
|
||||||
_rid: "",
|
_rid: "",
|
||||||
_self: "",
|
_self: "",
|
||||||
_etag: "",
|
_etag: "",
|
||||||
@@ -471,10 +468,7 @@ describe("Settings tab", () => {
|
|||||||
|
|
||||||
describe("AutoPilot", () => {
|
describe("AutoPilot", () => {
|
||||||
function getCollection(autoPilotTier: DataModels.AutopilotTier) {
|
function getCollection(autoPilotTier: DataModels.AutopilotTier) {
|
||||||
const explorer = new Explorer({
|
const explorer = new Explorer();
|
||||||
notificationsClient: null,
|
|
||||||
isEmulator: false
|
|
||||||
});
|
|
||||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||||
|
|
||||||
explorer.databaseAccount({
|
explorer.databaseAccount({
|
||||||
|
|||||||
@@ -12,16 +12,17 @@ import editable from "../../Common/EditableUtility";
|
|||||||
import Q from "q";
|
import Q from "q";
|
||||||
import SaveIcon from "../../../images/save-cosmos.svg";
|
import SaveIcon from "../../../images/save-cosmos.svg";
|
||||||
import TabsBase from "./TabsBase";
|
import TabsBase from "./TabsBase";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { PlatformType } from "../../PlatformType";
|
import { PlatformType } from "../../PlatformType";
|
||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { updateOffer } from "../../Common/DocumentClientUtilityBase";
|
import { updateOffer, updateCollection } from "../../Common/DocumentClientUtilityBase";
|
||||||
import { updateCollection } from "../../Common/dataAccess/updateCollection";
|
|
||||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { updateOfferThroughputBeyondLimit } from "../../Common/dataAccess/updateOfferThroughputBeyondLimit";
|
import { updateOfferThroughputBeyondLimit } from "../../Common/dataAccess/updateOfferThroughputBeyondLimit";
|
||||||
|
import { config } from "process";
|
||||||
|
import { configContext, Platform } from "../../ConfigContext";
|
||||||
|
|
||||||
const ttlWarning: string = `
|
const ttlWarning: string = `
|
||||||
The system will automatically delete items based on the TTL value (in seconds) you provide, without needing a delete operation explicitly issued by a client application.
|
The system will automatically delete items based on the TTL value (in seconds) you provide, without needing a delete operation explicitly issued by a client application.
|
||||||
@@ -455,7 +456,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.rupmVisible = ko.computed(() => {
|
this.rupmVisible = ko.computed(() => {
|
||||||
if (this.container.isEmulator) {
|
if (configContext.platform === Platform.Emulator) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.container.isFeatureEnabled(Constants.Features.enableRupm)) {
|
if (this.container.isFeatureEnabled(Constants.Features.enableRupm)) {
|
||||||
@@ -485,7 +486,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.costsVisible = ko.computed(() => {
|
this.costsVisible = ko.computed(() => {
|
||||||
return !this.container.isEmulator;
|
return configContext.platform !== Platform.Emulator;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.isTryCosmosDBSubscription = ko.computed<boolean>(() => {
|
this.isTryCosmosDBSubscription = ko.computed<boolean>(() => {
|
||||||
@@ -501,7 +502,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.canRequestSupport = ko.pureComputed(() => {
|
this.canRequestSupport = ko.pureComputed(() => {
|
||||||
if (this.container.isEmulator) {
|
if (configContext.platform === Platform.Emulator) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -708,7 +709,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isThroughputGreaterThanMaxRus = this.throughput() > this.maxRUs();
|
const isThroughputGreaterThanMaxRus = this.throughput() > this.maxRUs();
|
||||||
const isEmulator = this.container.isEmulator;
|
const isEmulator = configContext.platform === Platform.Emulator;
|
||||||
if (isThroughputGreaterThanMaxRus && isEmulator) {
|
if (isThroughputGreaterThanMaxRus && isEmulator) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -879,7 +880,8 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
|||||||
this.maxRUs() <= SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
this.maxRUs() <= SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
||||||
this.throughput() > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million;
|
this.throughput() > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million;
|
||||||
|
|
||||||
const throughputExceedsMaxValue: boolean = !this.container.isEmulator && this.throughput() > this.maxRUs();
|
const throughputExceedsMaxValue: boolean =
|
||||||
|
configContext.platform !== Platform.Emulator && this.throughput() > this.maxRUs();
|
||||||
|
|
||||||
const ttlOptionDirty: boolean = this.timeToLive.editableIsDirty();
|
const ttlOptionDirty: boolean = this.timeToLive.editableIsDirty();
|
||||||
const ttlOrIndexingPolicyFieldsDirty: boolean =
|
const ttlOrIndexingPolicyFieldsDirty: boolean =
|
||||||
@@ -1010,7 +1012,8 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSaveClick = async (): Promise<any> => {
|
public onSaveClick = (): Q.Promise<any> => {
|
||||||
|
let promises: Q.Promise<void>[] = [];
|
||||||
this.isExecutionError(false);
|
this.isExecutionError(false);
|
||||||
|
|
||||||
this.isExecuting(true);
|
this.isExecuting(true);
|
||||||
@@ -1023,60 +1026,50 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
|||||||
|
|
||||||
const newCollectionAttributes: any = {};
|
const newCollectionAttributes: any = {};
|
||||||
|
|
||||||
try {
|
if (this.shouldUpdateCollection()) {
|
||||||
if (this.shouldUpdateCollection()) {
|
let defaultTtl: number;
|
||||||
let defaultTtl: number;
|
switch (this.timeToLive()) {
|
||||||
switch (this.timeToLive()) {
|
case "on":
|
||||||
case "on":
|
defaultTtl = Number(this.timeToLiveSeconds());
|
||||||
defaultTtl = Number(this.timeToLiveSeconds());
|
break;
|
||||||
break;
|
case "on-nodefault":
|
||||||
case "on-nodefault":
|
defaultTtl = -1;
|
||||||
defaultTtl = -1;
|
break;
|
||||||
break;
|
case "off":
|
||||||
case "off":
|
default:
|
||||||
default:
|
defaultTtl = undefined;
|
||||||
defaultTtl = undefined;
|
break;
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
newCollectionAttributes.defaultTtl = defaultTtl;
|
newCollectionAttributes.defaultTtl = defaultTtl;
|
||||||
|
|
||||||
newCollectionAttributes.indexingPolicy = this.indexingPolicyContent();
|
newCollectionAttributes.indexingPolicy = this.indexingPolicyContent();
|
||||||
|
|
||||||
newCollectionAttributes.changeFeedPolicy =
|
newCollectionAttributes.changeFeedPolicy =
|
||||||
this.changeFeedPolicyVisible() && this.changeFeedPolicyToggled() === ChangeFeedPolicyToggledState.On
|
this.changeFeedPolicyVisible() && this.changeFeedPolicyToggled() === ChangeFeedPolicyToggledState.On
|
||||||
? ({
|
? ({
|
||||||
retentionDuration: Constants.BackendDefaults.maxChangeFeedRetentionDuration
|
retentionDuration: Constants.BackendDefaults.maxChangeFeedRetentionDuration
|
||||||
} as DataModels.ChangeFeedPolicy)
|
} as DataModels.ChangeFeedPolicy)
|
||||||
: undefined;
|
|
||||||
|
|
||||||
newCollectionAttributes.analyticalStorageTtl = this.isAnalyticalStorageEnabled
|
|
||||||
? this.analyticalStorageTtlSelection() === "on"
|
|
||||||
? Number(this.analyticalStorageTtlSeconds())
|
|
||||||
: Constants.AnalyticalStorageTtl.Infinite
|
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
newCollectionAttributes.geospatialConfig = {
|
newCollectionAttributes.analyticalStorageTtl = this.isAnalyticalStorageEnabled
|
||||||
type: this.geospatialConfigType()
|
? this.analyticalStorageTtlSelection() === "on"
|
||||||
};
|
? Number(this.analyticalStorageTtlSeconds())
|
||||||
|
: Constants.AnalyticalStorageTtl.Infinite
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const conflictResolutionChanges: DataModels.ConflictResolutionPolicy = this.getUpdatedConflictResolutionPolicy();
|
newCollectionAttributes.geospatialConfig = {
|
||||||
if (!!conflictResolutionChanges) {
|
type: this.geospatialConfigType()
|
||||||
newCollectionAttributes.conflictResolutionPolicy = conflictResolutionChanges;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const newCollection: DataModels.Collection = _.extend(
|
const conflictResolutionChanges: DataModels.ConflictResolutionPolicy = this.getUpdatedConflictResolutionPolicy();
|
||||||
{},
|
if (!!conflictResolutionChanges) {
|
||||||
this.collection.rawDataModel,
|
newCollectionAttributes.conflictResolutionPolicy = conflictResolutionChanges;
|
||||||
newCollectionAttributes
|
}
|
||||||
);
|
|
||||||
const updatedCollection: DataModels.Collection = await updateCollection(
|
|
||||||
this.collection.databaseId,
|
|
||||||
this.collection.id(),
|
|
||||||
newCollection
|
|
||||||
);
|
|
||||||
|
|
||||||
if (updatedCollection) {
|
const newCollection: DataModels.Collection = _.extend({}, this.collection.rawDataModel, newCollectionAttributes);
|
||||||
|
const updateCollectionPromise = updateCollection(this.collection.databaseId, this.collection, newCollection).then(
|
||||||
|
(updatedCollection: DataModels.Collection) => {
|
||||||
this.collection.rawDataModel = updatedCollection;
|
this.collection.rawDataModel = updatedCollection;
|
||||||
this.collection.defaultTtl(updatedCollection.defaultTtl);
|
this.collection.defaultTtl(updatedCollection.defaultTtl);
|
||||||
this.collection.analyticalStorageTtl(updatedCollection.analyticalStorageTtl);
|
this.collection.analyticalStorageTtl(updatedCollection.analyticalStorageTtl);
|
||||||
@@ -1086,133 +1079,164 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
|||||||
this.collection.changeFeedPolicy(updatedCollection.changeFeedPolicy);
|
this.collection.changeFeedPolicy(updatedCollection.changeFeedPolicy);
|
||||||
this.collection.geospatialConfig(updatedCollection.geospatialConfig);
|
this.collection.geospatialConfig(updatedCollection.geospatialConfig);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
promises.push(updateCollectionPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.throughput.editableIsDirty() ||
|
||||||
|
this.rupm.editableIsDirty() ||
|
||||||
|
this._isAutoPilotDirty() ||
|
||||||
|
this._hasProvisioningTypeChanged()
|
||||||
|
) {
|
||||||
|
const newThroughput = this.throughput();
|
||||||
|
const isRUPerMinuteThroughputEnabled: boolean = this.rupm() === Constants.RUPMStates.on;
|
||||||
|
let newOffer: DataModels.Offer = _.extend({}, this.collection.offer());
|
||||||
|
const originalThroughputValue: number = this.throughput.getEditableOriginalValue();
|
||||||
|
|
||||||
|
if (newOffer.content) {
|
||||||
|
newOffer.content.offerThroughput = newThroughput;
|
||||||
|
newOffer.content.offerIsRUPerMinuteThroughputEnabled = isRUPerMinuteThroughputEnabled;
|
||||||
|
} else {
|
||||||
|
newOffer = _.extend({}, newOffer, {
|
||||||
|
content: {
|
||||||
|
offerThroughput: newThroughput,
|
||||||
|
offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerOptions: RequestOptions = { initialHeaders: {} };
|
||||||
|
|
||||||
|
if (this.isAutoPilotSelected()) {
|
||||||
|
if (!this.hasAutoPilotV2FeatureFlag()) {
|
||||||
|
newOffer.content.offerAutopilotSettings = {
|
||||||
|
maxThroughput: this.autoPilotThroughput()
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
newOffer.content.offerAutopilotSettings = {
|
||||||
|
tier: this.selectedAutoPilotTier()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// user has changed from provisioned --> autoscale
|
||||||
|
if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) {
|
||||||
|
headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToAutopilot] = "true";
|
||||||
|
delete newOffer.content.offerAutopilotSettings;
|
||||||
|
} else {
|
||||||
|
delete newOffer.content.offerThroughput;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.isAutoPilotSelected(false);
|
||||||
|
this.userCanChangeProvisioningTypes(false || !this.hasAutoPilotV2FeatureFlag());
|
||||||
|
|
||||||
|
// user has changed from autoscale --> provisioned
|
||||||
|
if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) {
|
||||||
|
headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToManualThroughput] = "true";
|
||||||
|
} else {
|
||||||
|
delete newOffer.content.offerAutopilotSettings;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.throughput.editableIsDirty() ||
|
this.maxRUs() <= SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
||||||
this.rupm.editableIsDirty() ||
|
newThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
||||||
this._isAutoPilotDirty() ||
|
this.container != null
|
||||||
this._hasProvisioningTypeChanged()
|
|
||||||
) {
|
) {
|
||||||
const newThroughput = this.throughput();
|
const requestPayload = {
|
||||||
const isRUPerMinuteThroughputEnabled: boolean = this.rupm() === Constants.RUPMStates.on;
|
subscriptionId: userContext.subscriptionId,
|
||||||
let newOffer: DataModels.Offer = _.extend({}, this.collection.offer());
|
databaseAccountName: userContext.databaseAccount.name,
|
||||||
const originalThroughputValue: number = this.throughput.getEditableOriginalValue();
|
resourceGroup: userContext.resourceGroup,
|
||||||
|
databaseName: this.collection.databaseId,
|
||||||
if (newOffer.content) {
|
collectionName: this.collection.id(),
|
||||||
newOffer.content.offerThroughput = newThroughput;
|
throughput: newThroughput,
|
||||||
newOffer.content.offerIsRUPerMinuteThroughputEnabled = isRUPerMinuteThroughputEnabled;
|
offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled
|
||||||
} else {
|
};
|
||||||
newOffer = _.extend({}, newOffer, {
|
const updateOfferBeyondLimitPromise = updateOfferThroughputBeyondLimit(requestPayload).then(
|
||||||
content: {
|
() => {
|
||||||
offerThroughput: newThroughput,
|
this.collection.offer().content.offerThroughput = originalThroughputValue;
|
||||||
offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled
|
this.throughput(originalThroughputValue);
|
||||||
}
|
this.notificationStatusInfo(
|
||||||
});
|
throughputApplyDelayedMessage(
|
||||||
}
|
this.isAutoPilotSelected(),
|
||||||
|
originalThroughputValue,
|
||||||
const headerOptions: RequestOptions = { initialHeaders: {} };
|
this._getThroughputUnit(),
|
||||||
|
this.collection.databaseId,
|
||||||
if (this.isAutoPilotSelected()) {
|
this.collection.id(),
|
||||||
if (!this.hasAutoPilotV2FeatureFlag()) {
|
newThroughput
|
||||||
newOffer.content.offerAutopilotSettings = {
|
)
|
||||||
maxThroughput: this.autoPilotThroughput()
|
);
|
||||||
};
|
this.throughput.valueHasMutated(); // force component re-render
|
||||||
} else {
|
},
|
||||||
newOffer.content.offerAutopilotSettings = {
|
(error: any) => {
|
||||||
tier: this.selectedAutoPilotTier()
|
TelemetryProcessor.traceFailure(
|
||||||
};
|
Action.UpdateSettings,
|
||||||
|
{
|
||||||
|
databaseAccountName: this.container.databaseAccount().name,
|
||||||
|
databaseName: this.collection && this.collection.databaseId,
|
||||||
|
collectionName: this.collection && this.collection.id(),
|
||||||
|
defaultExperience: this.container.defaultExperience(),
|
||||||
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
|
tabTitle: this.tabTitle(),
|
||||||
|
error: error
|
||||||
|
},
|
||||||
|
startKey
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
// user has changed from provisioned --> autoscale
|
promises.push(Q(updateOfferBeyondLimitPromise));
|
||||||
if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) {
|
} else {
|
||||||
headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToAutopilot] = "true";
|
const updateOfferPromise = updateOffer(this.collection.offer(), newOffer, headerOptions).then(
|
||||||
delete newOffer.content.offerAutopilotSettings;
|
(updatedOffer: DataModels.Offer) => {
|
||||||
} else {
|
this.collection.offer(updatedOffer);
|
||||||
delete newOffer.content.offerThroughput;
|
this.collection.offer.valueHasMutated();
|
||||||
}
|
}
|
||||||
} else {
|
);
|
||||||
this.isAutoPilotSelected(false);
|
|
||||||
this.userCanChangeProvisioningTypes(false || !this.hasAutoPilotV2FeatureFlag());
|
|
||||||
|
|
||||||
// user has changed from autoscale --> provisioned
|
promises.push(updateOfferPromise);
|
||||||
if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) {
|
|
||||||
headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToManualThroughput] = "true";
|
|
||||||
} else {
|
|
||||||
delete newOffer.content.offerAutopilotSettings;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.maxRUs() <= SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
|
||||||
newThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
|
||||||
this.container != null
|
|
||||||
) {
|
|
||||||
const requestPayload = {
|
|
||||||
subscriptionId: userContext.subscriptionId,
|
|
||||||
databaseAccountName: userContext.databaseAccount.name,
|
|
||||||
resourceGroup: userContext.resourceGroup,
|
|
||||||
databaseName: this.collection.databaseId,
|
|
||||||
collectionName: this.collection.id(),
|
|
||||||
throughput: newThroughput,
|
|
||||||
offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled
|
|
||||||
};
|
|
||||||
|
|
||||||
await updateOfferThroughputBeyondLimit(requestPayload);
|
|
||||||
this.collection.offer().content.offerThroughput = originalThroughputValue;
|
|
||||||
this.throughput(originalThroughputValue);
|
|
||||||
this.notificationStatusInfo(
|
|
||||||
throughputApplyDelayedMessage(
|
|
||||||
this.isAutoPilotSelected(),
|
|
||||||
originalThroughputValue,
|
|
||||||
this._getThroughputUnit(),
|
|
||||||
this.collection.databaseId,
|
|
||||||
this.collection.id(),
|
|
||||||
newThroughput
|
|
||||||
)
|
|
||||||
);
|
|
||||||
this.throughput.valueHasMutated(); // force component re-render
|
|
||||||
} else {
|
|
||||||
const updatedOffer: DataModels.Offer = await updateOffer(this.collection.offer(), newOffer, headerOptions);
|
|
||||||
this.collection.offer(updatedOffer);
|
|
||||||
this.collection.offer.valueHasMutated();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.container.isRefreshingExplorer(false);
|
|
||||||
this._setBaseline();
|
|
||||||
this.collection.readSettings();
|
|
||||||
this._wasAutopilotOriginallySet(this.isAutoPilotSelected());
|
|
||||||
TelemetryProcessor.traceSuccess(
|
|
||||||
Action.UpdateSettings,
|
|
||||||
{
|
|
||||||
databaseAccountName: this.container.databaseAccount().name,
|
|
||||||
defaultExperience: this.container.defaultExperience(),
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.tabTitle()
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
this.container.isRefreshingExplorer(false);
|
|
||||||
this.isExecutionError(true);
|
|
||||||
console.error(error);
|
|
||||||
TelemetryProcessor.traceFailure(
|
|
||||||
Action.UpdateSettings,
|
|
||||||
{
|
|
||||||
databaseAccountName: this.container.databaseAccount().name,
|
|
||||||
databaseName: this.collection && this.collection.databaseId,
|
|
||||||
collectionName: this.collection && this.collection.id(),
|
|
||||||
defaultExperience: this.container.defaultExperience(),
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.tabTitle(),
|
|
||||||
error: error
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isExecuting(false);
|
if (promises.length === 0) {
|
||||||
|
this.isExecuting(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Q.all(promises)
|
||||||
|
.then(
|
||||||
|
() => {
|
||||||
|
this.container.isRefreshingExplorer(false);
|
||||||
|
this._setBaseline();
|
||||||
|
this.collection.readSettings();
|
||||||
|
this._wasAutopilotOriginallySet(this.isAutoPilotSelected());
|
||||||
|
TelemetryProcessor.traceSuccess(
|
||||||
|
Action.UpdateSettings,
|
||||||
|
{
|
||||||
|
databaseAccountName: this.container.databaseAccount().name,
|
||||||
|
defaultExperience: this.container.defaultExperience(),
|
||||||
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
|
tabTitle: this.tabTitle()
|
||||||
|
},
|
||||||
|
startKey
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(reason: any) => {
|
||||||
|
this.container.isRefreshingExplorer(false);
|
||||||
|
this.isExecutionError(true);
|
||||||
|
console.error(reason);
|
||||||
|
TelemetryProcessor.traceFailure(
|
||||||
|
Action.UpdateSettings,
|
||||||
|
{
|
||||||
|
databaseAccountName: this.container.databaseAccount().name,
|
||||||
|
defaultExperience: this.container.defaultExperience(),
|
||||||
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
|
tabTitle: this.tabTitle()
|
||||||
|
},
|
||||||
|
startKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.finally(() => this.isExecuting(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
public onRevertClick = (): Q.Promise<any> => {
|
public onRevertClick = (): Q.Promise<any> => {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import * as ViewModels from "../../Contracts/ViewModels";
|
|||||||
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import editable from "../../Common/EditableUtility";
|
import editable from "../../Common/EditableUtility";
|
||||||
import ScriptTabBase from "./ScriptTabBase";
|
import ScriptTabBase from "./ScriptTabBase";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg";
|
import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg";
|
||||||
import StoredProcedure from "../Tree/StoredProcedure";
|
import StoredProcedure from "../Tree/StoredProcedure";
|
||||||
import { createStoredProcedure, updateStoredProcedure } from "../../Common/DocumentClientUtilityBase";
|
import { createStoredProcedure, updateStoredProcedure } from "../../Common/DocumentClientUtilityBase";
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import * as ViewModels from "../../Contracts/ViewModels";
|
|||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { RouteHandler } from "../../RouteHandlers/RouteHandler";
|
import { RouteHandler } from "../../RouteHandlers/RouteHandler";
|
||||||
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
|
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import ThemeUtility from "../../Common/ThemeUtility";
|
import ThemeUtility from "../../Common/ThemeUtility";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ describe("Tabs manager tests", () => {
|
|||||||
let documentsTab: DocumentsTab;
|
let documentsTab: DocumentsTab;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
explorer = new Explorer({ notificationsClient: undefined, isEmulator: false });
|
explorer = new Explorer();
|
||||||
explorer.databaseAccount = ko.observable<DataModels.DatabaseAccount>({
|
explorer.databaseAccount = ko.observable<DataModels.DatabaseAccount>({
|
||||||
id: "test",
|
id: "test",
|
||||||
name: "test",
|
name: "test",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import * as ViewModels from "../../Contracts/ViewModels";
|
|||||||
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import ScriptTabBase from "./ScriptTabBase";
|
import ScriptTabBase from "./ScriptTabBase";
|
||||||
import editable from "../../Common/EditableUtility";
|
import editable from "../../Common/EditableUtility";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import Trigger from "../Tree/Trigger";
|
import Trigger from "../Tree/Trigger";
|
||||||
import { createTrigger, updateTrigger } from "../../Common/DocumentClientUtilityBase";
|
import { createTrigger, updateTrigger } from "../../Common/DocumentClientUtilityBase";
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as DataModels from "../../Contracts/DataModels";
|
|||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import ScriptTabBase from "./ScriptTabBase";
|
import ScriptTabBase from "./ScriptTabBase";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import UserDefinedFunction from "../Tree/UserDefinedFunction";
|
import UserDefinedFunction from "../Tree/UserDefinedFunction";
|
||||||
import { createUserDefinedFunction, updateUserDefinedFunction } from "../../Common/DocumentClientUtilityBase";
|
import { createUserDefinedFunction, updateUserDefinedFunction } from "../../Common/DocumentClientUtilityBase";
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import * as DataModels from "../../Contracts/DataModels";
|
|||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { PlatformType } from "../../PlatformType";
|
import { PlatformType } from "../../PlatformType";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
import { OfferUtils } from "../../Utils/OfferUtils";
|
import { OfferUtils } from "../../Utils/OfferUtils";
|
||||||
import { StartUploadMessageParams, UploadDetails, UploadDetailsRecord } from "../../workers/upload/definitions";
|
import { StartUploadMessageParams, UploadDetails, UploadDetailsRecord } from "../../workers/upload/definitions";
|
||||||
@@ -559,7 +559,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
||||||
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
|
|
||||||
const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Settings, tab => {
|
const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Settings, tab => {
|
||||||
return tab.collection && tab.collection.rid === this.rid;
|
return tab.collection && tab.collection.rid === this.rid;
|
||||||
});
|
});
|
||||||
@@ -575,9 +574,8 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
tabTitle: tabTitle
|
tabTitle: tabTitle
|
||||||
});
|
});
|
||||||
|
|
||||||
Q.all([pendingNotificationsPromise, this.readSettings()]).then(
|
Q.all([this.readSettings()]).then(
|
||||||
(data: any) => {
|
(data: any) => {
|
||||||
const pendingNotification: DataModels.Notification = data && data[0];
|
|
||||||
settingsTab = new SettingsTab({
|
settingsTab = new SettingsTab({
|
||||||
tabKind: ViewModels.CollectionTabKind.Settings,
|
tabKind: ViewModels.CollectionTabKind.Settings,
|
||||||
title: !this.offer() ? "Settings" : "Scale & Settings",
|
title: !this.offer() ? "Settings" : "Scale & Settings",
|
||||||
@@ -592,7 +590,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||||
});
|
});
|
||||||
this.container.tabsManager.activateNewTab(settingsTab);
|
this.container.tabsManager.activateNewTab(settingsTab);
|
||||||
settingsTab.pendingNotification(pendingNotification);
|
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
TelemetryProcessor.traceFailure(
|
TelemetryProcessor.traceFailure(
|
||||||
@@ -616,16 +613,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
pendingNotificationsPromise.then(
|
this.container.tabsManager.activateTab(settingsTab);
|
||||||
(pendingNotification: DataModels.Notification) => {
|
|
||||||
settingsTab.pendingNotification(pendingNotification);
|
|
||||||
this.container.tabsManager.activateTab(settingsTab);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
settingsTab.pendingNotification(undefined);
|
|
||||||
this.container.tabsManager.activateTab(settingsTab);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -648,9 +636,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
});
|
});
|
||||||
// TODO: Use the collection entity cache to get quota info
|
// TODO: Use the collection entity cache to get quota info
|
||||||
const quotaInfoPromise: Q.Promise<DataModels.CollectionQuotaInfo> = readCollectionQuotaInfo(this);
|
const quotaInfoPromise: Q.Promise<DataModels.CollectionQuotaInfo> = readCollectionQuotaInfo(this);
|
||||||
const offerInfoPromise: Q.Promise<DataModels.Offer[]> = readOffers({
|
const offerInfoPromise: Q.Promise<DataModels.Offer[]> = readOffers();
|
||||||
isServerless: this.container.isServerlessEnabled()
|
|
||||||
});
|
|
||||||
Q.all([quotaInfoPromise, offerInfoPromise]).then(
|
Q.all([quotaInfoPromise, offerInfoPromise]).then(
|
||||||
() => {
|
() => {
|
||||||
this.container.isRefreshingExplorer(false);
|
this.container.isRefreshingExplorer(false);
|
||||||
@@ -659,7 +645,9 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
const quotaInfo = _.omit(quotaInfoWithUniqueKeyPolicy, "uniqueKeyPolicy");
|
const quotaInfo = _.omit(quotaInfoWithUniqueKeyPolicy, "uniqueKeyPolicy");
|
||||||
|
|
||||||
const collectionOffer = this._getOfferForCollection(offerInfoPromise.valueOf(), collectionDataModel);
|
const collectionOffer = this._getOfferForCollection(offerInfoPromise.valueOf(), collectionDataModel);
|
||||||
if (!collectionOffer) {
|
const isDatabaseShared = this.getDatabase() && this.getDatabase().isDatabaseShared();
|
||||||
|
const isServerless = this.container.isServerlessEnabled();
|
||||||
|
if ((isDatabaseShared || isServerless) && !collectionOffer) {
|
||||||
this.quotaInfo(quotaInfo);
|
this.quotaInfo(quotaInfo);
|
||||||
TelemetryProcessor.traceSuccess(
|
TelemetryProcessor.traceSuccess(
|
||||||
Action.LoadOffers,
|
Action.LoadOffers,
|
||||||
@@ -1283,48 +1271,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getPendingThroughputSplitNotification(): Q.Promise<DataModels.Notification> {
|
|
||||||
if (!this.container) {
|
|
||||||
return Q.resolve(undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
const deferred: Q.Deferred<DataModels.Notification> = Q.defer<DataModels.Notification>();
|
|
||||||
this.container.notificationsClient.fetchNotifications().then(
|
|
||||||
(notifications: DataModels.Notification[]) => {
|
|
||||||
if (!notifications || notifications.length === 0) {
|
|
||||||
deferred.resolve(undefined);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pendingNotification = _.find(notifications, (notification: DataModels.Notification) => {
|
|
||||||
const throughputUpdateRegExp: RegExp = new RegExp("Throughput update (.*) in progress");
|
|
||||||
return (
|
|
||||||
notification.kind === "message" &&
|
|
||||||
notification.collectionName === this.id() &&
|
|
||||||
notification.description &&
|
|
||||||
throughputUpdateRegExp.test(notification.description)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
deferred.resolve(pendingNotification);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
Logger.logError(
|
|
||||||
JSON.stringify({
|
|
||||||
error: JSON.stringify(error),
|
|
||||||
accountName: this.container && this.container.databaseAccount(),
|
|
||||||
databaseName: this.databaseId,
|
|
||||||
collectionName: this.id()
|
|
||||||
}),
|
|
||||||
"Settings tree node"
|
|
||||||
);
|
|
||||||
deferred.resolve(undefined);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _logUploadDetailsInConsole(uploadDetails: UploadDetails): void {
|
private _logUploadDetailsInConsole(uploadDetails: UploadDetails): void {
|
||||||
const uploadDetailsRecords: UploadDetailsRecord[] = uploadDetails.data;
|
const uploadDetailsRecords: UploadDetailsRecord[] = uploadDetails.data;
|
||||||
const numFiles: number = uploadDetailsRecords.length;
|
const numFiles: number = uploadDetailsRecords.length;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import * as DataModels from "../../Contracts/DataModels";
|
|||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import DatabaseSettingsTab from "../Tabs/DatabaseSettingsTab";
|
import DatabaseSettingsTab from "../Tabs/DatabaseSettingsTab";
|
||||||
import Collection from "./Collection";
|
import Collection from "./Collection";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import * as Logger from "../../Common/Logger";
|
import * as Logger from "../../Common/Logger";
|
||||||
@@ -52,7 +52,6 @@ export default class Database implements ViewModels.Database {
|
|||||||
dataExplorerArea: Constants.Areas.ResourceTree
|
dataExplorerArea: Constants.Areas.ResourceTree
|
||||||
});
|
});
|
||||||
|
|
||||||
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
|
|
||||||
const matchingTabs = this.container.tabsManager.getTabs(
|
const matchingTabs = this.container.tabsManager.getTabs(
|
||||||
ViewModels.CollectionTabKind.DatabaseSettings,
|
ViewModels.CollectionTabKind.DatabaseSettings,
|
||||||
tab => tab.rid === this.rid
|
tab => tab.rid === this.rid
|
||||||
@@ -66,9 +65,8 @@ export default class Database implements ViewModels.Database {
|
|||||||
dataExplorerArea: Constants.Areas.Tab,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
tabTitle: "Scale"
|
tabTitle: "Scale"
|
||||||
});
|
});
|
||||||
Q.all([pendingNotificationsPromise, this.readSettings()]).then(
|
Q.all([this.readSettings()]).then(
|
||||||
(data: any) => {
|
() => {
|
||||||
const pendingNotification: DataModels.Notification = data && data[0];
|
|
||||||
settingsTab = new DatabaseSettingsTab({
|
settingsTab = new DatabaseSettingsTab({
|
||||||
tabKind: ViewModels.CollectionTabKind.DatabaseSettings,
|
tabKind: ViewModels.CollectionTabKind.DatabaseSettings,
|
||||||
title: "Scale",
|
title: "Scale",
|
||||||
@@ -83,7 +81,6 @@ export default class Database implements ViewModels.Database {
|
|||||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||||
});
|
});
|
||||||
|
|
||||||
settingsTab.pendingNotification(pendingNotification);
|
|
||||||
this.container.tabsManager.activateNewTab(settingsTab);
|
this.container.tabsManager.activateNewTab(settingsTab);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
@@ -108,21 +105,16 @@ export default class Database implements ViewModels.Database {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
pendingNotificationsPromise.then(
|
this.container.tabsManager.activateTab(settingsTab);
|
||||||
(pendingNotification: DataModels.Notification) => {
|
|
||||||
settingsTab.pendingNotification(pendingNotification);
|
|
||||||
this.container.tabsManager.activateTab(settingsTab);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
settingsTab.pendingNotification(undefined);
|
|
||||||
this.container.tabsManager.activateTab(settingsTab);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public readSettings(): Q.Promise<void> {
|
public readSettings(): Q.Promise<void> {
|
||||||
const deferred: Q.Deferred<void> = Q.defer<void>();
|
const deferred: Q.Deferred<void> = Q.defer<void>();
|
||||||
|
if (this.container.isServerlessEnabled()) {
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
this.container.isRefreshingExplorer(true);
|
this.container.isRefreshingExplorer(true);
|
||||||
const databaseDataModel: DataModels.Database = <DataModels.Database>{
|
const databaseDataModel: DataModels.Database = <DataModels.Database>{
|
||||||
id: this.id(),
|
id: this.id(),
|
||||||
@@ -134,9 +126,7 @@ export default class Database implements ViewModels.Database {
|
|||||||
defaultExperience: this.container.defaultExperience()
|
defaultExperience: this.container.defaultExperience()
|
||||||
});
|
});
|
||||||
|
|
||||||
const offerInfoPromise: Q.Promise<DataModels.Offer[]> = readOffers({
|
const offerInfoPromise: Q.Promise<DataModels.Offer[]> = readOffers();
|
||||||
isServerless: this.container.isServerlessEnabled()
|
|
||||||
});
|
|
||||||
Q.all([offerInfoPromise]).then(
|
Q.all([offerInfoPromise]).then(
|
||||||
() => {
|
() => {
|
||||||
this.container.isRefreshingExplorer(false);
|
this.container.isRefreshingExplorer(false);
|
||||||
@@ -145,11 +135,6 @@ export default class Database implements ViewModels.Database {
|
|||||||
offerInfoPromise.valueOf(),
|
offerInfoPromise.valueOf(),
|
||||||
databaseDataModel
|
databaseDataModel
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!databaseOffer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
readOffer(databaseOffer).then((offerDetail: DataModels.OfferWithHeaders) => {
|
readOffer(databaseOffer).then((offerDetail: DataModels.OfferWithHeaders) => {
|
||||||
const offerThroughputInfo: DataModels.OfferThroughputInfo = {
|
const offerThroughputInfo: DataModels.OfferThroughputInfo = {
|
||||||
minimumRUForCollection:
|
minimumRUForCollection:
|
||||||
@@ -296,49 +281,6 @@ export default class Database implements ViewModels.Database {
|
|||||||
return _.find(this.collections(), (collection: ViewModels.Collection) => collection.id() === collectionId);
|
return _.find(this.collections(), (collection: ViewModels.Collection) => collection.id() === collectionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getPendingThroughputSplitNotification(): Q.Promise<DataModels.Notification> {
|
|
||||||
if (!this.container) {
|
|
||||||
return Q.resolve(undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
const deferred: Q.Deferred<DataModels.Notification> = Q.defer<DataModels.Notification>();
|
|
||||||
this.container.notificationsClient.fetchNotifications().then(
|
|
||||||
(notifications: DataModels.Notification[]) => {
|
|
||||||
if (!notifications || notifications.length === 0) {
|
|
||||||
deferred.resolve(undefined);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pendingNotification = _.find(notifications, (notification: DataModels.Notification) => {
|
|
||||||
const throughputUpdateRegExp: RegExp = new RegExp("Throughput update (.*) in progress");
|
|
||||||
return (
|
|
||||||
notification.kind === "message" &&
|
|
||||||
!notification.collectionName &&
|
|
||||||
notification.databaseName === this.id() &&
|
|
||||||
notification.description &&
|
|
||||||
throughputUpdateRegExp.test(notification.description)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
deferred.resolve(pendingNotification);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
Logger.logError(
|
|
||||||
JSON.stringify({
|
|
||||||
error: JSON.stringify(error),
|
|
||||||
accountName: this.container && this.container.databaseAccount(),
|
|
||||||
databaseName: this.id(),
|
|
||||||
collectionName: this.id()
|
|
||||||
}),
|
|
||||||
"Settings tree node"
|
|
||||||
);
|
|
||||||
deferred.resolve(undefined);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getDeltaCollections(
|
private getDeltaCollections(
|
||||||
updatedCollectionsList: DataModels.Collection[]
|
updatedCollectionsList: DataModels.Collection[]
|
||||||
): { toAdd: DataModels.Collection[]; toDelete: Collection[] } {
|
): { toAdd: DataModels.Collection[]; toDelete: Collection[] } {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import DocumentId from "./DocumentId";
|
|||||||
import DocumentsTab from "../Tabs/DocumentsTab";
|
import DocumentsTab from "../Tabs/DocumentsTab";
|
||||||
import Q from "q";
|
import Q from "q";
|
||||||
import QueryTab from "../Tabs/QueryTab";
|
import QueryTab from "../Tabs/QueryTab";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import TabsBase from "../Tabs/TabsBase";
|
import TabsBase from "../Tabs/TabsBase";
|
||||||
|
|
||||||
|
|||||||
@@ -1,112 +0,0 @@
|
|||||||
import Explorer from "../Explorer";
|
|
||||||
import * as ko from "knockout";
|
|
||||||
import { ResourceTreeAdapter } from "./ResourceTreeAdapter";
|
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
|
||||||
import TabsBase from "../Tabs/TabsBase";
|
|
||||||
|
|
||||||
describe("ResourceTreeAdapter", () => {
|
|
||||||
const mockContainer = (): Explorer =>
|
|
||||||
(({
|
|
||||||
selectedNode: ko.observable<ViewModels.TreeNode>({
|
|
||||||
nodeKind: "nodeKind",
|
|
||||||
rid: "rid",
|
|
||||||
id: ko.observable<string>("id")
|
|
||||||
}),
|
|
||||||
tabsManager: {
|
|
||||||
activeTab: ko.observable<TabsBase>({
|
|
||||||
tabKind: ViewModels.CollectionTabKind.Documents
|
|
||||||
} as TabsBase)
|
|
||||||
},
|
|
||||||
isNotebookEnabled: ko.observable<boolean>(true),
|
|
||||||
nonSystemDatabases: ko.observable<ViewModels.Database[]>([])
|
|
||||||
} as unknown) as Explorer);
|
|
||||||
|
|
||||||
// TODO isDataNodeSelected needs a better design and refactor, but for now, we protect some of the code paths
|
|
||||||
describe("isDataNodeSelected", () => {
|
|
||||||
it("it should not select if no selected node", () => {
|
|
||||||
const explorer = mockContainer();
|
|
||||||
explorer.selectedNode(undefined);
|
|
||||||
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
|
||||||
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("foo", "bar", undefined);
|
|
||||||
expect(isDataNodeSelected).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("it should not select incorrect subnodekinds", () => {
|
|
||||||
const resourceTreeAdapter = new ResourceTreeAdapter(mockContainer());
|
|
||||||
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("foo", "bar", undefined);
|
|
||||||
expect(isDataNodeSelected).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("it should not select if no active tab", () => {
|
|
||||||
const explorer = mockContainer();
|
|
||||||
explorer.tabsManager.activeTab(undefined);
|
|
||||||
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
|
||||||
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("foo", "bar", undefined);
|
|
||||||
expect(isDataNodeSelected).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should select if correct database node regardless of subnodekinds", () => {
|
|
||||||
const subNodeKind = ViewModels.CollectionTabKind.Documents;
|
|
||||||
const explorer = mockContainer();
|
|
||||||
explorer.selectedNode(({
|
|
||||||
nodeKind: "Database",
|
|
||||||
rid: "dbrid",
|
|
||||||
id: ko.observable<string>("id"),
|
|
||||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind)
|
|
||||||
} as unknown) as ViewModels.TreeNode);
|
|
||||||
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
|
||||||
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("dbrid", "Database", [
|
|
||||||
ViewModels.CollectionTabKind.Documents
|
|
||||||
]);
|
|
||||||
expect(isDataNodeSelected).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should select correct collection node (documents or graph node)", () => {
|
|
||||||
let subNodeKind = ViewModels.CollectionTabKind.Documents;
|
|
||||||
const explorer = mockContainer();
|
|
||||||
explorer.tabsManager.activeTab({
|
|
||||||
tabKind: subNodeKind
|
|
||||||
} as TabsBase);
|
|
||||||
explorer.selectedNode(({
|
|
||||||
nodeKind: "Collection",
|
|
||||||
rid: "collrid",
|
|
||||||
id: ko.observable<string>("id"),
|
|
||||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind)
|
|
||||||
} as unknown) as ViewModels.TreeNode);
|
|
||||||
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
|
||||||
let isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("collrid", "Collection", [subNodeKind]);
|
|
||||||
expect(isDataNodeSelected).toBeTruthy();
|
|
||||||
|
|
||||||
subNodeKind = ViewModels.CollectionTabKind.Graph;
|
|
||||||
explorer.tabsManager.activeTab({
|
|
||||||
tabKind: subNodeKind
|
|
||||||
} as TabsBase);
|
|
||||||
explorer.selectedNode(({
|
|
||||||
nodeKind: "Collection",
|
|
||||||
rid: "collrid",
|
|
||||||
id: ko.observable<string>("id"),
|
|
||||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind)
|
|
||||||
} as unknown) as ViewModels.TreeNode);
|
|
||||||
isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("collrid", "Collection", [subNodeKind]);
|
|
||||||
expect(isDataNodeSelected).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not select incorrect collection node (e.g. Settings)", () => {
|
|
||||||
const explorer = mockContainer();
|
|
||||||
explorer.selectedNode(({
|
|
||||||
nodeKind: "Collection",
|
|
||||||
rid: "collrid",
|
|
||||||
id: ko.observable<string>("id"),
|
|
||||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(ViewModels.CollectionTabKind.Documents)
|
|
||||||
} as unknown) as ViewModels.TreeNode);
|
|
||||||
explorer.tabsManager.activeTab({
|
|
||||||
tabKind: ViewModels.CollectionTabKind.Documents
|
|
||||||
} as TabsBase);
|
|
||||||
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
|
||||||
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("collrid", "Collection", [
|
|
||||||
ViewModels.CollectionTabKind.Settings
|
|
||||||
]);
|
|
||||||
expect(isDataNodeSelected).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -19,7 +19,7 @@ import { ArrayHashMap } from "../../Common/ArrayHashMap";
|
|||||||
import { NotebookUtil } from "../Notebook/NotebookUtil";
|
import { NotebookUtil } from "../Notebook/NotebookUtil";
|
||||||
import _ from "underscore";
|
import _ from "underscore";
|
||||||
import { IPinnedRepo } from "../../Juno/JunoClient";
|
import { IPinnedRepo } from "../../Juno/JunoClient";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { Areas } from "../../Common/Constants";
|
import { Areas } from "../../Common/Constants";
|
||||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||||
@@ -191,7 +191,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
databaseNode.children.push({
|
databaseNode.children.push({
|
||||||
label: "Scale",
|
label: "Scale",
|
||||||
isSelected: () =>
|
isSelected: () =>
|
||||||
this.isDataNodeSelected(database.rid, "Database", [ViewModels.CollectionTabKind.DatabaseSettings]),
|
this.isDataNodeSelected(database.rid, "Database", ViewModels.CollectionTabKind.DatabaseSettings),
|
||||||
onClick: database.onSettingsClick.bind(database)
|
onClick: database.onSettingsClick.bind(database)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -235,18 +235,14 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
data: collection.rid
|
data: collection.rid
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
isSelected: () =>
|
isSelected: () => this.isDataNodeSelected(collection.rid, "Collection", ViewModels.CollectionTabKind.Documents),
|
||||||
this.isDataNodeSelected(collection.rid, "Collection", [
|
|
||||||
ViewModels.CollectionTabKind.Documents,
|
|
||||||
ViewModels.CollectionTabKind.Graph
|
|
||||||
]),
|
|
||||||
contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(this.container, collection)
|
contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(this.container, collection)
|
||||||
});
|
});
|
||||||
|
|
||||||
children.push({
|
children.push({
|
||||||
label: database.isDatabaseShared() || this.container.isServerlessEnabled() ? "Settings" : "Scale & Settings",
|
label: database.isDatabaseShared() || this.container.isServerlessEnabled() ? "Settings" : "Scale & Settings",
|
||||||
onClick: collection.onSettingsClick.bind(collection),
|
onClick: collection.onSettingsClick.bind(collection),
|
||||||
isSelected: () => this.isDataNodeSelected(collection.rid, "Collection", [ViewModels.CollectionTabKind.Settings])
|
isSelected: () => this.isDataNodeSelected(collection.rid, "Collection", ViewModels.CollectionTabKind.Settings)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (ResourceTreeAdapter.showScriptNodes(this.container)) {
|
if (ResourceTreeAdapter.showScriptNodes(this.container)) {
|
||||||
@@ -268,8 +264,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
children.push({
|
children.push({
|
||||||
label: "Conflicts",
|
label: "Conflicts",
|
||||||
onClick: collection.onConflictsClick.bind(collection),
|
onClick: collection.onConflictsClick.bind(collection),
|
||||||
isSelected: () =>
|
isSelected: () => this.isDataNodeSelected(collection.rid, "Collection", ViewModels.CollectionTabKind.Conflicts)
|
||||||
this.isDataNodeSelected(collection.rid, "Collection", [ViewModels.CollectionTabKind.Conflicts])
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,7 +302,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
label: sp.id(),
|
label: sp.id(),
|
||||||
onClick: sp.open.bind(sp),
|
onClick: sp.open.bind(sp),
|
||||||
isSelected: () =>
|
isSelected: () =>
|
||||||
this.isDataNodeSelected(collection.rid, "Collection", [ViewModels.CollectionTabKind.StoredProcedures]),
|
this.isDataNodeSelected(collection.rid, "Collection", ViewModels.CollectionTabKind.StoredProcedures),
|
||||||
contextMenu: ResourceTreeContextMenuButtonFactory.createStoreProcedureContextMenuItems(this.container, sp)
|
contextMenu: ResourceTreeContextMenuButtonFactory.createStoreProcedureContextMenuItems(this.container, sp)
|
||||||
})),
|
})),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
@@ -326,7 +321,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
label: udf.id(),
|
label: udf.id(),
|
||||||
onClick: udf.open.bind(udf),
|
onClick: udf.open.bind(udf),
|
||||||
isSelected: () =>
|
isSelected: () =>
|
||||||
this.isDataNodeSelected(collection.rid, "Collection", [ViewModels.CollectionTabKind.UserDefinedFunctions]),
|
this.isDataNodeSelected(collection.rid, "Collection", ViewModels.CollectionTabKind.UserDefinedFunctions),
|
||||||
contextMenu: ResourceTreeContextMenuButtonFactory.createUserDefinedFunctionContextMenuItems(this.container, udf)
|
contextMenu: ResourceTreeContextMenuButtonFactory.createUserDefinedFunctionContextMenuItems(this.container, udf)
|
||||||
})),
|
})),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
@@ -344,8 +339,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
children: collection.triggers().map((trigger: Trigger) => ({
|
children: collection.triggers().map((trigger: Trigger) => ({
|
||||||
label: trigger.id(),
|
label: trigger.id(),
|
||||||
onClick: trigger.open.bind(trigger),
|
onClick: trigger.open.bind(trigger),
|
||||||
isSelected: () =>
|
isSelected: () => this.isDataNodeSelected(collection.rid, "Collection", ViewModels.CollectionTabKind.Triggers),
|
||||||
this.isDataNodeSelected(collection.rid, "Collection", [ViewModels.CollectionTabKind.Triggers]),
|
|
||||||
contextMenu: ResourceTreeContextMenuButtonFactory.createTriggerContextMenuItems(this.container, trigger)
|
contextMenu: ResourceTreeContextMenuButtonFactory.createTriggerContextMenuItems(this.container, trigger)
|
||||||
})),
|
})),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
@@ -552,52 +546,43 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
(activeTab as any).notebookPath() === item.path
|
(activeTab as any).notebookPath() === item.path
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
contextMenu: createFileContextMenu && this.createFileContextMenu(item),
|
contextMenu: createFileContextMenu
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
label: "Rename",
|
||||||
|
iconSrc: NotebookIcon,
|
||||||
|
onClick: () => this.container.renameNotebook(item)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Delete",
|
||||||
|
iconSrc: DeleteIcon,
|
||||||
|
onClick: () => {
|
||||||
|
this.container.showOkCancelModalDialog(
|
||||||
|
"Confirm delete",
|
||||||
|
`Are you sure you want to delete "${item.name}"`,
|
||||||
|
"Delete",
|
||||||
|
() => this.container.deleteNotebookFile(item).then(() => this.triggerRender()),
|
||||||
|
"Cancel",
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Copy to ...",
|
||||||
|
iconSrc: CopyIcon,
|
||||||
|
onClick: () => this.copyNotebook(item)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Download",
|
||||||
|
iconSrc: NotebookIcon,
|
||||||
|
onClick: () => this.container.downloadFile(item)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: undefined,
|
||||||
data: item
|
data: item
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private createFileContextMenu(item: NotebookContentItem): TreeNodeMenuItem[] {
|
|
||||||
let items: TreeNodeMenuItem[] = [
|
|
||||||
{
|
|
||||||
label: "Rename",
|
|
||||||
iconSrc: NotebookIcon,
|
|
||||||
onClick: () => this.container.renameNotebook(item)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Delete",
|
|
||||||
iconSrc: DeleteIcon,
|
|
||||||
onClick: () => {
|
|
||||||
this.container.showOkCancelModalDialog(
|
|
||||||
"Confirm delete",
|
|
||||||
`Are you sure you want to delete "${item.name}"`,
|
|
||||||
"Delete",
|
|
||||||
() => this.container.deleteNotebookFile(item).then(() => this.triggerRender()),
|
|
||||||
"Cancel",
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Copy to ...",
|
|
||||||
iconSrc: CopyIcon,
|
|
||||||
onClick: () => this.copyNotebook(item)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Download",
|
|
||||||
iconSrc: NotebookIcon,
|
|
||||||
onClick: () => this.container.downloadFile(item)
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// "Copy to ..." isn't needed if github locations are not available
|
|
||||||
if (!this.container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
|
|
||||||
items = items.filter(item => item.label !== "Copy to ...");
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
private copyNotebook = async (item: NotebookContentItem) => {
|
private copyNotebook = async (item: NotebookContentItem) => {
|
||||||
const content = await this.container.readFile(item);
|
const content = await this.container.readFile(item);
|
||||||
if (content) {
|
if (content) {
|
||||||
@@ -703,19 +688,13 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
window.requestAnimationFrame(() => this.parameters(Date.now()));
|
window.requestAnimationFrame(() => this.parameters(Date.now()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private isDataNodeSelected(rid: string, nodeKind: string, subnodeKind: ViewModels.CollectionTabKind): boolean {
|
||||||
* public for testing purposes
|
|
||||||
* @param rid
|
|
||||||
* @param nodeKind
|
|
||||||
* @param subnodeKinds
|
|
||||||
*/
|
|
||||||
public isDataNodeSelected(rid: string, nodeKind: string, subnodeKinds: ViewModels.CollectionTabKind[]): boolean {
|
|
||||||
if (!this.container.selectedNode || !this.container.selectedNode()) {
|
if (!this.container.selectedNode || !this.container.selectedNode()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const selectedNode = this.container.selectedNode();
|
const selectedNode = this.container.selectedNode();
|
||||||
|
|
||||||
if (subnodeKinds === undefined || !Array.isArray(subnodeKinds)) {
|
if (subnodeKind === undefined) {
|
||||||
return selectedNode.rid === rid && selectedNode.nodeKind === nodeKind;
|
return selectedNode.rid === rid && selectedNode.nodeKind === nodeKind;
|
||||||
} else {
|
} else {
|
||||||
const activeTab = this.container.tabsManager.activeTab();
|
const activeTab = this.container.tabsManager.activeTab();
|
||||||
@@ -728,10 +707,10 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
activeTab &&
|
activeTab &&
|
||||||
subnodeKinds.includes(activeTab.tabKind) &&
|
activeTab.tabKind === subnodeKind &&
|
||||||
selectedNode.rid === rid &&
|
selectedNode.rid === rid &&
|
||||||
selectedSubnodeKind !== undefined &&
|
selectedSubnodeKind !== undefined &&
|
||||||
subnodeKinds.includes(selectedSubnodeKind)
|
selectedSubnodeKind === subnodeKind
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import * as DataModels from "../../Contracts/DataModels";
|
|||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
|
||||||
import StoredProcedureTab from "../Tabs/StoredProcedureTab";
|
import StoredProcedureTab from "../Tabs/StoredProcedureTab";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { deleteStoredProcedure, executeStoredProcedure } from "../../Common/DocumentClientUtilityBase";
|
import { deleteStoredProcedure, executeStoredProcedure } from "../../Common/DocumentClientUtilityBase";
|
||||||
import TabsBase from "../Tabs/TabsBase";
|
import TabsBase from "../Tabs/TabsBase";
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as Constants from "../../Common/Constants";
|
|||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import TriggerTab from "../Tabs/TriggerTab";
|
import TriggerTab from "../Tabs/TriggerTab";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { deleteTrigger } from "../../Common/DocumentClientUtilityBase";
|
import { deleteTrigger } from "../../Common/DocumentClientUtilityBase";
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as Constants from "../../Common/Constants";
|
|||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import UserDefinedFunctionTab from "../Tabs/UserDefinedFunctionTab";
|
import UserDefinedFunctionTab from "../Tabs/UserDefinedFunctionTab";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { deleteUserDefinedFunction } from "../../Common/DocumentClientUtilityBase";
|
import { deleteUserDefinedFunction } from "../../Common/DocumentClientUtilityBase";
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import { MeControlComponentAdapter } from "./Explorer/Menus/NavBar/MeControlComp
|
|||||||
import { MessageTypes } from "./Contracts/ExplorerContracts";
|
import { MessageTypes } from "./Contracts/ExplorerContracts";
|
||||||
import * as ReactBindingHandler from "./Bindings/ReactBindingHandler";
|
import * as ReactBindingHandler from "./Bindings/ReactBindingHandler";
|
||||||
import { SwitchDirectoryPane, SwitchDirectoryPaneComponent } from "./Explorer/Panes/SwitchDirectoryPane";
|
import { SwitchDirectoryPane, SwitchDirectoryPaneComponent } from "./Explorer/Panes/SwitchDirectoryPane";
|
||||||
import * as TelemetryProcessor from "./Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "./Shared/Telemetry/TelemetryProcessor";
|
||||||
import { isInvalidParentFrameOrigin } from "./Utils/MessageValidation";
|
import { isInvalidParentFrameOrigin } from "./Utils/MessageValidation";
|
||||||
import "../less/hostedexplorer.less";
|
import "../less/hostedexplorer.less";
|
||||||
import "./Explorer/Menus/NavBar/MeControlComponent.less";
|
import "./Explorer/Menus/NavBar/MeControlComponent.less";
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ import "url-polyfill/url-polyfill.min";
|
|||||||
// import "./ReactDevTools"
|
// import "./ReactDevTools"
|
||||||
|
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as TelemetryProcessor from "./Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "./Shared/Telemetry/TelemetryProcessor";
|
||||||
import * as ViewModels from "./Contracts/ViewModels";
|
import * as ViewModels from "./Contracts/ViewModels";
|
||||||
import { Action, ActionModifiers } from "./Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "./Shared/Telemetry/TelemetryConstants";
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,9 @@ import { AccountKind, TagNames, DefaultAccountExperience } from "../../Common/Co
|
|||||||
|
|
||||||
import Explorer from "../../Explorer/Explorer";
|
import Explorer from "../../Explorer/Explorer";
|
||||||
|
|
||||||
import { NotificationsClient } from "./NotificationsClient";
|
|
||||||
|
|
||||||
export default class EmulatorExplorerFactory {
|
export default class EmulatorExplorerFactory {
|
||||||
public static createExplorer(): Explorer {
|
public static createExplorer(): Explorer {
|
||||||
const explorer: Explorer = new Explorer({
|
const explorer: Explorer = new Explorer();
|
||||||
notificationsClient: new NotificationsClient(),
|
|
||||||
isEmulator: true
|
|
||||||
});
|
|
||||||
explorer.databaseAccount({
|
explorer.databaseAccount({
|
||||||
name: "",
|
name: "",
|
||||||
id: "",
|
id: "",
|
||||||
|
|||||||
@@ -1,6 +1,24 @@
|
|||||||
import EmulatorExplorerFactory from "./ExplorerFactory";
|
|
||||||
import Explorer from "../../Explorer/Explorer";
|
import Explorer from "../../Explorer/Explorer";
|
||||||
|
import { AccountKind, DefaultAccountExperience, TagNames } from "../../Common/Constants";
|
||||||
|
|
||||||
export function initializeExplorer(): Explorer {
|
export function initializeExplorer(): Explorer {
|
||||||
return EmulatorExplorerFactory.createExplorer();
|
const explorer: Explorer = new Explorer();
|
||||||
|
explorer.databaseAccount({
|
||||||
|
name: "",
|
||||||
|
id: "",
|
||||||
|
location: "",
|
||||||
|
type: "",
|
||||||
|
kind: AccountKind.DocumentDB,
|
||||||
|
tags: {
|
||||||
|
[TagNames.defaultExperience]: DefaultAccountExperience.DocumentDB
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
documentEndpoint: "",
|
||||||
|
tableEndpoint: "",
|
||||||
|
gremlinEndpoint: "",
|
||||||
|
cassandraEndpoint: ""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
explorer.isAccountReady(true);
|
||||||
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import Q from "q";
|
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
|
||||||
import { NotificationsClientBase } from "../../Common/NotificationsClientBase";
|
|
||||||
|
|
||||||
export class NotificationsClient extends NotificationsClientBase {
|
|
||||||
private static readonly _notificationsApiSuffix: string = "/api/notifications";
|
|
||||||
|
|
||||||
public constructor() {
|
|
||||||
super(NotificationsClient._notificationsApiSuffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
public fetchNotifications(): Q.Promise<DataModels.Notification[]> {
|
|
||||||
// no notifications for the emulator
|
|
||||||
return Q([]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,8 @@
|
|||||||
import Explorer from "../../Explorer/Explorer";
|
import Explorer from "../../Explorer/Explorer";
|
||||||
import { NotificationsClient } from "./NotificationsClient";
|
|
||||||
|
|
||||||
export default class HostedExplorerFactory {
|
export default class HostedExplorerFactory {
|
||||||
public createExplorer(): Explorer {
|
public createExplorer(): Explorer {
|
||||||
const explorer = new Explorer({
|
return new Explorer();
|
||||||
notificationsClient: new NotificationsClient(),
|
|
||||||
isEmulator: false
|
|
||||||
});
|
|
||||||
|
|
||||||
return explorer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static reInitializeDocumentClientUtilityForExplorer(explorer: Explorer): void {
|
public static reInitializeDocumentClientUtilityForExplorer(explorer: Explorer): void {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user