mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-23 19:01:28 +00:00
Compare commits
2 Commits
CE-2021-09
...
eslint/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4bc0ad5f9 | ||
|
|
a0a77002d2 |
@@ -137,18 +137,10 @@ src/Terminal/NotebookAppContracts.d.ts
|
|||||||
src/applyExplorerBindings.ts
|
src/applyExplorerBindings.ts
|
||||||
src/global.d.ts
|
src/global.d.ts
|
||||||
src/setupTests.ts
|
src/setupTests.ts
|
||||||
src/Explorer/Controls/InputTypeahead/InputTypeaheadComponent.tsx
|
|
||||||
src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx
|
|
||||||
src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx
|
|
||||||
src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx
|
|
||||||
src/Explorer/Controls/TreeComponent/TreeComponent.tsx
|
src/Explorer/Controls/TreeComponent/TreeComponent.tsx
|
||||||
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx
|
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx
|
||||||
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx
|
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx
|
||||||
src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx
|
src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx
|
||||||
src/Explorer/Graph/GraphExplorerComponent/LeftPaneComponent.tsx
|
|
||||||
src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx
|
|
||||||
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.test.tsx
|
|
||||||
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.tsx
|
|
||||||
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx
|
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx
|
||||||
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx
|
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx
|
||||||
src/Explorer/Menus/CommandBar/CommandBarUtil.tsx
|
src/Explorer/Menus/CommandBar/CommandBarUtil.tsx
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ module.exports = {
|
|||||||
global: {
|
global: {
|
||||||
branches: 25,
|
branches: 25,
|
||||||
functions: 25,
|
functions: 25,
|
||||||
lines: 29.5,
|
lines: 30,
|
||||||
statements: 29.5,
|
statements: 30,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react";
|
import React, { FunctionComponent } from "react";
|
||||||
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
|
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { NormalizedEventKey } from "./Constants";
|
|
||||||
|
|
||||||
export interface CollapsedResourceTreeProps {
|
export interface CollapsedResourceTreeProps {
|
||||||
toggleLeftPaneExpanded: () => void;
|
toggleLeftPaneExpanded: () => void;
|
||||||
@@ -12,21 +11,6 @@ export const CollapsedResourceTree: FunctionComponent<CollapsedResourceTreeProps
|
|||||||
toggleLeftPaneExpanded,
|
toggleLeftPaneExpanded,
|
||||||
isLeftPaneExpanded,
|
isLeftPaneExpanded,
|
||||||
}: CollapsedResourceTreeProps): JSX.Element => {
|
}: CollapsedResourceTreeProps): JSX.Element => {
|
||||||
const focusButton = useRef<HTMLLIElement>() as MutableRefObject<HTMLLIElement>;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (focusButton.current) {
|
|
||||||
focusButton.current.focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const onKeyPressToggleLeftPaneExpanded = (event: React.KeyboardEvent) => {
|
|
||||||
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
|
|
||||||
toggleLeftPaneExpanded();
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="mini" className={!isLeftPaneExpanded ? "mini toggle-mini" : "hiddenMain"}>
|
<div id="mini" className={!isLeftPaneExpanded ? "mini toggle-mini" : "hiddenMain"}>
|
||||||
<div className="main-nav nav">
|
<div className="main-nav nav">
|
||||||
@@ -37,14 +21,11 @@ export const CollapsedResourceTree: FunctionComponent<CollapsedResourceTreeProps
|
|||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
aria-label="Expand Tree"
|
aria-label="Expand Tree"
|
||||||
onClick={toggleLeftPaneExpanded}
|
|
||||||
onKeyPress={onKeyPressToggleLeftPaneExpanded}
|
|
||||||
ref={focusButton}
|
|
||||||
>
|
>
|
||||||
<span className="leftarrowCollapsed">
|
<span className="leftarrowCollapsed" onClick={toggleLeftPaneExpanded}>
|
||||||
<img className="arrowCollapsed" src={arrowLeftImg} alt="Expand" />
|
<img className="arrowCollapsed" src={arrowLeftImg} alt="Expand" />
|
||||||
</span>
|
</span>
|
||||||
<span className="collectionCollapsed">
|
<span className="collectionCollapsed" onClick={toggleLeftPaneExpanded}>
|
||||||
<span>{userContext.apiType} API</span>
|
<span>{userContext.apiType} API</span>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -340,6 +340,7 @@ export enum ConflictOperationType {
|
|||||||
|
|
||||||
export enum ConnectionStatusType {
|
export enum ConnectionStatusType {
|
||||||
Connecting = "Connecting",
|
Connecting = "Connecting",
|
||||||
|
Allocating = "Allocating",
|
||||||
Connected = "Connected",
|
Connected = "Connected",
|
||||||
Failed = "Connection Failed",
|
Failed = "Connection Failed",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,8 @@ import { resetConfigContext, updateConfigContext } from "../ConfigContext";
|
|||||||
import { DatabaseAccount } from "../Contracts/DataModels";
|
import { DatabaseAccount } from "../Contracts/DataModels";
|
||||||
import { Collection } from "../Contracts/ViewModels";
|
import { Collection } from "../Contracts/ViewModels";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
import { extractFeatures } from "../Platform/Hosted/extractFeatures";
|
|
||||||
import { updateUserContext } from "../UserContext";
|
import { updateUserContext } from "../UserContext";
|
||||||
import {
|
import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
|
||||||
deleteDocument,
|
|
||||||
getEndpoint,
|
|
||||||
getFeatureEndpointOrDefault,
|
|
||||||
queryDocuments,
|
|
||||||
readDocument,
|
|
||||||
updateDocument,
|
|
||||||
} from "./MongoProxyClient";
|
|
||||||
|
|
||||||
const databaseId = "testDB";
|
const databaseId = "testDB";
|
||||||
|
|
||||||
@@ -254,31 +246,4 @@ describe("MongoProxyClient", () => {
|
|||||||
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer");
|
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe("getFeatureEndpointOrDefault", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
resetConfigContext();
|
|
||||||
updateConfigContext({
|
|
||||||
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
|
|
||||||
});
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
"feature.mongoProxyEndpoint": "https://localhost:12901",
|
|
||||||
"feature.mongoProxyAPIs": "readDocument|createDocument",
|
|
||||||
});
|
|
||||||
const features = extractFeatures(params);
|
|
||||||
updateUserContext({
|
|
||||||
authType: AuthType.AAD,
|
|
||||||
features: features,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns a local endpoint", () => {
|
|
||||||
const endpoint = getFeatureEndpointOrDefault("readDocument");
|
|
||||||
expect(endpoint).toEqual("https://localhost:12901/api/mongo/explorer");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns a production endpoint", () => {
|
|
||||||
const endpoint = getFeatureEndpointOrDefault("deleteDocument");
|
|
||||||
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/mongo/explorer");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import * as DataModels from "../Contracts/DataModels";
|
|||||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
import { Collection } from "../Contracts/ViewModels";
|
import { Collection } from "../Contracts/ViewModels";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
import { hasFlag } from "../Platform/Hosted/extractFeatures";
|
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||||
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
||||||
@@ -79,7 +78,7 @@ export function queryDocuments(
|
|||||||
: "",
|
: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getFeatureEndpointOrDefault("resourcelist") || "";
|
const endpoint = getEndpoint() || "";
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
...defaultHeaders,
|
...defaultHeaders,
|
||||||
@@ -142,8 +141,7 @@ export function readDocument(
|
|||||||
: "",
|
: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getFeatureEndpointOrDefault("readDocument");
|
const endpoint = getEndpoint();
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -183,7 +181,7 @@ export function createDocument(
|
|||||||
pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "",
|
pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getFeatureEndpointOrDefault("createDocument");
|
const endpoint = getEndpoint();
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}/resourcelist?${queryString.stringify(params)}`, {
|
.fetch(`${endpoint}/resourcelist?${queryString.stringify(params)}`, {
|
||||||
@@ -227,7 +225,7 @@ export function updateDocument(
|
|||||||
? documentId.partitionKeyProperty
|
? documentId.partitionKeyProperty
|
||||||
: "",
|
: "",
|
||||||
};
|
};
|
||||||
const endpoint = getFeatureEndpointOrDefault("updateDocument");
|
const endpoint = getEndpoint();
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
||||||
@@ -268,7 +266,7 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
|
|||||||
? documentId.partitionKeyProperty
|
? documentId.partitionKeyProperty
|
||||||
: "",
|
: "",
|
||||||
};
|
};
|
||||||
const endpoint = getFeatureEndpointOrDefault("deleteDocument");
|
const endpoint = getEndpoint();
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
||||||
@@ -311,7 +309,7 @@ export function createMongoCollectionWithProxy(
|
|||||||
autoPilotThroughput: params.autoPilotMaxThroughput?.toString(),
|
autoPilotThroughput: params.autoPilotMaxThroughput?.toString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getFeatureEndpointOrDefault("createCollectionWithProxy");
|
const endpoint = getEndpoint();
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(
|
.fetch(
|
||||||
@@ -335,15 +333,8 @@ export function createMongoCollectionWithProxy(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFeatureEndpointOrDefault(feature: string): string {
|
export function getEndpoint(): string {
|
||||||
return hasFlag(userContext.features.mongoProxyAPIs, feature)
|
let url = (configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT) + "/api/mongo/explorer";
|
||||||
? getEndpoint(userContext.features.mongoProxyEndpoint)
|
|
||||||
: getEndpoint();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getEndpoint(customEndpoint?: string): string {
|
|
||||||
let url = customEndpoint ? customEndpoint : configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT;
|
|
||||||
url += "/api/mongo/explorer";
|
|
||||||
|
|
||||||
if (userContext.authType === AuthType.EncryptedToken) {
|
if (userContext.authType === AuthType.EncryptedToken) {
|
||||||
url = url.replace("api/mongo", "api/guest/mongo");
|
url = url.replace("api/mongo", "api/guest/mongo");
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react";
|
import React, { FunctionComponent } from "react";
|
||||||
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
|
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
|
||||||
import refreshImg from "../../images/refresh-cosmos.svg";
|
import refreshImg from "../../images/refresh-cosmos.svg";
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
@@ -6,7 +6,6 @@ import Explorer from "../Explorer/Explorer";
|
|||||||
import { ResourceTokenTree } from "../Explorer/Tree/ResourceTokenTree";
|
import { ResourceTokenTree } from "../Explorer/Tree/ResourceTokenTree";
|
||||||
import { ResourceTree } from "../Explorer/Tree/ResourceTree";
|
import { ResourceTree } from "../Explorer/Tree/ResourceTree";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { NormalizedEventKey } from "./Constants";
|
|
||||||
|
|
||||||
export interface ResourceTreeContainerProps {
|
export interface ResourceTreeContainerProps {
|
||||||
toggleLeftPaneExpanded: () => void;
|
toggleLeftPaneExpanded: () => void;
|
||||||
@@ -19,22 +18,6 @@ export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps
|
|||||||
isLeftPaneExpanded,
|
isLeftPaneExpanded,
|
||||||
container,
|
container,
|
||||||
}: ResourceTreeContainerProps): JSX.Element => {
|
}: ResourceTreeContainerProps): JSX.Element => {
|
||||||
const focusButton = useRef<HTMLLIElement>() as MutableRefObject<HTMLLIElement>;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isLeftPaneExpanded) {
|
|
||||||
if (focusButton.current) {
|
|
||||||
focusButton.current.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const onKeyPressToggleLeftPaneExpanded = (event: React.KeyboardEvent) => {
|
|
||||||
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
|
|
||||||
toggleLeftPaneExpanded();
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<div id="main" className={isLeftPaneExpanded ? "main" : "hiddenMain"}>
|
<div id="main" className={isLeftPaneExpanded ? "main" : "hiddenMain"}>
|
||||||
{/* Collections Window - - Start */}
|
{/* Collections Window - - Start */}
|
||||||
@@ -60,11 +43,9 @@ export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps
|
|||||||
id="expandToggleLeftPaneButton"
|
id="expandToggleLeftPaneButton"
|
||||||
role="button"
|
role="button"
|
||||||
onClick={toggleLeftPaneExpanded}
|
onClick={toggleLeftPaneExpanded}
|
||||||
onKeyPress={onKeyPressToggleLeftPaneExpanded}
|
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
aria-label="Collapse Tree"
|
aria-label="Collapse Tree"
|
||||||
title="Collapse Tree"
|
title="Collapse Tree"
|
||||||
ref={focusButton}
|
|
||||||
>
|
>
|
||||||
<img className="refreshcol1" src={arrowLeftImg} alt="Hide" />
|
<img className="refreshcol1" src={arrowLeftImg} alt="Hide" />
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const InfoTooltip: React.FunctionComponent<TooltipProps> = ({ children }:
|
|||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
<TooltipHost content={children}>
|
<TooltipHost content={children}>
|
||||||
<Icon iconName="Info" ariaLabel="Info" className="panelInfoIcon" tabIndex={0} />
|
<Icon iconName="Info" ariaLabel="Info" className="panelInfoIcon" />
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Icon, Label, Stack } from "@fluentui/react";
|
import { Icon, Label, Stack } from "@fluentui/react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { NormalizedEventKey } from "../../../Common/Constants";
|
|
||||||
import { accordionStackTokens } from "../Settings/SettingsRenderUtils";
|
import { accordionStackTokens } from "../Settings/SettingsRenderUtils";
|
||||||
|
|
||||||
export interface CollapsibleSectionProps {
|
export interface CollapsibleSectionProps {
|
||||||
@@ -31,13 +30,6 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onKeyPress = (event: React.KeyboardEvent) => {
|
|
||||||
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
|
|
||||||
this.toggleCollapsed();
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -47,11 +39,6 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
|
|||||||
verticalAlign="center"
|
verticalAlign="center"
|
||||||
tokens={accordionStackTokens}
|
tokens={accordionStackTokens}
|
||||||
onClick={this.toggleCollapsed}
|
onClick={this.toggleCollapsed}
|
||||||
onKeyPress={this.onKeyPress}
|
|
||||||
tabIndex={0}
|
|
||||||
aria-name="Advanced"
|
|
||||||
role="button"
|
|
||||||
aria-expanded={this.state.isExpanded}
|
|
||||||
>
|
>
|
||||||
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} />
|
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} />
|
||||||
<Label>{this.props.title}</Label>
|
<Label>{this.props.title}</Label>
|
||||||
|
|||||||
@@ -3,14 +3,9 @@
|
|||||||
exports[`CollapsibleSectionComponent renders 1`] = `
|
exports[`CollapsibleSectionComponent renders 1`] = `
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Stack
|
<Stack
|
||||||
aria-expanded={true}
|
|
||||||
aria-name="Advanced"
|
|
||||||
className="collapsibleSection"
|
className="collapsibleSection"
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyPress={[Function]}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
tokens={
|
tokens={
|
||||||
Object {
|
Object {
|
||||||
"childrenGap": 10,
|
"childrenGap": 10,
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export interface InputTypeaheadComponentProps {
|
|||||||
* Override default jquery-typeahead options
|
* Override default jquery-typeahead options
|
||||||
* WARNING: do not override input, source or callback to avoid breaking the components behavior.
|
* WARNING: do not override input, source or callback to avoid breaking the components behavior.
|
||||||
*/
|
*/
|
||||||
typeaheadOverrideOptions?: any;
|
typeaheadOverrideOptions?: { dynamic: boolean };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function gets called when pressing ENTER on the input box
|
* This function gets called when pressing ENTER on the input box
|
||||||
@@ -132,6 +132,7 @@ export class InputTypeaheadComponent extends React.Component<
|
|||||||
|
|
||||||
private filterChoiceByValue = (choices: Item[], searchKeyword: string): Item[] => {
|
private filterChoiceByValue = (choices: Item[], searchKeyword: string): Item[] => {
|
||||||
return choices.filter((choice) =>
|
return choices.filter((choice) =>
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
Object.keys(choice).some((key) => choice[key].toLowerCase().includes(searchKeyword.toLowerCase()))
|
Object.keys(choice).some((key) => choice[key].toLowerCase().includes(searchKeyword.toLowerCase()))
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -264,6 +264,6 @@ export class NotebookViewerComponent
|
|||||||
};
|
};
|
||||||
|
|
||||||
private reportAbuse = (): void => {
|
private reportAbuse = (): void => {
|
||||||
GalleryUtils.reportAbuse(this.props.junoClient, this.state.galleryItem, this, () => {});
|
GalleryUtils.reportAbuse(this.props.junoClient, this.state.galleryItem, this, () => ({}));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +1,45 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||||
|
import { AutopilotDocumentation, hoursInAMonth } from "../../../Shared/Constants";
|
||||||
|
import { Urls, StyleConstants } from "../../../Common/Constants";
|
||||||
|
import {
|
||||||
|
getPriceCurrency,
|
||||||
|
getCurrencySign,
|
||||||
|
getAutoscalePricePerRu,
|
||||||
|
getMultimasterMultiplier,
|
||||||
|
computeRUUsagePriceHourly,
|
||||||
|
getPricePerRu,
|
||||||
|
estimatedCostDisclaimer,
|
||||||
|
} from "../../../Utils/PricingUtils";
|
||||||
import {
|
import {
|
||||||
DetailsList,
|
|
||||||
DetailsListLayoutMode,
|
|
||||||
DetailsRow,
|
|
||||||
ICheckboxStyles,
|
|
||||||
IChoiceGroupStyles,
|
|
||||||
IColumn,
|
|
||||||
IDetailsColumnStyles,
|
|
||||||
IDetailsListStyles,
|
|
||||||
IDetailsRowProps,
|
|
||||||
IDetailsRowStyles,
|
|
||||||
IDropdownStyles,
|
|
||||||
IMessageBarStyles,
|
|
||||||
ISeparatorStyles,
|
|
||||||
IStackProps,
|
|
||||||
IStackStyles,
|
|
||||||
IStackTokens,
|
|
||||||
ITextFieldStyles,
|
ITextFieldStyles,
|
||||||
ITextStyles,
|
ICheckboxStyles,
|
||||||
|
IStackProps,
|
||||||
|
IStackTokens,
|
||||||
|
IChoiceGroupStyles,
|
||||||
Link,
|
Link,
|
||||||
|
Text,
|
||||||
|
IMessageBarStyles,
|
||||||
|
ITextStyles,
|
||||||
|
IDetailsRowStyles,
|
||||||
|
IStackStyles,
|
||||||
|
IDetailsListStyles,
|
||||||
|
IDropdownStyles,
|
||||||
|
ISeparatorStyles,
|
||||||
MessageBar,
|
MessageBar,
|
||||||
MessageBarType,
|
MessageBarType,
|
||||||
SelectionMode,
|
Stack,
|
||||||
Spinner,
|
Spinner,
|
||||||
SpinnerSize,
|
SpinnerSize,
|
||||||
Stack,
|
DetailsList,
|
||||||
Text,
|
IColumn,
|
||||||
|
SelectionMode,
|
||||||
|
DetailsListLayoutMode,
|
||||||
|
IDetailsRowProps,
|
||||||
|
DetailsRow,
|
||||||
|
IDetailsColumnStyles,
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
import * as React from "react";
|
import { isDirtyTypes, isDirty } from "./SettingsUtils";
|
||||||
import { StyleConstants, Urls } from "../../../Common/Constants";
|
|
||||||
import { AutopilotDocumentation, hoursInAMonth } from "../../../Shared/Constants";
|
|
||||||
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
|
||||||
import {
|
|
||||||
computeRUUsagePriceHourly,
|
|
||||||
estimatedCostDisclaimer,
|
|
||||||
getAutoscalePricePerRu,
|
|
||||||
getCurrencySign,
|
|
||||||
getMultimasterMultiplier,
|
|
||||||
getPriceCurrency,
|
|
||||||
getPricePerRu,
|
|
||||||
} from "../../../Utils/PricingUtils";
|
|
||||||
import { isDirty, isDirtyTypes } from "./SettingsUtils";
|
|
||||||
|
|
||||||
export interface EstimatedSpendingDisplayProps {
|
export interface EstimatedSpendingDisplayProps {
|
||||||
costType: JSX.Element;
|
costType: JSX.Element;
|
||||||
@@ -223,15 +223,14 @@ export const getRuPriceBreakdown = (
|
|||||||
multimasterEnabled: isMultimaster,
|
multimasterEnabled: isMultimaster,
|
||||||
isAutoscale: isAutoscale,
|
isAutoscale: isAutoscale,
|
||||||
});
|
});
|
||||||
const multimasterMultiplier = getMultimasterMultiplier(numberOfRegions, isMultimaster);
|
const basePricePerRu: number = isAutoscale
|
||||||
const pricePerRu: number = isAutoscale
|
? getAutoscalePricePerRu(serverId, getMultimasterMultiplier(numberOfRegions, isMultimaster))
|
||||||
? getAutoscalePricePerRu(serverId, multimasterMultiplier)
|
: getPricePerRu(serverId);
|
||||||
: getPricePerRu(serverId, multimasterMultiplier);
|
|
||||||
return {
|
return {
|
||||||
hourlyPrice,
|
hourlyPrice: hourlyPrice,
|
||||||
dailyPrice: hourlyPrice * 24,
|
dailyPrice: hourlyPrice * 24,
|
||||||
monthlyPrice: hourlyPrice * hoursInAMonth,
|
monthlyPrice: hourlyPrice * hoursInAMonth,
|
||||||
pricePerRu,
|
pricePerRu: basePricePerRu * getMultimasterMultiplier(numberOfRegions, isMultimaster),
|
||||||
currency: getPriceCurrency(serverId),
|
currency: getPriceCurrency(serverId),
|
||||||
currencySign: getCurrencySign(serverId),
|
currencySign: getCurrencySign(serverId),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { userContext } from "../../../../UserContext";
|
|||||||
import {
|
import {
|
||||||
calculateEstimateNumber,
|
calculateEstimateNumber,
|
||||||
computeRUUsagePriceHourly,
|
computeRUUsagePriceHourly,
|
||||||
estimatedCostDisclaimer,
|
|
||||||
getAutoscalePricePerRu,
|
getAutoscalePricePerRu,
|
||||||
getCurrencySign,
|
getCurrencySign,
|
||||||
getMultimasterMultiplier,
|
getMultimasterMultiplier,
|
||||||
@@ -43,9 +42,11 @@ export const CostEstimateText: FunctionComponent<CostEstimateTextProps> = ({
|
|||||||
const currency: string = getPriceCurrency(serverId);
|
const currency: string = getPriceCurrency(serverId);
|
||||||
const currencySign: string = getCurrencySign(serverId);
|
const currencySign: string = getCurrencySign(serverId);
|
||||||
const multiplier = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
|
const multiplier = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
|
||||||
const pricePerRu = isAutoscale ? getAutoscalePricePerRu(serverId, multiplier) : getPricePerRu(serverId, multiplier);
|
const pricePerRu = isAutoscale
|
||||||
|
? getAutoscalePricePerRu(serverId, multiplier) * multiplier
|
||||||
|
: getPricePerRu(serverId) * multiplier;
|
||||||
|
|
||||||
const iconWithEstimatedCostDisclaimer: JSX.Element = <InfoTooltip>{estimatedCostDisclaimer}</InfoTooltip>;
|
const iconWithEstimatedCostDisclaimer: JSX.Element = <InfoTooltip>PricingUtils.estimatedCostDisclaimer</InfoTooltip>;
|
||||||
|
|
||||||
if (isAutoscale) {
|
if (isAutoscale) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -118,7 +118,6 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
<input
|
<input
|
||||||
className="throughputInputRadioBtn"
|
className="throughputInputRadioBtn"
|
||||||
aria-label="Autoscale mode"
|
aria-label="Autoscale mode"
|
||||||
aria-required={true}
|
|
||||||
checked={isAutoscaleSelected}
|
checked={isAutoscaleSelected}
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
role="radio"
|
||||||
@@ -132,7 +131,6 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
aria-label="Manual mode"
|
aria-label="Manual mode"
|
||||||
checked={!isAutoscaleSelected}
|
checked={!isAutoscaleSelected}
|
||||||
type="radio"
|
type="radio"
|
||||||
aria-required={true}
|
|
||||||
role="radio"
|
role="radio"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={(e) => handleOnChangeMode(e, "Manual")}
|
onChange={(e) => handleOnChangeMode(e, "Manual")}
|
||||||
|
|||||||
@@ -345,14 +345,12 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
ariaLabel="Info"
|
ariaLabel="Info"
|
||||||
className="panelInfoIcon"
|
className="panelInfoIcon"
|
||||||
iconName="Info"
|
iconName="Info"
|
||||||
tabIndex={0}
|
|
||||||
>
|
>
|
||||||
<IconBase
|
<IconBase
|
||||||
ariaLabel="Info"
|
ariaLabel="Info"
|
||||||
className="panelInfoIcon"
|
className="panelInfoIcon"
|
||||||
iconName="Info"
|
iconName="Info"
|
||||||
styles={[Function]}
|
styles={[Function]}
|
||||||
tabIndex={0}
|
|
||||||
theme={
|
theme={
|
||||||
Object {
|
Object {
|
||||||
"disableGlobalClassNames": false,
|
"disableGlobalClassNames": false,
|
||||||
@@ -632,7 +630,6 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
className="panelInfoIcon root-57"
|
className="panelInfoIcon root-57"
|
||||||
data-icon-name="Info"
|
data-icon-name="Info"
|
||||||
role="img"
|
role="img"
|
||||||
tabIndex={0}
|
|
||||||
>
|
>
|
||||||
|
|
||||||
</i>
|
</i>
|
||||||
@@ -654,7 +651,6 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-label="Autoscale mode"
|
aria-label="Autoscale mode"
|
||||||
aria-required={true}
|
|
||||||
checked={true}
|
checked={true}
|
||||||
className="throughputInputRadioBtn"
|
className="throughputInputRadioBtn"
|
||||||
key=".0:$.0"
|
key=".0:$.0"
|
||||||
@@ -671,7 +667,6 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
aria-label="Manual mode"
|
aria-label="Manual mode"
|
||||||
aria-required={true}
|
|
||||||
checked={false}
|
checked={false}
|
||||||
className="throughputInputRadioBtn"
|
className="throughputInputRadioBtn"
|
||||||
key=".0:$.2"
|
key=".0:$.2"
|
||||||
@@ -1332,14 +1327,12 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
ariaLabel="Info"
|
ariaLabel="Info"
|
||||||
className="panelInfoIcon"
|
className="panelInfoIcon"
|
||||||
iconName="Info"
|
iconName="Info"
|
||||||
tabIndex={0}
|
|
||||||
>
|
>
|
||||||
<IconBase
|
<IconBase
|
||||||
ariaLabel="Info"
|
ariaLabel="Info"
|
||||||
className="panelInfoIcon"
|
className="panelInfoIcon"
|
||||||
iconName="Info"
|
iconName="Info"
|
||||||
styles={[Function]}
|
styles={[Function]}
|
||||||
tabIndex={0}
|
|
||||||
theme={
|
theme={
|
||||||
Object {
|
Object {
|
||||||
"disableGlobalClassNames": false,
|
"disableGlobalClassNames": false,
|
||||||
@@ -1619,7 +1612,6 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
className="panelInfoIcon root-57"
|
className="panelInfoIcon root-57"
|
||||||
data-icon-name="Info"
|
data-icon-name="Info"
|
||||||
role="img"
|
role="img"
|
||||||
tabIndex={0}
|
|
||||||
>
|
>
|
||||||
|
|
||||||
</i>
|
</i>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import _ from "underscore";
|
|||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
|
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
|
||||||
import * as Constants from "../Common/Constants";
|
import * as Constants from "../Common/Constants";
|
||||||
|
import { ConnectionStatusType } from "../Common/Constants";
|
||||||
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 { isPublicInternetAccessAllowed } from "../Common/DatabaseAccountUtility";
|
import { isPublicInternetAccessAllowed } from "../Common/DatabaseAccountUtility";
|
||||||
@@ -346,6 +347,10 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
this._isInitializingNotebooks = true;
|
this._isInitializingNotebooks = true;
|
||||||
if (userContext.features.phoenix) {
|
if (userContext.features.phoenix) {
|
||||||
|
const connectionStatus: DataModels.ContainerConnectionInfo = {
|
||||||
|
status: ConnectionStatusType.Allocating,
|
||||||
|
};
|
||||||
|
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||||
const provisionData = {
|
const provisionData = {
|
||||||
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
|
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
|
||||||
resourceId: userContext.databaseAccount.id,
|
resourceId: userContext.databaseAccount.id,
|
||||||
@@ -356,6 +361,9 @@ export default class Explorer {
|
|||||||
};
|
};
|
||||||
const connectionInfo = await this.phoenixClient.containerConnectionInfo(provisionData);
|
const connectionInfo = await this.phoenixClient.containerConnectionInfo(provisionData);
|
||||||
if (connectionInfo.data && connectionInfo.data.notebookServerUrl) {
|
if (connectionInfo.data && connectionInfo.data.notebookServerUrl) {
|
||||||
|
connectionStatus.status = ConnectionStatusType.Connected;
|
||||||
|
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||||
|
|
||||||
useNotebook.getState().setNotebookServerInfo({
|
useNotebook.getState().setNotebookServerInfo({
|
||||||
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.data.notebookServerUrl,
|
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.data.notebookServerUrl,
|
||||||
authToken: userContext.features.notebookServerToken || connectionInfo.data.notebookAuthToken,
|
authToken: userContext.features.notebookServerToken || connectionInfo.data.notebookAuthToken,
|
||||||
|
|||||||
@@ -330,10 +330,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
const partitionKeyProperty = this.props.collectionPartitionKeyProperty;
|
const partitionKeyProperty = this.props.collectionPartitionKeyProperty;
|
||||||
|
|
||||||
// aggregate all the properties, remove dropped ones
|
// aggregate all the properties, remove dropped ones
|
||||||
let finalProperties = editedProperties.existingProperties.concat(editedProperties.addedProperties);
|
const finalProperties = editedProperties.existingProperties.concat(editedProperties.addedProperties);
|
||||||
|
|
||||||
// Compose the query
|
// Compose the query
|
||||||
let pkId = editedProperties.pkId;
|
const pkId = editedProperties.pkId;
|
||||||
let updateQueryFragment = "";
|
let updateQueryFragment = "";
|
||||||
|
|
||||||
finalProperties.forEach((p) => {
|
finalProperties.forEach((p) => {
|
||||||
@@ -473,7 +473,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let pairs: any[] = data;
|
const pairs: any[] = data;
|
||||||
for (let i = 0; i < pairs.length; i++) {
|
for (let i = 0; i < pairs.length; i++) {
|
||||||
const item = pairs[i];
|
const item = pairs[i];
|
||||||
if (
|
if (
|
||||||
@@ -772,8 +772,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let edge = edges[0];
|
const edge = edges[0];
|
||||||
let graphData = this.originalGraphData;
|
const graphData = this.originalGraphData;
|
||||||
graphData.addEdge(edge);
|
graphData.addEdge(edge);
|
||||||
|
|
||||||
// Allow loadNeighbors to load list new edge
|
// Allow loadNeighbors to load list new edge
|
||||||
@@ -803,7 +803,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
public removeEdge(edgeId: string): Q.Promise<any> {
|
public removeEdge(edgeId: string): Q.Promise<any> {
|
||||||
return this.submitToBackend(`g.E('${GraphUtil.escapeSingleQuotes(edgeId)}').drop()`).then(
|
return this.submitToBackend(`g.E('${GraphUtil.escapeSingleQuotes(edgeId)}').drop()`).then(
|
||||||
() => {
|
() => {
|
||||||
let graphData = this.originalGraphData;
|
const graphData = this.originalGraphData;
|
||||||
graphData.removeEdge(edgeId, false);
|
graphData.removeEdge(edgeId, false);
|
||||||
this.updateGraphData(graphData, this.state.igraphConfig);
|
this.updateGraphData(graphData, this.state.igraphConfig);
|
||||||
},
|
},
|
||||||
@@ -826,9 +826,9 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let vertices: any[] = data;
|
const vertices: any[] = data;
|
||||||
if (vertices.length > 0) {
|
if (vertices.length > 0) {
|
||||||
let v0 = vertices[0];
|
const v0 = vertices[0];
|
||||||
if (!v0.hasOwnProperty("id") || !v0.hasOwnProperty("type") || v0.type !== "vertex") {
|
if (!v0.hasOwnProperty("id") || !v0.hasOwnProperty("type") || v0.type !== "vertex") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -933,7 +933,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
throw { title: err };
|
throw { title: err };
|
||||||
}
|
}
|
||||||
|
|
||||||
let vertex = vertices[0];
|
const vertex = vertices[0];
|
||||||
const graphData = this.originalGraphData;
|
const graphData = this.originalGraphData;
|
||||||
graphData.addVertex(vertex);
|
graphData.addVertex(vertex);
|
||||||
this.updateGraphData(graphData, this.state.igraphConfig);
|
this.updateGraphData(graphData, this.state.igraphConfig);
|
||||||
@@ -1082,7 +1082,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
public static reportToConsole(type: ConsoleDataType.Info, msg: string, ...errorData: any[]): void;
|
public static reportToConsole(type: ConsoleDataType.Info, msg: string, ...errorData: any[]): void;
|
||||||
public static reportToConsole(type: ConsoleDataType.Error, msg: string, ...errorData: any[]): void;
|
public static reportToConsole(type: ConsoleDataType.Error, msg: string, ...errorData: any[]): void;
|
||||||
public static reportToConsole(type: ConsoleDataType, msg: string, ...errorData: any[]): void | (() => void) {
|
public static reportToConsole(type: ConsoleDataType, msg: string, ...errorData: any[]): void | (() => void) {
|
||||||
let errorDataStr: string = "";
|
let errorDataStr = "";
|
||||||
if (errorData && errorData.length > 0) {
|
if (errorData && errorData.length > 0) {
|
||||||
console.error(msg, errorData);
|
console.error(msg, errorData);
|
||||||
errorDataStr = ": " + JSON.stringify(errorData);
|
errorDataStr = ": " + JSON.stringify(errorData);
|
||||||
@@ -1224,7 +1224,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
return $.map(
|
return $.map(
|
||||||
this.state.rootMap,
|
this.state.rootMap,
|
||||||
(value: any, index: number): LeftPane.CaptionId => {
|
(value: any, index: number): LeftPane.CaptionId => {
|
||||||
let result = GraphData.GraphData.getNodePropValue(value, key);
|
const result = GraphData.GraphData.getNodePropValue(value, key);
|
||||||
return {
|
return {
|
||||||
caption: result !== undefined ? result : value.id,
|
caption: result !== undefined ? result : value.id,
|
||||||
id: value.id,
|
id: value.id,
|
||||||
@@ -1388,7 +1388,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
* @return id
|
* @return id
|
||||||
*/
|
*/
|
||||||
public static getPkIdFromDocumentId(d: DataModels.DocumentId, collectionPartitionKeyProperty: string): string {
|
public static getPkIdFromDocumentId(d: DataModels.DocumentId, collectionPartitionKeyProperty: string): string {
|
||||||
let { id } = d;
|
const { id } = d;
|
||||||
if (typeof id !== "string") {
|
if (typeof id !== "string") {
|
||||||
const error = `Vertex id is not a string: ${JSON.stringify(id)}.`;
|
const error = `Vertex id is not a string: ${JSON.stringify(id)}.`;
|
||||||
logConsoleError(error);
|
logConsoleError(error);
|
||||||
@@ -1425,7 +1425,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
}"] AS p FROM c WHERE NOT IS_DEFINED(c._isEdge)`;
|
}"] AS p FROM c WHERE NOT IS_DEFINED(c._isEdge)`;
|
||||||
return this.executeNonPagedDocDbQuery(q).then(
|
return this.executeNonPagedDocDbQuery(q).then(
|
||||||
(documents: DataModels.DocumentId[]) => {
|
(documents: DataModels.DocumentId[]) => {
|
||||||
let possibleVertices = [] as PossibleVertex[];
|
const possibleVertices = [] as PossibleVertex[];
|
||||||
$.each(documents, (index: number, item: any) => {
|
$.each(documents, (index: number, item: any) => {
|
||||||
if (highlightedNodeId && item.id === highlightedNodeId) {
|
if (highlightedNodeId && item.id === highlightedNodeId) {
|
||||||
// Exclude highlighed node in the list
|
// Exclude highlighed node in the list
|
||||||
@@ -1463,16 +1463,16 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
* @return promise when done
|
* @return promise when done
|
||||||
*/
|
*/
|
||||||
private editGraphEdges(editedEdges: EditedEdges): Q.Promise<any> {
|
private editGraphEdges(editedEdges: EditedEdges): Q.Promise<any> {
|
||||||
let promises = [];
|
const promises = [];
|
||||||
// Drop edges
|
// Drop edges
|
||||||
for (let i = 0; i < editedEdges.droppedIds.length; i++) {
|
for (let i = 0; i < editedEdges.droppedIds.length; i++) {
|
||||||
let id = editedEdges.droppedIds[i];
|
const id = editedEdges.droppedIds[i];
|
||||||
promises.push(this.removeEdge(id));
|
promises.push(this.removeEdge(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add edges
|
// Add edges
|
||||||
for (let i = 0; i < editedEdges.addedEdges.length; i++) {
|
for (let i = 0; i < editedEdges.addedEdges.length; i++) {
|
||||||
let e = editedEdges.addedEdges[i];
|
const e = editedEdges.addedEdges[i];
|
||||||
promises.push(
|
promises.push(
|
||||||
this.createNewEdge(e).then(() => {
|
this.createNewEdge(e).then(() => {
|
||||||
// Reload neighbors in case we linked to a vertex that isn't loaded in the graph
|
// Reload neighbors in case we linked to a vertex that isn't loaded in the graph
|
||||||
@@ -1533,7 +1533,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
private collectNodeProperties(vertices: GraphData.GremlinVertex[]) {
|
private collectNodeProperties(vertices: GraphData.GremlinVertex[]) {
|
||||||
const props = {} as any; // Hashset
|
const props = {} as any; // Hashset
|
||||||
$.each(vertices, (index: number, item: GraphData.GremlinVertex) => {
|
$.each(vertices, (index: number, item: GraphData.GremlinVertex) => {
|
||||||
for (var p in item) {
|
for (const p in item) {
|
||||||
// DocDB: Exclude type because it's always 'vertex'
|
// DocDB: Exclude type because it's always 'vertex'
|
||||||
if (p !== "type" && typeof (item as any)[p] === "string") {
|
if (p !== "type" && typeof (item as any)[p] === "string") {
|
||||||
props[p] = true;
|
props[p] = true;
|
||||||
@@ -1543,7 +1543,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
if (item.hasOwnProperty("properties")) {
|
if (item.hasOwnProperty("properties")) {
|
||||||
// TODO This is DocDB-graph specific
|
// TODO This is DocDB-graph specific
|
||||||
// Assume each property value is [{value:... }]
|
// Assume each property value is [{value:... }]
|
||||||
for (var f in item.properties) {
|
for (const f in item.properties) {
|
||||||
props[f] = true;
|
props[f] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1570,21 +1570,21 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = this.originalGraphData.getVertexById(id);
|
const data = this.originalGraphData.getVertexById(id);
|
||||||
|
|
||||||
// A bit of translation to make it easier to display
|
// A bit of translation to make it easier to display
|
||||||
let props: { [id: string]: ViewModels.GremlinPropertyValueType[] } = {};
|
const props: { [id: string]: ViewModels.GremlinPropertyValueType[] } = {};
|
||||||
for (let p in data.properties) {
|
for (const p in data.properties) {
|
||||||
props[p] = data.properties[p].map((gremlinProperty) => gremlinProperty.value);
|
props[p] = data.properties[p].map((gremlinProperty) => gremlinProperty.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// update neighbors
|
// update neighbors
|
||||||
let sources: NeighborVertexBasicInfo[] = [];
|
const sources: NeighborVertexBasicInfo[] = [];
|
||||||
let targets: NeighborVertexBasicInfo[] = [];
|
const targets: NeighborVertexBasicInfo[] = [];
|
||||||
this.props.onResetDefaultGraphConfigValues();
|
this.props.onResetDefaultGraphConfigValues();
|
||||||
let nodeCaption = this.state.igraphConfigUiData.nodeCaptionChoice;
|
const nodeCaption = this.state.igraphConfigUiData.nodeCaptionChoice;
|
||||||
this.updateSelectedNodeNeighbors(data.id, nodeCaption, sources, targets);
|
this.updateSelectedNodeNeighbors(data.id, nodeCaption, sources, targets);
|
||||||
let sData: GraphHighlightedNodeData = {
|
const sData: GraphHighlightedNodeData = {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
label: data.label,
|
label: data.label,
|
||||||
properties: props,
|
properties: props,
|
||||||
@@ -1611,16 +1611,16 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
targets: NeighborVertexBasicInfo[]
|
targets: NeighborVertexBasicInfo[]
|
||||||
): void {
|
): void {
|
||||||
// update neighbors
|
// update neighbors
|
||||||
let gd = this.originalGraphData;
|
const gd = this.originalGraphData;
|
||||||
let v = gd.getVertexById(id);
|
const v = gd.getVertexById(id);
|
||||||
|
|
||||||
// Clear the array while keeping the references
|
// Clear the array while keeping the references
|
||||||
sources.length = 0;
|
sources.length = 0;
|
||||||
targets.length = 0;
|
targets.length = 0;
|
||||||
|
|
||||||
let possibleEdgeLabels = {} as any; // Collect all edge labels in a hashset
|
const possibleEdgeLabels = {} as any; // Collect all edge labels in a hashset
|
||||||
|
|
||||||
for (let p in v.inE) {
|
for (const p in v.inE) {
|
||||||
possibleEdgeLabels[p] = true;
|
possibleEdgeLabels[p] = true;
|
||||||
const edges = v.inE[p];
|
const edges = v.inE[p];
|
||||||
$.each(edges, (index: number, edge: GraphData.GremlinShortInEdge) => {
|
$.each(edges, (index: number, edge: GraphData.GremlinShortInEdge) => {
|
||||||
@@ -1629,7 +1629,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
// If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet
|
// If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
const caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
||||||
sources.push({
|
sources.push({
|
||||||
name: caption,
|
name: caption,
|
||||||
id: neighborId,
|
id: neighborId,
|
||||||
@@ -1639,7 +1639,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let p in v.outE) {
|
for (const p in v.outE) {
|
||||||
possibleEdgeLabels[p] = true;
|
possibleEdgeLabels[p] = true;
|
||||||
const edges = v.outE[p];
|
const edges = v.outE[p];
|
||||||
$.each(edges, (index: number, edge: GraphData.GremlinShortOutEdge) => {
|
$.each(edges, (index: number, edge: GraphData.GremlinShortOutEdge) => {
|
||||||
@@ -1648,7 +1648,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
// If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet
|
// If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
const caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
||||||
targets.push({
|
targets.push({
|
||||||
name: caption,
|
name: caption,
|
||||||
id: neighborId,
|
id: neighborId,
|
||||||
@@ -1681,20 +1681,20 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let updatedVertex = vertices[0];
|
const updatedVertex = vertices[0];
|
||||||
if (this.originalGraphData.hasVertexId(updatedVertex.id)) {
|
if (this.originalGraphData.hasVertexId(updatedVertex.id)) {
|
||||||
let currentVertex = this.originalGraphData.getVertexById(updatedVertex.id);
|
const currentVertex = this.originalGraphData.getVertexById(updatedVertex.id);
|
||||||
// Copy updated properties
|
// Copy updated properties
|
||||||
if (currentVertex.hasOwnProperty("properties")) {
|
if (currentVertex.hasOwnProperty("properties")) {
|
||||||
delete currentVertex["properties"];
|
delete currentVertex["properties"];
|
||||||
}
|
}
|
||||||
for (var p in updatedVertex) {
|
for (const p in updatedVertex) {
|
||||||
(currentVertex as any)[p] = updatedVertex[p];
|
(currentVertex as any)[p] = updatedVertex[p];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO This kind of assumes saveVertexProperty is done from property panes.
|
// TODO This kind of assumes saveVertexProperty is done from property panes.
|
||||||
let hn = this.state.highlightedNode;
|
const hn = this.state.highlightedNode;
|
||||||
if (hn && hn.id === updatedVertex.id) {
|
if (hn && hn.id === updatedVertex.id) {
|
||||||
this.updatePropertiesPane(hn.id);
|
this.updatePropertiesPane(hn.id);
|
||||||
}
|
}
|
||||||
@@ -1708,7 +1708,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
igraphConfig?: IGraphConfig
|
igraphConfig?: IGraphConfig
|
||||||
) {
|
) {
|
||||||
this.originalGraphData = graphData;
|
this.originalGraphData = graphData;
|
||||||
let gd = JSON.parse(JSON.stringify(this.originalGraphData));
|
const gd = JSON.parse(JSON.stringify(this.originalGraphData));
|
||||||
if (!this.d3ForceGraph) {
|
if (!this.d3ForceGraph) {
|
||||||
console.warn("Attempting to update graph, but d3ForceGraph not initialized, yet.");
|
console.warn("Attempting to update graph, but d3ForceGraph not initialized, yet.");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export class LeftPaneComponent extends React.Component<LeftPaneComponentProps> {
|
|||||||
className={className}
|
className={className}
|
||||||
as="tr"
|
as="tr"
|
||||||
aria-label={node.caption}
|
aria-label={node.caption}
|
||||||
onActivated={(e) => this.props.onRootNodeSelected(node.id)}
|
onActivated={() => this.props.onRootNodeSelected(node.id)}
|
||||||
key={node.id}
|
key={node.id}
|
||||||
>
|
>
|
||||||
<td className="resultItem">
|
<td className="resultItem">
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from "react";
|
|
||||||
import { mount, ReactWrapper } from "enzyme";
|
import { mount, ReactWrapper } from "enzyme";
|
||||||
import * as Q from "q";
|
import * as Q from "q";
|
||||||
import { NodePropertiesComponent, NodePropertiesComponentProps, Mode } from "./NodePropertiesComponent";
|
import React from "react";
|
||||||
import { GraphHighlightedNodeData, EditedProperties, EditedEdges, PossibleVertex } from "./GraphExplorer";
|
import { GraphHighlightedNodeData, PossibleVertex } from "./GraphExplorer";
|
||||||
|
import { Mode, NodePropertiesComponent, NodePropertiesComponentProps } from "./NodePropertiesComponent";
|
||||||
|
|
||||||
describe("Property pane", () => {
|
describe("Property pane", () => {
|
||||||
const title = "My Title";
|
const title = "My Title";
|
||||||
@@ -37,17 +37,25 @@ describe("Property pane", () => {
|
|||||||
return {
|
return {
|
||||||
expandedTitle: title,
|
expandedTitle: title,
|
||||||
isCollapsed: false,
|
isCollapsed: false,
|
||||||
onCollapsedChanged: (newValue: boolean): void => {},
|
onCollapsedChanged: (): void => {
|
||||||
|
("");
|
||||||
|
},
|
||||||
node: highlightedNode,
|
node: highlightedNode,
|
||||||
getPkIdFromNodeData: (v: GraphHighlightedNodeData): string => null,
|
getPkIdFromNodeData: (): string => undefined,
|
||||||
collectionPartitionKeyProperty: null,
|
collectionPartitionKeyProperty: undefined,
|
||||||
updateVertexProperties: (editedProperties: EditedProperties): Q.Promise<void> => Q.resolve(),
|
updateVertexProperties: (): Q.Promise<void> => Q.resolve(),
|
||||||
selectNode: (id: string): void => {},
|
selectNode: (): void => {
|
||||||
updatePossibleVertices: (): Q.Promise<PossibleVertex[]> => Q.resolve(null),
|
("");
|
||||||
possibleEdgeLabels: null,
|
},
|
||||||
editGraphEdges: (editedEdges: EditedEdges): Q.Promise<any> => Q.resolve(),
|
updatePossibleVertices: (): Q.Promise<PossibleVertex[]> => Q.resolve(undefined),
|
||||||
deleteHighlightedNode: (): void => {},
|
possibleEdgeLabels: undefined,
|
||||||
onModeChanged: (newMode: Mode): void => {},
|
editGraphEdges: (): Q.Promise<unknown> => Q.resolve(),
|
||||||
|
deleteHighlightedNode: (): void => {
|
||||||
|
("");
|
||||||
|
},
|
||||||
|
onModeChanged: (): void => {
|
||||||
|
("");
|
||||||
|
},
|
||||||
viewMode: Mode.READONLY_PROP,
|
viewMode: Mode.READONLY_PROP,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export interface NodePropertiesComponentProps {
|
|||||||
selectNode: (id: string) => void;
|
selectNode: (id: string) => void;
|
||||||
updatePossibleVertices: () => Q.Promise<PossibleVertex[]>;
|
updatePossibleVertices: () => Q.Promise<PossibleVertex[]>;
|
||||||
possibleEdgeLabels: Item[];
|
possibleEdgeLabels: Item[];
|
||||||
editGraphEdges: (editedEdges: EditedEdges) => Q.Promise<any>;
|
editGraphEdges: (editedEdges: EditedEdges) => Q.Promise<unknown>;
|
||||||
deleteHighlightedNode: () => void;
|
deleteHighlightedNode: () => void;
|
||||||
onModeChanged: (newMode: Mode) => void;
|
onModeChanged: (newMode: Mode) => void;
|
||||||
viewMode: Mode; // If viewMode is specified in parent, keep state in sync with it
|
viewMode: Mode; // If viewMode is specified in parent, keep state in sync with it
|
||||||
@@ -72,7 +72,7 @@ export class NodePropertiesComponent extends React.Component<
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
editedProperties: {
|
editedProperties: {
|
||||||
pkId: null,
|
pkId: undefined,
|
||||||
readOnlyProperties: [],
|
readOnlyProperties: [],
|
||||||
existingProperties: [],
|
existingProperties: [],
|
||||||
addedProperties: [],
|
addedProperties: [],
|
||||||
@@ -98,15 +98,12 @@ export class NodePropertiesComponent extends React.Component<
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getDerivedStateFromProps(
|
public static getDerivedStateFromProps(props: NodePropertiesComponentProps): Partial<NodePropertiesComponentState> {
|
||||||
props: NodePropertiesComponentProps,
|
|
||||||
state: NodePropertiesComponentState
|
|
||||||
): Partial<NodePropertiesComponentState> {
|
|
||||||
if (props.viewMode !== Mode.READONLY_PROP) {
|
if (props.viewMode !== Mode.READONLY_PROP) {
|
||||||
return { isDeleteConfirm: false };
|
return { isDeleteConfirm: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
@@ -137,11 +134,14 @@ export class NodePropertiesComponent extends React.Component<
|
|||||||
* Get type option. Limit to string, number or boolean
|
* Get type option. Limit to string, number or boolean
|
||||||
* @param value
|
* @param value
|
||||||
*/
|
*/
|
||||||
private static getTypeOption(value: any): ViewModels.InputPropertyValueTypeString {
|
private static getTypeOption(
|
||||||
if (value == null) {
|
value: null | string | number | undefined | boolean
|
||||||
|
): ViewModels.InputPropertyValueTypeString {
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
if (value === null) {
|
||||||
return "null";
|
return "null";
|
||||||
}
|
}
|
||||||
let type = typeof value;
|
const type = typeof value;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "number":
|
case "number":
|
||||||
case "boolean":
|
case "boolean":
|
||||||
@@ -173,9 +173,10 @@ export class NodePropertiesComponent extends React.Component<
|
|||||||
|
|
||||||
const existingProps: ViewModels.InputProperty[] = [];
|
const existingProps: ViewModels.InputProperty[] = [];
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-prototype-builtins
|
||||||
if (this.props.node.hasOwnProperty("properties")) {
|
if (this.props.node.hasOwnProperty("properties")) {
|
||||||
const hProps = this.props.node["properties"];
|
const hProps = this.props.node["properties"];
|
||||||
for (let p in hProps) {
|
for (const p in hProps) {
|
||||||
const propValues = hProps[p];
|
const propValues = hProps[p];
|
||||||
(p === partitionKeyProperty ? readOnlyProps : existingProps).push({
|
(p === partitionKeyProperty ? readOnlyProps : existingProps).push({
|
||||||
key: p,
|
key: p,
|
||||||
@@ -437,7 +438,7 @@ export class NodePropertiesComponent extends React.Component<
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import create, { UseStore } from "zustand";
|
|||||||
import { StyleConstants } from "../../../Common/Constants";
|
import { StyleConstants } from "../../../Common/Constants";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import { useTabs } from "../../../hooks/useTabs";
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
import { userContext } from "../../../UserContext";
|
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { useSelectedNode } from "../../useSelectedNode";
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
@@ -55,13 +54,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
|||||||
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
|
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
|
||||||
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
||||||
|
|
||||||
if (
|
|
||||||
userContext.features.notebooksTemporarilyDown === false &&
|
|
||||||
userContext.features.phoenix === true &&
|
|
||||||
useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2
|
|
||||||
) {
|
|
||||||
uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus("connectionStatus"));
|
uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus("connectionStatus"));
|
||||||
}
|
|
||||||
|
|
||||||
if (useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
|
if (useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
|
||||||
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker"));
|
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker"));
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Icon, ProgressIndicator, Stack, TooltipHost } from "@fluentui/react";
|
import { Icon, ProgressIndicator, Spinner, SpinnerSize, Stack, TooltipHost } from "@fluentui/react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { ConnectionStatusType } from "../../../Common/Constants";
|
import { ConnectionStatusType } from "../../../Common/Constants";
|
||||||
import { useNotebook } from "../../Notebook/useNotebook";
|
import { useNotebook } from "../../Notebook/useNotebook";
|
||||||
@@ -40,9 +40,14 @@ export const ConnectionStatus: React.FC = (): JSX.Element => {
|
|||||||
|
|
||||||
const connectionInfo = useNotebook((state) => state.connectionInfo);
|
const connectionInfo = useNotebook((state) => state.connectionInfo);
|
||||||
if (!connectionInfo) {
|
if (!connectionInfo) {
|
||||||
return <></>;
|
return (
|
||||||
|
<Stack className="connectionStatusContainer" horizontal>
|
||||||
|
<span>Connecting</span>
|
||||||
|
<Spinner size={SpinnerSize.medium} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connecting && isActive === false) {
|
if (connectionInfo && connectionInfo.status === ConnectionStatusType.Allocating && isActive === false) {
|
||||||
setIsActive(true);
|
setIsActive(true);
|
||||||
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connected && isActive === true) {
|
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connected && isActive === true) {
|
||||||
stopTimer();
|
stopTimer();
|
||||||
@@ -63,7 +68,7 @@ export const ConnectionStatus: React.FC = (): JSX.Element => {
|
|||||||
<span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}>
|
<span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}>
|
||||||
{connectionInfo.status}
|
{connectionInfo.status}
|
||||||
</span>
|
</span>
|
||||||
{connectionInfo.status === ConnectionStatusType.Connecting && isActive && (
|
{connectionInfo.status === ConnectionStatusType.Allocating && isActive && (
|
||||||
<ProgressIndicator description={minute + ":" + second} />
|
<ProgressIndicator description={minute + ":" + second} />
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ interface NotebookState {
|
|||||||
gitHubNotebooksContentRoot: NotebookContentItem;
|
gitHubNotebooksContentRoot: NotebookContentItem;
|
||||||
galleryContentRoot: NotebookContentItem;
|
galleryContentRoot: NotebookContentItem;
|
||||||
connectionInfo: DataModels.ContainerConnectionInfo;
|
connectionInfo: DataModels.ContainerConnectionInfo;
|
||||||
notebookFolderName: string;
|
NotebookFolderName: string;
|
||||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
|
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
|
||||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
|
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
|
||||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
|
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
|
||||||
@@ -38,7 +38,6 @@ interface NotebookState {
|
|||||||
setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => void;
|
setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => void;
|
||||||
setIsShellEnabled: (isShellEnabled: boolean) => void;
|
setIsShellEnabled: (isShellEnabled: boolean) => void;
|
||||||
setNotebookBasePath: (notebookBasePath: string) => void;
|
setNotebookBasePath: (notebookBasePath: string) => void;
|
||||||
setNotebookFolderName: (notebookFolderName: string) => void;
|
|
||||||
refreshNotebooksEnabledStateForAccount: () => Promise<void>;
|
refreshNotebooksEnabledStateForAccount: () => Promise<void>;
|
||||||
findItem: (root: NotebookContentItem, item: NotebookContentItem) => NotebookContentItem;
|
findItem: (root: NotebookContentItem, item: NotebookContentItem) => NotebookContentItem;
|
||||||
insertNotebookItem: (parent: NotebookContentItem, item: NotebookContentItem, isGithubTree?: boolean) => void;
|
insertNotebookItem: (parent: NotebookContentItem, item: NotebookContentItem, isGithubTree?: boolean) => void;
|
||||||
@@ -70,7 +69,7 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
gitHubNotebooksContentRoot: undefined,
|
gitHubNotebooksContentRoot: undefined,
|
||||||
galleryContentRoot: undefined,
|
galleryContentRoot: undefined,
|
||||||
connectionInfo: undefined,
|
connectionInfo: undefined,
|
||||||
notebookFolderName: undefined,
|
NotebookFolderName: userContext.features.phoenix ? "My Notebooks Scratch" : "My Notebooks",
|
||||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
|
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
|
||||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
|
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
|
||||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
||||||
@@ -81,7 +80,6 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => set({ memoryUsageInfo }),
|
setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => set({ memoryUsageInfo }),
|
||||||
setIsShellEnabled: (isShellEnabled: boolean) => set({ isShellEnabled }),
|
setIsShellEnabled: (isShellEnabled: boolean) => set({ isShellEnabled }),
|
||||||
setNotebookBasePath: (notebookBasePath: string) => set({ notebookBasePath }),
|
setNotebookBasePath: (notebookBasePath: string) => set({ notebookBasePath }),
|
||||||
setNotebookFolderName: (notebookFolderName: string) => set({ notebookFolderName }),
|
|
||||||
refreshNotebooksEnabledStateForAccount: async (): Promise<void> => {
|
refreshNotebooksEnabledStateForAccount: async (): Promise<void> => {
|
||||||
const { databaseAccount, authType } = userContext;
|
const { databaseAccount, authType } = userContext;
|
||||||
if (
|
if (
|
||||||
@@ -175,10 +173,8 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
||||||
},
|
},
|
||||||
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
|
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
|
||||||
const notebookFolderName = userContext.features.phoenix === true ? "Temporary Notebooks" : "My Notebooks";
|
|
||||||
set({ notebookFolderName });
|
|
||||||
const myNotebooksContentRoot = {
|
const myNotebooksContentRoot = {
|
||||||
name: get().notebookFolderName,
|
name: get().NotebookFolderName,
|
||||||
path: get().notebookBasePath,
|
path: get().notebookBasePath,
|
||||||
type: NotebookContentItemType.Directory,
|
type: NotebookContentItemType.Directory,
|
||||||
};
|
};
|
||||||
@@ -187,14 +183,11 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
path: "Gallery",
|
path: "Gallery",
|
||||||
type: NotebookContentItemType.File,
|
type: NotebookContentItemType.File,
|
||||||
};
|
};
|
||||||
const gitHubNotebooksContentRoot = notebookManager?.gitHubOAuthService?.isLoggedIn()
|
const gitHubNotebooksContentRoot = {
|
||||||
? {
|
|
||||||
name: "GitHub repos",
|
name: "GitHub repos",
|
||||||
path: "PsuedoDir",
|
path: "PsuedoDir",
|
||||||
type: NotebookContentItemType.Directory,
|
type: NotebookContentItemType.Directory,
|
||||||
}
|
};
|
||||||
: undefined;
|
|
||||||
|
|
||||||
set({
|
set({
|
||||||
myNotebooksContentRoot,
|
myNotebooksContentRoot,
|
||||||
galleryContentRoot,
|
galleryContentRoot,
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
true
|
true
|
||||||
).toLocaleLowerCase()}.`}
|
).toLocaleLowerCase()}.`}
|
||||||
>
|
>
|
||||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
<Icon iconName="Info" className="panelInfoIcon" />
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
@@ -210,7 +210,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
className="panelTextField"
|
className="panelTextField"
|
||||||
aria-label="New database id"
|
aria-label="New database id"
|
||||||
autoFocus
|
autoFocus
|
||||||
tabIndex={0}
|
|
||||||
value={this.state.newDatabaseId}
|
value={this.state.newDatabaseId}
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
this.setState({ newDatabaseId: event.target.value })
|
this.setState({ newDatabaseId: event.target.value })
|
||||||
@@ -237,7 +236,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
true
|
true
|
||||||
).toLocaleLowerCase()} within the database.`}
|
).toLocaleLowerCase()} within the database.`}
|
||||||
>
|
>
|
||||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
<Icon iconName="Info" className="panelInfoIcon" />
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
@@ -280,7 +279,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||||
content={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
|
content={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
|
||||||
>
|
>
|
||||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
<Icon iconName="Info" className="panelInfoIcon" />
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
@@ -363,7 +362,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
"Sharded collections split your data across many replica sets (shards) to achieve unlimited scalability. Sharded collections require choosing a shard key (field) to evenly distribute your data."
|
"Sharded collections split your data across many replica sets (shards) to achieve unlimited scalability. Sharded collections require choosing a shard key (field) to evenly distribute your data."
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
<Icon iconName="Info" className="panelInfoIcon" />
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
@@ -410,7 +409,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||||
content={this.getPartitionKeyTooltipText()}
|
content={this.getPartitionKeyTooltipText()}
|
||||||
>
|
>
|
||||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
<Icon iconName="Info" className="panelInfoIcon" />
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
@@ -468,7 +467,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
does not count towards the throughput you provisioned for the database. This throughput amount will be
|
does not count towards the throughput you provisioned for the database. This throughput amount will be
|
||||||
billed in addition to the throughput amount you provisioned at the database level.`}
|
billed in addition to the throughput amount you provisioned at the database level.`}
|
||||||
>
|
>
|
||||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
<Icon iconName="Info" className="panelInfoIcon" />
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
@@ -498,7 +497,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
creating a unique key policy when a container is created, you ensure the uniqueness of one or more values
|
creating a unique key policy when a container is created, you ensure the uniqueness of one or more values
|
||||||
per partition key."
|
per partition key."
|
||||||
>
|
>
|
||||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
<Icon iconName="Info" className="panelInfoIcon" />
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
@@ -561,7 +560,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||||
content={this.getAnalyticalStorageTooltipContent()}
|
content={this.getAnalyticalStorageTooltipContent()}
|
||||||
>
|
>
|
||||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
<Icon iconName="Info" className="panelInfoIcon" />
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
@@ -638,7 +637,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||||
content="The _id field is indexed by default. Creating a wildcard index for all fields will optimize queries and is recommended for development."
|
content="The _id field is indexed by default. Creating a wildcard index for all fields will optimize queries and is recommended for development."
|
||||||
>
|
>
|
||||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
<Icon iconName="Info" className="panelInfoIcon" />
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ export const CopyNotebookPaneComponent: FunctionComponent<CopyNotebookPaneProps>
|
|||||||
const options: IDropdownOption[] = [];
|
const options: IDropdownOption[] = [];
|
||||||
options.push({
|
options.push({
|
||||||
key: "MyNotebooks-Item",
|
key: "MyNotebooks-Item",
|
||||||
text: useNotebook.getState().notebookFolderName,
|
text: useNotebook.getState().NotebookFolderName,
|
||||||
title: useNotebook.getState().notebookFolderName,
|
title: useNotebook.getState().NotebookFolderName,
|
||||||
data: {
|
data: {
|
||||||
type: "MyNotebooks",
|
type: "MyNotebooks",
|
||||||
} as Location,
|
} as Location,
|
||||||
|
|||||||
@@ -108,8 +108,6 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
submitButtonText: "OK",
|
submitButtonText: "OK",
|
||||||
onSubmit,
|
onSubmit,
|
||||||
};
|
};
|
||||||
const confirmContainer = `Confirm by typing the ${collectionName.toLowerCase()} id`;
|
|
||||||
const reasonInfo = `Help us improve Azure Cosmos DB! What is the reason why you are deleting this ${collectionName}?`;
|
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...props}>
|
<RightPaneForm {...props}>
|
||||||
<div className="panelFormWrapper">
|
<div className="panelFormWrapper">
|
||||||
@@ -125,7 +123,6 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
onChange={(event, newInput?: string) => {
|
onChange={(event, newInput?: string) => {
|
||||||
setInputCollectionName(newInput);
|
setInputCollectionName(newInput);
|
||||||
}}
|
}}
|
||||||
ariaLabel={confirmContainer}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{shouldRecordFeedback() && (
|
{shouldRecordFeedback() && (
|
||||||
@@ -145,7 +142,6 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
onChange={(event, newInput?: string) => {
|
onChange={(event, newInput?: string) => {
|
||||||
setDeleteCollectionFeedback(newInput);
|
setDeleteCollectionFeedback(newInput);
|
||||||
}}
|
}}
|
||||||
ariaLabel={reasonInfo}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
</span>
|
</span>
|
||||||
</Text>
|
</Text>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
ariaLabel="Confirm by typing the container id"
|
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -54,7 +53,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
value=""
|
value=""
|
||||||
>
|
>
|
||||||
<TextFieldBase
|
<TextFieldBase
|
||||||
ariaLabel="Confirm by typing the container id"
|
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
deferredValidationTime={200}
|
deferredValidationTime={200}
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
@@ -348,7 +346,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-invalid={false}
|
aria-invalid={false}
|
||||||
aria-label="Confirm by typing the container id"
|
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className="ms-TextField-field field-57"
|
className="ms-TextField-field field-57"
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
|
|||||||
@@ -118,8 +118,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
message:
|
message:
|
||||||
"Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.",
|
"Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.",
|
||||||
};
|
};
|
||||||
const confirmDatabase = "Confirm by typing the database id";
|
|
||||||
const reasonInfo = "Help us improve Azure Cosmos DB! What is the reason why you are deleting this database?";
|
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...props}>
|
<RightPaneForm {...props}>
|
||||||
{!formError && <PanelInfoErrorComponent {...errorProps} />}
|
{!formError && <PanelInfoErrorComponent {...errorProps} />}
|
||||||
@@ -134,7 +133,6 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
onChange={(event, newInput?: string) => {
|
onChange={(event, newInput?: string) => {
|
||||||
setDatabaseInput(newInput);
|
setDatabaseInput(newInput);
|
||||||
}}
|
}}
|
||||||
ariaLabel={confirmDatabase}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{isLastNonEmptyDatabase() && (
|
{isLastNonEmptyDatabase() && (
|
||||||
@@ -153,7 +151,6 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
onChange={(event, newInput?: string) => {
|
onChange={(event, newInput?: string) => {
|
||||||
setDatabaseFeedbackInput(newInput);
|
setDatabaseFeedbackInput(newInput);
|
||||||
}}
|
}}
|
||||||
ariaLabel={reasonInfo}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
|
|||||||
selectedKey={paramKeyValue && paramKeyValue.key}
|
selectedKey={paramKeyValue && paramKeyValue.key}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<Stack horizontal onClick={addNewParamAtLastIndex} tabIndex={0}>
|
<Stack horizontal onClick={addNewParamAtLastIndex}>
|
||||||
<Image {...imageProps} src={AddPropertyIcon} alt="Add param" />
|
<Image {...imageProps} src={AddPropertyIcon} alt="Add param" />
|
||||||
<Text className="addNewParamStyle">Add New Param</Text>
|
<Text className="addNewParamStyle">Add New Param</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -59,36 +59,30 @@ export const InputParameter: FunctionComponent<InputParameterProps> = ({
|
|||||||
onChange={onParamKeyChange}
|
onChange={onParamKeyChange}
|
||||||
options={options}
|
options={options}
|
||||||
styles={dropdownStyles}
|
styles={dropdownStyles}
|
||||||
tabIndex={0}
|
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label={inputLabel && inputLabel}
|
label={inputLabel && inputLabel}
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
|
autoFocus
|
||||||
value={paramValue}
|
value={paramValue}
|
||||||
onChange={onParamValueChange}
|
onChange={onParamValueChange}
|
||||||
/>
|
/>
|
||||||
{isAddRemoveVisible && (
|
{isAddRemoveVisible && (
|
||||||
<>
|
<>
|
||||||
<div tabIndex={0}>
|
|
||||||
<Image
|
<Image
|
||||||
{...imageProps}
|
{...imageProps}
|
||||||
src={EntityCancelIcon}
|
src={EntityCancelIcon}
|
||||||
alt="Delete param"
|
alt="Delete param"
|
||||||
id="deleteparam"
|
id="deleteparam"
|
||||||
role="button"
|
|
||||||
onClick={onDeleteParamKeyPress}
|
onClick={onDeleteParamKeyPress}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div tabIndex={0}>
|
|
||||||
<Image
|
<Image
|
||||||
{...imageProps}
|
{...imageProps}
|
||||||
src={AddPropertyIcon}
|
src={AddPropertyIcon}
|
||||||
alt="Add param"
|
alt="Add param"
|
||||||
id="addparam"
|
id="addparam"
|
||||||
role="button"
|
|
||||||
onClick={onAddNewParamKeyPress}
|
onClick={onAddNewParamKeyPress}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -345,7 +345,6 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tabIndex={0}
|
|
||||||
>
|
>
|
||||||
<DropdownBase
|
<DropdownBase
|
||||||
label="Key"
|
label="Key"
|
||||||
@@ -364,7 +363,6 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
|||||||
}
|
}
|
||||||
selectedKey="string"
|
selectedKey="string"
|
||||||
styles={[Function]}
|
styles={[Function]}
|
||||||
tabIndex={0}
|
|
||||||
theme={
|
theme={
|
||||||
Object {
|
Object {
|
||||||
"disableGlobalClassNames": false,
|
"disableGlobalClassNames": false,
|
||||||
@@ -666,7 +664,6 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
|||||||
responsiveMode={3}
|
responsiveMode={3}
|
||||||
selectedKey="string"
|
selectedKey="string"
|
||||||
styles={[Function]}
|
styles={[Function]}
|
||||||
tabIndex={0}
|
|
||||||
theme={
|
theme={
|
||||||
Object {
|
Object {
|
||||||
"disableGlobalClassNames": false,
|
"disableGlobalClassNames": false,
|
||||||
@@ -1565,12 +1562,14 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
|||||||
</DropdownBase>
|
</DropdownBase>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
|
autoFocus={true}
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
key=".0:$.1"
|
key=".0:$.1"
|
||||||
label="Value"
|
label="Value"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
>
|
>
|
||||||
<TextFieldBase
|
<TextFieldBase
|
||||||
|
autoFocus={true}
|
||||||
deferredValidationTime={200}
|
deferredValidationTime={200}
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
label="Value"
|
label="Value"
|
||||||
@@ -2156,6 +2155,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
|||||||
<input
|
<input
|
||||||
aria-invalid={false}
|
aria-invalid={false}
|
||||||
aria-labelledby="TextFieldLabel3"
|
aria-labelledby="TextFieldLabel3"
|
||||||
|
autoFocus={true}
|
||||||
className="ms-TextField-field field-77"
|
className="ms-TextField-field field-77"
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
@@ -2500,7 +2500,6 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tabIndex={0}
|
|
||||||
>
|
>
|
||||||
<DropdownBase
|
<DropdownBase
|
||||||
label="Key"
|
label="Key"
|
||||||
@@ -2519,7 +2518,6 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
|||||||
}
|
}
|
||||||
selectedKey="string"
|
selectedKey="string"
|
||||||
styles={[Function]}
|
styles={[Function]}
|
||||||
tabIndex={0}
|
|
||||||
theme={
|
theme={
|
||||||
Object {
|
Object {
|
||||||
"disableGlobalClassNames": false,
|
"disableGlobalClassNames": false,
|
||||||
@@ -2821,7 +2819,6 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
|||||||
responsiveMode={3}
|
responsiveMode={3}
|
||||||
selectedKey="string"
|
selectedKey="string"
|
||||||
styles={[Function]}
|
styles={[Function]}
|
||||||
tabIndex={0}
|
|
||||||
theme={
|
theme={
|
||||||
Object {
|
Object {
|
||||||
"disableGlobalClassNames": false,
|
"disableGlobalClassNames": false,
|
||||||
@@ -3720,6 +3717,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
|||||||
</DropdownBase>
|
</DropdownBase>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
|
autoFocus={true}
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
key=".0:$.1"
|
key=".0:$.1"
|
||||||
label="Param"
|
label="Param"
|
||||||
@@ -3727,6 +3725,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
|||||||
value=""
|
value=""
|
||||||
>
|
>
|
||||||
<TextFieldBase
|
<TextFieldBase
|
||||||
|
autoFocus={true}
|
||||||
deferredValidationTime={200}
|
deferredValidationTime={200}
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
label="Param"
|
label="Param"
|
||||||
@@ -4313,6 +4312,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
|||||||
<input
|
<input
|
||||||
aria-invalid={false}
|
aria-invalid={false}
|
||||||
aria-labelledby="TextFieldLabel7"
|
aria-labelledby="TextFieldLabel7"
|
||||||
|
autoFocus={true}
|
||||||
className="ms-TextField-field field-77"
|
className="ms-TextField-field field-77"
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
@@ -4327,16 +4327,12 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</TextFieldBase>
|
</TextFieldBase>
|
||||||
</StyledTextFieldBase>
|
</StyledTextFieldBase>
|
||||||
<div
|
|
||||||
tabIndex={0}
|
|
||||||
>
|
|
||||||
<StyledImageBase
|
<StyledImageBase
|
||||||
alt="Delete param"
|
alt="Delete param"
|
||||||
className="addRemoveIconLabel"
|
className="addRemoveIconLabel"
|
||||||
height={30}
|
height={30}
|
||||||
id="deleteparam"
|
id="deleteparam"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
role="button"
|
|
||||||
src=""
|
src=""
|
||||||
width={20}
|
width={20}
|
||||||
>
|
>
|
||||||
@@ -4346,7 +4342,6 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
|||||||
height={30}
|
height={30}
|
||||||
id="deleteparam"
|
id="deleteparam"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
role="button"
|
|
||||||
src=""
|
src=""
|
||||||
styles={[Function]}
|
styles={[Function]}
|
||||||
theme={
|
theme={
|
||||||
@@ -4641,23 +4636,17 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
|||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onError={[Function]}
|
onError={[Function]}
|
||||||
onLoad={[Function]}
|
onLoad={[Function]}
|
||||||
role="button"
|
|
||||||
src=""
|
src=""
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ImageBase>
|
</ImageBase>
|
||||||
</StyledImageBase>
|
</StyledImageBase>
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
tabIndex={0}
|
|
||||||
>
|
|
||||||
<StyledImageBase
|
<StyledImageBase
|
||||||
alt="Add param"
|
alt="Add param"
|
||||||
className="addRemoveIconLabel"
|
className="addRemoveIconLabel"
|
||||||
height={30}
|
height={30}
|
||||||
id="addparam"
|
id="addparam"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
role="button"
|
|
||||||
src=""
|
src=""
|
||||||
width={20}
|
width={20}
|
||||||
>
|
>
|
||||||
@@ -4667,7 +4656,6 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
|||||||
height={30}
|
height={30}
|
||||||
id="addparam"
|
id="addparam"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
role="button"
|
|
||||||
src=""
|
src=""
|
||||||
styles={[Function]}
|
styles={[Function]}
|
||||||
theme={
|
theme={
|
||||||
@@ -4962,25 +4950,21 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
|||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onError={[Function]}
|
onError={[Function]}
|
||||||
onLoad={[Function]}
|
onLoad={[Function]}
|
||||||
role="button"
|
|
||||||
src=""
|
src=""
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ImageBase>
|
</ImageBase>
|
||||||
</StyledImageBase>
|
</StyledImageBase>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</InputParameter>
|
</InputParameter>
|
||||||
<Stack
|
<Stack
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
tabIndex={0}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-Stack css-54"
|
className="ms-Stack css-54"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
tabIndex={0}
|
|
||||||
>
|
>
|
||||||
<StyledImageBase
|
<StyledImageBase
|
||||||
alt="Add param"
|
alt="Add param"
|
||||||
|
|||||||
@@ -3,12 +3,13 @@
|
|||||||
.panelFormWrapper {
|
.panelFormWrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
.panelMainContent {
|
.panelMainContent {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding: 0 34px;
|
padding: 0 34px;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
& > :not(.collapsibleSection) {
|
& > :not(.collapsibleSection) {
|
||||||
margin-bottom: @DefaultSpace;
|
margin-bottom: @DefaultSpace;
|
||||||
|
|||||||
@@ -195,6 +195,7 @@ export const SettingsPane: FunctionComponent = () => {
|
|||||||
step={1}
|
step={1}
|
||||||
className="textfontclr"
|
className="textfontclr"
|
||||||
role="textbox"
|
role="textbox"
|
||||||
|
tabIndex={0}
|
||||||
id="max-degree"
|
id="max-degree"
|
||||||
value={"" + maxDegreeOfParallelism}
|
value={"" + maxDegreeOfParallelism}
|
||||||
onIncrement={(newValue) => setMaxDegreeOfParallelism(parseInt(newValue) + 1 || maxDegreeOfParallelism)}
|
onIncrement={(newValue) => setMaxDegreeOfParallelism(parseInt(newValue) + 1 || maxDegreeOfParallelism)}
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ exports[`Settings Pane should render Default properly 1`] = `
|
|||||||
onValidate={[Function]}
|
onValidate={[Function]}
|
||||||
role="textbox"
|
role="textbox"
|
||||||
step={1}
|
step={1}
|
||||||
|
tabIndex={0}
|
||||||
value="6"
|
value="6"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -363,7 +363,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
</Text>
|
</Text>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
ariaLabel="Confirm by typing the database id"
|
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
id="confirmDatabaseId"
|
id="confirmDatabaseId"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -376,7 +375,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<TextFieldBase
|
<TextFieldBase
|
||||||
ariaLabel="Confirm by typing the database id"
|
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
deferredValidationTime={200}
|
deferredValidationTime={200}
|
||||||
id="confirmDatabaseId"
|
id="confirmDatabaseId"
|
||||||
@@ -669,7 +667,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-invalid={false}
|
aria-invalid={false}
|
||||||
aria-label="Confirm by typing the database id"
|
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className="ms-TextField-field field-60"
|
className="ms-TextField-field field-60"
|
||||||
id="confirmDatabaseId"
|
id="confirmDatabaseId"
|
||||||
@@ -710,7 +707,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
</Text>
|
</Text>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
ariaLabel="Help us improve Azure Cosmos DB! What is the reason why you are deleting this database?"
|
|
||||||
id="deleteDatabaseFeedbackInput"
|
id="deleteDatabaseFeedbackInput"
|
||||||
multiline={true}
|
multiline={true}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -724,7 +720,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<TextFieldBase
|
<TextFieldBase
|
||||||
ariaLabel="Help us improve Azure Cosmos DB! What is the reason why you are deleting this database?"
|
|
||||||
deferredValidationTime={200}
|
deferredValidationTime={200}
|
||||||
id="deleteDatabaseFeedbackInput"
|
id="deleteDatabaseFeedbackInput"
|
||||||
multiline={true}
|
multiline={true}
|
||||||
@@ -1018,7 +1013,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
>
|
>
|
||||||
<textarea
|
<textarea
|
||||||
aria-invalid={false}
|
aria-invalid={false}
|
||||||
aria-label="Help us improve Azure Cosmos DB! What is the reason why you are deleting this database?"
|
|
||||||
className="ms-TextField-field field-71"
|
className="ms-TextField-field field-71"
|
||||||
id="deleteDatabaseFeedbackInput"
|
id="deleteDatabaseFeedbackInput"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export function createDataTable($dataTableElem: JQuery, settings: any): DataTabl
|
|||||||
* @return The given settings with all columns having a rendering function
|
* @return The given settings with all columns having a rendering function
|
||||||
*/
|
*/
|
||||||
function applyDefaultRendering(settings: any): DataTables.SettingsLegacy {
|
function applyDefaultRendering(settings: any): DataTables.SettingsLegacy {
|
||||||
var tableColumns: DataTables.ColumnLegacy[] = null;
|
let tableColumns: DataTables.ColumnLegacy[] = null;
|
||||||
|
|
||||||
if (settings.aoColumns) {
|
if (settings.aoColumns) {
|
||||||
tableColumns = settings.aoColumns;
|
tableColumns = settings.aoColumns;
|
||||||
@@ -34,7 +34,7 @@ function applyDefaultRendering(settings: any): DataTables.SettingsLegacy {
|
|||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < tableColumns.length; i++) {
|
for (let i = 0; i < tableColumns.length; i++) {
|
||||||
// the column does not have a render function
|
// the column does not have a render function
|
||||||
if (!tableColumns[i].mRender) {
|
if (!tableColumns[i].mRender) {
|
||||||
tableColumns[i].mRender = defaultDataRender;
|
tableColumns[i].mRender = defaultDataRender;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DetailsList, DetailsListLayoutMode, IColumn, Pivot, PivotItem, SelectionMode, Text } from "@fluentui/react";
|
import { DetailsList, DetailsListLayoutMode, IColumn, Pivot, PivotItem, SelectionMode } from "@fluentui/react";
|
||||||
import React, { Fragment } from "react";
|
import React, { Fragment } from "react";
|
||||||
import SplitterLayout from "react-splitter-layout";
|
import SplitterLayout from "react-splitter-layout";
|
||||||
import "react-splitter-layout/lib/index.css";
|
import "react-splitter-layout/lib/index.css";
|
||||||
@@ -120,13 +120,21 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
constructor(props: IQueryTabComponentProps) {
|
constructor(props: IQueryTabComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
const columns: IColumn[] = [
|
const columns: IColumn[] = [
|
||||||
|
{
|
||||||
|
key: "column1",
|
||||||
|
name: "",
|
||||||
|
minWidth: 16,
|
||||||
|
maxWidth: 16,
|
||||||
|
data: String,
|
||||||
|
fieldName: "toolTip",
|
||||||
|
onRender: this.onRenderColumnItem,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "column2",
|
key: "column2",
|
||||||
name: "METRIC",
|
name: "METRIC",
|
||||||
minWidth: 200,
|
minWidth: 200,
|
||||||
data: String,
|
data: String,
|
||||||
fieldName: "metric",
|
fieldName: "metric",
|
||||||
onRender: this.onRenderColumnItem,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "column3",
|
key: "column3",
|
||||||
@@ -198,12 +206,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
|
|
||||||
public onRenderColumnItem(item: IDocument): JSX.Element {
|
public onRenderColumnItem(item: IDocument): JSX.Element {
|
||||||
if (item.toolTip !== "") {
|
if (item.toolTip !== "") {
|
||||||
return (
|
return <InfoTooltip>{`${item.toolTip}`}</InfoTooltip>;
|
||||||
<>
|
|
||||||
<InfoTooltip>{`${item.toolTip}`}</InfoTooltip>
|
|
||||||
<Text style={{ paddingLeft: 10, margin: 0 }}>{`${item.metric}`}</Text>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import loadingIcon from "../../../images/circular_loader_black_16x16.gif";
|
import loadingIcon from "../../../images/circular_loader_black_16x16.gif";
|
||||||
import errorIcon from "../../../images/close-black.svg";
|
import errorIcon from "../../../images/close-black.svg";
|
||||||
import { useObservable } from "../../hooks/useObservable";
|
import { useObservable } from "../../hooks/useObservable";
|
||||||
@@ -32,13 +32,7 @@ export const Tabs = (): JSX.Element => {
|
|||||||
|
|
||||||
function TabNav({ tab, active }: { tab: Tab; active: boolean }) {
|
function TabNav({ tab, active }: { tab: Tab; active: boolean }) {
|
||||||
const [hovering, setHovering] = useState(false);
|
const [hovering, setHovering] = useState(false);
|
||||||
const focusTab = useRef<HTMLLIElement>() as MutableRefObject<HTMLLIElement>;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (active && focusTab.current) {
|
|
||||||
focusTab.current.focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
onMouseOver={() => setHovering(true)}
|
onMouseOver={() => setHovering(true)}
|
||||||
@@ -52,7 +46,6 @@ function TabNav({ tab, active }: { tab: Tab; active: boolean }) {
|
|||||||
aria-controls={tab.tabId}
|
aria-controls={tab.tabId}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role="tab"
|
role="tab"
|
||||||
ref={focusTab}
|
|
||||||
>
|
>
|
||||||
<span className="tabNavContentContainer">
|
<span className="tabNavContentContainer">
|
||||||
<a data-toggle="tab" href={"#" + tab.tabId} tabIndex={-1}>
|
<a data-toggle="tab" href={"#" + tab.tabId} tabIndex={-1}>
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ export default class UserDefinedFunctionTabContent extends Component<
|
|||||||
language={"javascript"}
|
language={"javascript"}
|
||||||
content={udfBody}
|
content={udfBody}
|
||||||
isReadOnly={false}
|
isReadOnly={false}
|
||||||
ariaLabel={"User defined function body"}
|
ariaLabel={"Graph JSON"}
|
||||||
onContentChanged={this.handleUdfBodyChange}
|
onContentChanged={this.handleUdfBodyChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import NotebookIcon from "../../../images/notebook/Notebook-resource.svg";
|
|||||||
import PublishIcon from "../../../images/notebook/publish_content.svg";
|
import PublishIcon from "../../../images/notebook/publish_content.svg";
|
||||||
import RefreshIcon from "../../../images/refresh-cosmos.svg";
|
import RefreshIcon from "../../../images/refresh-cosmos.svg";
|
||||||
import CollectionIcon from "../../../images/tree-collection.svg";
|
import CollectionIcon from "../../../images/tree-collection.svg";
|
||||||
import { Areas, ConnectionStatusType, Notebook } from "../../Common/Constants";
|
import { Areas, Notebook } from "../../Common/Constants";
|
||||||
import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility";
|
import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
@@ -128,13 +128,15 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
notebooksTree.children.push(buildGalleryNotebooksTree());
|
notebooksTree.children.push(buildGalleryNotebooksTree());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (myNotebooksContentRoot && useNotebook.getState().connectionInfo.status == ConnectionStatusType.Connected) {
|
if (myNotebooksContentRoot) {
|
||||||
notebooksTree.children.push(buildMyNotebooksTree());
|
notebooksTree.children.push(buildMyNotebooksTree());
|
||||||
}
|
}
|
||||||
if (container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
|
if (container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
|
||||||
// collapse all other notebook nodes
|
// collapse all other notebook nodes
|
||||||
notebooksTree.children.forEach((node) => (node.isExpanded = false));
|
notebooksTree.children.forEach((node) => (node.isExpanded = false));
|
||||||
notebooksTree.children.push(buildGitHubNotebooksTree(true));
|
notebooksTree.children.push(buildGitHubNotebooksTree(true));
|
||||||
|
} else if (container.notebookManager && !container.notebookManager.gitHubOAuthService.isLoggedIn()) {
|
||||||
|
notebooksTree.children.push(buildGitHubNotebooksTree(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return notebooksTree;
|
return notebooksTree;
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
type: NotebookContentItemType.File,
|
type: NotebookContentItemType.File,
|
||||||
};
|
};
|
||||||
this.myNotebooksContentRoot = {
|
this.myNotebooksContentRoot = {
|
||||||
name: useNotebook.getState().notebookFolderName,
|
name: useNotebook.getState().NotebookFolderName,
|
||||||
path: useNotebook.getState().notebookBasePath,
|
path: useNotebook.getState().notebookBasePath,
|
||||||
type: NotebookContentItemType.Directory,
|
type: NotebookContentItemType.Directory,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
{
|
|
||||||
"GraphAPIDescription": "Provision a Graph API Compute for your Azure Cosmos DB account.",
|
|
||||||
"GraphAPICompute": "Graph GraphAPI Compute",
|
|
||||||
"Provisioned": "Provisioned",
|
|
||||||
"Deprovisioned": "Deprovisioned",
|
|
||||||
"Compute": "Compute",
|
|
||||||
"GremlinV2": "GremlinV2",
|
|
||||||
"LearnAboutCompute": "Learn more about GraphAPI Compute.",
|
|
||||||
"DeprovisioningDetailsText": "Learn more about deprovisioning the GraphAPI Compute.",
|
|
||||||
"ComputePricing": "Learn more about GraphAPI Compute pricing.",
|
|
||||||
"SKUs": "SKUs",
|
|
||||||
"SKUsPlaceHolder": "Select SKUs",
|
|
||||||
"NumberOfInstances": "Number of instances",
|
|
||||||
"CosmosD4s": "Cosmos.D4s (General Purpose Cosmos Compute with 4 vCPUs, 16 GB Memory)",
|
|
||||||
"CosmosD8s": "Cosmos.D8s (General Purpose Cosmos Compute with 8 vCPUs, 32 GB Memory)",
|
|
||||||
"CosmosD16s": "Cosmos.D16s (General Purpose Cosmos Compute with 16 vCPUs, 64 GB Memory)",
|
|
||||||
"CosmosD32s": "Cosmos.D32s (General Purpose Cosmos Compute with 32 vCPUs, 128 GB Memory)",
|
|
||||||
"CreateMessage": "Graph GraphAPI Compute resource is being created.",
|
|
||||||
"CreateInitializeTitle": "Provisioning resource",
|
|
||||||
"CreateInitializeMessage": "GraphAPI Compute resource will be provisioned.",
|
|
||||||
"CreateSuccessTitle": "Resource provisioned",
|
|
||||||
"CreateSuccesseMessage": "GraphAPI Compute resource provisioned.",
|
|
||||||
"CreateFailureTitle": "Failed to provision resource",
|
|
||||||
"CreateFailureMessage": "GraphAPI Compute resource provisioning failed.",
|
|
||||||
"UpdateMessage": "GraphAPI Compute resource is being updated.",
|
|
||||||
"UpdateInitializeTitle": "Updating resource",
|
|
||||||
"UpdateInitializeMessage": "GraphAPI Compute resource will be updated.",
|
|
||||||
"UpdateSuccessTitle": "Resource updated",
|
|
||||||
"UpdateSuccesseMessage": "GraphAPI Compute resource updated.",
|
|
||||||
"UpdateFailureTitle": "Failed to update resource",
|
|
||||||
"UpdateFailureMessage": "GraphAPI Compute resource updation failed.",
|
|
||||||
"DeleteMessage": "GraphAPI Compute resource is being deleted.",
|
|
||||||
"DeleteInitializeTitle": "Deleting resource",
|
|
||||||
"DeleteInitializeMessage": "GraphAPI Compute resource will be deleted.",
|
|
||||||
"DeleteSuccessTitle": "Resource deleted",
|
|
||||||
"DeleteSuccesseMessage": "GraphAPI Compute resource deleted.",
|
|
||||||
"DeleteFailureTitle": "Failed to delete resource",
|
|
||||||
"DeleteFailureMessage": "GraphAPI Compute resource deletion failed.",
|
|
||||||
"CannotSave": "Cannot save the changes to the GraphAPI Compute resource at the moment.",
|
|
||||||
"GraphAccountEndpoint": "Graph Account Endpoint",
|
|
||||||
"CosmosD4Details": "General Purpose Cosmos Compute with 4 vCPUs, 16 GB Memory",
|
|
||||||
"CosmosD8Details": "General Purpose Cosmos Compute with 8 vCPUs, 32 GB Memory",
|
|
||||||
"CosmosD16Details": "General Purpose Cosmos Compute with 16 vCPUs, 64 GB Memory",
|
|
||||||
"ApproximateCost": "Approximate Cost Per Hour",
|
|
||||||
"CostText": "Hourly cost of the GraphAPI Compute resource depends on the SKU selection, number of instances per region, and number of regions.",
|
|
||||||
"ConnectionString": "Connection String",
|
|
||||||
"ConnectionStringText": "To use the GraphAPI Compute, use the connection string shown in ",
|
|
||||||
"KeysBlade": "the keys blade.",
|
|
||||||
"MetricsString": "Metrics",
|
|
||||||
"MetricsText": "Monitor the CPU and memory usage for the GraphAPI Compute instances in ",
|
|
||||||
"MetricsBlade": "the metrics blade.",
|
|
||||||
"MonitorUsage": "Monitor Usage",
|
|
||||||
"ResizingDecisionText": "Number of instances has to be 1 during provisioning. Instances can only be incremented by 1 at once. ",
|
|
||||||
"ResizingDecisionLink": "Learn more about GraphAPI Compute sizing.",
|
|
||||||
"WarningBannerOnUpdate": "Adding or modifying GraphAPI Compute instances may affect your bill.",
|
|
||||||
"WarningBannerOnDelete": "After deprovisioning the GraphAPI Compute, you will not be able to connect to the Graph API account."
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ConnectionStatusType, HttpHeaders, HttpStatusCodes } from "../Common/Constants";
|
import { ConnectionStatusType, HttpHeaders, HttpStatusCodes } from "../Common/Constants";
|
||||||
import { configContext } from "../ConfigContext";
|
import { configContext } from "../ConfigContext";
|
||||||
import { ContainerConnectionInfo } from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
import { useNotebook } from "../Explorer/Notebook/useNotebook";
|
import { useNotebook } from "../Explorer/Notebook/useNotebook";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||||
@@ -25,11 +25,6 @@ export class PhoenixClient {
|
|||||||
public async containerConnectionInfo(
|
public async containerConnectionInfo(
|
||||||
provisionData: IProvosionData
|
provisionData: IProvosionData
|
||||||
): Promise<IPhoenixResponse<IPhoenixConnectionInfoResult>> {
|
): Promise<IPhoenixResponse<IPhoenixConnectionInfoResult>> {
|
||||||
try {
|
|
||||||
const connectionStatus: ContainerConnectionInfo = {
|
|
||||||
status: ConnectionStatusType.Connecting,
|
|
||||||
};
|
|
||||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
|
||||||
const response = await window.fetch(`${this.getPhoenixContainerPoolingEndPoint()}/provision`, {
|
const response = await window.fetch(`${this.getPhoenixContainerPoolingEndPoint()}/provision`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: PhoenixClient.getHeaders(),
|
headers: PhoenixClient.getHeaders(),
|
||||||
@@ -38,12 +33,10 @@ export class PhoenixClient {
|
|||||||
let data: IPhoenixConnectionInfoResult;
|
let data: IPhoenixConnectionInfoResult;
|
||||||
if (response.status === HttpStatusCodes.OK) {
|
if (response.status === HttpStatusCodes.OK) {
|
||||||
data = await response.json();
|
data = await response.json();
|
||||||
if (data && data.notebookServerUrl) {
|
|
||||||
connectionStatus.status = ConnectionStatusType.Connected;
|
|
||||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
connectionStatus.status = ConnectionStatusType.Failed;
|
const connectionStatus: DataModels.ContainerConnectionInfo = {
|
||||||
|
status: ConnectionStatusType.Failed,
|
||||||
|
};
|
||||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,14 +44,6 @@ export class PhoenixClient {
|
|||||||
status: response.status,
|
status: response.status,
|
||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
|
||||||
const connectionStatus: ContainerConnectionInfo = {
|
|
||||||
status: ConnectionStatusType.Failed,
|
|
||||||
};
|
|
||||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
|
||||||
console.error(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getPhoenixEndpoint(): string {
|
public static getPhoenixEndpoint(): string {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { extractFeatures, hasFlag } from "./extractFeatures";
|
import { extractFeatures } from "./extractFeatures";
|
||||||
|
|
||||||
describe("extractFeatures", () => {
|
describe("extractFeatures", () => {
|
||||||
it("correctly detects feature flags in a case insensitive manner", () => {
|
it("correctly detects feature flags in a case insensitive manner", () => {
|
||||||
@@ -14,25 +14,9 @@ describe("extractFeatures", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const features = extractFeatures(params);
|
const features = extractFeatures(params);
|
||||||
|
|
||||||
expect(features.notebookServerUrl).toBe(url);
|
expect(features.notebookServerUrl).toBe(url);
|
||||||
expect(features.notebookServerToken).toBe(token);
|
expect(features.notebookServerToken).toBe(token);
|
||||||
expect(features.enableNotebooks).toBe(notebooksEnabled);
|
expect(features.enableNotebooks).toBe(notebooksEnabled);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("hasFlag", () => {
|
|
||||||
it("correctly determines if value has flag", () => {
|
|
||||||
const desiredFlag = "readDocument";
|
|
||||||
|
|
||||||
const singleFlagValue = "readDocument";
|
|
||||||
const multipleFlagValues = "readDocument|createDocument";
|
|
||||||
const differentFlagValue = "createDocument";
|
|
||||||
|
|
||||||
expect(hasFlag(singleFlagValue, desiredFlag)).toBe(true);
|
|
||||||
expect(hasFlag(multipleFlagValues, desiredFlag)).toBe(true);
|
|
||||||
expect(hasFlag(differentFlagValue, desiredFlag)).toBe(false);
|
|
||||||
expect(hasFlag(multipleFlagValues, (undefined as unknown) as string)).toBe(false);
|
|
||||||
expect(hasFlag((undefined as unknown) as string, desiredFlag)).toBe(false);
|
|
||||||
expect(hasFlag((undefined as unknown) as string, (undefined as unknown) as string)).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ export type Features = {
|
|||||||
readonly pr?: string;
|
readonly pr?: string;
|
||||||
readonly showMinRUSurvey: boolean;
|
readonly showMinRUSurvey: boolean;
|
||||||
readonly ttl90Days: boolean;
|
readonly ttl90Days: boolean;
|
||||||
readonly mongoProxyEndpoint?: string;
|
|
||||||
readonly mongoProxyAPIs?: string;
|
|
||||||
readonly notebooksTemporarilyDown: boolean;
|
readonly notebooksTemporarilyDown: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -65,8 +63,6 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
|
|||||||
enableKoResourceTree: "true" === get("enablekoresourcetree"),
|
enableKoResourceTree: "true" === get("enablekoresourcetree"),
|
||||||
executeSproc: "true" === get("dataexplorerexecutesproc"),
|
executeSproc: "true" === get("dataexplorerexecutesproc"),
|
||||||
hostedDataExplorer: "true" === get("hosteddataexplorerenabled"),
|
hostedDataExplorer: "true" === get("hosteddataexplorerenabled"),
|
||||||
mongoProxyEndpoint: get("mongoproxyendpoint"),
|
|
||||||
mongoProxyAPIs: get("mongoproxyapis"),
|
|
||||||
junoEndpoint: get("junoendpoint"),
|
junoEndpoint: get("junoendpoint"),
|
||||||
livyEndpoint: get("livyendpoint"),
|
livyEndpoint: get("livyendpoint"),
|
||||||
notebookBasePath: get("notebookbasepath"),
|
notebookBasePath: get("notebookbasepath"),
|
||||||
@@ -84,12 +80,3 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
|
|||||||
phoenix: "true" === get("phoenix"),
|
phoenix: "true" === get("phoenix"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasFlag(flags: string, desiredFlag: string): boolean {
|
|
||||||
if (!flags || !desiredFlag) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const features = flags.split("|");
|
|
||||||
return features.find((feature) => feature === desiredFlag) ? true : false;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,200 +0,0 @@
|
|||||||
import { configContext } from "../../ConfigContext";
|
|
||||||
import { userContext } from "../../UserContext";
|
|
||||||
import { armRequestWithoutPolling } from "../../Utils/arm/request";
|
|
||||||
import { selfServeTraceFailure, selfServeTraceStart, selfServeTraceSuccess } from "../SelfServeTelemetryProcessor";
|
|
||||||
import { RefreshResult } from "../SelfServeTypes";
|
|
||||||
import GraphAPICompute from "./GraphAPICompute";
|
|
||||||
import {
|
|
||||||
FetchPricesResponse,
|
|
||||||
RegionsResponse,
|
|
||||||
GraphAPIComputeServiceResource,
|
|
||||||
UpdateComputeRequestParameters,
|
|
||||||
} from "./GraphAPICompute.types";
|
|
||||||
|
|
||||||
const apiVersion = "2021-04-01-preview";
|
|
||||||
const gremlinV2 = "GremlinV2";
|
|
||||||
|
|
||||||
export enum ResourceStatus {
|
|
||||||
Running = "Running",
|
|
||||||
Creating = "Creating",
|
|
||||||
Updating = "Updating",
|
|
||||||
Deleting = "Deleting",
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ComputeResponse {
|
|
||||||
sku: string;
|
|
||||||
instances: number;
|
|
||||||
status: string;
|
|
||||||
endpoint: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getPath = (subscriptionId: string, resourceGroup: string, name: string): string => {
|
|
||||||
return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${name}/services/${gremlinV2}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const updateComputeResource = async (sku: string, instances: number): Promise<string> => {
|
|
||||||
const path = getPath(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name);
|
|
||||||
const body: UpdateComputeRequestParameters = {
|
|
||||||
properties: {
|
|
||||||
instanceSize: sku,
|
|
||||||
instanceCount: instances,
|
|
||||||
serviceType: gremlinV2,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const telemetryData = { ...body, httpMethod: "PUT", selfServeClassName: GraphAPICompute.name };
|
|
||||||
const updateTimeStamp = selfServeTraceStart(telemetryData);
|
|
||||||
let armRequestResult;
|
|
||||||
try {
|
|
||||||
armRequestResult = await armRequestWithoutPolling({
|
|
||||||
host: configContext.ARM_ENDPOINT,
|
|
||||||
path,
|
|
||||||
method: "PUT",
|
|
||||||
apiVersion,
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
selfServeTraceSuccess(telemetryData, updateTimeStamp);
|
|
||||||
} catch (e) {
|
|
||||||
const failureTelemetry = { ...body, e, selfServeClassName: GraphAPICompute.name };
|
|
||||||
selfServeTraceFailure(failureTelemetry, updateTimeStamp);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
return armRequestResult?.operationStatusUrl;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const deleteComputeResource = async (): Promise<string> => {
|
|
||||||
const path = getPath(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name);
|
|
||||||
const telemetryData = { httpMethod: "DELETE", selfServeClassName: GraphAPICompute.name };
|
|
||||||
const deleteTimeStamp = selfServeTraceStart(telemetryData);
|
|
||||||
let armRequestResult;
|
|
||||||
try {
|
|
||||||
armRequestResult = await armRequestWithoutPolling({
|
|
||||||
host: configContext.ARM_ENDPOINT,
|
|
||||||
path,
|
|
||||||
method: "DELETE",
|
|
||||||
apiVersion,
|
|
||||||
});
|
|
||||||
selfServeTraceSuccess(telemetryData, deleteTimeStamp);
|
|
||||||
} catch (e) {
|
|
||||||
const failureTelemetry = { e, selfServeClassName: GraphAPICompute.name };
|
|
||||||
selfServeTraceFailure(failureTelemetry, deleteTimeStamp);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
return armRequestResult?.operationStatusUrl;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getComputeResource = async (): Promise<GraphAPIComputeServiceResource> => {
|
|
||||||
const path = getPath(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name);
|
|
||||||
const telemetryData = { httpMethod: "GET", selfServeClassName: GraphAPICompute.name };
|
|
||||||
const getResourceTimeStamp = selfServeTraceStart(telemetryData);
|
|
||||||
let armRequestResult;
|
|
||||||
try {
|
|
||||||
armRequestResult = await armRequestWithoutPolling<GraphAPIComputeServiceResource>({
|
|
||||||
host: configContext.ARM_ENDPOINT,
|
|
||||||
path,
|
|
||||||
method: "GET",
|
|
||||||
apiVersion,
|
|
||||||
});
|
|
||||||
selfServeTraceSuccess(telemetryData, getResourceTimeStamp);
|
|
||||||
} catch (e) {
|
|
||||||
const failureTelemetry = { e, selfServeClassName: GraphAPICompute.name };
|
|
||||||
selfServeTraceFailure(failureTelemetry, getResourceTimeStamp);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
return armRequestResult?.result;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getCurrentProvisioningState = async (): Promise<ComputeResponse> => {
|
|
||||||
try {
|
|
||||||
const response = await getComputeResource();
|
|
||||||
return {
|
|
||||||
sku: response.properties.instanceSize,
|
|
||||||
instances: response.properties.instanceCount,
|
|
||||||
status: response.properties.status,
|
|
||||||
endpoint: response.properties.GraphAPIComputeEndPoint,
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
return { sku: undefined, instances: undefined, status: undefined, endpoint: undefined };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const refreshComputeProvisioning = async (): Promise<RefreshResult> => {
|
|
||||||
try {
|
|
||||||
const response = await getComputeResource();
|
|
||||||
if (response.properties.status === ResourceStatus.Running.toString()) {
|
|
||||||
return { isUpdateInProgress: false, updateInProgressMessageTKey: undefined };
|
|
||||||
} else if (response.properties.status === ResourceStatus.Creating.toString()) {
|
|
||||||
return { isUpdateInProgress: true, updateInProgressMessageTKey: "CreateMessage" };
|
|
||||||
} else if (response.properties.status === ResourceStatus.Deleting.toString()) {
|
|
||||||
return { isUpdateInProgress: true, updateInProgressMessageTKey: "DeleteMessage" };
|
|
||||||
} else {
|
|
||||||
return { isUpdateInProgress: true, updateInProgressMessageTKey: "UpdateMessage" };
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
//TODO differentiate between different failures
|
|
||||||
return { isUpdateInProgress: false, updateInProgressMessageTKey: undefined };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getGeneralPath = (subscriptionId: string, resourceGroup: string, name: string): string => {
|
|
||||||
return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${name}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getReadRegions = async (): Promise<Array<string>> => {
|
|
||||||
try {
|
|
||||||
const readRegions = new Array<string>();
|
|
||||||
|
|
||||||
const response = await armRequestWithoutPolling<RegionsResponse>({
|
|
||||||
host: configContext.ARM_ENDPOINT,
|
|
||||||
path: getGeneralPath(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name),
|
|
||||||
method: "GET",
|
|
||||||
apiVersion: "2021-04-01-preview",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.result.location !== undefined) {
|
|
||||||
readRegions.push(response.result.location.replace(" ", "").toLowerCase());
|
|
||||||
} else {
|
|
||||||
for (const location of response.result.locations) {
|
|
||||||
readRegions.push(location.locationName.replace(" ", "").toLowerCase());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return readRegions;
|
|
||||||
} catch (err) {
|
|
||||||
return new Array<string>();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getFetchPricesPathForRegion = (subscriptionId: string): string => {
|
|
||||||
return `/subscriptions/${subscriptionId}/providers/Microsoft.CostManagement/fetchPrices`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getPriceMap = async (regions: Array<string>): Promise<Map<string, Map<string, number>>> => {
|
|
||||||
try {
|
|
||||||
const priceMap = new Map<string, Map<string, number>>();
|
|
||||||
|
|
||||||
for (const region of regions) {
|
|
||||||
const regionPriceMap = new Map<string, number>();
|
|
||||||
|
|
||||||
const response = await armRequestWithoutPolling<FetchPricesResponse>({
|
|
||||||
host: configContext.ARM_ENDPOINT,
|
|
||||||
path: getFetchPricesPathForRegion(userContext.subscriptionId),
|
|
||||||
method: "POST",
|
|
||||||
apiVersion: "2020-01-01-preview",
|
|
||||||
queryParams: {
|
|
||||||
filter:
|
|
||||||
"armRegionName eq '" +
|
|
||||||
region +
|
|
||||||
"' and serviceFamily eq 'Databases' and productName eq 'Azure Cosmos DB Dedicated Gateway - General Purpose'",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const item of response.result.Items) {
|
|
||||||
regionPriceMap.set(item.skuName, item.retailPrice);
|
|
||||||
}
|
|
||||||
priceMap.set(region, regionPriceMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
return priceMap;
|
|
||||||
} catch (err) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,423 +0,0 @@
|
|||||||
import { IsDisplayable, OnChange, PropertyInfo, RefreshOptions, Values } from "../Decorators";
|
|
||||||
import { selfServeTrace } from "../SelfServeTelemetryProcessor";
|
|
||||||
import {
|
|
||||||
ChoiceItem,
|
|
||||||
Description,
|
|
||||||
DescriptionType,
|
|
||||||
Info,
|
|
||||||
InputType,
|
|
||||||
NumberUiType,
|
|
||||||
OnSaveResult,
|
|
||||||
RefreshResult,
|
|
||||||
SelfServeBaseClass,
|
|
||||||
SmartUiInput,
|
|
||||||
} from "../SelfServeTypes";
|
|
||||||
import { BladeType, generateBladeLink } from "../SelfServeUtils";
|
|
||||||
import {
|
|
||||||
deleteComputeResource,
|
|
||||||
getCurrentProvisioningState,
|
|
||||||
getPriceMap,
|
|
||||||
getReadRegions,
|
|
||||||
refreshComputeProvisioning,
|
|
||||||
updateComputeResource,
|
|
||||||
} from "./GraphAPICompute.rp";
|
|
||||||
|
|
||||||
const costPerHourDefaultValue: Description = {
|
|
||||||
textTKey: "CostText",
|
|
||||||
type: DescriptionType.Text,
|
|
||||||
link: {
|
|
||||||
href: "https://aka.ms/cosmos-db-dedicated-gateway-pricing",
|
|
||||||
textTKey: "ComputePricing",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const connectionStringValue: Description = {
|
|
||||||
textTKey: "ConnectionStringText",
|
|
||||||
type: DescriptionType.Text,
|
|
||||||
link: {
|
|
||||||
href: generateBladeLink(BladeType.SqlKeys),
|
|
||||||
textTKey: "KeysBlade",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const metricsStringValue: Description = {
|
|
||||||
textTKey: "MetricsText",
|
|
||||||
type: DescriptionType.Text,
|
|
||||||
link: {
|
|
||||||
href: generateBladeLink(BladeType.Metrics),
|
|
||||||
textTKey: "MetricsBlade",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const CosmosD4s = "Cosmos.D4s";
|
|
||||||
const CosmosD8s = "Cosmos.D8s";
|
|
||||||
const CosmosD16s = "Cosmos.D16s";
|
|
||||||
|
|
||||||
const onSKUChange = (newValue: InputType, currentValues: Map<string, SmartUiInput>): Map<string, SmartUiInput> => {
|
|
||||||
currentValues.set("sku", { value: newValue });
|
|
||||||
currentValues.set("costPerHour", {
|
|
||||||
value: calculateCost(newValue as string, currentValues.get("instances").value as number),
|
|
||||||
});
|
|
||||||
|
|
||||||
return currentValues;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onNumberOfInstancesChange = (
|
|
||||||
newValue: InputType,
|
|
||||||
currentValues: Map<string, SmartUiInput>,
|
|
||||||
baselineValues: Map<string, SmartUiInput>
|
|
||||||
): Map<string, SmartUiInput> => {
|
|
||||||
currentValues.set("instances", { value: newValue });
|
|
||||||
const ComputeOriginallyEnabled = baselineValues.get("enableCompute")?.value as boolean;
|
|
||||||
const baselineInstances = baselineValues.get("instances")?.value as number;
|
|
||||||
if (!ComputeOriginallyEnabled || baselineInstances !== newValue) {
|
|
||||||
currentValues.set("warningBanner", {
|
|
||||||
value: {
|
|
||||||
textTKey: "WarningBannerOnUpdate",
|
|
||||||
link: {
|
|
||||||
href: "https://aka.ms/cosmos-db-dedicated-gateway-overview",
|
|
||||||
textTKey: "ComputePricing",
|
|
||||||
},
|
|
||||||
} as Description,
|
|
||||||
hidden: false,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
currentValues.set("warningBanner", undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentValues.set("costPerHour", {
|
|
||||||
value: calculateCost(currentValues.get("sku").value as string, newValue as number),
|
|
||||||
});
|
|
||||||
|
|
||||||
return currentValues;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onEnableComputeChange = (
|
|
||||||
newValue: InputType,
|
|
||||||
currentValues: Map<string, SmartUiInput>,
|
|
||||||
baselineValues: ReadonlyMap<string, SmartUiInput>
|
|
||||||
): Map<string, SmartUiInput> => {
|
|
||||||
currentValues.set("enableCompute", { value: newValue });
|
|
||||||
const ComputeOriginallyEnabled = baselineValues.get("enableCompute")?.value as boolean;
|
|
||||||
if (ComputeOriginallyEnabled === newValue) {
|
|
||||||
currentValues.set("sku", baselineValues.get("sku"));
|
|
||||||
currentValues.set("instances", baselineValues.get("instances"));
|
|
||||||
currentValues.set("costPerHour", baselineValues.get("costPerHour"));
|
|
||||||
currentValues.set("warningBanner", baselineValues.get("warningBanner"));
|
|
||||||
currentValues.set("connectionString", baselineValues.get("connectionString"));
|
|
||||||
currentValues.set("metricsString", baselineValues.get("metricsString"));
|
|
||||||
return currentValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentValues.set("warningBanner", undefined);
|
|
||||||
if (newValue === true) {
|
|
||||||
currentValues.set("warningBanner", {
|
|
||||||
value: {
|
|
||||||
textTKey: "WarningBannerOnUpdate",
|
|
||||||
link: {
|
|
||||||
href: "https://aka.ms/cosmos-db-dedicated-gateway-pricing", //needs updating
|
|
||||||
textTKey: "ComputePricing",
|
|
||||||
},
|
|
||||||
} as Description,
|
|
||||||
hidden: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
currentValues.set("costPerHour", {
|
|
||||||
value: calculateCost(baselineValues.get("sku").value as string, baselineValues.get("instances").value as number),
|
|
||||||
hidden: false,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
currentValues.set("warningBanner", {
|
|
||||||
value: {
|
|
||||||
textTKey: "WarningBannerOnDelete",
|
|
||||||
link: {
|
|
||||||
href: "https://aka.ms/cosmos-db-dedicated-gateway-overview", // needs updating
|
|
||||||
textTKey: "DeprovisioningDetailsText",
|
|
||||||
},
|
|
||||||
} as Description,
|
|
||||||
hidden: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
currentValues.set("costPerHour", { value: costPerHourDefaultValue, hidden: true });
|
|
||||||
}
|
|
||||||
const sku = currentValues.get("sku");
|
|
||||||
const hideAttributes = newValue === undefined || !(newValue as boolean);
|
|
||||||
currentValues.set("sku", {
|
|
||||||
value: sku.value,
|
|
||||||
hidden: hideAttributes,
|
|
||||||
disabled: ComputeOriginallyEnabled,
|
|
||||||
});
|
|
||||||
currentValues.set("instances", {
|
|
||||||
value: 1,
|
|
||||||
hidden: hideAttributes,
|
|
||||||
disabled: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
currentValues.set("connectionString", {
|
|
||||||
value: connectionStringValue,
|
|
||||||
hidden: !newValue || !ComputeOriginallyEnabled,
|
|
||||||
});
|
|
||||||
|
|
||||||
currentValues.set("metricsString", {
|
|
||||||
value: metricsStringValue,
|
|
||||||
hidden: !newValue || !ComputeOriginallyEnabled,
|
|
||||||
});
|
|
||||||
|
|
||||||
return currentValues;
|
|
||||||
};
|
|
||||||
|
|
||||||
const skuDropDownItems: ChoiceItem[] = [
|
|
||||||
{ labelTKey: "CosmosD4s", key: CosmosD4s },
|
|
||||||
{ labelTKey: "CosmosD8s", key: CosmosD8s },
|
|
||||||
{ labelTKey: "CosmosD16s", key: CosmosD16s },
|
|
||||||
];
|
|
||||||
|
|
||||||
const getSkus = async (): Promise<ChoiceItem[]> => {
|
|
||||||
return skuDropDownItems;
|
|
||||||
};
|
|
||||||
|
|
||||||
const NumberOfInstancesDropdownInfo: Info = {
|
|
||||||
messageTKey: "ResizingDecisionText",
|
|
||||||
link: {
|
|
||||||
href: "https://aka.ms/cosmos-db-dedicated-gateway-size", // todo
|
|
||||||
textTKey: "ResizingDecisionLink",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const getInstancesMin = async (): Promise<number> => {
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getInstancesMax = async (): Promise<number> => {
|
|
||||||
return 5;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ApproximateCostDropDownInfo: Info = {
|
|
||||||
messageTKey: "CostText",
|
|
||||||
link: {
|
|
||||||
href: "https://aka.ms/cosmos-db-dedicated-gateway-pricing", //todo
|
|
||||||
textTKey: "ComputePricing",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let priceMap: Map<string, Map<string, number>>;
|
|
||||||
let regions: Array<string>;
|
|
||||||
|
|
||||||
const calculateCost = (skuName: string, instanceCount: number): Description => {
|
|
||||||
try {
|
|
||||||
let costPerHour = 0;
|
|
||||||
for (const region of regions) {
|
|
||||||
const incrementalCost = priceMap.get(region).get(skuName.replace("Cosmos.", ""));
|
|
||||||
if (incrementalCost === undefined) {
|
|
||||||
throw new Error("Value not found in map.");
|
|
||||||
}
|
|
||||||
costPerHour += incrementalCost;
|
|
||||||
}
|
|
||||||
|
|
||||||
costPerHour *= instanceCount;
|
|
||||||
costPerHour = Math.round(costPerHour * 100) / 100;
|
|
||||||
|
|
||||||
return {
|
|
||||||
textTKey: `${costPerHour} USD`,
|
|
||||||
type: DescriptionType.Text,
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
return costPerHourDefaultValue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@IsDisplayable()
|
|
||||||
@RefreshOptions({ retryIntervalInMs: 20000 })
|
|
||||||
export default class GraphAPICompute extends SelfServeBaseClass {
|
|
||||||
public onRefresh = async (): Promise<RefreshResult> => {
|
|
||||||
return await refreshComputeProvisioning();
|
|
||||||
};
|
|
||||||
|
|
||||||
public onSave = async (
|
|
||||||
currentValues: Map<string, SmartUiInput>,
|
|
||||||
baselineValues: Map<string, SmartUiInput>
|
|
||||||
): Promise<OnSaveResult> => {
|
|
||||||
selfServeTrace({ selfServeClassName: GraphAPICompute.name });
|
|
||||||
|
|
||||||
const ComputeCurrentlyEnabled = currentValues.get("enableCompute")?.value as boolean;
|
|
||||||
const ComputeOriginallyEnabled = baselineValues.get("enableCompute")?.value as boolean;
|
|
||||||
|
|
||||||
currentValues.set("warningBanner", undefined);
|
|
||||||
|
|
||||||
if (ComputeOriginallyEnabled) {
|
|
||||||
if (!ComputeCurrentlyEnabled) {
|
|
||||||
const operationStatusUrl = await deleteComputeResource();
|
|
||||||
return {
|
|
||||||
operationStatusUrl: operationStatusUrl,
|
|
||||||
portalNotification: {
|
|
||||||
initialize: {
|
|
||||||
titleTKey: "DeleteInitializeTitle",
|
|
||||||
messageTKey: "DeleteInitializeMessage",
|
|
||||||
},
|
|
||||||
success: {
|
|
||||||
titleTKey: "DeleteSuccessTitle",
|
|
||||||
messageTKey: "DeleteSuccesseMessage",
|
|
||||||
},
|
|
||||||
failure: {
|
|
||||||
titleTKey: "DeleteFailureTitle",
|
|
||||||
messageTKey: "DeleteFailureMessage",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const sku = currentValues.get("sku")?.value as string;
|
|
||||||
const instances = currentValues.get("instances").value as number;
|
|
||||||
const operationStatusUrl = await updateComputeResource(sku, instances);
|
|
||||||
return {
|
|
||||||
operationStatusUrl: operationStatusUrl,
|
|
||||||
portalNotification: {
|
|
||||||
initialize: {
|
|
||||||
titleTKey: "UpdateInitializeTitle",
|
|
||||||
messageTKey: "UpdateInitializeMessage",
|
|
||||||
},
|
|
||||||
success: {
|
|
||||||
titleTKey: "UpdateSuccessTitle",
|
|
||||||
messageTKey: "UpdateSuccesseMessage",
|
|
||||||
},
|
|
||||||
failure: {
|
|
||||||
titleTKey: "UpdateFailureTitle",
|
|
||||||
messageTKey: "UpdateFailureMessage",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const sku = currentValues.get("sku")?.value as string;
|
|
||||||
const instances = currentValues.get("instances").value as number;
|
|
||||||
const operationStatusUrl = await updateComputeResource(sku, instances);
|
|
||||||
return {
|
|
||||||
operationStatusUrl: operationStatusUrl,
|
|
||||||
portalNotification: {
|
|
||||||
initialize: {
|
|
||||||
titleTKey: "CreateInitializeTitle",
|
|
||||||
messageTKey: "CreateInitializeMessage",
|
|
||||||
},
|
|
||||||
success: {
|
|
||||||
titleTKey: "CreateSuccessTitle",
|
|
||||||
messageTKey: "CreateSuccesseMessage",
|
|
||||||
},
|
|
||||||
failure: {
|
|
||||||
titleTKey: "CreateFailureTitle",
|
|
||||||
messageTKey: "CreateFailureMessage",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public initialize = async (): Promise<Map<string, SmartUiInput>> => {
|
|
||||||
// Based on the RP call enableCompute will be true if it has not yet been enabled and false if it has.
|
|
||||||
const defaults = new Map<string, SmartUiInput>();
|
|
||||||
defaults.set("enableCompute", { value: false });
|
|
||||||
defaults.set("sku", { value: CosmosD4s, hidden: true });
|
|
||||||
defaults.set("instances", { value: 1, hidden: true });
|
|
||||||
defaults.set("costPerHour", undefined);
|
|
||||||
defaults.set("connectionString", undefined);
|
|
||||||
defaults.set("metricsString", {
|
|
||||||
value: undefined,
|
|
||||||
hidden: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
regions = await getReadRegions();
|
|
||||||
priceMap = await getPriceMap(regions);
|
|
||||||
const response = await getCurrentProvisioningState();
|
|
||||||
if (response.status && response.status === "Creating") {
|
|
||||||
defaults.set("enableCompute", { value: true });
|
|
||||||
defaults.set("sku", { value: response.sku, disabled: true });
|
|
||||||
defaults.set("instances", { value: response.instances, disabled: true });
|
|
||||||
defaults.set("costPerHour", { value: calculateCost(response.sku, response.instances) });
|
|
||||||
defaults.set("connectionString", {
|
|
||||||
value: connectionStringValue,
|
|
||||||
hidden: true,
|
|
||||||
});
|
|
||||||
defaults.set("metricsString", {
|
|
||||||
value: metricsStringValue,
|
|
||||||
hidden: true,
|
|
||||||
});
|
|
||||||
} else if (response.status && response.status !== "Deleting") {
|
|
||||||
defaults.set("enableCompute", { value: true });
|
|
||||||
defaults.set("sku", { value: response.sku, disabled: true });
|
|
||||||
defaults.set("instances", { value: response.instances });
|
|
||||||
defaults.set("costPerHour", { value: calculateCost(response.sku, response.instances) });
|
|
||||||
defaults.set("connectionString", {
|
|
||||||
value: connectionStringValue,
|
|
||||||
hidden: false,
|
|
||||||
});
|
|
||||||
defaults.set("metricsString", {
|
|
||||||
value: metricsStringValue,
|
|
||||||
hidden: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
defaults.set("warningBanner", undefined);
|
|
||||||
return defaults;
|
|
||||||
};
|
|
||||||
|
|
||||||
@Values({
|
|
||||||
isDynamicDescription: true,
|
|
||||||
})
|
|
||||||
warningBanner: string;
|
|
||||||
|
|
||||||
@Values({
|
|
||||||
description: {
|
|
||||||
textTKey: "GraphAPIDescription",
|
|
||||||
type: DescriptionType.Text,
|
|
||||||
link: {
|
|
||||||
href: "https://aka.ms/cosmos-db-dedicated-gateway-overview", //todo
|
|
||||||
textTKey: "LearnAboutCompute",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
description: string;
|
|
||||||
|
|
||||||
@OnChange(onEnableComputeChange)
|
|
||||||
@Values({
|
|
||||||
labelTKey: "Compute",
|
|
||||||
trueLabelTKey: "Provisioned",
|
|
||||||
falseLabelTKey: "Deprovisioned",
|
|
||||||
})
|
|
||||||
enableCompute: boolean;
|
|
||||||
|
|
||||||
@OnChange(onSKUChange)
|
|
||||||
@Values({
|
|
||||||
labelTKey: "SKUs",
|
|
||||||
choices: getSkus,
|
|
||||||
placeholderTKey: "SKUsPlaceHolder",
|
|
||||||
})
|
|
||||||
sku: ChoiceItem;
|
|
||||||
|
|
||||||
@OnChange(onNumberOfInstancesChange)
|
|
||||||
@PropertyInfo(NumberOfInstancesDropdownInfo)
|
|
||||||
@Values({
|
|
||||||
labelTKey: "NumberOfInstances",
|
|
||||||
min: getInstancesMin,
|
|
||||||
max: getInstancesMax,
|
|
||||||
step: 1,
|
|
||||||
uiType: NumberUiType.Spinner,
|
|
||||||
})
|
|
||||||
instances: number;
|
|
||||||
|
|
||||||
@PropertyInfo(ApproximateCostDropDownInfo)
|
|
||||||
@Values({
|
|
||||||
labelTKey: "ApproximateCost",
|
|
||||||
isDynamicDescription: true,
|
|
||||||
})
|
|
||||||
costPerHour: string;
|
|
||||||
|
|
||||||
@Values({
|
|
||||||
labelTKey: "ConnectionString",
|
|
||||||
isDynamicDescription: true,
|
|
||||||
})
|
|
||||||
connectionString: string;
|
|
||||||
|
|
||||||
@Values({
|
|
||||||
labelTKey: "MonitorUsage",
|
|
||||||
description: metricsStringValue,
|
|
||||||
})
|
|
||||||
metricsString: string;
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
export enum Regions {
|
|
||||||
NorthCentralUS = "NorthCentralUS",
|
|
||||||
WestUS = "WestUS",
|
|
||||||
EastUS2 = "EastUS2",
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AccountProps {
|
|
||||||
regions: Regions;
|
|
||||||
enableLogging: boolean;
|
|
||||||
accountName: string;
|
|
||||||
collectionThroughput: number;
|
|
||||||
dbThroughput: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GraphAPIComputeServiceResource = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
properties: GraphAPIComputeServiceProps;
|
|
||||||
locations: GraphAPIComputeServiceLocations;
|
|
||||||
};
|
|
||||||
export type GraphAPIComputeServiceProps = {
|
|
||||||
serviceType: string;
|
|
||||||
creationTime: string;
|
|
||||||
status: string;
|
|
||||||
instanceSize: string;
|
|
||||||
instanceCount: number;
|
|
||||||
GraphAPIComputeEndPoint: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type GraphAPIComputeServiceLocations = {
|
|
||||||
location: string;
|
|
||||||
status: string;
|
|
||||||
GraphAPIComputeEndpoint: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type UpdateComputeRequestParameters = {
|
|
||||||
properties: UpdateComputeRequestProperties;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type UpdateComputeRequestProperties = {
|
|
||||||
instanceSize: string;
|
|
||||||
instanceCount: number;
|
|
||||||
serviceType: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type FetchPricesResponse = {
|
|
||||||
Items: Array<PriceItem>;
|
|
||||||
NextPageLink: string | undefined;
|
|
||||||
Count: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PriceItem = {
|
|
||||||
retailPrice: number;
|
|
||||||
skuName: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RegionsResponse = {
|
|
||||||
locations: Array<RegionItem>;
|
|
||||||
location: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RegionItem = {
|
|
||||||
locationName: string;
|
|
||||||
};
|
|
||||||
@@ -50,14 +50,6 @@ const getDescriptor = async (selfServeType: SelfServeType): Promise<SelfServeDes
|
|||||||
await loadTranslations(sqlX.constructor.name);
|
await loadTranslations(sqlX.constructor.name);
|
||||||
return sqlX.toSelfServeDescriptor();
|
return sqlX.toSelfServeDescriptor();
|
||||||
}
|
}
|
||||||
case SelfServeType.graphapicompute: {
|
|
||||||
const GraphAPICompute = await import(
|
|
||||||
/* webpackChunkName: "GraphAPICompute" */ "./GraphAPICompute/GraphAPICompute"
|
|
||||||
);
|
|
||||||
const graphAPICompute = new GraphAPICompute.default();
|
|
||||||
await loadTranslations(graphAPICompute.constructor.name);
|
|
||||||
return graphAPICompute.toSelfServeDescriptor();
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ export enum SelfServeType {
|
|||||||
// Add your self serve types here
|
// Add your self serve types here
|
||||||
example = "example",
|
example = "example",
|
||||||
sqlx = "sqlx",
|
sqlx = "sqlx",
|
||||||
graphapicompute = "graphapicompute",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -138,9 +138,9 @@ const getGeneralPath = (subscriptionId: string, resourceGroup: string, name: str
|
|||||||
return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${name}`;
|
return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${name}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRegions = async (): Promise<Array<string>> => {
|
export const getReadRegions = async (): Promise<Array<string>> => {
|
||||||
try {
|
try {
|
||||||
const regions = new Array<string>();
|
const readRegions = new Array<string>();
|
||||||
|
|
||||||
const response = await armRequestWithoutPolling<RegionsResponse>({
|
const response = await armRequestWithoutPolling<RegionsResponse>({
|
||||||
host: configContext.ARM_ENDPOINT,
|
host: configContext.ARM_ENDPOINT,
|
||||||
@@ -150,13 +150,13 @@ export const getRegions = async (): Promise<Array<string>> => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.result.location !== undefined) {
|
if (response.result.location !== undefined) {
|
||||||
regions.push(response.result.location.split(" ").join("").toLowerCase());
|
readRegions.push(response.result.location.replace(" ", "").toLowerCase());
|
||||||
} else {
|
} else {
|
||||||
for (const location of response.result.locations) {
|
for (const location of response.result.locations) {
|
||||||
regions.push(location.locationName.split(" ").join("").toLowerCase());
|
readRegions.push(location.locationName.replace(" ", "").toLowerCase());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return regions;
|
return readRegions;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return new Array<string>();
|
return new Array<string>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
deleteDedicatedGatewayResource,
|
deleteDedicatedGatewayResource,
|
||||||
getCurrentProvisioningState,
|
getCurrentProvisioningState,
|
||||||
getPriceMap,
|
getPriceMap,
|
||||||
getRegions,
|
getReadRegions,
|
||||||
refreshDedicatedGatewayProvisioning,
|
refreshDedicatedGatewayProvisioning,
|
||||||
updateDedicatedGatewayResource,
|
updateDedicatedGatewayResource,
|
||||||
} from "./SqlX.rp";
|
} from "./SqlX.rp";
|
||||||
@@ -324,7 +324,7 @@ export default class SqlX extends SelfServeBaseClass {
|
|||||||
hidden: true,
|
hidden: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
regions = await getRegions();
|
regions = await getReadRegions();
|
||||||
priceMap = await getPriceMap(regions);
|
priceMap = await getPriceMap(regions);
|
||||||
|
|
||||||
const response = await getCurrentProvisioningState();
|
const response = await getCurrentProvisioningState();
|
||||||
|
|||||||
@@ -125,8 +125,7 @@ export class OfferPricing {
|
|||||||
S3Price: 0.1344,
|
S3Price: 0.1344,
|
||||||
Standard: {
|
Standard: {
|
||||||
StartingPrice: 24 / hoursInAMonth, // per hour
|
StartingPrice: 24 / hoursInAMonth, // per hour
|
||||||
SingleMasterPricePerRU: 0.00008,
|
PricePerRU: 0.00008,
|
||||||
MultiMasterPricePerRU: 0.00016,
|
|
||||||
PricePerGB: 0.25 / hoursInAMonth,
|
PricePerGB: 0.25 / hoursInAMonth,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -138,8 +137,7 @@ export class OfferPricing {
|
|||||||
S3Price: 0.6,
|
S3Price: 0.6,
|
||||||
Standard: {
|
Standard: {
|
||||||
StartingPrice: OfferPricing.MonthlyPricing.mooncake.Standard.StartingPrice / hoursInAMonth, // per hour
|
StartingPrice: OfferPricing.MonthlyPricing.mooncake.Standard.StartingPrice / hoursInAMonth, // per hour
|
||||||
SingleMasterPricePerRU: 0.00051,
|
PricePerRU: 0.00051,
|
||||||
MultiMasterPricePerRU: 0.00102,
|
|
||||||
PricePerGB: OfferPricing.MonthlyPricing.mooncake.Standard.PricePerGB / hoursInAMonth,
|
PricePerGB: OfferPricing.MonthlyPricing.mooncake.Standard.PricePerGB / hoursInAMonth,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import * as Constants from "./Constants";
|
|||||||
|
|
||||||
export function computeRUUsagePrice(serverId: string, requestUnits: number): string {
|
export function computeRUUsagePrice(serverId: string, requestUnits: number): string {
|
||||||
if (serverId === "mooncake") {
|
if (serverId === "mooncake") {
|
||||||
const ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.mooncake.Standard.SingleMasterPricePerRU;
|
const ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerRU;
|
||||||
return calculateEstimateNumber(ruCharge) + " " + Constants.OfferPricing.HourlyPricing.mooncake.Currency;
|
return calculateEstimateNumber(ruCharge) + " " + Constants.OfferPricing.HourlyPricing.mooncake.Currency;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.default.Standard.SingleMasterPricePerRU;
|
const ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRU;
|
||||||
return calculateEstimateNumber(ruCharge) + " " + Constants.OfferPricing.HourlyPricing.default.Currency;
|
return calculateEstimateNumber(ruCharge) + " " + Constants.OfferPricing.HourlyPricing.default.Currency;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { HttpStatusCodes } from "../Common/Constants";
|
|||||||
import { useDialog } from "../Explorer/Controls/Dialog";
|
import { useDialog } from "../Explorer/Controls/Dialog";
|
||||||
import { GalleryTab, SortBy } from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
|
import { GalleryTab, SortBy } from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
|
||||||
import Explorer from "../Explorer/Explorer";
|
import Explorer from "../Explorer/Explorer";
|
||||||
import { useNotebook } from "../Explorer/Notebook/useNotebook";
|
|
||||||
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
|
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
|
||||||
import * as GalleryUtils from "./GalleryUtils";
|
import * as GalleryUtils from "./GalleryUtils";
|
||||||
|
|
||||||
@@ -35,7 +34,7 @@ describe("GalleryUtils", () => {
|
|||||||
|
|
||||||
expect(useDialog.getState().visible).toBe(true);
|
expect(useDialog.getState().visible).toBe(true);
|
||||||
expect(useDialog.getState().dialogProps).toBeDefined();
|
expect(useDialog.getState().dialogProps).toBeDefined();
|
||||||
expect(useDialog.getState().dialogProps.title).toBe(`Download to ${useNotebook.getState().notebookFolderName}`);
|
expect(useDialog.getState().dialogProps.title).toBe("Download to My Notebooks");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("favoriteItem favorites item", async () => {
|
it("favoriteItem favorites item", async () => {
|
||||||
|
|||||||
@@ -224,12 +224,12 @@ export function downloadItem(
|
|||||||
|
|
||||||
const name = data.name;
|
const name = data.name;
|
||||||
useDialog.getState().showOkCancelModalDialog(
|
useDialog.getState().showOkCancelModalDialog(
|
||||||
`Download to ${useNotebook.getState().notebookFolderName}`,
|
`Download to ${useNotebook.getState().NotebookFolderName}`,
|
||||||
`Download ${name} from gallery as a copy to your notebooks to run and/or edit the notebook.`,
|
`Download ${name} from gallery as a copy to your notebooks to run and/or edit the notebook.`,
|
||||||
"Download",
|
"Download",
|
||||||
async () => {
|
async () => {
|
||||||
const clearInProgressMessage = logConsoleProgress(
|
const clearInProgressMessage = logConsoleProgress(
|
||||||
`Downloading ${name} to ${useNotebook.getState().notebookFolderName}`
|
`Downloading ${name} to ${useNotebook.getState().NotebookFolderName}`
|
||||||
);
|
);
|
||||||
const startKey = traceStart(Action.NotebooksGalleryDownload, {
|
const startKey = traceStart(Action.NotebooksGalleryDownload, {
|
||||||
notebookId: data.id,
|
notebookId: data.id,
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ describe("PricingUtils Tests", () => {
|
|||||||
expect(value).toBe(0.00012);
|
expect(value).toBe(0.00012);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 0.00032 for default cloud, 1RU, 2 region, multimaster enabled", () => {
|
it("should return 0.00048 for default cloud, 1RU, 2 region, multimaster enabled", () => {
|
||||||
const value = PricingUtils.computeRUUsagePriceHourly({
|
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||||
serverId: "default",
|
serverId: "default",
|
||||||
requestUnits: 1,
|
requestUnits: 1,
|
||||||
@@ -158,9 +158,9 @@ describe("PricingUtils Tests", () => {
|
|||||||
multimasterEnabled: true,
|
multimasterEnabled: true,
|
||||||
isAutoscale: false,
|
isAutoscale: false,
|
||||||
});
|
});
|
||||||
expect(value).toBe(0.00032);
|
expect(value).toBe(0.00048);
|
||||||
});
|
});
|
||||||
it("should return 0.00032 for default cloud, 1RU, 2 region, multimaster enabled, autoscale", () => {
|
it("should return 0.00048 for default cloud, 1RU, 2 region, multimaster enabled, autoscale", () => {
|
||||||
const value = PricingUtils.computeRUUsagePriceHourly({
|
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||||
serverId: "default",
|
serverId: "default",
|
||||||
requestUnits: 1,
|
requestUnits: 1,
|
||||||
@@ -168,7 +168,7 @@ describe("PricingUtils Tests", () => {
|
|||||||
multimasterEnabled: true,
|
multimasterEnabled: true,
|
||||||
isAutoscale: true,
|
isAutoscale: true,
|
||||||
});
|
});
|
||||||
expect(value).toBe(0.00032);
|
expect(value).toBe(0.00096);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -251,50 +251,73 @@ describe("PricingUtils Tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("getPricePerRu()", () => {
|
describe("getPricePerRu()", () => {
|
||||||
it("should return 0.00008 for single master default clouds", () => {
|
it("should return 0.00008 for default clouds", () => {
|
||||||
const value = PricingUtils.getPricePerRu("default", 1);
|
const value = PricingUtils.getPricePerRu("default");
|
||||||
expect(value).toBe(0.00008);
|
expect(value).toBe(0.00008);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 0.00016 for multi master default clouds", () => {
|
it("should return 0.00051 for mooncake", () => {
|
||||||
const value = PricingUtils.getPricePerRu("default", 2);
|
const value = PricingUtils.getPricePerRu("mooncake");
|
||||||
expect(value).toBe(0.00016);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return 0.00051 for single master mooncake", () => {
|
|
||||||
const value = PricingUtils.getPricePerRu("mooncake", 1);
|
|
||||||
expect(value).toBe(0.00051);
|
expect(value).toBe(0.00051);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 0.00102 for multi master mooncake", () => {
|
|
||||||
const value = PricingUtils.getPricePerRu("mooncake", 2);
|
|
||||||
expect(value).toBe(0.00102);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getRegionMultiplier()", () => {
|
describe("getRegionMultiplier()", () => {
|
||||||
|
describe("without multimaster", () => {
|
||||||
it("should return 0 for undefined", () => {
|
it("should return 0 for undefined", () => {
|
||||||
const value = PricingUtils.getRegionMultiplier(undefined);
|
const value = PricingUtils.getRegionMultiplier(undefined, false);
|
||||||
expect(value).toBe(0);
|
expect(value).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 0 for -1", () => {
|
it("should return 0 for -1", () => {
|
||||||
const value = PricingUtils.getRegionMultiplier(-1);
|
const value = PricingUtils.getRegionMultiplier(-1, false);
|
||||||
expect(value).toBe(0);
|
expect(value).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 0 for 0", () => {
|
it("should return 0 for 0", () => {
|
||||||
const value = PricingUtils.getRegionMultiplier(0);
|
const value = PricingUtils.getRegionMultiplier(0, false);
|
||||||
expect(value).toBe(0);
|
expect(value).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 1 for 1", () => {
|
it("should return 1 for 1", () => {
|
||||||
const value = PricingUtils.getRegionMultiplier(1);
|
const value = PricingUtils.getRegionMultiplier(1, false);
|
||||||
expect(value).toBe(1);
|
expect(value).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 2 for 2", () => {
|
it("should return 2 for 2", () => {
|
||||||
const value = PricingUtils.getRegionMultiplier(2);
|
const value = PricingUtils.getRegionMultiplier(2, false);
|
||||||
expect(value).toBe(2);
|
expect(value).toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("with multimaster", () => {
|
||||||
|
it("should return 0 for undefined", () => {
|
||||||
|
const value = PricingUtils.getRegionMultiplier(undefined, true);
|
||||||
|
expect(value).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return 0 for -1", () => {
|
||||||
|
const value = PricingUtils.getRegionMultiplier(-1, true);
|
||||||
|
expect(value).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return 0 for 0", () => {
|
||||||
|
const value = PricingUtils.getRegionMultiplier(0, true);
|
||||||
|
expect(value).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return 1 for 1", () => {
|
||||||
|
const value = PricingUtils.getRegionMultiplier(1, true);
|
||||||
|
expect(value).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return 3 for 2", () => {
|
||||||
|
const value = PricingUtils.getRegionMultiplier(2, true);
|
||||||
|
expect(value).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("getMultimasterMultiplier()", () => {
|
describe("getMultimasterMultiplier()", () => {
|
||||||
it("should return 1 for multimaster disabled", () => {
|
it("should return 1 for multimaster disabled", () => {
|
||||||
const value = PricingUtils.getMultimasterMultiplier(1, false);
|
const value = PricingUtils.getMultimasterMultiplier(1, false);
|
||||||
@@ -353,7 +376,7 @@ describe("PricingUtils Tests", () => {
|
|||||||
true /* multimaster */
|
true /* multimaster */
|
||||||
);
|
);
|
||||||
expect(value).toBe(
|
expect(value).toBe(
|
||||||
"Cost (USD): <b>$0.13 hourly / $3.07 daily / $93.44 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>"
|
"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>"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -401,7 +424,7 @@ describe("PricingUtils Tests", () => {
|
|||||||
true /* multimaster */,
|
true /* multimaster */,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
expect(value).toBe("I acknowledge the estimated $3.07 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", () => {
|
||||||
|
|||||||
@@ -34,18 +34,26 @@ export function getRuToolTipText(): string {
|
|||||||
* Otherwise, return numberOfRegions
|
* Otherwise, return numberOfRegions
|
||||||
* @param numberOfRegions
|
* @param numberOfRegions
|
||||||
*/
|
*/
|
||||||
export function getRegionMultiplier(numberOfRegions: number): number {
|
export function getRegionMultiplier(numberOfRegions: number, multimasterEnabled: boolean): number {
|
||||||
const normalizedNumberOfRegions: number = normalizeNumber(numberOfRegions);
|
const normalizedNumberOfRegions: number = normalizeNumber(numberOfRegions);
|
||||||
|
|
||||||
if (normalizedNumberOfRegions <= 0) {
|
if (normalizedNumberOfRegions <= 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (numberOfRegions === 1) {
|
||||||
|
return numberOfRegions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (multimasterEnabled) {
|
||||||
|
return numberOfRegions + 1;
|
||||||
|
}
|
||||||
|
|
||||||
return numberOfRegions;
|
return numberOfRegions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMultimasterMultiplier(numberOfRegions: number, multimasterEnabled: boolean): number {
|
export function getMultimasterMultiplier(numberOfRegions: number, multimasterEnabled: boolean): number {
|
||||||
const regionMultiplier: number = getRegionMultiplier(numberOfRegions);
|
const regionMultiplier: number = getRegionMultiplier(numberOfRegions, multimasterEnabled);
|
||||||
const multimasterMultiplier: number = !multimasterEnabled ? 1 : regionMultiplier > 1 ? 2 : 1;
|
const multimasterMultiplier: number = !multimasterEnabled ? 1 : regionMultiplier > 1 ? 2 : 1;
|
||||||
|
|
||||||
return multimasterMultiplier;
|
return multimasterMultiplier;
|
||||||
@@ -58,12 +66,10 @@ export function computeRUUsagePriceHourly({
|
|||||||
multimasterEnabled,
|
multimasterEnabled,
|
||||||
isAutoscale,
|
isAutoscale,
|
||||||
}: ComputeRUUsagePriceHourlyArgs): number {
|
}: ComputeRUUsagePriceHourlyArgs): number {
|
||||||
const regionMultiplier: number = getRegionMultiplier(numberOfRegions);
|
const regionMultiplier: number = getRegionMultiplier(numberOfRegions, multimasterEnabled);
|
||||||
const multimasterMultiplier: number = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
|
const multimasterMultiplier: number = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
|
||||||
const pricePerRu = isAutoscale
|
const pricePerRu = isAutoscale ? getAutoscalePricePerRu(serverId, multimasterMultiplier) : getPricePerRu(serverId);
|
||||||
? getAutoscalePricePerRu(serverId, multimasterMultiplier)
|
const ruCharge = requestUnits * pricePerRu * multimasterMultiplier * regionMultiplier;
|
||||||
: getPricePerRu(serverId, multimasterMultiplier);
|
|
||||||
const ruCharge = requestUnits * pricePerRu * regionMultiplier;
|
|
||||||
|
|
||||||
return Number(ruCharge.toFixed(5));
|
return Number(ruCharge.toFixed(5));
|
||||||
}
|
}
|
||||||
@@ -143,16 +149,12 @@ export function getAutoscalePricePerRu(serverId: string, mmMultiplier: number):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPricePerRu(serverId: string, mmMultiplier: number): number {
|
export function getPricePerRu(serverId: string): number {
|
||||||
if (serverId === "mooncake") {
|
if (serverId === "mooncake") {
|
||||||
return mmMultiplier > 1
|
return Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerRU;
|
||||||
? Constants.OfferPricing.HourlyPricing.mooncake.Standard.MultiMasterPricePerRU
|
|
||||||
: Constants.OfferPricing.HourlyPricing.mooncake.Standard.SingleMasterPricePerRU;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return mmMultiplier > 1
|
return Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRU;
|
||||||
? Constants.OfferPricing.HourlyPricing.default.Standard.MultiMasterPricePerRU
|
|
||||||
: Constants.OfferPricing.HourlyPricing.default.Standard.SingleMasterPricePerRU;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAutoPilotV3SpendHtml(maxAutoPilotThroughputSet: number, isDatabaseThroughput: boolean): string {
|
export function getAutoPilotV3SpendHtml(maxAutoPilotThroughputSet: number, isDatabaseThroughput: boolean): string {
|
||||||
@@ -186,7 +188,9 @@ export function getEstimatedAutoscaleSpendHtml(
|
|||||||
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
|
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
|
||||||
const currency: string = getPriceCurrency(serverId);
|
const currency: string = getPriceCurrency(serverId);
|
||||||
const currencySign: string = getCurrencySign(serverId);
|
const currencySign: string = getCurrencySign(serverId);
|
||||||
const pricePerRu = getAutoscalePricePerRu(serverId, getMultimasterMultiplier(regions, multimaster));
|
const pricePerRu =
|
||||||
|
getAutoscalePricePerRu(serverId, getMultimasterMultiplier(regions, multimaster)) *
|
||||||
|
getMultimasterMultiplier(regions, multimaster);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
`Estimated monthly cost (${currency}): <b>` +
|
`Estimated monthly cost (${currency}): <b>` +
|
||||||
@@ -215,7 +219,7 @@ export function getEstimatedSpendHtml(
|
|||||||
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
|
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
|
||||||
const currency: string = getPriceCurrency(serverId);
|
const currency: string = getPriceCurrency(serverId);
|
||||||
const currencySign: string = getCurrencySign(serverId);
|
const currencySign: string = getCurrencySign(serverId);
|
||||||
const pricePerRu = getPricePerRu(serverId, getMultimasterMultiplier(regions, multimaster));
|
const pricePerRu = getPricePerRu(serverId) * getMultimasterMultiplier(regions, multimaster);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
`Cost (${currency}): <b>` +
|
`Cost (${currency}): <b>` +
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ module.exports = function (_env = {}, argv = {}) {
|
|||||||
envVars.NODE_ENV = "development";
|
envVars.NODE_ENV = "development";
|
||||||
envVars.AZURE_CLIENT_ID = AZURE_CLIENT_ID;
|
envVars.AZURE_CLIENT_ID = AZURE_CLIENT_ID;
|
||||||
envVars.AZURE_TENANT_ID = AZURE_TENANT_ID;
|
envVars.AZURE_TENANT_ID = AZURE_TENANT_ID;
|
||||||
envVars.AZURE_CLIENT_SECRET = AZURE_CLIENT_SECRET || null;
|
envVars.AZURE_CLIENT_SECRET = AZURE_CLIENT_SECRET;
|
||||||
envVars.SUBSCRIPTION_ID = SUBSCRIPTION_ID;
|
envVars.SUBSCRIPTION_ID = SUBSCRIPTION_ID;
|
||||||
envVars.RESOURCE_GROUP = RESOURCE_GROUP;
|
envVars.RESOURCE_GROUP = RESOURCE_GROUP;
|
||||||
typescriptRule.use[0].options.compilerOptions = { target: "ES2018" };
|
typescriptRule.use[0].options.compilerOptions = { target: "ES2018" };
|
||||||
|
|||||||
Reference in New Issue
Block a user