mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-06-13 07:57:26 +01:00
Merge branch 'master' into users/srnara/selfServeDemo
This commit is contained in:
@@ -111,13 +111,9 @@ src/Explorer/OpenActionsStubs.ts
|
|||||||
src/Explorer/Panes/AddDatabasePane.ts
|
src/Explorer/Panes/AddDatabasePane.ts
|
||||||
src/Explorer/Panes/AddDatabasePane.test.ts
|
src/Explorer/Panes/AddDatabasePane.test.ts
|
||||||
src/Explorer/Panes/BrowseQueriesPane.ts
|
src/Explorer/Panes/BrowseQueriesPane.ts
|
||||||
src/Explorer/Panes/CassandraAddCollectionPane.ts
|
|
||||||
src/Explorer/Panes/ContextualPaneBase.ts
|
src/Explorer/Panes/ContextualPaneBase.ts
|
||||||
src/Explorer/Panes/DeleteDatabaseConfirmationPane.test.ts
|
|
||||||
src/Explorer/Panes/DeleteDatabaseConfirmationPane.ts
|
|
||||||
# src/Explorer/Panes/GraphStylingPane.ts
|
# src/Explorer/Panes/GraphStylingPane.ts
|
||||||
# src/Explorer/Panes/NewVertexPane.ts
|
# src/Explorer/Panes/NewVertexPane.ts
|
||||||
src/Explorer/Panes/PaneComponents.ts
|
|
||||||
src/Explorer/Panes/RenewAdHocAccessPane.ts
|
src/Explorer/Panes/RenewAdHocAccessPane.ts
|
||||||
src/Explorer/Panes/SetupNotebooksPane.ts
|
src/Explorer/Panes/SetupNotebooksPane.ts
|
||||||
src/Explorer/Panes/SwitchDirectoryPane.ts
|
src/Explorer/Panes/SwitchDirectoryPane.ts
|
||||||
|
|||||||
@@ -10,6 +10,13 @@ const _global = typeof self === "undefined" ? window : self;
|
|||||||
|
|
||||||
export const tokenProvider = async (requestInfo: RequestInfo) => {
|
export const tokenProvider = async (requestInfo: RequestInfo) => {
|
||||||
const { verb, resourceId, resourceType, headers } = requestInfo;
|
const { verb, resourceId, resourceType, headers } = requestInfo;
|
||||||
|
|
||||||
|
if (userContext.features.enableAadDataPlane && userContext.aadToken) {
|
||||||
|
const AUTH_PREFIX = `type=aad&ver=1.0&sig=`;
|
||||||
|
const authorizationToken = `${AUTH_PREFIX}${userContext.aadToken}`;
|
||||||
|
return authorizationToken;
|
||||||
|
}
|
||||||
|
|
||||||
if (configContext.platform === Platform.Emulator) {
|
if (configContext.platform === Platform.Emulator) {
|
||||||
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
|
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
|
||||||
await setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
|
await setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
|
||||||
@@ -76,7 +83,7 @@ export function client(): Cosmos.CosmosClient {
|
|||||||
if (_client) return _client;
|
if (_client) return _client;
|
||||||
const options: Cosmos.CosmosClientOptions = {
|
const options: Cosmos.CosmosClientOptions = {
|
||||||
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
|
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
|
||||||
key: userContext.masterKey,
|
...(!userContext.features.enableAadDataPlane && { key: userContext.masterKey }),
|
||||||
tokenProvider,
|
tokenProvider,
|
||||||
connectionPolicy: {
|
connectionPolicy: {
|
||||||
enableEndpointDiscovery: false,
|
enableEndpointDiscovery: false,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { CollectionBase } from "../../Contracts/ViewModels";
|
import { CollectionBase } from "../../Contracts/ViewModels";
|
||||||
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { getEntityName } from "../DocumentUtility";
|
import { getEntityName } from "../DocumentUtility";
|
||||||
import { handleError } from "../ErrorHandlingUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
|
|
||||||
export const createDocument = async (collection: CollectionBase, newDocument: unknown): Promise<unknown> => {
|
export const createDocument = async (collection: CollectionBase, newDocument: unknown): Promise<unknown> => {
|
||||||
const entityName = getEntityName();
|
const entityName = getEntityName();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Queries } from "../Constants";
|
|
||||||
import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||||
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
|
||||||
|
import { Queries } from "../Constants";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
|
|
||||||
export const queryDocuments = (
|
export const queryDocuments = (
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { QueryResults } from "../../Contracts/ViewModels";
|
import { QueryResults } from "../../Contracts/ViewModels";
|
||||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { MinimalQueryIterator, nextPage } from "../IteratorUtilities";
|
|
||||||
import { handleError } from "../ErrorHandlingUtils";
|
|
||||||
import { getEntityName } from "../DocumentUtility";
|
import { getEntityName } from "../DocumentUtility";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { MinimalQueryIterator, nextPage } from "../IteratorUtilities";
|
||||||
|
|
||||||
export const queryDocumentsPage = async (
|
export const queryDocumentsPage = async (
|
||||||
resourceName: string,
|
resourceName: string,
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ export interface DatabaseAccountExtendedProperties {
|
|||||||
writeLocations?: DatabaseAccountResponseLocation[];
|
writeLocations?: DatabaseAccountResponseLocation[];
|
||||||
enableFreeTier?: boolean;
|
enableFreeTier?: boolean;
|
||||||
enableAnalyticalStorage?: boolean;
|
enableAnalyticalStorage?: boolean;
|
||||||
|
isVirtualNetworkFilterEnabled?: boolean;
|
||||||
|
ipRules?: IpRule[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatabaseAccountResponseLocation {
|
export interface DatabaseAccountResponseLocation {
|
||||||
@@ -31,6 +33,10 @@ export interface DatabaseAccountResponseLocation {
|
|||||||
provisioningState: string;
|
provisioningState: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IpRule {
|
||||||
|
ipAddressOrRange: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ConfigurationOverrides {
|
export interface ConfigurationOverrides {
|
||||||
EnableBsonSchema: string;
|
EnableBsonSchema: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as Versions from "./Versions";
|
|
||||||
import * as ActionContracts from "./ActionContracts";
|
import * as ActionContracts from "./ActionContracts";
|
||||||
import * as Diagnostics from "./Diagnostics";
|
import * as Diagnostics from "./Diagnostics";
|
||||||
|
import * as Versions from "./Versions";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Messaging types used with Data Explorer <-> Portal communication
|
* Messaging types used with Data Explorer <-> Portal communication
|
||||||
|
|||||||
@@ -4,13 +4,9 @@ import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponen
|
|||||||
import { EditorComponent } from "./Controls/Editor/EditorComponent";
|
import { EditorComponent } from "./Controls/Editor/EditorComponent";
|
||||||
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
|
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
|
||||||
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
|
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
|
||||||
import * as PaneComponents from "./Panes/PaneComponents";
|
|
||||||
|
|
||||||
ko.components.register("editor", new EditorComponent());
|
ko.components.register("editor", new EditorComponent());
|
||||||
ko.components.register("json-editor", new JsonEditorComponent());
|
ko.components.register("json-editor", new JsonEditorComponent());
|
||||||
ko.components.register("diff-editor", new DiffEditorComponent());
|
ko.components.register("diff-editor", new DiffEditorComponent());
|
||||||
ko.components.register("dynamic-list", DynamicListComponent);
|
ko.components.register("dynamic-list", DynamicListComponent);
|
||||||
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
|
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
|
||||||
|
|
||||||
// Panes
|
|
||||||
ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent());
|
|
||||||
|
|||||||
@@ -73,9 +73,13 @@ export class ResourceTreeContextMenuButtonFactory {
|
|||||||
iconSrc: HostedTerminalIcon,
|
iconSrc: HostedTerminalIcon,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
||||||
|
if (container.isShellEnabled()) {
|
||||||
|
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||||
|
} else {
|
||||||
selectedCollection && selectedCollection.onNewMongoShellClick();
|
selectedCollection && selectedCollection.onNewMongoShellClick();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
label: "New Shell",
|
label: container.isShellEnabled() ? "Open Mongo Shell" : "New Shell",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -1,12 +1,12 @@
|
|||||||
jest.mock("../../../../Juno/JunoClient");
|
jest.mock("../../../../Juno/JunoClient");
|
||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { CodeOfConductComponent, CodeOfConductComponentProps } from ".";
|
|
||||||
import { HttpStatusCodes } from "../../../../Common/Constants";
|
import { HttpStatusCodes } from "../../../../Common/Constants";
|
||||||
import { JunoClient } from "../../../../Juno/JunoClient";
|
import { JunoClient } from "../../../../Juno/JunoClient";
|
||||||
|
import { CodeOfConduct, CodeOfConductProps } from "./CodeOfConduct";
|
||||||
|
|
||||||
describe("CodeOfConductComponent", () => {
|
describe("CodeOfConduct", () => {
|
||||||
let codeOfConductProps: CodeOfConductComponentProps;
|
let codeOfConductProps: CodeOfConductProps;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const junoClient = new JunoClient();
|
const junoClient = new JunoClient();
|
||||||
@@ -21,12 +21,12 @@ describe("CodeOfConductComponent", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
const wrapper = shallow(<CodeOfConductComponent {...codeOfConductProps} />);
|
const wrapper = shallow(<CodeOfConduct {...codeOfConductProps} />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("onAcceptedCodeOfConductCalled", async () => {
|
it("onAcceptedCodeOfConductCalled", async () => {
|
||||||
const wrapper = shallow(<CodeOfConductComponent {...codeOfConductProps} />);
|
const wrapper = shallow(<CodeOfConduct {...codeOfConductProps} />);
|
||||||
wrapper.find(".genericPaneSubmitBtn").first().simulate("click");
|
wrapper.find(".genericPaneSubmitBtn").first().simulate("click");
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
expect(codeOfConductProps.onAcceptCodeOfConduct).toBeCalled();
|
expect(codeOfConductProps.onAcceptCodeOfConduct).toBeCalled();
|
||||||
+4
-4
@@ -6,15 +6,15 @@ import { JunoClient } from "../../../../Juno/JunoClient";
|
|||||||
import { Action } from "../../../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { trace, traceFailure, traceStart, traceSuccess } from "../../../../Shared/Telemetry/TelemetryProcessor";
|
import { trace, traceFailure, traceStart, traceSuccess } from "../../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
|
||||||
export interface CodeOfConductComponentProps {
|
export interface CodeOfConductProps {
|
||||||
junoClient: JunoClient;
|
junoClient: JunoClient;
|
||||||
onAcceptCodeOfConduct: (result: boolean) => void;
|
onAcceptCodeOfConduct: (result: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CodeOfConductComponent: FunctionComponent<CodeOfConductComponentProps> = ({
|
export const CodeOfConduct: FunctionComponent<CodeOfConductProps> = ({
|
||||||
junoClient,
|
junoClient,
|
||||||
onAcceptCodeOfConduct,
|
onAcceptCodeOfConduct,
|
||||||
}: CodeOfConductComponentProps) => {
|
}: CodeOfConductProps) => {
|
||||||
const descriptionPara1 = "Azure Cosmos DB Notebook Gallery - Code of Conduct";
|
const descriptionPara1 = "Azure Cosmos DB Notebook Gallery - Code of Conduct";
|
||||||
const descriptionPara2 = "The notebook public gallery contains notebook samples shared by users of Azure Cosmos DB.";
|
const descriptionPara2 = "The notebook public gallery contains notebook samples shared by users of Azure Cosmos DB.";
|
||||||
const descriptionPara3 = "In order to view and publish your samples to the gallery, you must accept the ";
|
const descriptionPara3 = "In order to view and publish your samples to the gallery, you must accept the ";
|
||||||
@@ -47,7 +47,7 @@ export const CodeOfConductComponent: FunctionComponent<CodeOfConductComponentPro
|
|||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
|
|
||||||
handleError(error, "CodeOfConductComponent/acceptCodeOfConduct", "Failed to accept code of conduct");
|
handleError(error, "CodeOfConduct/acceptCodeOfConduct", "Failed to accept code of conduct");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`CodeOfConductComponent renders 1`] = `
|
exports[`CodeOfConduct renders 1`] = `
|
||||||
<Stack
|
<Stack
|
||||||
tokens={
|
tokens={
|
||||||
Object {
|
Object {
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
import * as React from "react";
|
|
||||||
import { JunoClient } from "../../../Juno/JunoClient";
|
|
||||||
import { HttpStatusCodes, CodeOfConductEndpoints } from "../../../Common/Constants";
|
|
||||||
import { Stack, Text, Checkbox, PrimaryButton, Link } from "@fluentui/react";
|
|
||||||
import { getErrorMessage, getErrorStack, handleError } from "../../../Common/ErrorHandlingUtils";
|
|
||||||
import { trace, traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
|
||||||
|
|
||||||
export interface CodeOfConductComponentProps {
|
|
||||||
junoClient: JunoClient;
|
|
||||||
onAcceptCodeOfConduct: (result: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CodeOfConductComponentState {
|
|
||||||
readCodeOfConduct: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CodeOfConductComponent extends React.Component<CodeOfConductComponentProps, CodeOfConductComponentState> {
|
|
||||||
private viewCodeOfConductTraced: boolean;
|
|
||||||
private descriptionPara1: string;
|
|
||||||
private descriptionPara2: string;
|
|
||||||
private descriptionPara3: string;
|
|
||||||
private link1: { label: string; url: string };
|
|
||||||
|
|
||||||
constructor(props: CodeOfConductComponentProps) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
readCodeOfConduct: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.descriptionPara1 = "Azure Cosmos DB Notebook Gallery - Code of Conduct";
|
|
||||||
this.descriptionPara2 = "The notebook public gallery contains notebook samples shared by users of Azure Cosmos DB.";
|
|
||||||
this.descriptionPara3 = "In order to view and publish your samples to the gallery, you must accept the ";
|
|
||||||
this.link1 = { label: "code of conduct.", url: CodeOfConductEndpoints.codeOfConduct };
|
|
||||||
}
|
|
||||||
|
|
||||||
private async acceptCodeOfConduct(): Promise<void> {
|
|
||||||
const startKey = traceStart(Action.NotebooksGalleryAcceptCodeOfConduct);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await this.props.junoClient.acceptCodeOfConduct();
|
|
||||||
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
|
|
||||||
throw new Error(`Received HTTP ${response.status} when accepting code of conduct`);
|
|
||||||
}
|
|
||||||
|
|
||||||
traceSuccess(Action.NotebooksGalleryAcceptCodeOfConduct, {}, startKey);
|
|
||||||
|
|
||||||
this.props.onAcceptCodeOfConduct(response.data);
|
|
||||||
} catch (error) {
|
|
||||||
traceFailure(
|
|
||||||
Action.NotebooksGalleryAcceptCodeOfConduct,
|
|
||||||
{
|
|
||||||
error: getErrorMessage(error),
|
|
||||||
errorStack: getErrorStack(error),
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
|
|
||||||
handleError(error, "CodeOfConductComponent/acceptCodeOfConduct", "Failed to accept code of conduct");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private onChangeCheckbox = (): void => {
|
|
||||||
this.setState({ readCodeOfConduct: !this.state.readCodeOfConduct });
|
|
||||||
};
|
|
||||||
|
|
||||||
public render(): JSX.Element {
|
|
||||||
if (!this.viewCodeOfConductTraced) {
|
|
||||||
this.viewCodeOfConductTraced = true;
|
|
||||||
trace(Action.NotebooksGalleryViewCodeOfConduct);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack tokens={{ childrenGap: 20 }}>
|
|
||||||
<Stack.Item>
|
|
||||||
<Text style={{ fontWeight: 500, fontSize: "20px" }}>{this.descriptionPara1}</Text>
|
|
||||||
</Stack.Item>
|
|
||||||
|
|
||||||
<Stack.Item>
|
|
||||||
<Text>{this.descriptionPara2}</Text>
|
|
||||||
</Stack.Item>
|
|
||||||
|
|
||||||
<Stack.Item>
|
|
||||||
<Text>
|
|
||||||
{this.descriptionPara3}
|
|
||||||
<Link href={this.link1.url} target="_blank">
|
|
||||||
{this.link1.label}
|
|
||||||
</Link>
|
|
||||||
</Text>
|
|
||||||
</Stack.Item>
|
|
||||||
|
|
||||||
<Stack.Item>
|
|
||||||
<Checkbox
|
|
||||||
styles={{
|
|
||||||
label: {
|
|
||||||
margin: 0,
|
|
||||||
padding: "2 0 2 0",
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
fontSize: 12,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
label="I have read and accept the code of conduct."
|
|
||||||
onChange={this.onChangeCheckbox}
|
|
||||||
/>
|
|
||||||
</Stack.Item>
|
|
||||||
|
|
||||||
<Stack.Item>
|
|
||||||
<PrimaryButton
|
|
||||||
ariaLabel="Continue"
|
|
||||||
title="Continue"
|
|
||||||
onClick={async () => await this.acceptCodeOfConduct()}
|
|
||||||
tabIndex={0}
|
|
||||||
className="genericPaneSubmitBtn"
|
|
||||||
text="Continue"
|
|
||||||
disabled={!this.state.readCodeOfConduct}
|
|
||||||
/>
|
|
||||||
</Stack.Item>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -30,7 +30,7 @@ import * as GalleryUtils from "../../../Utils/GalleryUtils";
|
|||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { Dialog, DialogProps } from "../Dialog";
|
import { Dialog, DialogProps } from "../Dialog";
|
||||||
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
|
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
|
||||||
import { CodeOfConductComponent } from "./CodeOfConductComponent";
|
import { CodeOfConduct } from "./CodeOfConduct/CodeOfConduct";
|
||||||
import "./GalleryViewerComponent.less";
|
import "./GalleryViewerComponent.less";
|
||||||
import { InfoComponent } from "./InfoComponent/InfoComponent";
|
import { InfoComponent } from "./InfoComponent/InfoComponent";
|
||||||
|
|
||||||
@@ -372,7 +372,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
{acceptedCodeOfConduct === false && (
|
{acceptedCodeOfConduct === false && (
|
||||||
<Overlay isDarkThemed>
|
<Overlay isDarkThemed>
|
||||||
<div className="publicGalleryTabOverlayContent">
|
<div className="publicGalleryTabOverlayContent">
|
||||||
<CodeOfConductComponent
|
<CodeOfConduct
|
||||||
junoClient={this.props.junoClient}
|
junoClient={this.props.junoClient}
|
||||||
onAcceptCodeOfConduct={(result: boolean) => {
|
onAcceptCodeOfConduct={(result: boolean) => {
|
||||||
this.setState({ isCodeOfConductAccepted: result });
|
this.setState({ isCodeOfConductAccepted: result });
|
||||||
|
|||||||
@@ -38,51 +38,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
|
||||||
"autoPilotUsageCost": [Function],
|
|
||||||
"canConfigureThroughput": [Function],
|
|
||||||
"canExceedMaximumValue": [Function],
|
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"container": [Circular],
|
|
||||||
"costsVisible": [Function],
|
|
||||||
"createTableQuery": [Function],
|
|
||||||
"dedicateTableThroughput": [Function],
|
|
||||||
"firstFieldHasFocus": [Function],
|
|
||||||
"formErrors": [Function],
|
|
||||||
"formErrorsDetails": [Function],
|
|
||||||
"id": "cassandraaddcollectionpane",
|
|
||||||
"isAutoPilotSelected": [Function],
|
|
||||||
"isExecuting": [Function],
|
|
||||||
"isFreeTierAccount": [Function],
|
|
||||||
"isSharedAutoPilotSelected": [Function],
|
|
||||||
"isTemplateReady": [Function],
|
|
||||||
"keyspaceCreateNew": [Function],
|
|
||||||
"keyspaceHasSharedOffer": [Function],
|
|
||||||
"keyspaceId": [Function],
|
|
||||||
"keyspaceIds": [Function],
|
|
||||||
"keyspaceOffers": Map {},
|
|
||||||
"keyspaceThroughput": [Function],
|
|
||||||
"maxThroughputRU": [Function],
|
|
||||||
"minThroughputRU": [Function],
|
|
||||||
"requestUnitsUsageCostDedicated": [Function],
|
|
||||||
"requestUnitsUsageCostShared": [Function],
|
|
||||||
"ruToolTipText": [Function],
|
|
||||||
"selectedAutoPilotThroughput": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
|
||||||
"sharedThroughputSpendAck": [Function],
|
|
||||||
"sharedThroughputSpendAckText": [Function],
|
|
||||||
"sharedThroughputSpendAckVisible": [Function],
|
|
||||||
"tableId": [Function],
|
|
||||||
"throughput": [Function],
|
|
||||||
"throughputRangeText": [Function],
|
|
||||||
"throughputSpendAck": [Function],
|
|
||||||
"throughputSpendAckText": [Function],
|
|
||||||
"throughputSpendAckVisible": [Function],
|
|
||||||
"title": [Function],
|
|
||||||
"userTableQuery": [Function],
|
|
||||||
"visible": [Function],
|
|
||||||
},
|
|
||||||
"closeDialog": undefined,
|
"closeDialog": undefined,
|
||||||
"closeSidePanel": undefined,
|
"closeSidePanel": undefined,
|
||||||
"collapsedResourceTreeWidth": 36,
|
"collapsedResourceTreeWidth": 36,
|
||||||
@@ -887,6 +842,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isResourceTokenCollectionNodeSelected": [Function],
|
"isResourceTokenCollectionNodeSelected": [Function],
|
||||||
"isSchemaEnabled": [Function],
|
"isSchemaEnabled": [Function],
|
||||||
"isServerlessEnabled": [Function],
|
"isServerlessEnabled": [Function],
|
||||||
|
"isShellEnabled": [Function],
|
||||||
"isSparkEnabled": [Function],
|
"isSparkEnabled": [Function],
|
||||||
"isSparkEnabledForAccount": [Function],
|
"isSparkEnabledForAccount": [Function],
|
||||||
"isSynapseLinkUpdating": [Function],
|
"isSynapseLinkUpdating": [Function],
|
||||||
@@ -978,51 +934,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
|
||||||
"autoPilotUsageCost": [Function],
|
|
||||||
"canConfigureThroughput": [Function],
|
|
||||||
"canExceedMaximumValue": [Function],
|
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"container": [Circular],
|
|
||||||
"costsVisible": [Function],
|
|
||||||
"createTableQuery": [Function],
|
|
||||||
"dedicateTableThroughput": [Function],
|
|
||||||
"firstFieldHasFocus": [Function],
|
|
||||||
"formErrors": [Function],
|
|
||||||
"formErrorsDetails": [Function],
|
|
||||||
"id": "cassandraaddcollectionpane",
|
|
||||||
"isAutoPilotSelected": [Function],
|
|
||||||
"isExecuting": [Function],
|
|
||||||
"isFreeTierAccount": [Function],
|
|
||||||
"isSharedAutoPilotSelected": [Function],
|
|
||||||
"isTemplateReady": [Function],
|
|
||||||
"keyspaceCreateNew": [Function],
|
|
||||||
"keyspaceHasSharedOffer": [Function],
|
|
||||||
"keyspaceId": [Function],
|
|
||||||
"keyspaceIds": [Function],
|
|
||||||
"keyspaceOffers": Map {},
|
|
||||||
"keyspaceThroughput": [Function],
|
|
||||||
"maxThroughputRU": [Function],
|
|
||||||
"minThroughputRU": [Function],
|
|
||||||
"requestUnitsUsageCostDedicated": [Function],
|
|
||||||
"requestUnitsUsageCostShared": [Function],
|
|
||||||
"ruToolTipText": [Function],
|
|
||||||
"selectedAutoPilotThroughput": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
|
||||||
"sharedThroughputSpendAck": [Function],
|
|
||||||
"sharedThroughputSpendAckText": [Function],
|
|
||||||
"sharedThroughputSpendAckVisible": [Function],
|
|
||||||
"tableId": [Function],
|
|
||||||
"throughput": [Function],
|
|
||||||
"throughputRangeText": [Function],
|
|
||||||
"throughputSpendAck": [Function],
|
|
||||||
"throughputSpendAckText": [Function],
|
|
||||||
"throughputSpendAckVisible": [Function],
|
|
||||||
"title": [Function],
|
|
||||||
"userTableQuery": [Function],
|
|
||||||
"visible": [Function],
|
|
||||||
},
|
|
||||||
"closeDialog": undefined,
|
"closeDialog": undefined,
|
||||||
"closeSidePanel": undefined,
|
"closeSidePanel": undefined,
|
||||||
"collapsedResourceTreeWidth": 36,
|
"collapsedResourceTreeWidth": 36,
|
||||||
@@ -1827,6 +1738,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isResourceTokenCollectionNodeSelected": [Function],
|
"isResourceTokenCollectionNodeSelected": [Function],
|
||||||
"isSchemaEnabled": [Function],
|
"isSchemaEnabled": [Function],
|
||||||
"isServerlessEnabled": [Function],
|
"isServerlessEnabled": [Function],
|
||||||
|
"isShellEnabled": [Function],
|
||||||
"isSparkEnabled": [Function],
|
"isSparkEnabled": [Function],
|
||||||
"isSparkEnabledForAccount": [Function],
|
"isSparkEnabledForAccount": [Function],
|
||||||
"isSynapseLinkUpdating": [Function],
|
"isSynapseLinkUpdating": [Function],
|
||||||
@@ -1931,51 +1843,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
|
||||||
"autoPilotUsageCost": [Function],
|
|
||||||
"canConfigureThroughput": [Function],
|
|
||||||
"canExceedMaximumValue": [Function],
|
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"container": [Circular],
|
|
||||||
"costsVisible": [Function],
|
|
||||||
"createTableQuery": [Function],
|
|
||||||
"dedicateTableThroughput": [Function],
|
|
||||||
"firstFieldHasFocus": [Function],
|
|
||||||
"formErrors": [Function],
|
|
||||||
"formErrorsDetails": [Function],
|
|
||||||
"id": "cassandraaddcollectionpane",
|
|
||||||
"isAutoPilotSelected": [Function],
|
|
||||||
"isExecuting": [Function],
|
|
||||||
"isFreeTierAccount": [Function],
|
|
||||||
"isSharedAutoPilotSelected": [Function],
|
|
||||||
"isTemplateReady": [Function],
|
|
||||||
"keyspaceCreateNew": [Function],
|
|
||||||
"keyspaceHasSharedOffer": [Function],
|
|
||||||
"keyspaceId": [Function],
|
|
||||||
"keyspaceIds": [Function],
|
|
||||||
"keyspaceOffers": Map {},
|
|
||||||
"keyspaceThroughput": [Function],
|
|
||||||
"maxThroughputRU": [Function],
|
|
||||||
"minThroughputRU": [Function],
|
|
||||||
"requestUnitsUsageCostDedicated": [Function],
|
|
||||||
"requestUnitsUsageCostShared": [Function],
|
|
||||||
"ruToolTipText": [Function],
|
|
||||||
"selectedAutoPilotThroughput": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
|
||||||
"sharedThroughputSpendAck": [Function],
|
|
||||||
"sharedThroughputSpendAckText": [Function],
|
|
||||||
"sharedThroughputSpendAckVisible": [Function],
|
|
||||||
"tableId": [Function],
|
|
||||||
"throughput": [Function],
|
|
||||||
"throughputRangeText": [Function],
|
|
||||||
"throughputSpendAck": [Function],
|
|
||||||
"throughputSpendAckText": [Function],
|
|
||||||
"throughputSpendAckVisible": [Function],
|
|
||||||
"title": [Function],
|
|
||||||
"userTableQuery": [Function],
|
|
||||||
"visible": [Function],
|
|
||||||
},
|
|
||||||
"closeDialog": undefined,
|
"closeDialog": undefined,
|
||||||
"closeSidePanel": undefined,
|
"closeSidePanel": undefined,
|
||||||
"collapsedResourceTreeWidth": 36,
|
"collapsedResourceTreeWidth": 36,
|
||||||
@@ -2780,6 +2647,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isResourceTokenCollectionNodeSelected": [Function],
|
"isResourceTokenCollectionNodeSelected": [Function],
|
||||||
"isSchemaEnabled": [Function],
|
"isSchemaEnabled": [Function],
|
||||||
"isServerlessEnabled": [Function],
|
"isServerlessEnabled": [Function],
|
||||||
|
"isShellEnabled": [Function],
|
||||||
"isSparkEnabled": [Function],
|
"isSparkEnabled": [Function],
|
||||||
"isSparkEnabledForAccount": [Function],
|
"isSparkEnabledForAccount": [Function],
|
||||||
"isSynapseLinkUpdating": [Function],
|
"isSynapseLinkUpdating": [Function],
|
||||||
@@ -2871,51 +2739,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
|
||||||
"autoPilotUsageCost": [Function],
|
|
||||||
"canConfigureThroughput": [Function],
|
|
||||||
"canExceedMaximumValue": [Function],
|
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"container": [Circular],
|
|
||||||
"costsVisible": [Function],
|
|
||||||
"createTableQuery": [Function],
|
|
||||||
"dedicateTableThroughput": [Function],
|
|
||||||
"firstFieldHasFocus": [Function],
|
|
||||||
"formErrors": [Function],
|
|
||||||
"formErrorsDetails": [Function],
|
|
||||||
"id": "cassandraaddcollectionpane",
|
|
||||||
"isAutoPilotSelected": [Function],
|
|
||||||
"isExecuting": [Function],
|
|
||||||
"isFreeTierAccount": [Function],
|
|
||||||
"isSharedAutoPilotSelected": [Function],
|
|
||||||
"isTemplateReady": [Function],
|
|
||||||
"keyspaceCreateNew": [Function],
|
|
||||||
"keyspaceHasSharedOffer": [Function],
|
|
||||||
"keyspaceId": [Function],
|
|
||||||
"keyspaceIds": [Function],
|
|
||||||
"keyspaceOffers": Map {},
|
|
||||||
"keyspaceThroughput": [Function],
|
|
||||||
"maxThroughputRU": [Function],
|
|
||||||
"minThroughputRU": [Function],
|
|
||||||
"requestUnitsUsageCostDedicated": [Function],
|
|
||||||
"requestUnitsUsageCostShared": [Function],
|
|
||||||
"ruToolTipText": [Function],
|
|
||||||
"selectedAutoPilotThroughput": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
|
||||||
"sharedThroughputSpendAck": [Function],
|
|
||||||
"sharedThroughputSpendAckText": [Function],
|
|
||||||
"sharedThroughputSpendAckVisible": [Function],
|
|
||||||
"tableId": [Function],
|
|
||||||
"throughput": [Function],
|
|
||||||
"throughputRangeText": [Function],
|
|
||||||
"throughputSpendAck": [Function],
|
|
||||||
"throughputSpendAckText": [Function],
|
|
||||||
"throughputSpendAckVisible": [Function],
|
|
||||||
"title": [Function],
|
|
||||||
"userTableQuery": [Function],
|
|
||||||
"visible": [Function],
|
|
||||||
},
|
|
||||||
"closeDialog": undefined,
|
"closeDialog": undefined,
|
||||||
"closeSidePanel": undefined,
|
"closeSidePanel": undefined,
|
||||||
"collapsedResourceTreeWidth": 36,
|
"collapsedResourceTreeWidth": 36,
|
||||||
@@ -3720,6 +3543,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isResourceTokenCollectionNodeSelected": [Function],
|
"isResourceTokenCollectionNodeSelected": [Function],
|
||||||
"isSchemaEnabled": [Function],
|
"isSchemaEnabled": [Function],
|
||||||
"isServerlessEnabled": [Function],
|
"isServerlessEnabled": [Function],
|
||||||
|
"isShellEnabled": [Function],
|
||||||
"isSparkEnabled": [Function],
|
"isSparkEnabled": [Function],
|
||||||
"isSparkEnabledForAccount": [Function],
|
"isSparkEnabledForAccount": [Function],
|
||||||
"isSynapseLinkUpdating": [Function],
|
"isSynapseLinkUpdating": [Function],
|
||||||
|
|||||||
+35
-26
@@ -54,7 +54,7 @@ import { NotebookUtil } from "./Notebook/NotebookUtil";
|
|||||||
import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
|
import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
|
||||||
import { AddDatabasePanel } from "./Panes/AddDatabasePanel/AddDatabasePanel";
|
import { AddDatabasePanel } from "./Panes/AddDatabasePanel/AddDatabasePanel";
|
||||||
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane/BrowseQueriesPane";
|
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane/BrowseQueriesPane";
|
||||||
import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
|
import { CassandraAddCollectionPane } from "./Panes/CassandraAddCollectionPane/CassandraAddCollectionPane";
|
||||||
import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
|
import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
|
||||||
import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane";
|
import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane";
|
||||||
import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmationPanel";
|
import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmationPanel";
|
||||||
@@ -67,7 +67,7 @@ import { SetupNoteBooksPanel } from "./Panes/SetupNotebooksPanel/SetupNotebooksP
|
|||||||
import { StringInputPane } from "./Panes/StringInputPane/StringInputPane";
|
import { StringInputPane } from "./Panes/StringInputPane/StringInputPane";
|
||||||
import { AddTableEntityPanel } from "./Panes/Tables/AddTableEntityPanel";
|
import { AddTableEntityPanel } from "./Panes/Tables/AddTableEntityPanel";
|
||||||
import { EditTableEntityPanel } from "./Panes/Tables/EditTableEntityPanel";
|
import { EditTableEntityPanel } from "./Panes/Tables/EditTableEntityPanel";
|
||||||
import { TableQuerySelectPanel } from "./Panes/Tables/TableQuerySelectPanel";
|
import { TableQuerySelectPanel } from "./Panes/Tables/TableQuerySelectPanel/TableQuerySelectPanel";
|
||||||
import { UploadFilePane } from "./Panes/UploadFilePane/UploadFilePane";
|
import { UploadFilePane } from "./Panes/UploadFilePane/UploadFilePane";
|
||||||
import { UploadItemsPane } from "./Panes/UploadItemsPane/UploadItemsPane";
|
import { UploadItemsPane } from "./Panes/UploadItemsPane/UploadItemsPane";
|
||||||
import TableListViewModal from "./Tables/DataTable/TableEntityListViewModel";
|
import TableListViewModal from "./Tables/DataTable/TableEntityListViewModel";
|
||||||
@@ -148,7 +148,6 @@ export default class Explorer {
|
|||||||
public tabsManager: TabsManager;
|
public tabsManager: TabsManager;
|
||||||
|
|
||||||
// Contextual panes
|
// Contextual panes
|
||||||
public cassandraAddCollectionPane: CassandraAddCollectionPane;
|
|
||||||
private gitHubClient: GitHubClient;
|
private gitHubClient: GitHubClient;
|
||||||
public gitHubOAuthService: GitHubOAuthService;
|
public gitHubOAuthService: GitHubOAuthService;
|
||||||
public junoClient: JunoClient;
|
public junoClient: JunoClient;
|
||||||
@@ -178,6 +177,8 @@ export default class Explorer {
|
|||||||
public openDialog: ExplorerParams["openDialog"];
|
public openDialog: ExplorerParams["openDialog"];
|
||||||
public closeDialog: ExplorerParams["closeDialog"];
|
public closeDialog: ExplorerParams["closeDialog"];
|
||||||
|
|
||||||
|
public isShellEnabled: ko.Observable<boolean>;
|
||||||
|
|
||||||
private _isInitializingNotebooks: boolean;
|
private _isInitializingNotebooks: boolean;
|
||||||
private notebookBasePath: ko.Observable<string>;
|
private notebookBasePath: ko.Observable<string>;
|
||||||
private _arcadiaManager: ArcadiaResourceManager;
|
private _arcadiaManager: ArcadiaResourceManager;
|
||||||
@@ -223,6 +224,7 @@ export default class Explorer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.isShellEnabled = ko.observable(false);
|
||||||
this.isNotebooksEnabledForAccount = ko.observable(false);
|
this.isNotebooksEnabledForAccount = ko.observable(false);
|
||||||
this.isNotebooksEnabledForAccount.subscribe((isEnabledForAccount: boolean) => this.refreshCommandBarButtons());
|
this.isNotebooksEnabledForAccount.subscribe((isEnabledForAccount: boolean) => this.refreshCommandBarButtons());
|
||||||
this.isSparkEnabledForAccount = ko.observable(false);
|
this.isSparkEnabledForAccount = ko.observable(false);
|
||||||
@@ -249,6 +251,12 @@ export default class Explorer {
|
|||||||
((await this._containsDefaultNotebookWorkspace(userContext.databaseAccount)) ||
|
((await this._containsDefaultNotebookWorkspace(userContext.databaseAccount)) ||
|
||||||
userContext.features.enableNotebooks)
|
userContext.features.enableNotebooks)
|
||||||
);
|
);
|
||||||
|
this.isShellEnabled(
|
||||||
|
this.isNotebookEnabled() &&
|
||||||
|
!userContext.databaseAccount.properties.isVirtualNetworkFilterEnabled &&
|
||||||
|
userContext.databaseAccount.properties.ipRules.length === 0
|
||||||
|
);
|
||||||
|
|
||||||
TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, {
|
TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, {
|
||||||
isNotebookEnabled: this.isNotebookEnabled(),
|
isNotebookEnabled: this.isNotebookEnabled(),
|
||||||
dataExplorerArea: Constants.Areas.Notebook,
|
dataExplorerArea: Constants.Areas.Notebook,
|
||||||
@@ -398,13 +406,6 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.cassandraAddCollectionPane = new CassandraAddCollectionPane({
|
|
||||||
id: "cassandraaddcollectionpane",
|
|
||||||
visible: ko.observable<boolean>(false),
|
|
||||||
|
|
||||||
container: this,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.tabsManager = params?.tabsManager ?? new TabsManager();
|
this.tabsManager = params?.tabsManager ?? new TabsManager();
|
||||||
this.tabsManager.openedTabs.subscribe((tabs) => {
|
this.tabsManager.openedTabs.subscribe((tabs) => {
|
||||||
if (tabs.length === 0) {
|
if (tabs.length === 0) {
|
||||||
@@ -1138,7 +1139,10 @@ export default class Explorer {
|
|||||||
|
|
||||||
private getDeltaDatabases(
|
private getDeltaDatabases(
|
||||||
updatedDatabaseList: DataModels.Database[]
|
updatedDatabaseList: DataModels.Database[]
|
||||||
): { toAdd: ViewModels.Database[]; toDelete: ViewModels.Database[] } {
|
): {
|
||||||
|
toAdd: ViewModels.Database[];
|
||||||
|
toDelete: ViewModels.Database[];
|
||||||
|
} {
|
||||||
const newDatabases: DataModels.Database[] = _.filter(updatedDatabaseList, (database: DataModels.Database) => {
|
const newDatabases: DataModels.Database[] = _.filter(updatedDatabaseList, (database: DataModels.Database) => {
|
||||||
const databaseExists = _.some(
|
const databaseExists = _.some(
|
||||||
this.databases(),
|
this.databases(),
|
||||||
@@ -1384,7 +1388,7 @@ export default class Explorer {
|
|||||||
this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again.");
|
this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again.");
|
||||||
} else {
|
} else {
|
||||||
this.openSidePanel(
|
this.openSidePanel(
|
||||||
"",
|
"Rename Notebook",
|
||||||
<StringInputPane
|
<StringInputPane
|
||||||
explorer={this}
|
explorer={this}
|
||||||
closePanel={() => {
|
closePanel={() => {
|
||||||
@@ -1415,7 +1419,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.openSidePanel(
|
this.openSidePanel(
|
||||||
"",
|
"Create new directory",
|
||||||
<StringInputPane
|
<StringInputPane
|
||||||
explorer={this}
|
explorer={this}
|
||||||
closePanel={() => {
|
closePanel={() => {
|
||||||
@@ -1720,23 +1724,19 @@ export default class Explorer {
|
|||||||
throw new Error("Terminal kind: ${kind} not supported");
|
throw new Error("Terminal kind: ${kind} not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
const terminalTabs: TerminalTab[] = this.tabsManager.getTabs(
|
const terminalTabs: TerminalTab[] = this.tabsManager.getTabs(ViewModels.CollectionTabKind.Terminal, (tab) =>
|
||||||
ViewModels.CollectionTabKind.Terminal,
|
tab.hashLocation().startsWith(hashLocation)
|
||||||
(tab) => tab.hashLocation() == hashLocation
|
|
||||||
) as TerminalTab[];
|
) as TerminalTab[];
|
||||||
let terminalTab: TerminalTab = terminalTabs && terminalTabs[0];
|
|
||||||
|
|
||||||
if (terminalTab) {
|
const index = terminalTabs.length + 1;
|
||||||
this.tabsManager.activateTab(terminalTab);
|
|
||||||
} else {
|
|
||||||
const newTab = new TerminalTab({
|
const newTab = new TerminalTab({
|
||||||
account: userContext.databaseAccount,
|
account: userContext.databaseAccount,
|
||||||
tabKind: ViewModels.CollectionTabKind.Terminal,
|
tabKind: ViewModels.CollectionTabKind.Terminal,
|
||||||
node: null,
|
node: null,
|
||||||
title: title,
|
title: `${title} ${index}`,
|
||||||
tabPath: title,
|
tabPath: `${title} ${index}`,
|
||||||
collection: null,
|
collection: null,
|
||||||
hashLocation: hashLocation,
|
hashLocation: `${hashLocation} ${index}`,
|
||||||
isTabsContentExpanded: ko.observable(true),
|
isTabsContentExpanded: ko.observable(true),
|
||||||
onLoadStartKey: null,
|
onLoadStartKey: null,
|
||||||
onUpdateTabsButtons: this.onUpdateTabsButtons,
|
onUpdateTabsButtons: this.onUpdateTabsButtons,
|
||||||
@@ -1746,7 +1746,6 @@ export default class Explorer {
|
|||||||
|
|
||||||
this.tabsManager.activateNewTab(newTab);
|
this.tabsManager.activateNewTab(newTab);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public async openGallery(
|
public async openGallery(
|
||||||
selectedTab?: GalleryTabKind,
|
selectedTab?: GalleryTabKind,
|
||||||
@@ -1791,7 +1790,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
public onNewCollectionClicked(databaseId?: string): void {
|
public onNewCollectionClicked(databaseId?: string): void {
|
||||||
if (userContext.apiType === "Cassandra") {
|
if (userContext.apiType === "Cassandra") {
|
||||||
this.cassandraAddCollectionPane.open();
|
this.openCassandraAddCollectionPane();
|
||||||
} else {
|
} else {
|
||||||
this.openAddCollectionPanel(databaseId);
|
this.openAddCollectionPanel(databaseId);
|
||||||
}
|
}
|
||||||
@@ -1907,7 +1906,7 @@ export default class Explorer {
|
|||||||
"Delete " + getDatabaseName(),
|
"Delete " + getDatabaseName(),
|
||||||
<DeleteDatabaseConfirmationPanel
|
<DeleteDatabaseConfirmationPanel
|
||||||
explorer={this}
|
explorer={this}
|
||||||
openNotificationConsole={this.expandConsole}
|
openNotificationConsole={() => this.expandConsole()}
|
||||||
closePanel={this.closeSidePanel}
|
closePanel={this.closeSidePanel}
|
||||||
selectedDatabase={this.findSelectedDatabase()}
|
selectedDatabase={this.findSelectedDatabase()}
|
||||||
/>
|
/>
|
||||||
@@ -1983,6 +1982,16 @@ export default class Explorer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public openCassandraAddCollectionPane(): void {
|
||||||
|
this.openSidePanel(
|
||||||
|
"Add Table",
|
||||||
|
<CassandraAddCollectionPane
|
||||||
|
explorer={this}
|
||||||
|
closePanel={() => this.closeSidePanel()}
|
||||||
|
cassandraApiClient={new CassandraAPIDataClient()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
public openGitHubReposPanel(header: string, junoClient?: JunoClient): void {
|
public openGitHubReposPanel(header: string, junoClient?: JunoClient): void {
|
||||||
this.openSidePanel(
|
this.openSidePanel(
|
||||||
header,
|
header,
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = (
|
|||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
onLabelChange(event);
|
onLabelChange(event);
|
||||||
}}
|
}}
|
||||||
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<div className="actionCol"></div>
|
<div className="actionCol"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
|
|
||||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
||||||
|
mockExplorer.isShellEnabled = ko.observable(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
@@ -154,6 +155,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||||
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
|
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
|
||||||
|
mockExplorer.isShellEnabled = ko.observable(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Mongo Api not available - button should be hidden", () => {
|
it("Mongo Api not available - button should be hidden", () => {
|
||||||
@@ -173,24 +175,18 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
expect(openMongoShellBtn).toBeUndefined();
|
expect(openMongoShellBtn).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => {
|
it("Notebooks is not enabled and is unavailable - button should be hidden", () => {
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
||||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||||
expect(openMongoShellBtn).toBeDefined();
|
expect(openMongoShellBtn).toBeUndefined();
|
||||||
expect(openMongoShellBtn.disabled).toBe(true);
|
|
||||||
expect(openMongoShellBtn.tooltipText).toBe(
|
|
||||||
"This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Notebooks is not enabled and is available - button should be shown and enabled", () => {
|
it("Notebooks is not enabled and is available - button should be hidden", () => {
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
||||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||||
expect(openMongoShellBtn).toBeDefined();
|
expect(openMongoShellBtn).toBeUndefined();
|
||||||
expect(openMongoShellBtn.disabled).toBe(false);
|
|
||||||
expect(openMongoShellBtn.tooltipText).toBe("");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
|
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
|
||||||
@@ -213,6 +209,16 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
expect(openMongoShellBtn.disabled).toBe(false);
|
expect(openMongoShellBtn.disabled).toBe(false);
|
||||||
expect(openMongoShellBtn.tooltipText).toBe("");
|
expect(openMongoShellBtn.tooltipText).toBe("");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Notebooks is enabled and is available, terminal is unavailable due to ipRules - button should be hidden", () => {
|
||||||
|
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||||
|
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||||
|
mockExplorer.isShellEnabled = ko.observable(false);
|
||||||
|
|
||||||
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
||||||
|
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||||
|
expect(openMongoShellBtn).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Open Cassandra Shell button", () => {
|
describe("Open Cassandra Shell button", () => {
|
||||||
@@ -273,11 +279,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => {
|
it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => {
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
||||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||||
expect(openCassandraShellBtn).toBeDefined();
|
expect(openCassandraShellBtn).toBeUndefined();
|
||||||
expect(openCassandraShellBtn.disabled).toBe(true);
|
|
||||||
expect(openCassandraShellBtn.tooltipText).toBe(
|
|
||||||
"This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Notebooks is not enabled and is available - button should be shown and enabled", () => {
|
it("Notebooks is not enabled and is available - button should be shown and enabled", () => {
|
||||||
@@ -285,9 +287,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
|
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
||||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||||
expect(openCassandraShellBtn).toBeDefined();
|
expect(openCassandraShellBtn).toBeUndefined();
|
||||||
expect(openCassandraShellBtn.disabled).toBe(false);
|
|
||||||
expect(openCassandraShellBtn.tooltipText).toBe("");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
|
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
|
||||||
|
|||||||
@@ -61,48 +61,40 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
|
|||||||
if (container.notebookManager?.gitHubOAuthService) {
|
if (container.notebookManager?.gitHubOAuthService) {
|
||||||
buttons.push(createManageGitHubAccountButton(container));
|
buttons.push(createManageGitHubAccountButton(container));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!container.isRunningOnNationalCloud()) {
|
|
||||||
if (!container.isNotebookEnabled()) {
|
|
||||||
buttons.push(createEnableNotebooksButton(container));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userContext.apiType === "Mongo") {
|
|
||||||
buttons.push(createOpenMongoTerminalButton(container));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userContext.apiType === "Cassandra") {
|
|
||||||
buttons.push(createOpenCassandraTerminalButton(container));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (container.isNotebookEnabled()) {
|
|
||||||
buttons.push(createOpenTerminalButton(container));
|
buttons.push(createOpenTerminalButton(container));
|
||||||
|
|
||||||
buttons.push(createNotebookWorkspaceResetButton(container));
|
buttons.push(createNotebookWorkspaceResetButton(container));
|
||||||
|
if (
|
||||||
|
(userContext.apiType === "Mongo" && container.isShellEnabled() && container.isDatabaseNodeOrNoneSelected()) ||
|
||||||
|
userContext.apiType === "Cassandra"
|
||||||
|
) {
|
||||||
|
buttons.push(createDivider());
|
||||||
|
if (userContext.apiType === "Cassandra") {
|
||||||
|
buttons.push(createOpenCassandraTerminalButton(container));
|
||||||
|
} else {
|
||||||
|
buttons.push(createOpenMongoTerminalButton(container));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!container.isRunningOnNationalCloud()) {
|
||||||
|
buttons.push(createEnableNotebooksButton(container));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!container.isDatabaseNodeOrNoneSelected()) {
|
if (!container.isDatabaseNodeOrNoneSelected()) {
|
||||||
if (container.isNotebookEnabled()) {
|
const isQuerySupported = userContext.apiType === "SQL" || userContext.apiType === "Gremlin";
|
||||||
buttons.push(createDivider());
|
|
||||||
}
|
|
||||||
|
|
||||||
const isSqlQuerySupported = userContext.apiType === "SQL" || userContext.apiType === "Gremlin";
|
if (isQuerySupported) {
|
||||||
if (isSqlQuerySupported) {
|
buttons.push(createDivider());
|
||||||
const newSqlQueryBtn = createNewSQLQueryButton(container);
|
const newSqlQueryBtn = createNewSQLQueryButton(container);
|
||||||
buttons.push(newSqlQueryBtn);
|
buttons.push(newSqlQueryBtn);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSupportedOpenQueryApi =
|
if (isQuerySupported && container.selectedNode() && container.findSelectedCollection()) {
|
||||||
userContext.apiType === "SQL" || userContext.apiType === "Mongo" || userContext.apiType === "Gremlin";
|
|
||||||
const isSupportedOpenQueryFromDiskApi = userContext.apiType === "SQL" || userContext.apiType === "Gremlin";
|
|
||||||
if (isSupportedOpenQueryApi && container.selectedNode() && container.findSelectedCollection()) {
|
|
||||||
const openQueryBtn = createOpenQueryButton(container);
|
const openQueryBtn = createOpenQueryButton(container);
|
||||||
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton(container)];
|
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton(container)];
|
||||||
buttons.push(openQueryBtn);
|
buttons.push(openQueryBtn);
|
||||||
} else if (isSupportedOpenQueryFromDiskApi && container.selectedNode() && container.findSelectedCollection()) {
|
|
||||||
buttons.push(createOpenQueryFromDiskButton(container));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (areScriptsSupported()) {
|
if (areScriptsSupported()) {
|
||||||
@@ -132,13 +124,17 @@ export function createContextCommandBarButtons(container: Explorer): CommandButt
|
|||||||
const buttons: CommandButtonComponentProps[] = [];
|
const buttons: CommandButtonComponentProps[] = [];
|
||||||
|
|
||||||
if (!container.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") {
|
if (!container.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") {
|
||||||
const label = "New Shell";
|
const label = container.isShellEnabled() ? "Open Mongo Shell" : "New Shell";
|
||||||
const newMongoShellBtn: CommandButtonComponentProps = {
|
const newMongoShellBtn: CommandButtonComponentProps = {
|
||||||
iconSrc: HostedTerminalIcon,
|
iconSrc: HostedTerminalIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
||||||
|
if (container.isShellEnabled()) {
|
||||||
|
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||||
|
} else {
|
||||||
selectedCollection && selectedCollection.onNewMongoShellClick();
|
selectedCollection && selectedCollection.onNewMongoShellClick();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { ActionContracts } from "../Contracts/ExplorerContracts";
|
|||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import Explorer from "./Explorer";
|
import Explorer from "./Explorer";
|
||||||
import { handleOpenAction } from "./OpenActions";
|
import { handleOpenAction } from "./OpenActions";
|
||||||
import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
|
|
||||||
|
|
||||||
describe("OpenActions", () => {
|
describe("OpenActions", () => {
|
||||||
describe("handleOpenAction", () => {
|
describe("handleOpenAction", () => {
|
||||||
@@ -15,8 +14,6 @@ describe("OpenActions", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = {} as Explorer;
|
explorer = {} as Explorer;
|
||||||
explorer.onNewCollectionClicked = jest.fn();
|
explorer.onNewCollectionClicked = jest.fn();
|
||||||
explorer.cassandraAddCollectionPane = {} as CassandraAddCollectionPane;
|
|
||||||
explorer.cassandraAddCollectionPane.open = jest.fn();
|
|
||||||
|
|
||||||
database = {
|
database = {
|
||||||
id: ko.observable("db"),
|
id: ko.observable("db"),
|
||||||
@@ -64,28 +61,6 @@ describe("OpenActions", () => {
|
|||||||
expect(actionHandled).toBe(true);
|
expect(actionHandled).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("CassandraAddCollection pane kind", () => {
|
|
||||||
it("string value should call cassandraAddCollectionPane.open", () => {
|
|
||||||
const action = {
|
|
||||||
actionType: "OpenPane",
|
|
||||||
paneKind: "CassandraAddCollection",
|
|
||||||
};
|
|
||||||
|
|
||||||
const actionHandled = handleOpenAction(action, [], explorer);
|
|
||||||
expect(explorer.cassandraAddCollectionPane.open).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("enum value should call cassandraAddCollectionPane.open", () => {
|
|
||||||
const action = {
|
|
||||||
actionType: "OpenPane",
|
|
||||||
paneKind: ActionContracts.PaneKind.CassandraAddCollection,
|
|
||||||
};
|
|
||||||
|
|
||||||
const actionHandled = handleOpenAction(action, [], explorer);
|
|
||||||
expect(explorer.cassandraAddCollectionPane.open).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("AddCollection pane kind", () => {
|
describe("AddCollection pane kind", () => {
|
||||||
it("string value should call explorer.onNewCollectionClicked", () => {
|
it("string value should call explorer.onNewCollectionClicked", () => {
|
||||||
const action = {
|
const action = {
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ function openPane(action: ActionContracts.OpenPane, explorer: Explorer) {
|
|||||||
action.paneKind === ActionContracts.PaneKind.CassandraAddCollection ||
|
action.paneKind === ActionContracts.PaneKind.CassandraAddCollection ||
|
||||||
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.CassandraAddCollection]
|
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.CassandraAddCollection]
|
||||||
) {
|
) {
|
||||||
explorer.cassandraAddCollectionPane.open();
|
explorer.openCassandraAddCollectionPane();
|
||||||
} else if (
|
} else if (
|
||||||
action.paneKind === ActionContracts.PaneKind.GlobalSettings ||
|
action.paneKind === ActionContracts.PaneKind.GlobalSettings ||
|
||||||
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.GlobalSettings]
|
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.GlobalSettings]
|
||||||
|
|||||||
@@ -1,273 +0,0 @@
|
|||||||
<div data-bind="visible: visible, event: { keydown: onPaneKeyDown }">
|
|
||||||
<div
|
|
||||||
class="contextual-pane-out"
|
|
||||||
data-bind="
|
|
||||||
click: cancel,
|
|
||||||
clickBubble: false"
|
|
||||||
></div>
|
|
||||||
<div class="contextual-pane" id="cassandraaddcollectionpane">
|
|
||||||
<!-- Add Cassandra collection form - Start -->
|
|
||||||
<div class="contextual-pane-in">
|
|
||||||
<form
|
|
||||||
class="paneContentContainer"
|
|
||||||
role="dialog"
|
|
||||||
aria-label="Add Table"
|
|
||||||
data-bind="
|
|
||||||
submit: submit"
|
|
||||||
>
|
|
||||||
<!-- Add Cassandra collection header - Start -->
|
|
||||||
<div class="firstdivbg headerline">
|
|
||||||
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
|
||||||
<div
|
|
||||||
class="closeImg"
|
|
||||||
role="button"
|
|
||||||
aria-label="Close pane"
|
|
||||||
tabindex="0"
|
|
||||||
data-bind="
|
|
||||||
click: cancel, event: { keypress: onCloseKeyPress }"
|
|
||||||
>
|
|
||||||
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Add Cassandra collection header - End -->
|
|
||||||
<!-- Add Cassandra collection errors - Start -->
|
|
||||||
<div
|
|
||||||
class="warningErrorContainer"
|
|
||||||
aria-live="assertive"
|
|
||||||
data-bind="visible: formErrors() && formErrors() !== ''"
|
|
||||||
>
|
|
||||||
<div class="warningErrorContent">
|
|
||||||
<span><img class="paneErrorIcon" src="/error_red.svg" alt="Error" /></span>
|
|
||||||
<span class="warningErrorDetailsLinkContainer">
|
|
||||||
<span class="formErrors" data-bind="text: formErrors, attr: { title: formErrors }"></span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Add Cassandra collection errors - End -->
|
|
||||||
<div class="paneMainContent">
|
|
||||||
<div class="seconddivpadding">
|
|
||||||
<p>
|
|
||||||
<span class="mandatoryStar">*</span> Keyspace name
|
|
||||||
<span class="infoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
|
|
||||||
<span class="tooltiptext infoTooltipWidth"
|
|
||||||
>Select an existing keyspace or enter a new keyspace id.</span
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="createNewDatabaseOrUseExisting">
|
|
||||||
<input
|
|
||||||
class="createNewDatabaseOrUseExistingRadio"
|
|
||||||
aria-label="Create new keyspace"
|
|
||||||
name="databaseType"
|
|
||||||
type="radio"
|
|
||||||
role="radio"
|
|
||||||
id="keyspaceCreateNew"
|
|
||||||
data-test="addCollection-newDatabase"
|
|
||||||
tabindex="0"
|
|
||||||
data-bind="checked: keyspaceCreateNew, checkedValue: true, attr: { 'aria-checked': keyspaceCreateNew() ? 'true' : 'false' }"
|
|
||||||
/>
|
|
||||||
<span class="createNewDatabaseOrUseExistingSpace" for="keyspaceCreateNew">Create new</span>
|
|
||||||
|
|
||||||
<input
|
|
||||||
class="createNewDatabaseOrUseExistingRadio"
|
|
||||||
aria-label="Use existing keyspace"
|
|
||||||
name="databaseType"
|
|
||||||
type="radio"
|
|
||||||
role="radio"
|
|
||||||
id="keyspaceUseExisting"
|
|
||||||
data-test="addCollection-existingDatabase"
|
|
||||||
tabindex="0"
|
|
||||||
data-bind="checked: keyspaceCreateNew, checkedValue: false, attr: { 'aria-checked': !keyspaceCreateNew() ? 'true' : 'false' }"
|
|
||||||
/>
|
|
||||||
<span class="createNewDatabaseOrUseExistingSpace" for="keyspaceUseExisting">Use existing</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input
|
|
||||||
id="keyspace-id"
|
|
||||||
data-test="addCollection-keyspaceId"
|
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
pattern="[^/?#\\]*[^/?# \\]"
|
|
||||||
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
|
||||||
placeholder="Type a new keyspace id"
|
|
||||||
size="40"
|
|
||||||
class="collid"
|
|
||||||
data-bind="visible: keyspaceCreateNew, textInput: keyspaceId, hasFocus: firstFieldHasFocus"
|
|
||||||
aria-label="Keyspace id"
|
|
||||||
aria-required="true"
|
|
||||||
autofocus
|
|
||||||
/>
|
|
||||||
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
aria-required="true"
|
|
||||||
autocomplete="off"
|
|
||||||
pattern="[^/?#\\]*[^/?# \\]"
|
|
||||||
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
|
||||||
list="keyspacesList"
|
|
||||||
placeholder="Choose existing keyspace id"
|
|
||||||
size="40"
|
|
||||||
class="collid"
|
|
||||||
data-bind="visible: !keyspaceCreateNew(), textInput: keyspaceId, hasFocus: firstFieldHasFocus"
|
|
||||||
aria-label="Keyspace id"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<datalist id="keyspacesList" data-bind="foreach: container.databases">
|
|
||||||
<option data-bind="value: $data.id"></option>
|
|
||||||
</datalist>
|
|
||||||
|
|
||||||
<!-- Database provisioned throughput - Start -->
|
|
||||||
<!-- ko if: canConfigureThroughput -->
|
|
||||||
<div
|
|
||||||
class="databaseProvision"
|
|
||||||
aria-label="New database provision support"
|
|
||||||
data-bind="visible: keyspaceCreateNew"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
tabindex="0"
|
|
||||||
type="checkbox"
|
|
||||||
id="keyspaceSharedThroughput"
|
|
||||||
title="Provision shared throughput"
|
|
||||||
data-bind="checked: keyspaceHasSharedOffer"
|
|
||||||
/>
|
|
||||||
<span class="databaseProvisionText" for="keyspaceSharedThroughput">Provision keyspace throughput</span>
|
|
||||||
<span class="infoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
|
|
||||||
<span class="tooltiptext provisionDatabaseThroughput"
|
|
||||||
>Provisioned throughput at the keyspace level will be shared across unlimited number of tables within
|
|
||||||
the keyspace</span
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<!-- 1 -->
|
|
||||||
<div data-bind="visible: keyspaceCreateNew() && keyspaceHasSharedOffer()">
|
|
||||||
<throughput-input-autopilot-v3
|
|
||||||
params="{
|
|
||||||
testId: 'cassandraThroughputValue-v3-shared',
|
|
||||||
value: keyspaceThroughput,
|
|
||||||
minimum: minThroughputRU,
|
|
||||||
maximum: maxThroughputRU,
|
|
||||||
isEnabled: keyspaceCreateNew() && keyspaceHasSharedOffer(),
|
|
||||||
label: sharedThroughputRangeText,
|
|
||||||
ariaLabel: sharedThroughputRangeText,
|
|
||||||
requestUnitsUsageCost: requestUnitsUsageCostShared,
|
|
||||||
spendAckChecked: sharedThroughputSpendAck,
|
|
||||||
spendAckId: 'sharedThroughputSpendAck-v3-shared',
|
|
||||||
spendAckText: sharedThroughputSpendAckText,
|
|
||||||
spendAckVisible: sharedThroughputSpendAckVisible,
|
|
||||||
showAsMandatory: true,
|
|
||||||
infoBubbleText: ruToolTipText,
|
|
||||||
throughputAutoPilotRadioId: 'newKeyspace-databaseThroughput-autoPilotRadio-v3-shared',
|
|
||||||
throughputProvisionedRadioId: 'newKeyspace-databaseThroughput-manualRadio-v3-shared',
|
|
||||||
isAutoPilotSelected: isSharedAutoPilotSelected,
|
|
||||||
maxAutoPilotThroughputSet: sharedAutoPilotThroughput,
|
|
||||||
autoPilotUsageCost: autoPilotUsageCost,
|
|
||||||
canExceedMaximumValue: canExceedMaximumValue,
|
|
||||||
costsVisible: costsVisible,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
</throughput-input-autopilot-v3>
|
|
||||||
</div>
|
|
||||||
<!-- /ko -->
|
|
||||||
<!-- Database provisioned throughput - End -->
|
|
||||||
</div>
|
|
||||||
<div class="seconddivpadding">
|
|
||||||
<p>
|
|
||||||
<span class="mandatoryStar">*</span> Enter CQL command to create the table.
|
|
||||||
<a href="https://aka.ms/cassandra-create-table" target="_blank">Learn More</a>
|
|
||||||
</p>
|
|
||||||
<div data-bind="text: createTableQuery" style="float: left; padding-top: 3px; padding-right: 3px"></div>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
data-test="addCollection-tableId"
|
|
||||||
aria-required="true"
|
|
||||||
autocomplete="off"
|
|
||||||
pattern="[^/?#\\]*[^/?# \\]"
|
|
||||||
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
|
||||||
data-test="addCollection-tableId"
|
|
||||||
placeholder="Enter tableId"
|
|
||||||
size="20"
|
|
||||||
class="textfontclr"
|
|
||||||
data-bind="value: tableId"
|
|
||||||
style="margin-bottom: 5px"
|
|
||||||
/>
|
|
||||||
<textarea
|
|
||||||
id="editor-area"
|
|
||||||
rows="15"
|
|
||||||
aria-label="Table Schema"
|
|
||||||
data-bind="value: userTableQuery"
|
|
||||||
style="height: 125px; width: calc(100% - 80px); resize: vertical"
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Provision table throughput - start -->
|
|
||||||
<!-- ko if: canConfigureThroughput -->
|
|
||||||
<div class="seconddivpadding" data-bind="visible: keyspaceHasSharedOffer() && !keyspaceCreateNew()">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="tableSharedThroughput"
|
|
||||||
title="Provision dedicated throughput for this table"
|
|
||||||
data-bind="checked: dedicateTableThroughput"
|
|
||||||
/>
|
|
||||||
<span for="tableSharedThroughput">Provision dedicated throughput for this table</span>
|
|
||||||
<span class="leftAlignInfoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
|
|
||||||
<span class="tooltiptext sharedCollectionThroughputTooltipWidth"
|
|
||||||
>You can optionally provision dedicated throughput for a table within a keyspace that has throughput
|
|
||||||
provisioned. This dedicated throughput amount will not be shared with other tables in the keyspace and
|
|
||||||
does not count towards the throughput you provisioned for the keyspace. This throughput amount will be
|
|
||||||
billed in addition to the throughput amount you provisioned at the keyspace level.</span
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<!-- 2 -->
|
|
||||||
<div data-bind="visible: !keyspaceHasSharedOffer() || dedicateTableThroughput()">
|
|
||||||
<throughput-input-autopilot-v3
|
|
||||||
params="{
|
|
||||||
testId: 'cassandraSharedThroughputValue-v3-dedicated',
|
|
||||||
value: throughput,
|
|
||||||
minimum: minThroughputRU,
|
|
||||||
maximum: maxThroughputRU,
|
|
||||||
isEnabled: !keyspaceHasSharedOffer() || dedicateTableThroughput(),
|
|
||||||
label: throughputRangeText,
|
|
||||||
ariaLabel: throughputRangeText,
|
|
||||||
costsVisible: costsVisible,
|
|
||||||
requestUnitsUsageCost: requestUnitsUsageCostDedicated,
|
|
||||||
spendAckChecked: throughputSpendAck,
|
|
||||||
spendAckId: 'throughputSpendAckCassandra-v3-dedicated',
|
|
||||||
spendAckText: throughputSpendAckText,
|
|
||||||
spendAckVisible: throughputSpendAckVisible,
|
|
||||||
showAsMandatory: true,
|
|
||||||
infoBubbleText: ruToolTipText,
|
|
||||||
throughputAutoPilotRadioId: 'newKeyspace-containerThroughput-autoPilotRadio-v3-dedicated',
|
|
||||||
throughputProvisionedRadioId: 'newKeyspace-containerThroughput-manualRadio-v3-dedicated',
|
|
||||||
isAutoPilotSelected: isAutoPilotSelected,
|
|
||||||
maxAutoPilotThroughputSet: selectedAutoPilotThroughput,
|
|
||||||
autoPilotUsageCost: autoPilotUsageCost,
|
|
||||||
canExceedMaximumValue: canExceedMaximumValue,
|
|
||||||
overrideWithAutoPilotSettings: false,
|
|
||||||
overrideWithProvisionedThroughputSettings: false
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
</throughput-input-autopilot-v3>
|
|
||||||
</div>
|
|
||||||
<!-- /ko -->
|
|
||||||
<!-- Provision table throughput - end -->
|
|
||||||
</div>
|
|
||||||
<div class="paneFooter">
|
|
||||||
<div class="leftpanel-okbut">
|
|
||||||
<input type="submit" data-test="addCollection-createCollection" value="OK" class="btncreatecoll1" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<!-- Add Cassandra collection form - End -->
|
|
||||||
<!-- Loader - Start -->
|
|
||||||
<div class="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer" data-bind="visible: isExecuting">
|
|
||||||
<img class="dataExplorerLoader" src="/LoadingIndicator_3Squares.gif" alt="loading indicator" />
|
|
||||||
</div>
|
|
||||||
<!-- Loader - End -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,539 +0,0 @@
|
|||||||
import * as ko from "knockout";
|
|
||||||
import * as _ from "underscore";
|
|
||||||
import * as Constants from "../../Common/Constants";
|
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
|
||||||
import { configContext, Platform } from "../../ConfigContext";
|
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
|
||||||
import * as AddCollectionUtility from "../../Shared/AddCollectionUtility";
|
|
||||||
import * as SharedConstants from "../../Shared/Constants";
|
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import { userContext } from "../../UserContext";
|
|
||||||
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
|
|
||||||
import * as PricingUtils from "../../Utils/PricingUtils";
|
|
||||||
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
|
|
||||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
|
||||||
|
|
||||||
export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|
||||||
public createTableQuery: ko.Observable<string>;
|
|
||||||
public keyspaceId: ko.Observable<string>;
|
|
||||||
public maxThroughputRU: ko.Observable<number>;
|
|
||||||
public minThroughputRU: ko.Observable<number>;
|
|
||||||
public tableId: ko.Observable<string>;
|
|
||||||
public throughput: ko.Observable<number>;
|
|
||||||
public throughputRangeText: ko.Computed<string>;
|
|
||||||
public sharedThroughputRangeText: ko.Computed<string>;
|
|
||||||
public userTableQuery: ko.Observable<string>;
|
|
||||||
public requestUnitsUsageCostDedicated: ko.Computed<string>;
|
|
||||||
public requestUnitsUsageCostShared: ko.Computed<string>;
|
|
||||||
public costsVisible: ko.PureComputed<boolean>;
|
|
||||||
public keyspaceHasSharedOffer: ko.Observable<boolean>;
|
|
||||||
public keyspaceIds: ko.ObservableArray<string>;
|
|
||||||
public keyspaceThroughput: ko.Observable<number>;
|
|
||||||
public keyspaceCreateNew: ko.Observable<boolean>;
|
|
||||||
public dedicateTableThroughput: ko.Observable<boolean>;
|
|
||||||
public canRequestSupport: ko.PureComputed<boolean>;
|
|
||||||
public throughputSpendAckText: ko.Observable<string>;
|
|
||||||
public throughputSpendAck: ko.Observable<boolean>;
|
|
||||||
public sharedThroughputSpendAck: ko.Observable<boolean>;
|
|
||||||
public sharedThroughputSpendAckText: ko.Observable<string>;
|
|
||||||
public isAutoPilotSelected: ko.Observable<boolean>;
|
|
||||||
public isSharedAutoPilotSelected: ko.Observable<boolean>;
|
|
||||||
public selectedAutoPilotThroughput: ko.Observable<number>;
|
|
||||||
public sharedAutoPilotThroughput: ko.Observable<number>;
|
|
||||||
public autoPilotUsageCost: ko.Computed<string>;
|
|
||||||
public sharedThroughputSpendAckVisible: ko.Computed<boolean>;
|
|
||||||
public throughputSpendAckVisible: ko.Computed<boolean>;
|
|
||||||
public canExceedMaximumValue: ko.PureComputed<boolean>;
|
|
||||||
public isFreeTierAccount: ko.Computed<boolean>;
|
|
||||||
public ruToolTipText: ko.Computed<string>;
|
|
||||||
public canConfigureThroughput: ko.PureComputed<boolean>;
|
|
||||||
|
|
||||||
private keyspaceOffers: Map<string, DataModels.Offer>;
|
|
||||||
|
|
||||||
constructor(options: ViewModels.PaneOptions) {
|
|
||||||
super(options);
|
|
||||||
this.title("Add Table");
|
|
||||||
this.createTableQuery = ko.observable<string>("CREATE TABLE ");
|
|
||||||
this.keyspaceCreateNew = ko.observable<boolean>(true);
|
|
||||||
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText());
|
|
||||||
this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled());
|
|
||||||
this.keyspaceOffers = new Map();
|
|
||||||
this.keyspaceIds = ko.observableArray<string>();
|
|
||||||
this.keyspaceHasSharedOffer = ko.observable<boolean>(false);
|
|
||||||
this.keyspaceThroughput = ko.observable<number>();
|
|
||||||
this.keyspaceId = ko.observable<string>("");
|
|
||||||
this.keyspaceId.subscribe((keyspaceId: string) => {
|
|
||||||
if (this.keyspaceIds.indexOf(keyspaceId) >= 0) {
|
|
||||||
this.keyspaceHasSharedOffer(this.keyspaceOffers.has(keyspaceId));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.keyspaceId.extend({ rateLimit: 100 });
|
|
||||||
this.dedicateTableThroughput = ko.observable<boolean>(false);
|
|
||||||
const throughputDefaults = this.container.collectionCreationDefaults.throughput;
|
|
||||||
this.maxThroughputRU = ko.observable<number>(throughputDefaults.unlimitedmax);
|
|
||||||
this.minThroughputRU = ko.observable<number>(throughputDefaults.unlimitedmin);
|
|
||||||
|
|
||||||
this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue());
|
|
||||||
|
|
||||||
this.isFreeTierAccount = ko.computed<boolean>(() => {
|
|
||||||
return userContext?.databaseAccount?.properties?.enableFreeTier;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.tableId = ko.observable<string>("");
|
|
||||||
this.isAutoPilotSelected = ko.observable<boolean>(false);
|
|
||||||
this.isSharedAutoPilotSelected = ko.observable<boolean>(false);
|
|
||||||
this.selectedAutoPilotThroughput = ko.observable<number>();
|
|
||||||
this.sharedAutoPilotThroughput = ko.observable<number>();
|
|
||||||
this.throughput = ko.observable<number>();
|
|
||||||
this.throughputRangeText = ko.pureComputed<string>(() => {
|
|
||||||
const enableAutoPilot = this.isAutoPilotSelected();
|
|
||||||
if (!enableAutoPilot) {
|
|
||||||
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
|
|
||||||
}
|
|
||||||
return AutoPilotUtils.getAutoPilotHeaderText();
|
|
||||||
});
|
|
||||||
this.sharedThroughputRangeText = ko.pureComputed<string>(() => {
|
|
||||||
if (this.isSharedAutoPilotSelected()) {
|
|
||||||
return AutoPilotUtils.getAutoPilotHeaderText();
|
|
||||||
}
|
|
||||||
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
|
|
||||||
});
|
|
||||||
this.userTableQuery = ko.observable<string>("(userid int, name text, email text, PRIMARY KEY (userid))");
|
|
||||||
this.keyspaceId.subscribe((keyspaceId) => {
|
|
||||||
this.createTableQuery(`CREATE TABLE ${keyspaceId}.`);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.throughputSpendAckText = ko.observable<string>();
|
|
||||||
this.throughputSpendAck = ko.observable<boolean>(false);
|
|
||||||
this.sharedThroughputSpendAck = ko.observable<boolean>(false);
|
|
||||||
this.sharedThroughputSpendAckText = ko.observable<string>();
|
|
||||||
|
|
||||||
this.resetData();
|
|
||||||
|
|
||||||
this.requestUnitsUsageCostDedicated = ko.computed(() => {
|
|
||||||
const { databaseAccount: account } = userContext;
|
|
||||||
if (!account) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
const regions =
|
|
||||||
(account &&
|
|
||||||
account.properties &&
|
|
||||||
account.properties.readLocations &&
|
|
||||||
account.properties.readLocations.length) ||
|
|
||||||
1;
|
|
||||||
const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false;
|
|
||||||
const offerThroughput: number = this.throughput();
|
|
||||||
let estimatedSpend: string;
|
|
||||||
let estimatedDedicatedSpendAcknowledge: string;
|
|
||||||
if (!this.isAutoPilotSelected()) {
|
|
||||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
|
|
||||||
offerThroughput,
|
|
||||||
userContext.portalEnv,
|
|
||||||
regions,
|
|
||||||
multimaster
|
|
||||||
);
|
|
||||||
estimatedDedicatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
|
|
||||||
offerThroughput,
|
|
||||||
userContext.portalEnv,
|
|
||||||
regions,
|
|
||||||
multimaster,
|
|
||||||
this.isAutoPilotSelected()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
|
|
||||||
this.selectedAutoPilotThroughput(),
|
|
||||||
userContext.portalEnv,
|
|
||||||
regions,
|
|
||||||
multimaster
|
|
||||||
);
|
|
||||||
estimatedDedicatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
|
|
||||||
this.selectedAutoPilotThroughput(),
|
|
||||||
userContext.portalEnv,
|
|
||||||
regions,
|
|
||||||
multimaster,
|
|
||||||
this.isAutoPilotSelected()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.throughputSpendAckText(estimatedDedicatedSpendAcknowledge);
|
|
||||||
return estimatedSpend;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.requestUnitsUsageCostShared = ko.computed(() => {
|
|
||||||
const { databaseAccount: account } = userContext;
|
|
||||||
if (!account) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
const regions = account?.properties?.readLocations?.length || 1;
|
|
||||||
const multimaster = account?.properties?.enableMultipleWriteLocations || false;
|
|
||||||
let estimatedSpend: string;
|
|
||||||
let estimatedSharedSpendAcknowledge: string;
|
|
||||||
if (!this.isSharedAutoPilotSelected()) {
|
|
||||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
|
|
||||||
this.keyspaceThroughput(),
|
|
||||||
userContext.portalEnv,
|
|
||||||
regions,
|
|
||||||
multimaster
|
|
||||||
);
|
|
||||||
estimatedSharedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
|
|
||||||
this.keyspaceThroughput(),
|
|
||||||
userContext.portalEnv,
|
|
||||||
regions,
|
|
||||||
multimaster,
|
|
||||||
this.isSharedAutoPilotSelected()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
|
|
||||||
this.sharedAutoPilotThroughput(),
|
|
||||||
userContext.portalEnv,
|
|
||||||
regions,
|
|
||||||
multimaster
|
|
||||||
);
|
|
||||||
estimatedSharedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
|
|
||||||
this.sharedAutoPilotThroughput(),
|
|
||||||
userContext.portalEnv,
|
|
||||||
regions,
|
|
||||||
multimaster,
|
|
||||||
this.isSharedAutoPilotSelected()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.sharedThroughputSpendAckText(estimatedSharedSpendAcknowledge);
|
|
||||||
return estimatedSpend;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.costsVisible = ko.pureComputed(() => {
|
|
||||||
return configContext.platform !== Platform.Emulator;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.canRequestSupport = ko.pureComputed(() => {
|
|
||||||
if (configContext.platform !== Platform.Emulator && !userContext.isTryCosmosDBSubscription) {
|
|
||||||
const offerThroughput: number = this.throughput();
|
|
||||||
return offerThroughput <= 100000;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.sharedThroughputSpendAckVisible = ko.computed<boolean>(() => {
|
|
||||||
const autoscaleThroughput = this.sharedAutoPilotThroughput() * 1;
|
|
||||||
if (this.isSharedAutoPilotSelected()) {
|
|
||||||
return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.keyspaceThroughput() > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => {
|
|
||||||
const autoscaleThroughput = this.selectedAutoPilotThroughput() * 1;
|
|
||||||
if (this.isAutoPilotSelected()) {
|
|
||||||
return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.throughput() > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!!this.container) {
|
|
||||||
const updateKeyspaceIds: (keyspaces: ViewModels.Database[]) => void = (
|
|
||||||
newKeyspaceIds: ViewModels.Database[]
|
|
||||||
): void => {
|
|
||||||
const cachedKeyspaceIdsList = _.map(newKeyspaceIds, (keyspace: ViewModels.Database) => {
|
|
||||||
if (keyspace && keyspace.offer && !!keyspace.offer()) {
|
|
||||||
this.keyspaceOffers.set(keyspace.id(), keyspace.offer());
|
|
||||||
}
|
|
||||||
return keyspace.id();
|
|
||||||
});
|
|
||||||
this.keyspaceIds(cachedKeyspaceIdsList);
|
|
||||||
};
|
|
||||||
this.container.databases.subscribe((newDatabases: ViewModels.Database[]) => updateKeyspaceIds(newDatabases));
|
|
||||||
updateKeyspaceIds(this.container.databases());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.autoPilotUsageCost = ko.pureComputed<string>(() => {
|
|
||||||
const autoPilot = this._getAutoPilot();
|
|
||||||
if (!autoPilot) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
const isDatabaseThroughput: boolean = this.keyspaceCreateNew();
|
|
||||||
return PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, isDatabaseThroughput);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public decreaseThroughput() {
|
|
||||||
let offerThroughput: number = this.throughput();
|
|
||||||
|
|
||||||
if (offerThroughput > this.minThroughputRU()) {
|
|
||||||
offerThroughput -= 100;
|
|
||||||
this.throughput(offerThroughput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public increaseThroughput() {
|
|
||||||
let offerThroughput: number = this.throughput();
|
|
||||||
|
|
||||||
if (offerThroughput < this.maxThroughputRU()) {
|
|
||||||
offerThroughput += 100;
|
|
||||||
this.throughput(offerThroughput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public open() {
|
|
||||||
super.open();
|
|
||||||
if (!this.container.isServerlessEnabled()) {
|
|
||||||
this.isAutoPilotSelected(this.container.isAutoscaleDefaultEnabled());
|
|
||||||
}
|
|
||||||
const addCollectionPaneOpenMessage = {
|
|
||||||
collection: ko.toJS({
|
|
||||||
id: this.tableId(),
|
|
||||||
storage: Constants.BackendDefaults.multiPartitionStorageInGb,
|
|
||||||
offerThroughput: this.throughput(),
|
|
||||||
partitionKey: "",
|
|
||||||
databaseId: this.keyspaceId(),
|
|
||||||
}),
|
|
||||||
subscriptionType: userContext.subscriptionType,
|
|
||||||
subscriptionQuotaId: userContext.quotaId,
|
|
||||||
defaultsCheck: {
|
|
||||||
storage: "u",
|
|
||||||
throughput: this.throughput(),
|
|
||||||
flight: userContext.addCollectionFlight,
|
|
||||||
},
|
|
||||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
|
||||||
};
|
|
||||||
const focusElement = document.getElementById("keyspace-id");
|
|
||||||
focusElement && focusElement.focus();
|
|
||||||
TelemetryProcessor.trace(Action.CreateCollection, ActionModifiers.Open, addCollectionPaneOpenMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public submit() {
|
|
||||||
if (!this._isValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.isExecuting(true);
|
|
||||||
const autoPilotCommand = `cosmosdb_autoscale_max_throughput`;
|
|
||||||
let createTableAndKeyspacePromise: Q.Promise<any>;
|
|
||||||
const toCreateKeyspace: boolean = this.keyspaceCreateNew();
|
|
||||||
const useAutoPilotForKeyspace: boolean = this.isSharedAutoPilotSelected() && !!this.sharedAutoPilotThroughput();
|
|
||||||
const createKeyspaceQueryPrefix: string = `CREATE KEYSPACE ${this.keyspaceId().trim()} WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 }`;
|
|
||||||
const createKeyspaceQuery: string = this.keyspaceHasSharedOffer()
|
|
||||||
? useAutoPilotForKeyspace
|
|
||||||
? `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${this.sharedAutoPilotThroughput()};`
|
|
||||||
: `${createKeyspaceQueryPrefix} AND cosmosdb_provisioned_throughput=${this.keyspaceThroughput()};`
|
|
||||||
: `${createKeyspaceQueryPrefix};`;
|
|
||||||
const createTableQueryPrefix: string = `${this.createTableQuery()}${this.tableId().trim()} ${this.userTableQuery()}`;
|
|
||||||
let createTableQuery: string;
|
|
||||||
|
|
||||||
if (this.canConfigureThroughput() && (this.dedicateTableThroughput() || !this.keyspaceHasSharedOffer())) {
|
|
||||||
if (this.isAutoPilotSelected() && this.selectedAutoPilotThroughput()) {
|
|
||||||
createTableQuery = `${createTableQueryPrefix} WITH ${autoPilotCommand}=${this.selectedAutoPilotThroughput()};`;
|
|
||||||
} else {
|
|
||||||
createTableQuery = `${createTableQueryPrefix} WITH cosmosdb_provisioned_throughput=${this.throughput()};`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
createTableQuery = `${createTableQueryPrefix};`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const addCollectionPaneStartMessage = {
|
|
||||||
collection: ko.toJS({
|
|
||||||
id: this.tableId(),
|
|
||||||
storage: Constants.BackendDefaults.multiPartitionStorageInGb,
|
|
||||||
offerThroughput: this.throughput(),
|
|
||||||
partitionKey: "",
|
|
||||||
databaseId: this.keyspaceId(),
|
|
||||||
hasDedicatedThroughput: this.dedicateTableThroughput(),
|
|
||||||
}),
|
|
||||||
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
|
||||||
subscriptionType: userContext.subscriptionType,
|
|
||||||
subscriptionQuotaId: userContext.quotaId,
|
|
||||||
defaultsCheck: {
|
|
||||||
storage: "u",
|
|
||||||
throughput: this.throughput(),
|
|
||||||
flight: userContext.addCollectionFlight,
|
|
||||||
},
|
|
||||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
|
||||||
toCreateKeyspace: toCreateKeyspace,
|
|
||||||
createKeyspaceQuery: createKeyspaceQuery,
|
|
||||||
createTableQuery: createTableQuery,
|
|
||||||
};
|
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.CreateCollection, addCollectionPaneStartMessage);
|
|
||||||
const { databaseAccount } = userContext;
|
|
||||||
if (toCreateKeyspace) {
|
|
||||||
createTableAndKeyspacePromise = (<CassandraAPIDataClient>this.container.tableDataClient).createTableAndKeyspace(
|
|
||||||
databaseAccount?.properties.cassandraEndpoint,
|
|
||||||
databaseAccount?.id,
|
|
||||||
this.container,
|
|
||||||
createTableQuery,
|
|
||||||
createKeyspaceQuery
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
createTableAndKeyspacePromise = (<CassandraAPIDataClient>this.container.tableDataClient).createTableAndKeyspace(
|
|
||||||
databaseAccount?.properties.cassandraEndpoint,
|
|
||||||
databaseAccount?.id,
|
|
||||||
this.container,
|
|
||||||
createTableQuery
|
|
||||||
);
|
|
||||||
}
|
|
||||||
createTableAndKeyspacePromise.then(
|
|
||||||
() => {
|
|
||||||
this.container.refreshAllDatabases();
|
|
||||||
this.isExecuting(false);
|
|
||||||
this.close();
|
|
||||||
const addCollectionPaneSuccessMessage = {
|
|
||||||
collection: ko.toJS({
|
|
||||||
id: this.tableId(),
|
|
||||||
storage: Constants.BackendDefaults.multiPartitionStorageInGb,
|
|
||||||
offerThroughput: this.throughput(),
|
|
||||||
partitionKey: "",
|
|
||||||
databaseId: this.keyspaceId(),
|
|
||||||
hasDedicatedThroughput: this.dedicateTableThroughput(),
|
|
||||||
}),
|
|
||||||
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
|
||||||
subscriptionType: userContext.subscriptionType,
|
|
||||||
subscriptionQuotaId: userContext.quotaId,
|
|
||||||
defaultsCheck: {
|
|
||||||
storage: "u",
|
|
||||||
throughput: this.throughput(),
|
|
||||||
flight: userContext.addCollectionFlight,
|
|
||||||
},
|
|
||||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
|
||||||
toCreateKeyspace: toCreateKeyspace,
|
|
||||||
createKeyspaceQuery: createKeyspaceQuery,
|
|
||||||
createTableQuery: createTableQuery,
|
|
||||||
};
|
|
||||||
TelemetryProcessor.traceSuccess(Action.CreateCollection, addCollectionPaneSuccessMessage, startKey);
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
const errorMessage = getErrorMessage(error);
|
|
||||||
this.formErrors(errorMessage);
|
|
||||||
this.isExecuting(false);
|
|
||||||
const addCollectionPaneFailedMessage = {
|
|
||||||
collection: {
|
|
||||||
id: this.tableId(),
|
|
||||||
storage: Constants.BackendDefaults.multiPartitionStorageInGb,
|
|
||||||
offerThroughput: this.throughput(),
|
|
||||||
partitionKey: "",
|
|
||||||
databaseId: this.keyspaceId(),
|
|
||||||
hasDedicatedThroughput: this.dedicateTableThroughput(),
|
|
||||||
},
|
|
||||||
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
|
||||||
subscriptionType: userContext.subscriptionType,
|
|
||||||
subscriptionQuotaId: userContext.quotaId,
|
|
||||||
defaultsCheck: {
|
|
||||||
storage: "u",
|
|
||||||
throughput: this.throughput(),
|
|
||||||
flight: userContext.addCollectionFlight,
|
|
||||||
},
|
|
||||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
|
||||||
toCreateKeyspace: toCreateKeyspace,
|
|
||||||
createKeyspaceQuery: createKeyspaceQuery,
|
|
||||||
createTableQuery: createTableQuery,
|
|
||||||
error: errorMessage,
|
|
||||||
errorStack: getErrorStack(error),
|
|
||||||
};
|
|
||||||
TelemetryProcessor.traceFailure(Action.CreateCollection, addCollectionPaneFailedMessage, startKey);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public resetData() {
|
|
||||||
super.resetData();
|
|
||||||
const throughputDefaults = this.container.collectionCreationDefaults.throughput;
|
|
||||||
this.isAutoPilotSelected(this.container.isAutoscaleDefaultEnabled());
|
|
||||||
this.isSharedAutoPilotSelected(this.container.isAutoscaleDefaultEnabled());
|
|
||||||
this.selectedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
|
||||||
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
|
||||||
this.throughput(AddCollectionUtility.getMaxThroughput(this.container.collectionCreationDefaults, this.container));
|
|
||||||
this.keyspaceThroughput(throughputDefaults.shared);
|
|
||||||
this.maxThroughputRU(throughputDefaults.unlimitedmax);
|
|
||||||
this.minThroughputRU(throughputDefaults.unlimitedmin);
|
|
||||||
this.createTableQuery("CREATE TABLE ");
|
|
||||||
this.userTableQuery("(userid int, name text, email text, PRIMARY KEY (userid))");
|
|
||||||
this.tableId("");
|
|
||||||
this.keyspaceId("");
|
|
||||||
this.throughputSpendAck(false);
|
|
||||||
this.keyspaceHasSharedOffer(false);
|
|
||||||
this.keyspaceCreateNew(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _isValid(): boolean {
|
|
||||||
const throughput = this.throughput();
|
|
||||||
const keyspaceThroughput = this.keyspaceThroughput();
|
|
||||||
|
|
||||||
const sharedAutoscaleThroughput = this.sharedAutoPilotThroughput() * 1;
|
|
||||||
if (
|
|
||||||
this.isSharedAutoPilotSelected() &&
|
|
||||||
sharedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
|
|
||||||
!this.sharedThroughputSpendAck()
|
|
||||||
) {
|
|
||||||
this.formErrors(`Please acknowledge the estimated monthly spend.`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dedicatedAutoscaleThroughput = this.selectedAutoPilotThroughput() * 1;
|
|
||||||
if (
|
|
||||||
this.isAutoPilotSelected() &&
|
|
||||||
dedicatedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
|
|
||||||
!this.throughputSpendAck()
|
|
||||||
) {
|
|
||||||
this.formErrors(`Please acknowledge the estimated monthly spend.`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
(this.keyspaceCreateNew() && this.keyspaceHasSharedOffer() && this.isSharedAutoPilotSelected()) ||
|
|
||||||
this.isAutoPilotSelected()
|
|
||||||
) {
|
|
||||||
const autoPilot = this._getAutoPilot();
|
|
||||||
if (
|
|
||||||
!autoPilot ||
|
|
||||||
!autoPilot.maxThroughput ||
|
|
||||||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput)
|
|
||||||
) {
|
|
||||||
this.formErrors(
|
|
||||||
`Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !this.throughputSpendAck()) {
|
|
||||||
this.formErrors(`Please acknowledge the estimated daily spend.`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.keyspaceHasSharedOffer() &&
|
|
||||||
this.keyspaceCreateNew() &&
|
|
||||||
keyspaceThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
|
|
||||||
!this.sharedThroughputSpendAck()
|
|
||||||
) {
|
|
||||||
this.formErrors("Please acknowledge the estimated daily spend");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _getAutoPilot(): DataModels.AutoPilotCreationSettings {
|
|
||||||
if (
|
|
||||||
this.keyspaceCreateNew() &&
|
|
||||||
this.keyspaceHasSharedOffer() &&
|
|
||||||
this.isSharedAutoPilotSelected() &&
|
|
||||||
this.sharedAutoPilotThroughput()
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
maxThroughput: this.sharedAutoPilotThroughput() * 1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.selectedAutoPilotThroughput()) {
|
|
||||||
return {
|
|
||||||
maxThroughput: this.selectedAutoPilotThroughput() * 1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { fireEvent, render, screen } from "@testing-library/react";
|
||||||
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
|
import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
|
||||||
|
import { CassandraAddCollectionPane } from "./CassandraAddCollectionPane";
|
||||||
|
const props = {
|
||||||
|
explorer: new Explorer(),
|
||||||
|
closePanel: (): void => undefined,
|
||||||
|
cassandraApiClient: new CassandraAPIDataClient(),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("CassandraAddCollectionPane Pane", () => {
|
||||||
|
beforeEach(() => render(<CassandraAddCollectionPane {...props} />));
|
||||||
|
|
||||||
|
it("should render Default properly", () => {
|
||||||
|
const wrapper = shallow(<CassandraAddCollectionPane {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
it("click on is Create new keyspace", () => {
|
||||||
|
fireEvent.click(screen.getByLabelText("Create new keyspace"));
|
||||||
|
expect(screen.getByLabelText("Provision keyspace throughput")).toBeDefined();
|
||||||
|
});
|
||||||
|
it("click on Use existing", () => {
|
||||||
|
fireEvent.click(screen.getByLabelText("Use existing keyspace"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Enter Keyspace name ", () => {
|
||||||
|
fireEvent.change(screen.getByLabelText("Keyspace id"), { target: { value: "unittest1" } });
|
||||||
|
expect(screen.getByLabelText("CREATE TABLE unittest1.")).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,427 @@
|
|||||||
|
import { Label, Stack, TextField } from "@fluentui/react";
|
||||||
|
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||||
|
import * as _ from "underscore";
|
||||||
|
import * as Constants from "../../../Common/Constants";
|
||||||
|
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
||||||
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
|
import * as AddCollectionUtility from "../../../Shared/AddCollectionUtility";
|
||||||
|
import * as SharedConstants from "../../../Shared/Constants";
|
||||||
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import { userContext } from "../../../UserContext";
|
||||||
|
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||||
|
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
|
import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
|
||||||
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
|
export interface CassandraAddCollectionPaneProps {
|
||||||
|
explorer: Explorer;
|
||||||
|
closePanel: () => void;
|
||||||
|
cassandraApiClient: CassandraAPIDataClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectionPaneProps> = ({
|
||||||
|
explorer: container,
|
||||||
|
closePanel,
|
||||||
|
cassandraApiClient,
|
||||||
|
}: CassandraAddCollectionPaneProps) => {
|
||||||
|
const throughputDefaults = container.collectionCreationDefaults.throughput;
|
||||||
|
const [createTableQuery, setCreateTableQuery] = useState<string>("CREATE TABLE ");
|
||||||
|
const [keyspaceId, setKeyspaceId] = useState<string>("");
|
||||||
|
const [tableId, setTableId] = useState<string>("");
|
||||||
|
const [throughput, setThroughput] = useState<number>(
|
||||||
|
AddCollectionUtility.getMaxThroughput(container.collectionCreationDefaults, container)
|
||||||
|
);
|
||||||
|
|
||||||
|
const [isAutoPilotSelected, setIsAutoPilotSelected] = useState<boolean>(container.isAutoscaleDefaultEnabled());
|
||||||
|
|
||||||
|
const [isSharedAutoPilotSelected, setIsSharedAutoPilotSelected] = useState<boolean>(
|
||||||
|
container.isAutoscaleDefaultEnabled()
|
||||||
|
);
|
||||||
|
|
||||||
|
const [userTableQuery, setUserTableQuery] = useState<string>(
|
||||||
|
"(userid int, name text, email text, PRIMARY KEY (userid))"
|
||||||
|
);
|
||||||
|
|
||||||
|
const [keyspaceHasSharedOffer, setKeyspaceHasSharedOffer] = useState<boolean>(false);
|
||||||
|
const [keyspaceIds, setKeyspaceIds] = useState<string[]>([]);
|
||||||
|
const [keyspaceThroughput, setKeyspaceThroughput] = useState<number>(throughputDefaults.shared);
|
||||||
|
const [keyspaceCreateNew, setKeyspaceCreateNew] = useState<boolean>(true);
|
||||||
|
const [dedicateTableThroughput, setDedicateTableThroughput] = useState<boolean>(false);
|
||||||
|
const [throughputSpendAck, setThroughputSpendAck] = useState<boolean>(false);
|
||||||
|
const [sharedThroughputSpendAck, setSharedThroughputSpendAck] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const { minAutoPilotThroughput: selectedAutoPilotThroughput } = AutoPilotUtils;
|
||||||
|
const { minAutoPilotThroughput: sharedAutoPilotThroughput } = AutoPilotUtils;
|
||||||
|
|
||||||
|
const _getAutoPilot = (): DataModels.AutoPilotCreationSettings => {
|
||||||
|
if (keyspaceCreateNew && keyspaceHasSharedOffer && isSharedAutoPilotSelected && sharedAutoPilotThroughput) {
|
||||||
|
return {
|
||||||
|
maxThroughput: sharedAutoPilotThroughput * 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedAutoPilotThroughput) {
|
||||||
|
return {
|
||||||
|
maxThroughput: selectedAutoPilotThroughput * 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
|
||||||
|
|
||||||
|
const canConfigureThroughput = !container.isServerlessEnabled();
|
||||||
|
|
||||||
|
const keyspaceOffers = new Map();
|
||||||
|
const [isExecuting, setIsExecuting] = useState<boolean>();
|
||||||
|
const [formErrors, setFormErrors] = useState<string>("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (keyspaceIds.indexOf(keyspaceId) >= 0) {
|
||||||
|
setKeyspaceHasSharedOffer(keyspaceOffers.has(keyspaceId));
|
||||||
|
}
|
||||||
|
setCreateTableQuery(`CREATE TABLE ${keyspaceId}.`);
|
||||||
|
}, [keyspaceId]);
|
||||||
|
|
||||||
|
const addCollectionPaneOpenMessage = {
|
||||||
|
collection: {
|
||||||
|
id: tableId,
|
||||||
|
storage: Constants.BackendDefaults.multiPartitionStorageInGb,
|
||||||
|
offerThroughput: throughput,
|
||||||
|
partitionKey: "",
|
||||||
|
databaseId: keyspaceId,
|
||||||
|
},
|
||||||
|
subscriptionType: userContext.subscriptionType,
|
||||||
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
|
defaultsCheck: {
|
||||||
|
storage: "u",
|
||||||
|
throughput,
|
||||||
|
flight: userContext.addCollectionFlight,
|
||||||
|
},
|
||||||
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!container.isServerlessEnabled()) {
|
||||||
|
setIsAutoPilotSelected(container.isAutoscaleDefaultEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
TelemetryProcessor.trace(Action.CreateCollection, ActionModifiers.Open, addCollectionPaneOpenMessage);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (container) {
|
||||||
|
const newKeyspaceIds: ViewModels.Database[] = container.databases();
|
||||||
|
const cachedKeyspaceIdsList = _.map(newKeyspaceIds, (keyspace: ViewModels.Database) => {
|
||||||
|
if (keyspace && keyspace.offer && !!keyspace.offer()) {
|
||||||
|
keyspaceOffers.set(keyspace.id(), keyspace.offer());
|
||||||
|
}
|
||||||
|
return keyspace.id();
|
||||||
|
});
|
||||||
|
setKeyspaceIds(cachedKeyspaceIdsList);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const _isValid = () => {
|
||||||
|
const sharedAutoscaleThroughput = sharedAutoPilotThroughput * 1;
|
||||||
|
if (
|
||||||
|
isSharedAutoPilotSelected &&
|
||||||
|
sharedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
|
||||||
|
!sharedThroughputSpendAck
|
||||||
|
) {
|
||||||
|
setFormErrors(`Please acknowledge the estimated monthly spend.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dedicatedAutoscaleThroughput = selectedAutoPilotThroughput * 1;
|
||||||
|
if (
|
||||||
|
isAutoPilotSelected &&
|
||||||
|
dedicatedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
|
||||||
|
!throughputSpendAck
|
||||||
|
) {
|
||||||
|
setFormErrors(`Please acknowledge the estimated monthly spend.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((keyspaceCreateNew && keyspaceHasSharedOffer && isSharedAutoPilotSelected) || isAutoPilotSelected) {
|
||||||
|
const autoPilot = _getAutoPilot();
|
||||||
|
if (
|
||||||
|
!autoPilot ||
|
||||||
|
!autoPilot.maxThroughput ||
|
||||||
|
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput)
|
||||||
|
) {
|
||||||
|
setFormErrors(
|
||||||
|
`Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !throughputSpendAck) {
|
||||||
|
setFormErrors(`Please acknowledge the estimated daily spend.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
keyspaceHasSharedOffer &&
|
||||||
|
keyspaceCreateNew &&
|
||||||
|
keyspaceThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
|
||||||
|
!sharedThroughputSpendAck
|
||||||
|
) {
|
||||||
|
setFormErrors("Please acknowledge the estimated daily spend");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async () => {
|
||||||
|
if (!_isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsExecuting(true);
|
||||||
|
const autoPilotCommand = `cosmosdb_autoscale_max_throughput`;
|
||||||
|
|
||||||
|
const toCreateKeyspace: boolean = keyspaceCreateNew;
|
||||||
|
const useAutoPilotForKeyspace: boolean = isSharedAutoPilotSelected && !!sharedAutoPilotThroughput;
|
||||||
|
const createKeyspaceQueryPrefix = `CREATE KEYSPACE ${keyspaceId.trim()} WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 }`;
|
||||||
|
const createKeyspaceQuery: string = keyspaceHasSharedOffer
|
||||||
|
? useAutoPilotForKeyspace
|
||||||
|
? `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${keyspaceThroughput};`
|
||||||
|
: `${createKeyspaceQueryPrefix} AND cosmosdb_provisioned_throughput=${keyspaceThroughput};`
|
||||||
|
: `${createKeyspaceQueryPrefix};`;
|
||||||
|
let tableQuery: string;
|
||||||
|
const createTableQueryPrefix = `${createTableQuery}${tableId.trim()} ${userTableQuery}`;
|
||||||
|
|
||||||
|
if (canConfigureThroughput && (dedicateTableThroughput || !keyspaceHasSharedOffer)) {
|
||||||
|
if (isAutoPilotSelected && selectedAutoPilotThroughput) {
|
||||||
|
tableQuery = `${createTableQueryPrefix} WITH ${autoPilotCommand}=${throughput};`;
|
||||||
|
} else {
|
||||||
|
tableQuery = `${createTableQueryPrefix} WITH cosmosdb_provisioned_throughput=${throughput};`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tableQuery = `${createTableQueryPrefix};`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const addCollectionPaneStartMessage = {
|
||||||
|
...addCollectionPaneOpenMessage,
|
||||||
|
collection: {
|
||||||
|
...addCollectionPaneOpenMessage.collection,
|
||||||
|
hasDedicatedThroughput: dedicateTableThroughput,
|
||||||
|
},
|
||||||
|
keyspaceHasSharedOffer,
|
||||||
|
toCreateKeyspace,
|
||||||
|
createKeyspaceQuery,
|
||||||
|
createTableQuery: tableQuery,
|
||||||
|
};
|
||||||
|
|
||||||
|
const startKey: number = TelemetryProcessor.traceStart(Action.CreateCollection, addCollectionPaneStartMessage);
|
||||||
|
try {
|
||||||
|
if (toCreateKeyspace) {
|
||||||
|
await cassandraApiClient.createTableAndKeyspace(
|
||||||
|
userContext?.databaseAccount?.properties?.cassandraEndpoint,
|
||||||
|
userContext?.databaseAccount?.id,
|
||||||
|
container,
|
||||||
|
tableQuery,
|
||||||
|
createKeyspaceQuery
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await cassandraApiClient.createTableAndKeyspace(
|
||||||
|
userContext?.databaseAccount?.properties?.cassandraEndpoint,
|
||||||
|
userContext?.databaseAccount?.id,
|
||||||
|
container,
|
||||||
|
tableQuery
|
||||||
|
);
|
||||||
|
}
|
||||||
|
container.refreshAllDatabases();
|
||||||
|
setIsExecuting(false);
|
||||||
|
closePanel();
|
||||||
|
|
||||||
|
TelemetryProcessor.traceSuccess(Action.CreateCollection, addCollectionPaneStartMessage, startKey);
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = getErrorMessage(error);
|
||||||
|
setFormErrors(errorMessage);
|
||||||
|
setIsExecuting(false);
|
||||||
|
const addCollectionPaneFailedMessage = {
|
||||||
|
...addCollectionPaneStartMessage,
|
||||||
|
error: errorMessage,
|
||||||
|
errorStack: getErrorStack(error),
|
||||||
|
};
|
||||||
|
TelemetryProcessor.traceFailure(Action.CreateCollection, addCollectionPaneFailedMessage, startKey);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleOnChangeKeyspaceType = (ev: React.FormEvent<HTMLInputElement>, mode: string): void => {
|
||||||
|
setKeyspaceCreateNew(mode === "Create new");
|
||||||
|
};
|
||||||
|
|
||||||
|
const props: RightPaneFormProps = {
|
||||||
|
expandConsole: () => container.expandConsole(),
|
||||||
|
formError: formErrors,
|
||||||
|
isExecuting,
|
||||||
|
submitButtonText: "Apply",
|
||||||
|
onSubmit,
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<RightPaneForm {...props}>
|
||||||
|
<div className="paneMainContent">
|
||||||
|
<div className="seconddivpadding">
|
||||||
|
<p>
|
||||||
|
<Label required>
|
||||||
|
Keyspace name <InfoTooltip>Select an existing keyspace or enter a new keyspace id.</InfoTooltip>
|
||||||
|
</Label>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Stack horizontal verticalAlign="center">
|
||||||
|
<input
|
||||||
|
className="throughputInputRadioBtn"
|
||||||
|
aria-label="Create new keyspace"
|
||||||
|
checked={keyspaceCreateNew}
|
||||||
|
type="radio"
|
||||||
|
role="radio"
|
||||||
|
tabIndex={0}
|
||||||
|
onChange={(e) => handleOnChangeKeyspaceType(e, "Create new")}
|
||||||
|
/>
|
||||||
|
<span className="throughputInputRadioBtnLabel">Create new</span>
|
||||||
|
|
||||||
|
<input
|
||||||
|
className="throughputInputRadioBtn"
|
||||||
|
aria-label="Use existing keyspace"
|
||||||
|
checked={!keyspaceCreateNew}
|
||||||
|
type="radio"
|
||||||
|
role="radio"
|
||||||
|
tabIndex={0}
|
||||||
|
onChange={(e) => handleOnChangeKeyspaceType(e, "Use existing")}
|
||||||
|
/>
|
||||||
|
<span className="throughputInputRadioBtnLabel">Use existing</span>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
aria-required="true"
|
||||||
|
autoComplete="off"
|
||||||
|
pattern="[^/?#\\]*[^/?# \\]"
|
||||||
|
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
||||||
|
list={keyspaceCreateNew ? "" : "keyspacesList"}
|
||||||
|
placeholder={keyspaceCreateNew ? "Type a new keyspace id" : "Choose existing keyspace id"}
|
||||||
|
size={40}
|
||||||
|
data-test="addCollection-keyspaceId"
|
||||||
|
value={keyspaceId}
|
||||||
|
onChange={(e, newValue) => setKeyspaceId(newValue)}
|
||||||
|
ariaLabel="Keyspace id"
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
<datalist id="keyspacesList">
|
||||||
|
{keyspaceIds?.map((id: string, index: number) => (
|
||||||
|
<option key={index}>{id}</option>
|
||||||
|
))}
|
||||||
|
</datalist>
|
||||||
|
{canConfigureThroughput && keyspaceCreateNew && (
|
||||||
|
<div className="databaseProvision">
|
||||||
|
<input
|
||||||
|
tabIndex={0}
|
||||||
|
type="checkbox"
|
||||||
|
id="keyspaceSharedThroughput"
|
||||||
|
title="Provision shared throughput"
|
||||||
|
checked={keyspaceHasSharedOffer}
|
||||||
|
onChange={(e) => setKeyspaceHasSharedOffer(e.target.checked)}
|
||||||
|
/>
|
||||||
|
<span className="databaseProvisionText" aria-label="Provision keyspace throughput">
|
||||||
|
Provision keyspace throughput
|
||||||
|
</span>
|
||||||
|
<InfoTooltip>
|
||||||
|
Provisioned throughput at the keyspace level will be shared across unlimited number of tables within the
|
||||||
|
keyspace
|
||||||
|
</InfoTooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{canConfigureThroughput && keyspaceCreateNew && keyspaceHasSharedOffer && (
|
||||||
|
<div>
|
||||||
|
<ThroughputInput
|
||||||
|
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container.isFirstResourceCreated()}
|
||||||
|
isDatabase
|
||||||
|
isSharded
|
||||||
|
setThroughputValue={(throughput: number) => setKeyspaceThroughput(throughput)}
|
||||||
|
setIsAutoscale={(isAutoscale: boolean) => setIsSharedAutoPilotSelected(isAutoscale)}
|
||||||
|
onCostAcknowledgeChange={(isAcknowledge: boolean) => {
|
||||||
|
setSharedThroughputSpendAck(isAcknowledge);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="seconddivpadding">
|
||||||
|
<p>
|
||||||
|
<Label required>
|
||||||
|
Enter CQL command to create the table.
|
||||||
|
<a href="https://aka.ms/cassandra-create-table" target="_blank" rel="noreferrer">
|
||||||
|
Learn More
|
||||||
|
</a>
|
||||||
|
</Label>
|
||||||
|
</p>
|
||||||
|
<div aria-label={createTableQuery} style={{ float: "left", paddingTop: "3px", paddingRight: "3px" }}>
|
||||||
|
{createTableQuery}
|
||||||
|
</div>
|
||||||
|
<TextField
|
||||||
|
aria-required="true"
|
||||||
|
ariaLabel="addCollection-tableId"
|
||||||
|
autoComplete="off"
|
||||||
|
pattern="[^/?#\\]*[^/?# \\]"
|
||||||
|
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
||||||
|
placeholder="Enter tableId"
|
||||||
|
size={20}
|
||||||
|
className="textfontclr"
|
||||||
|
value={tableId}
|
||||||
|
onChange={(e, newValue) => setTableId(newValue)}
|
||||||
|
style={{ marginBottom: "5px" }}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
multiline
|
||||||
|
id="editor-area"
|
||||||
|
rows={5}
|
||||||
|
aria-label="Table Schema"
|
||||||
|
value={userTableQuery}
|
||||||
|
onChange={(e, newValue) => setUserTableQuery(newValue)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{canConfigureThroughput && keyspaceHasSharedOffer && !keyspaceCreateNew && (
|
||||||
|
<div className="seconddivpadding">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="tableSharedThroughput"
|
||||||
|
title="Provision dedicated throughput for this table"
|
||||||
|
checked={dedicateTableThroughput}
|
||||||
|
onChange={(e) => setDedicateTableThroughput(e.target.checked)}
|
||||||
|
/>
|
||||||
|
<span>Provision dedicated throughput for this table</span>
|
||||||
|
<InfoTooltip>
|
||||||
|
You can optionally provision dedicated throughput for a table within a keyspace that has throughput
|
||||||
|
provisioned. This dedicated throughput amount will not be shared with other tables in the keyspace and
|
||||||
|
does not count towards the throughput you provisioned for the keyspace. This throughput amount will be
|
||||||
|
billed in addition to the throughput amount you provisioned at the keyspace level.
|
||||||
|
</InfoTooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{canConfigureThroughput && (!keyspaceHasSharedOffer || dedicateTableThroughput) && (
|
||||||
|
<div>
|
||||||
|
<ThroughputInput
|
||||||
|
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container.isFirstResourceCreated()}
|
||||||
|
isDatabase={false}
|
||||||
|
isSharded={false}
|
||||||
|
setThroughputValue={(throughput: number) => setThroughput(throughput)}
|
||||||
|
setIsAutoscale={(isAutoscale: boolean) => setIsAutoPilotSelected(isAutoscale)}
|
||||||
|
onCostAcknowledgeChange={(isAcknowledge: boolean) => {
|
||||||
|
setThroughputSpendAck(isAcknowledge);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</RightPaneForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
+164
@@ -0,0 +1,164 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`CassandraAddCollectionPane Pane should render Default properly 1`] = `
|
||||||
|
<RightPaneForm
|
||||||
|
expandConsole={[Function]}
|
||||||
|
formError=""
|
||||||
|
onSubmit={[Function]}
|
||||||
|
submitButtonText="Apply"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="paneMainContent"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="seconddivpadding"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<StyledLabelBase
|
||||||
|
required={true}
|
||||||
|
>
|
||||||
|
Keyspace name
|
||||||
|
<InfoTooltip>
|
||||||
|
Select an existing keyspace or enter a new keyspace id.
|
||||||
|
</InfoTooltip>
|
||||||
|
</StyledLabelBase>
|
||||||
|
</p>
|
||||||
|
<Stack
|
||||||
|
horizontal={true}
|
||||||
|
verticalAlign="center"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-label="Create new keyspace"
|
||||||
|
checked={true}
|
||||||
|
className="throughputInputRadioBtn"
|
||||||
|
onChange={[Function]}
|
||||||
|
role="radio"
|
||||||
|
tabIndex={0}
|
||||||
|
type="radio"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="throughputInputRadioBtnLabel"
|
||||||
|
>
|
||||||
|
Create new
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
aria-label="Use existing keyspace"
|
||||||
|
checked={false}
|
||||||
|
className="throughputInputRadioBtn"
|
||||||
|
onChange={[Function]}
|
||||||
|
role="radio"
|
||||||
|
tabIndex={0}
|
||||||
|
type="radio"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="throughputInputRadioBtnLabel"
|
||||||
|
>
|
||||||
|
Use existing
|
||||||
|
</span>
|
||||||
|
</Stack>
|
||||||
|
<StyledTextFieldBase
|
||||||
|
aria-required="true"
|
||||||
|
ariaLabel="Keyspace id"
|
||||||
|
autoComplete="off"
|
||||||
|
autoFocus={true}
|
||||||
|
data-test="addCollection-keyspaceId"
|
||||||
|
list=""
|
||||||
|
onChange={[Function]}
|
||||||
|
pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]"
|
||||||
|
placeholder="Type a new keyspace id"
|
||||||
|
size={40}
|
||||||
|
title="May not end with space nor contain characters '\\\\' '/' '#' '?'"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<datalist
|
||||||
|
id="keyspacesList"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="databaseProvision"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked={false}
|
||||||
|
id="keyspaceSharedThroughput"
|
||||||
|
onChange={[Function]}
|
||||||
|
tabIndex={0}
|
||||||
|
title="Provision shared throughput"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
aria-label="Provision keyspace throughput"
|
||||||
|
className="databaseProvisionText"
|
||||||
|
>
|
||||||
|
Provision keyspace throughput
|
||||||
|
</span>
|
||||||
|
<InfoTooltip>
|
||||||
|
Provisioned throughput at the keyspace level will be shared across unlimited number of tables within the keyspace
|
||||||
|
</InfoTooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="seconddivpadding"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<StyledLabelBase
|
||||||
|
required={true}
|
||||||
|
>
|
||||||
|
Enter CQL command to create the table.
|
||||||
|
<a
|
||||||
|
href="https://aka.ms/cassandra-create-table"
|
||||||
|
rel="noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Learn More
|
||||||
|
</a>
|
||||||
|
</StyledLabelBase>
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
aria-label="CREATE TABLE "
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"float": "left",
|
||||||
|
"paddingRight": "3px",
|
||||||
|
"paddingTop": "3px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
CREATE TABLE
|
||||||
|
</div>
|
||||||
|
<StyledTextFieldBase
|
||||||
|
aria-required="true"
|
||||||
|
ariaLabel="addCollection-tableId"
|
||||||
|
autoComplete="off"
|
||||||
|
className="textfontclr"
|
||||||
|
onChange={[Function]}
|
||||||
|
pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]"
|
||||||
|
placeholder="Enter tableId"
|
||||||
|
size={20}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"marginBottom": "5px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
title="May not end with space nor contain characters '\\\\' '/' '#' '?'"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<StyledTextFieldBase
|
||||||
|
aria-label="Table Schema"
|
||||||
|
id="editor-area"
|
||||||
|
multiline={true}
|
||||||
|
onChange={[Function]}
|
||||||
|
rows={5}
|
||||||
|
value="(userid int, name text, email text, PRIMARY KEY (userid))"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ThroughputInput
|
||||||
|
isDatabase={false}
|
||||||
|
isSharded={false}
|
||||||
|
onCostAcknowledgeChange={[Function]}
|
||||||
|
setIsAutoscale={[Function]}
|
||||||
|
setThroughputValue={[Function]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</RightPaneForm>
|
||||||
|
`;
|
||||||
@@ -9,10 +9,7 @@ import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUti
|
|||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { NotebookContentItem, NotebookContentItemType } from "../../Notebook/NotebookContentItem";
|
import { NotebookContentItem, NotebookContentItemType } from "../../Notebook/NotebookContentItem";
|
||||||
import { ResourceTreeAdapter } from "../../Tree/ResourceTreeAdapter";
|
import { ResourceTreeAdapter } from "../../Tree/ResourceTreeAdapter";
|
||||||
import {
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
GenericRightPaneComponent,
|
|
||||||
GenericRightPaneProps,
|
|
||||||
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
|
|
||||||
import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent";
|
import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent";
|
||||||
|
|
||||||
interface Location {
|
interface Location {
|
||||||
@@ -42,7 +39,6 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
|||||||
}: CopyNotebookPanelProps) => {
|
}: CopyNotebookPanelProps) => {
|
||||||
const [isExecuting, setIsExecuting] = useState<boolean>();
|
const [isExecuting, setIsExecuting] = useState<boolean>();
|
||||||
const [formError, setFormError] = useState<string>("");
|
const [formError, setFormError] = useState<string>("");
|
||||||
const [formErrorDetail, setFormErrorDetail] = useState<string>("");
|
|
||||||
const [pinnedRepos, setPinnedRepos] = useState<IPinnedRepo[]>();
|
const [pinnedRepos, setPinnedRepos] = useState<IPinnedRepo[]>();
|
||||||
const [selectedLocation, setSelectedLocation] = useState<Location>();
|
const [selectedLocation, setSelectedLocation] = useState<Location>();
|
||||||
|
|
||||||
@@ -92,7 +88,6 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = getErrorMessage(error);
|
const errorMessage = getErrorMessage(error);
|
||||||
setFormError(`Failed to copy ${name} to ${destination}`);
|
setFormError(`Failed to copy ${name} to ${destination}`);
|
||||||
setFormErrorDetail(`${errorMessage}`);
|
|
||||||
handleError(errorMessage, "CopyNotebookPaneAdapter/submit", formError);
|
handleError(errorMessage, "CopyNotebookPaneAdapter/submit", formError);
|
||||||
} finally {
|
} finally {
|
||||||
clearMessage && clearMessage();
|
clearMessage && clearMessage();
|
||||||
@@ -130,14 +125,10 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
|||||||
setSelectedLocation(option?.data);
|
setSelectedLocation(option?.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const genericPaneProps: GenericRightPaneProps = {
|
const props: RightPaneFormProps = {
|
||||||
formError,
|
formError,
|
||||||
formErrorDetail,
|
|
||||||
id: "copynotebookpane",
|
|
||||||
isExecuting: isExecuting,
|
isExecuting: isExecuting,
|
||||||
title: "Copy notebook",
|
|
||||||
submitButtonText: "OK",
|
submitButtonText: "OK",
|
||||||
onClose: closePanel,
|
|
||||||
onSubmit: () => submit(),
|
onSubmit: () => submit(),
|
||||||
expandConsole: () => container.expandConsole(),
|
expandConsole: () => container.expandConsole(),
|
||||||
};
|
};
|
||||||
@@ -149,8 +140,8 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GenericRightPaneComponent {...genericPaneProps}>
|
<RightPaneForm {...props}>
|
||||||
<CopyNotebookPaneComponent {...copyNotebookPaneProps} />
|
<CopyNotebookPaneComponent {...copyNotebookPaneProps} />
|
||||||
</GenericRightPaneComponent>
|
</RightPaneForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+4
-4
@@ -130,8 +130,8 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
.hostNodes()
|
.hostNodes()
|
||||||
.simulate("change", { target: { value: selectedCollectionId } });
|
.simulate("change", { target: { value: selectedCollectionId } });
|
||||||
|
|
||||||
expect(wrapper.exists(".genericPaneSubmitBtn")).toBe(true);
|
expect(wrapper.exists("#sidePanelOkButton")).toBe(true);
|
||||||
wrapper.find(".genericPaneSubmitBtn").hostNodes().simulate("click");
|
wrapper.find("#sidePanelOkButton").hostNodes().simulate("submit");
|
||||||
expect(deleteCollection).toHaveBeenCalledWith(databaseId, selectedCollectionId);
|
expect(deleteCollection).toHaveBeenCalledWith(databaseId, selectedCollectionId);
|
||||||
|
|
||||||
wrapper.unmount();
|
wrapper.unmount();
|
||||||
@@ -151,8 +151,8 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
.hostNodes()
|
.hostNodes()
|
||||||
.simulate("change", { target: { value: feedbackText } });
|
.simulate("change", { target: { value: feedbackText } });
|
||||||
|
|
||||||
expect(wrapper.exists(".genericPaneSubmitBtn")).toBe(true);
|
expect(wrapper.exists("#sidePanelOkButton")).toBe(true);
|
||||||
wrapper.find(".genericPaneSubmitBtn").hostNodes().simulate("click");
|
wrapper.find("#sidePanelOkButton").hostNodes().simulate("submit");
|
||||||
expect(deleteCollection).toHaveBeenCalledWith(databaseId, selectedCollectionId);
|
expect(deleteCollection).toHaveBeenCalledWith(databaseId, selectedCollectionId);
|
||||||
|
|
||||||
const deleteFeedback = new DeleteFeedback(
|
const deleteFeedback = new DeleteFeedback(
|
||||||
|
|||||||
+6
-13
@@ -12,10 +12,7 @@ import { userContext } from "../../../UserContext";
|
|||||||
import { getCollectionName } from "../../../Utils/APITypeUtils";
|
import { getCollectionName } from "../../../Utils/APITypeUtils";
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import {
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
GenericRightPaneComponent,
|
|
||||||
GenericRightPaneProps,
|
|
||||||
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
|
|
||||||
export interface DeleteCollectionConfirmationPaneProps {
|
export interface DeleteCollectionConfirmationPaneProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
closePanel: () => void;
|
closePanel: () => void;
|
||||||
@@ -35,7 +32,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
};
|
};
|
||||||
const collectionName = getCollectionName().toLocaleLowerCase();
|
const collectionName = getCollectionName().toLocaleLowerCase();
|
||||||
const paneTitle = "Delete " + collectionName;
|
const paneTitle = "Delete " + collectionName;
|
||||||
const submit = async (): Promise<void> => {
|
const onSubmit = async (): Promise<void> => {
|
||||||
const collection = explorer.findSelectedCollection();
|
const collection = explorer.findSelectedCollection();
|
||||||
if (!collection || inputCollectionName !== collection.id()) {
|
if (!collection || inputCollectionName !== collection.id()) {
|
||||||
const errorMessage = "Input " + collectionName + " name does not match the selected " + collectionName;
|
const errorMessage = "Input " + collectionName + " name does not match the selected " + collectionName;
|
||||||
@@ -100,19 +97,15 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const genericPaneProps: GenericRightPaneProps = {
|
const props: RightPaneFormProps = {
|
||||||
formError: formError,
|
formError: formError,
|
||||||
formErrorDetail: formError,
|
|
||||||
id: "deleteCollectionpane",
|
|
||||||
isExecuting,
|
isExecuting,
|
||||||
title: paneTitle,
|
|
||||||
submitButtonText: "OK",
|
submitButtonText: "OK",
|
||||||
onClose: closePanel,
|
onSubmit,
|
||||||
onSubmit: submit,
|
|
||||||
expandConsole: () => explorer.expandConsole(),
|
expandConsole: () => explorer.expandConsole(),
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<GenericRightPaneComponent {...genericPaneProps}>
|
<RightPaneForm {...props}>
|
||||||
<div className="panelFormWrapper">
|
<div className="panelFormWrapper">
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
<div className="confirmDeleteInput">
|
<div className="confirmDeleteInput">
|
||||||
@@ -150,6 +143,6 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GenericRightPaneComponent>
|
</RightPaneForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+43
-1241
File diff suppressed because it is too large
Load Diff
@@ -98,8 +98,8 @@ describe("Delete Database Confirmation Pane", () => {
|
|||||||
.find("#confirmDatabaseId")
|
.find("#confirmDatabaseId")
|
||||||
.hostNodes()
|
.hostNodes()
|
||||||
.simulate("change", { target: { value: selectedDatabaseId } });
|
.simulate("change", { target: { value: selectedDatabaseId } });
|
||||||
expect(wrapper.exists("#sidePanelOkButton")).toBe(true);
|
expect(wrapper.exists("button")).toBe(true);
|
||||||
wrapper.find("#sidePanelOkButton").hostNodes().simulate("submit");
|
wrapper.find("button").hostNodes().simulate("submit");
|
||||||
expect(deleteDatabase).toHaveBeenCalledWith(selectedDatabaseId);
|
expect(deleteDatabase).toHaveBeenCalledWith(selectedDatabaseId);
|
||||||
wrapper.unmount();
|
wrapper.unmount();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useBoolean } from "@fluentui/react-hooks";
|
|
||||||
import { Text, TextField } from "@fluentui/react";
|
import { Text, TextField } from "@fluentui/react";
|
||||||
|
import { useBoolean } from "@fluentui/react-hooks";
|
||||||
import React, { FunctionComponent, useState } from "react";
|
import React, { FunctionComponent, useState } from "react";
|
||||||
import { Areas } from "../../Common/Constants";
|
import { Areas } from "../../Common/Constants";
|
||||||
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
|
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
|
||||||
@@ -12,9 +12,8 @@ import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
|||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { PanelFooterComponent } from "./PanelFooterComponent";
|
|
||||||
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";
|
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";
|
||||||
import { PanelLoadingScreen } from "./PanelLoadingScreen";
|
import { RightPaneForm, RightPaneFormProps } from "./RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
interface DeleteDatabaseConfirmationPanelProps {
|
interface DeleteDatabaseConfirmationPanelProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
@@ -23,36 +22,19 @@ interface DeleteDatabaseConfirmationPanelProps {
|
|||||||
selectedDatabase: Database;
|
selectedDatabase: Database;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseConfirmationPanelProps> = (
|
export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseConfirmationPanelProps> = ({
|
||||||
props: DeleteDatabaseConfirmationPanelProps
|
explorer,
|
||||||
): JSX.Element => {
|
openNotificationConsole,
|
||||||
|
closePanel,
|
||||||
|
selectedDatabase,
|
||||||
|
}: DeleteDatabaseConfirmationPanelProps): JSX.Element => {
|
||||||
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
|
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
|
||||||
|
|
||||||
const [formError, setFormError] = useState<string>("");
|
const [formError, setFormError] = useState<string>("");
|
||||||
const [databaseInput, setDatabaseInput] = useState<string>("");
|
const [databaseInput, setDatabaseInput] = useState<string>("");
|
||||||
const [databaseFeedbackInput, setDatabaseFeedbackInput] = useState<string>("");
|
const [databaseFeedbackInput, setDatabaseFeedbackInput] = useState<string>("");
|
||||||
|
|
||||||
const getPanelErrorProps = (): PanelInfoErrorProps => {
|
const submit = async (): Promise<void> => {
|
||||||
if (formError) {
|
|
||||||
return {
|
|
||||||
messageType: "error",
|
|
||||||
message: formError,
|
|
||||||
showErrorDetails: true,
|
|
||||||
openNotificationConsole: props.openNotificationConsole,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
messageType: "warning",
|
|
||||||
showErrorDetails: false,
|
|
||||||
message:
|
|
||||||
"Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.",
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const submit = async (event: React.FormEvent<HTMLFormElement>): Promise<void> => {
|
|
||||||
const { selectedDatabase, explorer } = props;
|
|
||||||
event.preventDefault();
|
|
||||||
if (selectedDatabase?.id() && databaseInput !== selectedDatabase.id()) {
|
if (selectedDatabase?.id() && databaseInput !== selectedDatabase.id()) {
|
||||||
setFormError("Input database name does not match the selected database");
|
setFormError("Input database name does not match the selected database");
|
||||||
logConsoleError(`Error while deleting collection ${selectedDatabase && selectedDatabase.id()}`);
|
logConsoleError(`Error while deleting collection ${selectedDatabase && selectedDatabase.id()}`);
|
||||||
@@ -69,7 +51,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await deleteDatabase(selectedDatabase.id());
|
await deleteDatabase(selectedDatabase.id());
|
||||||
props.closePanel();
|
closePanel();
|
||||||
explorer.refreshAllDatabases();
|
explorer.refreshAllDatabases();
|
||||||
explorer.tabsManager.closeTabsByComparator((tab) => tab.node?.id() === selectedDatabase.id());
|
explorer.tabsManager.closeTabsByComparator((tab) => tab.node?.id() === selectedDatabase.id());
|
||||||
explorer.selectedNode(undefined);
|
explorer.selectedNode(undefined);
|
||||||
@@ -121,13 +103,27 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
};
|
};
|
||||||
|
|
||||||
const shouldRecordFeedback = (): boolean => {
|
const shouldRecordFeedback = (): boolean => {
|
||||||
const { explorer } = props;
|
|
||||||
return explorer.isLastNonEmptyDatabase() || (explorer.isLastDatabase() && explorer.isSelectedDatabaseShared());
|
return explorer.isLastNonEmptyDatabase() || (explorer.isLastDatabase() && explorer.isSelectedDatabaseShared());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const props: RightPaneFormProps = {
|
||||||
|
formError,
|
||||||
|
isExecuting: isLoading,
|
||||||
|
submitButtonText: "OK",
|
||||||
|
onSubmit: () => submit(),
|
||||||
|
expandConsole: openNotificationConsole,
|
||||||
|
};
|
||||||
|
|
||||||
|
const errorProps: PanelInfoErrorProps = {
|
||||||
|
messageType: "warning",
|
||||||
|
showErrorDetails: false,
|
||||||
|
message:
|
||||||
|
"Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.",
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="panelFormWrapper" onSubmit={submit}>
|
<RightPaneForm {...props}>
|
||||||
<PanelInfoErrorComponent {...getPanelErrorProps()} />
|
{!formError && <PanelInfoErrorComponent {...errorProps} />}
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
<div className="confirmDeleteInput">
|
<div className="confirmDeleteInput">
|
||||||
<span className="mandatoryStar">* </span>
|
<span className="mandatoryStar">* </span>
|
||||||
@@ -161,8 +157,6 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<PanelFooterComponent buttonLabel="OK" />
|
</RightPaneForm>
|
||||||
{isLoading && <PanelLoadingScreen />}
|
|
||||||
</form>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { useBoolean } from "@fluentui/react-hooks";
|
|
||||||
import { IDropdownOption, IImageProps, Image, Stack, Text } from "@fluentui/react";
|
import { IDropdownOption, IImageProps, Image, Stack, Text } from "@fluentui/react";
|
||||||
|
import { useBoolean } from "@fluentui/react-hooks";
|
||||||
import React, { FunctionComponent, useState } from "react";
|
import React, { FunctionComponent, useState } from "react";
|
||||||
import AddPropertyIcon from "../../../../images/Add-property.svg";
|
import AddPropertyIcon from "../../../../images/Add-property.svg";
|
||||||
|
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||||
import StoredProcedure from "../../Tree/StoredProcedure";
|
import StoredProcedure from "../../Tree/StoredProcedure";
|
||||||
import {
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
GenericRightPaneComponent,
|
|
||||||
GenericRightPaneProps,
|
|
||||||
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
|
|
||||||
import { InputParameter } from "./InputParameter";
|
import { InputParameter } from "./InputParameter";
|
||||||
|
|
||||||
interface ExecuteSprocParamsPaneProps {
|
interface ExecuteSprocParamsPaneProps {
|
||||||
@@ -35,24 +33,11 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
|
|||||||
const [partitionValue, setPartitionValue] = useState<string>(); // Defaulting to undefined here is important. It is not the same partition key as ""
|
const [partitionValue, setPartitionValue] = useState<string>(); // Defaulting to undefined here is important. It is not the same partition key as ""
|
||||||
const [selectedKey, setSelectedKey] = React.useState<IDropdownOption>({ key: "string", text: "" });
|
const [selectedKey, setSelectedKey] = React.useState<IDropdownOption>({ key: "string", text: "" });
|
||||||
const [formError, setFormError] = useState<string>("");
|
const [formError, setFormError] = useState<string>("");
|
||||||
const [formErrorsDetails, setFormErrorsDetails] = useState<string>("");
|
|
||||||
|
|
||||||
const onPartitionKeyChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
|
const onPartitionKeyChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
|
||||||
setSelectedKey(item);
|
setSelectedKey(item);
|
||||||
};
|
};
|
||||||
|
|
||||||
const genericPaneProps: GenericRightPaneProps = {
|
|
||||||
expandConsole,
|
|
||||||
formError: formError,
|
|
||||||
formErrorDetail: formErrorsDetails,
|
|
||||||
id: "executesprocparamspane",
|
|
||||||
isExecuting: isLoading,
|
|
||||||
title: "Input parameters",
|
|
||||||
submitButtonText: "Execute",
|
|
||||||
onClose: () => closePanel(),
|
|
||||||
onSubmit: () => submit(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const validateUnwrappedParams = (): boolean => {
|
const validateUnwrappedParams = (): boolean => {
|
||||||
const unwrappedParams: UnwrappedExecuteSprocParam[] = paramKeyValues;
|
const unwrappedParams: UnwrappedExecuteSprocParam[] = paramKeyValues;
|
||||||
for (let i = 0; i < unwrappedParams.length; i++) {
|
for (let i = 0; i < unwrappedParams.length; i++) {
|
||||||
@@ -66,7 +51,7 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
|
|||||||
|
|
||||||
const setInvalidParamError = (invalidParam: string): void => {
|
const setInvalidParamError = (invalidParam: string): void => {
|
||||||
setFormError(`Invalid param specified: ${invalidParam}`);
|
setFormError(`Invalid param specified: ${invalidParam}`);
|
||||||
setFormErrorsDetails(`Invalid param specified: ${invalidParam} is not a valid literal value`);
|
logConsoleError(`Invalid param specified: ${invalidParam} is not a valid literal value`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const submit = (): void => {
|
const submit = (): void => {
|
||||||
@@ -128,8 +113,16 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
|
|||||||
setParamKeyValues(cloneParamKeyValue);
|
setParamKeyValues(cloneParamKeyValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const props: RightPaneFormProps = {
|
||||||
|
expandConsole,
|
||||||
|
formError: formError,
|
||||||
|
isExecuting: isLoading,
|
||||||
|
submitButtonText: "Execute",
|
||||||
|
onSubmit: () => submit(),
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GenericRightPaneComponent {...genericPaneProps}>
|
<RightPaneForm {...props}>
|
||||||
<div className="panelFormWrapper">
|
<div className="panelFormWrapper">
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
<InputParameter
|
<InputParameter
|
||||||
@@ -169,6 +162,6 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
|
|||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GenericRightPaneComponent>
|
</RightPaneForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+88
-1286
File diff suppressed because it is too large
Load Diff
@@ -1,126 +0,0 @@
|
|||||||
import { IconButton, PrimaryButton } from "@fluentui/react";
|
|
||||||
import React, { FunctionComponent, ReactNode } from "react";
|
|
||||||
import ErrorRedIcon from "../../../../images/error_red.svg";
|
|
||||||
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
|
|
||||||
import { KeyCodes } from "../../../Common/Constants";
|
|
||||||
|
|
||||||
export interface GenericRightPaneProps {
|
|
||||||
expandConsole: () => void;
|
|
||||||
formError: string;
|
|
||||||
formErrorDetail: string;
|
|
||||||
id: string;
|
|
||||||
isExecuting: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
onSubmit: () => void;
|
|
||||||
submitButtonText: string;
|
|
||||||
title: string;
|
|
||||||
isSubmitButtonHidden?: boolean;
|
|
||||||
children?: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GenericRightPaneComponent: FunctionComponent<GenericRightPaneProps> = ({
|
|
||||||
expandConsole,
|
|
||||||
formError,
|
|
||||||
formErrorDetail,
|
|
||||||
id,
|
|
||||||
isExecuting,
|
|
||||||
onClose,
|
|
||||||
onSubmit,
|
|
||||||
submitButtonText,
|
|
||||||
title,
|
|
||||||
isSubmitButtonHidden,
|
|
||||||
children,
|
|
||||||
}: GenericRightPaneProps) => {
|
|
||||||
const getPanelHeight = (): number => {
|
|
||||||
const notificationConsoleElement: HTMLElement = document.getElementById("explorerNotificationConsole");
|
|
||||||
return window.innerHeight - $(notificationConsoleElement).height();
|
|
||||||
};
|
|
||||||
|
|
||||||
const panelHeight: number = getPanelHeight();
|
|
||||||
|
|
||||||
const renderPanelHeader = (): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<div className="firstdivbg headerline">
|
|
||||||
<span id="databaseTitle" role="heading" aria-level={2}>
|
|
||||||
{title}
|
|
||||||
</span>
|
|
||||||
<IconButton
|
|
||||||
ariaLabel="Close pane"
|
|
||||||
title="Close pane"
|
|
||||||
onClick={onClose}
|
|
||||||
tabIndex={0}
|
|
||||||
className="closePaneBtn"
|
|
||||||
iconProps={{ iconName: "Cancel" }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderErrorSection = (): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<div className="warningErrorContainer" aria-live="assertive" hidden={!formError}>
|
|
||||||
<div className="warningErrorContent">
|
|
||||||
<span>
|
|
||||||
<img className="paneErrorIcon" src={ErrorRedIcon} alt="Error" />
|
|
||||||
</span>
|
|
||||||
<span className="warningErrorDetailsLinkContainer">
|
|
||||||
<span className="formErrors" title={formError}>
|
|
||||||
{formError}
|
|
||||||
</span>
|
|
||||||
<a className="errorLink" role="link" hidden={!formErrorDetail} onClick={expandConsole}>
|
|
||||||
More details
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderPanelFooter = (): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<div className="paneFooter">
|
|
||||||
<div className="leftpanel-okbut">
|
|
||||||
<PrimaryButton
|
|
||||||
style={{ visibility: isSubmitButtonHidden ? "hidden" : "visible" }}
|
|
||||||
ariaLabel="Submit"
|
|
||||||
title="Submit"
|
|
||||||
onClick={onSubmit}
|
|
||||||
tabIndex={0}
|
|
||||||
className="genericPaneSubmitBtn"
|
|
||||||
text={submitButtonText}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderLoadingScreen = (): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<div className="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer" hidden={!isExecuting}>
|
|
||||||
<img className="dataExplorerLoader" src={LoadingIndicatorIcon} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {
|
|
||||||
if (event.keyCode === KeyCodes.Escape) {
|
|
||||||
onClose();
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div tabIndex={-1} onKeyDown={onKeyDown}>
|
|
||||||
<div className="contextual-pane-out" onClick={onClose}></div>
|
|
||||||
<div className="contextual-pane" id={id} style={{ height: panelHeight }} onKeyDown={onKeyDown}>
|
|
||||||
<div className="panelContentWrapper">
|
|
||||||
{renderPanelHeader()}
|
|
||||||
{renderErrorSection()}
|
|
||||||
{children}
|
|
||||||
{renderPanelFooter()}
|
|
||||||
</div>
|
|
||||||
{renderLoadingScreen()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -27,51 +27,6 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
|
|||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
|
||||||
"autoPilotUsageCost": [Function],
|
|
||||||
"canConfigureThroughput": [Function],
|
|
||||||
"canExceedMaximumValue": [Function],
|
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"container": [Circular],
|
|
||||||
"costsVisible": [Function],
|
|
||||||
"createTableQuery": [Function],
|
|
||||||
"dedicateTableThroughput": [Function],
|
|
||||||
"firstFieldHasFocus": [Function],
|
|
||||||
"formErrors": [Function],
|
|
||||||
"formErrorsDetails": [Function],
|
|
||||||
"id": "cassandraaddcollectionpane",
|
|
||||||
"isAutoPilotSelected": [Function],
|
|
||||||
"isExecuting": [Function],
|
|
||||||
"isFreeTierAccount": [Function],
|
|
||||||
"isSharedAutoPilotSelected": [Function],
|
|
||||||
"isTemplateReady": [Function],
|
|
||||||
"keyspaceCreateNew": [Function],
|
|
||||||
"keyspaceHasSharedOffer": [Function],
|
|
||||||
"keyspaceId": [Function],
|
|
||||||
"keyspaceIds": [Function],
|
|
||||||
"keyspaceOffers": Map {},
|
|
||||||
"keyspaceThroughput": [Function],
|
|
||||||
"maxThroughputRU": [Function],
|
|
||||||
"minThroughputRU": [Function],
|
|
||||||
"requestUnitsUsageCostDedicated": [Function],
|
|
||||||
"requestUnitsUsageCostShared": [Function],
|
|
||||||
"ruToolTipText": [Function],
|
|
||||||
"selectedAutoPilotThroughput": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
|
||||||
"sharedThroughputSpendAck": [Function],
|
|
||||||
"sharedThroughputSpendAckText": [Function],
|
|
||||||
"sharedThroughputSpendAckVisible": [Function],
|
|
||||||
"tableId": [Function],
|
|
||||||
"throughput": [Function],
|
|
||||||
"throughputRangeText": [Function],
|
|
||||||
"throughputSpendAck": [Function],
|
|
||||||
"throughputSpendAckText": [Function],
|
|
||||||
"throughputSpendAckVisible": [Function],
|
|
||||||
"title": [Function],
|
|
||||||
"userTableQuery": [Function],
|
|
||||||
"visible": [Function],
|
|
||||||
},
|
|
||||||
"closeDialog": undefined,
|
"closeDialog": undefined,
|
||||||
"closeSidePanel": undefined,
|
"closeSidePanel": undefined,
|
||||||
"collapsedResourceTreeWidth": 36,
|
"collapsedResourceTreeWidth": 36,
|
||||||
@@ -876,6 +831,7 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
|
|||||||
"isResourceTokenCollectionNodeSelected": [Function],
|
"isResourceTokenCollectionNodeSelected": [Function],
|
||||||
"isSchemaEnabled": [Function],
|
"isSchemaEnabled": [Function],
|
||||||
"isServerlessEnabled": [Function],
|
"isServerlessEnabled": [Function],
|
||||||
|
"isShellEnabled": [Function],
|
||||||
"isSparkEnabled": [Function],
|
"isSparkEnabled": [Function],
|
||||||
"isSparkEnabledForAccount": [Function],
|
"isSparkEnabledForAccount": [Function],
|
||||||
"isSynapseLinkUpdating": [Function],
|
"isSynapseLinkUpdating": [Function],
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useBoolean } from "@fluentui/react-hooks";
|
|
||||||
import { IImageProps, Image, ImageFit, Stack, TextField } from "@fluentui/react";
|
import { IImageProps, Image, ImageFit, Stack, TextField } from "@fluentui/react";
|
||||||
|
import { useBoolean } from "@fluentui/react-hooks";
|
||||||
import React, { FunctionComponent, useState } from "react";
|
import React, { FunctionComponent, useState } from "react";
|
||||||
import folderIcon from "../../../../images/folder_16x16.svg";
|
import folderIcon from "../../../../images/folder_16x16.svg";
|
||||||
import { logError } from "../../../Common/Logger";
|
import { logError } from "../../../Common/Logger";
|
||||||
@@ -8,10 +8,7 @@ import { userContext } from "../../../UserContext";
|
|||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
|
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import QueryTab from "../../Tabs/QueryTab";
|
import QueryTab from "../../Tabs/QueryTab";
|
||||||
import {
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
GenericRightPaneComponent,
|
|
||||||
GenericRightPaneProps,
|
|
||||||
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
|
|
||||||
|
|
||||||
interface LoadQueryPaneProps {
|
interface LoadQueryPaneProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
@@ -24,7 +21,6 @@ export const LoadQueryPane: FunctionComponent<LoadQueryPaneProps> = ({
|
|||||||
}: LoadQueryPaneProps): JSX.Element => {
|
}: LoadQueryPaneProps): JSX.Element => {
|
||||||
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
|
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
|
||||||
const [formError, setFormError] = useState<string>("");
|
const [formError, setFormError] = useState<string>("");
|
||||||
const [formErrorsDetails, setFormErrorsDetails] = useState<string>("");
|
|
||||||
const [selectedFileName, setSelectedFileName] = useState<string>("");
|
const [selectedFileName, setSelectedFileName] = useState<string>("");
|
||||||
const [selectedFiles, setSelectedFiles] = useState<FileList>();
|
const [selectedFiles, setSelectedFiles] = useState<FileList>();
|
||||||
|
|
||||||
@@ -35,19 +31,6 @@ export const LoadQueryPane: FunctionComponent<LoadQueryPaneProps> = ({
|
|||||||
className: "fileIcon",
|
className: "fileIcon",
|
||||||
};
|
};
|
||||||
|
|
||||||
const title = "Load Query";
|
|
||||||
const genericPaneProps: GenericRightPaneProps = {
|
|
||||||
expandConsole: () => explorer.expandConsole(),
|
|
||||||
formError: formError,
|
|
||||||
formErrorDetail: formErrorsDetails,
|
|
||||||
id: "loadQueryPane",
|
|
||||||
isExecuting: isLoading,
|
|
||||||
title,
|
|
||||||
submitButtonText: "Load",
|
|
||||||
onClose: () => closePanel(),
|
|
||||||
onSubmit: () => submit(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const onFileSelected = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
const onFileSelected = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
const { files } = e.target;
|
const { files } = e.target;
|
||||||
setSelectedFiles(files);
|
setSelectedFiles(files);
|
||||||
@@ -56,10 +39,8 @@ export const LoadQueryPane: FunctionComponent<LoadQueryPaneProps> = ({
|
|||||||
|
|
||||||
const submit = async (): Promise<void> => {
|
const submit = async (): Promise<void> => {
|
||||||
setFormError("");
|
setFormError("");
|
||||||
setFormErrorsDetails("");
|
|
||||||
if (!selectedFiles || selectedFiles.length === 0) {
|
if (!selectedFiles || selectedFiles.length === 0) {
|
||||||
setFormError("No file specified");
|
setFormError("No file specified");
|
||||||
setFormErrorsDetails("No file specified. Please input a file.");
|
|
||||||
logConsoleError("Could not load query -- No file specified. Please input a file.");
|
logConsoleError("Could not load query -- No file specified. Please input a file.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -75,7 +56,6 @@ export const LoadQueryPane: FunctionComponent<LoadQueryPaneProps> = ({
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
setLoadingFalse();
|
setLoadingFalse();
|
||||||
setFormError("Failed to load query");
|
setFormError("Failed to load query");
|
||||||
setFormErrorsDetails(`Failed to load query: ${error}`);
|
|
||||||
logConsoleError(`Failed to load query from file ${file.name}: ${error}`);
|
logConsoleError(`Failed to load query from file ${file.name}: ${error}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -100,14 +80,20 @@ export const LoadQueryPane: FunctionComponent<LoadQueryPaneProps> = ({
|
|||||||
|
|
||||||
reader.onerror = (): void => {
|
reader.onerror = (): void => {
|
||||||
setFormError("Failed to load query");
|
setFormError("Failed to load query");
|
||||||
setFormErrorsDetails(`Failed to load query`);
|
|
||||||
logConsoleError(`Failed to load query from file ${file.name}`);
|
logConsoleError(`Failed to load query from file ${file.name}`);
|
||||||
};
|
};
|
||||||
return reader.readAsText(file);
|
return reader.readAsText(file);
|
||||||
};
|
};
|
||||||
|
const props: RightPaneFormProps = {
|
||||||
|
formError: formError,
|
||||||
|
isExecuting: isLoading,
|
||||||
|
submitButtonText: "Load",
|
||||||
|
onSubmit: () => submit(),
|
||||||
|
expandConsole: () => explorer.expandConsole(),
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GenericRightPaneComponent {...genericPaneProps}>
|
<RightPaneForm {...props}>
|
||||||
<div className="panelFormWrapper">
|
<div className="panelFormWrapper">
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
@@ -132,6 +118,6 @@ export const LoadQueryPane: FunctionComponent<LoadQueryPaneProps> = ({
|
|||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GenericRightPaneComponent>
|
</RightPaneForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`Load Query Pane should render Default properly 1`] = `
|
exports[`Load Query Pane should render Default properly 1`] = `
|
||||||
<GenericRightPaneComponent
|
<RightPaneForm
|
||||||
expandConsole={[Function]}
|
expandConsole={[Function]}
|
||||||
formError=""
|
formError=""
|
||||||
formErrorDetail=""
|
|
||||||
id="loadQueryPane"
|
|
||||||
isExecuting={false}
|
isExecuting={false}
|
||||||
onClose={[Function]}
|
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
submitButtonText="Load"
|
submitButtonText="Load"
|
||||||
title="Load Query"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="panelFormWrapper"
|
className="panelFormWrapper"
|
||||||
@@ -58,5 +54,5 @@ exports[`Load Query Pane should render Default properly 1`] = `
|
|||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GenericRightPaneComponent>
|
</RightPaneForm>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { shallow, ShallowWrapper } from "enzyme";
|
import { mount, shallow, ShallowWrapper } from "enzyme";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
@@ -36,7 +36,7 @@ describe("New Vertex Panel", () => {
|
|||||||
it("should call form submit method", () => {
|
it("should call form submit method", () => {
|
||||||
const onSubmitSpy = jest.fn();
|
const onSubmitSpy = jest.fn();
|
||||||
|
|
||||||
const newWrapper = shallow(
|
const newWrapper = mount(
|
||||||
<NewVertexPanel
|
<NewVertexPanel
|
||||||
explorer={fakeExplorer}
|
explorer={fakeExplorer}
|
||||||
partitionKeyPropertyProp={undefined}
|
partitionKeyPropertyProp={undefined}
|
||||||
@@ -61,7 +61,7 @@ describe("New Vertex Panel", () => {
|
|||||||
|
|
||||||
const result = onSubmitSpy(fakeNewVertexData, onErrorSpy, onSuccessSpy);
|
const result = onSubmitSpy(fakeNewVertexData, onErrorSpy, onSuccessSpy);
|
||||||
|
|
||||||
const newWrapper = shallow(
|
const newWrapper = mount(
|
||||||
<NewVertexPanel
|
<NewVertexPanel
|
||||||
explorer={fakeExplorer}
|
explorer={fakeExplorer}
|
||||||
partitionKeyPropertyProp={undefined}
|
partitionKeyPropertyProp={undefined}
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ import React, { FunctionComponent, useState } from "react";
|
|||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { NewVertexComponent } from "../../Graph/NewVertexComponent/NewVertexComponent";
|
import { NewVertexComponent } from "../../Graph/NewVertexComponent/NewVertexComponent";
|
||||||
import { PanelFooterComponent } from "../PanelFooterComponent";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
|
|
||||||
import { PanelLoadingScreen } from "../PanelLoadingScreen";
|
|
||||||
export interface INewVertexPanelProps {
|
export interface INewVertexPanelProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
partitionKeyPropertyProp: string;
|
partitionKeyPropertyProp: string;
|
||||||
@@ -21,14 +19,10 @@ export const NewVertexPanel: FunctionComponent<INewVertexPanelProps> = ({
|
|||||||
}: INewVertexPanelProps): JSX.Element => {
|
}: INewVertexPanelProps): JSX.Element => {
|
||||||
let newVertexDataValue: ViewModels.NewVertexData;
|
let newVertexDataValue: ViewModels.NewVertexData;
|
||||||
const [errorMessage, setErrorMessage] = useState<string>("");
|
const [errorMessage, setErrorMessage] = useState<string>("");
|
||||||
const [showErrorDetails, setShowErrorDetails] = useState<boolean>(false);
|
|
||||||
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
|
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
|
||||||
const buttonLabel = "OK";
|
|
||||||
|
|
||||||
const submit = (event: React.MouseEvent<HTMLFormElement>) => {
|
const submit = () => {
|
||||||
event.preventDefault();
|
|
||||||
setErrorMessage(undefined);
|
setErrorMessage(undefined);
|
||||||
setShowErrorDetails(false);
|
|
||||||
if (onSubmit !== undefined) {
|
if (onSubmit !== undefined) {
|
||||||
setLoadingTrue();
|
setLoadingTrue();
|
||||||
onSubmit(newVertexDataValue, onError, onSuccess);
|
onSubmit(newVertexDataValue, onError, onSuccess);
|
||||||
@@ -37,7 +31,6 @@ export const NewVertexPanel: FunctionComponent<INewVertexPanelProps> = ({
|
|||||||
|
|
||||||
const onError = (errorMsg: string) => {
|
const onError = (errorMsg: string) => {
|
||||||
setErrorMessage(errorMsg);
|
setErrorMessage(errorMsg);
|
||||||
setShowErrorDetails(true);
|
|
||||||
setLoadingFalse();
|
setLoadingFalse();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -49,17 +42,16 @@ export const NewVertexPanel: FunctionComponent<INewVertexPanelProps> = ({
|
|||||||
const onChange = (newVertexData: ViewModels.NewVertexData) => {
|
const onChange = (newVertexData: ViewModels.NewVertexData) => {
|
||||||
newVertexDataValue = newVertexData;
|
newVertexDataValue = newVertexData;
|
||||||
};
|
};
|
||||||
|
const props: RightPaneFormProps = {
|
||||||
|
formError: errorMessage,
|
||||||
|
isExecuting: isLoading,
|
||||||
|
submitButtonText: "OK",
|
||||||
|
onSubmit: () => submit(),
|
||||||
|
expandConsole: openNotificationConsole,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="panelFormWrapper" onSubmit={(event: React.MouseEvent<HTMLFormElement>) => submit(event)}>
|
<RightPaneForm {...props}>
|
||||||
{errorMessage && (
|
|
||||||
<PanelInfoErrorComponent
|
|
||||||
message={errorMessage}
|
|
||||||
messageType="error"
|
|
||||||
showErrorDetails={showErrorDetails}
|
|
||||||
openNotificationConsole={openNotificationConsole}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
<NewVertexComponent
|
<NewVertexComponent
|
||||||
newVertexDataProp={newVertexDataValue}
|
newVertexDataProp={newVertexDataValue}
|
||||||
@@ -67,8 +59,6 @@ export const NewVertexPanel: FunctionComponent<INewVertexPanelProps> = ({
|
|||||||
onChangeProp={onChange}
|
onChangeProp={onChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<PanelFooterComponent buttonLabel={buttonLabel} />
|
</RightPaneForm>
|
||||||
{isLoading && <PanelLoadingScreen />}
|
|
||||||
</form>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`New Vertex Panel should render default property 1`] = `
|
exports[`New Vertex Panel should render default property 1`] = `
|
||||||
<form
|
<RightPaneForm
|
||||||
className="panelFormWrapper"
|
expandConsole={[Function]}
|
||||||
|
formError=""
|
||||||
|
isExecuting={false}
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
|
submitButtonText="OK"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="panelMainContent"
|
className="panelMainContent"
|
||||||
@@ -13,8 +16,5 @@ exports[`New Vertex Panel should render default property 1`] = `
|
|||||||
partitionKeyPropertyProp=""
|
partitionKeyPropertyProp=""
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<PanelFooterComponent
|
</RightPaneForm>
|
||||||
buttonLabel="OK"
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import CassandraAddCollectionPaneTemplate from "./CassandraAddCollectionPane.html";
|
|
||||||
export class PaneComponent {
|
|
||||||
constructor(data: any) {
|
|
||||||
return data.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CassandraAddCollectionPaneComponent {
|
|
||||||
constructor() {
|
|
||||||
return {
|
|
||||||
viewModel: PaneComponent,
|
|
||||||
template: CassandraAddCollectionPaneTemplate,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,7 @@ export interface PanelInfoErrorProps {
|
|||||||
link?: string;
|
link?: string;
|
||||||
linkText?: string;
|
linkText?: string;
|
||||||
openNotificationConsole?: () => void;
|
openNotificationConsole?: () => void;
|
||||||
|
formError?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProps> = ({
|
export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProps> = ({
|
||||||
|
|||||||
@@ -7,15 +7,12 @@ import { JunoClient } from "../../../Juno/JunoClient";
|
|||||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
import { CodeOfConductComponent } from "../../Controls/NotebookGallery/CodeOfConductComponent";
|
import { CodeOfConduct } from "../../Controls/NotebookGallery/CodeOfConduct/CodeOfConduct";
|
||||||
import { GalleryTab } from "../../Controls/NotebookGallery/GalleryViewerComponent";
|
import { GalleryTab } from "../../Controls/NotebookGallery/GalleryViewerComponent";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import * as FileSystemUtil from "../../Notebook/FileSystemUtil";
|
import * as FileSystemUtil from "../../Notebook/FileSystemUtil";
|
||||||
import { SnapshotRequest } from "../../Notebook/NotebookComponent/types";
|
import { SnapshotRequest } from "../../Notebook/NotebookComponent/types";
|
||||||
import {
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
GenericRightPaneComponent,
|
|
||||||
GenericRightPaneProps,
|
|
||||||
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
|
|
||||||
import { PublishNotebookPaneComponent, PublishNotebookPaneProps } from "./PublishNotebookPaneComponent";
|
import { PublishNotebookPaneComponent, PublishNotebookPaneProps } from "./PublishNotebookPaneComponent";
|
||||||
|
|
||||||
export interface PublishNotebookPaneAProps {
|
export interface PublishNotebookPaneAProps {
|
||||||
@@ -155,7 +152,6 @@ export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> =
|
|||||||
clearPublishingMessage();
|
clearPublishingMessage();
|
||||||
setIsExecuting(false);
|
setIsExecuting(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
closePanel();
|
closePanel();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -170,15 +166,11 @@ export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> =
|
|||||||
setFormErrorDetail("");
|
setFormErrorDetail("");
|
||||||
};
|
};
|
||||||
|
|
||||||
const props: GenericRightPaneProps = {
|
const props: RightPaneFormProps = {
|
||||||
formError: formError,
|
formError: formError,
|
||||||
formErrorDetail: formErrorDetail,
|
|
||||||
id: "publishnotebookpane",
|
|
||||||
isExecuting: isExecuting,
|
isExecuting: isExecuting,
|
||||||
title: "Publish to gallery",
|
|
||||||
submitButtonText: "Publish",
|
submitButtonText: "Publish",
|
||||||
onSubmit: () => submit(),
|
onSubmit: () => submit(),
|
||||||
onClose: closePanel,
|
|
||||||
expandConsole: () => container.expandConsole(),
|
expandConsole: () => container.expandConsole(),
|
||||||
isSubmitButtonHidden: !isCodeOfConductAccepted,
|
isSubmitButtonHidden: !isCodeOfConductAccepted,
|
||||||
};
|
};
|
||||||
@@ -201,10 +193,10 @@ export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> =
|
|||||||
onTakeSnapshot,
|
onTakeSnapshot,
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<GenericRightPaneComponent {...props}>
|
<RightPaneForm {...props}>
|
||||||
{!isCodeOfConductAccepted ? (
|
{!isCodeOfConductAccepted ? (
|
||||||
<div style={{ padding: "25px", marginTop: "10px" }}>
|
<div style={{ padding: "25px", marginTop: "10px" }}>
|
||||||
<CodeOfConductComponent
|
<CodeOfConduct
|
||||||
junoClient={junoClient}
|
junoClient={junoClient}
|
||||||
onAcceptCodeOfConduct={(isAccepted) => {
|
onAcceptCodeOfConduct={(isAccepted) => {
|
||||||
setIsCodeOfConductAccepted(isAccepted);
|
setIsCodeOfConductAccepted(isAccepted);
|
||||||
@@ -214,6 +206,6 @@ export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> =
|
|||||||
) : (
|
) : (
|
||||||
<PublishNotebookPaneComponent {...publishNotebookPaneProps} />
|
<PublishNotebookPaneComponent {...publishNotebookPaneProps} />
|
||||||
)}
|
)}
|
||||||
</GenericRightPaneComponent>
|
</RightPaneForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useBoolean } from "@fluentui/react-hooks";
|
|
||||||
import { Text, TextField } from "@fluentui/react";
|
import { Text, TextField } from "@fluentui/react";
|
||||||
|
import { useBoolean } from "@fluentui/react-hooks";
|
||||||
import React, { FunctionComponent, useState } from "react";
|
import React, { FunctionComponent, useState } from "react";
|
||||||
import { Areas, SavedQueries } from "../../../Common/Constants";
|
import { Areas, SavedQueries } from "../../../Common/Constants";
|
||||||
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||||
@@ -9,10 +9,7 @@ import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetr
|
|||||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import QueryTab from "../../Tabs/QueryTab";
|
import QueryTab from "../../Tabs/QueryTab";
|
||||||
import {
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
GenericRightPaneComponent,
|
|
||||||
GenericRightPaneProps,
|
|
||||||
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
|
|
||||||
|
|
||||||
interface SaveQueryPaneProps {
|
interface SaveQueryPaneProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
@@ -25,32 +22,16 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({
|
|||||||
}: SaveQueryPaneProps): JSX.Element => {
|
}: SaveQueryPaneProps): JSX.Element => {
|
||||||
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
|
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
|
||||||
const [formError, setFormError] = useState<string>("");
|
const [formError, setFormError] = useState<string>("");
|
||||||
const [formErrorsDetails, setFormErrorsDetails] = useState<string>("");
|
|
||||||
const [queryName, setQueryName] = useState<string>("");
|
const [queryName, setQueryName] = useState<string>("");
|
||||||
|
|
||||||
const setupSaveQueriesText = `For compliance reasons, we save queries in a container in your Azure Cosmos account, in a separate database called “${SavedQueries.DatabaseName}”. To proceed, we need to create a container in your account, estimated additional cost is $0.77 daily.`;
|
const setupSaveQueriesText = `For compliance reasons, we save queries in a container in your Azure Cosmos account, in a separate database called “${SavedQueries.DatabaseName}”. To proceed, we need to create a container in your account, estimated additional cost is $0.77 daily.`;
|
||||||
const title = "Save Query";
|
const title = "Save Query";
|
||||||
const { canSaveQueries } = explorer;
|
const { canSaveQueries } = explorer;
|
||||||
const genericPaneProps: GenericRightPaneProps = {
|
|
||||||
expandConsole: () => explorer.expandConsole(),
|
|
||||||
formError: formError,
|
|
||||||
formErrorDetail: formErrorsDetails,
|
|
||||||
id: "saveQueryPane",
|
|
||||||
isExecuting: isLoading,
|
|
||||||
title,
|
|
||||||
submitButtonText: canSaveQueries() ? "Save" : "Complete setup",
|
|
||||||
onClose: () => closePanel(),
|
|
||||||
onSubmit: () => {
|
|
||||||
canSaveQueries() ? submit() : setupQueries();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const submit = async (): Promise<void> => {
|
const submit = async (): Promise<void> => {
|
||||||
setFormError("");
|
setFormError("");
|
||||||
setFormErrorsDetails("");
|
|
||||||
if (!canSaveQueries()) {
|
if (!canSaveQueries()) {
|
||||||
setFormError("Cannot save query");
|
setFormError("Cannot save query");
|
||||||
setFormErrorsDetails("Failed to save query: account not set up to save queries");
|
|
||||||
logConsoleError("Failed to save query: account not setup to save queries");
|
logConsoleError("Failed to save query: account not setup to save queries");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,12 +39,10 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({
|
|||||||
const query: string = queryTab && queryTab.sqlQueryEditorContent();
|
const query: string = queryTab && queryTab.sqlQueryEditorContent();
|
||||||
if (!queryName || queryName.length === 0) {
|
if (!queryName || queryName.length === 0) {
|
||||||
setFormError("No query name specified");
|
setFormError("No query name specified");
|
||||||
setFormErrorsDetails("No query name specified. Please specify a query name.");
|
|
||||||
logConsoleError("Could not save query -- No query name specified. Please specify a query name.");
|
logConsoleError("Could not save query -- No query name specified. Please specify a query name.");
|
||||||
return;
|
return;
|
||||||
} else if (!query || query.length === 0) {
|
} else if (!query || query.length === 0) {
|
||||||
setFormError("Invalid query content specified");
|
setFormError("Invalid query content specified");
|
||||||
setFormErrorsDetails("Invalid query content specified. Please enter query content.");
|
|
||||||
logConsoleError("Could not save query -- Invalid query content specified. Please enter query content.");
|
logConsoleError("Could not save query -- Invalid query content specified. Please enter query content.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -97,7 +76,7 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({
|
|||||||
setLoadingFalse();
|
setLoadingFalse();
|
||||||
const errorMessage = getErrorMessage(error);
|
const errorMessage = getErrorMessage(error);
|
||||||
setFormError("Failed to save query");
|
setFormError("Failed to save query");
|
||||||
setFormErrorsDetails(`Failed to save query: ${errorMessage}`);
|
logConsoleError(`Failed to save query: ${errorMessage}`);
|
||||||
traceFailure(
|
traceFailure(
|
||||||
Action.SaveQuery,
|
Action.SaveQuery,
|
||||||
{
|
{
|
||||||
@@ -142,14 +121,23 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({
|
|||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
setFormError("Failed to setup a container for saved queries");
|
setFormError("Failed to setup a container for saved queries");
|
||||||
setFormErrorsDetails(`Failed to setup a container for saved queries: ${errorMessage}`);
|
logConsoleError(`Failed to setup a container for saved queries: ${errorMessage}`);
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingFalse();
|
setLoadingFalse();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const props: RightPaneFormProps = {
|
||||||
|
expandConsole: () => explorer.expandConsole(),
|
||||||
|
formError: formError,
|
||||||
|
isExecuting: isLoading,
|
||||||
|
submitButtonText: canSaveQueries() ? "Save" : "Complete setup",
|
||||||
|
onSubmit: () => {
|
||||||
|
canSaveQueries() ? submit() : setupQueries();
|
||||||
|
},
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<GenericRightPaneComponent {...genericPaneProps}>
|
<RightPaneForm {...props}>
|
||||||
<div className="panelFormWrapper">
|
<div className="panelFormWrapper">
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
{!canSaveQueries() ? (
|
{!canSaveQueries() ? (
|
||||||
@@ -158,6 +146,7 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({
|
|||||||
<TextField
|
<TextField
|
||||||
id="saveQueryInput"
|
id="saveQueryInput"
|
||||||
label="Name"
|
label="Name"
|
||||||
|
autoFocus
|
||||||
styles={{ fieldGroup: { width: 300 } }}
|
styles={{ fieldGroup: { width: 300 } }}
|
||||||
onChange={(event, newInput?: string) => {
|
onChange={(event, newInput?: string) => {
|
||||||
setQueryName(newInput);
|
setQueryName(newInput);
|
||||||
@@ -166,6 +155,6 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GenericRightPaneComponent>
|
</RightPaneForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`Save Query Pane should render Default properly 1`] = `
|
exports[`Save Query Pane should render Default properly 1`] = `
|
||||||
<GenericRightPaneComponent
|
<RightPaneForm
|
||||||
expandConsole={[Function]}
|
expandConsole={[Function]}
|
||||||
formError=""
|
formError=""
|
||||||
formErrorDetail=""
|
|
||||||
id="saveQueryPane"
|
|
||||||
isExecuting={false}
|
isExecuting={false}
|
||||||
onClose={[Function]}
|
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
submitButtonText="Complete setup"
|
submitButtonText="Complete setup"
|
||||||
title="Save Query"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="panelFormWrapper"
|
className="panelFormWrapper"
|
||||||
@@ -25,5 +21,5 @@ exports[`Save Query Pane should render Default properly 1`] = `
|
|||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GenericRightPaneComponent>
|
</RightPaneForm>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ import Explorer from "../../Explorer";
|
|||||||
import * as FileSystemUtil from "../../Notebook/FileSystemUtil";
|
import * as FileSystemUtil from "../../Notebook/FileSystemUtil";
|
||||||
import { NotebookContentItem } from "../../Notebook/NotebookContentItem";
|
import { NotebookContentItem } from "../../Notebook/NotebookContentItem";
|
||||||
import NotebookV2Tab from "../../Tabs/NotebookV2Tab";
|
import NotebookV2Tab from "../../Tabs/NotebookV2Tab";
|
||||||
import {
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
GenericRightPaneComponent,
|
|
||||||
GenericRightPaneProps,
|
|
||||||
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
|
|
||||||
|
|
||||||
export interface StringInputPanelProps {
|
export interface StringInputPanelProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
@@ -40,7 +37,6 @@ export const StringInputPane: FunctionComponent<StringInputPanelProps> = ({
|
|||||||
}: StringInputPanelProps): JSX.Element => {
|
}: StringInputPanelProps): JSX.Element => {
|
||||||
const [stringInput, setStringInput] = useState<string>(defaultInput);
|
const [stringInput, setStringInput] = useState<string>(defaultInput);
|
||||||
const [formErrors, setFormErrors] = useState<string>("");
|
const [formErrors, setFormErrors] = useState<string>("");
|
||||||
const [formErrorsDetails, setFormErrorsDetails] = useState<string>("");
|
|
||||||
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
||||||
|
|
||||||
const submit = async (): Promise<void> => {
|
const submit = async (): Promise<void> => {
|
||||||
@@ -51,7 +47,6 @@ export const StringInputPane: FunctionComponent<StringInputPanelProps> = ({
|
|||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
setFormErrors("");
|
setFormErrors("");
|
||||||
setFormErrorsDetails("");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearMessage = logConsoleProgress(`${inProgressMessage} ${stringInput}`);
|
const clearMessage = logConsoleProgress(`${inProgressMessage} ${stringInput}`);
|
||||||
@@ -78,32 +73,26 @@ export const StringInputPane: FunctionComponent<StringInputPanelProps> = ({
|
|||||||
error = JSON.stringify(reason);
|
error = JSON.stringify(reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's an AjaxError (AjaxObservable), add more error
|
|
||||||
if (reason?.response?.message) {
|
if (reason?.response?.message) {
|
||||||
error += `. ${reason.response.message}`;
|
error += `. ${reason.response.message}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
setFormErrors(errorMessage);
|
setFormErrors(errorMessage);
|
||||||
setFormErrorsDetails(`${errorMessage}: ${error}`);
|
|
||||||
logConsoleError(`${errorMessage} ${stringInput}: ${error}`);
|
logConsoleError(`${errorMessage} ${stringInput}: ${error}`);
|
||||||
} finally {
|
} finally {
|
||||||
setIsExecuting(false);
|
setIsExecuting(false);
|
||||||
clearMessage();
|
clearMessage();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const genericPaneProps: GenericRightPaneProps = {
|
const props: RightPaneFormProps = {
|
||||||
formError: formErrors,
|
formError: formErrors,
|
||||||
formErrorDetail: formErrorsDetails,
|
|
||||||
id: "stringInputPane",
|
|
||||||
isExecuting: isExecuting,
|
isExecuting: isExecuting,
|
||||||
title: paneTitle,
|
|
||||||
submitButtonText: submitButtonLabel,
|
submitButtonText: submitButtonLabel,
|
||||||
onClose: closePanel,
|
|
||||||
onSubmit: submit,
|
onSubmit: submit,
|
||||||
expandConsole: () => container.expandConsole(),
|
expandConsole: () => container.expandConsole(),
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<GenericRightPaneComponent {...genericPaneProps}>
|
<RightPaneForm {...props}>
|
||||||
<div className="paneMainContent">
|
<div className="paneMainContent">
|
||||||
<TextField
|
<TextField
|
||||||
label={inputLabel}
|
label={inputLabel}
|
||||||
@@ -117,6 +106,6 @@ export const StringInputPane: FunctionComponent<StringInputPanelProps> = ({
|
|||||||
aria-label={inputLabel}
|
aria-label={inputLabel}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</GenericRightPaneComponent>
|
</RightPaneForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -3,7 +3,7 @@ import * as ko from "knockout";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import Explorer from "../../../Explorer";
|
import Explorer from "../../../Explorer";
|
||||||
import QueryViewModel from "../../../Tables/QueryBuilder/QueryViewModel";
|
import QueryViewModel from "../../../Tables/QueryBuilder/QueryViewModel";
|
||||||
import { TableQuerySelectPanel } from "./index";
|
import { TableQuerySelectPanel } from "./TableQuerySelectPanel";
|
||||||
|
|
||||||
describe("Table query select Panel", () => {
|
describe("Table query select Panel", () => {
|
||||||
const fakeExplorer = {} as Explorer;
|
const fakeExplorer = {} as Explorer;
|
||||||
+12
-19
@@ -4,10 +4,7 @@ import { userContext } from "../../../../UserContext";
|
|||||||
import Explorer from "../../../Explorer";
|
import Explorer from "../../../Explorer";
|
||||||
import * as Constants from "../../../Tables/Constants";
|
import * as Constants from "../../../Tables/Constants";
|
||||||
import QueryViewModel from "../../../Tables/QueryBuilder/QueryViewModel";
|
import QueryViewModel from "../../../Tables/QueryBuilder/QueryViewModel";
|
||||||
import {
|
import { RightPaneForm, RightPaneFormProps } from "../../RightPaneForm/RightPaneForm";
|
||||||
GenericRightPaneComponent,
|
|
||||||
GenericRightPaneProps,
|
|
||||||
} from "../../GenericRightPaneComponent/GenericRightPaneComponent";
|
|
||||||
|
|
||||||
interface TableQuerySelectPanelProps {
|
interface TableQuerySelectPanelProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
@@ -31,24 +28,20 @@ export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps
|
|||||||
]);
|
]);
|
||||||
const [isAvailableColumnChecked, setIsAvailableColumnChecked] = useState<boolean>(true);
|
const [isAvailableColumnChecked, setIsAvailableColumnChecked] = useState<boolean>(true);
|
||||||
|
|
||||||
const genericPaneProps: GenericRightPaneProps = {
|
const onSubmit = (): void => {
|
||||||
formError: "",
|
|
||||||
formErrorDetail: "",
|
|
||||||
id: "querySelectPane",
|
|
||||||
isExecuting: false,
|
|
||||||
title: "Select Column",
|
|
||||||
submitButtonText: "OK",
|
|
||||||
onClose: () => closePanel(),
|
|
||||||
onSubmit: () => submit(),
|
|
||||||
expandConsole: () => explorer.expandConsole(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const submit = (): void => {
|
|
||||||
queryViewModel.selectText(getParameters());
|
queryViewModel.selectText(getParameters());
|
||||||
queryViewModel.getSelectMessage();
|
queryViewModel.getSelectMessage();
|
||||||
closePanel();
|
closePanel();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const props: RightPaneFormProps = {
|
||||||
|
formError: "",
|
||||||
|
isExecuting: false,
|
||||||
|
submitButtonText: "OK",
|
||||||
|
onSubmit,
|
||||||
|
expandConsole: () => explorer.expandConsole(),
|
||||||
|
};
|
||||||
|
|
||||||
const handleClick = (isChecked: boolean, selectedColumn: string): void => {
|
const handleClick = (isChecked: boolean, selectedColumn: string): void => {
|
||||||
const columns = columnOptions.map((column) => {
|
const columns = columnOptions.map((column) => {
|
||||||
if (column.columnName === selectedColumn) {
|
if (column.columnName === selectedColumn) {
|
||||||
@@ -128,7 +121,7 @@ export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GenericRightPaneComponent {...genericPaneProps}>
|
<RightPaneForm {...props}>
|
||||||
<div className="panelFormWrapper">
|
<div className="panelFormWrapper">
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
<Text>Select the columns that you want to query.</Text>
|
<Text>Select the columns that you want to query.</Text>
|
||||||
@@ -153,6 +146,6 @@ export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GenericRightPaneComponent>
|
</RightPaneForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
+3007
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -78,7 +78,7 @@ export const UploadFilePane: FunctionComponent<UploadFilePanelProps> = ({
|
|||||||
return uploadFile(file.name, fileContent);
|
return uploadFile(file.name, fileContent);
|
||||||
};
|
};
|
||||||
|
|
||||||
const genericPaneProps: RightPaneFormProps = {
|
const props: RightPaneFormProps = {
|
||||||
expandConsole,
|
expandConsole,
|
||||||
formError: formErrors,
|
formError: formErrors,
|
||||||
isExecuting: isExecuting,
|
isExecuting: isExecuting,
|
||||||
@@ -87,7 +87,7 @@ export const UploadFilePane: FunctionComponent<UploadFilePanelProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...genericPaneProps}>
|
<RightPaneForm {...props}>
|
||||||
<div className="paneMainContent">
|
<div className="paneMainContent">
|
||||||
<Upload label="Select file to upload" accept={extensions} onUpload={updateSelectedFiles} />
|
<Upload label="Select file to upload" accept={extensions} onUpload={updateSelectedFiles} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ explo
|
|||||||
setFiles(event.target.files);
|
setFiles(event.target.files);
|
||||||
};
|
};
|
||||||
|
|
||||||
const genericPaneProps: RightPaneFormProps = {
|
const props: RightPaneFormProps = {
|
||||||
expandConsole: () => explorer.expandConsole(),
|
expandConsole: () => explorer.expandConsole(),
|
||||||
formError,
|
formError,
|
||||||
isExecuting: isExecuting,
|
isExecuting: isExecuting,
|
||||||
@@ -89,7 +89,7 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ explo
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...genericPaneProps}>
|
<RightPaneForm {...props}>
|
||||||
<div className="paneMainContent">
|
<div className="paneMainContent">
|
||||||
<Upload
|
<Upload
|
||||||
label="Select JSON Files"
|
label="Select JSON Files"
|
||||||
|
|||||||
@@ -15,51 +15,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
|
||||||
"autoPilotUsageCost": [Function],
|
|
||||||
"canConfigureThroughput": [Function],
|
|
||||||
"canExceedMaximumValue": [Function],
|
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"container": [Circular],
|
|
||||||
"costsVisible": [Function],
|
|
||||||
"createTableQuery": [Function],
|
|
||||||
"dedicateTableThroughput": [Function],
|
|
||||||
"firstFieldHasFocus": [Function],
|
|
||||||
"formErrors": [Function],
|
|
||||||
"formErrorsDetails": [Function],
|
|
||||||
"id": "cassandraaddcollectionpane",
|
|
||||||
"isAutoPilotSelected": [Function],
|
|
||||||
"isExecuting": [Function],
|
|
||||||
"isFreeTierAccount": [Function],
|
|
||||||
"isSharedAutoPilotSelected": [Function],
|
|
||||||
"isTemplateReady": [Function],
|
|
||||||
"keyspaceCreateNew": [Function],
|
|
||||||
"keyspaceHasSharedOffer": [Function],
|
|
||||||
"keyspaceId": [Function],
|
|
||||||
"keyspaceIds": [Function],
|
|
||||||
"keyspaceOffers": Map {},
|
|
||||||
"keyspaceThroughput": [Function],
|
|
||||||
"maxThroughputRU": [Function],
|
|
||||||
"minThroughputRU": [Function],
|
|
||||||
"requestUnitsUsageCostDedicated": [Function],
|
|
||||||
"requestUnitsUsageCostShared": [Function],
|
|
||||||
"ruToolTipText": [Function],
|
|
||||||
"selectedAutoPilotThroughput": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
|
||||||
"sharedThroughputSpendAck": [Function],
|
|
||||||
"sharedThroughputSpendAckText": [Function],
|
|
||||||
"sharedThroughputSpendAckVisible": [Function],
|
|
||||||
"tableId": [Function],
|
|
||||||
"throughput": [Function],
|
|
||||||
"throughputRangeText": [Function],
|
|
||||||
"throughputSpendAck": [Function],
|
|
||||||
"throughputSpendAckText": [Function],
|
|
||||||
"throughputSpendAckVisible": [Function],
|
|
||||||
"title": [Function],
|
|
||||||
"userTableQuery": [Function],
|
|
||||||
"visible": [Function],
|
|
||||||
},
|
|
||||||
"closeDialog": undefined,
|
"closeDialog": undefined,
|
||||||
"closeSidePanel": undefined,
|
"closeSidePanel": undefined,
|
||||||
"collapsedResourceTreeWidth": 36,
|
"collapsedResourceTreeWidth": 36,
|
||||||
@@ -867,6 +822,7 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
"isSchemaEnabled": [Function],
|
"isSchemaEnabled": [Function],
|
||||||
"isSelectedDatabaseShared": [Function],
|
"isSelectedDatabaseShared": [Function],
|
||||||
"isServerlessEnabled": [Function],
|
"isServerlessEnabled": [Function],
|
||||||
|
"isShellEnabled": [Function],
|
||||||
"isSparkEnabled": [Function],
|
"isSparkEnabled": [Function],
|
||||||
"isSparkEnabledForAccount": [Function],
|
"isSparkEnabledForAccount": [Function],
|
||||||
"isSynapseLinkUpdating": [Function],
|
"isSynapseLinkUpdating": [Function],
|
||||||
@@ -938,6 +894,13 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<RightPaneForm
|
||||||
|
expandConsole={[Function]}
|
||||||
|
formError=""
|
||||||
|
isExecuting={false}
|
||||||
|
onSubmit={[Function]}
|
||||||
|
submitButtonText="OK"
|
||||||
|
>
|
||||||
<form
|
<form
|
||||||
className="panelFormWrapper"
|
className="panelFormWrapper"
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
@@ -3704,5 +3667,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
</div>
|
</div>
|
||||||
</PanelFooterComponent>
|
</PanelFooterComponent>
|
||||||
</form>
|
</form>
|
||||||
|
</RightPaneForm>
|
||||||
</DeleteDatabaseConfirmationPanel>
|
</DeleteDatabaseConfirmationPanel>
|
||||||
`;
|
`;
|
||||||
|
|||||||
+11
-10
@@ -5,20 +5,20 @@ import { render } from "react-dom";
|
|||||||
import ChevronRight from "../images/chevron-right.svg";
|
import ChevronRight from "../images/chevron-right.svg";
|
||||||
import "../less/hostedexplorer.less";
|
import "../less/hostedexplorer.less";
|
||||||
import { AuthType } from "./AuthType";
|
import { AuthType } from "./AuthType";
|
||||||
import { ConnectExplorer } from "./Platform/Hosted/Components/ConnectExplorer";
|
|
||||||
import { DatabaseAccount } from "./Contracts/DataModels";
|
import { DatabaseAccount } from "./Contracts/DataModels";
|
||||||
import { DirectoryPickerPanel } from "./Platform/Hosted/Components/DirectoryPickerPanel";
|
|
||||||
import { AccountSwitcher } from "./Platform/Hosted/Components/AccountSwitcher";
|
|
||||||
import "./Explorer/Menus/NavBar/MeControlComponent.less";
|
import "./Explorer/Menus/NavBar/MeControlComponent.less";
|
||||||
import { useTokenMetadata } from "./hooks/usePortalAccessToken";
|
|
||||||
import { MeControl } from "./Platform/Hosted/Components/MeControl";
|
|
||||||
import "./Platform/Hosted/ConnectScreen.less";
|
|
||||||
import "./Shared/appInsights";
|
|
||||||
import { SignInButton } from "./Platform/Hosted/Components/SignInButton";
|
|
||||||
import { useAADAuth } from "./hooks/useAADAuth";
|
import { useAADAuth } from "./hooks/useAADAuth";
|
||||||
import { FeedbackCommandButton } from "./Platform/Hosted/Components/FeedbackCommandButton";
|
import { useTokenMetadata } from "./hooks/usePortalAccessToken";
|
||||||
import { HostedExplorerChildFrame } from "./HostedExplorerChildFrame";
|
import { HostedExplorerChildFrame } from "./HostedExplorerChildFrame";
|
||||||
|
import { AccountSwitcher } from "./Platform/Hosted/Components/AccountSwitcher";
|
||||||
|
import { ConnectExplorer } from "./Platform/Hosted/Components/ConnectExplorer";
|
||||||
|
import { DirectoryPickerPanel } from "./Platform/Hosted/Components/DirectoryPickerPanel";
|
||||||
|
import { FeedbackCommandButton } from "./Platform/Hosted/Components/FeedbackCommandButton";
|
||||||
|
import { MeControl } from "./Platform/Hosted/Components/MeControl";
|
||||||
|
import { SignInButton } from "./Platform/Hosted/Components/SignInButton";
|
||||||
|
import "./Platform/Hosted/ConnectScreen.less";
|
||||||
import { extractMasterKeyfromConnectionString } from "./Platform/Hosted/HostedUtils";
|
import { extractMasterKeyfromConnectionString } from "./Platform/Hosted/HostedUtils";
|
||||||
|
import "./Shared/appInsights";
|
||||||
|
|
||||||
initializeIcons();
|
initializeIcons();
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ const App: React.FunctionComponent = () => {
|
|||||||
// For showing/hiding panel
|
// For showing/hiding panel
|
||||||
const [isOpen, { setTrue: openPanel, setFalse: dismissPanel }] = useBoolean(false);
|
const [isOpen, { setTrue: openPanel, setFalse: dismissPanel }] = useBoolean(false);
|
||||||
|
|
||||||
const { isLoggedIn, armToken, graphToken, account, tenantId, logout, login, switchTenant } = useAADAuth();
|
const { isLoggedIn, armToken, graphToken, aadToken, account, tenantId, logout, login, switchTenant } = useAADAuth();
|
||||||
const [databaseAccount, setDatabaseAccount] = React.useState<DatabaseAccount>();
|
const [databaseAccount, setDatabaseAccount] = React.useState<DatabaseAccount>();
|
||||||
const [authType, setAuthType] = React.useState<AuthType>(encryptedToken ? AuthType.EncryptedToken : undefined);
|
const [authType, setAuthType] = React.useState<AuthType>(encryptedToken ? AuthType.EncryptedToken : undefined);
|
||||||
const [connectionString, setConnectionString] = React.useState<string>();
|
const [connectionString, setConnectionString] = React.useState<string>();
|
||||||
@@ -50,6 +50,7 @@ const App: React.FunctionComponent = () => {
|
|||||||
authType: AuthType.AAD,
|
authType: AuthType.AAD,
|
||||||
databaseAccount,
|
databaseAccount,
|
||||||
authorizationToken: armToken,
|
authorizationToken: armToken,
|
||||||
|
aadToken,
|
||||||
};
|
};
|
||||||
} else if (authType === AuthType.EncryptedToken) {
|
} else if (authType === AuthType.EncryptedToken) {
|
||||||
frameWindow.hostedConfig = {
|
frameWindow.hostedConfig = {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export interface HostedExplorerChildFrame extends Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AAD {
|
export interface AAD {
|
||||||
|
aadToken: string;
|
||||||
authType: AuthType.AAD;
|
authType: AuthType.AAD;
|
||||||
databaseAccount: DatabaseAccount;
|
databaseAccount: DatabaseAccount;
|
||||||
authorizationToken: string;
|
authorizationToken: string;
|
||||||
|
|||||||
@@ -37,16 +37,19 @@
|
|||||||
"CannotSave": "Cannot save the changes to the Dedicated gateway resource at the moment.",
|
"CannotSave": "Cannot save the changes to the Dedicated gateway resource at the moment.",
|
||||||
"DedicatedGatewayEndpoint": "Dedicated gatewayEndpoint",
|
"DedicatedGatewayEndpoint": "Dedicated gatewayEndpoint",
|
||||||
"NoValue": "",
|
"NoValue": "",
|
||||||
"SKUDetails": "SKU Details:",
|
|
||||||
"CosmosD4Details": "General Purpose Cosmos Compute with 4 vCPUs, 16 GB Memory",
|
"CosmosD4Details": "General Purpose Cosmos Compute with 4 vCPUs, 16 GB Memory",
|
||||||
"CosmosD8Details": "General Purpose Cosmos Compute with 8 vCPUs, 32 GB Memory",
|
"CosmosD8Details": "General Purpose Cosmos Compute with 8 vCPUs, 32 GB Memory",
|
||||||
"CosmosD16Details": "General Purpose Cosmos Compute with 16 vCPUs, 64 GB Memory",
|
"CosmosD16Details": "General Purpose Cosmos Compute with 16 vCPUs, 64 GB Memory",
|
||||||
"CosmosD32Details": "General Purpose Cosmos Compute with 32 vCPUs, 128 GB Memory",
|
|
||||||
"Cost": "Cost",
|
"Cost": "Cost",
|
||||||
"CostText": "Hourly cost of the dedicated gateway resource depends on the SKU selection, number of instances per region, and number of regions.",
|
"CostText": "Hourly cost of the dedicated gateway resource depends on the SKU selection, number of instances per region, and number of regions.",
|
||||||
"ConnectionString": "Connection String",
|
"ConnectionString": "Connection String",
|
||||||
"ConnectionStringText": "To use the dedicated gateway, use the connection string shown in ",
|
"ConnectionStringText": "To use the dedicated gateway, use the connection string shown in ",
|
||||||
"KeysBlade": "the keys blade",
|
"KeysBlade": "the keys blade.",
|
||||||
|
"MetricsString": "Metrics",
|
||||||
|
"MetricsText": "Monitor the \"DedicatedGatewayMaximumCpuUsage\" and \"DedicatedGatewayAverageMemoryUsage\" in ",
|
||||||
|
"MetricsBlade": "the metrics blade.",
|
||||||
|
"ResizingDecisionText": "To understand if the dedicated gateway is the right size, ",
|
||||||
|
"ResizingDecisionLink": "learn more about dedicated gateway sizing.",
|
||||||
"WarningBannerOnUpdate": "Adding or modifying dedicated gateway instances may affect your bill.",
|
"WarningBannerOnUpdate": "Adding or modifying dedicated gateway instances may affect your bill.",
|
||||||
"WarningBannerOnDelete": "After deprovisioning the dedicated gateway, you must update any applications using the old dedicated gateway connection string."
|
"WarningBannerOnDelete": "After deprovisioning the dedicated gateway, you must update any applications using the old dedicated gateway connection string."
|
||||||
}
|
}
|
||||||
@@ -226,7 +226,6 @@ const App: React.FunctionComponent = () => {
|
|||||||
closePanel={closeSidePanel}
|
closePanel={closeSidePanel}
|
||||||
isConsoleExpanded={isNotificationConsoleExpanded}
|
isConsoleExpanded={isNotificationConsoleExpanded}
|
||||||
/>
|
/>
|
||||||
<div data-bind='component: { name: "cassandra-add-collection-pane", params: { data: cassandraAddCollectionPane} }' />
|
|
||||||
{showDialog && <Dialog {...dialogProps} />}
|
{showDialog && <Dialog {...dialogProps} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export type Features = {
|
|||||||
readonly enableSpark: boolean;
|
readonly enableSpark: boolean;
|
||||||
readonly enableTtl: boolean;
|
readonly enableTtl: boolean;
|
||||||
readonly executeSproc: boolean;
|
readonly executeSproc: boolean;
|
||||||
|
readonly enableAadDataPlane: boolean;
|
||||||
readonly hostedDataExplorer: boolean;
|
readonly hostedDataExplorer: boolean;
|
||||||
readonly junoEndpoint?: string;
|
readonly junoEndpoint?: string;
|
||||||
readonly livyEndpoint?: string;
|
readonly livyEndpoint?: string;
|
||||||
@@ -43,6 +44,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
|
|||||||
return {
|
return {
|
||||||
canExceedMaximumValue: "true" === get("canexceedmaximumvalue"),
|
canExceedMaximumValue: "true" === get("canexceedmaximumvalue"),
|
||||||
cosmosdb: "true" === get("cosmosdb"),
|
cosmosdb: "true" === get("cosmosdb"),
|
||||||
|
enableAadDataPlane: "true" === get("enableaaddataplane"),
|
||||||
enableChangeFeedPolicy: "true" === get("enablechangefeedpolicy"),
|
enableChangeFeedPolicy: "true" === get("enablechangefeedpolicy"),
|
||||||
enableFixedCollectionWithSharedThroughput: "true" === get("enablefixedcollectionwithsharedthroughput"),
|
enableFixedCollectionWithSharedThroughput: "true" === get("enablefixedcollectionwithsharedthroughput"),
|
||||||
enableKOPanel: "true" === get("enablekopanel"),
|
enableKOPanel: "true" === get("enablekopanel"),
|
||||||
|
|||||||
@@ -190,7 +190,8 @@ describe("SelfServeUtils", () => {
|
|||||||
max: 5,
|
max: 5,
|
||||||
step: 1,
|
step: 1,
|
||||||
uiType: "Spinner",
|
uiType: "Spinner",
|
||||||
errorMessage: "label, truelabel and falselabel are required for boolean input 'invalidThroughput'.",
|
errorMessage:
|
||||||
|
"labelTkey, trueLabelTKey and falseLabelTKey are required for boolean input 'invalidThroughput'.",
|
||||||
},
|
},
|
||||||
children: [] as Node[],
|
children: [] as Node[],
|
||||||
},
|
},
|
||||||
@@ -225,7 +226,8 @@ describe("SelfServeUtils", () => {
|
|||||||
type: "boolean",
|
type: "boolean",
|
||||||
labelTKey: "Invalid Enable Logging",
|
labelTKey: "Invalid Enable Logging",
|
||||||
placeholderTKey: "placeholder text",
|
placeholderTKey: "placeholder text",
|
||||||
errorMessage: "label, truelabel and falselabel are required for boolean input 'invalidEnableLogging'.",
|
errorMessage:
|
||||||
|
"labelTkey, trueLabelTKey and falseLabelTKey are required for boolean input 'invalidEnableLogging'.",
|
||||||
},
|
},
|
||||||
children: [] as Node[],
|
children: [] as Node[],
|
||||||
},
|
},
|
||||||
@@ -252,7 +254,7 @@ describe("SelfServeUtils", () => {
|
|||||||
type: "object",
|
type: "object",
|
||||||
labelTKey: "Invalid Regions",
|
labelTKey: "Invalid Regions",
|
||||||
placeholderTKey: "placeholder text",
|
placeholderTKey: "placeholder text",
|
||||||
errorMessage: "label and choices are required for Choice input 'invalidRegions'.",
|
errorMessage: "labelTKey and choices are required for Choice input 'invalidRegions'.",
|
||||||
},
|
},
|
||||||
children: [] as Node[],
|
children: [] as Node[],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -207,8 +207,8 @@ const addToDescriptor = (
|
|||||||
const getInput = (value: DecoratorProperties): AnyDisplay => {
|
const getInput = (value: DecoratorProperties): AnyDisplay => {
|
||||||
switch (value.type) {
|
switch (value.type) {
|
||||||
case "number":
|
case "number":
|
||||||
if (!value.labelTKey || !value.step || !value.uiType || !value.min || !value.max) {
|
if (!value.labelTKey || !value.uiType || !value.step || !value.max || value.min === undefined) {
|
||||||
value.errorMessage = `label, step, min, max and uiType are required for number input '${value.id}'.`;
|
value.errorMessage = `labelTkey, step, min, max and uiType are required for number input '${value.id}'.`;
|
||||||
}
|
}
|
||||||
return value as NumberInput;
|
return value as NumberInput;
|
||||||
case "string":
|
case "string":
|
||||||
@@ -219,17 +219,17 @@ const getInput = (value: DecoratorProperties): AnyDisplay => {
|
|||||||
return value as DescriptionDisplay;
|
return value as DescriptionDisplay;
|
||||||
}
|
}
|
||||||
if (!value.labelTKey) {
|
if (!value.labelTKey) {
|
||||||
value.errorMessage = `label is required for string input '${value.id}'.`;
|
value.errorMessage = `labelTKey is required for string input '${value.id}'.`;
|
||||||
}
|
}
|
||||||
return value as StringInput;
|
return value as StringInput;
|
||||||
case "boolean":
|
case "boolean":
|
||||||
if (!value.labelTKey || !value.trueLabelTKey || !value.falseLabelTKey) {
|
if (!value.labelTKey || !value.trueLabelTKey || !value.falseLabelTKey) {
|
||||||
value.errorMessage = `label, truelabel and falselabel are required for boolean input '${value.id}'.`;
|
value.errorMessage = `labelTkey, trueLabelTKey and falseLabelTKey are required for boolean input '${value.id}'.`;
|
||||||
}
|
}
|
||||||
return value as BooleanInput;
|
return value as BooleanInput;
|
||||||
default:
|
default:
|
||||||
if (!value.labelTKey || !value.choices) {
|
if (!value.labelTKey || !value.choices) {
|
||||||
value.errorMessage = `label and choices are required for Choice input '${value.id}'.`;
|
value.errorMessage = `labelTKey and choices are required for Choice input '${value.id}'.`;
|
||||||
}
|
}
|
||||||
return value as ChoiceInput;
|
return value as ChoiceInput;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { RefreshResult } from "../SelfServeTypes";
|
import { configContext } from "../../ConfigContext";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { armRequestWithoutPolling } from "../../Utils/arm/request";
|
import { armRequestWithoutPolling } from "../../Utils/arm/request";
|
||||||
import { configContext } from "../../ConfigContext";
|
import { selfServeTraceFailure, selfServeTraceStart, selfServeTraceSuccess } from "../SelfServeTelemetryProcessor";
|
||||||
|
import { RefreshResult } from "../SelfServeTypes";
|
||||||
|
import SqlX from "./SqlX";
|
||||||
import { SqlxServiceResource, UpdateDedicatedGatewayRequestParameters } from "./SqlxTypes";
|
import { SqlxServiceResource, UpdateDedicatedGatewayRequestParameters } from "./SqlxTypes";
|
||||||
|
|
||||||
const apiVersion = "2020-06-01-preview";
|
const apiVersion = "2021-04-01-preview";
|
||||||
|
|
||||||
export enum ResourceStatus {
|
export enum ResourceStatus {
|
||||||
Running = "Running",
|
Running = "Running",
|
||||||
@@ -21,7 +23,7 @@ export interface DedicatedGatewayResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getPath = (subscriptionId: string, resourceGroup: string, name: string): string => {
|
export const getPath = (subscriptionId: string, resourceGroup: string, name: string): string => {
|
||||||
return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${name}/services/sqlx`;
|
return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${name}/services/SqlDedicatedGateway`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateDedicatedGatewayResource = async (sku: string, instances: number): Promise<string> => {
|
export const updateDedicatedGatewayResource = async (sku: string, instances: number): Promise<string> => {
|
||||||
@@ -30,39 +32,66 @@ export const updateDedicatedGatewayResource = async (sku: string, instances: num
|
|||||||
properties: {
|
properties: {
|
||||||
instanceSize: sku,
|
instanceSize: sku,
|
||||||
instanceCount: instances,
|
instanceCount: instances,
|
||||||
serviceType: "Sqlx",
|
serviceType: "SqlDedicatedGateway",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const armRequestResult = await armRequestWithoutPolling({
|
const telemetryData = { ...body, httpMethod: "PUT", selfServeClassName: SqlX.name };
|
||||||
|
const updateTimeStamp = selfServeTraceStart(telemetryData);
|
||||||
|
let armRequestResult;
|
||||||
|
try {
|
||||||
|
armRequestResult = await armRequestWithoutPolling({
|
||||||
host: configContext.ARM_ENDPOINT,
|
host: configContext.ARM_ENDPOINT,
|
||||||
path,
|
path,
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
apiVersion,
|
apiVersion,
|
||||||
body,
|
body,
|
||||||
});
|
});
|
||||||
return armRequestResult.operationStatusUrl;
|
selfServeTraceSuccess(telemetryData, updateTimeStamp);
|
||||||
|
} catch (e) {
|
||||||
|
const failureTelemetry = { ...body, e, selfServeClassName: SqlX.name };
|
||||||
|
selfServeTraceFailure(failureTelemetry, updateTimeStamp);
|
||||||
|
}
|
||||||
|
return armRequestResult?.operationStatusUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteDedicatedGatewayResource = async (): Promise<string> => {
|
export const deleteDedicatedGatewayResource = async (): Promise<string> => {
|
||||||
const path = getPath(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name);
|
const path = getPath(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name);
|
||||||
const armRequestResult = await armRequestWithoutPolling({
|
const telemetryData = { httpMethod: "DELETE", selfServeClassName: SqlX.name };
|
||||||
|
const deleteTimeStamp = selfServeTraceStart(telemetryData);
|
||||||
|
let armRequestResult;
|
||||||
|
try {
|
||||||
|
armRequestResult = await armRequestWithoutPolling({
|
||||||
host: configContext.ARM_ENDPOINT,
|
host: configContext.ARM_ENDPOINT,
|
||||||
path,
|
path,
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
apiVersion,
|
apiVersion,
|
||||||
});
|
});
|
||||||
return armRequestResult.operationStatusUrl;
|
selfServeTraceSuccess(telemetryData, deleteTimeStamp);
|
||||||
|
} catch (e) {
|
||||||
|
const failureTelemetry = { e, selfServeClassName: SqlX.name };
|
||||||
|
selfServeTraceFailure(failureTelemetry, deleteTimeStamp);
|
||||||
|
}
|
||||||
|
return armRequestResult?.operationStatusUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDedicatedGatewayResource = async (): Promise<SqlxServiceResource> => {
|
export const getDedicatedGatewayResource = async (): Promise<SqlxServiceResource> => {
|
||||||
const path = getPath(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name);
|
const path = getPath(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name);
|
||||||
const armRequestResult = await armRequestWithoutPolling<SqlxServiceResource>({
|
const telemetryData = { httpMethod: "GET", selfServeClassName: SqlX.name };
|
||||||
|
const getResourceTimeStamp = selfServeTraceStart(telemetryData);
|
||||||
|
let armRequestResult;
|
||||||
|
try {
|
||||||
|
armRequestResult = await armRequestWithoutPolling<SqlxServiceResource>({
|
||||||
host: configContext.ARM_ENDPOINT,
|
host: configContext.ARM_ENDPOINT,
|
||||||
path,
|
path,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
apiVersion,
|
apiVersion,
|
||||||
});
|
});
|
||||||
return armRequestResult.result;
|
selfServeTraceSuccess(telemetryData, getResourceTimeStamp);
|
||||||
|
} catch (e) {
|
||||||
|
const failureTelemetry = { e, selfServeClassName: SqlX.name };
|
||||||
|
selfServeTraceFailure(failureTelemetry, getResourceTimeStamp);
|
||||||
|
}
|
||||||
|
return armRequestResult?.result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getCurrentProvisioningState = async (): Promise<DedicatedGatewayResponse> => {
|
export const getCurrentProvisioningState = async (): Promise<DedicatedGatewayResponse> => {
|
||||||
|
|||||||
+82
-47
@@ -23,7 +23,7 @@ const costPerHourValue: Description = {
|
|||||||
textTKey: "CostText",
|
textTKey: "CostText",
|
||||||
type: DescriptionType.Text,
|
type: DescriptionType.Text,
|
||||||
link: {
|
link: {
|
||||||
href: "https://azure.microsoft.com/en-us/pricing/details/cosmos-db/",
|
href: "https://aka.ms/cosmos-db-dedicated-gateway-pricing",
|
||||||
textTKey: "DedicatedGatewayPricing",
|
textTKey: "DedicatedGatewayPricing",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -37,43 +37,56 @@ const connectionStringValue: Description = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const metricsStringValue: Description = {
|
||||||
|
textTKey: "MetricsText",
|
||||||
|
type: DescriptionType.Text,
|
||||||
|
link: {
|
||||||
|
href: generateBladeLink(BladeType.Metrics),
|
||||||
|
textTKey: "MetricsBlade",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const resizingDecisionValue: Description = {
|
||||||
|
textTKey: "ResizingDecisionText",
|
||||||
|
type: DescriptionType.Text,
|
||||||
|
link: {
|
||||||
|
href: "https://aka.ms/cosmos-db-dedicated-gateway-size",
|
||||||
|
textTKey: "ResizingDecisionLink",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const CosmosD4s = "Cosmos.D4s";
|
const CosmosD4s = "Cosmos.D4s";
|
||||||
const CosmosD8s = "Cosmos.D8s";
|
const CosmosD8s = "Cosmos.D8s";
|
||||||
const CosmosD16s = "Cosmos.D16s";
|
const CosmosD16s = "Cosmos.D16s";
|
||||||
const CosmosD32s = "Cosmos.D32s";
|
|
||||||
|
|
||||||
const getSKUDetails = (sku: string): string => {
|
|
||||||
if (sku === CosmosD4s) {
|
|
||||||
return "CosmosD4Details";
|
|
||||||
} else if (sku === CosmosD8s) {
|
|
||||||
return "CosmosD8Details";
|
|
||||||
} else if (sku === CosmosD16s) {
|
|
||||||
return "CosmosD16Details";
|
|
||||||
} else if (sku === CosmosD32s) {
|
|
||||||
return "CosmosD32Details";
|
|
||||||
}
|
|
||||||
return "Not Supported Yet";
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSKUChange = (newValue: InputType, currentValues: Map<string, SmartUiInput>): Map<string, SmartUiInput> => {
|
const onSKUChange = (newValue: InputType, currentValues: Map<string, SmartUiInput>): Map<string, SmartUiInput> => {
|
||||||
currentValues.set("sku", { value: newValue });
|
currentValues.set("sku", { value: newValue });
|
||||||
currentValues.set("skuDetails", {
|
|
||||||
value: { textTKey: getSKUDetails(`${newValue.toString()}`), type: DescriptionType.Text } as Description,
|
|
||||||
});
|
|
||||||
currentValues.set("costPerHour", { value: costPerHourValue });
|
currentValues.set("costPerHour", { value: costPerHourValue });
|
||||||
return currentValues;
|
return currentValues;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onNumberOfInstancesChange = (
|
const onNumberOfInstancesChange = (
|
||||||
newValue: InputType,
|
newValue: InputType,
|
||||||
currentValues: Map<string, SmartUiInput>
|
currentValues: Map<string, SmartUiInput>,
|
||||||
|
baselineValues: Map<string, SmartUiInput>
|
||||||
): Map<string, SmartUiInput> => {
|
): Map<string, SmartUiInput> => {
|
||||||
currentValues.set("instances", { value: newValue });
|
currentValues.set("instances", { value: newValue });
|
||||||
|
const dedicatedGatewayOriginallyEnabled = baselineValues.get("enableDedicatedGateway")?.value as boolean;
|
||||||
|
const baselineInstances = baselineValues.get("instances")?.value as number;
|
||||||
|
if (!dedicatedGatewayOriginallyEnabled || baselineInstances !== newValue) {
|
||||||
currentValues.set("warningBanner", {
|
currentValues.set("warningBanner", {
|
||||||
value: { textTKey: "WarningBannerOnUpdate" } as Description,
|
value: {
|
||||||
|
textTKey: "WarningBannerOnUpdate",
|
||||||
|
link: {
|
||||||
|
href: "https://aka.ms/cosmos-db-dedicated-gateway-overview",
|
||||||
|
textTKey: "DedicatedGatewayPricing",
|
||||||
|
},
|
||||||
|
} as Description,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
currentValues.set("warningBanner", undefined);
|
||||||
|
}
|
||||||
return currentValues;
|
return currentValues;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -87,10 +100,11 @@ const onEnableDedicatedGatewayChange = (
|
|||||||
if (dedicatedGatewayOriginallyEnabled === newValue) {
|
if (dedicatedGatewayOriginallyEnabled === newValue) {
|
||||||
currentValues.set("sku", baselineValues.get("sku"));
|
currentValues.set("sku", baselineValues.get("sku"));
|
||||||
currentValues.set("instances", baselineValues.get("instances"));
|
currentValues.set("instances", baselineValues.get("instances"));
|
||||||
currentValues.set("skuDetails", baselineValues.get("skuDetails"));
|
|
||||||
currentValues.set("costPerHour", baselineValues.get("costPerHour"));
|
currentValues.set("costPerHour", baselineValues.get("costPerHour"));
|
||||||
currentValues.set("warningBanner", baselineValues.get("warningBanner"));
|
currentValues.set("warningBanner", baselineValues.get("warningBanner"));
|
||||||
currentValues.set("connectionString", baselineValues.get("connectionString"));
|
currentValues.set("connectionString", baselineValues.get("connectionString"));
|
||||||
|
currentValues.set("metricsString", baselineValues.get("metricsString"));
|
||||||
|
currentValues.set("resizingDecisionString", baselineValues.get("resizingDecisionString"));
|
||||||
return currentValues;
|
return currentValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +114,7 @@ const onEnableDedicatedGatewayChange = (
|
|||||||
value: {
|
value: {
|
||||||
textTKey: "WarningBannerOnUpdate",
|
textTKey: "WarningBannerOnUpdate",
|
||||||
link: {
|
link: {
|
||||||
href: "https://docs.microsoft.com/en-us/azure/cosmos-db/introduction",
|
href: "https://aka.ms/cosmos-db-dedicated-gateway-pricing",
|
||||||
textTKey: "DedicatedGatewayPricing",
|
textTKey: "DedicatedGatewayPricing",
|
||||||
},
|
},
|
||||||
} as Description,
|
} as Description,
|
||||||
@@ -111,7 +125,7 @@ const onEnableDedicatedGatewayChange = (
|
|||||||
value: {
|
value: {
|
||||||
textTKey: "WarningBannerOnDelete",
|
textTKey: "WarningBannerOnDelete",
|
||||||
link: {
|
link: {
|
||||||
href: "https://docs.microsoft.com/en-us/azure/cosmos-db/introduction",
|
href: "https://aka.ms/cosmos-db-dedicated-gateway-overview",
|
||||||
textTKey: "DeprovisioningDetailsText",
|
textTKey: "DeprovisioningDetailsText",
|
||||||
},
|
},
|
||||||
} as Description,
|
} as Description,
|
||||||
@@ -132,18 +146,22 @@ const onEnableDedicatedGatewayChange = (
|
|||||||
disabled: dedicatedGatewayOriginallyEnabled,
|
disabled: dedicatedGatewayOriginallyEnabled,
|
||||||
});
|
});
|
||||||
|
|
||||||
currentValues.set("skuDetails", {
|
|
||||||
value: { textTKey: getSKUDetails(`${currentValues.get("sku").value}`), type: DescriptionType.Text } as Description,
|
|
||||||
hidden: hideAttributes,
|
|
||||||
disabled: dedicatedGatewayOriginallyEnabled,
|
|
||||||
});
|
|
||||||
|
|
||||||
currentValues.set("costPerHour", { value: costPerHourValue, hidden: hideAttributes });
|
currentValues.set("costPerHour", { value: costPerHourValue, hidden: hideAttributes });
|
||||||
currentValues.set("connectionString", {
|
currentValues.set("connectionString", {
|
||||||
value: connectionStringValue,
|
value: connectionStringValue,
|
||||||
hidden: !newValue || !dedicatedGatewayOriginallyEnabled,
|
hidden: !newValue || !dedicatedGatewayOriginallyEnabled,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
currentValues.set("metricsString", {
|
||||||
|
value: metricsStringValue,
|
||||||
|
hidden: !newValue || !dedicatedGatewayOriginallyEnabled,
|
||||||
|
});
|
||||||
|
|
||||||
|
currentValues.set("resizingDecisionString", {
|
||||||
|
value: resizingDecisionValue,
|
||||||
|
hidden: !newValue || !dedicatedGatewayOriginallyEnabled,
|
||||||
|
});
|
||||||
|
|
||||||
return currentValues;
|
return currentValues;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -151,7 +169,6 @@ const skuDropDownItems: ChoiceItem[] = [
|
|||||||
{ labelTKey: "CosmosD4s", key: CosmosD4s },
|
{ labelTKey: "CosmosD4s", key: CosmosD4s },
|
||||||
{ labelTKey: "CosmosD8s", key: CosmosD8s },
|
{ labelTKey: "CosmosD8s", key: CosmosD8s },
|
||||||
{ labelTKey: "CosmosD16s", key: CosmosD16s },
|
{ labelTKey: "CosmosD16s", key: CosmosD16s },
|
||||||
{ labelTKey: "CosmosD32s", key: CosmosD32s },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const getSkus = async (): Promise<ChoiceItem[]> => {
|
const getSkus = async (): Promise<ChoiceItem[]> => {
|
||||||
@@ -184,7 +201,6 @@ export default class SqlX extends SelfServeBaseClass {
|
|||||||
|
|
||||||
currentValues.set("warningBanner", undefined);
|
currentValues.set("warningBanner", undefined);
|
||||||
|
|
||||||
//TODO : Add try catch for each RP call and return relevant notifications
|
|
||||||
if (dedicatedGatewayOriginallyEnabled) {
|
if (dedicatedGatewayOriginallyEnabled) {
|
||||||
if (!dedicatedGatewayCurrentlyEnabled) {
|
if (!dedicatedGatewayCurrentlyEnabled) {
|
||||||
const operationStatusUrl = await deleteDedicatedGatewayResource();
|
const operationStatusUrl = await deleteDedicatedGatewayResource();
|
||||||
@@ -206,9 +222,11 @@ export default class SqlX extends SelfServeBaseClass {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// Check for scaling up/down/in/out
|
const sku = currentValues.get("sku")?.value as string;
|
||||||
|
const instances = currentValues.get("instances").value as number;
|
||||||
|
const operationStatusUrl = await updateDedicatedGatewayResource(sku, instances);
|
||||||
return {
|
return {
|
||||||
operationStatusUrl: undefined,
|
operationStatusUrl: operationStatusUrl,
|
||||||
portalNotification: {
|
portalNotification: {
|
||||||
initialize: {
|
initialize: {
|
||||||
titleTKey: "UpdateInitializeTitle",
|
titleTKey: "UpdateInitializeTitle",
|
||||||
@@ -255,24 +273,37 @@ export default class SqlX extends SelfServeBaseClass {
|
|||||||
defaults.set("enableDedicatedGateway", { value: false });
|
defaults.set("enableDedicatedGateway", { value: false });
|
||||||
defaults.set("sku", { value: CosmosD4s, hidden: true });
|
defaults.set("sku", { value: CosmosD4s, hidden: true });
|
||||||
defaults.set("instances", { value: await getInstancesMin(), hidden: true });
|
defaults.set("instances", { value: await getInstancesMin(), hidden: true });
|
||||||
defaults.set("skuDetails", undefined);
|
|
||||||
defaults.set("costPerHour", undefined);
|
defaults.set("costPerHour", undefined);
|
||||||
defaults.set("connectionString", undefined);
|
defaults.set("connectionString", undefined);
|
||||||
|
defaults.set("metricsString", {
|
||||||
|
value: undefined,
|
||||||
|
hidden: true,
|
||||||
|
});
|
||||||
|
defaults.set("resizingDecisionString", {
|
||||||
|
value: undefined,
|
||||||
|
hidden: true,
|
||||||
|
});
|
||||||
|
|
||||||
const response = await getCurrentProvisioningState();
|
const response = await getCurrentProvisioningState();
|
||||||
if (response.status && response.status !== "Deleting") {
|
if (response.status && response.status !== "Deleting") {
|
||||||
defaults.set("enableDedicatedGateway", { value: true });
|
defaults.set("enableDedicatedGateway", { value: true });
|
||||||
defaults.set("sku", { value: response.sku, disabled: true });
|
defaults.set("sku", { value: response.sku, disabled: true });
|
||||||
defaults.set("instances", { value: response.instances, disabled: true });
|
defaults.set("instances", { value: response.instances, disabled: false });
|
||||||
defaults.set("costPerHour", { value: costPerHourValue });
|
defaults.set("costPerHour", { value: costPerHourValue });
|
||||||
defaults.set("skuDetails", {
|
|
||||||
value: { textTKey: getSKUDetails(`${defaults.get("sku").value}`), type: DescriptionType.Text } as Description,
|
|
||||||
hidden: false,
|
|
||||||
});
|
|
||||||
defaults.set("connectionString", {
|
defaults.set("connectionString", {
|
||||||
value: connectionStringValue,
|
value: connectionStringValue,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
defaults.set("metricsString", {
|
||||||
|
value: metricsStringValue,
|
||||||
|
hidden: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
defaults.set("resizingDecisionString", {
|
||||||
|
value: resizingDecisionValue,
|
||||||
|
hidden: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
defaults.set("warningBanner", undefined);
|
defaults.set("warningBanner", undefined);
|
||||||
@@ -289,7 +320,7 @@ export default class SqlX extends SelfServeBaseClass {
|
|||||||
textTKey: "DedicatedGatewayDescription",
|
textTKey: "DedicatedGatewayDescription",
|
||||||
type: DescriptionType.Text,
|
type: DescriptionType.Text,
|
||||||
link: {
|
link: {
|
||||||
href: "https://docs.microsoft.com/en-us/azure/cosmos-db/introduction",
|
href: "https://aka.ms/cosmos-db-dedicated-gateway-overview",
|
||||||
textTKey: "LearnAboutDedicatedGateway",
|
textTKey: "LearnAboutDedicatedGateway",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -312,12 +343,6 @@ export default class SqlX extends SelfServeBaseClass {
|
|||||||
})
|
})
|
||||||
sku: ChoiceItem;
|
sku: ChoiceItem;
|
||||||
|
|
||||||
@Values({
|
|
||||||
labelTKey: "SKUDetails",
|
|
||||||
isDynamicDescription: true,
|
|
||||||
})
|
|
||||||
skuDetails: string;
|
|
||||||
|
|
||||||
@OnChange(onNumberOfInstancesChange)
|
@OnChange(onNumberOfInstancesChange)
|
||||||
@Values({
|
@Values({
|
||||||
labelTKey: "NumberOfInstances",
|
labelTKey: "NumberOfInstances",
|
||||||
@@ -328,6 +353,16 @@ export default class SqlX extends SelfServeBaseClass {
|
|||||||
})
|
})
|
||||||
instances: number;
|
instances: number;
|
||||||
|
|
||||||
|
@Values({
|
||||||
|
description: metricsStringValue,
|
||||||
|
})
|
||||||
|
metricsString: string;
|
||||||
|
|
||||||
|
@Values({
|
||||||
|
description: resizingDecisionValue,
|
||||||
|
})
|
||||||
|
resizingDecisionString: string;
|
||||||
|
|
||||||
@Values({
|
@Values({
|
||||||
labelTKey: "Cost",
|
labelTKey: "Cost",
|
||||||
isDynamicDescription: true,
|
isDynamicDescription: true,
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import "@jupyterlab/terminal/style/index.css";
|
|
||||||
import "./index.css";
|
|
||||||
import { ServerConnection } from "@jupyterlab/services";
|
import { ServerConnection } from "@jupyterlab/services";
|
||||||
import { JupyterLabAppFactory } from "./JupyterLabAppFactory";
|
import "@jupyterlab/terminal/style/index.css";
|
||||||
|
import { HttpHeaders, TerminalQueryParams } from "../Common/Constants";
|
||||||
import { Action } from "../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { updateUserContext } from "../UserContext";
|
import { updateUserContext } from "../UserContext";
|
||||||
import { HttpHeaders, TerminalQueryParams } from "../Common/Constants";
|
import "./index.css";
|
||||||
|
import { JupyterLabAppFactory } from "./JupyterLabAppFactory";
|
||||||
|
|
||||||
const getUrlVars = (): { [key: string]: string } => {
|
const getUrlVars = (): { [key: string]: string } => {
|
||||||
const vars: { [key: string]: string } = {};
|
const vars: { [key: string]: string } = {};
|
||||||
@@ -50,7 +50,7 @@ const createServerSettings = (urlVars: { [key: string]: string }): ServerConnect
|
|||||||
const main = async (): Promise<void> => {
|
const main = async (): Promise<void> => {
|
||||||
const urlVars = getUrlVars();
|
const urlVars = getUrlVars();
|
||||||
|
|
||||||
// Initialize userContext. Currently only subscrptionId is required by TelemetryProcessor
|
// Initialize userContext. Currently only subscriptionId is required by TelemetryProcessor
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
subscriptionId: urlVars[TerminalQueryParams.SubscriptionId],
|
subscriptionId: urlVars[TerminalQueryParams.SubscriptionId],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ interface UserContext {
|
|||||||
readonly resourceGroup?: string;
|
readonly resourceGroup?: string;
|
||||||
readonly databaseAccount?: DatabaseAccount;
|
readonly databaseAccount?: DatabaseAccount;
|
||||||
readonly endpoint?: string;
|
readonly endpoint?: string;
|
||||||
|
readonly aadToken?: string;
|
||||||
readonly accessToken?: string;
|
readonly accessToken?: string;
|
||||||
readonly authorizationToken?: string;
|
readonly authorizationToken?: string;
|
||||||
readonly resourceToken?: string;
|
readonly resourceToken?: string;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ interface ReturnType {
|
|||||||
isLoggedIn: boolean;
|
isLoggedIn: boolean;
|
||||||
graphToken: string;
|
graphToken: string;
|
||||||
armToken: string;
|
armToken: string;
|
||||||
|
aadToken: string;
|
||||||
login: () => void;
|
login: () => void;
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
tenantId: string;
|
tenantId: string;
|
||||||
@@ -40,6 +41,7 @@ export function useAADAuth(): ReturnType {
|
|||||||
const [tenantId, setTenantId] = React.useState<string>(cachedTenantId);
|
const [tenantId, setTenantId] = React.useState<string>(cachedTenantId);
|
||||||
const [graphToken, setGraphToken] = React.useState<string>();
|
const [graphToken, setGraphToken] = React.useState<string>();
|
||||||
const [armToken, setArmToken] = React.useState<string>();
|
const [armToken, setArmToken] = React.useState<string>();
|
||||||
|
const [aadToken, setAadToken] = React.useState<string>();
|
||||||
|
|
||||||
msalInstance.setActiveAccount(account);
|
msalInstance.setActiveAccount(account);
|
||||||
const login = React.useCallback(async () => {
|
const login = React.useCallback(async () => {
|
||||||
@@ -79,9 +81,13 @@ export function useAADAuth(): ReturnType {
|
|||||||
authority: `https://login.microsoftonline.com/${tenantId}`,
|
authority: `https://login.microsoftonline.com/${tenantId}`,
|
||||||
scopes: ["https://management.azure.com//.default"],
|
scopes: ["https://management.azure.com//.default"],
|
||||||
}),
|
}),
|
||||||
]).then(([graphTokenResponse, armTokenResponse]) => {
|
msalInstance.acquireTokenSilent({
|
||||||
|
scopes: ["https://cosmos.azure.com/.default"],
|
||||||
|
}),
|
||||||
|
]).then(([graphTokenResponse, armTokenResponse, aadTokenResponse]) => {
|
||||||
setGraphToken(graphTokenResponse.accessToken);
|
setGraphToken(graphTokenResponse.accessToken);
|
||||||
setArmToken(armTokenResponse.accessToken);
|
setArmToken(armTokenResponse.accessToken);
|
||||||
|
setAadToken(aadTokenResponse.accessToken);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [account, tenantId]);
|
}, [account, tenantId]);
|
||||||
@@ -92,6 +98,7 @@ export function useAADAuth(): ReturnType {
|
|||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
graphToken,
|
graphToken,
|
||||||
armToken,
|
armToken,
|
||||||
|
aadToken,
|
||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
switchTenant,
|
switchTenant,
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ async function configureHostedWithAAD(config: AAD, explorerParams: ExplorerParam
|
|||||||
updateUserContext({
|
updateUserContext({
|
||||||
authType: AuthType.AAD,
|
authType: AuthType.AAD,
|
||||||
authorizationToken: `Bearer ${config.authorizationToken}`,
|
authorizationToken: `Bearer ${config.authorizationToken}`,
|
||||||
|
aadToken: config.aadToken,
|
||||||
});
|
});
|
||||||
const account = config.databaseAccount;
|
const account = config.databaseAccount;
|
||||||
const accountResourceId = account.id;
|
const accountResourceId = account.id;
|
||||||
|
|||||||
+1
-2
@@ -8,8 +8,7 @@ i18n
|
|||||||
.init({
|
.init({
|
||||||
fallbackLng: "en",
|
fallbackLng: "en",
|
||||||
detection: { order: ["navigator", "cookie", "localStorage", "sessionStorage", "querystring", "htmlTag"] },
|
detection: { order: ["navigator", "cookie", "localStorage", "sessionStorage", "querystring", "htmlTag"] },
|
||||||
// temporarily setting debug to true to investigate loading issues in prod
|
debug: process.env.NODE_ENV === "development",
|
||||||
debug: true,
|
|
||||||
keySeparator: ".",
|
keySeparator: ".",
|
||||||
interpolation: {
|
interpolation: {
|
||||||
formatSeparator: ",",
|
formatSeparator: ",",
|
||||||
|
|||||||
@@ -15,16 +15,16 @@ test("Cassandra keyspace and table CRUD", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await explorer.click('[data-test="New Table"]');
|
await explorer.click('[data-test="New Table"]');
|
||||||
await explorer.click('[data-test="addCollection-keyspaceId"]');
|
await explorer.click('[aria-label="Keyspace id"]');
|
||||||
await explorer.fill('[data-test="addCollection-keyspaceId"]', keyspaceId);
|
await explorer.fill('[aria-label="Keyspace id"]', keyspaceId);
|
||||||
await explorer.click('[data-test="addCollection-tableId"]');
|
await explorer.click('[aria-label="addCollection-tableId"]');
|
||||||
await explorer.fill('[data-test="addCollection-tableId"]', tableId);
|
await explorer.fill('[aria-label="addCollection-tableId"]', tableId);
|
||||||
await explorer.click('[aria-label="Add Table"] [data-test="addCollection-createCollection"]');
|
await explorer.click("#sidePanelOkButton");
|
||||||
await safeClick(explorer, `.nodeItem >> text=${keyspaceId}`);
|
await safeClick(explorer, `.nodeItem >> text=${keyspaceId}`);
|
||||||
await safeClick(explorer, `[data-test="${tableId}"] [aria-label="More"]`);
|
await safeClick(explorer, `[data-test="${tableId}"] [aria-label="More"]`);
|
||||||
await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Table")');
|
await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Table")');
|
||||||
await explorer.fill('text=* Confirm by typing the table id >> input[type="text"]', tableId);
|
await explorer.fill('text=* Confirm by typing the table id >> input[type="text"]', tableId);
|
||||||
await explorer.click('[aria-label="Submit"]');
|
await explorer.click('[aria-label="OK"]');
|
||||||
await explorer.click(`[data-test="${keyspaceId}"] [aria-label="More"]`);
|
await explorer.click(`[data-test="${keyspaceId}"] [aria-label="More"]`);
|
||||||
await explorer.click('button[role="menuitem"]:has-text("Delete Keyspace")');
|
await explorer.click('button[role="menuitem"]:has-text("Delete Keyspace")');
|
||||||
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
|
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ test("Graph CRUD", async () => {
|
|||||||
await safeClick(explorer, `[data-test="${containerId}"] [aria-label="More"]`);
|
await safeClick(explorer, `[data-test="${containerId}"] [aria-label="More"]`);
|
||||||
await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Graph")');
|
await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Graph")');
|
||||||
await explorer.fill('text=* Confirm by typing the graph id >> input[type="text"]', containerId);
|
await explorer.fill('text=* Confirm by typing the graph id >> input[type="text"]', containerId);
|
||||||
await explorer.click('[aria-label="Submit"]');
|
await explorer.click('[aria-label="OK"]');
|
||||||
await explorer.click(`[data-test="${databaseId}"] [aria-label="More"]`);
|
await explorer.click(`[data-test="${databaseId}"] [aria-label="More"]`);
|
||||||
await explorer.click('button[role="menuitem"]:has-text("Delete Database")');
|
await explorer.click('button[role="menuitem"]:has-text("Delete Database")');
|
||||||
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
|
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ test("Mongo CRUD", async () => {
|
|||||||
await safeClick(explorer, `.nodeItem >> text=${databaseId}`);
|
await safeClick(explorer, `.nodeItem >> text=${databaseId}`);
|
||||||
await safeClick(explorer, `.nodeItem >> text=${containerId}`);
|
await safeClick(explorer, `.nodeItem >> text=${containerId}`);
|
||||||
// Create indexing policy
|
// Create indexing policy
|
||||||
await safeClick(explorer, ".nodeItem >> text=Setting");
|
await safeClick(explorer, ".nodeItem >> text=Settings");
|
||||||
await explorer.click('button[role="tab"]:has-text("Indexing Policy")');
|
await explorer.click('button[role="tab"]:has-text("Indexing Policy")');
|
||||||
await explorer.click('[aria-label="Index Field Name 0"]');
|
await explorer.click('[aria-label="Index Field Name 0"]');
|
||||||
await explorer.fill('[aria-label="Index Field Name 0"]', "foo");
|
await explorer.fill('[aria-label="Index Field Name 0"]', "foo");
|
||||||
@@ -37,7 +37,7 @@ test("Mongo CRUD", async () => {
|
|||||||
await safeClick(explorer, `[data-test="${containerId}"] [aria-label="More"]`);
|
await safeClick(explorer, `[data-test="${containerId}"] [aria-label="More"]`);
|
||||||
await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Collection")');
|
await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Collection")');
|
||||||
await explorer.fill('text=* Confirm by typing the collection id >> input[type="text"]', containerId);
|
await explorer.fill('text=* Confirm by typing the collection id >> input[type="text"]', containerId);
|
||||||
await explorer.click('[aria-label="Submit"]');
|
await explorer.click('[aria-label="OK"]');
|
||||||
await explorer.click(`[data-test="${databaseId}"] [aria-label="More"]`);
|
await explorer.click(`[data-test="${databaseId}"] [aria-label="More"]`);
|
||||||
await explorer.click('button[role="menuitem"]:has-text("Delete Database")');
|
await explorer.click('button[role="menuitem"]:has-text("Delete Database")');
|
||||||
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
|
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ test("Mongo CRUD", async () => {
|
|||||||
await safeClick(explorer, `[data-test="${containerId}"] [aria-label="More"]`);
|
await safeClick(explorer, `[data-test="${containerId}"] [aria-label="More"]`);
|
||||||
await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Collection")');
|
await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Collection")');
|
||||||
await explorer.fill('text=* Confirm by typing the collection id >> input[type="text"]', containerId);
|
await explorer.fill('text=* Confirm by typing the collection id >> input[type="text"]', containerId);
|
||||||
await explorer.click('[aria-label="Submit"]');
|
await explorer.click('[aria-label="OK"]');
|
||||||
await explorer.click(`[data-test="${databaseId}"] [aria-label="More"]`);
|
await explorer.click(`[data-test="${databaseId}"] [aria-label="More"]`);
|
||||||
await explorer.click('button[role="menuitem"]:has-text("Delete Database")');
|
await explorer.click('button[role="menuitem"]:has-text("Delete Database")');
|
||||||
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
|
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ test("SQL CRUD", async () => {
|
|||||||
await safeClick(explorer, `[data-test="${containerId}"] [aria-label="More"]`);
|
await safeClick(explorer, `[data-test="${containerId}"] [aria-label="More"]`);
|
||||||
await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Container")');
|
await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Container")');
|
||||||
await explorer.fill('text=* Confirm by typing the container id >> input[type="text"]', containerId);
|
await explorer.fill('text=* Confirm by typing the container id >> input[type="text"]', containerId);
|
||||||
await explorer.click('[aria-label="Submit"]');
|
await explorer.click('[aria-label="OK"]');
|
||||||
await explorer.click(`[data-test="${databaseId}"] [aria-label="More"]`);
|
await explorer.click(`[data-test="${databaseId}"] [aria-label="More"]`);
|
||||||
await explorer.click('button[role="menuitem"]:has-text("Delete Database")');
|
await explorer.click('button[role="menuitem"]:has-text("Delete Database")');
|
||||||
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
|
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
|
||||||
|
|||||||
@@ -21,6 +21,6 @@ test("Tables CRUD", async () => {
|
|||||||
await safeClick(explorer, `[data-test="${tableId}"] [aria-label="More"]`);
|
await safeClick(explorer, `[data-test="${tableId}"] [aria-label="More"]`);
|
||||||
await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Table")');
|
await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Table")');
|
||||||
await explorer.fill('text=* Confirm by typing the table id >> input[type="text"]', tableId);
|
await explorer.fill('text=* Confirm by typing the table id >> input[type="text"]', tableId);
|
||||||
await explorer.click('[aria-label="Submit"]');
|
await explorer.click('[aria-label="OK"]');
|
||||||
await expect(explorer).not.toHaveText(".dataResourceTree", tableId);
|
await expect(explorer).not.toHaveText(".dataResourceTree", tableId);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -60,7 +60,6 @@
|
|||||||
"./src/Explorer/Notebook/NotebookUtil.ts",
|
"./src/Explorer/Notebook/NotebookUtil.ts",
|
||||||
"./src/Explorer/OpenFullScreen.test.tsx",
|
"./src/Explorer/OpenFullScreen.test.tsx",
|
||||||
"./src/Explorer/OpenFullScreen.tsx",
|
"./src/Explorer/OpenFullScreen.tsx",
|
||||||
"./src/Explorer/Panes/PaneComponents.ts",
|
|
||||||
"./src/Explorer/Panes/PanelFooterComponent.tsx",
|
"./src/Explorer/Panes/PanelFooterComponent.tsx",
|
||||||
"./src/Explorer/Panes/PanelInfoErrorComponent.tsx",
|
"./src/Explorer/Panes/PanelInfoErrorComponent.tsx",
|
||||||
"./src/Explorer/Panes/PanelLoadingScreen.tsx",
|
"./src/Explorer/Panes/PanelLoadingScreen.tsx",
|
||||||
|
|||||||
Reference in New Issue
Block a user