mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-23 10:51:30 +00:00
Compare commits
9 Commits
hosted-msa
...
e2e-test-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4be1389705 | ||
|
|
65844414dd | ||
|
|
b9461de695 | ||
|
|
0473c49cc6 | ||
|
|
53ea8dc528 | ||
|
|
8b7d43823a | ||
|
|
3ab7e93bab | ||
|
|
9e2efa01e5 | ||
|
|
ba5ab37bac |
@@ -42,6 +42,7 @@ src/Contracts/ViewModels.ts
|
||||
src/Controls/Heatmap/Heatmap.test.ts
|
||||
src/Controls/Heatmap/Heatmap.ts
|
||||
src/Controls/Heatmap/HeatmapDatatypes.ts
|
||||
src/Definitions/adal.d.ts
|
||||
src/Definitions/datatables.d.ts
|
||||
src/Definitions/gif.d.ts
|
||||
src/Definitions/globals.d.ts
|
||||
|
||||
29
.github/workflows/ci.yml
vendored
29
.github/workflows/ci.yml
vendored
@@ -167,7 +167,7 @@ jobs:
|
||||
nuget:
|
||||
name: Publish Nuget
|
||||
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
||||
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted, accessibility]
|
||||
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted]
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
||||
@@ -191,7 +191,7 @@ jobs:
|
||||
nugetmpac:
|
||||
name: Publish Nuget MPAC
|
||||
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
||||
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted, accessibility]
|
||||
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted]
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
||||
@@ -213,28 +213,3 @@ jobs:
|
||||
name: packages
|
||||
with:
|
||||
path: "*.nupkg"
|
||||
nugetie:
|
||||
name: Publish Nuget IE
|
||||
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
||||
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted, accessibility]
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
||||
AZURE_DEVOPS_PAT: ${{ secrets.AZURE_DEVOPS_PAT }}
|
||||
steps:
|
||||
- uses: nuget/setup-nuget@v1
|
||||
with:
|
||||
nuget-api-key: ${{ secrets.NUGET_API_KEY }}
|
||||
- name: Download Dist Folder
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: dist
|
||||
- run: cp ./configs/prod.json config.json
|
||||
- run: sed -i 's/Azure.Cosmos.DB.Data.Explorer/Azure.Cosmos.DB.Data.Explorer.IE/g' DataExplorer.nuspec
|
||||
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "GitHub" -Password "$AZURE_DEVOPS_PAT"
|
||||
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
|
||||
- run: nuget push -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
|
||||
- uses: actions/upload-artifact@v2
|
||||
name: packages
|
||||
with:
|
||||
path: "*.nupkg"
|
||||
|
||||
BIN
.vs/slnx.sqlite
BIN
.vs/slnx.sqlite
Binary file not shown.
1963
externals/adal.js
vendored
Normal file
1963
externals/adal.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
27
package-lock.json
generated
27
package-lock.json
generated
@@ -275,27 +275,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@azure/msal-browser": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.8.0.tgz",
|
||||
"integrity": "sha512-I6n7EQnwsZXgKPOLlS5X48jhzUNUFwMVm180wDBA/pwEkUy8ei6zWiPMBfWaMSxz9uNx9WHaEhgAyhJy0ze3AQ==",
|
||||
"requires": {
|
||||
"@azure/msal-common": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@azure/msal-common": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-2.0.0.tgz",
|
||||
"integrity": "sha512-d1RNcJb+P1EGzMHtgbZoVlHLQWjlVfr504jywNk9YEfoq8Hw3BxJ0wepu+1w0hc64D8zG0wljcvHaIH1jTn2SA==",
|
||||
"requires": {
|
||||
"debug": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"@azure/msal-react": {
|
||||
"version": "1.0.0-alpha.1",
|
||||
"resolved": "https://registry.npmjs.org/@azure/msal-react/-/msal-react-1.0.0-alpha.1.tgz",
|
||||
"integrity": "sha512-8BftMP1DyXf7/Fa7mxi14/fmHBdDGDUONmE8sm1T6w7ERJyY1RN7PZgdnUOcYcqj2xMnxfz9++8HsrzMrtMc0Q=="
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
|
||||
@@ -5469,6 +5448,12 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz",
|
||||
"integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA=="
|
||||
},
|
||||
"adal-angular": {
|
||||
"version": "1.0.15",
|
||||
"resolved": "https://registry.npmjs.org/adal-angular/-/adal-angular-1.0.15.tgz",
|
||||
"integrity": "sha1-8qnvgvNYxEToMUKs5l0yJ6RBBDs=",
|
||||
"dev": true
|
||||
},
|
||||
"agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
|
||||
@@ -6,10 +6,8 @@
|
||||
"dependencies": {
|
||||
"@azure/arm-cosmosdb": "9.1.0",
|
||||
"@azure/cosmos": "3.9.0",
|
||||
"@azure/cosmos-language-service": "0.0.5",
|
||||
"@azure/identity": "1.1.0",
|
||||
"@azure/msal-browser": "2.8.0",
|
||||
"@azure/msal-react": "1.0.0-alpha.1",
|
||||
"@azure/cosmos-language-service": "0.0.5",
|
||||
"@jupyterlab/services": "6.0.0-rc.2",
|
||||
"@jupyterlab/terminal": "3.0.0-rc.2",
|
||||
"@microsoft/applicationinsights-web": "2.5.9",
|
||||
@@ -130,6 +128,7 @@
|
||||
"@types/webfontloader": "1.6.29",
|
||||
"@typescript-eslint/eslint-plugin": "4.0.1",
|
||||
"@typescript-eslint/parser": "4.0.1",
|
||||
"adal-angular": "1.0.15",
|
||||
"axe-puppeteer": "1.1.0",
|
||||
"babel-jest": "24.9.0",
|
||||
"babel-loader": "8.1.0",
|
||||
|
||||
@@ -362,7 +362,7 @@ export enum CollectionTabKind {
|
||||
Gallery = 17,
|
||||
NotebookViewer = 18,
|
||||
Schema = 19,
|
||||
SettingsV2 = 20
|
||||
SettingsV2 = 19
|
||||
}
|
||||
|
||||
export enum TerminalKind {
|
||||
|
||||
383
src/Definitions/adal.d.ts
vendored
Normal file
383
src/Definitions/adal.d.ts
vendored
Normal file
@@ -0,0 +1,383 @@
|
||||
// Type definitions for adal-angular 1.0.1.1
|
||||
// Project: https://github.com/AzureAD/azure-activedirectory-library-for-js#readme
|
||||
// Definitions by: Daniel Perez Alvarez <https://github.com/unindented>
|
||||
// Anthony Ciccarello <https://github.com/aciccarello>
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
|
||||
//This is a customized version of adal on top of version 1.0.1 which does not support multi tenant
|
||||
// Customized version add tenantId to stored tokens so when tenant change, adal will refetch instead of read from sessionStorage
|
||||
|
||||
// In module contexts the class constructor function is the exported object
|
||||
// export = AuthenticationContext;
|
||||
|
||||
// This class is defined globally in not in a module context
|
||||
declare class AuthenticationContext {
|
||||
instance: string;
|
||||
config: AuthenticationContext.Options;
|
||||
callback: AuthenticationContext.TokenCallback;
|
||||
popUp: boolean;
|
||||
isAngular: boolean;
|
||||
|
||||
/**
|
||||
* Enum for request type
|
||||
*/
|
||||
REQUEST_TYPE: AuthenticationContext.RequestType;
|
||||
RESPONSE_TYPE: AuthenticationContext.ResponseType;
|
||||
CONSTANTS: AuthenticationContext.Constants;
|
||||
|
||||
constructor(options: AuthenticationContext.Options);
|
||||
/**
|
||||
* Initiates the login process by redirecting the user to Azure AD authorization endpoint.
|
||||
*/
|
||||
login(): void;
|
||||
/**
|
||||
* Returns whether a login is in progress.
|
||||
*/
|
||||
loginInProgress(): boolean;
|
||||
/**
|
||||
* Gets token for the specified resource from the cache.
|
||||
* @param resource A URI that identifies the resource for which the token is requested.
|
||||
* @param tenantId tenant Id.
|
||||
*/
|
||||
getCachedToken(resource: string, tenantId: string): string;
|
||||
/**
|
||||
* If user object exists, returns it. Else creates a new user object by decoding `id_token` from the cache.
|
||||
*/
|
||||
getCachedUser(): AuthenticationContext.UserInfo;
|
||||
/**
|
||||
* Adds the passed callback to the array of callbacks for the specified resource.
|
||||
* @param resource A URI that identifies the resource for which the token is requested.
|
||||
* @param expectedState A unique identifier (guid).
|
||||
* @param callback The callback provided by the caller. It will be called with token or error.
|
||||
*/
|
||||
registerCallback(
|
||||
expectedState: string,
|
||||
resource: string,
|
||||
callback: AuthenticationContext.TokenCallback,
|
||||
tenantId: string
|
||||
): void;
|
||||
/**
|
||||
* Acquires token from the cache if it is not expired. Otherwise sends request to AAD to obtain a new token.
|
||||
* @param resource Resource URI identifying the target resource.
|
||||
* @param callback The callback provided by the caller. It will be called with token or error.
|
||||
*/
|
||||
acquireToken(resource: string, tenantId: string, callback: AuthenticationContext.TokenCallback): void;
|
||||
/**
|
||||
* Acquires token (interactive flow using a popup window) by sending request to AAD to obtain a new token.
|
||||
* @param resource Resource URI identifying the target resource.
|
||||
* @param extraQueryParameters Query parameters to add to the authentication request.
|
||||
* @param claims Claims to add to the authentication request.
|
||||
* @param callback The callback provided by the caller. It will be called with token or error.
|
||||
*/
|
||||
acquireTokenPopup(
|
||||
resource: string,
|
||||
tenantId: string,
|
||||
extraQueryParameters: string | null | undefined,
|
||||
claims: string | null | undefined,
|
||||
callback: AuthenticationContext.TokenCallback
|
||||
): void;
|
||||
/**
|
||||
* Acquires token (interactive flow using a redirect) by sending request to AAD to obtain a new token. In this case the callback passed in the authentication request constructor will be called.
|
||||
* @param resource Resource URI identifying the target resource.
|
||||
* @param extraQueryParameters Query parameters to add to the authentication request.
|
||||
* @param claims Claims to add to the authentication request.
|
||||
*/
|
||||
acquireTokenRedirect(
|
||||
resource: string,
|
||||
tenantId: string,
|
||||
extraQueryParameters?: string | null,
|
||||
claims?: string | null
|
||||
): void;
|
||||
/**
|
||||
* Redirects the browser to Azure AD authorization endpoint.
|
||||
* @param urlNavigate URL of the authorization endpoint.
|
||||
*/
|
||||
promptUser(urlNavigate: string): void;
|
||||
/**
|
||||
* Clears cache items.
|
||||
*/
|
||||
clearCache(): void;
|
||||
/**
|
||||
* Clears cache items for a given resource.
|
||||
* @param resource Resource URI identifying the target resource.
|
||||
*/
|
||||
clearCacheForResource(resource: string): void;
|
||||
/**
|
||||
* Redirects user to logout endpoint. After logout, it will redirect to `postLogoutRedirectUri` if added as a property on the config object.
|
||||
*/
|
||||
logOut(): void;
|
||||
/**
|
||||
* Calls the passed in callback with the user object or error message related to the user.
|
||||
* @param callback The callback provided by the caller. It will be called with user or error.
|
||||
*/
|
||||
getUser(callback: AuthenticationContext.UserCallback): void;
|
||||
/**
|
||||
* Checks if the URL fragment contains access token, id token or error description.
|
||||
* @param hash Hash passed from redirect page.
|
||||
*/
|
||||
isCallback(hash: string): boolean;
|
||||
/**
|
||||
* Gets login error.
|
||||
*/
|
||||
getLoginError(): string;
|
||||
/**
|
||||
* Creates a request info object from the URL fragment and returns it.
|
||||
*/
|
||||
getRequestInfo(hash: string): AuthenticationContext.RequestInfo;
|
||||
/**
|
||||
* Saves token or error received in the response from AAD in the cache. In case of `id_token`, it also creates the user object.
|
||||
*/
|
||||
saveTokenFromHash(requestInfo: AuthenticationContext.RequestInfo): void;
|
||||
/**
|
||||
* Gets resource for given endpoint if mapping is provided with config.
|
||||
* @param endpoint Resource URI identifying the target resource.
|
||||
*/
|
||||
getResourceForEndpoint(resource: string): string;
|
||||
/**
|
||||
* This method must be called for processing the response received from AAD. It extracts the hash, processes the token or error, saves it in the cache and calls the callbacks with the result.
|
||||
* @param hash Hash fragment of URL. Defaults to `window.location.hash`.
|
||||
*/
|
||||
handleWindowCallback(hash?: string): void;
|
||||
|
||||
/**
|
||||
* Checks the logging Level, constructs the log message and logs it. Users need to implement/override this method to turn on logging.
|
||||
* @param level Level can be set 0, 1, 2 and 3 which turns on 'error', 'warning', 'info' or 'verbose' level logging respectively.
|
||||
* @param message Message to log.
|
||||
* @param error Error to log.
|
||||
*/
|
||||
log(level: AuthenticationContext.LoggingLevel, message: string, error: any): void;
|
||||
/**
|
||||
* Logs messages when logging level is set to 0.
|
||||
* @param message Message to log.
|
||||
* @param error Error to log.
|
||||
*/
|
||||
error(message: string, error: any): void;
|
||||
/**
|
||||
* Logs messages when logging level is set to 1.
|
||||
* @param message Message to log.
|
||||
*/
|
||||
warn(message: string): void;
|
||||
/**
|
||||
* Logs messages when logging level is set to 2.
|
||||
* @param message Message to log.
|
||||
*/
|
||||
info(message: string): void;
|
||||
/**
|
||||
* Logs messages when logging level is set to 3.
|
||||
* @param message Message to log.
|
||||
*/
|
||||
verbose(message: string): void;
|
||||
|
||||
/**
|
||||
* Logs Pii messages when Logging Level is set to 0 and window.piiLoggingEnabled is set to true.
|
||||
* @param message Message to log.
|
||||
* @param error Error to log.
|
||||
*/
|
||||
errorPii(message: string, error: any): void;
|
||||
|
||||
/**
|
||||
* Logs Pii messages when Logging Level is set to 1 and window.piiLoggingEnabled is set to true.
|
||||
* @param message Message to log.
|
||||
*/
|
||||
warnPii(message: string): void;
|
||||
|
||||
/**
|
||||
* Logs messages when Logging Level is set to 2 and window.piiLoggingEnabled is set to true.
|
||||
* @param message Message to log.
|
||||
*/
|
||||
infoPii(message: string): void;
|
||||
|
||||
/**
|
||||
* Logs messages when Logging Level is set to 3 and window.piiLoggingEnabled is set to true.
|
||||
* @param message Message to log.
|
||||
*/
|
||||
verbosePii(message: string): void;
|
||||
}
|
||||
|
||||
declare namespace AuthenticationContext {
|
||||
function inject(config: Options): AuthenticationContext;
|
||||
|
||||
type LoggingLevel = 0 | 1 | 2 | 3;
|
||||
|
||||
type RequestType = "LOGIN" | "RENEW_TOKEN" | "UNKNOWN";
|
||||
|
||||
type ResponseType = "id_token token" | "token";
|
||||
|
||||
interface RequestInfo {
|
||||
/**
|
||||
* Object comprising of fields such as id_token/error, session_state, state, e.t.c.
|
||||
*/
|
||||
parameters: any;
|
||||
/**
|
||||
* Request type.
|
||||
*/
|
||||
requestType: RequestType;
|
||||
/**
|
||||
* Whether state is valid.
|
||||
*/
|
||||
stateMatch: boolean;
|
||||
/**
|
||||
* Unique guid used to match the response with the request.
|
||||
*/
|
||||
stateResponse: string;
|
||||
/**
|
||||
* Whether `requestType` contains `id_token`, `access_token` or error.
|
||||
*/
|
||||
valid: boolean;
|
||||
}
|
||||
|
||||
interface UserInfo {
|
||||
/**
|
||||
* Username assigned from UPN or email.
|
||||
*/
|
||||
userName: string;
|
||||
/**
|
||||
* Properties parsed from `id_token`.
|
||||
*/
|
||||
profile: any;
|
||||
}
|
||||
|
||||
type TokenCallback = (errorDesc: string | null, token: string | null, error: any) => void;
|
||||
|
||||
type UserCallback = (errorDesc: string | null, user: UserInfo | null) => void;
|
||||
|
||||
/**
|
||||
* Configuration options for Authentication Context
|
||||
*/
|
||||
interface Options {
|
||||
/**
|
||||
* Client ID assigned to your app by Azure Active Directory.
|
||||
*/
|
||||
clientId: string;
|
||||
/**
|
||||
* Endpoint at which you expect to receive tokens.Defaults to `window.location.href`.
|
||||
*/
|
||||
redirectUri?: string;
|
||||
/**
|
||||
* Azure Active Directory instance. Defaults to `https://login.microsoftonline.com/`.
|
||||
*/
|
||||
instance?: string;
|
||||
/**
|
||||
* Your target tenant. Defaults to `common`.
|
||||
*/
|
||||
tenant?: string;
|
||||
/**
|
||||
* Query parameters to add to the authentication request.
|
||||
*/
|
||||
extraQueryParameter?: string;
|
||||
/**
|
||||
* Unique identifier used to map the request with the response. Defaults to RFC4122 version 4 guid (128 bits).
|
||||
*/
|
||||
correlationId?: string;
|
||||
/**
|
||||
* User defined function of handling the navigation to Azure AD authorization endpoint in case of login.
|
||||
*/
|
||||
displayCall?: (url: string) => void;
|
||||
/**
|
||||
* Set this to true to enable login in a popup winodow instead of a full redirect. Defaults to `false`.
|
||||
*/
|
||||
popUp?: boolean;
|
||||
/**
|
||||
* Set this to the resource to request on login. Defaults to `clientId`.
|
||||
*/
|
||||
loginResource?: string;
|
||||
/**
|
||||
* Set this to redirect the user to a custom login page.
|
||||
*/
|
||||
localLoginUrl?: string;
|
||||
/**
|
||||
* Redirects to start page after login. Defaults to `true`.
|
||||
*/
|
||||
navigateToLoginRequestUrl?: boolean;
|
||||
/**
|
||||
* Set this to redirect the user to a custom logout page.
|
||||
*/
|
||||
logOutUri?: string;
|
||||
/**
|
||||
* Redirects the user to postLogoutRedirectUri after logout. Defaults to `redirectUri`.
|
||||
*/
|
||||
postLogoutRedirectUri?: string;
|
||||
/**
|
||||
* Sets browser storage to either 'localStorage' or sessionStorage'. Defaults to `sessionStorage`.
|
||||
*/
|
||||
cacheLocation?: "localStorage" | "sessionStorage";
|
||||
/**
|
||||
* Array of keywords or URIs. Adal will attach a token to outgoing requests that have these keywords or URIs.
|
||||
*/
|
||||
endpoints?: { [resource: string]: string };
|
||||
/**
|
||||
* Array of keywords or URIs. Adal will not attach a token to outgoing requests that have these keywords or URIs.
|
||||
*/
|
||||
anonymousEndpoints?: string[];
|
||||
/**
|
||||
* If the cached token is about to be expired in the expireOffsetSeconds (in seconds), Adal will renew the token instead of using the cached token. Defaults to 300 seconds.
|
||||
*/
|
||||
expireOffsetSeconds?: number;
|
||||
/**
|
||||
* The number of milliseconds of inactivity before a token renewal response from AAD should be considered timed out. Defaults to 6 seconds.
|
||||
*/
|
||||
loadFrameTimeout?: number;
|
||||
/**
|
||||
* Callback to be invoked when a token is acquired.
|
||||
*/
|
||||
callback?: TokenCallback;
|
||||
}
|
||||
|
||||
interface LoggingConfig {
|
||||
level: LoggingLevel;
|
||||
log: (message: string) => void;
|
||||
piiLoggingEnabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum for storage constants
|
||||
*/
|
||||
interface Constants {
|
||||
ACCESS_TOKEN: "access_token";
|
||||
EXPIRES_IN: "expires_in";
|
||||
ID_TOKEN: "id_token";
|
||||
ERROR_DESCRIPTION: "error_description";
|
||||
SESSION_STATE: "session_state";
|
||||
STORAGE: {
|
||||
TOKEN_KEYS: "adal.token.keys";
|
||||
ACCESS_TOKEN_KEY: "adal.access.token.key";
|
||||
EXPIRATION_KEY: "adal.expiration.key";
|
||||
STATE_LOGIN: "adal.state.login";
|
||||
STATE_RENEW: "adal.state.renew";
|
||||
NONCE_IDTOKEN: "adal.nonce.idtoken";
|
||||
SESSION_STATE: "adal.session.state";
|
||||
USERNAME: "adal.username";
|
||||
IDTOKEN: "adal.idtoken";
|
||||
ERROR: "adal.error";
|
||||
ERROR_DESCRIPTION: "adal.error.description";
|
||||
LOGIN_REQUEST: "adal.login.request";
|
||||
LOGIN_ERROR: "adal.login.error";
|
||||
RENEW_STATUS: "adal.token.renew.status";
|
||||
};
|
||||
RESOURCE_DELIMETER: "|";
|
||||
LOADFRAME_TIMEOUT: "6000";
|
||||
TOKEN_RENEW_STATUS_CANCELED: "Canceled";
|
||||
TOKEN_RENEW_STATUS_COMPLETED: "Completed";
|
||||
TOKEN_RENEW_STATUS_IN_PROGRESS: "In Progress";
|
||||
LOGGING_LEVEL: {
|
||||
ERROR: 0;
|
||||
WARN: 1;
|
||||
INFO: 2;
|
||||
VERBOSE: 3;
|
||||
};
|
||||
LEVEL_STRING_MAP: {
|
||||
0: "ERROR:";
|
||||
1: "WARNING:";
|
||||
2: "INFO:";
|
||||
3: "VERBOSE:";
|
||||
};
|
||||
POPUP_WIDTH: 483;
|
||||
POPUP_HEIGHT: 600;
|
||||
}
|
||||
}
|
||||
|
||||
// declare global {
|
||||
// interface Window {
|
||||
// Logging: AuthenticationContext.LoggingConfig;
|
||||
// }
|
||||
// }
|
||||
@@ -38,7 +38,7 @@ export interface CommandButtonComponentProps {
|
||||
/**
|
||||
* Label for the button
|
||||
*/
|
||||
commandButtonLabel?: string;
|
||||
commandButtonLabel: string;
|
||||
|
||||
/**
|
||||
* True if this button opens a tab or pane, false otherwise.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { IColumn, Text } from "office-ui-fabric-react";
|
||||
import {
|
||||
getAutoPilotV3SpendElement,
|
||||
getEstimatedSpendingElement,
|
||||
getEstimatedSpendElement,
|
||||
getEstimatedAutoscaleSpendElement,
|
||||
manualToAutoscaleDisclaimerElement,
|
||||
ttlWarning,
|
||||
indexingPolicynUnsavedWarningMessage,
|
||||
@@ -19,37 +19,11 @@ import {
|
||||
mongoIndexingPolicyDisclaimer,
|
||||
mongoIndexingPolicyAADError,
|
||||
mongoIndexTransformationRefreshingMessage,
|
||||
renderMongoIndexTransformationRefreshMessage,
|
||||
ManualEstimatedSpendingDisplayProps,
|
||||
PriceBreakdown,
|
||||
getRuPriceBreakdown
|
||||
renderMongoIndexTransformationRefreshMessage
|
||||
} from "./SettingsRenderUtils";
|
||||
|
||||
class SettingsRenderUtilsTestComponent extends React.Component {
|
||||
public render(): JSX.Element {
|
||||
const estimatedSpendingColumns: IColumn[] = [
|
||||
{ key: "costType", name: "", fieldName: "costType", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||
{ key: "hourly", name: "Hourly", fieldName: "hourly", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||
{ key: "daily", name: "Daily", fieldName: "daily", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||
{ key: "monthly", name: "Monthly", fieldName: "monthly", minWidth: 100, maxWidth: 200, isResizable: true }
|
||||
];
|
||||
const estimatedSpendingItems: ManualEstimatedSpendingDisplayProps[] = [
|
||||
{
|
||||
costType: <Text>Current Cost</Text>,
|
||||
hourly: <Text>$ 1.02</Text>,
|
||||
daily: <Text>$ 24.48</Text>,
|
||||
monthly: <Text>$ 744.6</Text>
|
||||
}
|
||||
];
|
||||
const priceBreakdown: PriceBreakdown = {
|
||||
hourlyPrice: 1.02,
|
||||
dailyPrice: 24.48,
|
||||
monthlyPrice: 744.6,
|
||||
pricePerRu: 0.00051,
|
||||
currency: "RMB",
|
||||
currencySign: "¥"
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{getAutoPilotV3SpendElement(1000, false)}
|
||||
@@ -57,7 +31,9 @@ class SettingsRenderUtilsTestComponent extends React.Component {
|
||||
{getAutoPilotV3SpendElement(1000, true)}
|
||||
{getAutoPilotV3SpendElement(undefined, true)}
|
||||
|
||||
{getEstimatedSpendingElement(estimatedSpendingColumns, estimatedSpendingItems, 1000, 2, priceBreakdown, false)}
|
||||
{getEstimatedSpendElement(1000, "mooncake", 2, false)}
|
||||
|
||||
{getEstimatedAutoscaleSpendElement(1000, "mooncake", 2, false)}
|
||||
|
||||
{manualToAutoscaleDisclaimerElement}
|
||||
{ttlWarning}
|
||||
@@ -93,14 +69,4 @@ describe("SettingsUtils functions", () => {
|
||||
const wrapper = shallow(<SettingsRenderUtilsTestComponent />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should return correct price breakdown for a manual RU setting of 500, 1 region, multimaster disabled", () => {
|
||||
const prices = getRuPriceBreakdown(500, "", 1, false, false);
|
||||
expect(prices.hourlyPrice).toBe(0.04);
|
||||
expect(prices.dailyPrice).toBe(0.96);
|
||||
expect(prices.monthlyPrice).toBe(29.2);
|
||||
expect(prices.pricePerRu).toBe(0.00008);
|
||||
expect(prices.currency).toBe("USD");
|
||||
expect(prices.currencySign).toBe("$");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,13 +3,14 @@ import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||
import { AutopilotDocumentation, hoursInAMonth } from "../../../Shared/Constants";
|
||||
import { Urls, StyleConstants } from "../../../Common/Constants";
|
||||
import {
|
||||
computeAutoscaleUsagePriceHourly,
|
||||
getPriceCurrency,
|
||||
getCurrencySign,
|
||||
getAutoscalePricePerRu,
|
||||
getMultimasterMultiplier,
|
||||
computeRUUsagePriceHourly,
|
||||
getPricePerRu,
|
||||
estimatedCostDisclaimer
|
||||
calculateEstimateNumber
|
||||
} from "../../../Utils/PricingUtils";
|
||||
import {
|
||||
ITextFieldStyles,
|
||||
@@ -31,41 +32,10 @@ import {
|
||||
MessageBarType,
|
||||
Stack,
|
||||
Spinner,
|
||||
SpinnerSize,
|
||||
DetailsList,
|
||||
IColumn,
|
||||
SelectionMode,
|
||||
DetailsListLayoutMode,
|
||||
IDetailsRowProps,
|
||||
DetailsRow,
|
||||
IDetailsColumnStyles
|
||||
SpinnerSize
|
||||
} from "office-ui-fabric-react";
|
||||
import { isDirtyTypes, isDirty } from "./SettingsUtils";
|
||||
|
||||
export interface EstimatedSpendingDisplayProps {
|
||||
costType: JSX.Element;
|
||||
}
|
||||
|
||||
export interface ManualEstimatedSpendingDisplayProps extends EstimatedSpendingDisplayProps {
|
||||
hourly: JSX.Element;
|
||||
daily: JSX.Element;
|
||||
monthly: JSX.Element;
|
||||
}
|
||||
|
||||
export interface AutoscaleEstimatedSpendingDisplayProps extends EstimatedSpendingDisplayProps {
|
||||
minPerMonth: JSX.Element;
|
||||
maxPerMonth: JSX.Element;
|
||||
}
|
||||
|
||||
export interface PriceBreakdown {
|
||||
hourlyPrice: number;
|
||||
dailyPrice: number;
|
||||
monthlyPrice: number;
|
||||
pricePerRu: number;
|
||||
currency: string;
|
||||
currencySign: string;
|
||||
}
|
||||
|
||||
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 12 } };
|
||||
|
||||
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
|
||||
@@ -134,16 +104,6 @@ export const transparentDetailsRowStyles: Partial<IDetailsRowStyles> = {
|
||||
}
|
||||
};
|
||||
|
||||
export const transparentDetailsHeaderStyle: Partial<IDetailsColumnStyles> = {
|
||||
root: {
|
||||
selectors: {
|
||||
":hover": {
|
||||
background: "transparent"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const customDetailsListStyles: Partial<IDetailsListStyles> = {
|
||||
root: {
|
||||
selectors: {
|
||||
@@ -170,10 +130,6 @@ export const messageBarStyles: Partial<IMessageBarStyles> = { root: { marginTop:
|
||||
|
||||
export const throughputUnit = "RU/s";
|
||||
|
||||
export function onRenderRow(props: IDetailsRowProps): JSX.Element {
|
||||
return <DetailsRow {...props} styles={transparentDetailsRowStyles} />;
|
||||
}
|
||||
|
||||
export const getAutoPilotV3SpendElement = (
|
||||
maxAutoPilotThroughputSet: number,
|
||||
isDatabaseThroughput: boolean,
|
||||
@@ -209,61 +165,63 @@ export const getAutoPilotV3SpendElement = (
|
||||
);
|
||||
};
|
||||
|
||||
export const getRuPriceBreakdown = (
|
||||
export const getEstimatedAutoscaleSpendElement = (
|
||||
throughput: number,
|
||||
serverId: string,
|
||||
numberOfRegions: number,
|
||||
isMultimaster: boolean,
|
||||
isAutoscale: boolean
|
||||
): PriceBreakdown => {
|
||||
const hourlyPrice: number = computeRUUsagePriceHourly({
|
||||
serverId: serverId,
|
||||
requestUnits: throughput,
|
||||
numberOfRegions: numberOfRegions,
|
||||
multimasterEnabled: isMultimaster,
|
||||
isAutoscale: isAutoscale
|
||||
});
|
||||
const basePricePerRu: number = isAutoscale
|
||||
? getAutoscalePricePerRu(serverId, getMultimasterMultiplier(numberOfRegions, isMultimaster))
|
||||
: getPricePerRu(serverId);
|
||||
return {
|
||||
hourlyPrice: hourlyPrice,
|
||||
dailyPrice: hourlyPrice * 24,
|
||||
monthlyPrice: hourlyPrice * hoursInAMonth,
|
||||
pricePerRu: basePricePerRu * getMultimasterMultiplier(numberOfRegions, isMultimaster),
|
||||
currency: getPriceCurrency(serverId),
|
||||
currencySign: getCurrencySign(serverId)
|
||||
};
|
||||
regions: number,
|
||||
multimaster: boolean
|
||||
): JSX.Element => {
|
||||
const hourlyPrice: number = computeAutoscaleUsagePriceHourly(serverId, throughput, regions, multimaster);
|
||||
const monthlyPrice: number = hourlyPrice * hoursInAMonth;
|
||||
const currency: string = getPriceCurrency(serverId);
|
||||
const currencySign: string = getCurrencySign(serverId);
|
||||
const pricePerRu =
|
||||
getAutoscalePricePerRu(serverId, getMultimasterMultiplier(regions, multimaster)) *
|
||||
getMultimasterMultiplier(regions, multimaster);
|
||||
|
||||
return (
|
||||
<Text id="autoscaleSpendElement">
|
||||
Estimated monthly cost ({currency}) is{" "}
|
||||
<b>
|
||||
{currencySign}
|
||||
{calculateEstimateNumber(monthlyPrice / 10)}
|
||||
{` - `}
|
||||
{currencySign}
|
||||
{calculateEstimateNumber(monthlyPrice)}{" "}
|
||||
</b>
|
||||
({"regions: "} {regions}, {throughput / 10} - {throughput} RU/s, {currencySign}
|
||||
{pricePerRu}/RU)
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
export const getEstimatedSpendingElement = (
|
||||
estimatedSpendingColumns: IColumn[],
|
||||
estimatedSpendingItems: EstimatedSpendingDisplayProps[],
|
||||
export const getEstimatedSpendElement = (
|
||||
throughput: number,
|
||||
numberOfRegions: number,
|
||||
priceBreakdown: PriceBreakdown,
|
||||
isAutoscale: boolean
|
||||
serverId: string,
|
||||
regions: number,
|
||||
multimaster: boolean
|
||||
): JSX.Element => {
|
||||
const ruRange: string = isAutoscale ? throughput / 10 + " RU/s - " : "";
|
||||
const hourlyPrice: number = computeRUUsagePriceHourly(serverId, throughput, regions, multimaster);
|
||||
const dailyPrice: number = hourlyPrice * 24;
|
||||
const monthlyPrice: number = hourlyPrice * hoursInAMonth;
|
||||
const currency: string = getPriceCurrency(serverId);
|
||||
const currencySign: string = getCurrencySign(serverId);
|
||||
const pricePerRu = getPricePerRu(serverId) * getMultimasterMultiplier(regions, multimaster);
|
||||
|
||||
return (
|
||||
<Stack {...addMongoIndexStackProps} styles={mediumWidthStackStyles}>
|
||||
<DetailsList
|
||||
disableSelectionZone
|
||||
items={estimatedSpendingItems}
|
||||
columns={estimatedSpendingColumns}
|
||||
selectionMode={SelectionMode.none}
|
||||
layoutMode={DetailsListLayoutMode.justified}
|
||||
onRenderRow={onRenderRow}
|
||||
/>
|
||||
<Text id="throughputSpendElement">
|
||||
({"regions: "} {numberOfRegions}, {ruRange}
|
||||
{throughput} RU/s, {priceBreakdown.currencySign}
|
||||
{priceBreakdown.pricePerRu}/RU)
|
||||
Estimated cost ({currency}):{" "}
|
||||
<b>
|
||||
{currencySign}
|
||||
{calculateEstimateNumber(hourlyPrice)} hourly {` / `}
|
||||
{currencySign}
|
||||
{calculateEstimateNumber(dailyPrice)} daily {` / `}
|
||||
{currencySign}
|
||||
{calculateEstimateNumber(monthlyPrice)} monthly{" "}
|
||||
</b>
|
||||
({"regions: "} {regions}, {throughput}RU/s, {currencySign}
|
||||
{pricePerRu}/RU)
|
||||
</Text>
|
||||
<Text>
|
||||
<em>{estimatedCostDisclaimer}</em>
|
||||
</Text>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -307,13 +265,6 @@ export const updateThroughputDelayedApplyWarningMessage: JSX.Element = (
|
||||
</Text>
|
||||
);
|
||||
|
||||
export const saveThroughputWarningMessage: JSX.Element = (
|
||||
<Text styles={infoAndToolTipTextStyle}>
|
||||
Your bill will be affected as you update your throughput settings. Please review the updated cost estimate below
|
||||
before saving your changes
|
||||
</Text>
|
||||
);
|
||||
|
||||
const getCurrentThroughput = (
|
||||
isAutoscale: boolean,
|
||||
throughput: number,
|
||||
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
IconButton,
|
||||
Text,
|
||||
SelectionMode,
|
||||
IDetailsRowProps,
|
||||
DetailsRow,
|
||||
IColumn,
|
||||
MessageBar,
|
||||
MessageBarType,
|
||||
@@ -19,11 +21,11 @@ import {
|
||||
mongoIndexingPolicyDisclaimer,
|
||||
mediumWidthStackStyles,
|
||||
subComponentStackProps,
|
||||
transparentDetailsRowStyles,
|
||||
createAndAddMongoIndexStackProps,
|
||||
separatorStyles,
|
||||
indexingPolicynUnsavedWarningMessage,
|
||||
infoAndToolTipTextStyle,
|
||||
onRenderRow
|
||||
infoAndToolTipTextStyle
|
||||
} from "../../SettingsRenderUtils";
|
||||
import { MongoIndex } from "../../../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import {
|
||||
@@ -138,6 +140,10 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
||||
return undefined;
|
||||
};
|
||||
|
||||
private onRenderRow = (props: IDetailsRowProps): JSX.Element => {
|
||||
return <DetailsRow {...props} styles={transparentDetailsRowStyles} />;
|
||||
};
|
||||
|
||||
private getActionButton = (arrayPosition: number, isCurrentIndex: boolean): JSX.Element => {
|
||||
return isCurrentIndex ? (
|
||||
<IconButton
|
||||
@@ -247,7 +253,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
||||
items={initialIndexes}
|
||||
columns={this.initialIndexesColumns}
|
||||
selectionMode={SelectionMode.none}
|
||||
onRenderRow={onRenderRow}
|
||||
onRenderRow={this.onRenderRow}
|
||||
layoutMode={DetailsListLayoutMode.justified}
|
||||
/>
|
||||
{this.renderIndexesToBeAdded()}
|
||||
@@ -273,7 +279,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
||||
items={indexesToBeDropped}
|
||||
columns={this.indexesToBeDroppedColumns}
|
||||
selectionMode={SelectionMode.none}
|
||||
onRenderRow={onRenderRow}
|
||||
onRenderRow={this.onRenderRow}
|
||||
layoutMode={DetailsListLayoutMode.justified}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -54,6 +54,7 @@ describe("ThroughputInputAutoPilotV3Component", () => {
|
||||
expect(wrapper.exists("#throughputInput")).toEqual(true);
|
||||
expect(wrapper.exists("#autopilotInput")).toEqual(false);
|
||||
expect(wrapper.exists("#throughputSpendElement")).toEqual(true);
|
||||
expect(wrapper.exists("#autoscaleSpendElement")).toEqual(false);
|
||||
});
|
||||
|
||||
it("autopilot input visible", () => {
|
||||
@@ -71,7 +72,8 @@ describe("ThroughputInputAutoPilotV3Component", () => {
|
||||
|
||||
wrapper.setProps({ wasAutopilotOriginallySet: true });
|
||||
wrapper.update();
|
||||
expect(wrapper.exists("#throughputSpendElement")).toEqual(true);
|
||||
expect(wrapper.exists("#autoscaleSpendElement")).toEqual(true);
|
||||
expect(wrapper.exists("#throughputSpendElement")).toEqual(false);
|
||||
});
|
||||
|
||||
it("spendAck checkbox visible", () => {
|
||||
|
||||
@@ -8,15 +8,10 @@ import {
|
||||
checkBoxAndInputStackProps,
|
||||
getChoiceGroupStyles,
|
||||
messageBarStyles,
|
||||
getEstimatedSpendingElement,
|
||||
getEstimatedSpendElement,
|
||||
getEstimatedAutoscaleSpendElement,
|
||||
getAutoPilotV3SpendElement,
|
||||
manualToAutoscaleDisclaimerElement,
|
||||
saveThroughputWarningMessage,
|
||||
ManualEstimatedSpendingDisplayProps,
|
||||
AutoscaleEstimatedSpendingDisplayProps,
|
||||
PriceBreakdown,
|
||||
getRuPriceBreakdown,
|
||||
transparentDetailsHeaderStyle
|
||||
manualToAutoscaleDisclaimerElement
|
||||
} from "../../SettingsRenderUtils";
|
||||
import {
|
||||
Text,
|
||||
@@ -28,9 +23,7 @@ import {
|
||||
Label,
|
||||
Link,
|
||||
MessageBar,
|
||||
MessageBarType,
|
||||
FontIcon,
|
||||
IColumn
|
||||
MessageBarType
|
||||
} from "office-ui-fabric-react";
|
||||
import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
|
||||
import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
|
||||
@@ -39,7 +32,7 @@ import * as DataModels from "../../../../../Contracts/DataModels";
|
||||
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
||||
import { userContext } from "../../../../../UserContext";
|
||||
import { SubscriptionType } from "../../../../../Contracts/SubscriptionType";
|
||||
import { usageInGB, calculateEstimateNumber } from "../../../../../Utils/PricingUtils";
|
||||
import { usageInGB } from "../../../../../Utils/PricingUtils";
|
||||
import { Features } from "../../../../../Common/Constants";
|
||||
|
||||
export interface ThroughputInputAutoPilotV3Props {
|
||||
@@ -172,243 +165,33 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const isDirty: boolean = this.IsComponentDirty().isDiscardable;
|
||||
const serverId: string = this.props.serverId;
|
||||
const offerThroughput: number = this.props.throughput;
|
||||
|
||||
const regions = account?.properties?.readLocations?.length || 1;
|
||||
const multimaster = account?.properties?.enableMultipleWriteLocations || false;
|
||||
|
||||
let estimatedSpend: JSX.Element;
|
||||
|
||||
if (!this.props.isAutoPilotSelected) {
|
||||
estimatedSpend = this.getEstimatedManualSpendElement(
|
||||
estimatedSpend = getEstimatedSpendElement(
|
||||
// if migrating from autoscale to manual, we use the autoscale RUs value as that is what will be set...
|
||||
this.overrideWithAutoPilotSettings() ? this.props.maxAutoPilotThroughput : this.props.throughputBaseline,
|
||||
this.overrideWithAutoPilotSettings() ? this.props.maxAutoPilotThroughput : offerThroughput,
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
isDirty ? this.props.throughput : undefined
|
||||
multimaster
|
||||
);
|
||||
} else {
|
||||
estimatedSpend = this.getEstimatedAutoscaleSpendElement(
|
||||
this.props.maxAutoPilotThroughputBaseline,
|
||||
estimatedSpend = getEstimatedAutoscaleSpendElement(
|
||||
this.props.maxAutoPilotThroughput,
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
isDirty ? this.props.maxAutoPilotThroughput : undefined
|
||||
multimaster
|
||||
);
|
||||
}
|
||||
return estimatedSpend;
|
||||
};
|
||||
|
||||
private getEstimatedAutoscaleSpendElement = (
|
||||
throughput: number,
|
||||
serverId: string,
|
||||
numberOfRegions: number,
|
||||
isMultimaster: boolean,
|
||||
newThroughput?: number
|
||||
): JSX.Element => {
|
||||
const prices: PriceBreakdown = getRuPriceBreakdown(throughput, serverId, numberOfRegions, isMultimaster, true);
|
||||
const estimatedSpendingColumns: IColumn[] = [
|
||||
{
|
||||
key: "costType",
|
||||
name: "",
|
||||
fieldName: "costType",
|
||||
minWidth: 100,
|
||||
maxWidth: 200,
|
||||
isResizable: true,
|
||||
styles: transparentDetailsHeaderStyle
|
||||
},
|
||||
{
|
||||
key: "minPerMonth",
|
||||
name: "Min Per Month",
|
||||
fieldName: "minPerMonth",
|
||||
minWidth: 100,
|
||||
maxWidth: 200,
|
||||
isResizable: true,
|
||||
styles: transparentDetailsHeaderStyle
|
||||
},
|
||||
{
|
||||
key: "maxPerMonth",
|
||||
name: "Max Per Month",
|
||||
fieldName: "maxPerMonth",
|
||||
minWidth: 100,
|
||||
maxWidth: 200,
|
||||
isResizable: true,
|
||||
styles: transparentDetailsHeaderStyle
|
||||
}
|
||||
];
|
||||
const estimatedSpendingItems: AutoscaleEstimatedSpendingDisplayProps[] = [
|
||||
{
|
||||
costType: <Text>Current Cost</Text>,
|
||||
minPerMonth: (
|
||||
<Text>
|
||||
{prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice / 10)}
|
||||
</Text>
|
||||
),
|
||||
maxPerMonth: (
|
||||
<Text>
|
||||
{prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice)}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
if (newThroughput) {
|
||||
const newPrices: PriceBreakdown = getRuPriceBreakdown(
|
||||
newThroughput,
|
||||
serverId,
|
||||
numberOfRegions,
|
||||
isMultimaster,
|
||||
true
|
||||
);
|
||||
estimatedSpendingItems.unshift({
|
||||
costType: (
|
||||
<Text>
|
||||
<b>Updated Cost</b>
|
||||
</Text>
|
||||
),
|
||||
minPerMonth: (
|
||||
<Text>
|
||||
<b>
|
||||
{newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice / 10)}
|
||||
</b>
|
||||
</Text>
|
||||
),
|
||||
maxPerMonth: (
|
||||
<Text>
|
||||
<b>
|
||||
{newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice)}
|
||||
</b>
|
||||
</Text>
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
return getEstimatedSpendingElement(
|
||||
estimatedSpendingColumns,
|
||||
estimatedSpendingItems,
|
||||
newThroughput ?? throughput,
|
||||
numberOfRegions,
|
||||
prices,
|
||||
true
|
||||
);
|
||||
};
|
||||
|
||||
private getEstimatedManualSpendElement = (
|
||||
throughput: number,
|
||||
serverId: string,
|
||||
numberOfRegions: number,
|
||||
isMultimaster: boolean,
|
||||
newThroughput?: number
|
||||
): JSX.Element => {
|
||||
const prices: PriceBreakdown = getRuPriceBreakdown(throughput, serverId, numberOfRegions, isMultimaster, false);
|
||||
const estimatedSpendingColumns: IColumn[] = [
|
||||
{
|
||||
key: "costType",
|
||||
name: "",
|
||||
fieldName: "costType",
|
||||
minWidth: 100,
|
||||
maxWidth: 200,
|
||||
isResizable: true,
|
||||
styles: transparentDetailsHeaderStyle
|
||||
},
|
||||
{
|
||||
key: "hourly",
|
||||
name: "Hourly",
|
||||
fieldName: "hourly",
|
||||
minWidth: 100,
|
||||
maxWidth: 200,
|
||||
isResizable: true,
|
||||
styles: transparentDetailsHeaderStyle
|
||||
},
|
||||
{
|
||||
key: "daily",
|
||||
name: "Daily",
|
||||
fieldName: "daily",
|
||||
minWidth: 100,
|
||||
maxWidth: 200,
|
||||
isResizable: true,
|
||||
styles: transparentDetailsHeaderStyle
|
||||
},
|
||||
{
|
||||
key: "monthly",
|
||||
name: "Monthly",
|
||||
fieldName: "monthly",
|
||||
minWidth: 100,
|
||||
maxWidth: 200,
|
||||
isResizable: true,
|
||||
styles: transparentDetailsHeaderStyle
|
||||
}
|
||||
];
|
||||
const estimatedSpendingItems: ManualEstimatedSpendingDisplayProps[] = [
|
||||
{
|
||||
costType: <Text>Current Cost</Text>,
|
||||
hourly: (
|
||||
<Text>
|
||||
{prices.currencySign} {calculateEstimateNumber(prices.hourlyPrice)}
|
||||
</Text>
|
||||
),
|
||||
daily: (
|
||||
<Text>
|
||||
{prices.currencySign} {calculateEstimateNumber(prices.dailyPrice)}
|
||||
</Text>
|
||||
),
|
||||
monthly: (
|
||||
<Text>
|
||||
{prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice)}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
if (newThroughput) {
|
||||
const newPrices: PriceBreakdown = getRuPriceBreakdown(
|
||||
newThroughput,
|
||||
serverId,
|
||||
numberOfRegions,
|
||||
isMultimaster,
|
||||
false
|
||||
);
|
||||
estimatedSpendingItems.unshift({
|
||||
costType: (
|
||||
<Text>
|
||||
<b>Updated Cost</b>
|
||||
</Text>
|
||||
),
|
||||
hourly: (
|
||||
<Text>
|
||||
<b>
|
||||
{newPrices.currencySign} {calculateEstimateNumber(newPrices.hourlyPrice)}
|
||||
</b>
|
||||
</Text>
|
||||
),
|
||||
daily: (
|
||||
<Text>
|
||||
<b>
|
||||
{newPrices.currencySign} {calculateEstimateNumber(newPrices.dailyPrice)}
|
||||
</b>
|
||||
</Text>
|
||||
),
|
||||
monthly: (
|
||||
<Text>
|
||||
<b>
|
||||
{newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice)}
|
||||
</b>
|
||||
</Text>
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
return getEstimatedSpendingElement(
|
||||
estimatedSpendingColumns,
|
||||
estimatedSpendingItems,
|
||||
newThroughput ?? throughput,
|
||||
numberOfRegions,
|
||||
prices,
|
||||
false
|
||||
);
|
||||
};
|
||||
|
||||
private getAutoPilotUsageCost = (): JSX.Element => {
|
||||
if (!this.props.maxAutoPilotThroughput) {
|
||||
return <></>;
|
||||
@@ -535,12 +318,6 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
|
||||
private renderThroughputInput = (): JSX.Element => (
|
||||
<Stack {...titleAndInputStackProps}>
|
||||
<Text>
|
||||
Estimate your required throughput with
|
||||
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/">
|
||||
{` capacity calculator`} <FontIcon iconName="NavigateExternalInline" />
|
||||
</Link>
|
||||
</Text>
|
||||
<TextField
|
||||
required
|
||||
type="number"
|
||||
@@ -572,24 +349,13 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
onChange={this.onSpendAckChecked}
|
||||
/>
|
||||
)}
|
||||
<br />
|
||||
{this.props.isFixed && <p>When using a collection with fixed storage capacity, you can set up to 10,000 RU/s.</p>}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
private renderWarningMessage = (): JSX.Element => {
|
||||
let warningMessage: JSX.Element;
|
||||
if (this.IsComponentDirty().isDiscardable) {
|
||||
warningMessage = saveThroughputWarningMessage;
|
||||
}
|
||||
|
||||
return <>{warningMessage && <MessageBar messageBarType={MessageBarType.warning}>{warningMessage}</MessageBar>}</>;
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<Stack {...checkBoxAndInputStackProps}>
|
||||
{this.renderWarningMessage()}
|
||||
{this.renderThroughputModeChoices()}
|
||||
|
||||
{this.props.isAutoPilotSelected ? this.renderAutoPilotInput() : this.renderThroughputInput()}
|
||||
|
||||
@@ -8,21 +8,6 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<StyledMessageBarBase
|
||||
messageBarType={5}
|
||||
>
|
||||
<Text
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"fontSize": 12,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
Your bill will be affected as you update your throughput settings. Please review the updated cost estimate below before saving your changes
|
||||
</Text>
|
||||
</StyledMessageBarBase>
|
||||
<Stack>
|
||||
<StyledLabelBase
|
||||
id="settingsV2RadioButtonLabelId"
|
||||
@@ -229,19 +214,6 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text>
|
||||
Estimate your required throughput with
|
||||
<StyledLinkBase
|
||||
href="https://cosmos.azure.com/capacitycalculator/"
|
||||
target="_blank"
|
||||
>
|
||||
capacity calculator
|
||||
|
||||
<Component
|
||||
iconName="NavigateExternalInline"
|
||||
/>
|
||||
</StyledLinkBase>
|
||||
</Text>
|
||||
<StyledTextFieldBase
|
||||
disabled={false}
|
||||
id="throughputInput"
|
||||
@@ -267,125 +239,27 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
||||
type="number"
|
||||
value="100"
|
||||
/>
|
||||
<Stack
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"width": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<StyledWithViewportComponent
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"fieldName": "costType",
|
||||
"isResizable": true,
|
||||
"key": "costType",
|
||||
"maxWidth": 200,
|
||||
"minWidth": 100,
|
||||
"name": "",
|
||||
"styles": Object {
|
||||
"root": Object {
|
||||
"selectors": Object {
|
||||
":hover": Object {
|
||||
"background": "transparent",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"fieldName": "hourly",
|
||||
"isResizable": true,
|
||||
"key": "hourly",
|
||||
"maxWidth": 200,
|
||||
"minWidth": 100,
|
||||
"name": "Hourly",
|
||||
"styles": Object {
|
||||
"root": Object {
|
||||
"selectors": Object {
|
||||
":hover": Object {
|
||||
"background": "transparent",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"fieldName": "daily",
|
||||
"isResizable": true,
|
||||
"key": "daily",
|
||||
"maxWidth": 200,
|
||||
"minWidth": 100,
|
||||
"name": "Daily",
|
||||
"styles": Object {
|
||||
"root": Object {
|
||||
"selectors": Object {
|
||||
":hover": Object {
|
||||
"background": "transparent",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"fieldName": "monthly",
|
||||
"isResizable": true,
|
||||
"key": "monthly",
|
||||
"maxWidth": 200,
|
||||
"minWidth": 100,
|
||||
"name": "Monthly",
|
||||
"styles": Object {
|
||||
"root": Object {
|
||||
"selectors": Object {
|
||||
":hover": Object {
|
||||
"background": "transparent",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
disableSelectionZone={true}
|
||||
items={
|
||||
Array [
|
||||
Object {
|
||||
"costType": <Text>
|
||||
Current Cost
|
||||
</Text>,
|
||||
"daily": <Text>
|
||||
$
|
||||
|
||||
0.19
|
||||
</Text>,
|
||||
"hourly": <Text>
|
||||
$
|
||||
|
||||
0.0080
|
||||
</Text>,
|
||||
"monthly": <Text>
|
||||
$
|
||||
|
||||
5.84
|
||||
</Text>,
|
||||
},
|
||||
]
|
||||
}
|
||||
layoutMode={1}
|
||||
onRenderRow={[Function]}
|
||||
selectionMode={0}
|
||||
/>
|
||||
<Text
|
||||
id="throughputSpendElement"
|
||||
>
|
||||
Estimated cost (
|
||||
USD
|
||||
):
|
||||
|
||||
<b>
|
||||
$
|
||||
0.0080
|
||||
hourly
|
||||
/
|
||||
$
|
||||
0.19
|
||||
daily
|
||||
/
|
||||
$
|
||||
5.84
|
||||
monthly
|
||||
|
||||
</b>
|
||||
(
|
||||
regions:
|
||||
|
||||
@@ -397,12 +271,6 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
||||
0.00008
|
||||
/RU)
|
||||
</Text>
|
||||
<Text>
|
||||
<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>
|
||||
</Text>
|
||||
</Stack>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
id="spendAckCheckBox"
|
||||
@@ -420,7 +288,6 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
||||
}
|
||||
}
|
||||
/>
|
||||
<br />
|
||||
</Stack>
|
||||
</Stack>
|
||||
`;
|
||||
@@ -502,19 +369,6 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text>
|
||||
Estimate your required throughput with
|
||||
<StyledLinkBase
|
||||
href="https://cosmos.azure.com/capacitycalculator/"
|
||||
target="_blank"
|
||||
>
|
||||
capacity calculator
|
||||
|
||||
<Component
|
||||
iconName="NavigateExternalInline"
|
||||
/>
|
||||
</StyledLinkBase>
|
||||
</Text>
|
||||
<StyledTextFieldBase
|
||||
disabled={false}
|
||||
id="throughputInput"
|
||||
@@ -540,125 +394,27 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
|
||||
type="number"
|
||||
value="100"
|
||||
/>
|
||||
<Stack
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"width": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<StyledWithViewportComponent
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"fieldName": "costType",
|
||||
"isResizable": true,
|
||||
"key": "costType",
|
||||
"maxWidth": 200,
|
||||
"minWidth": 100,
|
||||
"name": "",
|
||||
"styles": Object {
|
||||
"root": Object {
|
||||
"selectors": Object {
|
||||
":hover": Object {
|
||||
"background": "transparent",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"fieldName": "hourly",
|
||||
"isResizable": true,
|
||||
"key": "hourly",
|
||||
"maxWidth": 200,
|
||||
"minWidth": 100,
|
||||
"name": "Hourly",
|
||||
"styles": Object {
|
||||
"root": Object {
|
||||
"selectors": Object {
|
||||
":hover": Object {
|
||||
"background": "transparent",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"fieldName": "daily",
|
||||
"isResizable": true,
|
||||
"key": "daily",
|
||||
"maxWidth": 200,
|
||||
"minWidth": 100,
|
||||
"name": "Daily",
|
||||
"styles": Object {
|
||||
"root": Object {
|
||||
"selectors": Object {
|
||||
":hover": Object {
|
||||
"background": "transparent",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"fieldName": "monthly",
|
||||
"isResizable": true,
|
||||
"key": "monthly",
|
||||
"maxWidth": 200,
|
||||
"minWidth": 100,
|
||||
"name": "Monthly",
|
||||
"styles": Object {
|
||||
"root": Object {
|
||||
"selectors": Object {
|
||||
":hover": Object {
|
||||
"background": "transparent",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
disableSelectionZone={true}
|
||||
items={
|
||||
Array [
|
||||
Object {
|
||||
"costType": <Text>
|
||||
Current Cost
|
||||
</Text>,
|
||||
"daily": <Text>
|
||||
$
|
||||
|
||||
0.19
|
||||
</Text>,
|
||||
"hourly": <Text>
|
||||
$
|
||||
|
||||
0.0080
|
||||
</Text>,
|
||||
"monthly": <Text>
|
||||
$
|
||||
|
||||
5.84
|
||||
</Text>,
|
||||
},
|
||||
]
|
||||
}
|
||||
layoutMode={1}
|
||||
onRenderRow={[Function]}
|
||||
selectionMode={0}
|
||||
/>
|
||||
<Text
|
||||
id="throughputSpendElement"
|
||||
>
|
||||
Estimated cost (
|
||||
USD
|
||||
):
|
||||
|
||||
<b>
|
||||
$
|
||||
0.0080
|
||||
hourly
|
||||
/
|
||||
$
|
||||
0.19
|
||||
daily
|
||||
/
|
||||
$
|
||||
5.84
|
||||
monthly
|
||||
|
||||
</b>
|
||||
(
|
||||
regions:
|
||||
|
||||
@@ -670,13 +426,6 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
|
||||
0.00008
|
||||
/RU)
|
||||
</Text>
|
||||
<Text>
|
||||
<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>
|
||||
</Text>
|
||||
</Stack>
|
||||
<br />
|
||||
</Stack>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
@@ -60,83 +60,27 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
</StyledLinkBase>
|
||||
.
|
||||
</Text>
|
||||
<Stack
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"width": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<StyledWithViewportComponent
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"fieldName": "costType",
|
||||
"isResizable": true,
|
||||
"key": "costType",
|
||||
"maxWidth": 200,
|
||||
"minWidth": 100,
|
||||
"name": "",
|
||||
},
|
||||
Object {
|
||||
"fieldName": "hourly",
|
||||
"isResizable": true,
|
||||
"key": "hourly",
|
||||
"maxWidth": 200,
|
||||
"minWidth": 100,
|
||||
"name": "Hourly",
|
||||
},
|
||||
Object {
|
||||
"fieldName": "daily",
|
||||
"isResizable": true,
|
||||
"key": "daily",
|
||||
"maxWidth": 200,
|
||||
"minWidth": 100,
|
||||
"name": "Daily",
|
||||
},
|
||||
Object {
|
||||
"fieldName": "monthly",
|
||||
"isResizable": true,
|
||||
"key": "monthly",
|
||||
"maxWidth": 200,
|
||||
"minWidth": 100,
|
||||
"name": "Monthly",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableSelectionZone={true}
|
||||
items={
|
||||
Array [
|
||||
Object {
|
||||
"costType": <Text>
|
||||
Current Cost
|
||||
</Text>,
|
||||
"daily": <Text>
|
||||
$ 24.48
|
||||
</Text>,
|
||||
"hourly": <Text>
|
||||
$ 1.02
|
||||
</Text>,
|
||||
"monthly": <Text>
|
||||
$ 744.6
|
||||
</Text>,
|
||||
},
|
||||
]
|
||||
}
|
||||
layoutMode={1}
|
||||
onRenderRow={[Function]}
|
||||
selectionMode={0}
|
||||
/>
|
||||
<Text
|
||||
id="throughputSpendElement"
|
||||
>
|
||||
Estimated cost (
|
||||
RMB
|
||||
):
|
||||
|
||||
<b>
|
||||
¥
|
||||
1.02
|
||||
hourly
|
||||
/
|
||||
¥
|
||||
24.48
|
||||
daily
|
||||
/
|
||||
¥
|
||||
744.60
|
||||
monthly
|
||||
|
||||
</b>
|
||||
(
|
||||
regions:
|
||||
|
||||
@@ -148,12 +92,34 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
0.00051
|
||||
/RU)
|
||||
</Text>
|
||||
<Text>
|
||||
<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>
|
||||
<Text
|
||||
id="autoscaleSpendElement"
|
||||
>
|
||||
Estimated monthly cost (
|
||||
RMB
|
||||
) is
|
||||
|
||||
<b>
|
||||
¥
|
||||
111.69
|
||||
-
|
||||
¥
|
||||
1116.90
|
||||
|
||||
</b>
|
||||
(
|
||||
regions:
|
||||
|
||||
2
|
||||
,
|
||||
100
|
||||
-
|
||||
1000
|
||||
RU/s,
|
||||
¥
|
||||
0.000765
|
||||
/RU)
|
||||
</Text>
|
||||
</Stack>
|
||||
<Text
|
||||
id="manualToAutoscaleDisclaimerElement"
|
||||
styles={
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import "./MeControlComponent.less";
|
||||
import * as React from "react";
|
||||
import { DefaultButton, BaseButton, IButtonProps } from "office-ui-fabric-react/lib/Button";
|
||||
import { DirectionalHint, IContextualMenuProps } from "office-ui-fabric-react/lib/ContextualMenu";
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
</div>
|
||||
<json-editor
|
||||
params="{ content: queryResults, isReadOnly: true, ariaLabel: 'Query results' }"
|
||||
data-bind="visible: queryResults() && queryResults().length > 0 && isResultToggled() && allResultsMetadata().length > 0 && !error()"
|
||||
data-bind="visible: queryResults().length > 0 && isResultToggled() && allResultsMetadata().length > 0 && !error()"
|
||||
>
|
||||
</json-editor>
|
||||
<div
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
<notebook-viewer-tab params="{data: $data}"></notebook-viewer-tab>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: $data.tabKind === 20 -->
|
||||
<!-- ko if: $data.tabKind === 19 -->
|
||||
<settings-tab-v2 params="{data: $data}"></settings-tab-v2>
|
||||
<!-- /ko -->
|
||||
</div>
|
||||
|
||||
@@ -551,7 +551,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
|
||||
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
||||
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
|
||||
const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.SettingsV2, tab => {
|
||||
const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Settings, tab => {
|
||||
return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,242 +0,0 @@
|
||||
import { Configuration, PublicClientApplication } from "@azure/msal-browser";
|
||||
import { AuthenticatedTemplate, MsalProvider, UnauthenticatedTemplate } from "@azure/msal-react";
|
||||
import { useBoolean } from "@uifabric/react-hooks";
|
||||
import {
|
||||
DefaultButton,
|
||||
DirectionalHint,
|
||||
FocusZone,
|
||||
initializeIcons,
|
||||
Panel,
|
||||
Persona,
|
||||
PersonaInitialsColor,
|
||||
PersonaSize
|
||||
} from "office-ui-fabric-react";
|
||||
import * as React from "react";
|
||||
import { render } from "react-dom";
|
||||
import FeedbackIcon from "../images/Feedback.svg";
|
||||
import ConnectIcon from "../images/HostedConnectwhite.svg";
|
||||
import "../less/hostedexplorer.less";
|
||||
import { CommandButtonComponent } from "./Explorer/Controls/CommandButton/CommandButtonComponent";
|
||||
import { DefaultDirectoryDropdownComponent } from "./Explorer/Controls/Directory/DefaultDirectoryDropdownComponent";
|
||||
import { DirectoryListComponent } from "./Explorer/Controls/Directory/DirectoryListComponent";
|
||||
import "./Explorer/Menus/NavBar/MeControlComponent.less";
|
||||
import { useGraphProfile } from "./hooks/useGraphProfile";
|
||||
import "./Shared/appInsights";
|
||||
|
||||
initializeIcons();
|
||||
|
||||
// MSAL configuration
|
||||
const configuration: Configuration = {
|
||||
auth: {
|
||||
clientId: "e8ae3d28-de2a-4dc8-8fa3-2d2998b1c38f",
|
||||
redirectUri: "https://localhost:1234/hostedExplorer.html",
|
||||
authority: "https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47"
|
||||
}
|
||||
};
|
||||
|
||||
const application = new PublicClientApplication(configuration);
|
||||
|
||||
const App: React.FunctionComponent = () => {
|
||||
const [isOpen, { setTrue: openPanel, setFalse: dismissPanel }] = useBoolean(false);
|
||||
const { graphData, photo } = useGraphProfile();
|
||||
|
||||
const menuProps = {
|
||||
className: "mecontrolContextualMenu",
|
||||
isBeakVisible: false,
|
||||
directionalHintFixed: true,
|
||||
directionalHint: DirectionalHint.bottomRightEdge,
|
||||
calloutProps: {
|
||||
minPagePadding: 0
|
||||
},
|
||||
items: [
|
||||
{
|
||||
key: "Persona",
|
||||
onRender: () => <Persona />
|
||||
},
|
||||
{
|
||||
key: "SwitchDirectory",
|
||||
onRender: () => (
|
||||
<div className="switchDirectoryLink" onClick={() => openPanel}>
|
||||
Switch Directory
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: "SignOut",
|
||||
onRender: () => (
|
||||
<div
|
||||
className="signOutLink"
|
||||
onClick={() => {
|
||||
instance.logout();
|
||||
}}
|
||||
>
|
||||
Sign out
|
||||
</div>
|
||||
)
|
||||
}
|
||||
]
|
||||
};
|
||||
const personaProps = {};
|
||||
|
||||
// {
|
||||
// id: "commandbutton-settings",
|
||||
// iconSrc: SettingsIcon,
|
||||
// iconAlt: "setting button",
|
||||
// onCommandClick: () => {},
|
||||
// commandButtonLabel: undefined,
|
||||
// ariaLabel: "setting button",
|
||||
// tooltipText: "Global settings",
|
||||
// hasPopup: true,
|
||||
// disabled: false
|
||||
// },
|
||||
// {
|
||||
// id: "commandbutton-feedback",
|
||||
// iconSrc: FeedbackIcon,
|
||||
// iconAlt: "feeback button",
|
||||
// onCommandClick: () =>
|
||||
// window.open(
|
||||
// "https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20Hosted%20Data%20Explorer%20Feedback"
|
||||
// ),
|
||||
// commandButtonLabel: undefined,
|
||||
// ariaLabel: "feeback button",
|
||||
// tooltipText: "Send feedback",
|
||||
// hasPopup: true,
|
||||
// disabled: false
|
||||
// }
|
||||
|
||||
const buttonProps = {
|
||||
id: "mecontrolHeader",
|
||||
className: "mecontrolHeaderButton",
|
||||
menuProps: menuProps,
|
||||
onRenderMenuIcon: () => <span />,
|
||||
styles: {
|
||||
rootHovered: { backgroundColor: "#393939" },
|
||||
rootFocused: { backgroundColor: "#393939" },
|
||||
rootPressed: { backgroundColor: "#393939" },
|
||||
rootExpanded: { backgroundColor: "#393939" }
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<header>
|
||||
<div className="items" role="menubar">
|
||||
<div className="cosmosDBTitle">
|
||||
<span
|
||||
className="title"
|
||||
data-bind="click: openAzurePortal, event: { keypress: onOpenAzurePortalKeyPress }"
|
||||
tabIndex={0}
|
||||
title="Go to Azure Portal"
|
||||
>
|
||||
Microsoft Azure
|
||||
</span>
|
||||
<span className="accontSplitter" /> <span className="serviceTitle">Cosmos DB</span>
|
||||
<img
|
||||
className="chevronRight"
|
||||
src="/chevron-right.svg"
|
||||
alt="account separator"
|
||||
data-bind="visible: isAccountActive"
|
||||
/>
|
||||
<span
|
||||
className="accountSwitchComponentContainer"
|
||||
data-bind="react: accountSwitchComponentAdapter, visible: isAccountActive"
|
||||
/>
|
||||
</div>
|
||||
<div className="feedbackConnectSettingIcons">
|
||||
<AuthenticatedTemplate>
|
||||
<CommandButtonComponent
|
||||
id="commandbutton-connect"
|
||||
iconSrc={ConnectIcon}
|
||||
iconAlt="connect button"
|
||||
onCommandClick={() => {}}
|
||||
ariaLabel="connect button"
|
||||
tooltipText="Connect to a Cosmos DB account"
|
||||
hasPopup={true}
|
||||
disabled={false}
|
||||
/>
|
||||
</AuthenticatedTemplate>
|
||||
<UnauthenticatedTemplate>
|
||||
<CommandButtonComponent
|
||||
id="commandbutton-feedback"
|
||||
iconSrc={FeedbackIcon}
|
||||
iconAlt="feeback button"
|
||||
onCommandClick={() =>
|
||||
window.open(
|
||||
"https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20Hosted%20Data%20Explorer%20Feedback"
|
||||
)
|
||||
}
|
||||
ariaLabel="feeback button"
|
||||
tooltipText="Send feedback"
|
||||
hasPopup={true}
|
||||
disabled={false}
|
||||
/>
|
||||
</UnauthenticatedTemplate>
|
||||
</div>
|
||||
<div className="meControl">
|
||||
<AuthenticatedTemplate>
|
||||
<FocusZone>
|
||||
<DefaultButton {...buttonProps}>
|
||||
<Persona
|
||||
imageUrl={photo}
|
||||
text={graphData?.displayName}
|
||||
secondaryText={graphData?.displayName}
|
||||
showSecondaryText={true}
|
||||
showInitialsUntilImageLoads={true}
|
||||
initialsColor={PersonaInitialsColor.teal}
|
||||
size={PersonaSize.size28}
|
||||
className="mecontrolHeaderPersona"
|
||||
/>
|
||||
</DefaultButton>
|
||||
</FocusZone>
|
||||
</AuthenticatedTemplate>
|
||||
<UnauthenticatedTemplate>
|
||||
<DefaultButton
|
||||
className="mecontrolSigninButton"
|
||||
text="Sign In"
|
||||
onClick={() => {
|
||||
instance.loginPopup();
|
||||
}}
|
||||
styles={{
|
||||
rootHovered: { backgroundColor: "#393939", color: "#fff" },
|
||||
rootFocused: { backgroundColor: "#393939", color: "#fff" },
|
||||
rootPressed: { backgroundColor: "#393939", color: "#fff" }
|
||||
}}
|
||||
/>
|
||||
</UnauthenticatedTemplate>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{/* <iframe
|
||||
id="explorerMenu"
|
||||
name="explorer"
|
||||
className="iframe"
|
||||
title="explorer"
|
||||
src="explorer.html?v=1.0.1&platform=Hosted"
|
||||
data-bind="visible: navigationSelection() === 'explorer'"
|
||||
></iframe> */}
|
||||
<div data-bind="react: firewallWarningComponentAdapter" />
|
||||
<div data-bind="react: dialogComponentAdapter" />
|
||||
<Panel
|
||||
headerText="Select Directory"
|
||||
isOpen={!isOpen}
|
||||
onDismiss={dismissPanel}
|
||||
// You MUST provide this prop! Otherwise screen readers will just say "button" with no label.
|
||||
closeButtonAriaLabel="Close"
|
||||
>
|
||||
<div className="directoryDropdownContainer">
|
||||
<DefaultDirectoryDropdownComponent />
|
||||
</div>
|
||||
<div className="directoryDivider" />
|
||||
<div className="directoryListContainer">
|
||||
<DirectoryListComponent />
|
||||
</div>
|
||||
</Panel>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render(
|
||||
<MsalProvider instance={application}>
|
||||
<App />
|
||||
</MsalProvider>,
|
||||
document.body
|
||||
);
|
||||
@@ -44,6 +44,7 @@ import "./Libs/jquery";
|
||||
import "bootstrap/dist/js/npm";
|
||||
import "../externals/jquery.typeahead.min.js";
|
||||
import "../externals/jquery-ui.min.js";
|
||||
import "../externals/adal.js";
|
||||
import "promise-polyfill/src/polyfill";
|
||||
import "abort-controller/polyfill";
|
||||
import "whatwg-fetch";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "expose-loader?AuthenticationContext!../../../externals/adal";
|
||||
|
||||
import Q from "q";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
|
||||
@@ -17,7 +17,9 @@ export class TabRouteHandler {
|
||||
): void {
|
||||
this._initRouter();
|
||||
const parseHash = (newHash: string, oldHash: string) => this._tabRouter.parse(newHash);
|
||||
const defaultRoutedCallback = (request: string, data: { route: any; params: string[]; isFirst: boolean }) => {};
|
||||
const defaultRoutedCallback = (request: string, data: { route: any; params: string[]; isFirst: boolean }) => {
|
||||
console.log(request);
|
||||
};
|
||||
this._tabRouter.routed.add(onMatch || defaultRoutedCallback);
|
||||
hasher.initialized.add(parseHash);
|
||||
hasher.changed.add(parseHash);
|
||||
|
||||
@@ -25,151 +25,39 @@ describe("PricingUtils Tests", () => {
|
||||
|
||||
describe("computeRUUsagePriceHourly()", () => {
|
||||
it("should return 0 for NaN regions default cloud", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||
serverId: "default",
|
||||
requestUnits: 1,
|
||||
numberOfRegions: null,
|
||||
multimasterEnabled: false,
|
||||
isAutoscale: false
|
||||
});
|
||||
expect(value).toBe(0);
|
||||
});
|
||||
it("should return 0 for NaN regions default cloud, autoscale", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||
serverId: "default",
|
||||
requestUnits: 1,
|
||||
numberOfRegions: null,
|
||||
multimasterEnabled: false,
|
||||
isAutoscale: true
|
||||
});
|
||||
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, null, false);
|
||||
expect(value).toBe(0);
|
||||
});
|
||||
|
||||
it("should return 0 for -1 regions", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||
serverId: "default",
|
||||
requestUnits: 1,
|
||||
numberOfRegions: -1,
|
||||
multimasterEnabled: false,
|
||||
isAutoscale: false
|
||||
});
|
||||
expect(value).toBe(0);
|
||||
});
|
||||
it("should return 0 for -1 regions, autoscale", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||
serverId: "default",
|
||||
requestUnits: 1,
|
||||
numberOfRegions: -1,
|
||||
multimasterEnabled: false,
|
||||
isAutoscale: true
|
||||
});
|
||||
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, -1, false);
|
||||
expect(value).toBe(0);
|
||||
});
|
||||
|
||||
it("should return 0.00008 for default cloud, 1RU, 1 region, multimaster disabled", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||
serverId: "default",
|
||||
requestUnits: 1,
|
||||
numberOfRegions: 1,
|
||||
multimasterEnabled: false,
|
||||
isAutoscale: false
|
||||
});
|
||||
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 1, false);
|
||||
expect(value).toBe(0.00008);
|
||||
});
|
||||
it("should return 0.00008 for default cloud, 1RU, 1 region, multimaster disabled, autoscale", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||
serverId: "default",
|
||||
requestUnits: 1,
|
||||
numberOfRegions: 1,
|
||||
multimasterEnabled: false,
|
||||
isAutoscale: true
|
||||
});
|
||||
expect(value).toBe(0.00012);
|
||||
});
|
||||
|
||||
it("should return 0.00051 for Mooncake cloud, 1RU, 1 region, multimaster disabled", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||
serverId: "mooncake",
|
||||
requestUnits: 1,
|
||||
numberOfRegions: 1,
|
||||
multimasterEnabled: false,
|
||||
isAutoscale: false
|
||||
});
|
||||
const value = PricingUtils.computeRUUsagePriceHourly("mooncake", 1, 1, false);
|
||||
expect(value).toBe(0.00051);
|
||||
});
|
||||
it("should return 0.00051 for Mooncake cloud, 1RU, 1 region, multimaster disabled, autoscale", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||
serverId: "mooncake",
|
||||
requestUnits: 1,
|
||||
numberOfRegions: 1,
|
||||
multimasterEnabled: false,
|
||||
isAutoscale: true
|
||||
});
|
||||
expect(value).toBe(0.00076);
|
||||
});
|
||||
|
||||
it("should return 0.00016 for default cloud, 1RU, 2 regions, multimaster disabled", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||
serverId: "default",
|
||||
requestUnits: 1,
|
||||
numberOfRegions: 2,
|
||||
multimasterEnabled: false,
|
||||
isAutoscale: false
|
||||
});
|
||||
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 2, false);
|
||||
expect(value).toBe(0.00016);
|
||||
});
|
||||
it("should return 0.00016 for default cloud, 1RU, 2 regions, multimaster disabled, autoscale", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||
serverId: "default",
|
||||
requestUnits: 1,
|
||||
numberOfRegions: 2,
|
||||
multimasterEnabled: false,
|
||||
isAutoscale: true
|
||||
});
|
||||
expect(value).toBe(0.00024);
|
||||
});
|
||||
|
||||
it("should return 0.00008 for default cloud, 1RU, 1 region, multimaster enabled", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||
serverId: "default",
|
||||
requestUnits: 1,
|
||||
numberOfRegions: 1,
|
||||
multimasterEnabled: true,
|
||||
isAutoscale: false
|
||||
});
|
||||
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 1, true);
|
||||
expect(value).toBe(0.00008);
|
||||
});
|
||||
it("should return 0.00008 for default cloud, 1RU, 1 region, multimaster enabled, autoscale", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||
serverId: "default",
|
||||
requestUnits: 1,
|
||||
numberOfRegions: 1,
|
||||
multimasterEnabled: true,
|
||||
isAutoscale: true
|
||||
});
|
||||
expect(value).toBe(0.00012);
|
||||
});
|
||||
|
||||
it("should return 0.00048 for default cloud, 1RU, 2 region, multimaster enabled", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||
serverId: "default",
|
||||
requestUnits: 1,
|
||||
numberOfRegions: 2,
|
||||
multimasterEnabled: true,
|
||||
isAutoscale: false
|
||||
});
|
||||
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 2, true);
|
||||
expect(value).toBe(0.00048);
|
||||
});
|
||||
it("should return 0.00048 for default cloud, 1RU, 2 region, multimaster enabled, autoscale", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||
serverId: "default",
|
||||
requestUnits: 1,
|
||||
numberOfRegions: 2,
|
||||
multimasterEnabled: true,
|
||||
isAutoscale: true
|
||||
});
|
||||
expect(value).toBe(0.00096);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getPriceCurrency()", () => {
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
import * as AutoPilotUtils from "../Utils/AutoPilotUtils";
|
||||
import * as Constants from "../Shared/Constants";
|
||||
|
||||
interface ComputeRUUsagePriceHourlyArgs {
|
||||
serverId: string;
|
||||
requestUnits: number;
|
||||
numberOfRegions: number;
|
||||
multimasterEnabled: boolean;
|
||||
isAutoscale: boolean;
|
||||
}
|
||||
|
||||
export const estimatedCostDisclaimer =
|
||||
"*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account";
|
||||
|
||||
/**
|
||||
* Anything that is not a number should return 0
|
||||
* Otherwise, return numberOfRegions
|
||||
@@ -58,16 +47,15 @@ export function getMultimasterMultiplier(numberOfRegions: number, multimasterEna
|
||||
return multimasterMultiplier;
|
||||
}
|
||||
|
||||
export function computeRUUsagePriceHourly({
|
||||
serverId,
|
||||
requestUnits,
|
||||
numberOfRegions,
|
||||
multimasterEnabled,
|
||||
isAutoscale
|
||||
}: ComputeRUUsagePriceHourlyArgs): number {
|
||||
export function computeRUUsagePriceHourly(
|
||||
serverId: string,
|
||||
requestUnits: number,
|
||||
numberOfRegions: number,
|
||||
multimasterEnabled: boolean
|
||||
): number {
|
||||
const regionMultiplier: number = getRegionMultiplier(numberOfRegions, multimasterEnabled);
|
||||
const multimasterMultiplier: number = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
|
||||
const pricePerRu = isAutoscale ? getAutoscalePricePerRu(serverId, multimasterMultiplier) : getPricePerRu(serverId);
|
||||
const pricePerRu = getPricePerRu(serverId);
|
||||
const ruCharge = requestUnits * pricePerRu * multimasterMultiplier * regionMultiplier;
|
||||
|
||||
return Number(ruCharge.toFixed(5));
|
||||
@@ -171,19 +159,28 @@ export function getAutoPilotV3SpendHtml(maxAutoPilotThroughputSet: number, isDat
|
||||
}' target='_blank' aria-label='Learn more about autoscale throughput'>Learn more</a>.`;
|
||||
}
|
||||
|
||||
export function computeAutoscaleUsagePriceHourly(
|
||||
serverId: string,
|
||||
requestUnits: number,
|
||||
numberOfRegions: number,
|
||||
multimasterEnabled: boolean
|
||||
): number {
|
||||
const regionMultiplier: number = getRegionMultiplier(numberOfRegions, multimasterEnabled);
|
||||
const multimasterMultiplier: number = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
|
||||
|
||||
const pricePerRu = getAutoscalePricePerRu(serverId, multimasterMultiplier);
|
||||
const ruCharge = requestUnits * pricePerRu * multimasterMultiplier * regionMultiplier;
|
||||
|
||||
return Number(ruCharge.toFixed(5));
|
||||
}
|
||||
|
||||
export function getEstimatedAutoscaleSpendHtml(
|
||||
throughput: number,
|
||||
serverId: string,
|
||||
regions: number,
|
||||
multimaster: boolean
|
||||
): string {
|
||||
const hourlyPrice: number = computeRUUsagePriceHourly({
|
||||
serverId: serverId,
|
||||
requestUnits: throughput,
|
||||
numberOfRegions: regions,
|
||||
multimasterEnabled: multimaster,
|
||||
isAutoscale: true
|
||||
});
|
||||
const hourlyPrice: number = computeAutoscaleUsagePriceHourly(serverId, throughput, regions, multimaster);
|
||||
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
|
||||
const currency: string = getPriceCurrency(serverId);
|
||||
const currencySign: string = getCurrencySign(serverId);
|
||||
@@ -206,13 +203,7 @@ export function getEstimatedSpendHtml(
|
||||
regions: number,
|
||||
multimaster: boolean
|
||||
): string {
|
||||
const hourlyPrice: number = computeRUUsagePriceHourly({
|
||||
serverId: serverId,
|
||||
requestUnits: throughput,
|
||||
numberOfRegions: regions,
|
||||
multimasterEnabled: multimaster,
|
||||
isAutoscale: false
|
||||
});
|
||||
const hourlyPrice: number = computeRUUsagePriceHourly(serverId, throughput, regions, multimaster);
|
||||
const dailyPrice: number = hourlyPrice * 24;
|
||||
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
|
||||
const currency: string = getPriceCurrency(serverId);
|
||||
@@ -226,7 +217,7 @@ export function getEstimatedSpendHtml(
|
||||
`${currencySign}${calculateEstimateNumber(monthlyPrice)} monthly </b> ` +
|
||||
`(${regions} ${regions === 1 ? "region" : "regions"}, ${throughput}RU/s, ${currencySign}${pricePerRu}/RU)` +
|
||||
`<p style='padding: 10px 0px 0px 0px;'>` +
|
||||
`<em>${estimatedCostDisclaimer}</em></p>`
|
||||
`<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>`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -237,13 +228,9 @@ export function getEstimatedSpendAcknowledgeString(
|
||||
multimaster: boolean,
|
||||
isAutoscale: boolean
|
||||
): string {
|
||||
const hourlyPrice: number = computeRUUsagePriceHourly({
|
||||
serverId: serverId,
|
||||
requestUnits: throughput,
|
||||
numberOfRegions: regions,
|
||||
multimasterEnabled: multimaster,
|
||||
isAutoscale: isAutoscale
|
||||
});
|
||||
const hourlyPrice: number = isAutoscale
|
||||
? computeAutoscaleUsagePriceHourly(serverId, throughput, regions, multimaster)
|
||||
: computeRUUsagePriceHourly(serverId, throughput, regions, multimaster);
|
||||
const dailyPrice: number = hourlyPrice * 24;
|
||||
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
|
||||
const currencySign: string = getCurrencySign(serverId);
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import { useAccount, useMsal } from "@azure/msal-react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export async function fetchMe(accessToken: string): Promise<GraphMeResponse> {
|
||||
const headers = new Headers();
|
||||
const bearer = `Bearer ${accessToken}`;
|
||||
|
||||
headers.append("Authorization", bearer);
|
||||
|
||||
const options = {
|
||||
method: "GET",
|
||||
headers: headers
|
||||
};
|
||||
|
||||
console.log("EXECUTING REQUEST");
|
||||
return fetch("https://graph.microsoft.com/v1.0/me", options)
|
||||
.then(response => response.json())
|
||||
.catch(error => console.log(error));
|
||||
}
|
||||
|
||||
export async function fetchPhoto(accessToken: string): Promise<Blob | void> {
|
||||
const headers = new Headers();
|
||||
const bearer = `Bearer ${accessToken}`;
|
||||
|
||||
headers.append("Authorization", bearer);
|
||||
headers.append("Content-Type", "image/jpg");
|
||||
|
||||
const options = {
|
||||
method: "GET",
|
||||
headers: headers
|
||||
};
|
||||
|
||||
console.log("EXECUTING REQUEST");
|
||||
return fetch("https://graph.microsoft.com/v1.0/me/photo/$value", options)
|
||||
.then(response => response.blob())
|
||||
.catch(error => console.log(error));
|
||||
}
|
||||
|
||||
interface GraphMeResponse {
|
||||
businessPhones: any[];
|
||||
displayName: string;
|
||||
givenName: string;
|
||||
jobTitle: string;
|
||||
mail: string;
|
||||
mobilePhone: null;
|
||||
officeLocation: string;
|
||||
preferredLanguage: null;
|
||||
surname: string;
|
||||
userPrincipalName: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export function useGraphProfile(): { graphData: GraphMeResponse; photo: string } {
|
||||
const { instance, accounts } = useMsal();
|
||||
const account = useAccount(accounts[0] || {});
|
||||
const [graphData, setGraphData] = useState<GraphMeResponse>();
|
||||
const [photo, setPhoto] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
console.log("account", account);
|
||||
if (account) {
|
||||
instance
|
||||
.acquireTokenSilent({
|
||||
scopes: ["User.Read"],
|
||||
account
|
||||
})
|
||||
.then(response => {
|
||||
fetchMe(response.accessToken).then(response => setGraphData(response));
|
||||
fetchPhoto(response.accessToken).then(response => setPhoto(URL.createObjectURL(response)));
|
||||
});
|
||||
}
|
||||
}, [account]);
|
||||
return { graphData, photo };
|
||||
}
|
||||
@@ -7,5 +7,59 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<div class="items" role="menubar">
|
||||
<div class="cosmosDBTitle">
|
||||
<span
|
||||
class="title"
|
||||
data-bind="click: openAzurePortal, event: { keypress: onOpenAzurePortalKeyPress }"
|
||||
tabindex="0"
|
||||
title="Go to Azure Portal"
|
||||
>Microsoft Azure</span
|
||||
>
|
||||
<span class="accontSplitter"></span> <span class="serviceTitle">Cosmos DB</span>
|
||||
<img
|
||||
class="chevronRight"
|
||||
src="/chevron-right.svg"
|
||||
alt="account separator"
|
||||
data-bind="visible: isAccountActive"
|
||||
/>
|
||||
<span
|
||||
class="accountSwitchComponentContainer"
|
||||
data-bind="react: accountSwitchComponentAdapter, visible: isAccountActive"
|
||||
></span>
|
||||
</div>
|
||||
<div class="feedbackConnectSettingIcons" data-bind="react: controlBarComponentAdapter"></div>
|
||||
<div class="meControl" data-bind="react: meControlComponentAdapter"></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- TODO display after introducing multiple account access -->
|
||||
<nav class="fixedleftpane" style="display: none;">
|
||||
<div class="fixedLeftPaneIcons"><img src="/Hamburger.svg" alt="Expand" /></div>
|
||||
<div class="fixedLeftPaneIcons"><img src="/Connect.svg" alt="Connect to an account" /></div>
|
||||
<div
|
||||
class="fixedLeftPaneIcons"
|
||||
data-bind="click: explorer_click, css:{ topSelected: navigationSelection() === 'explorer' }"
|
||||
>
|
||||
<img src="/HostedExplorer.svg" alt="Open Data Explorer" />
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<switch-directory-pane params="{data: switchDirectoryPane}"></switch-directory-pane>
|
||||
|
||||
<!-- TODO generate version number dynamically -->
|
||||
<iframe
|
||||
id="explorerMenu"
|
||||
name="explorer"
|
||||
class="iframe"
|
||||
title="explorer"
|
||||
src="explorer.html?v=1.0.1&platform=Hosted"
|
||||
data-bind="visible: navigationSelection() === 'explorer'"
|
||||
>
|
||||
</iframe>
|
||||
|
||||
<div data-bind="react: firewallWarningComponentAdapter"></div>
|
||||
<div data-bind="react: dialogComponentAdapter"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -120,7 +120,7 @@ describe("Collection Add and Delete Cassandra spec", () => {
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const testName = (expect as any).getState().currentTestName;
|
||||
await page.screenshot({ path: `failed-${testName}.jpg` });
|
||||
await page.screenshot({ path: `./failed-${testName}.jpg` });
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -136,7 +136,7 @@ describe("Collection Add and Delete Mongo spec", () => {
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const testName = (expect as any).getState().currentTestName;
|
||||
await page.screenshot({ path: `failed-${testName}.jpg` });
|
||||
await page.screenshot({ path: `./failed-${testName}.jpg` });
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -54,7 +54,7 @@ describe("Collection Add and Delete SQL spec", () => {
|
||||
// validate created
|
||||
// open database menu
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
await frame.waitFor(CREATE_DELAY);
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
const databases = await frame.$$(`div[class="databaseHeader main1 nodeItem "] > div[class="treeNodeHeader "]`);
|
||||
const selectedDbId = await frame.evaluate(element => {
|
||||
@@ -139,7 +139,7 @@ describe("Collection Add and Delete SQL spec", () => {
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const testName = (expect as any).getState().currentTestName;
|
||||
await page.screenshot({ path: `failed-${testName}.jpg` });
|
||||
await page.screenshot({ path: `./failed-${testName}.jpg` });
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -83,7 +83,7 @@ describe("Collection Add and Delete Tables spec", () => {
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const testName = (expect as any).getState().currentTestName;
|
||||
await page.screenshot({ path: `failed-${testName}.jpg` });
|
||||
await page.screenshot({ path: `./failed-${testName}.jpg` });
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -182,7 +182,7 @@ module.exports = function(env = {}, argv = {}) {
|
||||
main: "./src/Main.tsx",
|
||||
index: "./src/Index.ts",
|
||||
quickstart: "./src/quickstart.ts",
|
||||
hostedExplorer: "./src/HostedExplorer.tsx",
|
||||
hostedExplorer: "./src/HostedExplorer.ts",
|
||||
testExplorer: "./test/notebooks/testExplorer/TestExplorer.ts",
|
||||
heatmap: "./src/Controls/Heatmap/Heatmap.ts",
|
||||
terminal: "./src/Terminal/index.ts",
|
||||
|
||||
Reference in New Issue
Block a user