mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-24 20:24:13 +00:00
Compare commits
1 Commits
explorer-c
...
user/swvis
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35b15a1e20 |
@@ -14,6 +14,7 @@ src/Common/DataAccessUtilityBase.ts
|
|||||||
src/Common/DeleteFeedback.ts
|
src/Common/DeleteFeedback.ts
|
||||||
src/Common/DocumentClientUtilityBase.ts
|
src/Common/DocumentClientUtilityBase.ts
|
||||||
src/Common/EditableUtility.ts
|
src/Common/EditableUtility.ts
|
||||||
|
src/Common/EnvironmentUtility.ts
|
||||||
src/Common/HashMap.test.ts
|
src/Common/HashMap.test.ts
|
||||||
src/Common/HashMap.ts
|
src/Common/HashMap.ts
|
||||||
src/Common/HeadersUtility.test.ts
|
src/Common/HeadersUtility.test.ts
|
||||||
|
|||||||
25
.github/workflows/runners.yml
vendored
Normal file
25
.github/workflows/runners.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
name: Runners
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 * 1 * *"
|
||||||
|
jobs:
|
||||||
|
sqlcreatecollection:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: "SQL | Create Collection"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-node@v1
|
||||||
|
- run: npm ci
|
||||||
|
- run: npm run test:e2e
|
||||||
|
env:
|
||||||
|
PORTAL_RUNNER_APP_INSIGHTS_KEY: ${{ secrets.PORTAL_RUNNER_APP_INSIGHTS_KEY }}
|
||||||
|
PORTAL_RUNNER_USERNAME: ${{ secrets.PORTAL_RUNNER_USERNAME }}
|
||||||
|
PORTAL_RUNNER_PASSWORD: ${{ secrets.PORTAL_RUNNER_PASSWORD }}
|
||||||
|
PORTAL_RUNNER_SUBSCRIPTION: 69e02f2d-f059-4409-9eac-97e8a276ae2c
|
||||||
|
PORTAL_RUNNER_RESOURCE_GROUP: runners
|
||||||
|
PORTAL_RUNNER_DATABASE_ACCOUNT: portal-sql-runner
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
if: failure()
|
||||||
|
with:
|
||||||
|
name: screenshots
|
||||||
|
path: failure.png
|
||||||
37
README.md
37
README.md
@@ -13,18 +13,29 @@ UI for Azure Cosmos DB. Powers the [Azure Portal](https://portal.azure.com/), ht
|
|||||||
|
|
||||||
### Watch mode
|
### Watch mode
|
||||||
|
|
||||||
Run `npm start` to start the development server and automatically rebuild on changes
|
Run `npm run watch` to start the development server and automatically rebuild on changes
|
||||||
|
|
||||||
### Hosted Development (https://cosmos.azure.com)
|
### Specifying Development Platform
|
||||||
|
|
||||||
- Visit: `https://localhost:1234/hostedExplorer.html`
|
Setting the environment variable `PLATFORM` during the build process will force the explorer to load the specified platform. By default in development it will run in `Hosted` mode. Valid options:
|
||||||
- Local sign in via AAD will NOT work. Connection string only in dev mode. Use the Portal if you need AAD auth.
|
|
||||||
- The default webpack dev server configuration will proxy requests to the production portal backend: `https://main.documentdb.ext.azure.com`. This will allow you to use production connection strings on your local machine.
|
- Hosted
|
||||||
|
- Emulator
|
||||||
|
- Portal
|
||||||
|
|
||||||
|
`PLATFORM=Emulator npm run watch`
|
||||||
|
|
||||||
|
### Hosted Development
|
||||||
|
|
||||||
|
The default webpack dev server configuration will proxy requests to the production portal backend: `https://main.documentdb.ext.azure.com`. This will allow you to use production connection strings on your local machine.
|
||||||
|
|
||||||
|
To run pure hosted mode, in `webpack.config.js` change index HtmlWebpackPlugin to use hostedExplorer.html and change entry for index to use HostedExplorer.ts.
|
||||||
|
|
||||||
### Emulator Development
|
### Emulator Development
|
||||||
|
|
||||||
- Start the Cosmos Emulator
|
In a window environment, running `npm run build` will automatically copy the built files from `/dist` over to the default emulator install paths. In a non-windows environment you can specify an alternate endpoint using `EMULATOR_ENDPOINT` and webpack dev server will proxy requests for you.
|
||||||
- Visit: https://localhost:1234/index.html
|
|
||||||
|
`PLATFORM=Emulator EMULATOR_ENDPOINT=https://my-vm.azure.com:8081 npm run watch`
|
||||||
|
|
||||||
#### Setting up a Remote Emulator
|
#### Setting up a Remote Emulator
|
||||||
|
|
||||||
@@ -44,8 +55,16 @@ The Cosmos emulator currently only runs in Windows environments. You can still d
|
|||||||
|
|
||||||
### Portal Development
|
### Portal Development
|
||||||
|
|
||||||
- Visit: https://ms.portal.azure.com/?dataExplorerSource=https%3A%2F%2Flocalhost%3A1234%2Fexplorer.html
|
The Cosmos Portal that consumes this repo is not currently open source. If you have access to this project, `npm run build` will copy the built files over to the portal where they will be loaded by the portal development environment
|
||||||
- You may have to manually visit https://localhost:1234/explorer.html first and click through any SSL certificate warnings
|
|
||||||
|
You can however load a local running instance of data explorer in the production portal.
|
||||||
|
|
||||||
|
1. Turn off browser SSL validation for localhost: chrome://flags/#allow-insecure-localhost OR Install valid SSL certs for localhost (on IE, follow these [instructions](https://www.technipages.com/ie-bypass-problem-with-this-websites-security-certificate) to install the localhost certificate in the right place)
|
||||||
|
2. Allowlist `https://localhost:1234` domain for CORS in the Azure Cosmos DB portal
|
||||||
|
3. Start the project in portal mode: `PLATFORM=Portal npm run watch`
|
||||||
|
4. Load the portal using the following link: https://ms.portal.azure.com/?dataExplorerSource=https%3A%2F%2Flocalhost%3A1234%2Fexplorer.html
|
||||||
|
|
||||||
|
Live reload will occur, but data explorer will not properly integrate again with the parent iframe. You will have to manually reload the page.
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
|
|||||||
1082
package-lock.json
generated
1082
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -6,15 +6,15 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/arm-cosmosdb": "9.1.0",
|
"@azure/arm-cosmosdb": "9.1.0",
|
||||||
"@azure/cosmos": "3.9.0",
|
"@azure/cosmos": "3.9.0",
|
||||||
"@azure/identity": "1.1.0",
|
|
||||||
"@azure/cosmos-language-service": "0.0.5",
|
"@azure/cosmos-language-service": "0.0.5",
|
||||||
|
"@azure/identity": "1.1.0",
|
||||||
"@jupyterlab/services": "6.0.0-rc.2",
|
"@jupyterlab/services": "6.0.0-rc.2",
|
||||||
"@jupyterlab/terminal": "3.0.0-rc.2",
|
"@jupyterlab/terminal": "3.0.0-rc.2",
|
||||||
"@microsoft/applicationinsights-web": "2.5.9",
|
"@microsoft/applicationinsights-web": "2.5.9",
|
||||||
"@nteract/commutable": "7.3.2",
|
"@nteract/commutable": "7.3.2",
|
||||||
"@nteract/connected-components": "6.8.2",
|
"@nteract/connected-components": "6.8.2",
|
||||||
"@nteract/core": "15.1.0",
|
"@nteract/core": "15.1.0",
|
||||||
"@nteract/data-explorer": "8.0.3",
|
"@nteract/data-explorer": "8.2.9",
|
||||||
"@nteract/directory-listing": "2.0.6",
|
"@nteract/directory-listing": "2.0.6",
|
||||||
"@nteract/dropdown-menu": "1.0.1",
|
"@nteract/dropdown-menu": "1.0.1",
|
||||||
"@nteract/editor": "10.1.2",
|
"@nteract/editor": "10.1.2",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"offerThroughput": 400,
|
"offerThroughput": 400,
|
||||||
"databaseLevelThroughput": false,
|
"databaseLevelThroughput": false,
|
||||||
"collectionId": "Persons",
|
"collectionId": "Persons",
|
||||||
|
"rupmEnabled": false,
|
||||||
"partitionKey": { "kind": "Hash", "paths": ["/name"] },
|
"partitionKey": { "kind": "Hash", "paths": ["/name"] },
|
||||||
"data": [
|
"data": [
|
||||||
"g.addV('person').property(id, '1').property('name', 'Eva').property('age', 44)",
|
"g.addV('person').property(id, '1').property('name', 'Eva').property('age', 44)",
|
||||||
|
|||||||
@@ -108,11 +108,13 @@ export class CapabilityNames {
|
|||||||
export class Features {
|
export class Features {
|
||||||
public static readonly cosmosdb = "cosmosdb";
|
public static readonly cosmosdb = "cosmosdb";
|
||||||
public static readonly enableChangeFeedPolicy = "enablechangefeedpolicy";
|
public static readonly enableChangeFeedPolicy = "enablechangefeedpolicy";
|
||||||
|
public static readonly enableRupm = "enablerupm";
|
||||||
public static readonly executeSproc = "dataexplorerexecutesproc";
|
public static readonly executeSproc = "dataexplorerexecutesproc";
|
||||||
public static readonly hostedDataExplorer = "hosteddataexplorerenabled";
|
public static readonly hostedDataExplorer = "hosteddataexplorerenabled";
|
||||||
public static readonly enableTtl = "enablettl";
|
public static readonly enableTtl = "enablettl";
|
||||||
public static readonly enableNotebooks = "enablenotebooks";
|
public static readonly enableNotebooks = "enablenotebooks";
|
||||||
public static readonly enableGalleryPublish = "enablegallerypublish";
|
public static readonly enableGalleryPublish = "enablegallerypublish";
|
||||||
|
public static readonly enableCodeOfConduct = "enablecodeofconduct";
|
||||||
public static readonly enableLinkInjection = "enablelinkinjection";
|
public static readonly enableLinkInjection = "enablelinkinjection";
|
||||||
public static readonly enableSpark = "enablespark";
|
public static readonly enableSpark = "enablespark";
|
||||||
public static readonly livyEndpoint = "livyendpoint";
|
public static readonly livyEndpoint = "livyendpoint";
|
||||||
@@ -179,6 +181,11 @@ export class CassandraBackend {
|
|||||||
public static readonly guestSchemaApi: string = "api/guest/cassandra/schema";
|
public static readonly guestSchemaApi: string = "api/guest/cassandra/schema";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class RUPMStates {
|
||||||
|
public static on: string = "on";
|
||||||
|
public static off: string = "off";
|
||||||
|
}
|
||||||
|
|
||||||
export class Queries {
|
export class Queries {
|
||||||
public static CustomPageOption: string = "custom";
|
public static CustomPageOption: string = "custom";
|
||||||
public static UnlimitedPageOption: string = "unlimited";
|
public static UnlimitedPageOption: string = "unlimited";
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
export function normalizeArmEndpoint(uri: string): string {
|
export default class EnvironmentUtility {
|
||||||
if (uri && uri.slice(-1) !== "/") {
|
public static normalizeArmEndpointUri(uri: string): string {
|
||||||
return `${uri}/`;
|
if (uri && uri.slice(-1) !== "/") {
|
||||||
|
return `${uri}/`;
|
||||||
|
}
|
||||||
|
return uri;
|
||||||
}
|
}
|
||||||
return uri;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ describe("parseSDKOfferResponse", () => {
|
|||||||
autoscaleMaxThroughput: undefined,
|
autoscaleMaxThroughput: undefined,
|
||||||
minimumThroughput: 400,
|
minimumThroughput: 400,
|
||||||
id: "test",
|
id: "test",
|
||||||
offerDefinition: mockOfferDefinition,
|
offerDefinition: mockOfferDefinition
|
||||||
offerReplacePending: false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(expectedResult);
|
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(expectedResult);
|
||||||
@@ -55,8 +54,7 @@ describe("parseSDKOfferResponse", () => {
|
|||||||
autoscaleMaxThroughput: 5000,
|
autoscaleMaxThroughput: 5000,
|
||||||
minimumThroughput: 400,
|
minimumThroughput: 400,
|
||||||
id: "test",
|
id: "test",
|
||||||
offerDefinition: mockOfferDefinition,
|
offerDefinition: mockOfferDefinition
|
||||||
offerReplacePending: false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(expectedResult);
|
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(expectedResult);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Offer, SDKOfferDefinition } from "../Contracts/DataModels";
|
import { Offer, SDKOfferDefinition } from "../Contracts/DataModels";
|
||||||
import { OfferResponse } from "@azure/cosmos";
|
import { OfferResponse } from "@azure/cosmos";
|
||||||
import { HttpHeaders } from "./Constants";
|
|
||||||
|
|
||||||
export const parseSDKOfferResponse = (offerResponse: OfferResponse): Offer => {
|
export const parseSDKOfferResponse = (offerResponse: OfferResponse): Offer => {
|
||||||
const offerDefinition: SDKOfferDefinition = offerResponse?.resource;
|
const offerDefinition: SDKOfferDefinition = offerResponse?.resource;
|
||||||
@@ -19,7 +18,7 @@ export const parseSDKOfferResponse = (offerResponse: OfferResponse): Offer => {
|
|||||||
manualThroughput: undefined,
|
manualThroughput: undefined,
|
||||||
minimumThroughput,
|
minimumThroughput,
|
||||||
offerDefinition,
|
offerDefinition,
|
||||||
offerReplacePending: offerResponse.headers?.[HttpHeaders.offerReplacePending] === "true"
|
headers: offerResponse.headers
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,6 +28,6 @@ export const parseSDKOfferResponse = (offerResponse: OfferResponse): Offer => {
|
|||||||
manualThroughput: offerContent.offerThroughput,
|
manualThroughput: offerContent.offerThroughput,
|
||||||
minimumThroughput,
|
minimumThroughput,
|
||||||
offerDefinition,
|
offerDefinition,
|
||||||
offerReplacePending: offerResponse.headers?.[HttpHeaders.offerReplacePending] === "true"
|
headers: offerResponse.headers
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
import { AuthType } from "../../AuthType";
|
|
||||||
import { armRequest } from "../../Utils/arm/request";
|
|
||||||
import { configContext } from "../../ConfigContext";
|
|
||||||
import { handleError } from "../ErrorHandlingUtils";
|
|
||||||
import { userContext } from "../../UserContext";
|
|
||||||
|
|
||||||
interface TimeSeriesData {
|
|
||||||
data: {
|
|
||||||
timeStamp: string;
|
|
||||||
total: number;
|
|
||||||
}[];
|
|
||||||
metadatavalues: {
|
|
||||||
name: {
|
|
||||||
localizedValue: string;
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MetricsData {
|
|
||||||
displayDescription: string;
|
|
||||||
errorCode: string;
|
|
||||||
id: string;
|
|
||||||
name: {
|
|
||||||
value: string;
|
|
||||||
localizedValue: string;
|
|
||||||
};
|
|
||||||
timeseries: TimeSeriesData[];
|
|
||||||
type: string;
|
|
||||||
unit: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MetricsResponse {
|
|
||||||
cost: number;
|
|
||||||
interval: string;
|
|
||||||
namespace: string;
|
|
||||||
resourceregion: string;
|
|
||||||
timespan: string;
|
|
||||||
value: MetricsData[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getCollectionUsageSizeInKB = async (databaseName: string, containerName: string): Promise<number> => {
|
|
||||||
if (window.authType !== AuthType.AAD) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const subscriptionId = userContext.subscriptionId;
|
|
||||||
const resourceGroup = userContext.resourceGroup;
|
|
||||||
const accountName = userContext.databaseAccount.name;
|
|
||||||
const filter = `DatabaseName eq '${databaseName}' and CollectionName eq '${containerName}'`;
|
|
||||||
const metricNames = "DataUsage,IndexUsage";
|
|
||||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/providers/microsoft.insights/metrics`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const metricsResponse: MetricsResponse = await armRequest({
|
|
||||||
host: configContext.ARM_ENDPOINT,
|
|
||||||
path,
|
|
||||||
method: "GET",
|
|
||||||
apiVersion: "2018-01-01",
|
|
||||||
queryParams: {
|
|
||||||
filter,
|
|
||||||
metricNames
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (metricsResponse?.value?.length !== 2) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dataUsageData: MetricsData = metricsResponse.value[0];
|
|
||||||
const indexUsagedata: MetricsData = metricsResponse.value[1];
|
|
||||||
const dataUsageSizeInKb: number = getUsageSizeInKb(dataUsageData);
|
|
||||||
const indexUsageSizeInKb: number = getUsageSizeInKb(indexUsagedata);
|
|
||||||
|
|
||||||
return dataUsageSizeInKb + indexUsageSizeInKb;
|
|
||||||
} catch (error) {
|
|
||||||
handleError(error, "getCollectionUsageSize");
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getUsageSizeInKb = (metricsData: MetricsData): number => {
|
|
||||||
if (metricsData?.errorCode !== "Success") {
|
|
||||||
throw Error(`Get collection usage size failed: ${metricsData.errorCode}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeSeriesData: TimeSeriesData = metricsData?.timeseries?.[0];
|
|
||||||
const usageSizeInBytes: number = timeSeriesData?.data?.[0]?.total;
|
|
||||||
|
|
||||||
return usageSizeInBytes ? usageSizeInBytes / 1024 : 0;
|
|
||||||
};
|
|
||||||
@@ -105,8 +105,7 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
|
|||||||
id: offerId,
|
id: offerId,
|
||||||
autoscaleMaxThroughput: autoscaleSettings.maxThroughput,
|
autoscaleMaxThroughput: autoscaleSettings.maxThroughput,
|
||||||
manualThroughput: undefined,
|
manualThroughput: undefined,
|
||||||
minimumThroughput,
|
minimumThroughput
|
||||||
offerReplacePending: resource.offerReplacePending === "true"
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,8 +113,7 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
|
|||||||
id: offerId,
|
id: offerId,
|
||||||
autoscaleMaxThroughput: undefined,
|
autoscaleMaxThroughput: undefined,
|
||||||
manualThroughput: resource.throughput,
|
manualThroughput: resource.throughput,
|
||||||
minimumThroughput,
|
minimumThroughput
|
||||||
offerReplacePending: resource.offerReplacePending === "true"
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
45
src/Common/dataAccess/readCollectionQuotaInfo.ts
Normal file
45
src/Common/dataAccess/readCollectionQuotaInfo.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
|
import * as HeadersUtility from "../HeadersUtility";
|
||||||
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
|
import { ContainerDefinition, Resource } from "@azure/cosmos";
|
||||||
|
import { HttpHeaders } from "../Constants";
|
||||||
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
|
import { client } from "../CosmosClient";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
|
||||||
|
interface ResourceWithStatistics {
|
||||||
|
statistics: DataModels.Statistic[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const readCollectionQuotaInfo = async (
|
||||||
|
collection: ViewModels.Collection
|
||||||
|
): Promise<DataModels.CollectionQuotaInfo> => {
|
||||||
|
const clearMessage = logConsoleProgress(`Querying containers for database ${collection.id}`);
|
||||||
|
const options: RequestOptions = {};
|
||||||
|
options.populateQuotaInfo = true;
|
||||||
|
options.initialHeaders = options.initialHeaders || {};
|
||||||
|
options.initialHeaders[HttpHeaders.populatePartitionStatistics] = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.read(options);
|
||||||
|
const quota: DataModels.CollectionQuotaInfo = HeadersUtility.getQuota(response.headers);
|
||||||
|
const resource = response.resource as ContainerDefinition & Resource & ResourceWithStatistics;
|
||||||
|
quota["usageSizeInKB"] = resource.statistics.reduce(
|
||||||
|
(previousValue: number, currentValue: DataModels.Statistic) => previousValue + currentValue.sizeInKB,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
quota["numPartitions"] = resource.statistics.length;
|
||||||
|
quota["uniqueKeyPolicy"] = collection.uniqueKeyPolicy; // TODO: Remove after refactoring (#119617)
|
||||||
|
|
||||||
|
return quota;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "ReadCollectionQuotaInfo", `Error while querying quota info for container ${collection.id}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -77,8 +77,7 @@ const readDatabaseOfferWithARM = async (databaseId: string): Promise<Offer> => {
|
|||||||
id: offerId,
|
id: offerId,
|
||||||
autoscaleMaxThroughput: autoscaleSettings.maxThroughput,
|
autoscaleMaxThroughput: autoscaleSettings.maxThroughput,
|
||||||
manualThroughput: undefined,
|
manualThroughput: undefined,
|
||||||
minimumThroughput,
|
minimumThroughput
|
||||||
offerReplacePending: resource.offerReplacePending === "true"
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,8 +85,7 @@ const readDatabaseOfferWithARM = async (databaseId: string): Promise<Offer> => {
|
|||||||
id: offerId,
|
id: offerId,
|
||||||
autoscaleMaxThroughput: undefined,
|
autoscaleMaxThroughput: undefined,
|
||||||
manualThroughput: resource.throughput,
|
manualThroughput: resource.throughput,
|
||||||
minimumThroughput,
|
minimumThroughput
|
||||||
offerReplacePending: resource.offerReplacePending === "true"
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ interface ConfigContext {
|
|||||||
GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it.
|
GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it.
|
||||||
hostedExplorerURL: string;
|
hostedExplorerURL: string;
|
||||||
armAPIVersion?: string;
|
armAPIVersion?: string;
|
||||||
serverId?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default configuration
|
// Default configuration
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ export interface Offer {
|
|||||||
manualThroughput: number;
|
manualThroughput: number;
|
||||||
minimumThroughput: number;
|
minimumThroughput: number;
|
||||||
offerDefinition?: SDKOfferDefinition;
|
offerDefinition?: SDKOfferDefinition;
|
||||||
offerReplacePending: boolean;
|
headers?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SDKOfferDefinition extends Resource {
|
export interface SDKOfferDefinition extends Resource {
|
||||||
@@ -230,6 +230,18 @@ export interface SDKOfferDefinition extends Resource {
|
|||||||
offerResourceId?: string;
|
offerResourceId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CollectionQuotaInfo {
|
||||||
|
storedProcedures: number;
|
||||||
|
triggers: number;
|
||||||
|
functions: number;
|
||||||
|
documentsSize: number;
|
||||||
|
collectionSize: number;
|
||||||
|
documentsCount: number;
|
||||||
|
usageSizeInKB: number;
|
||||||
|
numPartitions: number;
|
||||||
|
uniqueKeyPolicy?: UniqueKeyPolicy; // TODO: This should ideally not be a part of the collection quota. Remove after refactoring. (#119617)
|
||||||
|
}
|
||||||
|
|
||||||
export interface OfferThroughputInfo {
|
export interface OfferThroughputInfo {
|
||||||
minimumRUForCollection: number;
|
minimumRUForCollection: number;
|
||||||
numPhysicalPartitions: number;
|
numPhysicalPartitions: number;
|
||||||
@@ -248,6 +260,7 @@ export interface CreateDatabaseAndCollectionRequest {
|
|||||||
collectionId: string;
|
collectionId: string;
|
||||||
offerThroughput: number;
|
offerThroughput: number;
|
||||||
databaseLevelThroughput: boolean;
|
databaseLevelThroughput: boolean;
|
||||||
|
rupmEnabled?: boolean;
|
||||||
partitionKey?: PartitionKey;
|
partitionKey?: PartitionKey;
|
||||||
indexingPolicy?: IndexingPolicy;
|
indexingPolicy?: IndexingPolicy;
|
||||||
uniqueKeyPolicy?: UniqueKeyPolicy;
|
uniqueKeyPolicy?: UniqueKeyPolicy;
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ export interface Collection extends CollectionBase {
|
|||||||
requestSchema?: () => void;
|
requestSchema?: () => void;
|
||||||
indexingPolicy: ko.Observable<DataModels.IndexingPolicy>;
|
indexingPolicy: ko.Observable<DataModels.IndexingPolicy>;
|
||||||
uniqueKeyPolicy: DataModels.UniqueKeyPolicy;
|
uniqueKeyPolicy: DataModels.UniqueKeyPolicy;
|
||||||
usageSizeInKB: ko.Observable<number>;
|
quotaInfo: ko.Observable<DataModels.CollectionQuotaInfo>;
|
||||||
offer: ko.Observable<DataModels.Offer>;
|
offer: ko.Observable<DataModels.Offer>;
|
||||||
conflictResolutionPolicy: ko.Observable<DataModels.ConflictResolutionPolicy>;
|
conflictResolutionPolicy: ko.Observable<DataModels.ConflictResolutionPolicy>;
|
||||||
changeFeedPolicy: ko.Observable<DataModels.ChangeFeedPolicy>;
|
changeFeedPolicy: ko.Observable<DataModels.ChangeFeedPolicy>;
|
||||||
|
|||||||
@@ -44,10 +44,12 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
|
|||||||
onChange?: (_?: React.FormEvent<HTMLElement | HTMLInputElement>, checked?: boolean) => void;
|
onChange?: (_?: React.FormEvent<HTMLElement | HTMLInputElement>, checked?: boolean) => void;
|
||||||
}[] = [
|
}[] = [
|
||||||
{ key: "feature.enablechangefeedpolicy", label: "Enable change feed policy", value: "true" },
|
{ key: "feature.enablechangefeedpolicy", label: "Enable change feed policy", value: "true" },
|
||||||
|
{ key: "feature.enablerupm", label: "Enable RUPM", value: "true" },
|
||||||
{ key: "feature.dataexplorerexecutesproc", label: "Execute stored procedure", value: "true" },
|
{ key: "feature.dataexplorerexecutesproc", label: "Execute stored procedure", value: "true" },
|
||||||
{ key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" },
|
{ key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" },
|
||||||
{ key: "feature.enablettl", label: "Enable TTL", value: "true" },
|
{ key: "feature.enablettl", label: "Enable TTL", value: "true" },
|
||||||
{ key: "feature.enablegallerypublish", label: "Enable Notebook Gallery Publishing", value: "true" },
|
{ key: "feature.enablegallerypublish", label: "Enable Notebook Gallery Publishing", value: "true" },
|
||||||
|
{ key: "feature.enablecodeofconduct", label: "Enable Code Of Conduct Acknowledgement", value: "true" },
|
||||||
{
|
{
|
||||||
key: "feature.enableLinkInjection",
|
key: "feature.enableLinkInjection",
|
||||||
label: "Enable Injecting Notebook Viewer Link into the first cell",
|
label: "Enable Injecting Notebook Viewer Link into the first cell",
|
||||||
|
|||||||
@@ -131,6 +131,12 @@ exports[`Feature panel renders all flags 1`] = `
|
|||||||
label="Enable change feed policy"
|
label="Enable change feed policy"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
/>
|
/>
|
||||||
|
<StyledCheckboxBase
|
||||||
|
checked={false}
|
||||||
|
key="feature.enablerupm"
|
||||||
|
label="Enable RUPM"
|
||||||
|
onChange={[Function]}
|
||||||
|
/>
|
||||||
<StyledCheckboxBase
|
<StyledCheckboxBase
|
||||||
checked={false}
|
checked={false}
|
||||||
key="feature.dataexplorerexecutesproc"
|
key="feature.dataexplorerexecutesproc"
|
||||||
@@ -157,14 +163,14 @@ exports[`Feature panel renders all flags 1`] = `
|
|||||||
/>
|
/>
|
||||||
<StyledCheckboxBase
|
<StyledCheckboxBase
|
||||||
checked={false}
|
checked={false}
|
||||||
key="feature.enableLinkInjection"
|
key="feature.enablecodeofconduct"
|
||||||
label="Enable Injecting Notebook Viewer Link into the first cell"
|
label="Enable Code Of Conduct Acknowledgement"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
/>
|
/>
|
||||||
<StyledCheckboxBase
|
<StyledCheckboxBase
|
||||||
checked={false}
|
checked={false}
|
||||||
key="feature.canexceedmaximumvalue"
|
key="feature.enableLinkInjection"
|
||||||
label="Can exceed max value"
|
label="Enable Injecting Notebook Viewer Link into the first cell"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -172,6 +178,12 @@ exports[`Feature panel renders all flags 1`] = `
|
|||||||
className="checkboxRow"
|
className="checkboxRow"
|
||||||
horizontalAlign="space-between"
|
horizontalAlign="space-between"
|
||||||
>
|
>
|
||||||
|
<StyledCheckboxBase
|
||||||
|
checked={false}
|
||||||
|
key="feature.canexceedmaximumvalue"
|
||||||
|
label="Can exceed max value"
|
||||||
|
onChange={[Function]}
|
||||||
|
/>
|
||||||
<StyledCheckboxBase
|
<StyledCheckboxBase
|
||||||
checked={false}
|
checked={false}
|
||||||
key="feature.enablefixedcollectionwithsharedthroughput"
|
key="feature.enablefixedcollectionwithsharedthroughput"
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
|
|||||||
params.set(TerminalQueryParams.TerminalEndpoint, terminalEndpoint);
|
params.set(TerminalQueryParams.TerminalEndpoint, terminalEndpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
params.set("account","contoso-retail-mongodb");
|
||||||
|
params.set("port","10255");
|
||||||
|
//tofill
|
||||||
|
params.set("token","");
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
Dropdown,
|
Dropdown,
|
||||||
FocusZone,
|
FocusZone,
|
||||||
FontIcon,
|
|
||||||
FontWeights,
|
FontWeights,
|
||||||
IDropdownOption,
|
IDropdownOption,
|
||||||
IPageSpecification,
|
IPageSpecification,
|
||||||
@@ -17,7 +16,7 @@ import {
|
|||||||
Text
|
Text
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { IGalleryItem, IJunoResponse, IPublicGalleryData, JunoClient } from "../../../Juno/JunoClient";
|
import { IGalleryItem, JunoClient, IJunoResponse, IPublicGalleryData } from "../../../Juno/JunoClient";
|
||||||
import * as GalleryUtils from "../../../Utils/GalleryUtils";
|
import * as GalleryUtils from "../../../Utils/GalleryUtils";
|
||||||
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent";
|
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent";
|
||||||
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
|
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
|
||||||
@@ -137,7 +136,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
const tabs: GalleryTabInfo[] = [this.createSamplesTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)];
|
const tabs: GalleryTabInfo[] = [this.createTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)];
|
||||||
|
|
||||||
if (this.props.container?.isGalleryPublishEnabled()) {
|
if (this.props.container?.isGalleryPublishEnabled()) {
|
||||||
tabs.push(
|
tabs.push(
|
||||||
@@ -147,7 +146,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
this.state.isCodeOfConductAccepted
|
this.state.isCodeOfConductAccepted
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
tabs.push(this.createFavoritesTab(GalleryTab.Favorites, this.state.favoriteNotebooks));
|
tabs.push(this.createTab(GalleryTab.Favorites, this.state.favoriteNotebooks));
|
||||||
|
|
||||||
// explicitly checking if isCodeOfConductAccepted is not false, as it is initially undefined.
|
// explicitly checking if isCodeOfConductAccepted is not false, as it is initially undefined.
|
||||||
// Displaying code of conduct component on gallery load should not be the default behavior.
|
// Displaying code of conduct component on gallery load should not be the default behavior.
|
||||||
@@ -184,27 +183,6 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private isEmptyData = (data: IGalleryItem[]): boolean => {
|
|
||||||
return !data || data.length === 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
private createEmptyTabContent = (iconName: string, line1: string, line2: string): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<Stack horizontalAlign="center" tokens={{ childrenGap: 10 }}>
|
|
||||||
<FontIcon iconName={iconName} style={{ fontSize: 100, color: "lightgray", marginTop: 20 }} />
|
|
||||||
<Text styles={{ root: { fontWeight: FontWeights.semibold } }}>{line1}</Text>
|
|
||||||
<Text>{line2}</Text>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
private createSamplesTab = (tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo => {
|
|
||||||
return {
|
|
||||||
tab,
|
|
||||||
content: this.createSearchBarHeader(this.createCardsTabContent(data))
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
private createPublicGalleryTab(
|
private createPublicGalleryTab(
|
||||||
tab: GalleryTab,
|
tab: GalleryTab,
|
||||||
data: IGalleryItem[],
|
data: IGalleryItem[],
|
||||||
@@ -216,29 +194,17 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private createFavoritesTab(tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo {
|
private createTab(tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo {
|
||||||
return {
|
return {
|
||||||
tab,
|
tab,
|
||||||
content: this.isEmptyData(data)
|
content: this.createSearchBarHeader(this.createCardsTabContent(data))
|
||||||
? this.createEmptyTabContent(
|
|
||||||
"ContactHeart",
|
|
||||||
"You have not liked anything",
|
|
||||||
"Like any notebook from Official Samples or Public gallery"
|
|
||||||
)
|
|
||||||
: this.createSearchBarHeader(this.createCardsTabContent(data))
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private createPublishedNotebooksTab = (tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo => {
|
private createPublishedNotebooksTab = (tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo => {
|
||||||
return {
|
return {
|
||||||
tab,
|
tab,
|
||||||
content: this.isEmptyData(data)
|
content: this.createPublishedNotebooksTabContent(data)
|
||||||
? this.createEmptyTabContent(
|
|
||||||
"Contact",
|
|
||||||
"You have not published anything",
|
|
||||||
"Publish your sample notebooks to share your published work with others"
|
|
||||||
)
|
|
||||||
: this.createPublishedNotebooksTabContent(data)
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -398,9 +364,9 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
private async loadPublicNotebooks(searchText: string, sortBy: SortBy, offline: boolean): Promise<void> {
|
private async loadPublicNotebooks(searchText: string, sortBy: SortBy, offline: boolean): Promise<void> {
|
||||||
if (!offline) {
|
if (!offline) {
|
||||||
try {
|
try {
|
||||||
let response: IJunoResponse<IGalleryItem[]> | IJunoResponse<IPublicGalleryData>;
|
let response: IJunoResponse<IPublicGalleryData> | IJunoResponse<IGalleryItem[]>;
|
||||||
if (this.props.container) {
|
if (this.props.container.isCodeOfConductEnabled()) {
|
||||||
response = await this.props.junoClient.getPublicGalleryData();
|
response = await this.props.junoClient.fetchPublicNotebooks();
|
||||||
this.isCodeOfConductAccepted = response.data?.metadata.acceptedCodeOfConduct;
|
this.isCodeOfConductAccepted = response.data?.metadata.acceptedCodeOfConduct;
|
||||||
this.publicNotebooks = response.data?.notebooksData;
|
this.publicNotebooks = response.data?.notebooksData;
|
||||||
} else {
|
} else {
|
||||||
@@ -602,7 +568,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
|
|
||||||
private deleteItem = async (data: IGalleryItem): Promise<void> => {
|
private deleteItem = async (data: IGalleryItem): Promise<void> => {
|
||||||
GalleryUtils.deleteItem(this.props.container, this.props.junoClient, data, item => {
|
GalleryUtils.deleteItem(this.props.container, this.props.junoClient, data, item => {
|
||||||
this.publishedNotebooks = this.publishedNotebooks?.filter(notebook => item.id !== notebook.id);
|
this.publishedNotebooks = this.publishedNotebooks.filter(notebook => item.id !== notebook.id);
|
||||||
this.refreshSelectedTab(item);
|
this.refreshSelectedTab(item);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -92,8 +92,7 @@ describe("SettingsComponent", () => {
|
|||||||
autoscaleMaxThroughput: 10000,
|
autoscaleMaxThroughput: 10000,
|
||||||
manualThroughput: undefined,
|
manualThroughput: undefined,
|
||||||
minimumThroughput: 400,
|
minimumThroughput: 400,
|
||||||
id: "test",
|
id: "test"
|
||||||
offerReplacePending: false
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const props = { ...baseProps };
|
const props = { ...baseProps };
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
!!this.collection.conflictResolutionPolicy();
|
!!this.collection.conflictResolutionPolicy();
|
||||||
|
|
||||||
public isOfferReplacePending = (): boolean => {
|
public isOfferReplacePending = (): boolean => {
|
||||||
return this.collection?.offer()?.offerReplacePending;
|
return !!this.collection?.offer()?.headers?.[Constants.HttpHeaders.offerReplacePending];
|
||||||
};
|
};
|
||||||
|
|
||||||
public onSaveClick = async (): Promise<void> => {
|
public onSaveClick = async (): Promise<void> => {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class SettingsRenderUtilsTestComponent extends React.Component {
|
|||||||
{getAutoPilotV3SpendElement(1000, true)}
|
{getAutoPilotV3SpendElement(1000, true)}
|
||||||
{getAutoPilotV3SpendElement(undefined, true)}
|
{getAutoPilotV3SpendElement(undefined, true)}
|
||||||
|
|
||||||
{getEstimatedSpendElement(1000, "mooncake", 2, false)}
|
{getEstimatedSpendElement(1000, "mooncake", 2, false, true)}
|
||||||
|
|
||||||
{getEstimatedAutoscaleSpendElement(1000, "mooncake", 2, false)}
|
{getEstimatedAutoscaleSpendElement(1000, "mooncake", 2, false)}
|
||||||
|
|
||||||
|
|||||||
@@ -199,9 +199,10 @@ export const getEstimatedSpendElement = (
|
|||||||
throughput: number,
|
throughput: number,
|
||||||
serverId: string,
|
serverId: string,
|
||||||
regions: number,
|
regions: number,
|
||||||
multimaster: boolean
|
multimaster: boolean,
|
||||||
|
rupmEnabled: boolean
|
||||||
): JSX.Element => {
|
): JSX.Element => {
|
||||||
const hourlyPrice: number = computeRUUsagePriceHourly(serverId, throughput, regions, multimaster);
|
const hourlyPrice: number = computeRUUsagePriceHourly(serverId, rupmEnabled, throughput, regions, multimaster);
|
||||||
const dailyPrice: number = hourlyPrice * 24;
|
const dailyPrice: number = hourlyPrice * 24;
|
||||||
const monthlyPrice: number = hourlyPrice * hoursInAMonth;
|
const monthlyPrice: number = hourlyPrice * hoursInAMonth;
|
||||||
const currency: string = getPriceCurrency(serverId);
|
const currency: string = getPriceCurrency(serverId);
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ describe("ScaleComponent", () => {
|
|||||||
autoscaleMaxThroughput: maxThroughput,
|
autoscaleMaxThroughput: maxThroughput,
|
||||||
minimumThroughput: 400,
|
minimumThroughput: 400,
|
||||||
id: "offer",
|
id: "offer",
|
||||||
offerReplacePending: true
|
headers: { "x-ms-offer-replace-pending": true }
|
||||||
});
|
});
|
||||||
const newProps = {
|
const newProps = {
|
||||||
...baseProps,
|
...baseProps,
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const offer = this.props.collection?.offer();
|
const offer = this.props.collection?.offer();
|
||||||
if (offer?.offerReplacePending) {
|
if (offer?.headers?.[Constants.HttpHeaders.offerReplacePending]) {
|
||||||
const throughput = offer.manualThroughput || offer.autoscaleMaxThroughput;
|
const throughput = offer.manualThroughput || offer.autoscaleMaxThroughput;
|
||||||
return getThroughputApplyShortDelayMessage(
|
return getThroughputApplyShortDelayMessage(
|
||||||
this.props.isAutoPilotSelected,
|
this.props.isAutoPilotSelected,
|
||||||
@@ -165,7 +165,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
private getThroughputInputComponent = (): JSX.Element => (
|
private getThroughputInputComponent = (): JSX.Element => (
|
||||||
<ThroughputInputAutoPilotV3Component
|
<ThroughputInputAutoPilotV3Component
|
||||||
databaseAccount={this.props.container.databaseAccount()}
|
databaseAccount={this.props.container.databaseAccount()}
|
||||||
serverId={configContext.serverId}
|
serverId={this.props.container.serverId()}
|
||||||
throughput={this.props.throughput}
|
throughput={this.props.throughput}
|
||||||
throughputBaseline={this.props.throughputBaseline}
|
throughputBaseline={this.props.throughputBaseline}
|
||||||
onThroughputChange={this.props.onThroughputChange}
|
onThroughputChange={this.props.onThroughputChange}
|
||||||
@@ -186,7 +186,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
onScaleSaveableChange={this.props.onScaleSaveableChange}
|
onScaleSaveableChange={this.props.onScaleSaveableChange}
|
||||||
onScaleDiscardableChange={this.props.onScaleDiscardableChange}
|
onScaleDiscardableChange={this.props.onScaleDiscardableChange}
|
||||||
getThroughputWarningMessage={this.getThroughputWarningMessage}
|
getThroughputWarningMessage={this.getThroughputWarningMessage}
|
||||||
usageSizeInKB={this.props.collection.usageSizeInKB()}
|
usageSizeInKB={this.props.collection.quotaInfo().usageSizeInKB}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -179,7 +179,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
this.overrideWithAutoPilotSettings() ? this.props.maxAutoPilotThroughput : offerThroughput,
|
this.overrideWithAutoPilotSettings() ? this.props.maxAutoPilotThroughput : offerThroughput,
|
||||||
serverId,
|
serverId,
|
||||||
regions,
|
regions,
|
||||||
multimaster
|
multimaster,
|
||||||
|
false
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
estimatedSpend = getEstimatedAutoscaleSpendElement(
|
estimatedSpend = getEstimatedAutoscaleSpendElement(
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ exports[`ScaleComponent renders with correct initial notification 1`] = `
|
|||||||
spendAckChecked={false}
|
spendAckChecked={false}
|
||||||
throughput={1000}
|
throughput={1000}
|
||||||
throughputBaseline={1000}
|
throughputBaseline={1000}
|
||||||
usageSizeInKB={100}
|
|
||||||
wasAutopilotOriginallySet={true}
|
wasAutopilotOriginallySet={true}
|
||||||
/>
|
/>
|
||||||
<Stack
|
<Stack
|
||||||
|
|||||||
@@ -18,13 +18,12 @@ export const collection = ({
|
|||||||
excludedPaths: []
|
excludedPaths: []
|
||||||
}),
|
}),
|
||||||
uniqueKeyPolicy: {} as DataModels.UniqueKeyPolicy,
|
uniqueKeyPolicy: {} as DataModels.UniqueKeyPolicy,
|
||||||
usageSizeInKB: ko.observable(100),
|
quotaInfo: ko.observable<DataModels.CollectionQuotaInfo>({} as DataModels.CollectionQuotaInfo),
|
||||||
offer: ko.observable<DataModels.Offer>({
|
offer: ko.observable<DataModels.Offer>({
|
||||||
autoscaleMaxThroughput: undefined,
|
autoscaleMaxThroughput: undefined,
|
||||||
manualThroughput: 10000,
|
manualThroughput: 10000,
|
||||||
minimumThroughput: 6000,
|
minimumThroughput: 6000,
|
||||||
id: "offer",
|
id: "offer"
|
||||||
offerReplacePending: false
|
|
||||||
}),
|
}),
|
||||||
conflictResolutionPolicy: ko.observable<DataModels.ConflictResolutionPolicy>(
|
conflictResolutionPolicy: ko.observable<DataModels.ConflictResolutionPolicy>(
|
||||||
{} as DataModels.ConflictResolutionPolicy
|
{} as DataModels.ConflictResolutionPolicy
|
||||||
|
|||||||
@@ -133,6 +133,8 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"partitionKeyVisible": [Function],
|
"partitionKeyVisible": [Function],
|
||||||
"requestUnitsUsageCost": [Function],
|
"requestUnitsUsageCost": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
|
"rupm": [Function],
|
||||||
|
"rupmVisible": [Function],
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
"shouldCreateMongoWildcardIndex": [Function],
|
||||||
@@ -620,6 +622,8 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"partitionKeyVisible": [Function],
|
"partitionKeyVisible": [Function],
|
||||||
"requestUnitsUsageCost": [Function],
|
"requestUnitsUsageCost": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
|
"rupm": [Function],
|
||||||
|
"rupmVisible": [Function],
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
"shouldCreateMongoWildcardIndex": [Function],
|
||||||
@@ -731,6 +735,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
|
"armEndpoint": [Function],
|
||||||
"browseQueriesPane": BrowseQueriesPane {
|
"browseQueriesPane": BrowseQueriesPane {
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -942,6 +947,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"hasWriteAccess": [Function],
|
"hasWriteAccess": [Function],
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
"isAuthWithResourceToken": [Function],
|
"isAuthWithResourceToken": [Function],
|
||||||
|
"isCodeOfConductEnabled": [Function],
|
||||||
"isCopyNotebookPaneEnabled": [Function],
|
"isCopyNotebookPaneEnabled": [Function],
|
||||||
"isEnableMongoCapabilityPresent": [Function],
|
"isEnableMongoCapabilityPresent": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
@@ -1022,6 +1028,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"onSwitchToConnectionString": [Function],
|
"onSwitchToConnectionString": [Function],
|
||||||
"onToggleKeyDown": [Function],
|
"onToggleKeyDown": [Function],
|
||||||
|
"parentFrameDataExplorerVersion": [Function],
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -1047,6 +1054,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"titleLabel": "Select Columns",
|
"titleLabel": "Select Columns",
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
|
"quotaId": [Function],
|
||||||
"refreshDatabaseAccount": [Function],
|
"refreshDatabaseAccount": [Function],
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"refreshTreeTitle": [Function],
|
"refreshTreeTitle": [Function],
|
||||||
@@ -1292,9 +1300,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"version": 2,
|
"version": 2,
|
||||||
},
|
},
|
||||||
"partitionKeyProperty": "partitionKey",
|
"partitionKeyProperty": "partitionKey",
|
||||||
|
"quotaInfo": [Function],
|
||||||
"readSettings": [Function],
|
"readSettings": [Function],
|
||||||
"uniqueKeyPolicy": Object {},
|
"uniqueKeyPolicy": Object {},
|
||||||
"usageSizeInKB": [Function],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
container={
|
container={
|
||||||
@@ -1404,6 +1412,8 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"partitionKeyVisible": [Function],
|
"partitionKeyVisible": [Function],
|
||||||
"requestUnitsUsageCost": [Function],
|
"requestUnitsUsageCost": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
|
"rupm": [Function],
|
||||||
|
"rupmVisible": [Function],
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
"shouldCreateMongoWildcardIndex": [Function],
|
||||||
@@ -1891,6 +1901,8 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"partitionKeyVisible": [Function],
|
"partitionKeyVisible": [Function],
|
||||||
"requestUnitsUsageCost": [Function],
|
"requestUnitsUsageCost": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
|
"rupm": [Function],
|
||||||
|
"rupmVisible": [Function],
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
"shouldCreateMongoWildcardIndex": [Function],
|
||||||
@@ -2002,6 +2014,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
|
"armEndpoint": [Function],
|
||||||
"browseQueriesPane": BrowseQueriesPane {
|
"browseQueriesPane": BrowseQueriesPane {
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -2213,6 +2226,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"hasWriteAccess": [Function],
|
"hasWriteAccess": [Function],
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
"isAuthWithResourceToken": [Function],
|
"isAuthWithResourceToken": [Function],
|
||||||
|
"isCodeOfConductEnabled": [Function],
|
||||||
"isCopyNotebookPaneEnabled": [Function],
|
"isCopyNotebookPaneEnabled": [Function],
|
||||||
"isEnableMongoCapabilityPresent": [Function],
|
"isEnableMongoCapabilityPresent": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
@@ -2293,6 +2307,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"onSwitchToConnectionString": [Function],
|
"onSwitchToConnectionString": [Function],
|
||||||
"onToggleKeyDown": [Function],
|
"onToggleKeyDown": [Function],
|
||||||
|
"parentFrameDataExplorerVersion": [Function],
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -2318,6 +2333,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"titleLabel": "Select Columns",
|
"titleLabel": "Select Columns",
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
|
"quotaId": [Function],
|
||||||
"refreshDatabaseAccount": [Function],
|
"refreshDatabaseAccount": [Function],
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"refreshTreeTitle": [Function],
|
"refreshTreeTitle": [Function],
|
||||||
@@ -2688,6 +2704,8 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"partitionKeyVisible": [Function],
|
"partitionKeyVisible": [Function],
|
||||||
"requestUnitsUsageCost": [Function],
|
"requestUnitsUsageCost": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
|
"rupm": [Function],
|
||||||
|
"rupmVisible": [Function],
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
"shouldCreateMongoWildcardIndex": [Function],
|
||||||
@@ -3175,6 +3193,8 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"partitionKeyVisible": [Function],
|
"partitionKeyVisible": [Function],
|
||||||
"requestUnitsUsageCost": [Function],
|
"requestUnitsUsageCost": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
|
"rupm": [Function],
|
||||||
|
"rupmVisible": [Function],
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
"shouldCreateMongoWildcardIndex": [Function],
|
||||||
@@ -3286,6 +3306,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
|
"armEndpoint": [Function],
|
||||||
"browseQueriesPane": BrowseQueriesPane {
|
"browseQueriesPane": BrowseQueriesPane {
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -3497,6 +3518,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"hasWriteAccess": [Function],
|
"hasWriteAccess": [Function],
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
"isAuthWithResourceToken": [Function],
|
"isAuthWithResourceToken": [Function],
|
||||||
|
"isCodeOfConductEnabled": [Function],
|
||||||
"isCopyNotebookPaneEnabled": [Function],
|
"isCopyNotebookPaneEnabled": [Function],
|
||||||
"isEnableMongoCapabilityPresent": [Function],
|
"isEnableMongoCapabilityPresent": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
@@ -3577,6 +3599,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"onSwitchToConnectionString": [Function],
|
"onSwitchToConnectionString": [Function],
|
||||||
"onToggleKeyDown": [Function],
|
"onToggleKeyDown": [Function],
|
||||||
|
"parentFrameDataExplorerVersion": [Function],
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -3602,6 +3625,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"titleLabel": "Select Columns",
|
"titleLabel": "Select Columns",
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
|
"quotaId": [Function],
|
||||||
"refreshDatabaseAccount": [Function],
|
"refreshDatabaseAccount": [Function],
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"refreshTreeTitle": [Function],
|
"refreshTreeTitle": [Function],
|
||||||
@@ -3847,9 +3871,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"version": 2,
|
"version": 2,
|
||||||
},
|
},
|
||||||
"partitionKeyProperty": "partitionKey",
|
"partitionKeyProperty": "partitionKey",
|
||||||
|
"quotaInfo": [Function],
|
||||||
"readSettings": [Function],
|
"readSettings": [Function],
|
||||||
"uniqueKeyPolicy": Object {},
|
"uniqueKeyPolicy": Object {},
|
||||||
"usageSizeInKB": [Function],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
container={
|
container={
|
||||||
@@ -3959,6 +3983,8 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"partitionKeyVisible": [Function],
|
"partitionKeyVisible": [Function],
|
||||||
"requestUnitsUsageCost": [Function],
|
"requestUnitsUsageCost": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
|
"rupm": [Function],
|
||||||
|
"rupmVisible": [Function],
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
"shouldCreateMongoWildcardIndex": [Function],
|
||||||
@@ -4446,6 +4472,8 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"partitionKeyVisible": [Function],
|
"partitionKeyVisible": [Function],
|
||||||
"requestUnitsUsageCost": [Function],
|
"requestUnitsUsageCost": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
|
"rupm": [Function],
|
||||||
|
"rupmVisible": [Function],
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
"shouldCreateMongoWildcardIndex": [Function],
|
||||||
@@ -4557,6 +4585,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
|
"armEndpoint": [Function],
|
||||||
"browseQueriesPane": BrowseQueriesPane {
|
"browseQueriesPane": BrowseQueriesPane {
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -4768,6 +4797,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"hasWriteAccess": [Function],
|
"hasWriteAccess": [Function],
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
"isAuthWithResourceToken": [Function],
|
"isAuthWithResourceToken": [Function],
|
||||||
|
"isCodeOfConductEnabled": [Function],
|
||||||
"isCopyNotebookPaneEnabled": [Function],
|
"isCopyNotebookPaneEnabled": [Function],
|
||||||
"isEnableMongoCapabilityPresent": [Function],
|
"isEnableMongoCapabilityPresent": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
@@ -4848,6 +4878,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"onSwitchToConnectionString": [Function],
|
"onSwitchToConnectionString": [Function],
|
||||||
"onToggleKeyDown": [Function],
|
"onToggleKeyDown": [Function],
|
||||||
|
"parentFrameDataExplorerVersion": [Function],
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -4873,6 +4904,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"titleLabel": "Select Columns",
|
"titleLabel": "Select Columns",
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
|
"quotaId": [Function],
|
||||||
"refreshDatabaseAccount": [Function],
|
"refreshDatabaseAccount": [Function],
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"refreshTreeTitle": [Function],
|
"refreshTreeTitle": [Function],
|
||||||
|
|||||||
@@ -69,15 +69,15 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
|
|
||||||
<b>
|
<b>
|
||||||
¥
|
¥
|
||||||
1.02
|
1.29
|
||||||
hourly
|
hourly
|
||||||
/
|
/
|
||||||
¥
|
¥
|
||||||
24.48
|
31.06
|
||||||
daily
|
daily
|
||||||
/
|
/
|
||||||
¥
|
¥
|
||||||
744.60
|
944.60
|
||||||
monthly
|
monthly
|
||||||
|
|
||||||
</b>
|
</b>
|
||||||
|
|||||||
@@ -126,12 +126,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-bind="visible: !isAutoPilotSelected()">
|
<div data-bind="visible: !isAutoPilotSelected()">
|
||||||
<p>
|
|
||||||
<span
|
|
||||||
>Estimate your required throughput with
|
|
||||||
<a target="_blank" href="https://cosmos.azure.com/capacitycalculator/">capacity calculator</a></span
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
<div data-bind="setTemplateReady: true">
|
<div data-bind="setTemplateReady: true">
|
||||||
<input
|
<input
|
||||||
data-bind="
|
data-bind="
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import DeleteDatabaseConfirmationPane from "./Panes/DeleteDatabaseConfirmationPa
|
|||||||
import { readCollection } from "../Common/dataAccess/readCollection";
|
import { readCollection } from "../Common/dataAccess/readCollection";
|
||||||
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
||||||
import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane";
|
import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane";
|
||||||
import { normalizeArmEndpoint } from "../Common/EnvironmentUtility";
|
import EnvironmentUtility from "../Common/EnvironmentUtility";
|
||||||
import GraphStylingPane from "./Panes/GraphStylingPane";
|
import GraphStylingPane from "./Panes/GraphStylingPane";
|
||||||
import hasher from "hasher";
|
import hasher from "hasher";
|
||||||
import NewVertexPane from "./Panes/NewVertexPane";
|
import NewVertexPane from "./Panes/NewVertexPane";
|
||||||
@@ -121,6 +121,7 @@ export default class Explorer {
|
|||||||
public databaseAccount: ko.Observable<DataModels.DatabaseAccount>;
|
public databaseAccount: ko.Observable<DataModels.DatabaseAccount>;
|
||||||
public collectionCreationDefaults: ViewModels.CollectionCreationDefaults = SharedConstants.CollectionCreationDefaults;
|
public collectionCreationDefaults: ViewModels.CollectionCreationDefaults = SharedConstants.CollectionCreationDefaults;
|
||||||
public subscriptionType: ko.Observable<SubscriptionType>;
|
public subscriptionType: ko.Observable<SubscriptionType>;
|
||||||
|
public quotaId: ko.Observable<string>;
|
||||||
public defaultExperience: ko.Observable<string>;
|
public defaultExperience: ko.Observable<string>;
|
||||||
public isPreferredApiDocumentDB: ko.Computed<boolean>;
|
public isPreferredApiDocumentDB: ko.Computed<boolean>;
|
||||||
public isPreferredApiCassandra: ko.Computed<boolean>;
|
public isPreferredApiCassandra: ko.Computed<boolean>;
|
||||||
@@ -133,10 +134,13 @@ export default class Explorer {
|
|||||||
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>;
|
||||||
|
public serverId: ko.Observable<string>;
|
||||||
|
public armEndpoint: ko.Observable<string>;
|
||||||
public isTryCosmosDBSubscription: ko.Observable<boolean>;
|
public isTryCosmosDBSubscription: ko.Observable<boolean>;
|
||||||
public queriesClient: QueriesClient;
|
public queriesClient: QueriesClient;
|
||||||
public tableDataClient: TableDataClient;
|
public tableDataClient: TableDataClient;
|
||||||
public splitter: Splitter;
|
public splitter: Splitter;
|
||||||
|
public parentFrameDataExplorerVersion: ko.Observable<string> = ko.observable<string>("");
|
||||||
public mostRecentActivity: MostRecentActivity.MostRecentActivity;
|
public mostRecentActivity: MostRecentActivity.MostRecentActivity;
|
||||||
|
|
||||||
// Notification Console
|
// Notification Console
|
||||||
@@ -200,6 +204,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
// features
|
// features
|
||||||
public isGalleryPublishEnabled: ko.Computed<boolean>;
|
public isGalleryPublishEnabled: ko.Computed<boolean>;
|
||||||
|
public isCodeOfConductEnabled: ko.Computed<boolean>;
|
||||||
public isLinkInjectionEnabled: ko.Computed<boolean>;
|
public isLinkInjectionEnabled: ko.Computed<boolean>;
|
||||||
public isGitHubPaneEnabled: ko.Observable<boolean>;
|
public isGitHubPaneEnabled: ko.Observable<boolean>;
|
||||||
public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
|
public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
|
||||||
@@ -274,6 +279,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
this.databaseAccount = ko.observable<DataModels.DatabaseAccount>();
|
this.databaseAccount = ko.observable<DataModels.DatabaseAccount>();
|
||||||
this.subscriptionType = ko.observable<SubscriptionType>(SharedConstants.CollectionCreation.DefaultSubscriptionType);
|
this.subscriptionType = ko.observable<SubscriptionType>(SharedConstants.CollectionCreation.DefaultSubscriptionType);
|
||||||
|
this.quotaId = ko.observable<string>("");
|
||||||
let firstInitialization = true;
|
let firstInitialization = true;
|
||||||
this.isRefreshingExplorer = ko.observable<boolean>(true);
|
this.isRefreshingExplorer = ko.observable<boolean>(true);
|
||||||
this.isRefreshingExplorer.subscribe((isRefreshing: boolean) => {
|
this.isRefreshingExplorer.subscribe((isRefreshing: boolean) => {
|
||||||
@@ -313,9 +319,9 @@ export default class Explorer {
|
|||||||
if (isAccountReady) {
|
if (isAccountReady) {
|
||||||
this.isAuthWithResourceToken() ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases(true);
|
this.isAuthWithResourceToken() ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases(true);
|
||||||
RouteHandler.getInstance().initHandler();
|
RouteHandler.getInstance().initHandler();
|
||||||
this.notebookWorkspaceManager = new NotebookWorkspaceManager();
|
this.notebookWorkspaceManager = new NotebookWorkspaceManager(this.armEndpoint());
|
||||||
this.arcadiaWorkspaces = ko.observableArray();
|
this.arcadiaWorkspaces = ko.observableArray();
|
||||||
this._arcadiaManager = new ArcadiaResourceManager();
|
this._arcadiaManager = new ArcadiaResourceManager(this.armEndpoint());
|
||||||
this._isAfecFeatureRegistered(Constants.AfecFeatures.StorageAnalytics).then(isRegistered =>
|
this._isAfecFeatureRegistered(Constants.AfecFeatures.StorageAnalytics).then(isRegistered =>
|
||||||
this.hasStorageAnalyticsAfecFeature(isRegistered)
|
this.hasStorageAnalyticsAfecFeature(isRegistered)
|
||||||
);
|
);
|
||||||
@@ -364,6 +370,8 @@ export default class Explorer {
|
|||||||
this.memoryUsageInfo = ko.observable<DataModels.MemoryUsageInfo>();
|
this.memoryUsageInfo = ko.observable<DataModels.MemoryUsageInfo>();
|
||||||
|
|
||||||
this.features = ko.observable();
|
this.features = ko.observable();
|
||||||
|
this.serverId = ko.observable<string>();
|
||||||
|
this.armEndpoint = ko.observable<string>(undefined);
|
||||||
this.queriesClient = new QueriesClient(this);
|
this.queriesClient = new QueriesClient(this);
|
||||||
this.isTryCosmosDBSubscription = ko.observable<boolean>(false);
|
this.isTryCosmosDBSubscription = ko.observable<boolean>(false);
|
||||||
|
|
||||||
@@ -396,6 +404,9 @@ export default class Explorer {
|
|||||||
this.isGalleryPublishEnabled = ko.computed<boolean>(() =>
|
this.isGalleryPublishEnabled = ko.computed<boolean>(() =>
|
||||||
this.isFeatureEnabled(Constants.Features.enableGalleryPublish)
|
this.isFeatureEnabled(Constants.Features.enableGalleryPublish)
|
||||||
);
|
);
|
||||||
|
this.isCodeOfConductEnabled = ko.computed<boolean>(() =>
|
||||||
|
this.isFeatureEnabled(Constants.Features.enableCodeOfConduct)
|
||||||
|
);
|
||||||
this.isLinkInjectionEnabled = ko.computed<boolean>(() =>
|
this.isLinkInjectionEnabled = ko.computed<boolean>(() =>
|
||||||
this.isFeatureEnabled(Constants.Features.enableLinkInjection)
|
this.isFeatureEnabled(Constants.Features.enableLinkInjection)
|
||||||
);
|
);
|
||||||
@@ -1005,7 +1016,9 @@ export default class Explorer {
|
|||||||
this.isSynapseLinkUpdating(true);
|
this.isSynapseLinkUpdating(true);
|
||||||
this._closeSynapseLinkModalDialog();
|
this._closeSynapseLinkModalDialog();
|
||||||
|
|
||||||
const resourceProviderClient = new ResourceProviderClientFactory().getOrCreate(this.databaseAccount().id);
|
const resourceProviderClient = new ResourceProviderClientFactory(this.armEndpoint()).getOrCreate(
|
||||||
|
this.databaseAccount().id
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const databaseAccount: DataModels.DatabaseAccount = await resourceProviderClient.patchAsync(
|
const databaseAccount: DataModels.DatabaseAccount = await resourceProviderClient.patchAsync(
|
||||||
@@ -1745,59 +1758,61 @@ export default class Explorer {
|
|||||||
inputs.extensionEndpoint = configContext.PROXY_PATH;
|
inputs.extensionEndpoint = configContext.PROXY_PATH;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.initDataExplorerWithFrameInputs(inputs);
|
const initPromise: Q.Promise<void> = inputs ? this.initDataExplorerWithFrameInputs(inputs) : Q();
|
||||||
|
|
||||||
const openAction: ActionContracts.DataExplorerAction = message.openAction;
|
initPromise.then(() => {
|
||||||
if (!!openAction) {
|
const openAction: ActionContracts.DataExplorerAction = message.openAction;
|
||||||
if (this.isRefreshingExplorer()) {
|
if (!!openAction) {
|
||||||
const subscription = this.databases.subscribe((databases: ViewModels.Database[]) => {
|
if (this.isRefreshingExplorer()) {
|
||||||
|
const subscription = this.databases.subscribe((databases: ViewModels.Database[]) => {
|
||||||
|
handleOpenAction(openAction, this.nonSystemDatabases(), this);
|
||||||
|
subscription.dispose();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
handleOpenAction(openAction, this.nonSystemDatabases(), this);
|
handleOpenAction(openAction, this.nonSystemDatabases(), this);
|
||||||
subscription.dispose();
|
}
|
||||||
});
|
|
||||||
} else {
|
|
||||||
handleOpenAction(openAction, this.nonSystemDatabases(), this);
|
|
||||||
}
|
}
|
||||||
}
|
if (message.actionType === ActionContracts.ActionType.TransmitCachedData) {
|
||||||
if (message.actionType === ActionContracts.ActionType.TransmitCachedData) {
|
handleCachedDataMessage(message);
|
||||||
handleCachedDataMessage(message);
|
return;
|
||||||
return;
|
}
|
||||||
}
|
if (message.type) {
|
||||||
if (message.type) {
|
switch (message.type) {
|
||||||
switch (message.type) {
|
case MessageTypes.UpdateLocationHash:
|
||||||
case MessageTypes.UpdateLocationHash:
|
if (!message.locationHash) {
|
||||||
if (!message.locationHash) {
|
break;
|
||||||
break;
|
}
|
||||||
}
|
hasher.replaceHash(message.locationHash);
|
||||||
hasher.replaceHash(message.locationHash);
|
RouteHandler.getInstance().parseHash(message.locationHash);
|
||||||
RouteHandler.getInstance().parseHash(message.locationHash);
|
break;
|
||||||
break;
|
case MessageTypes.SendNotification:
|
||||||
case MessageTypes.SendNotification:
|
if (!message.message) {
|
||||||
if (!message.message) {
|
break;
|
||||||
break;
|
}
|
||||||
}
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
message.consoleDataType || ConsoleDataType.Info,
|
||||||
message.consoleDataType || ConsoleDataType.Info,
|
message.message,
|
||||||
message.message,
|
message.id
|
||||||
message.id
|
);
|
||||||
);
|
break;
|
||||||
break;
|
case MessageTypes.ClearNotification:
|
||||||
case MessageTypes.ClearNotification:
|
if (!message.id) {
|
||||||
if (!message.id) {
|
break;
|
||||||
break;
|
}
|
||||||
}
|
NotificationConsoleUtils.clearInProgressMessageWithId(message.id);
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(message.id);
|
break;
|
||||||
break;
|
case MessageTypes.LoadingStatus:
|
||||||
case MessageTypes.LoadingStatus:
|
if (!message.text) {
|
||||||
if (!message.text) {
|
break;
|
||||||
break;
|
}
|
||||||
}
|
this._setLoadingStatusText(message.text, message.title);
|
||||||
this._setLoadingStatusText(message.text, message.title);
|
break;
|
||||||
break;
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.splashScreenAdapter.forceRender();
|
this.splashScreenAdapter.forceRender();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public findSelectedDatabase(): ViewModels.Database {
|
public findSelectedDatabase(): ViewModels.Database {
|
||||||
@@ -1837,14 +1852,8 @@ export default class Explorer {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public initDataExplorerWithFrameInputs(inputs: ViewModels.DataExplorerInputsFrame): void {
|
public initDataExplorerWithFrameInputs(inputs: ViewModels.DataExplorerInputsFrame): Q.Promise<void> {
|
||||||
if (inputs != null) {
|
if (inputs != null) {
|
||||||
// In development mode, save the iframe message from the portal in session storage.
|
|
||||||
// This allows webpack hot reload to funciton properly
|
|
||||||
if (process.env.NODE_ENV === "development") {
|
|
||||||
sessionStorage.setItem("portalDataExplorerInitMessage", JSON.stringify(inputs));
|
|
||||||
}
|
|
||||||
|
|
||||||
const authorizationToken = inputs.authorizationToken || "";
|
const authorizationToken = inputs.authorizationToken || "";
|
||||||
const masterKey = inputs.masterKey || "";
|
const masterKey = inputs.masterKey || "";
|
||||||
const databaseAccount = inputs.databaseAccount || null;
|
const databaseAccount = inputs.databaseAccount || null;
|
||||||
@@ -1852,19 +1861,26 @@ export default class Explorer {
|
|||||||
this.collectionCreationDefaults = inputs.defaultCollectionThroughput;
|
this.collectionCreationDefaults = inputs.defaultCollectionThroughput;
|
||||||
}
|
}
|
||||||
this.features(inputs.features);
|
this.features(inputs.features);
|
||||||
|
this.serverId(inputs.serverId);
|
||||||
|
this.armEndpoint(EnvironmentUtility.normalizeArmEndpointUri(inputs.csmEndpoint || configContext.ARM_ENDPOINT));
|
||||||
this.databaseAccount(databaseAccount);
|
this.databaseAccount(databaseAccount);
|
||||||
this.subscriptionType(inputs.subscriptionType);
|
this.subscriptionType(inputs.subscriptionType);
|
||||||
|
this.quotaId(inputs.quotaId);
|
||||||
this.hasWriteAccess(inputs.hasWriteAccess);
|
this.hasWriteAccess(inputs.hasWriteAccess);
|
||||||
this.flight(inputs.addCollectionDefaultFlight);
|
this.flight(inputs.addCollectionDefaultFlight);
|
||||||
this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription);
|
this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription);
|
||||||
this.isAuthWithResourceToken(inputs.isAuthWithresourceToken);
|
this.isAuthWithResourceToken(inputs.isAuthWithresourceToken);
|
||||||
this.setFeatureFlagsFromFlights(inputs.flights);
|
this.setFeatureFlagsFromFlights(inputs.flights);
|
||||||
|
|
||||||
|
if (!!inputs.dataExplorerVersion) {
|
||||||
|
this.parentFrameDataExplorerVersion(inputs.dataExplorerVersion);
|
||||||
|
}
|
||||||
|
|
||||||
this._importExplorerConfigComplete = true;
|
this._importExplorerConfigComplete = true;
|
||||||
|
|
||||||
updateConfigContext({
|
updateConfigContext({
|
||||||
BACKEND_ENDPOINT: inputs.extensionEndpoint || "",
|
BACKEND_ENDPOINT: inputs.extensionEndpoint || "",
|
||||||
ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT),
|
ARM_ENDPOINT: this.armEndpoint()
|
||||||
serverId: inputs.serverId
|
|
||||||
});
|
});
|
||||||
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
@@ -1873,8 +1889,7 @@ export default class Explorer {
|
|||||||
databaseAccount,
|
databaseAccount,
|
||||||
resourceGroup: inputs.resourceGroup,
|
resourceGroup: inputs.resourceGroup,
|
||||||
subscriptionId: inputs.subscriptionId,
|
subscriptionId: inputs.subscriptionId,
|
||||||
subscriptionType: inputs.subscriptionType,
|
subscriptionType: inputs.subscriptionType
|
||||||
quotaId: inputs.quotaId
|
|
||||||
});
|
});
|
||||||
TelemetryProcessor.traceSuccess(
|
TelemetryProcessor.traceSuccess(
|
||||||
Action.LoadDatabaseAccount,
|
Action.LoadDatabaseAccount,
|
||||||
@@ -1888,6 +1903,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
this.isAccountReady(true);
|
this.isAccountReady(true);
|
||||||
}
|
}
|
||||||
|
return Q();
|
||||||
}
|
}
|
||||||
|
|
||||||
public setFeatureFlagsFromFlights(flights: readonly string[]): void {
|
public setFeatureFlagsFromFlights(flights: readonly string[]): void {
|
||||||
@@ -1951,9 +1967,9 @@ export default class Explorer {
|
|||||||
|
|
||||||
public isRunningOnNationalCloud(): boolean {
|
public isRunningOnNationalCloud(): boolean {
|
||||||
return (
|
return (
|
||||||
userContext === Constants.ServerIds.blackforest ||
|
this.serverId() === Constants.ServerIds.blackforest ||
|
||||||
userContext === Constants.ServerIds.fairfax ||
|
this.serverId() === Constants.ServerIds.fairfax ||
|
||||||
userContext === Constants.ServerIds.mooncake
|
this.serverId() === Constants.ServerIds.mooncake
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2260,6 +2276,7 @@ export default class Explorer {
|
|||||||
name,
|
name,
|
||||||
content,
|
content,
|
||||||
parentDomElement,
|
parentDomElement,
|
||||||
|
this.isCodeOfConductEnabled(),
|
||||||
this.isLinkInjectionEnabled()
|
this.isLinkInjectionEnabled()
|
||||||
);
|
);
|
||||||
this.publishNotebookPaneAdapter = this.notebookManager.publishNotebookPaneAdapter;
|
this.publishNotebookPaneAdapter = this.notebookManager.publishNotebookPaneAdapter;
|
||||||
@@ -2550,7 +2567,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
public _refreshSparkEnabledStateForAccount = async (): Promise<void> => {
|
public _refreshSparkEnabledStateForAccount = async (): Promise<void> => {
|
||||||
const subscriptionId = userContext.subscriptionId;
|
const subscriptionId = userContext.subscriptionId;
|
||||||
const armEndpoint = configContext.ARM_ENDPOINT;
|
const armEndpoint = this.armEndpoint();
|
||||||
const authType = window.authType as AuthType;
|
const authType = window.authType as AuthType;
|
||||||
if (!subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) {
|
if (!subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) {
|
||||||
// explorer is not aware of the database account yet
|
// explorer is not aware of the database account yet
|
||||||
@@ -2559,7 +2576,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const featureUri = `subscriptions/${subscriptionId}/providers/Microsoft.Features/providers/Microsoft.DocumentDb/features/${Constants.AfecFeatures.Spark}`;
|
const featureUri = `subscriptions/${subscriptionId}/providers/Microsoft.Features/providers/Microsoft.DocumentDb/features/${Constants.AfecFeatures.Spark}`;
|
||||||
const resourceProviderClient = new ResourceProviderClientFactory().getOrCreate(featureUri);
|
const resourceProviderClient = new ResourceProviderClientFactory(this.armEndpoint()).getOrCreate(featureUri);
|
||||||
try {
|
try {
|
||||||
const sparkNotebooksFeature: DataModels.AfecFeature = await resourceProviderClient.getAsync(
|
const sparkNotebooksFeature: DataModels.AfecFeature = await resourceProviderClient.getAsync(
|
||||||
featureUri,
|
featureUri,
|
||||||
@@ -2579,7 +2596,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
public _isAfecFeatureRegistered = async (featureName: string): Promise<boolean> => {
|
public _isAfecFeatureRegistered = async (featureName: string): Promise<boolean> => {
|
||||||
const subscriptionId = userContext.subscriptionId;
|
const subscriptionId = userContext.subscriptionId;
|
||||||
const armEndpoint = configContext.ARM_ENDPOINT;
|
const armEndpoint = this.armEndpoint();
|
||||||
const authType = window.authType as AuthType;
|
const authType = window.authType as AuthType;
|
||||||
if (!featureName || !subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) {
|
if (!featureName || !subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) {
|
||||||
// explorer is not aware of the database account yet
|
// explorer is not aware of the database account yet
|
||||||
@@ -2587,7 +2604,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const featureUri = `subscriptions/${subscriptionId}/providers/Microsoft.Features/providers/Microsoft.DocumentDb/features/${featureName}`;
|
const featureUri = `subscriptions/${subscriptionId}/providers/Microsoft.Features/providers/Microsoft.DocumentDb/features/${featureName}`;
|
||||||
const resourceProviderClient = new ResourceProviderClientFactory().getOrCreate(featureUri);
|
const resourceProviderClient = new ResourceProviderClientFactory(this.armEndpoint()).getOrCreate(featureUri);
|
||||||
try {
|
try {
|
||||||
const featureStatus: DataModels.AfecFeature = await resourceProviderClient.getAsync(
|
const featureStatus: DataModels.AfecFeature = await resourceProviderClient.getAsync(
|
||||||
featureUri,
|
featureUri,
|
||||||
|
|||||||
@@ -99,22 +99,7 @@
|
|||||||
.notificationConsoleControls {
|
.notificationConsoleControls {
|
||||||
padding: @MediumSpace;
|
padding: @MediumSpace;
|
||||||
margin-left:@DefaultSpace;
|
margin-left:@DefaultSpace;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.ms-Dropdown-container {
|
|
||||||
display: flex;
|
|
||||||
.ms-Dropdown-title {
|
|
||||||
height: 25px;
|
|
||||||
line-height: 25px;
|
|
||||||
}
|
|
||||||
.ms-Dropdown {
|
|
||||||
min-width: 110px;
|
|
||||||
margin-left: 10px;
|
|
||||||
height: 25px;
|
|
||||||
line-height: 25px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#consoleFilterLabel {
|
#consoleFilterLabel {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
@@ -122,7 +107,6 @@
|
|||||||
.consoleSplitter {
|
.consoleSplitter {
|
||||||
border-left: 1px solid @BaseMedium;
|
border-left: 1px solid @BaseMedium;
|
||||||
margin: @MediumSpace;
|
margin: @MediumSpace;
|
||||||
height: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.clearNotificationsButton {
|
.clearNotificationsButton {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { ClientDefaults, KeyCodes } from "../../../Common/Constants";
|
import { ClientDefaults, KeyCodes } from "../../../Common/Constants";
|
||||||
import AnimateHeight from "react-animate-height";
|
import AnimateHeight from "react-animate-height";
|
||||||
import { Dropdown, IDropdownOption } from "office-ui-fabric-react";
|
|
||||||
import LoadingIcon from "../../../../images/loading.svg";
|
import LoadingIcon from "../../../../images/loading.svg";
|
||||||
import ErrorBlackIcon from "../../../../images/error_black.svg";
|
import ErrorBlackIcon from "../../../../images/error_black.svg";
|
||||||
import infoBubbleIcon from "../../../../images/info-bubble-9x9.svg";
|
import infoBubbleIcon from "../../../../images/info-bubble-9x9.svg";
|
||||||
@@ -53,12 +53,7 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
NotificationConsoleComponentState
|
NotificationConsoleComponentState
|
||||||
> {
|
> {
|
||||||
private static readonly transitionDurationMs = 200;
|
private static readonly transitionDurationMs = 200;
|
||||||
private static readonly FilterOptions = [
|
private static readonly FilterOptions = ["All", "In Progress", "Info", "Error"];
|
||||||
{ key: "All", text: "All" },
|
|
||||||
{ key: "In Progress", text: "In progress" },
|
|
||||||
{ key: "Info", text: "Info" },
|
|
||||||
{ key: "Error", text: "Error" }
|
|
||||||
];
|
|
||||||
private headerTimeoutId: number;
|
private headerTimeoutId: number;
|
||||||
private prevHeaderStatus: string;
|
private prevHeaderStatus: string;
|
||||||
private consoleHeaderElement: HTMLElement;
|
private consoleHeaderElement: HTMLElement;
|
||||||
@@ -67,7 +62,7 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
headerStatus: "",
|
headerStatus: "",
|
||||||
selectedFilter: NotificationConsoleComponent.FilterOptions[0].key || "",
|
selectedFilter: NotificationConsoleComponent.FilterOptions[0],
|
||||||
isExpanded: props.isConsoleExpanded
|
isExpanded: props.isConsoleExpanded
|
||||||
};
|
};
|
||||||
this.prevHeaderStatus = null;
|
this.prevHeaderStatus = null;
|
||||||
@@ -155,15 +150,20 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
>
|
>
|
||||||
<div className="notificationConsoleContents">
|
<div className="notificationConsoleContents">
|
||||||
<div className="notificationConsoleControls">
|
<div className="notificationConsoleControls">
|
||||||
<Dropdown
|
<label id="consoleFilterLabel">Filter</label>
|
||||||
label="Filter:"
|
<select
|
||||||
role="combobox"
|
|
||||||
selectedKey={this.state.selectedFilter}
|
|
||||||
options={NotificationConsoleComponent.FilterOptions}
|
|
||||||
onChange={this.onFilterSelected.bind(this)}
|
|
||||||
aria-labelledby="consoleFilterLabel"
|
aria-labelledby="consoleFilterLabel"
|
||||||
|
role="combobox"
|
||||||
aria-label={this.state.selectedFilter}
|
aria-label={this.state.selectedFilter}
|
||||||
/>
|
value={this.state.selectedFilter}
|
||||||
|
onChange={this.onFilterSelected.bind(this)}
|
||||||
|
>
|
||||||
|
{NotificationConsoleComponent.FilterOptions.map((value: string) => (
|
||||||
|
<option value={value} key={value}>
|
||||||
|
{value}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
<span className="consoleSplitter" />
|
<span className="consoleSplitter" />
|
||||||
<span
|
<span
|
||||||
className="clearNotificationsButton"
|
className="clearNotificationsButton"
|
||||||
@@ -220,8 +220,8 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
private onFilterSelected(event: React.ChangeEvent<HTMLSelectElement>, option: IDropdownOption): void {
|
private onFilterSelected(event: React.ChangeEvent<HTMLSelectElement>): void {
|
||||||
this.setState({ selectedFilter: String(option.key) });
|
this.setState({ selectedFilter: event.target.value });
|
||||||
}
|
}
|
||||||
|
|
||||||
private getFilteredConsoleData(): ConsoleData[] {
|
private getFilteredConsoleData(): ConsoleData[] {
|
||||||
|
|||||||
@@ -110,34 +110,43 @@ exports[`NotificationConsoleComponent renders the console (expanded) 1`] = `
|
|||||||
<div
|
<div
|
||||||
className="notificationConsoleControls"
|
className="notificationConsoleControls"
|
||||||
>
|
>
|
||||||
<StyledWithResponsiveMode
|
<label
|
||||||
|
id="consoleFilterLabel"
|
||||||
|
>
|
||||||
|
Filter
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
aria-label="All"
|
aria-label="All"
|
||||||
aria-labelledby="consoleFilterLabel"
|
aria-labelledby="consoleFilterLabel"
|
||||||
label="Filter:"
|
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
options={
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"key": "All",
|
|
||||||
"text": "All",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"key": "In Progress",
|
|
||||||
"text": "In progress",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"key": "Info",
|
|
||||||
"text": "Info",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"key": "Error",
|
|
||||||
"text": "Error",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
role="combobox"
|
role="combobox"
|
||||||
selectedKey="All"
|
value="All"
|
||||||
/>
|
>
|
||||||
|
<option
|
||||||
|
key="All"
|
||||||
|
value="All"
|
||||||
|
>
|
||||||
|
All
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
key="In Progress"
|
||||||
|
value="In Progress"
|
||||||
|
>
|
||||||
|
In Progress
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
key="Info"
|
||||||
|
value="Info"
|
||||||
|
>
|
||||||
|
Info
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
key="Error"
|
||||||
|
value="Error"
|
||||||
|
>
|
||||||
|
Error
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
<span
|
<span
|
||||||
className="consoleSplitter"
|
className="consoleSplitter"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -350,7 +350,9 @@ export const launchWebSocketKernelEpic = (
|
|||||||
} as any,
|
} as any,
|
||||||
name: "",
|
name: "",
|
||||||
path: content.filepath.replace(/^\/+/g, ""),
|
path: content.filepath.replace(/^\/+/g, ""),
|
||||||
type: "notebook"
|
type: "notebook",
|
||||||
|
endpoint:"https://dech-notebooks-demo-7.documents.azure.com:443/",
|
||||||
|
token:"" //to fill
|
||||||
};
|
};
|
||||||
|
|
||||||
return sessions.create(serverConfig, sessionPayload).pipe(
|
return sessions.create(serverConfig, sessionPayload).pipe(
|
||||||
|
|||||||
@@ -128,9 +128,17 @@ export default class NotebookManager {
|
|||||||
name: string,
|
name: string,
|
||||||
content: string | ImmutableNotebook,
|
content: string | ImmutableNotebook,
|
||||||
parentDomElement: HTMLElement,
|
parentDomElement: HTMLElement,
|
||||||
|
isCodeOfConductEnabled: boolean,
|
||||||
isLinkInjectionEnabled: boolean
|
isLinkInjectionEnabled: boolean
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.publishNotebookPaneAdapter.open(name, getFullName(), content, parentDomElement, isLinkInjectionEnabled);
|
await this.publishNotebookPaneAdapter.open(
|
||||||
|
name,
|
||||||
|
getFullName(),
|
||||||
|
content,
|
||||||
|
parentDomElement,
|
||||||
|
isCodeOfConductEnabled,
|
||||||
|
isLinkInjectionEnabled
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public openCopyNotebookPane(name: string, content: string): void {
|
public openCopyNotebookPane(name: string, content: string): void {
|
||||||
|
|||||||
@@ -243,6 +243,38 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- Unlimited Button Content - Start -->
|
<!-- Unlimited Button Content - Start -->
|
||||||
<div class="tabcontent" data-bind="visible: isUnlimitedStorageSelected() || databaseHasSharedOffer()">
|
<div class="tabcontent" data-bind="visible: isUnlimitedStorageSelected() || databaseHasSharedOffer()">
|
||||||
|
<div data-bind="visible: rupmVisible">
|
||||||
|
<div class="tabs">
|
||||||
|
<p>
|
||||||
|
<span class="mandatoryStar">*</span>
|
||||||
|
<span class="addCollectionLabel">RU/m</span>
|
||||||
|
<span class="infoTooltip" role="tooltip" tabindex="0">
|
||||||
|
<img class="infoImg" src="/info-bubble.svg" alt="More information">
|
||||||
|
<span class="tooltiptext throughputRuInfo">
|
||||||
|
For each 100 Request Units per second (RU/s) provisioned, 1,000 Request Units
|
||||||
|
per
|
||||||
|
minute
|
||||||
|
(RU/m) can be provisioned. E.g.: for a container with 5,000 RU/s provisioned
|
||||||
|
with
|
||||||
|
RU/m
|
||||||
|
enabled, the RU/m budget will be 50,000 RU/m.
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<div tabindex="0" data-bind="event: { keydown: onRupmOptionsKeyDown }" aria-label="RU/m">
|
||||||
|
<div class="tab">
|
||||||
|
<input type="radio" id="rupmOn2" name="rupmcoll2" value="on" class="radio"
|
||||||
|
data-bind="checked: rupm">
|
||||||
|
<label for="rupmOn2">ON</label>
|
||||||
|
</div>
|
||||||
|
<div class="tab">
|
||||||
|
<input type="radio" id="rupmOff2" name="rupmcoll2" value="off" class="radio"
|
||||||
|
data-bind="checked: rupm">
|
||||||
|
<label for="rupmOff2">OFF</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div data-bind="visible: partitionKeyVisible">
|
<div data-bind="visible: partitionKeyVisible">
|
||||||
<p>
|
<p>
|
||||||
<span class="mandatoryStar">*</span>
|
<span class="mandatoryStar">*</span>
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import { ContextualPaneBase } from "./ContextualPaneBase";
|
|||||||
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
|
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
|
||||||
import { createCollection } from "../../Common/dataAccess/createCollection";
|
import { createCollection } from "../../Common/dataAccess/createCollection";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
import { userContext } from "../../UserContext";
|
|
||||||
|
|
||||||
export interface AddCollectionPaneOptions extends ViewModels.PaneOptions {
|
export interface AddCollectionPaneOptions extends ViewModels.PaneOptions {
|
||||||
isPreferredApiTable: ko.Computed<boolean>;
|
isPreferredApiTable: ko.Computed<boolean>;
|
||||||
@@ -43,6 +42,8 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
public partitionKeyVisible: ko.Computed<boolean>;
|
public partitionKeyVisible: ko.Computed<boolean>;
|
||||||
public partitionKeyPattern: ko.Computed<string>;
|
public partitionKeyPattern: ko.Computed<string>;
|
||||||
public partitionKeyTitle: ko.Computed<string>;
|
public partitionKeyTitle: ko.Computed<string>;
|
||||||
|
public rupm: ko.Observable<string>;
|
||||||
|
public rupmVisible: ko.Observable<boolean>;
|
||||||
public storage: ko.Observable<string>;
|
public storage: ko.Observable<string>;
|
||||||
public throughputSinglePartition: ViewModels.Editable<number>;
|
public throughputSinglePartition: ViewModels.Editable<number>;
|
||||||
public throughputMultiPartition: ViewModels.Editable<number>;
|
public throughputMultiPartition: ViewModels.Editable<number>;
|
||||||
@@ -142,6 +143,12 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
});
|
});
|
||||||
|
this.rupm = ko.observable<string>(Constants.RUPMStates.off);
|
||||||
|
this.rupmVisible = ko.observable<boolean>(false);
|
||||||
|
const featureSubcription = this.container.features.subscribe(() => {
|
||||||
|
this.rupmVisible(this.container.isFeatureEnabled(Constants.Features.enableRupm));
|
||||||
|
featureSubcription.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue());
|
this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue());
|
||||||
|
|
||||||
@@ -186,7 +193,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverId = configContext.serverId;
|
const serverId: string = this.container.serverId();
|
||||||
const regions =
|
const regions =
|
||||||
(account &&
|
(account &&
|
||||||
account.properties &&
|
account.properties &&
|
||||||
@@ -194,24 +201,33 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
account.properties.readLocations.length) ||
|
account.properties.readLocations.length) ||
|
||||||
1;
|
1;
|
||||||
const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false;
|
const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false;
|
||||||
|
const rupmEnabled: boolean = this.rupm() === Constants.RUPMStates.on;
|
||||||
|
|
||||||
let throughputSpendAckText: string;
|
let throughputSpendAckText: string;
|
||||||
let estimatedSpend: string;
|
let estimatedSpend: string;
|
||||||
if (!this.isSharedAutoPilotSelected()) {
|
if (!this.isSharedAutoPilotSelected()) {
|
||||||
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
|
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||||
offerThroughput,
|
offerThroughput,
|
||||||
configContext.serverId,
|
serverId,
|
||||||
regions,
|
regions,
|
||||||
multimaster,
|
multimaster,
|
||||||
|
rupmEnabled,
|
||||||
this.isSharedAutoPilotSelected()
|
this.isSharedAutoPilotSelected()
|
||||||
);
|
);
|
||||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(offerThroughput, serverId, regions, multimaster);
|
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
|
||||||
|
offerThroughput,
|
||||||
|
serverId,
|
||||||
|
regions,
|
||||||
|
multimaster,
|
||||||
|
rupmEnabled
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
|
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||||
this.sharedAutoPilotThroughput(),
|
this.sharedAutoPilotThroughput(),
|
||||||
serverId,
|
serverId,
|
||||||
regions,
|
regions,
|
||||||
multimaster,
|
multimaster,
|
||||||
|
rupmEnabled,
|
||||||
this.isSharedAutoPilotSelected()
|
this.isSharedAutoPilotSelected()
|
||||||
);
|
);
|
||||||
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
|
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
|
||||||
@@ -240,7 +256,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverId: string = configContext.serverId;
|
const serverId: string = this.container.serverId();
|
||||||
const regions =
|
const regions =
|
||||||
(account &&
|
(account &&
|
||||||
account.properties &&
|
account.properties &&
|
||||||
@@ -248,6 +264,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
account.properties.readLocations.length) ||
|
account.properties.readLocations.length) ||
|
||||||
1;
|
1;
|
||||||
const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false;
|
const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false;
|
||||||
|
const rupmEnabled: boolean = this.rupm() === Constants.RUPMStates.on;
|
||||||
|
|
||||||
let throughputSpendAckText: string;
|
let throughputSpendAckText: string;
|
||||||
let estimatedSpend: string;
|
let estimatedSpend: string;
|
||||||
@@ -257,13 +274,15 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
serverId,
|
serverId,
|
||||||
regions,
|
regions,
|
||||||
multimaster,
|
multimaster,
|
||||||
|
rupmEnabled,
|
||||||
this.isAutoPilotSelected()
|
this.isAutoPilotSelected()
|
||||||
);
|
);
|
||||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
|
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
|
||||||
this.throughputMultiPartition(),
|
this.throughputMultiPartition(),
|
||||||
serverId,
|
serverId,
|
||||||
regions,
|
regions,
|
||||||
multimaster
|
multimaster,
|
||||||
|
rupmEnabled
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
|
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||||
@@ -271,6 +290,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
serverId,
|
serverId,
|
||||||
regions,
|
regions,
|
||||||
multimaster,
|
multimaster,
|
||||||
|
rupmEnabled,
|
||||||
this.isAutoPilotSelected()
|
this.isAutoPilotSelected()
|
||||||
);
|
);
|
||||||
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
|
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
|
||||||
@@ -482,7 +502,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.upsellMessage = ko.pureComputed<string>(() => {
|
this.upsellMessage = ko.pureComputed<string>(() => {
|
||||||
return PricingUtils.getUpsellMessage(configContext.serverId, this.isFreeTierAccount());
|
return PricingUtils.getUpsellMessage(this.container.serverId(), this.isFreeTierAccount());
|
||||||
});
|
});
|
||||||
|
|
||||||
this.upsellMessageAriaLabel = ko.pureComputed<string>(() => {
|
this.upsellMessageAriaLabel = ko.pureComputed<string>(() => {
|
||||||
@@ -666,10 +686,11 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
storage: this.storage(),
|
storage: this.storage(),
|
||||||
offerThroughput: this._getThroughput(),
|
offerThroughput: this._getThroughput(),
|
||||||
partitionKey: this.partitionKey(),
|
partitionKey: this.partitionKey(),
|
||||||
databaseId: this.databaseId()
|
databaseId: this.databaseId(),
|
||||||
|
rupm: this.rupm()
|
||||||
}),
|
}),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: userContext.quotaId,
|
subscriptionQuotaId: this.container.quotaId(),
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
||||||
throughput: this._getThroughput(),
|
throughput: this._getThroughput(),
|
||||||
@@ -767,11 +788,12 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
id: this.collectionId(),
|
id: this.collectionId(),
|
||||||
storage: this.storage(),
|
storage: this.storage(),
|
||||||
partitionKey,
|
partitionKey,
|
||||||
|
rupm: this.rupm(),
|
||||||
uniqueKeyPolicy,
|
uniqueKeyPolicy,
|
||||||
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
||||||
}),
|
}),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: userContext.quotaId,
|
subscriptionQuotaId: this.container.quotaId(),
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
||||||
throughput: offerThroughput,
|
throughput: offerThroughput,
|
||||||
@@ -841,11 +863,12 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
id: this.collectionId(),
|
id: this.collectionId(),
|
||||||
storage: this.storage(),
|
storage: this.storage(),
|
||||||
partitionKey,
|
partitionKey,
|
||||||
|
rupm: this.rupm(),
|
||||||
uniqueKeyPolicy,
|
uniqueKeyPolicy,
|
||||||
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
||||||
}),
|
}),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: userContext.quotaId,
|
subscriptionQuotaId: this.container.quotaId(),
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
||||||
throughput: offerThroughput,
|
throughput: offerThroughput,
|
||||||
@@ -875,11 +898,12 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
id: this.collectionId(),
|
id: this.collectionId(),
|
||||||
storage: this.storage(),
|
storage: this.storage(),
|
||||||
partitionKey,
|
partitionKey,
|
||||||
|
rupm: this.rupm(),
|
||||||
uniqueKeyPolicy,
|
uniqueKeyPolicy,
|
||||||
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
||||||
},
|
},
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: userContext.quotaId,
|
subscriptionQuotaId: this.container.quotaId(),
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
||||||
throughput: offerThroughput,
|
throughput: offerThroughput,
|
||||||
@@ -957,6 +981,20 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onRupmOptionsKeyDown(source: any, event: KeyboardEvent): boolean {
|
||||||
|
if (event.key === "ArrowRight") {
|
||||||
|
this.rupm("off");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === "ArrowLeft") {
|
||||||
|
this.rupm("on");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public onEnableSynapseLinkButtonClicked() {
|
public onEnableSynapseLinkButtonClicked() {
|
||||||
this.container.openEnableSynapseLinkDialog();
|
this.container.openEnableSynapseLinkDialog();
|
||||||
}
|
}
|
||||||
@@ -980,6 +1018,16 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const throughput = this._getThroughput();
|
const throughput = this._getThroughput();
|
||||||
|
const maxThroughputWithRUPM =
|
||||||
|
SharedConstants.CollectionCreation.MaxRUPMPerPartition * this._calculateNumberOfPartitions();
|
||||||
|
|
||||||
|
if (this.rupm() === Constants.RUPMStates.on && throughput > maxThroughputWithRUPM) {
|
||||||
|
this.formErrors(
|
||||||
|
`The maximum supported provisioned throughput with RU/m enabled is ${maxThroughputWithRUPM} RU/s. Please turn off RU/m to incease thoughput above ${maxThroughputWithRUPM} RU/s.`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !this.throughputSpendAck()) {
|
if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !this.throughputSpendAck()) {
|
||||||
this.formErrors(`Please acknowledge the estimated daily spend.`);
|
this.formErrors(`Please acknowledge the estimated daily spend.`);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { createDatabase } from "../../Common/dataAccess/createDatabase";
|
|||||||
import { configContext, Platform } from "../../ConfigContext";
|
import { configContext, Platform } from "../../ConfigContext";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
import { SubscriptionType } from "../../Contracts/SubscriptionType";
|
import { SubscriptionType } from "../../Contracts/SubscriptionType";
|
||||||
import { userContext } from "../../UserContext";
|
|
||||||
|
|
||||||
export default class AddDatabasePane extends ContextualPaneBase {
|
export default class AddDatabasePane extends ContextualPaneBase {
|
||||||
public defaultExperience: ko.Computed<string>;
|
public defaultExperience: ko.Computed<string>;
|
||||||
@@ -122,7 +121,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverId = configContext.serverId;
|
const serverId = this.container.serverId();
|
||||||
const regions =
|
const regions =
|
||||||
(account &&
|
(account &&
|
||||||
account.properties &&
|
account.properties &&
|
||||||
@@ -134,12 +133,19 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
let estimatedSpendAcknowledge: string;
|
let estimatedSpendAcknowledge: string;
|
||||||
let estimatedSpend: string;
|
let estimatedSpend: string;
|
||||||
if (!this.isAutoPilotSelected()) {
|
if (!this.isAutoPilotSelected()) {
|
||||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(offerThroughput, serverId, regions, multimaster);
|
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
|
||||||
|
offerThroughput,
|
||||||
|
serverId,
|
||||||
|
regions,
|
||||||
|
multimaster,
|
||||||
|
false /*rupmEnabled*/
|
||||||
|
);
|
||||||
estimatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
|
estimatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||||
offerThroughput,
|
offerThroughput,
|
||||||
serverId,
|
serverId,
|
||||||
regions,
|
regions,
|
||||||
multimaster,
|
multimaster,
|
||||||
|
false /*rupmEnabled*/,
|
||||||
this.isAutoPilotSelected()
|
this.isAutoPilotSelected()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -154,6 +160,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
serverId,
|
serverId,
|
||||||
regions,
|
regions,
|
||||||
multimaster,
|
multimaster,
|
||||||
|
false /*rupmEnabled*/,
|
||||||
this.isAutoPilotSelected()
|
this.isAutoPilotSelected()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -220,7 +227,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.upsellMessage = ko.pureComputed<string>(() => {
|
this.upsellMessage = ko.pureComputed<string>(() => {
|
||||||
return PricingUtils.getUpsellMessage(configContext.serverId, this.isFreeTierAccount());
|
return PricingUtils.getUpsellMessage(this.container.serverId(), this.isFreeTierAccount());
|
||||||
});
|
});
|
||||||
|
|
||||||
this.upsellMessageAriaLabel = ko.pureComputed<string>(() => {
|
this.upsellMessageAriaLabel = ko.pureComputed<string>(() => {
|
||||||
@@ -251,7 +258,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
databaseAccountName: this.container.databaseAccount().name,
|
databaseAccountName: this.container.databaseAccount().name,
|
||||||
defaultExperience: this.container.defaultExperience(),
|
defaultExperience: this.container.defaultExperience(),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: userContext.quotaId,
|
subscriptionQuotaId: this.container.quotaId(),
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
throughput: this.throughput(),
|
throughput: this.throughput(),
|
||||||
flight: this.container.flight()
|
flight: this.container.flight()
|
||||||
@@ -279,7 +286,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
}),
|
}),
|
||||||
offerThroughput,
|
offerThroughput,
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: userContext.quotaId,
|
subscriptionQuotaId: this.container.quotaId(),
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
flight: this.container.flight()
|
flight: this.container.flight()
|
||||||
},
|
},
|
||||||
@@ -343,7 +350,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
}),
|
}),
|
||||||
offerThroughput: offerThroughput,
|
offerThroughput: offerThroughput,
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: userContext.quotaId,
|
subscriptionQuotaId: this.container.quotaId(),
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
flight: this.container.flight()
|
flight: this.container.flight()
|
||||||
},
|
},
|
||||||
@@ -367,7 +374,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
}),
|
}),
|
||||||
offerThroughput: offerThroughput,
|
offerThroughput: offerThroughput,
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: userContext.quotaId,
|
subscriptionQuotaId: this.container.quotaId(),
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
flight: this.container.flight()
|
flight: this.container.flight()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import { HashMap } from "../../Common/HashMap";
|
|||||||
import { configContext, Platform } from "../../ConfigContext";
|
import { configContext, Platform } from "../../ConfigContext";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
import { SubscriptionType } from "../../Contracts/SubscriptionType";
|
import { SubscriptionType } from "../../Contracts/SubscriptionType";
|
||||||
import { userContext } from "../../UserContext";
|
|
||||||
|
|
||||||
export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||||
public createTableQuery: ko.Observable<string>;
|
public createTableQuery: ko.Observable<string>;
|
||||||
@@ -127,7 +126,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverId = configContext.serverId;
|
const serverId = this.container.serverId();
|
||||||
const regions =
|
const regions =
|
||||||
(account &&
|
(account &&
|
||||||
account.properties &&
|
account.properties &&
|
||||||
@@ -139,12 +138,19 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
let estimatedSpend: string;
|
let estimatedSpend: string;
|
||||||
let estimatedDedicatedSpendAcknowledge: string;
|
let estimatedDedicatedSpendAcknowledge: string;
|
||||||
if (!this.isAutoPilotSelected()) {
|
if (!this.isAutoPilotSelected()) {
|
||||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(offerThroughput, serverId, regions, multimaster);
|
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
|
||||||
|
offerThroughput,
|
||||||
|
serverId,
|
||||||
|
regions,
|
||||||
|
multimaster,
|
||||||
|
false /*rupmEnabled*/
|
||||||
|
);
|
||||||
estimatedDedicatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
|
estimatedDedicatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||||
offerThroughput,
|
offerThroughput,
|
||||||
serverId,
|
serverId,
|
||||||
regions,
|
regions,
|
||||||
multimaster,
|
multimaster,
|
||||||
|
false /*rupmEnabled*/,
|
||||||
this.isAutoPilotSelected()
|
this.isAutoPilotSelected()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -159,6 +165,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
serverId,
|
serverId,
|
||||||
regions,
|
regions,
|
||||||
multimaster,
|
multimaster,
|
||||||
|
false /*rupmEnabled*/,
|
||||||
this.isAutoPilotSelected()
|
this.isAutoPilotSelected()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -172,7 +179,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverId = configContext.serverId;
|
const serverId = this.container.serverId();
|
||||||
const regions =
|
const regions =
|
||||||
(account &&
|
(account &&
|
||||||
account.properties &&
|
account.properties &&
|
||||||
@@ -183,12 +190,19 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
let estimatedSpend: string;
|
let estimatedSpend: string;
|
||||||
let estimatedSharedSpendAcknowledge: string;
|
let estimatedSharedSpendAcknowledge: string;
|
||||||
if (!this.isSharedAutoPilotSelected()) {
|
if (!this.isSharedAutoPilotSelected()) {
|
||||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(this.keyspaceThroughput(), serverId, regions, multimaster);
|
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
|
||||||
|
this.keyspaceThroughput(),
|
||||||
|
serverId,
|
||||||
|
regions,
|
||||||
|
multimaster,
|
||||||
|
false /*rupmEnabled*/
|
||||||
|
);
|
||||||
estimatedSharedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
|
estimatedSharedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||||
this.keyspaceThroughput(),
|
this.keyspaceThroughput(),
|
||||||
serverId,
|
serverId,
|
||||||
regions,
|
regions,
|
||||||
multimaster,
|
multimaster,
|
||||||
|
false /*rupmEnabled*/,
|
||||||
this.isSharedAutoPilotSelected()
|
this.isSharedAutoPilotSelected()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -203,6 +217,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
serverId,
|
serverId,
|
||||||
regions,
|
regions,
|
||||||
multimaster,
|
multimaster,
|
||||||
|
false /*rupmEnabled*/,
|
||||||
this.isSharedAutoPilotSelected()
|
this.isSharedAutoPilotSelected()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -297,10 +312,11 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
storage: Constants.BackendDefaults.multiPartitionStorageInGb,
|
storage: Constants.BackendDefaults.multiPartitionStorageInGb,
|
||||||
offerThroughput: this.throughput(),
|
offerThroughput: this.throughput(),
|
||||||
partitionKey: "",
|
partitionKey: "",
|
||||||
databaseId: this.keyspaceId()
|
databaseId: this.keyspaceId(),
|
||||||
|
rupm: false
|
||||||
}),
|
}),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: userContext.quotaId,
|
subscriptionQuotaId: this.container.quotaId(),
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: "u",
|
storage: "u",
|
||||||
throughput: this.throughput(),
|
throughput: this.throughput(),
|
||||||
@@ -350,11 +366,12 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
offerThroughput: this.throughput(),
|
offerThroughput: this.throughput(),
|
||||||
partitionKey: "",
|
partitionKey: "",
|
||||||
databaseId: this.keyspaceId(),
|
databaseId: this.keyspaceId(),
|
||||||
|
rupm: false,
|
||||||
hasDedicatedThroughput: this.dedicateTableThroughput()
|
hasDedicatedThroughput: this.dedicateTableThroughput()
|
||||||
}),
|
}),
|
||||||
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: userContext.quotaId,
|
subscriptionQuotaId: this.container.quotaId(),
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: "u",
|
storage: "u",
|
||||||
throughput: this.throughput(),
|
throughput: this.throughput(),
|
||||||
@@ -396,11 +413,12 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
offerThroughput: this.throughput(),
|
offerThroughput: this.throughput(),
|
||||||
partitionKey: "",
|
partitionKey: "",
|
||||||
databaseId: this.keyspaceId(),
|
databaseId: this.keyspaceId(),
|
||||||
|
rupm: false,
|
||||||
hasDedicatedThroughput: this.dedicateTableThroughput()
|
hasDedicatedThroughput: this.dedicateTableThroughput()
|
||||||
}),
|
}),
|
||||||
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: userContext.quotaId,
|
subscriptionQuotaId: this.container.quotaId(),
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: "u",
|
storage: "u",
|
||||||
throughput: this.throughput(),
|
throughput: this.throughput(),
|
||||||
@@ -426,11 +444,12 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
offerThroughput: this.throughput(),
|
offerThroughput: this.throughput(),
|
||||||
partitionKey: "",
|
partitionKey: "",
|
||||||
databaseId: this.keyspaceId(),
|
databaseId: this.keyspaceId(),
|
||||||
|
rupm: false,
|
||||||
hasDedicatedThroughput: this.dedicateTableThroughput()
|
hasDedicatedThroughput: this.dedicateTableThroughput()
|
||||||
},
|
},
|
||||||
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: userContext.quotaId,
|
subscriptionQuotaId: this.container.quotaId(),
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: "u",
|
storage: "u",
|
||||||
throughput: this.throughput(),
|
throughput: this.throughput(),
|
||||||
|
|||||||
@@ -98,21 +98,26 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
|
|||||||
author: string,
|
author: string,
|
||||||
notebookContent: string | ImmutableNotebook,
|
notebookContent: string | ImmutableNotebook,
|
||||||
parentDomElement: HTMLElement,
|
parentDomElement: HTMLElement,
|
||||||
|
isCodeOfConductEnabled: boolean,
|
||||||
isLinkInjectionEnabled: boolean
|
isLinkInjectionEnabled: boolean
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
if (isCodeOfConductEnabled) {
|
||||||
const response = await this.junoClient.isCodeOfConductAccepted();
|
try {
|
||||||
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
|
const response = await this.junoClient.isCodeOfConductAccepted();
|
||||||
throw new Error(`Received HTTP ${response.status} when accepting code of conduct`);
|
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
|
||||||
}
|
throw new Error(`Received HTTP ${response.status} when accepting code of conduct`);
|
||||||
|
}
|
||||||
|
|
||||||
this.isCodeOfConductAccepted = response.data;
|
this.isCodeOfConductAccepted = response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(
|
handleError(
|
||||||
error,
|
error,
|
||||||
"PublishNotebookPaneAdapter/isCodeOfConductAccepted",
|
"PublishNotebookPaneAdapter/isCodeOfConductAccepted",
|
||||||
"Failed to check if code of conduct was accepted"
|
"Failed to check if code of conduct was accepted"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.isCodeOfConductAccepted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
padding: 32px 16px;
|
padding: 32px 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: @BaseLight;
|
background-color: @BaseLight;
|
||||||
border: 1px solid #949494;
|
border: 1px solid #E5E5E5;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
|
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverId = configContext.serverId;
|
const serverId = this.container.serverId();
|
||||||
const regions =
|
const regions =
|
||||||
(account &&
|
(account &&
|
||||||
account.properties &&
|
account.properties &&
|
||||||
@@ -155,7 +155,8 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
|||||||
this.overrideWithAutoPilotSettings() ? this.autoPilotThroughput() : this.throughput(),
|
this.overrideWithAutoPilotSettings() ? this.autoPilotThroughput() : this.throughput(),
|
||||||
serverId,
|
serverId,
|
||||||
regions,
|
regions,
|
||||||
multimaster
|
multimaster,
|
||||||
|
false /*rupmEnabled*/
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
|
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
|
||||||
@@ -230,7 +231,9 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
|||||||
return this.throughputTitle() + this.requestUnitsUsageCost();
|
return this.throughputTitle() + this.requestUnitsUsageCost();
|
||||||
});
|
});
|
||||||
this.pendingNotification = ko.observable<DataModels.Notification>();
|
this.pendingNotification = ko.observable<DataModels.Notification>();
|
||||||
this._offerReplacePending = ko.observable<boolean>(!!this.database.offer()?.offerReplacePending);
|
this._offerReplacePending = ko.observable<boolean>(
|
||||||
|
!!this.database.offer()?.headers?.[Constants.HttpHeaders.offerReplacePending]
|
||||||
|
);
|
||||||
this.notificationStatusInfo = ko.observable<string>("");
|
this.notificationStatusInfo = ko.observable<string>("");
|
||||||
this.shouldShowNotificationStatusPrompt = ko.computed<boolean>(() => this.notificationStatusInfo().length > 0);
|
this.shouldShowNotificationStatusPrompt = ko.computed<boolean>(() => this.notificationStatusInfo().length > 0);
|
||||||
this.warningMessage = ko.computed<string>(() => {
|
this.warningMessage = ko.computed<string>(() => {
|
||||||
@@ -239,7 +242,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
|||||||
}
|
}
|
||||||
|
|
||||||
const offer = this.database.offer();
|
const offer = this.database.offer();
|
||||||
if (offer?.offerReplacePending) {
|
if (offer?.headers?.[Constants.HttpHeaders.offerReplacePending]) {
|
||||||
const throughput = offer.manualThroughput || offer.autoscaleMaxThroughput;
|
const throughput = offer.manualThroughput || offer.autoscaleMaxThroughput;
|
||||||
return throughputApplyShortDelayMessage(this.isAutoPilotSelected(), throughput, this.database.id());
|
return throughputApplyShortDelayMessage(this.isAutoPilotSelected(), throughput, this.database.id());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export default class MongoShellTab extends TabsBase {
|
|||||||
this._runtimeEndpoint = configContext.platform === Platform.Hosted ? configContext.BACKEND_ENDPOINT : "";
|
this._runtimeEndpoint = configContext.platform === Platform.Hosted ? configContext.BACKEND_ENDPOINT : "";
|
||||||
const extensionEndpoint: string = configContext.BACKEND_ENDPOINT || this._runtimeEndpoint || "";
|
const extensionEndpoint: string = configContext.BACKEND_ENDPOINT || this._runtimeEndpoint || "";
|
||||||
let baseUrl = "/content/mongoshell/dist/";
|
let baseUrl = "/content/mongoshell/dist/";
|
||||||
if (configContext.serverId === "localhost") {
|
if (this._container.serverId() === "localhost") {
|
||||||
baseUrl = "/content/mongoshell/";
|
baseUrl = "/content/mongoshell/";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ describe("Collection", () => {
|
|||||||
container: Explorer,
|
container: Explorer,
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
data: DataModels.Collection,
|
data: DataModels.Collection,
|
||||||
|
quotaInfo: DataModels.CollectionQuotaInfo,
|
||||||
offer: DataModels.Offer
|
offer: DataModels.Offer
|
||||||
): Collection {
|
): Collection {
|
||||||
return new Collection(container, databaseId, data);
|
return new Collection(container, databaseId, data, quotaInfo, offer);
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateMockCollectionsDataModelWithPartitionKey(
|
function generateMockCollectionsDataModelWithPartitionKey(
|
||||||
@@ -49,7 +50,7 @@ describe("Collection", () => {
|
|||||||
});
|
});
|
||||||
mockContainer.deleteCollectionText = ko.observable<string>("delete collection");
|
mockContainer.deleteCollectionText = ko.observable<string>("delete collection");
|
||||||
|
|
||||||
return generateCollection(mockContainer, "abc", data, {} as DataModels.Offer);
|
return generateCollection(mockContainer, "abc", data, {} as DataModels.CollectionQuotaInfo, {} as DataModels.Offer);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("Partition key path parsing", () => {
|
describe("Partition key path parsing", () => {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { readTriggers } from "../../Common/dataAccess/readTriggers";
|
|||||||
import { readUserDefinedFunctions } from "../../Common/dataAccess/readUserDefinedFunctions";
|
import { readUserDefinedFunctions } from "../../Common/dataAccess/readUserDefinedFunctions";
|
||||||
import { createDocument } from "../../Common/DocumentClientUtilityBase";
|
import { createDocument } from "../../Common/DocumentClientUtilityBase";
|
||||||
import { readCollectionOffer } from "../../Common/dataAccess/readCollectionOffer";
|
import { readCollectionOffer } from "../../Common/dataAccess/readCollectionOffer";
|
||||||
import { getCollectionUsageSizeInKB } from "../../Common/dataAccess/getCollectionDataUsageSize";
|
import { readCollectionQuotaInfo } from "../../Common/dataAccess/readCollectionQuotaInfo";
|
||||||
import * as Logger from "../../Common/Logger";
|
import * as Logger from "../../Common/Logger";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
@@ -37,6 +37,7 @@ import UserDefinedFunction from "./UserDefinedFunction";
|
|||||||
import { configContext, Platform } from "../../ConfigContext";
|
import { configContext, Platform } from "../../ConfigContext";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
import TabsBase from "../Tabs/TabsBase";
|
||||||
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
@@ -53,8 +54,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
public defaultTtl: ko.Observable<number>;
|
public defaultTtl: ko.Observable<number>;
|
||||||
public indexingPolicy: ko.Observable<DataModels.IndexingPolicy>;
|
public indexingPolicy: ko.Observable<DataModels.IndexingPolicy>;
|
||||||
public uniqueKeyPolicy: DataModels.UniqueKeyPolicy;
|
public uniqueKeyPolicy: DataModels.UniqueKeyPolicy;
|
||||||
public usageSizeInKB: ko.Observable<number>;
|
public quotaInfo: ko.Observable<DataModels.CollectionQuotaInfo>;
|
||||||
|
|
||||||
public offer: ko.Observable<DataModels.Offer>;
|
public offer: ko.Observable<DataModels.Offer>;
|
||||||
public conflictResolutionPolicy: ko.Observable<DataModels.ConflictResolutionPolicy>;
|
public conflictResolutionPolicy: ko.Observable<DataModels.ConflictResolutionPolicy>;
|
||||||
public changeFeedPolicy: ko.Observable<DataModels.ChangeFeedPolicy>;
|
public changeFeedPolicy: ko.Observable<DataModels.ChangeFeedPolicy>;
|
||||||
@@ -95,7 +95,13 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
public userDefinedFunctionsFocused: ko.Observable<boolean>;
|
public userDefinedFunctionsFocused: ko.Observable<boolean>;
|
||||||
public triggersFocused: ko.Observable<boolean>;
|
public triggersFocused: ko.Observable<boolean>;
|
||||||
|
|
||||||
constructor(container: Explorer, databaseId: string, data: DataModels.Collection) {
|
constructor(
|
||||||
|
container: Explorer,
|
||||||
|
databaseId: string,
|
||||||
|
data: DataModels.Collection,
|
||||||
|
quotaInfo: DataModels.CollectionQuotaInfo,
|
||||||
|
offer: DataModels.Offer
|
||||||
|
) {
|
||||||
this.nodeKind = "Collection";
|
this.nodeKind = "Collection";
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this.self = data._self;
|
this.self = data._self;
|
||||||
@@ -107,8 +113,8 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
this.id = ko.observable(data.id);
|
this.id = ko.observable(data.id);
|
||||||
this.defaultTtl = ko.observable(data.defaultTtl);
|
this.defaultTtl = ko.observable(data.defaultTtl);
|
||||||
this.indexingPolicy = ko.observable(data.indexingPolicy);
|
this.indexingPolicy = ko.observable(data.indexingPolicy);
|
||||||
this.usageSizeInKB = ko.observable();
|
this.quotaInfo = ko.observable(quotaInfo);
|
||||||
this.offer = ko.observable();
|
this.offer = ko.observable(offer);
|
||||||
this.conflictResolutionPolicy = ko.observable(data.conflictResolutionPolicy);
|
this.conflictResolutionPolicy = ko.observable(data.conflictResolutionPolicy);
|
||||||
this.changeFeedPolicy = ko.observable<DataModels.ChangeFeedPolicy>(data.changeFeedPolicy);
|
this.changeFeedPolicy = ko.observable<DataModels.ChangeFeedPolicy>(data.changeFeedPolicy);
|
||||||
this.analyticalStorageTtl = ko.observable(data.analyticalStorageTtl);
|
this.analyticalStorageTtl = ko.observable(data.analyticalStorageTtl);
|
||||||
@@ -601,6 +607,14 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private async loadCollectionQuotaInfo(): Promise<void> {
|
||||||
|
// TODO: Use the collection entity cache to get quota info
|
||||||
|
const quotaInfoWithUniqueKeyPolicy = await readCollectionQuotaInfo(this);
|
||||||
|
this.uniqueKeyPolicy = quotaInfoWithUniqueKeyPolicy.uniqueKeyPolicy;
|
||||||
|
const quotaInfo = _.omit(quotaInfoWithUniqueKeyPolicy, "uniqueKeyPolicy");
|
||||||
|
this.quotaInfo(quotaInfo);
|
||||||
|
}
|
||||||
|
|
||||||
public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
|
public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
|
||||||
const collection: ViewModels.Collection = source.collection || source;
|
const collection: ViewModels.Collection = source.collection || source;
|
||||||
const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
|
const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
|
||||||
@@ -1273,7 +1287,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
this.offer(await readCollectionOffer(params));
|
this.offer(await readCollectionOffer(params));
|
||||||
this.usageSizeInKB(await getCollectionUsageSizeInKB(this.databaseId, this.id()));
|
await this.loadCollectionQuotaInfo();
|
||||||
|
|
||||||
TelemetryProcessor.traceSuccess(
|
TelemetryProcessor.traceSuccess(
|
||||||
Action.LoadOffers,
|
Action.LoadOffers,
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ export default class Database implements ViewModels.Database {
|
|||||||
});
|
});
|
||||||
|
|
||||||
deltaCollections.toAdd.forEach((collection: DataModels.Collection) => {
|
deltaCollections.toAdd.forEach((collection: DataModels.Collection) => {
|
||||||
const collectionVM: Collection = new Collection(this.container, this.id(), collection);
|
const collectionVM: Collection = new Collection(this.container, this.id(), collection, null, null);
|
||||||
collectionVMs.push(collectionVM);
|
collectionVMs.push(collectionVM);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -229,7 +229,9 @@ const createMockCollection = (): ViewModels.Collection => {
|
|||||||
const mockCollectionVM: ViewModels.Collection = new Collection(
|
const mockCollectionVM: ViewModels.Collection = new Collection(
|
||||||
createMockContainer(),
|
createMockContainer(),
|
||||||
"fakeDatabaseId",
|
"fakeDatabaseId",
|
||||||
mockCollection
|
mockCollection,
|
||||||
|
undefined,
|
||||||
|
undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
return mockCollectionVM;
|
return mockCollectionVM;
|
||||||
|
|||||||
@@ -178,7 +178,8 @@ export class JunoClient {
|
|||||||
return this.getNotebooks(`${this.getNotebooksUrl()}/gallery/public`);
|
return this.getNotebooks(`${this.getNotebooksUrl()}/gallery/public`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getPublicGalleryData(): Promise<IJunoResponse<IPublicGalleryData>> {
|
// will be renamed once feature.enableCodeOfConduct flag is removed
|
||||||
|
public async fetchPublicNotebooks(): Promise<IJunoResponse<IPublicGalleryData>> {
|
||||||
const url = `${this.getNotebooksAccountUrl()}/gallery/public`;
|
const url = `${this.getNotebooksAccountUrl()}/gallery/public`;
|
||||||
const response = await window.fetch(url, {
|
const response = await window.fetch(url, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
@@ -404,7 +405,7 @@ export class JunoClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async reportAbuse(notebookId: string, abuseCategory: string, notes: string): Promise<IJunoResponse<boolean>> {
|
public async reportAbuse(notebookId: string, abuseCategory: string, notes: string): Promise<IJunoResponse<boolean>> {
|
||||||
const response = await window.fetch(`${this.getNotebooksUrl()}/gallery/reportAbuse`, {
|
const response = await window.fetch(`${this.getNotebooksUrl()}/avert/reportAbuse`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
notebookId,
|
notebookId,
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import { getErrorMessage } from "../Common/ErrorHandlingUtils";
|
|||||||
export class NotebookWorkspaceManager {
|
export class NotebookWorkspaceManager {
|
||||||
private resourceProviderClientFactory: IResourceProviderClientFactory<any>;
|
private resourceProviderClientFactory: IResourceProviderClientFactory<any>;
|
||||||
|
|
||||||
constructor() {
|
constructor(private _armEndpoint: string) {
|
||||||
this.resourceProviderClientFactory = new ResourceProviderClientFactory();
|
this.resourceProviderClientFactory = new ResourceProviderClientFactory(this._armEndpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getNotebookWorkspacesAsync(cosmosdbResourceId: string): Promise<NotebookWorkspace[]> {
|
public async getNotebookWorkspacesAsync(cosmosdbResourceId: string): Promise<NotebookWorkspace[]> {
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ export function initializeExplorer(): Explorer {
|
|||||||
cassandraEndpoint: ""
|
cassandraEndpoint: ""
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
explorer.isAccountReady(true);
|
explorer.isAccountReady(true);
|
||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ export default class Main {
|
|||||||
masterKey?: string /* master key extracted from connection string if available */,
|
masterKey?: string /* master key extracted from connection string if available */,
|
||||||
account?: DatabaseAccount,
|
account?: DatabaseAccount,
|
||||||
authorizationToken?: string /* access key */
|
authorizationToken?: string /* access key */
|
||||||
): void {
|
): Q.Promise<void> {
|
||||||
const serverId: string = AuthHeadersUtil.serverId;
|
const serverId: string = AuthHeadersUtil.serverId;
|
||||||
const authType: string = (<any>window).authType;
|
const authType: string = (<any>window).authType;
|
||||||
const accountResourceId =
|
const accountResourceId =
|
||||||
@@ -373,7 +373,7 @@ export default class Main {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Unsupported AuthType ${authType}`);
|
return Q.reject(`Unsupported AuthType ${authType}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static _instantiateExplorer(): Explorer {
|
private static _instantiateExplorer(): Explorer {
|
||||||
|
|||||||
@@ -1,23 +1,9 @@
|
|||||||
import "../../Explorer/Tables/DataTable/DataTableBindingManager";
|
import "../../Explorer/Tables/DataTable/DataTableBindingManager";
|
||||||
import Explorer from "../../Explorer/Explorer";
|
import Explorer from "../../Explorer/Explorer";
|
||||||
import { handleMessage } from "../../Controls/Heatmap/Heatmap";
|
|
||||||
|
|
||||||
export function initializeExplorer(): Explorer {
|
export function initializeExplorer(): Explorer {
|
||||||
const explorer = new Explorer();
|
const explorer = new Explorer();
|
||||||
|
|
||||||
// In development mode, try to load the iframe message from session storage.
|
|
||||||
// This allows webpack hot reload to funciton properly
|
|
||||||
if (process.env.NODE_ENV === "development") {
|
|
||||||
const initMessage = sessionStorage.getItem("portalDataExplorerInitMessage");
|
|
||||||
if (initMessage) {
|
|
||||||
const message = JSON.parse(initMessage);
|
|
||||||
console.warn("Loaded cached portal iframe message from session storage");
|
|
||||||
console.dir(message);
|
|
||||||
explorer.initDataExplorerWithFrameInputs(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("message", explorer.handleMessage.bind(explorer), false);
|
window.addEventListener("message", explorer.handleMessage.bind(explorer), false);
|
||||||
|
|
||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
import { configContext } from "../ConfigContext";
|
|
||||||
import { IResourceProviderClientFactory, IResourceProviderClient } from "./IResourceProviderClient";
|
import { IResourceProviderClientFactory, IResourceProviderClient } from "./IResourceProviderClient";
|
||||||
import { ResourceProviderClient } from "./ResourceProviderClient";
|
import { ResourceProviderClient } from "./ResourceProviderClient";
|
||||||
|
|
||||||
export class ResourceProviderClientFactory implements IResourceProviderClientFactory<any> {
|
export class ResourceProviderClientFactory implements IResourceProviderClientFactory<any> {
|
||||||
private armEndpoint: string;
|
|
||||||
private cachedClients: { [url: string]: IResourceProviderClient<any> } = {};
|
private cachedClients: { [url: string]: IResourceProviderClient<any> } = {};
|
||||||
|
|
||||||
constructor() {
|
constructor(private armEndpoint: string) {}
|
||||||
this.armEndpoint = configContext.ARM_ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getOrCreate(url: string): IResourceProviderClient<any> {
|
public getOrCreate(url: string): IResourceProviderClient<any> {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ export class OfferPricing {
|
|||||||
Standard: {
|
Standard: {
|
||||||
StartingPrice: 24 / hoursInAMonth, // per hour
|
StartingPrice: 24 / hoursInAMonth, // per hour
|
||||||
PricePerRU: 0.00008,
|
PricePerRU: 0.00008,
|
||||||
|
PricePerRUPM: (10 * 2) / 1000 / hoursInAMonth, // preview price: $2 per 1000 RU/m per month -> 100 RU/s
|
||||||
PricePerGB: 0.25 / hoursInAMonth
|
PricePerGB: 0.25 / hoursInAMonth
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -138,18 +139,24 @@ export class OfferPricing {
|
|||||||
Standard: {
|
Standard: {
|
||||||
StartingPrice: OfferPricing.MonthlyPricing.mooncake.Standard.StartingPrice / hoursInAMonth, // per hour
|
StartingPrice: OfferPricing.MonthlyPricing.mooncake.Standard.StartingPrice / hoursInAMonth, // per hour
|
||||||
PricePerRU: 0.00051,
|
PricePerRU: 0.00051,
|
||||||
|
PricePerRUPM: (10 * 20) / 1000 / hoursInAMonth, // preview price: 20rmb per 1000 RU/m per month -> 100 RU/s
|
||||||
PricePerGB: OfferPricing.MonthlyPricing.mooncake.Standard.PricePerGB / hoursInAMonth
|
PricePerGB: OfferPricing.MonthlyPricing.mooncake.Standard.PricePerGB / hoursInAMonth
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class GeneralResources {
|
||||||
|
public static loadingText: string = "Loading...";
|
||||||
|
}
|
||||||
|
|
||||||
export class CollectionCreation {
|
export class CollectionCreation {
|
||||||
// TODO generate these values based on Product\Services\Documents\ImageStore\GatewayApplication\Settings.xml
|
// TODO generate these values based on Product\Services\Documents\ImageStore\GatewayApplication\Settings.xml
|
||||||
public static readonly MinRUPerPartitionBelow7Partitions: number = 400;
|
public static readonly MinRUPerPartitionBelow7Partitions: number = 400;
|
||||||
public static readonly MinRU7PartitionsTo25Partitions: number = 2500;
|
public static readonly MinRU7PartitionsTo25Partitions: number = 2500;
|
||||||
public static readonly MinRUPerPartitionAbove25Partitions: number = 100;
|
public static readonly MinRUPerPartitionAbove25Partitions: number = 100;
|
||||||
public static readonly MaxRUPerPartition: number = 10000;
|
public static readonly MaxRUPerPartition: number = 10000;
|
||||||
|
public static readonly MaxRUPMPerPartition: number = 5000;
|
||||||
public static readonly MinPartitionedCollectionRUs: number = 2500;
|
public static readonly MinPartitionedCollectionRUs: number = 2500;
|
||||||
|
|
||||||
public static readonly NumberOfPartitionsInFixedCollection: number = 1;
|
public static readonly NumberOfPartitionsInFixedCollection: number = 1;
|
||||||
@@ -224,6 +231,32 @@ export class IndexingPolicies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class SubscriptionUtilMappings {
|
export class SubscriptionUtilMappings {
|
||||||
|
// TODO: Expose this through a web API from the portal
|
||||||
|
public static SubscriptionTypeMap: { [key: string]: SubscriptionType } = {
|
||||||
|
"AAD_2015-09-01": SubscriptionType.Free,
|
||||||
|
"AzureDynamics_2014-09-01": SubscriptionType.Free,
|
||||||
|
"AzureInOpen_2014-09-01": SubscriptionType.EA,
|
||||||
|
"AzurePass_2014-09-01": SubscriptionType.Free,
|
||||||
|
"BackupStorage_2014-09-01": SubscriptionType.PAYG,
|
||||||
|
"BizSpark_2014-09-01": SubscriptionType.Benefits,
|
||||||
|
"BizSparkPlus_2014-09-01": SubscriptionType.Benefits,
|
||||||
|
"CSP_2015-05-01": SubscriptionType.EA,
|
||||||
|
"Default_2014-09-01": SubscriptionType.PAYG,
|
||||||
|
"DevEssentials_2016-01-01": SubscriptionType.Benefits,
|
||||||
|
"DreamSpark_2015-02-01": SubscriptionType.Benefits,
|
||||||
|
"EnterpriseAgreement_2014-09-01": SubscriptionType.EA,
|
||||||
|
"FreeTrial_2014-09-01": SubscriptionType.Free,
|
||||||
|
"Internal_2014-09-01": SubscriptionType.Internal,
|
||||||
|
"LegacyMonetaryCommitment_2014-09-01": SubscriptionType.EA,
|
||||||
|
"LightweightTrial_2016-09-01": SubscriptionType.Free,
|
||||||
|
"MonetaryCommitment_2015-05-01": SubscriptionType.EA,
|
||||||
|
"MPN_2014-09-01": SubscriptionType.Benefits,
|
||||||
|
"MSDN_2014-09-01": SubscriptionType.Benefits,
|
||||||
|
"MSDNDevTest_2014-09-01": SubscriptionType.Benefits,
|
||||||
|
"PayAsYouGo_2014-09-01": SubscriptionType.PAYG,
|
||||||
|
"Sponsored_2016-01-01": SubscriptionType.Benefits
|
||||||
|
};
|
||||||
|
|
||||||
public static FreeTierSubscriptionIds: string[] = [
|
public static FreeTierSubscriptionIds: string[] = [
|
||||||
"b8f2ff04-0a81-4cf9-95ef-5828d16981d2",
|
"b8f2ff04-0a81-4cf9-95ef-5828d16981d2",
|
||||||
"39b1fdff-e5b2-4f83-adb4-33cb3aabf5ea",
|
"39b1fdff-e5b2-4f83-adb4-33cb3aabf5ea",
|
||||||
@@ -234,6 +267,57 @@ export class SubscriptionUtilMappings {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Offers {
|
||||||
|
public static offerTypeS1: string = "S1";
|
||||||
|
public static offerTypeS2: string = "S2";
|
||||||
|
public static offerTypeS3: string = "S3";
|
||||||
|
public static offerTypeStandard: string = "Standard";
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OfferThoughput {
|
||||||
|
public static offerS1Throughput: number = 250;
|
||||||
|
public static offerS2Throughput: number = 1000;
|
||||||
|
public static offerS3Throughput: number = 2500;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OfferVersions {
|
||||||
|
public static offerV1: string = "V1";
|
||||||
|
public static offerV2: string = "V2";
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InvalidOffers {
|
||||||
|
public static offerTypeInvalid: string = "Invalid";
|
||||||
|
public static offerTypeError: string = "Loading Error";
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SpecTypes {
|
||||||
|
public static collection: string = "DocumentDbCollection";
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CurrencyCodes {
|
||||||
|
public static usd: string = "USD";
|
||||||
|
public static rmb: string = "RMB";
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ColorSchemes {
|
||||||
|
public static standard: string = "mediumBlue";
|
||||||
|
public static legacy: string = "yellowGreen";
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FeatureIds {
|
||||||
|
public static storage: string = "storage";
|
||||||
|
public static sla: string = "sla";
|
||||||
|
public static partitioned: string = "partitioned";
|
||||||
|
public static singlePartitioned: string = "singlePartition";
|
||||||
|
public static legacySinglePartitioned: string = "legacySinglePartition";
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FeatureIconNames {
|
||||||
|
public static storage: string = "SSD";
|
||||||
|
public static sla: string = "Monitoring";
|
||||||
|
public static productionReady: string = "ProductionReadyDb";
|
||||||
|
}
|
||||||
|
|
||||||
export class AutopilotDocumentation {
|
export class AutopilotDocumentation {
|
||||||
public static Url: string = "https://aka.ms/cosmos-autoscale-info";
|
public static Url: string = "https://aka.ms/cosmos-autoscale-info";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
import * as Constants from "./Constants";
|
import * as Constants from "./Constants";
|
||||||
|
|
||||||
export function computeRUUsagePrice(serverId: string, requestUnits: number): string {
|
export function computeRUUsagePrice(serverId: string, rupmEnabled: boolean, requestUnits: number): string {
|
||||||
if (serverId === "mooncake") {
|
if (serverId === "mooncake") {
|
||||||
let ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerRU;
|
let ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerRU,
|
||||||
return calculateEstimateNumber(ruCharge) + " " + Constants.OfferPricing.HourlyPricing.mooncake.Currency;
|
rupmCharge = rupmEnabled ? requestUnits * Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerRUPM : 0;
|
||||||
|
return (
|
||||||
|
calculateEstimateNumber(ruCharge + rupmCharge) + " " + Constants.OfferPricing.HourlyPricing.mooncake.Currency
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRU;
|
let ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRU,
|
||||||
return calculateEstimateNumber(ruCharge) + " " + Constants.OfferPricing.HourlyPricing.default.Currency;
|
rupmCharge = rupmEnabled ? requestUnits * Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRUPM : 0;
|
||||||
|
return calculateEstimateNumber(ruCharge + rupmCharge) + " " + Constants.OfferPricing.HourlyPricing.default.Currency;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function computeStorageUsagePrice(serverId: string, storageUsedRoundUpToGB: number): string {
|
export function computeStorageUsagePrice(serverId: string, storageUsedRoundUpToGB: number): string {
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ import { ArmApiVersions, ArmResourceTypes } from "../Common/Constants";
|
|||||||
import { IResourceProviderClient, IResourceProviderClientFactory } from "../ResourceProvider/IResourceProviderClient";
|
import { IResourceProviderClient, IResourceProviderClientFactory } from "../ResourceProvider/IResourceProviderClient";
|
||||||
import * as Logger from "../Common/Logger";
|
import * as Logger from "../Common/Logger";
|
||||||
import { ResourceProviderClientFactory } from "../ResourceProvider/ResourceProviderClientFactory";
|
import { ResourceProviderClientFactory } from "../ResourceProvider/ResourceProviderClientFactory";
|
||||||
|
import { configContext } from "../ConfigContext";
|
||||||
import { getErrorMessage } from "../Common/ErrorHandlingUtils";
|
import { getErrorMessage } from "../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
export class ArcadiaResourceManager {
|
export class ArcadiaResourceManager {
|
||||||
private resourceProviderClientFactory: IResourceProviderClientFactory<any>;
|
private resourceProviderClientFactory: IResourceProviderClientFactory<any>;
|
||||||
|
|
||||||
constructor() {
|
constructor(private armEndpoint = configContext.ARM_ENDPOINT) {
|
||||||
this.resourceProviderClientFactory = new ResourceProviderClientFactory();
|
this.resourceProviderClientFactory = new ResourceProviderClientFactory(this.armEndpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getWorkspacesAsync(arcadiaResourceId: string): Promise<ArcadiaWorkspace[]> {
|
public async getWorkspacesAsync(arcadiaResourceId: string): Promise<ArcadiaWorkspace[]> {
|
||||||
|
|||||||
@@ -23,10 +23,14 @@ const createServerSettings = (urlVars: { [key: string]: string }): ServerConnect
|
|||||||
let headers: HeadersInit;
|
let headers: HeadersInit;
|
||||||
if (urlVars.hasOwnProperty(TerminalQueryParams.TerminalEndpoint)) {
|
if (urlVars.hasOwnProperty(TerminalQueryParams.TerminalEndpoint)) {
|
||||||
body = JSON.stringify({
|
body = JSON.stringify({
|
||||||
endpoint: urlVars[TerminalQueryParams.TerminalEndpoint]
|
endpoint: urlVars[TerminalQueryParams.TerminalEndpoint],
|
||||||
|
account:"contoso-retail-mongodb",
|
||||||
|
port: "10255",
|
||||||
|
token:"" //tofill
|
||||||
});
|
});
|
||||||
headers = {
|
headers = {
|
||||||
[HttpHeaders.contentType]: "application/json"
|
[HttpHeaders.contentType]: "application/json"
|
||||||
|
//"Access-Control-Allow-Origin": "https://localhost:5001"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,8 +63,9 @@ const main = async (): Promise<void> => {
|
|||||||
|
|
||||||
const serverSettings = createServerSettings(urlVars);
|
const serverSettings = createServerSettings(urlVars);
|
||||||
|
|
||||||
const data = { baseUrl: serverSettings.baseUrl };
|
const startTime = TelemetryProcessor.traceStart(Action.OpenTerminal, {
|
||||||
const startTime = TelemetryProcessor.traceStart(Action.OpenTerminal, data);
|
baseUrl: serverSettings.baseUrl
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (urlVars.hasOwnProperty(TerminalQueryParams.Terminal)) {
|
if (urlVars.hasOwnProperty(TerminalQueryParams.Terminal)) {
|
||||||
@@ -69,9 +74,9 @@ const main = async (): Promise<void> => {
|
|||||||
throw new Error("Only terminal is supported");
|
throw new Error("Only terminal is supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
TelemetryProcessor.traceSuccess(Action.OpenTerminal, data, startTime);
|
TelemetryProcessor.traceSuccess(Action.OpenTerminal, startTime);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
TelemetryProcessor.traceFailure(Action.OpenTerminal, data, startTime);
|
TelemetryProcessor.traceFailure(Action.OpenTerminal, startTime);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ interface UserContext {
|
|||||||
defaultExperience?: DefaultAccountExperienceType;
|
defaultExperience?: DefaultAccountExperienceType;
|
||||||
useSDKOperations?: boolean;
|
useSDKOperations?: boolean;
|
||||||
subscriptionType?: SubscriptionType;
|
subscriptionType?: SubscriptionType;
|
||||||
quotaId?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const userContext: Readonly<UserContext> = {} as const;
|
const userContext: Readonly<UserContext> = {} as const;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import Explorer from "../Explorer/Explorer";
|
|||||||
import { IChoiceGroupOption, IChoiceGroupProps } from "office-ui-fabric-react";
|
import { IChoiceGroupOption, IChoiceGroupProps } from "office-ui-fabric-react";
|
||||||
import { TextFieldProps } from "../Explorer/Controls/DialogReactComponent/DialogComponent";
|
import { TextFieldProps } from "../Explorer/Controls/DialogReactComponent/DialogComponent";
|
||||||
import { handleError } from "../Common/ErrorHandlingUtils";
|
import { handleError } from "../Common/ErrorHandlingUtils";
|
||||||
import { HttpStatusCodes } from "../Common/Constants";
|
|
||||||
|
|
||||||
const defaultSelectedAbuseCategory = "Other";
|
const defaultSelectedAbuseCategory = "Other";
|
||||||
const abuseCategories: IChoiceGroupOption[] = [
|
const abuseCategories: IChoiceGroupOption[] = [
|
||||||
@@ -114,7 +113,7 @@ export function reportAbuse(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await junoClient.reportAbuse(notebookId, abuseCategory, additionalDetails);
|
const response = await junoClient.reportAbuse(notebookId, abuseCategory, additionalDetails);
|
||||||
if (response.status !== HttpStatusCodes.Accepted) {
|
if (!response.data) {
|
||||||
throw new Error(`Received HTTP ${response.status} when submitting report for ${data.name}`);
|
throw new Error(`Received HTTP ${response.status} when submitting report for ${data.name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,37 +25,37 @@ describe("PricingUtils Tests", () => {
|
|||||||
|
|
||||||
describe("computeRUUsagePriceHourly()", () => {
|
describe("computeRUUsagePriceHourly()", () => {
|
||||||
it("should return 0 for NaN regions default cloud", () => {
|
it("should return 0 for NaN regions default cloud", () => {
|
||||||
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, null, false);
|
const value = PricingUtils.computeRUUsagePriceHourly("default", false, 1, null, false);
|
||||||
expect(value).toBe(0);
|
expect(value).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 0 for -1 regions", () => {
|
it("should return 0 for -1 regions", () => {
|
||||||
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, -1, false);
|
const value = PricingUtils.computeRUUsagePriceHourly("default", false, 1, -1, false);
|
||||||
expect(value).toBe(0);
|
expect(value).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 0.00008 for default cloud, 1RU, 1 region, multimaster disabled", () => {
|
it("should return 0.00008 for default cloud, rupm disabled, 1RU, 1 region, multimaster disabled", () => {
|
||||||
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 1, false);
|
const value = PricingUtils.computeRUUsagePriceHourly("default", false, 1, 1, false);
|
||||||
expect(value).toBe(0.00008);
|
expect(value).toBe(0.00008);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 0.00051 for Mooncake cloud, 1RU, 1 region, multimaster disabled", () => {
|
it("should return 0.00051 for Mooncake cloud, rupm disabled, 1RU, 1 region, multimaster disabled", () => {
|
||||||
const value = PricingUtils.computeRUUsagePriceHourly("mooncake", 1, 1, false);
|
const value = PricingUtils.computeRUUsagePriceHourly("mooncake", false, 1, 1, false);
|
||||||
expect(value).toBe(0.00051);
|
expect(value).toBe(0.00051);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 0.00016 for default cloud, 1RU, 2 regions, multimaster disabled", () => {
|
it("should return 0.00016 for default cloud, rupm disabled, 1RU, 2 regions, multimaster disabled", () => {
|
||||||
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 2, false);
|
const value = PricingUtils.computeRUUsagePriceHourly("default", false, 1, 2, false);
|
||||||
expect(value).toBe(0.00016);
|
expect(value).toBe(0.00016);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 0.00008 for default cloud, 1RU, 1 region, multimaster enabled", () => {
|
it("should return 0.00008 for default cloud, rupm disabled, 1RU, 1 region, multimaster enabled", () => {
|
||||||
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 1, true);
|
const value = PricingUtils.computeRUUsagePriceHourly("default", false, 1, 1, true);
|
||||||
expect(value).toBe(0.00008);
|
expect(value).toBe(0.00008);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 0.00048 for default cloud, 1RU, 2 region, multimaster enabled", () => {
|
it("should return 0.00048 for default cloud, rupm disabled, 1RU, 2 region, multimaster enabled", () => {
|
||||||
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 2, true);
|
const value = PricingUtils.computeRUUsagePriceHourly("default", false, 1, 2, true);
|
||||||
expect(value).toBe(0.00048);
|
expect(value).toBe(0.00048);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -150,6 +150,18 @@ describe("PricingUtils Tests", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("getPricePerRuPm()", () => {
|
||||||
|
it("should return 0.000027397260273972603 for default clouds", () => {
|
||||||
|
const value = PricingUtils.getPricePerRuPm("default");
|
||||||
|
expect(value).toBe(0.000027397260273972603);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return 0.00027397260273972606 for mooncake", () => {
|
||||||
|
const value = PricingUtils.getPricePerRuPm("mooncake");
|
||||||
|
expect(value).toBe(0.00027397260273972606);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("getRegionMultiplier()", () => {
|
describe("getRegionMultiplier()", () => {
|
||||||
describe("without multimaster", () => {
|
describe("without multimaster", () => {
|
||||||
it("should return 0 for null", () => {
|
it("should return 0 for null", () => {
|
||||||
@@ -242,95 +254,103 @@ describe("PricingUtils Tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("getEstimatedSpendHtml()", () => {
|
describe("getEstimatedSpendHtml()", () => {
|
||||||
it("should return 'Cost (USD): <b>$0.000080 hourly / $0.0019 daily / $0.058 monthly </b> (1 region, 1RU/s, $0.00008/RU)<p style='padding: 10px 0px 0px 0px;'><em>*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account</em></p>' for 1RU/s on default cloud, 1 region, with multimaster", () => {
|
it("should return 'Estimated cost (USD): <b>$0.000080 hourly / $0.0019 daily / $0.058 monthly </b> (1 region, 1RU/s, $0.00008/RU)' for 1RU/s on default cloud, 1 region, with multimaster, and no rupm", () => {
|
||||||
const value = PricingUtils.getEstimatedSpendHtml(
|
const value = PricingUtils.getEstimatedSpendHtml(
|
||||||
1 /*RU/s*/,
|
1 /*RU/s*/,
|
||||||
"default" /* cloud */,
|
"default" /* cloud */,
|
||||||
1 /* region */,
|
1 /* region */,
|
||||||
true /* multimaster */
|
true /* multimaster */,
|
||||||
|
false /* rupm */
|
||||||
);
|
);
|
||||||
expect(value).toBe(
|
expect(value).toBe(
|
||||||
"Cost (USD): <b>$0.000080 hourly / $0.0019 daily / $0.058 monthly </b> (1 region, 1RU/s, $0.00008/RU)<p style='padding: 10px 0px 0px 0px;'><em>*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account</em></p>"
|
"Estimated cost (USD): <b>$0.000080 hourly / $0.0019 daily / $0.058 monthly </b> (1 region, 1RU/s, $0.00008/RU)"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 'Cost (RMB): <b>¥0.00051 hourly / ¥0.012 daily / ¥0.37 monthly </b> (1 region, 1RU/s, ¥0.00051/RU)<p style='padding: 10px 0px 0px 0px;'><em>*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account</em></p>' for 1RU/s on mooncake, 1 region, with multimaster", () => {
|
it("should return 'Estimated cost (RMB): <b>¥0.00051 hourly / ¥0.012 daily / ¥0.37 monthly </b> (1 region, 1RU/s, ¥0.00051/RU)' for 1RU/s on mooncake, 1 region, with multimaster, and no rupm", () => {
|
||||||
const value = PricingUtils.getEstimatedSpendHtml(
|
const value = PricingUtils.getEstimatedSpendHtml(
|
||||||
1 /*RU/s*/,
|
1 /*RU/s*/,
|
||||||
"mooncake" /* cloud */,
|
"mooncake" /* cloud */,
|
||||||
1 /* region */,
|
1 /* region */,
|
||||||
true /* multimaster */
|
true /* multimaster */,
|
||||||
|
false /* rupm */
|
||||||
);
|
);
|
||||||
expect(value).toBe(
|
expect(value).toBe(
|
||||||
"Cost (RMB): <b>¥0.00051 hourly / ¥0.012 daily / ¥0.37 monthly </b> (1 region, 1RU/s, ¥0.00051/RU)<p style='padding: 10px 0px 0px 0px;'><em>*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account</em></p>"
|
"Estimated cost (RMB): <b>¥0.00051 hourly / ¥0.012 daily / ¥0.37 monthly </b> (1 region, 1RU/s, ¥0.00051/RU)"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 'Cost (USD): <b>$0.13 hourly / $3.07 daily / $140.16 monthly </b> (2 regions, 400RU/s, $0.00016/RU)<p style='padding: 10px 0px 0px 0px;'><em>*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account</em></p>' for 400RU/s on default cloud, 2 region, with multimaster", () => {
|
it("should return 'Estimated cost (USD): <b>$0.13 hourly / $3.07 daily / $140.16 monthly </b> (2 regions, 400RU/s, $0.00016/RU)' for 400RU/s on default cloud, 2 region, with multimaster, and no rupm", () => {
|
||||||
const value = PricingUtils.getEstimatedSpendHtml(
|
const value = PricingUtils.getEstimatedSpendHtml(
|
||||||
400 /*RU/s*/,
|
400 /*RU/s*/,
|
||||||
"default" /* cloud */,
|
"default" /* cloud */,
|
||||||
2 /* region */,
|
2 /* region */,
|
||||||
true /* multimaster */
|
true /* multimaster */,
|
||||||
|
false /* rupm */
|
||||||
);
|
);
|
||||||
expect(value).toBe(
|
expect(value).toBe(
|
||||||
"Cost (USD): <b>$0.19 hourly / $4.61 daily / $140.16 monthly </b> (2 regions, 400RU/s, $0.00016/RU)<p style='padding: 10px 0px 0px 0px;'><em>*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account</em></p>"
|
"Estimated cost (USD): <b>$0.19 hourly / $4.61 daily / $140.16 monthly </b> (2 regions, 400RU/s, $0.00016/RU)"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 'Cost (USD): <b>$0.064 hourly / $1.54 daily / $46.72 monthly </b> (2 regions, 400RU/s, $0.00008/RU)<p style='padding: 10px 0px 0px 0px;'><em>*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account</em></p>' for 400RU/s on default cloud, 2 region, without multimaster", () => {
|
it("should return 'Estimated cost (USD): <b>$0.064 hourly / $1.54 daily / $46.72 monthly </b> (2 regions, 400RU/s, $0.00008/RU)' for 400RU/s on default cloud, 2 region, without multimaster, and no rupm", () => {
|
||||||
const value = PricingUtils.getEstimatedSpendHtml(
|
const value = PricingUtils.getEstimatedSpendHtml(
|
||||||
400 /*RU/s*/,
|
400 /*RU/s*/,
|
||||||
"default" /* cloud */,
|
"default" /* cloud */,
|
||||||
2 /* region */,
|
2 /* region */,
|
||||||
false /* multimaster */
|
false /* multimaster */,
|
||||||
|
false /* rupm */
|
||||||
);
|
);
|
||||||
expect(value).toBe(
|
expect(value).toBe(
|
||||||
"Cost (USD): <b>$0.064 hourly / $1.54 daily / $46.72 monthly </b> (2 regions, 400RU/s, $0.00008/RU)<p style='padding: 10px 0px 0px 0px;'><em>*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account</em></p>"
|
"Estimated cost (USD): <b>$0.064 hourly / $1.54 daily / $46.72 monthly </b> (2 regions, 400RU/s, $0.00008/RU)"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getEstimatedSpendAcknowledgeString()", () => {
|
describe("getEstimatedSpendAcknowledgeString()", () => {
|
||||||
it("should return 'I acknowledge the estimated $0.0019 daily cost for the throughput above.' for 1RU/s on default cloud, 1 region, with multimaster", () => {
|
it("should return 'I acknowledge the estimated $0.0019 daily cost for the throughput above.' for 1RU/s on default cloud, 1 region, with multimaster, and no rupm", () => {
|
||||||
const value = PricingUtils.getEstimatedSpendAcknowledgeString(
|
const value = PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||||
1 /*RU/s*/,
|
1 /*RU/s*/,
|
||||||
"default" /* cloud */,
|
"default" /* cloud */,
|
||||||
1 /* region */,
|
1 /* region */,
|
||||||
true /* multimaster */,
|
true /* multimaster */,
|
||||||
|
false /* rupm */,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
expect(value).toBe("I acknowledge the estimated $0.0019 daily cost for the throughput above.");
|
expect(value).toBe("I acknowledge the estimated $0.0019 daily cost for the throughput above.");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 'I acknowledge the estimated ¥0.012 daily cost for the throughput above.' for 1RU/s on mooncake, 1 region, with multimaster", () => {
|
it("should return 'I acknowledge the estimated ¥0.012 daily cost for the throughput above.' for 1RU/s on mooncake, 1 region, with multimaster, and no rupm", () => {
|
||||||
const value = PricingUtils.getEstimatedSpendAcknowledgeString(
|
const value = PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||||
1 /*RU/s*/,
|
1 /*RU/s*/,
|
||||||
"mooncake" /* cloud */,
|
"mooncake" /* cloud */,
|
||||||
1 /* region */,
|
1 /* region */,
|
||||||
true /* multimaster */,
|
true /* multimaster */,
|
||||||
|
false /* rupm */,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
expect(value).toBe("I acknowledge the estimated ¥0.012 daily cost for the throughput above.");
|
expect(value).toBe("I acknowledge the estimated ¥0.012 daily cost for the throughput above.");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 'I acknowledge the estimated $3.07 daily cost for the throughput above.' for 400RU/s on default cloud, 2 region, with multimaster", () => {
|
it("should return 'I acknowledge the estimated $3.07 daily cost for the throughput above.' for 400RU/s on default cloud, 2 region, with multimaster, and no rupm", () => {
|
||||||
const value = PricingUtils.getEstimatedSpendAcknowledgeString(
|
const value = PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||||
400 /*RU/s*/,
|
400 /*RU/s*/,
|
||||||
"default" /* cloud */,
|
"default" /* cloud */,
|
||||||
2 /* region */,
|
2 /* region */,
|
||||||
true /* multimaster */,
|
true /* multimaster */,
|
||||||
|
false /* rupm */,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
expect(value).toBe("I acknowledge the estimated $4.61 daily cost for the throughput above.");
|
expect(value).toBe("I acknowledge the estimated $4.61 daily cost for the throughput above.");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 'I acknowledge the estimated $1.54 daily cost for the throughput above.' for 400RU/s on default cloud, 2 region, without multimaster", () => {
|
it("should return 'I acknowledge the estimated $1.54 daily cost for the throughput above.' for 400RU/s on default cloud, 2 region, without multimaster, and no rupm", () => {
|
||||||
const value = PricingUtils.getEstimatedSpendAcknowledgeString(
|
const value = PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||||
400 /*RU/s*/,
|
400 /*RU/s*/,
|
||||||
"default" /* cloud */,
|
"default" /* cloud */,
|
||||||
2 /* region */,
|
2 /* region */,
|
||||||
false /* multimaster */,
|
false /* multimaster */,
|
||||||
|
false /* rupm */,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
expect(value).toBe("I acknowledge the estimated $1.54 daily cost for the throughput above.");
|
expect(value).toBe("I acknowledge the estimated $1.54 daily cost for the throughput above.");
|
||||||
|
|||||||
@@ -49,16 +49,21 @@ export function getMultimasterMultiplier(numberOfRegions: number, multimasterEna
|
|||||||
|
|
||||||
export function computeRUUsagePriceHourly(
|
export function computeRUUsagePriceHourly(
|
||||||
serverId: string,
|
serverId: string,
|
||||||
|
rupmEnabled: boolean,
|
||||||
requestUnits: number,
|
requestUnits: number,
|
||||||
numberOfRegions: number,
|
numberOfRegions: number,
|
||||||
multimasterEnabled: boolean
|
multimasterEnabled: boolean
|
||||||
): number {
|
): number {
|
||||||
const regionMultiplier: number = getRegionMultiplier(numberOfRegions, multimasterEnabled);
|
const regionMultiplier: number = getRegionMultiplier(numberOfRegions, multimasterEnabled);
|
||||||
const multimasterMultiplier: number = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
|
const multimasterMultiplier: number = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
|
||||||
const pricePerRu = getPricePerRu(serverId);
|
|
||||||
const ruCharge = requestUnits * pricePerRu * multimasterMultiplier * regionMultiplier;
|
|
||||||
|
|
||||||
return Number(ruCharge.toFixed(5));
|
const pricePerRu = getPricePerRu(serverId);
|
||||||
|
const pricePerRuPm = getPricePerRuPm(serverId);
|
||||||
|
|
||||||
|
const ruCharge = requestUnits * pricePerRu * multimasterMultiplier * regionMultiplier;
|
||||||
|
const rupmCharge = rupmEnabled ? requestUnits * pricePerRuPm : 0;
|
||||||
|
|
||||||
|
return Number((ruCharge + rupmCharge).toFixed(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPriceCurrency(serverId: string): string {
|
export function getPriceCurrency(serverId: string): string {
|
||||||
@@ -144,6 +149,14 @@ export function getPricePerRu(serverId: string): number {
|
|||||||
return Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRU;
|
return Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRU;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPricePerRuPm(serverId: string): number {
|
||||||
|
if (serverId === "mooncake") {
|
||||||
|
return Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerRUPM;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRUPM;
|
||||||
|
}
|
||||||
|
|
||||||
export function getAutoPilotV3SpendHtml(maxAutoPilotThroughputSet: number, isDatabaseThroughput: boolean): string {
|
export function getAutoPilotV3SpendHtml(maxAutoPilotThroughputSet: number, isDatabaseThroughput: boolean): string {
|
||||||
if (!maxAutoPilotThroughputSet) {
|
if (!maxAutoPilotThroughputSet) {
|
||||||
return "";
|
return "";
|
||||||
@@ -201,9 +214,10 @@ export function getEstimatedSpendHtml(
|
|||||||
throughput: number,
|
throughput: number,
|
||||||
serverId: string,
|
serverId: string,
|
||||||
regions: number,
|
regions: number,
|
||||||
multimaster: boolean
|
multimaster: boolean,
|
||||||
|
rupmEnabled: boolean
|
||||||
): string {
|
): string {
|
||||||
const hourlyPrice: number = computeRUUsagePriceHourly(serverId, throughput, regions, multimaster);
|
const hourlyPrice: number = computeRUUsagePriceHourly(serverId, rupmEnabled, throughput, regions, multimaster);
|
||||||
const dailyPrice: number = hourlyPrice * 24;
|
const dailyPrice: number = hourlyPrice * 24;
|
||||||
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
|
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
|
||||||
const currency: string = getPriceCurrency(serverId);
|
const currency: string = getPriceCurrency(serverId);
|
||||||
@@ -211,13 +225,11 @@ export function getEstimatedSpendHtml(
|
|||||||
const pricePerRu = getPricePerRu(serverId) * getMultimasterMultiplier(regions, multimaster);
|
const pricePerRu = getPricePerRu(serverId) * getMultimasterMultiplier(regions, multimaster);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
`Cost (${currency}): <b>` +
|
`Estimated cost (${currency}): <b>` +
|
||||||
`${currencySign}${calculateEstimateNumber(hourlyPrice)} hourly / ` +
|
`${currencySign}${calculateEstimateNumber(hourlyPrice)} hourly / ` +
|
||||||
`${currencySign}${calculateEstimateNumber(dailyPrice)} daily / ` +
|
`${currencySign}${calculateEstimateNumber(dailyPrice)} daily / ` +
|
||||||
`${currencySign}${calculateEstimateNumber(monthlyPrice)} monthly </b> ` +
|
`${currencySign}${calculateEstimateNumber(monthlyPrice)} monthly </b> ` +
|
||||||
`(${regions} ${regions === 1 ? "region" : "regions"}, ${throughput}RU/s, ${currencySign}${pricePerRu}/RU)` +
|
`(${regions} ${regions === 1 ? "region" : "regions"}, ${throughput}RU/s, ${currencySign}${pricePerRu}/RU)`
|
||||||
`<p style='padding: 10px 0px 0px 0px;'>` +
|
|
||||||
`<em>*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account</em></p>`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,11 +238,12 @@ export function getEstimatedSpendAcknowledgeString(
|
|||||||
serverId: string,
|
serverId: string,
|
||||||
regions: number,
|
regions: number,
|
||||||
multimaster: boolean,
|
multimaster: boolean,
|
||||||
|
rupmEnabled: boolean,
|
||||||
isAutoscale: boolean
|
isAutoscale: boolean
|
||||||
): string {
|
): string {
|
||||||
const hourlyPrice: number = isAutoscale
|
const hourlyPrice: number = isAutoscale
|
||||||
? computeAutoscaleUsagePriceHourly(serverId, throughput, regions, multimaster)
|
? computeAutoscaleUsagePriceHourly(serverId, throughput, regions, multimaster)
|
||||||
: computeRUUsagePriceHourly(serverId, throughput, regions, multimaster);
|
: computeRUUsagePriceHourly(serverId, rupmEnabled, throughput, regions, multimaster);
|
||||||
const dailyPrice: number = hourlyPrice * 24;
|
const dailyPrice: number = hourlyPrice * 24;
|
||||||
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
|
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
|
||||||
const currencySign: string = getCurrencySign(serverId);
|
const currencySign: string = getCurrencySign(serverId);
|
||||||
|
|||||||
@@ -33,36 +33,18 @@ export class ARMError extends Error {
|
|||||||
public code: string | number;
|
public code: string | number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ARMQueryParams {
|
|
||||||
filter?: string;
|
|
||||||
metricNames?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Options {
|
interface Options {
|
||||||
host: string;
|
host: string;
|
||||||
path: string;
|
path: string;
|
||||||
apiVersion: string;
|
apiVersion: string;
|
||||||
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD";
|
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD";
|
||||||
body?: unknown;
|
body?: unknown;
|
||||||
queryParams?: ARMQueryParams;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This is very similar to what is happening in ResourceProviderClient.ts. Should probably merge them.
|
// TODO: This is very similar to what is happening in ResourceProviderClient.ts. Should probably merge them.
|
||||||
export async function armRequest<T>({
|
export async function armRequest<T>({ host, path, apiVersion, method, body: requestBody }: Options): Promise<T> {
|
||||||
host,
|
|
||||||
path,
|
|
||||||
apiVersion,
|
|
||||||
method,
|
|
||||||
body: requestBody,
|
|
||||||
queryParams
|
|
||||||
}: Options): Promise<T> {
|
|
||||||
const url = new URL(path, host);
|
const url = new URL(path, host);
|
||||||
url.searchParams.append("api-version", configContext.armAPIVersion || apiVersion);
|
url.searchParams.append("api-version", configContext.armAPIVersion || apiVersion);
|
||||||
if (queryParams) {
|
|
||||||
queryParams.filter && url.searchParams.append("$filter", queryParams.filter);
|
|
||||||
queryParams.metricNames && url.searchParams.append("metricnames", queryParams.metricNames);
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await window.fetch(url.href, {
|
const response = await window.fetch(url.href, {
|
||||||
method,
|
method,
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ module.exports = function(env = {}, argv = {}) {
|
|||||||
const rules = [fontRule, lessRule, imagesRule, cssRule, htmlRule, typescriptRule];
|
const rules = [fontRule, lessRule, imagesRule, cssRule, htmlRule, typescriptRule];
|
||||||
const envVars = {
|
const envVars = {
|
||||||
GIT_SHA: gitSha,
|
GIT_SHA: gitSha,
|
||||||
PORT: process.env.PORT || "1234"
|
PORT: process.env.PORT || "2223"
|
||||||
};
|
};
|
||||||
|
|
||||||
if (mode === "production") {
|
if (mode === "production") {
|
||||||
|
|||||||
Reference in New Issue
Block a user