mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-25 12:44:06 +00:00
Compare commits
9 Commits
genericRig
...
configure-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88ef4ea9ed | ||
|
|
6a28bd4898 | ||
|
|
2a2f55ff28 | ||
|
|
ac743d9efc | ||
|
|
8a5f8cb31e | ||
|
|
f0fe29a3b0 | ||
|
|
00f649643b | ||
|
|
aae4036e80 | ||
|
|
ff3ea402d7 |
@@ -3,8 +3,8 @@ module.exports = {
|
|||||||
browser: true,
|
browser: true,
|
||||||
es6: true,
|
es6: true,
|
||||||
},
|
},
|
||||||
plugins: ["@typescript-eslint", "no-null", "prefer-arrow", "react-hooks"],
|
plugins: ["@typescript-eslint", "no-null", "prefer-arrow", "react-hooks", "jsx-a11y"],
|
||||||
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:jsx-a11y/recommended"],
|
||||||
globals: {
|
globals: {
|
||||||
Atomics: "readonly",
|
Atomics: "readonly",
|
||||||
SharedArrayBuffer: "readonly",
|
SharedArrayBuffer: "readonly",
|
||||||
@@ -34,6 +34,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
|
"jsx-a11y/anchor-is-valid": 1,
|
||||||
"no-console": ["error", { allow: ["error", "warn", "dir"] }],
|
"no-console": ["error", { allow: ["error", "warn", "dir"] }],
|
||||||
curly: "error",
|
curly: "error",
|
||||||
"@typescript-eslint/switch-exhaustiveness-check": "error",
|
"@typescript-eslint/switch-exhaustiveness-check": "error",
|
||||||
|
|||||||
76
package-lock.json
generated
76
package-lock.json
generated
@@ -6723,6 +6723,12 @@
|
|||||||
"integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=",
|
"integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"ast-types-flow": {
|
||||||
|
"version": "0.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
|
||||||
|
"integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"astral-regex": {
|
"astral-regex": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
|
||||||
@@ -6784,6 +6790,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
|
||||||
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
|
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
|
||||||
},
|
},
|
||||||
|
"axe-core": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-1uIESzroqpaTzt9uX48HO+6gfnKu3RwvWdCcWSrX4csMInJfCo1yvKPNXCwXFRpJqRW25tiASb6No0YH57PXqg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"axios": {
|
"axios": {
|
||||||
"version": "0.21.1",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
|
||||||
@@ -6799,6 +6811,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"axobject-query": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"babel-code-frame": {
|
"babel-code-frame": {
|
||||||
"version": "6.26.0",
|
"version": "6.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
|
||||||
@@ -9087,6 +9105,12 @@
|
|||||||
"d3-transition": "2"
|
"d3-transition": "2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"damerau-levenshtein": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-VvdQIPGdWP0SqFXghj79Wf/5LArmreyMsGLa6FG6iC4t3j7j5s71TrwWmT/4akbDQIqjfACkLZmjXhA7g2oUZw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"dashdash": {
|
"dashdash": {
|
||||||
"version": "1.14.1",
|
"version": "1.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||||
@@ -10436,6 +10460,43 @@
|
|||||||
"@typescript-eslint/experimental-utils": "^2.5.0"
|
"@typescript-eslint/experimental-utils": "^2.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"eslint-plugin-jsx-a11y": {
|
||||||
|
"version": "6.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz",
|
||||||
|
"integrity": "sha512-0rGPJBbwHoGNPU73/QCLP/vveMlM1b1Z9PponxO87jfr6tuH5ligXbDT6nHSSzBC8ovX2Z+BQu7Bk5D/Xgq9zg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.11.2",
|
||||||
|
"aria-query": "^4.2.2",
|
||||||
|
"array-includes": "^3.1.1",
|
||||||
|
"ast-types-flow": "^0.0.7",
|
||||||
|
"axe-core": "^4.0.2",
|
||||||
|
"axobject-query": "^2.2.0",
|
||||||
|
"damerau-levenshtein": "^1.0.6",
|
||||||
|
"emoji-regex": "^9.0.0",
|
||||||
|
"has": "^1.0.3",
|
||||||
|
"jsx-ast-utils": "^3.1.0",
|
||||||
|
"language-tags": "^1.0.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": {
|
||||||
|
"version": "9.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||||
|
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"jsx-ast-utils": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"array-includes": "^3.1.2",
|
||||||
|
"object.assign": "^4.1.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"eslint-plugin-no-null": {
|
"eslint-plugin-no-null": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-no-null/-/eslint-plugin-no-null-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-no-null/-/eslint-plugin-no-null-1.0.2.tgz",
|
||||||
@@ -18239,6 +18300,21 @@
|
|||||||
"resolved": "https://registry.npmjs.org/labella/-/labella-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/labella/-/labella-1.1.4.tgz",
|
||||||
"integrity": "sha1-xsxaNA6N80DrM1YzaD6lm4KMMi0="
|
"integrity": "sha1-xsxaNA6N80DrM1YzaD6lm4KMMi0="
|
||||||
},
|
},
|
||||||
|
"language-subtag-registry": {
|
||||||
|
"version": "0.3.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz",
|
||||||
|
"integrity": "sha512-L0IqwlIXjilBVVYKFT37X9Ih11Um5NEl9cbJIuU/SwP/zEEAbBPOnEeeuxVMf45ydWQRDQN3Nqc96OgbH1K+Pg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"language-tags": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz",
|
||||||
|
"integrity": "sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"language-subtag-registry": "~0.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"lcid": {
|
"lcid": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
|
||||||
|
|||||||
@@ -142,6 +142,7 @@
|
|||||||
"enzyme-to-json": "3.6.1",
|
"enzyme-to-json": "3.6.1",
|
||||||
"eslint": "7.8.1",
|
"eslint": "7.8.1",
|
||||||
"eslint-cli": "1.1.1",
|
"eslint-cli": "1.1.1",
|
||||||
|
"eslint-plugin-jsx-a11y": "6.4.1",
|
||||||
"eslint-plugin-no-null": "1.0.2",
|
"eslint-plugin-no-null": "1.0.2",
|
||||||
"eslint-plugin-prefer-arrow": "1.2.2",
|
"eslint-plugin-prefer-arrow": "1.2.2",
|
||||||
"eslint-plugin-react-hooks": "4.2.0",
|
"eslint-plugin-react-hooks": "4.2.0",
|
||||||
|
|||||||
@@ -14,17 +14,23 @@ export const CollapsedResourceTree: FunctionComponent<CollapsedResourceTreeProps
|
|||||||
<div id="mini" className={!isLeftPaneExpanded ? "mini toggle-mini" : "hiddenMain"}>
|
<div id="mini" className={!isLeftPaneExpanded ? "mini toggle-mini" : "hiddenMain"}>
|
||||||
<div className="main-nav nav">
|
<div className="main-nav nav">
|
||||||
<ul className="nav">
|
<ul className="nav">
|
||||||
<li
|
<li className="resourceTreeCollapse" id="collapseToggleLeftPaneButton" aria-label="Expand Tree">
|
||||||
className="resourceTreeCollapse"
|
<span
|
||||||
id="collapseToggleLeftPaneButton"
|
className="leftarrowCollapsed"
|
||||||
role="button"
|
onClick={toggleLeftPaneExpanded}
|
||||||
tabIndex={0}
|
role="button"
|
||||||
aria-label="Expand Tree"
|
tabIndex={0}
|
||||||
>
|
onKeyDown={toggleLeftPaneExpanded}
|
||||||
<span className="leftarrowCollapsed" onClick={toggleLeftPaneExpanded}>
|
>
|
||||||
<img className="arrowCollapsed" src={arrowLeftImg} alt="Expand" />
|
<img className="arrowCollapsed" src={arrowLeftImg} alt="Expand" />
|
||||||
</span>
|
</span>
|
||||||
<span className="collectionCollapsed" onClick={toggleLeftPaneExpanded}>
|
<span
|
||||||
|
className="collectionCollapsed"
|
||||||
|
onClick={toggleLeftPaneExpanded}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyDown={toggleLeftPaneExpanded}
|
||||||
|
>
|
||||||
<span data-bind="text: collectionTitle" />
|
<span data-bind="text: collectionTitle" />
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
|
|||||||
<TextField
|
<TextField
|
||||||
label={entityValueLabel && entityValueLabel}
|
label={entityValueLabel && entityValueLabel}
|
||||||
id="entityTimeId"
|
id="entityTimeId"
|
||||||
autoFocus
|
|
||||||
type="time"
|
type="time"
|
||||||
value={entityTimeValue}
|
value={entityTimeValue}
|
||||||
onChange={onEntityTimeValueChange}
|
onChange={onEntityTimeValueChange}
|
||||||
@@ -55,7 +54,6 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
|
|||||||
label={entityValueLabel && entityValueLabel}
|
label={entityValueLabel && entityValueLabel}
|
||||||
className="addEntityTextField"
|
className="addEntityTextField"
|
||||||
id="entityValueId"
|
id="entityValueId"
|
||||||
autoFocus
|
|
||||||
disabled={isEntityValueDisable}
|
disabled={isEntityValueDisable}
|
||||||
type={entityValueType}
|
type={entityValueType}
|
||||||
placeholder={entityValuePlaceholder}
|
placeholder={entityValuePlaceholder}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export const ResourceTree: FunctionComponent<ResourceTreeProps> = ({
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
aria-label="Collapse Tree"
|
aria-label="Collapse Tree"
|
||||||
title="Collapse Tree"
|
title="Collapse Tree"
|
||||||
|
onKeyDown={toggleLeftPaneExpanded}
|
||||||
>
|
>
|
||||||
<img className="refreshcol1" src={arrowLeftImg} alt="Hide" />
|
<img className="refreshcol1" src={arrowLeftImg} alt="Hide" />
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
|
|||||||
<TextField
|
<TextField
|
||||||
label={entityPropertyLabel && entityPropertyLabel}
|
label={entityPropertyLabel && entityPropertyLabel}
|
||||||
id="entityPropertyId"
|
id="entityPropertyId"
|
||||||
autoFocus
|
|
||||||
disabled={isPropertyTypeDisable}
|
disabled={isPropertyTypeDisable}
|
||||||
placeholder={entityPropertyPlaceHolder}
|
placeholder={entityPropertyPlaceHolder}
|
||||||
value={entityProperty}
|
value={entityProperty}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export const Upload: FunctionComponent<UploadProps> = ({
|
|||||||
props.onUpload(event);
|
props.onUpload(event);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const title = label + " to upload";
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<span className="renewUploadItemsHeader">{label}</span>
|
<span className="renewUploadItemsHeader">{label}</span>
|
||||||
@@ -67,7 +67,7 @@ export const Upload: FunctionComponent<UploadProps> = ({
|
|||||||
role="button"
|
role="button"
|
||||||
/>
|
/>
|
||||||
<a href="#" id="fileImportLinkNotebook" onClick={onImportLinkClick} onKeyPress={onImportLinkKeyPress}>
|
<a href="#" id="fileImportLinkNotebook" onClick={onImportLinkClick} onKeyPress={onImportLinkKeyPress}>
|
||||||
<Image className="fileImportImg" src={FolderIcon} alt={title} title={title} />
|
<Image className="fileImportImg" src={FolderIcon} alt={label} title={label} />
|
||||||
</a>
|
</a>
|
||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,4 +13,6 @@ ko.components.register("dynamic-list", DynamicListComponent);
|
|||||||
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
|
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
|
||||||
|
|
||||||
// Panes
|
// Panes
|
||||||
|
|
||||||
|
ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent());
|
||||||
ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent());
|
ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent());
|
||||||
|
|||||||
@@ -32,6 +32,9 @@
|
|||||||
.errorLink {
|
.errorLink {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
.errorLinkColor {
|
||||||
|
color: @AccentMediumHigh;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.paneErrorIcon {
|
.paneErrorIcon {
|
||||||
|
|||||||
123
src/Explorer/Controls/NotebookGallery/CodeOfConductComponent.tsx
Normal file
123
src/Explorer/Controls/NotebookGallery/CodeOfConductComponent.tsx
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`CodeOfConduct renders 1`] = `
|
exports[`CodeOfConductComponent renders 1`] = `
|
||||||
<Stack
|
<Stack
|
||||||
tokens={
|
tokens={
|
||||||
Object {
|
Object {
|
||||||
@@ -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("CodeOfConduct", () => {
|
describe("CodeOfConductComponent", () => {
|
||||||
let codeOfConductProps: CodeOfConductProps;
|
let codeOfConductProps: CodeOfConductComponentProps;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const junoClient = new JunoClient();
|
const junoClient = new JunoClient();
|
||||||
@@ -21,12 +21,12 @@ describe("CodeOfConduct", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
const wrapper = shallow(<CodeOfConduct {...codeOfConductProps} />);
|
const wrapper = shallow(<CodeOfConductComponent {...codeOfConductProps} />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("onAcceptedCodeOfConductCalled", async () => {
|
it("onAcceptedCodeOfConductCalled", async () => {
|
||||||
const wrapper = shallow(<CodeOfConduct {...codeOfConductProps} />);
|
const wrapper = shallow(<CodeOfConductComponent {...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();
|
||||||
@@ -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 CodeOfConductProps {
|
export interface CodeOfConductComponentProps {
|
||||||
junoClient: JunoClient;
|
junoClient: JunoClient;
|
||||||
onAcceptCodeOfConduct: (result: boolean) => void;
|
onAcceptCodeOfConduct: (result: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CodeOfConduct: FunctionComponent<CodeOfConductProps> = ({
|
export const CodeOfConductComponent: FunctionComponent<CodeOfConductComponentProps> = ({
|
||||||
junoClient,
|
junoClient,
|
||||||
onAcceptCodeOfConduct,
|
onAcceptCodeOfConduct,
|
||||||
}: CodeOfConductProps) => {
|
}: CodeOfConductComponentProps) => {
|
||||||
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 CodeOfConduct: FunctionComponent<CodeOfConductProps> = ({
|
|||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
|
|
||||||
handleError(error, "CodeOfConduct/acceptCodeOfConduct", "Failed to accept code of conduct");
|
handleError(error, "CodeOfConductComponent/acceptCodeOfConduct", "Failed to accept code of conduct");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -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 { CodeOfConduct } from "./CodeOfConduct/CodeOfConduct";
|
import { CodeOfConductComponent } from "./CodeOfConductComponent";
|
||||||
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">
|
||||||
<CodeOfConduct
|
<CodeOfConductComponent
|
||||||
junoClient={this.props.junoClient}
|
junoClient={this.props.junoClient}
|
||||||
onAcceptCodeOfConduct={(result: boolean) => {
|
onAcceptCodeOfConduct={(result: boolean) => {
|
||||||
this.setState({ isCodeOfConductAccepted: result });
|
this.setState({ isCodeOfConductAccepted: result });
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export class RadioSwitchComponent extends React.Component<RadioSwitchComponentPr
|
|||||||
<div className="radioSwitchComponent">
|
<div className="radioSwitchComponent">
|
||||||
{this.props.choices.map((choice: Choice) => (
|
{this.props.choices.map((choice: Choice) => (
|
||||||
<span
|
<span
|
||||||
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
key={choice.key}
|
key={choice.key}
|
||||||
onClick={() => this.onSelect(choice)}
|
onClick={() => this.onSelect(choice)}
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export class IndexingPolicyComponent extends React.Component<
|
|||||||
{isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && (
|
{isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && (
|
||||||
<MessageBar messageBarType={MessageBarType.warning}>{indexingPolicynUnsavedWarningMessage}</MessageBar>
|
<MessageBar messageBarType={MessageBarType.warning}>{indexingPolicynUnsavedWarningMessage}</MessageBar>
|
||||||
)}
|
)}
|
||||||
<div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.indexingPolicyDiv}></div>
|
<div className="settingsV2IndexingPolicyEditor" role="button" tabIndex={0} ref={this.indexingPolicyDiv}></div>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ exports[`IndexingPolicyComponent renders 1`] = `
|
|||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className="settingsV2IndexingPolicyEditor"
|
className="settingsV2IndexingPolicyEditor"
|
||||||
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -35,6 +35,49 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"_refreshSparkEnabledStateForAccount": [Function],
|
"_refreshSparkEnabledStateForAccount": [Function],
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"addCollectionText": [Function],
|
"addCollectionText": [Function],
|
||||||
|
"addDatabasePane": AddDatabasePane {
|
||||||
|
"autoPilotUsageCost": [Function],
|
||||||
|
"canConfigureThroughput": [Function],
|
||||||
|
"canExceedMaximumValue": [Function],
|
||||||
|
"canRequestSupport": [Function],
|
||||||
|
"container": [Circular],
|
||||||
|
"costsVisible": [Function],
|
||||||
|
"databaseCreateNewShared": [Function],
|
||||||
|
"databaseId": [Function],
|
||||||
|
"databaseIdLabel": [Function],
|
||||||
|
"databaseIdPlaceHolder": [Function],
|
||||||
|
"databaseIdTooltipText": [Function],
|
||||||
|
"databaseLevelThroughputTooltipText": [Function],
|
||||||
|
"firstFieldHasFocus": [Function],
|
||||||
|
"formErrors": [Function],
|
||||||
|
"formErrorsDetails": [Function],
|
||||||
|
"freeTierExceedThroughputTooltip": [Function],
|
||||||
|
"id": "adddatabasepane",
|
||||||
|
"isAutoPilotSelected": [Function],
|
||||||
|
"isExecuting": [Function],
|
||||||
|
"isFreeTierAccount": [Function],
|
||||||
|
"isTemplateReady": [Function],
|
||||||
|
"maxAutoPilotThroughputSet": [Function],
|
||||||
|
"maxThroughputRU": [Function],
|
||||||
|
"maxThroughputRUText": [Function],
|
||||||
|
"minThroughputRU": [Function],
|
||||||
|
"onMoreDetailsKeyPress": [Function],
|
||||||
|
"requestUnitsUsageCost": [Function],
|
||||||
|
"ruToolTipText": [Function],
|
||||||
|
"showUpsellMessage": [Function],
|
||||||
|
"throughput": [Function],
|
||||||
|
"throughputRangeText": [Function],
|
||||||
|
"throughputSpendAck": [Function],
|
||||||
|
"throughputSpendAckText": [Function],
|
||||||
|
"throughputSpendAckVisible": [Function],
|
||||||
|
"title": [Function],
|
||||||
|
"upsellAnchorText": [Function],
|
||||||
|
"upsellAnchorUrl": [Function],
|
||||||
|
"upsellMessage": [Function],
|
||||||
|
"upsellMessageAriaLabel": [Function],
|
||||||
|
"visible": [Function],
|
||||||
|
},
|
||||||
|
"addDatabaseText": [Function],
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
@@ -973,6 +1016,49 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"_refreshSparkEnabledStateForAccount": [Function],
|
"_refreshSparkEnabledStateForAccount": [Function],
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"addCollectionText": [Function],
|
"addCollectionText": [Function],
|
||||||
|
"addDatabasePane": AddDatabasePane {
|
||||||
|
"autoPilotUsageCost": [Function],
|
||||||
|
"canConfigureThroughput": [Function],
|
||||||
|
"canExceedMaximumValue": [Function],
|
||||||
|
"canRequestSupport": [Function],
|
||||||
|
"container": [Circular],
|
||||||
|
"costsVisible": [Function],
|
||||||
|
"databaseCreateNewShared": [Function],
|
||||||
|
"databaseId": [Function],
|
||||||
|
"databaseIdLabel": [Function],
|
||||||
|
"databaseIdPlaceHolder": [Function],
|
||||||
|
"databaseIdTooltipText": [Function],
|
||||||
|
"databaseLevelThroughputTooltipText": [Function],
|
||||||
|
"firstFieldHasFocus": [Function],
|
||||||
|
"formErrors": [Function],
|
||||||
|
"formErrorsDetails": [Function],
|
||||||
|
"freeTierExceedThroughputTooltip": [Function],
|
||||||
|
"id": "adddatabasepane",
|
||||||
|
"isAutoPilotSelected": [Function],
|
||||||
|
"isExecuting": [Function],
|
||||||
|
"isFreeTierAccount": [Function],
|
||||||
|
"isTemplateReady": [Function],
|
||||||
|
"maxAutoPilotThroughputSet": [Function],
|
||||||
|
"maxThroughputRU": [Function],
|
||||||
|
"maxThroughputRUText": [Function],
|
||||||
|
"minThroughputRU": [Function],
|
||||||
|
"onMoreDetailsKeyPress": [Function],
|
||||||
|
"requestUnitsUsageCost": [Function],
|
||||||
|
"ruToolTipText": [Function],
|
||||||
|
"showUpsellMessage": [Function],
|
||||||
|
"throughput": [Function],
|
||||||
|
"throughputRangeText": [Function],
|
||||||
|
"throughputSpendAck": [Function],
|
||||||
|
"throughputSpendAckText": [Function],
|
||||||
|
"throughputSpendAckVisible": [Function],
|
||||||
|
"title": [Function],
|
||||||
|
"upsellAnchorText": [Function],
|
||||||
|
"upsellAnchorUrl": [Function],
|
||||||
|
"upsellMessage": [Function],
|
||||||
|
"upsellMessageAriaLabel": [Function],
|
||||||
|
"visible": [Function],
|
||||||
|
},
|
||||||
|
"addDatabaseText": [Function],
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
@@ -1924,6 +2010,49 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"_refreshSparkEnabledStateForAccount": [Function],
|
"_refreshSparkEnabledStateForAccount": [Function],
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"addCollectionText": [Function],
|
"addCollectionText": [Function],
|
||||||
|
"addDatabasePane": AddDatabasePane {
|
||||||
|
"autoPilotUsageCost": [Function],
|
||||||
|
"canConfigureThroughput": [Function],
|
||||||
|
"canExceedMaximumValue": [Function],
|
||||||
|
"canRequestSupport": [Function],
|
||||||
|
"container": [Circular],
|
||||||
|
"costsVisible": [Function],
|
||||||
|
"databaseCreateNewShared": [Function],
|
||||||
|
"databaseId": [Function],
|
||||||
|
"databaseIdLabel": [Function],
|
||||||
|
"databaseIdPlaceHolder": [Function],
|
||||||
|
"databaseIdTooltipText": [Function],
|
||||||
|
"databaseLevelThroughputTooltipText": [Function],
|
||||||
|
"firstFieldHasFocus": [Function],
|
||||||
|
"formErrors": [Function],
|
||||||
|
"formErrorsDetails": [Function],
|
||||||
|
"freeTierExceedThroughputTooltip": [Function],
|
||||||
|
"id": "adddatabasepane",
|
||||||
|
"isAutoPilotSelected": [Function],
|
||||||
|
"isExecuting": [Function],
|
||||||
|
"isFreeTierAccount": [Function],
|
||||||
|
"isTemplateReady": [Function],
|
||||||
|
"maxAutoPilotThroughputSet": [Function],
|
||||||
|
"maxThroughputRU": [Function],
|
||||||
|
"maxThroughputRUText": [Function],
|
||||||
|
"minThroughputRU": [Function],
|
||||||
|
"onMoreDetailsKeyPress": [Function],
|
||||||
|
"requestUnitsUsageCost": [Function],
|
||||||
|
"ruToolTipText": [Function],
|
||||||
|
"showUpsellMessage": [Function],
|
||||||
|
"throughput": [Function],
|
||||||
|
"throughputRangeText": [Function],
|
||||||
|
"throughputSpendAck": [Function],
|
||||||
|
"throughputSpendAckText": [Function],
|
||||||
|
"throughputSpendAckVisible": [Function],
|
||||||
|
"title": [Function],
|
||||||
|
"upsellAnchorText": [Function],
|
||||||
|
"upsellAnchorUrl": [Function],
|
||||||
|
"upsellMessage": [Function],
|
||||||
|
"upsellMessageAriaLabel": [Function],
|
||||||
|
"visible": [Function],
|
||||||
|
},
|
||||||
|
"addDatabaseText": [Function],
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
@@ -2862,6 +2991,49 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"_refreshSparkEnabledStateForAccount": [Function],
|
"_refreshSparkEnabledStateForAccount": [Function],
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"addCollectionText": [Function],
|
"addCollectionText": [Function],
|
||||||
|
"addDatabasePane": AddDatabasePane {
|
||||||
|
"autoPilotUsageCost": [Function],
|
||||||
|
"canConfigureThroughput": [Function],
|
||||||
|
"canExceedMaximumValue": [Function],
|
||||||
|
"canRequestSupport": [Function],
|
||||||
|
"container": [Circular],
|
||||||
|
"costsVisible": [Function],
|
||||||
|
"databaseCreateNewShared": [Function],
|
||||||
|
"databaseId": [Function],
|
||||||
|
"databaseIdLabel": [Function],
|
||||||
|
"databaseIdPlaceHolder": [Function],
|
||||||
|
"databaseIdTooltipText": [Function],
|
||||||
|
"databaseLevelThroughputTooltipText": [Function],
|
||||||
|
"firstFieldHasFocus": [Function],
|
||||||
|
"formErrors": [Function],
|
||||||
|
"formErrorsDetails": [Function],
|
||||||
|
"freeTierExceedThroughputTooltip": [Function],
|
||||||
|
"id": "adddatabasepane",
|
||||||
|
"isAutoPilotSelected": [Function],
|
||||||
|
"isExecuting": [Function],
|
||||||
|
"isFreeTierAccount": [Function],
|
||||||
|
"isTemplateReady": [Function],
|
||||||
|
"maxAutoPilotThroughputSet": [Function],
|
||||||
|
"maxThroughputRU": [Function],
|
||||||
|
"maxThroughputRUText": [Function],
|
||||||
|
"minThroughputRU": [Function],
|
||||||
|
"onMoreDetailsKeyPress": [Function],
|
||||||
|
"requestUnitsUsageCost": [Function],
|
||||||
|
"ruToolTipText": [Function],
|
||||||
|
"showUpsellMessage": [Function],
|
||||||
|
"throughput": [Function],
|
||||||
|
"throughputRangeText": [Function],
|
||||||
|
"throughputSpendAck": [Function],
|
||||||
|
"throughputSpendAckText": [Function],
|
||||||
|
"throughputSpendAckVisible": [Function],
|
||||||
|
"title": [Function],
|
||||||
|
"upsellAnchorText": [Function],
|
||||||
|
"upsellAnchorUrl": [Function],
|
||||||
|
"upsellMessage": [Function],
|
||||||
|
"upsellMessageAriaLabel": [Function],
|
||||||
|
"visible": [Function],
|
||||||
|
},
|
||||||
|
"addDatabaseText": [Function],
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
|
|||||||
@@ -113,7 +113,6 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
aria-label="Autoscale mode"
|
aria-label="Autoscale mode"
|
||||||
checked={isAutoscaleSelected}
|
checked={isAutoscaleSelected}
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={(e) => handleOnChangeMode(e, "Autoscale")}
|
onChange={(e) => handleOnChangeMode(e, "Autoscale")}
|
||||||
/>
|
/>
|
||||||
@@ -124,7 +123,6 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
aria-label="Manual mode"
|
aria-label="Manual mode"
|
||||||
checked={!isAutoscaleSelected}
|
checked={!isAutoscaleSelected}
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={(e) => handleOnChangeMode(e, "Manual")}
|
onChange={(e) => handleOnChangeMode(e, "Manual")}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -655,7 +655,6 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
className="throughputInputRadioBtn"
|
className="throughputInputRadioBtn"
|
||||||
key=".0:$.0"
|
key=".0:$.0"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
role="radio"
|
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
type="radio"
|
type="radio"
|
||||||
/>
|
/>
|
||||||
@@ -671,7 +670,6 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
className="throughputInputRadioBtn"
|
className="throughputInputRadioBtn"
|
||||||
key=".0:$.2"
|
key=".0:$.2"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
role="radio"
|
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
type="radio"
|
type="radio"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ import type NotebookManager from "./Notebook/NotebookManager";
|
|||||||
import type { NotebookPaneContent } from "./Notebook/NotebookManager";
|
import type { NotebookPaneContent } from "./Notebook/NotebookManager";
|
||||||
import { NotebookUtil } from "./Notebook/NotebookUtil";
|
import { NotebookUtil } from "./Notebook/NotebookUtil";
|
||||||
import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
|
import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
|
||||||
|
import AddDatabasePane from "./Panes/AddDatabasePane";
|
||||||
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";
|
||||||
@@ -67,7 +68,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/TableQuerySelectPanel";
|
import { TableQuerySelectPanel } from "./Panes/Tables/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";
|
||||||
@@ -100,6 +101,7 @@ export interface ExplorerParams {
|
|||||||
|
|
||||||
export default class Explorer {
|
export default class Explorer {
|
||||||
public addCollectionText: ko.Observable<string>;
|
public addCollectionText: ko.Observable<string>;
|
||||||
|
public addDatabaseText: ko.Observable<string>;
|
||||||
public collectionTitle: ko.Observable<string>;
|
public collectionTitle: ko.Observable<string>;
|
||||||
public deleteCollectionText: ko.Observable<string>;
|
public deleteCollectionText: ko.Observable<string>;
|
||||||
public deleteDatabaseText: ko.Observable<string>;
|
public deleteDatabaseText: ko.Observable<string>;
|
||||||
@@ -147,6 +149,7 @@ export default class Explorer {
|
|||||||
public tabsManager: TabsManager;
|
public tabsManager: TabsManager;
|
||||||
|
|
||||||
// Contextual panes
|
// Contextual panes
|
||||||
|
public addDatabasePane: AddDatabasePane;
|
||||||
public cassandraAddCollectionPane: CassandraAddCollectionPane;
|
public cassandraAddCollectionPane: CassandraAddCollectionPane;
|
||||||
private gitHubClient: GitHubClient;
|
private gitHubClient: GitHubClient;
|
||||||
public gitHubOAuthService: GitHubOAuthService;
|
public gitHubOAuthService: GitHubOAuthService;
|
||||||
@@ -205,6 +208,7 @@ export default class Explorer {
|
|||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
this.addCollectionText = ko.observable<string>("New Collection");
|
this.addCollectionText = ko.observable<string>("New Collection");
|
||||||
|
this.addDatabaseText = ko.observable<string>("New Database");
|
||||||
this.collectionTitle = ko.observable<string>("Collections");
|
this.collectionTitle = ko.observable<string>("Collections");
|
||||||
this.collectionTreeNodeAltText = ko.observable<string>("Collection");
|
this.collectionTreeNodeAltText = ko.observable<string>("Collection");
|
||||||
this.deleteCollectionText = ko.observable<string>("Delete Collection");
|
this.deleteCollectionText = ko.observable<string>("Delete Collection");
|
||||||
@@ -397,6 +401,13 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.addDatabasePane = new AddDatabasePane({
|
||||||
|
id: "adddatabasepane",
|
||||||
|
visible: ko.observable<boolean>(false),
|
||||||
|
|
||||||
|
container: this,
|
||||||
|
});
|
||||||
|
|
||||||
this.cassandraAddCollectionPane = new CassandraAddCollectionPane({
|
this.cassandraAddCollectionPane = new CassandraAddCollectionPane({
|
||||||
id: "cassandraaddcollectionpane",
|
id: "cassandraaddcollectionpane",
|
||||||
visible: ko.observable<boolean>(false),
|
visible: ko.observable<boolean>(false),
|
||||||
@@ -412,6 +423,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.addDatabaseText.subscribe((addDatabaseText: string) => this.addDatabasePane.title(addDatabaseText));
|
||||||
this.isTabsContentExpanded = ko.observable(false);
|
this.isTabsContentExpanded = ko.observable(false);
|
||||||
|
|
||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
@@ -429,6 +441,7 @@ export default class Explorer {
|
|||||||
switch (userContext.apiType) {
|
switch (userContext.apiType) {
|
||||||
case "SQL":
|
case "SQL":
|
||||||
this.addCollectionText("New Container");
|
this.addCollectionText("New Container");
|
||||||
|
this.addDatabaseText("New Database");
|
||||||
this.collectionTitle("SQL API");
|
this.collectionTitle("SQL API");
|
||||||
this.collectionTreeNodeAltText("Container");
|
this.collectionTreeNodeAltText("Container");
|
||||||
this.deleteCollectionText("Delete Container");
|
this.deleteCollectionText("Delete Container");
|
||||||
@@ -437,6 +450,7 @@ export default class Explorer {
|
|||||||
break;
|
break;
|
||||||
case "Mongo":
|
case "Mongo":
|
||||||
this.addCollectionText("New Collection");
|
this.addCollectionText("New Collection");
|
||||||
|
this.addDatabaseText("New Database");
|
||||||
this.collectionTitle("Collections");
|
this.collectionTitle("Collections");
|
||||||
this.collectionTreeNodeAltText("Collection");
|
this.collectionTreeNodeAltText("Collection");
|
||||||
this.deleteCollectionText("Delete Collection");
|
this.deleteCollectionText("Delete Collection");
|
||||||
@@ -445,6 +459,7 @@ export default class Explorer {
|
|||||||
break;
|
break;
|
||||||
case "Gremlin":
|
case "Gremlin":
|
||||||
this.addCollectionText("New Graph");
|
this.addCollectionText("New Graph");
|
||||||
|
this.addDatabaseText("New Database");
|
||||||
this.deleteCollectionText("Delete Graph");
|
this.deleteCollectionText("Delete Graph");
|
||||||
this.deleteDatabaseText("Delete Database");
|
this.deleteDatabaseText("Delete Database");
|
||||||
this.collectionTitle("Gremlin API");
|
this.collectionTitle("Gremlin API");
|
||||||
@@ -453,6 +468,7 @@ export default class Explorer {
|
|||||||
break;
|
break;
|
||||||
case "Tables":
|
case "Tables":
|
||||||
this.addCollectionText("New Table");
|
this.addCollectionText("New Table");
|
||||||
|
this.addDatabaseText("New Database");
|
||||||
this.deleteCollectionText("Delete Table");
|
this.deleteCollectionText("Delete Table");
|
||||||
this.deleteDatabaseText("Delete Database");
|
this.deleteDatabaseText("Delete Database");
|
||||||
this.collectionTitle("Azure Table API");
|
this.collectionTitle("Azure Table API");
|
||||||
@@ -462,6 +478,7 @@ export default class Explorer {
|
|||||||
break;
|
break;
|
||||||
case "Cassandra":
|
case "Cassandra":
|
||||||
this.addCollectionText("New Table");
|
this.addCollectionText("New Table");
|
||||||
|
this.addDatabaseText("New Keyspace");
|
||||||
this.deleteCollectionText("Delete Table");
|
this.deleteCollectionText("Delete Table");
|
||||||
this.deleteDatabaseText("Delete Keyspace");
|
this.deleteDatabaseText("Delete Keyspace");
|
||||||
this.collectionTitle("Cassandra API");
|
this.collectionTitle("Cassandra API");
|
||||||
@@ -1367,7 +1384,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={() => {
|
||||||
@@ -1398,7 +1415,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.openSidePanel(
|
this.openSidePanel(
|
||||||
"Create new directory",
|
"",
|
||||||
<StringInputPane
|
<StringInputPane
|
||||||
explorer={this}
|
explorer={this}
|
||||||
closePanel={() => {
|
closePanel={() => {
|
||||||
@@ -1890,7 +1907,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()}
|
||||||
/>
|
/>
|
||||||
@@ -1932,14 +1949,19 @@ export default class Explorer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
public openAddDatabasePane(): void {
|
public openAddDatabasePane(): void {
|
||||||
this.openSidePanel(
|
if (userContext.features.enableKOPanel) {
|
||||||
"New " + getDatabaseName(),
|
this.addDatabasePane.open();
|
||||||
<AddDatabasePanel
|
document.getElementById("linkAddDatabase").focus();
|
||||||
explorer={this}
|
} else {
|
||||||
openNotificationConsole={() => this.expandConsole()}
|
this.openSidePanel(
|
||||||
closePanel={this.closeSidePanel}
|
"Add " + getDatabaseName(),
|
||||||
/>
|
<AddDatabasePanel
|
||||||
);
|
explorer={this}
|
||||||
|
openNotificationConsole={() => this.expandConsole()}
|
||||||
|
closePanel={this.closeSidePanel}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public openBrowseQueriesPanel(): void {
|
public openBrowseQueriesPanel(): void {
|
||||||
|
|||||||
@@ -132,7 +132,6 @@ 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>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export class CommandBarComponentAdapter implements ReactAdapter {
|
|||||||
container.deleteCollectionText,
|
container.deleteCollectionText,
|
||||||
container.deleteDatabaseText,
|
container.deleteDatabaseText,
|
||||||
container.addCollectionText,
|
container.addCollectionText,
|
||||||
|
container.addDatabaseText,
|
||||||
container.isDatabaseNodeOrNoneSelected,
|
container.isDatabaseNodeOrNoneSelected,
|
||||||
container.isDatabaseNodeSelected,
|
container.isDatabaseNodeSelected,
|
||||||
container.isNoneSelected,
|
container.isNoneSelected,
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
mockExplorer = {} as Explorer;
|
mockExplorer = {} as Explorer;
|
||||||
|
mockExplorer.addDatabaseText = ko.observable("mockText");
|
||||||
mockExplorer.addCollectionText = ko.observable("mockText");
|
mockExplorer.addCollectionText = ko.observable("mockText");
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
databaseAccount: {
|
databaseAccount: {
|
||||||
@@ -220,6 +221,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
mockExplorer = {} as Explorer;
|
mockExplorer = {} as Explorer;
|
||||||
|
mockExplorer.addDatabaseText = ko.observable("mockText");
|
||||||
mockExplorer.addCollectionText = ko.observable("mockText");
|
mockExplorer.addCollectionText = ko.observable("mockText");
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
databaseAccount: {
|
databaseAccount: {
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import * as Constants from "../../../Common/Constants";
|
|||||||
import { configContext, Platform } from "../../../ConfigContext";
|
import { configContext, Platform } from "../../../ConfigContext";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { getDatabaseName } from "../../../Utils/APITypeUtils";
|
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { OpenFullScreen } from "../../OpenFullScreen";
|
import { OpenFullScreen } from "../../OpenFullScreen";
|
||||||
@@ -262,7 +261,7 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createNewDatabase(container: Explorer): CommandButtonComponentProps {
|
function createNewDatabase(container: Explorer): CommandButtonComponentProps {
|
||||||
const label = "New " + getDatabaseName();
|
const label = container.addDatabaseText();
|
||||||
return {
|
return {
|
||||||
iconSrc: AddDatabaseIcon,
|
iconSrc: AddDatabaseIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
onClick={() => this.expandCollapseConsole()}
|
onClick={() => this.expandCollapseConsole()}
|
||||||
onKeyDown={(event: React.KeyboardEvent<HTMLDivElement>) => this.onExpandCollapseKeyPress(event)}
|
onKeyDown={(event: React.KeyboardEvent<HTMLDivElement>) => this.onExpandCollapseKeyPress(event)}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
role="button"
|
||||||
>
|
>
|
||||||
<div className="statusBar">
|
<div className="statusBar">
|
||||||
<span className="dataTypeIcons">
|
<span className="dataTypeIcons">
|
||||||
@@ -179,7 +180,7 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
onKeyDown={(event: React.KeyboardEvent<HTMLSpanElement>) => this.onClearNotificationsKeyPress(event)}
|
onKeyDown={(event: React.KeyboardEvent<HTMLSpanElement>) => this.onClearNotificationsKeyPress(event)}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<img src={ClearIcon} alt="clear notifications image" />
|
<img src={ClearIcon} alt="clear notifications" />
|
||||||
Clear Notifications
|
Clear Notifications
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
|||||||
id="notificationConsoleHeader"
|
id="notificationConsoleHeader"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -150,7 +151,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="clear notifications image"
|
alt="clear notifications"
|
||||||
src=""
|
src=""
|
||||||
/>
|
/>
|
||||||
Clear Notifications
|
Clear Notifications
|
||||||
@@ -173,6 +174,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
|||||||
id="notificationConsoleHeader"
|
id="notificationConsoleHeader"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -316,7 +318,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="clear notifications image"
|
alt="clear notifications"
|
||||||
src=""
|
src=""
|
||||||
/>
|
/>
|
||||||
Clear Notifications
|
Clear Notifications
|
||||||
|
|||||||
@@ -139,7 +139,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
aria-checked={this.state.createNewDatabase}
|
aria-checked={this.state.createNewDatabase}
|
||||||
name="databaseType"
|
name="databaseType"
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
|
||||||
id="databaseCreateNew"
|
id="databaseCreateNew"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
|
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
|
||||||
@@ -153,7 +152,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
aria-checked={!this.state.createNewDatabase}
|
aria-checked={!this.state.createNewDatabase}
|
||||||
name="databaseType"
|
name="databaseType"
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
|
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
|
||||||
/>
|
/>
|
||||||
@@ -175,7 +173,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
size={40}
|
size={40}
|
||||||
className="panelTextField"
|
className="panelTextField"
|
||||||
aria-label="New database id"
|
aria-label="New database id"
|
||||||
autoFocus
|
|
||||||
value={this.state.newDatabaseId}
|
value={this.state.newDatabaseId}
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
this.setState({ newDatabaseId: event.target.value })
|
this.setState({ newDatabaseId: event.target.value })
|
||||||
@@ -287,7 +284,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
aria-label="Turn on indexing"
|
aria-label="Turn on indexing"
|
||||||
aria-checked={this.state.enableIndexing}
|
aria-checked={this.state.enableIndexing}
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={this.onTurnOnIndexing.bind(this)}
|
onChange={this.onTurnOnIndexing.bind(this)}
|
||||||
/>
|
/>
|
||||||
@@ -299,7 +295,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
aria-label="Turn off indexing"
|
aria-label="Turn off indexing"
|
||||||
aria-checked={!this.state.enableIndexing}
|
aria-checked={!this.state.enableIndexing}
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={this.onTurnOffIndexing.bind(this)}
|
onChange={this.onTurnOffIndexing.bind(this)}
|
||||||
/>
|
/>
|
||||||
@@ -342,7 +337,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
aria-checked={!this.state.isSharded}
|
aria-checked={!this.state.isSharded}
|
||||||
name="unsharded"
|
name="unsharded"
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
|
||||||
id="unshardedOption"
|
id="unshardedOption"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={this.onUnshardedRadioBtnChange.bind(this)}
|
onChange={this.onUnshardedRadioBtnChange.bind(this)}
|
||||||
@@ -356,7 +350,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
aria-checked={this.state.isSharded}
|
aria-checked={this.state.isSharded}
|
||||||
name="sharded"
|
name="sharded"
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
|
||||||
id="shardedOption"
|
id="shardedOption"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={this.onShardedRadioBtnChange.bind(this)}
|
onChange={this.onShardedRadioBtnChange.bind(this)}
|
||||||
@@ -480,7 +473,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
: "Comma separated paths e.g. /firstName,/address/zipCode"
|
: "Comma separated paths e.g. /firstName,/address/zipCode"
|
||||||
}
|
}
|
||||||
className="panelTextField"
|
className="panelTextField"
|
||||||
autoFocus
|
|
||||||
value={uniqueKey}
|
value={uniqueKey}
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const uniqueKeys = this.state.uniqueKeys.map((uniqueKey: string, j: number) => {
|
const uniqueKeys = this.state.uniqueKeys.map((uniqueKey: string, j: number) => {
|
||||||
@@ -594,7 +586,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
aria-checked={this.state.enableAnalyticalStore}
|
aria-checked={this.state.enableAnalyticalStore}
|
||||||
name="analyticalStore"
|
name="analyticalStore"
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
|
||||||
id="enableAnalyticalStoreBtn"
|
id="enableAnalyticalStoreBtn"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)}
|
onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)}
|
||||||
@@ -609,7 +600,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
aria-checked={!this.state.enableAnalyticalStore}
|
aria-checked={!this.state.enableAnalyticalStore}
|
||||||
name="analyticalStore"
|
name="analyticalStore"
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
|
||||||
id="disableAnalyticalStoreBtn"
|
id="disableAnalyticalStoreBtn"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)}
|
onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)}
|
||||||
|
|||||||
174
src/Explorer/Panes/AddDatabasePane.html
Normal file
174
src/Explorer/Panes/AddDatabasePane.html
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
<div data-bind="visible: visible, event: { keydown: onPaneKeyDown }">
|
||||||
|
<div class="contextual-pane-out" data-bind="click: cancel, clickBubble: false"></div>
|
||||||
|
<div class="contextual-pane" data-bind="attr: { id: id }">
|
||||||
|
<!-- Add database form -- Start -->
|
||||||
|
<div class="contextual-pane-in">
|
||||||
|
<form data-bind="submit: submit" style="height: 100%">
|
||||||
|
<div
|
||||||
|
class="paneContentContainer"
|
||||||
|
role="dialog"
|
||||||
|
aria-labelledby="databaseTitle"
|
||||||
|
data-bind="template: { name: 'add-database-inputs' }"
|
||||||
|
></div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<!-- Add database form -- End -->
|
||||||
|
<!-- Loader - Start -->
|
||||||
|
<div class="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer" data-bind="visible: isExecuting">
|
||||||
|
<img class="dataExplorerLoader" src="/LoadingIndicator_3Squares.gif" />
|
||||||
|
</div>
|
||||||
|
<!-- Loader - End -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/html" id="add-database-inputs">
|
||||||
|
<!-- Add database header - Start -->
|
||||||
|
<div class="firstdivbg headerline">
|
||||||
|
<span id="databaseTitle" role="heading" aria-level="2" data-bind="text: title"></span>
|
||||||
|
<div
|
||||||
|
class="closeImg"
|
||||||
|
role="button"
|
||||||
|
aria-label="Close pane"
|
||||||
|
data-bind="click: cancel, event: { keypress: onCloseKeyPress }"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Add database header - End -->
|
||||||
|
|
||||||
|
<!-- Add database 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>
|
||||||
|
<a
|
||||||
|
class="errorLink"
|
||||||
|
role="link"
|
||||||
|
data-bind="visible: formErrorsDetails() && formErrorsDetails() !== '', click: showErrorDetails, event: { keypress: onMoreDetailsKeyPress }"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
More details</a
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Add database errors - End -->
|
||||||
|
|
||||||
|
<!-- upsell message - start -->
|
||||||
|
<div
|
||||||
|
class="infoBoxContainer"
|
||||||
|
aria-live="assertive"
|
||||||
|
data-bind="visible: showUpsellMessage && showUpsellMessage() && formErrors && !formErrors()"
|
||||||
|
>
|
||||||
|
<div class="infoBoxContent">
|
||||||
|
<span><img class="infoBoxIcon" src="/info_color.svg" alt="Promo" /></span>
|
||||||
|
<span class="infoBoxDetails">
|
||||||
|
<span class="infoBoxMessage" data-bind="text: upsellMessage, attr: { title: upsellMessage }"></span>
|
||||||
|
<a
|
||||||
|
class="underlinedLink"
|
||||||
|
id="linkAddDatabase"
|
||||||
|
data-bind="text: upsellAnchorText, attr: { 'href': upsellAnchorUrl, 'aria-label': upsellMessageAriaLabel }"
|
||||||
|
target="_blank"
|
||||||
|
href=""
|
||||||
|
tabindex="0"
|
||||||
|
></a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- upsell message - end -->
|
||||||
|
|
||||||
|
<!-- Add database inputs - Start -->
|
||||||
|
<div class="paneMainContent">
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<span class="mandatoryStar">*</span>
|
||||||
|
<span data-bind="text: databaseIdLabel"></span>
|
||||||
|
<span class="infoTooltip" role="tooltip" tabindex="0">
|
||||||
|
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
|
||||||
|
<span class="tooltiptext infoTooltipWidth" data-bind="text: databaseIdTooltipText"></span>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<input
|
||||||
|
id="database-id"
|
||||||
|
type="text"
|
||||||
|
aria-required="true"
|
||||||
|
autocomplete="off"
|
||||||
|
pattern="[^/?#\\]*[^/?# \\]"
|
||||||
|
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
||||||
|
size="40"
|
||||||
|
class="collid"
|
||||||
|
data-bind="textInput: databaseId, hasFocus: firstFieldHasFocus, attr: { 'aria-label': databaseIdLabel, 'placeholder': databaseIdPlaceHolder }"
|
||||||
|
autofocus
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Database provisioned throughput - Start -->
|
||||||
|
<!-- ko if: canConfigureThroughput -->
|
||||||
|
<div class="databaseProvision" aria-label="New database provision support">
|
||||||
|
<input
|
||||||
|
tabindex="0"
|
||||||
|
type="checkbox"
|
||||||
|
id="addDatabasePane-databaseSharedThroughput"
|
||||||
|
title="Provision shared throughput"
|
||||||
|
data-bind="checked: databaseCreateNewShared"
|
||||||
|
/>
|
||||||
|
<span class="databaseProvisionText" for="databaseSharedThroughput">Provision throughput</span>
|
||||||
|
<span class="infoTooltip" role="tooltip" tabindex="0">
|
||||||
|
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
|
||||||
|
<span
|
||||||
|
class="tooltiptext provisionDatabaseThroughput"
|
||||||
|
data-bind="text: databaseLevelThroughputTooltipText"
|
||||||
|
></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div data-bind="visible: databaseCreateNewShared">
|
||||||
|
<throughput-input-autopilot-v3
|
||||||
|
params="{
|
||||||
|
step: 100,
|
||||||
|
value: throughput,
|
||||||
|
testId: 'sharedThroughputValue',
|
||||||
|
minimum: minThroughputRU,
|
||||||
|
maximum: maxThroughputRU,
|
||||||
|
isEnabled: databaseCreateNewShared,
|
||||||
|
label: throughputRangeText,
|
||||||
|
ariaLabel: throughputRangeText,
|
||||||
|
costsVisible: costsVisible,
|
||||||
|
requestUnitsUsageCost: requestUnitsUsageCost,
|
||||||
|
spendAckChecked: throughputSpendAck,
|
||||||
|
spendAckId: 'throughputSpendAckDatabase',
|
||||||
|
spendAckText: throughputSpendAckText,
|
||||||
|
spendAckVisible: throughputSpendAckVisible,
|
||||||
|
showAsMandatory: true,
|
||||||
|
infoBubbleText: ruToolTipText,
|
||||||
|
throughputAutoPilotRadioId: 'newDatabase-databaseThroughput-autoPilotRadio',
|
||||||
|
throughputProvisionedRadioId: 'newDatabase-databaseThroughput-manualRadio',
|
||||||
|
throughputModeRadioName: 'throughputModeRadioName',
|
||||||
|
isAutoPilotSelected: isAutoPilotSelected,
|
||||||
|
maxAutoPilotThroughputSet: maxAutoPilotThroughputSet,
|
||||||
|
autoPilotUsageCost: autoPilotUsageCost,
|
||||||
|
canExceedMaximumValue: canExceedMaximumValue,
|
||||||
|
freeTierExceedThroughputTooltip: freeTierExceedThroughputTooltip
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
</throughput-input-autopilot-v3>
|
||||||
|
<p data-bind="visible: canRequestSupport">
|
||||||
|
<!-- TODO: Replace link with call to the Azure Support blade --><a
|
||||||
|
href="https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20More%20Throughput%20Request"
|
||||||
|
>Contact support</a
|
||||||
|
>
|
||||||
|
for more than <span data-bind="text: maxThroughputRUText"></span> RU/s.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<!-- /ko -->
|
||||||
|
<!-- Database provisioned throughput - End -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="paneFooter">
|
||||||
|
<div class="leftpanel-okbut">
|
||||||
|
<input type="submit" value="OK" class="btncreatecoll1" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Add database inputs - End -->
|
||||||
|
</script>
|
||||||
105
src/Explorer/Panes/AddDatabasePane.test.ts
Normal file
105
src/Explorer/Panes/AddDatabasePane.test.ts
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import * as Constants from "../../Common/Constants";
|
||||||
|
import { DatabaseAccount } from "../../Contracts/DataModels";
|
||||||
|
import { SubscriptionType } from "../../Contracts/SubscriptionType";
|
||||||
|
import { updateUserContext } from "../../UserContext";
|
||||||
|
import Explorer from "../Explorer";
|
||||||
|
import AddDatabasePane from "./AddDatabasePane";
|
||||||
|
|
||||||
|
const mockDatabaseAccount: DatabaseAccount = {
|
||||||
|
id: "mock",
|
||||||
|
kind: "DocumentDB",
|
||||||
|
location: "",
|
||||||
|
name: "mock",
|
||||||
|
properties: {
|
||||||
|
documentEndpoint: "",
|
||||||
|
cassandraEndpoint: "",
|
||||||
|
gremlinEndpoint: "",
|
||||||
|
tableEndpoint: "",
|
||||||
|
enableFreeTier: false,
|
||||||
|
},
|
||||||
|
type: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockFreeTierDatabaseAccount: DatabaseAccount = {
|
||||||
|
id: "mock",
|
||||||
|
kind: "DocumentDB",
|
||||||
|
location: "",
|
||||||
|
name: "mock",
|
||||||
|
properties: {
|
||||||
|
documentEndpoint: "",
|
||||||
|
cassandraEndpoint: "",
|
||||||
|
gremlinEndpoint: "",
|
||||||
|
tableEndpoint: "",
|
||||||
|
enableFreeTier: true,
|
||||||
|
},
|
||||||
|
type: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("Add Database Pane", () => {
|
||||||
|
describe("getSharedThroughputDefault()", () => {
|
||||||
|
it("should be true if subscription type is Benefits", () => {
|
||||||
|
updateUserContext({
|
||||||
|
subscriptionType: SubscriptionType.Benefits,
|
||||||
|
});
|
||||||
|
const explorer = new Explorer();
|
||||||
|
const addDatabasePane = explorer.addDatabasePane as AddDatabasePane;
|
||||||
|
expect(addDatabasePane.getSharedThroughputDefault()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be false if subscription type is EA", () => {
|
||||||
|
updateUserContext({
|
||||||
|
subscriptionType: SubscriptionType.EA,
|
||||||
|
});
|
||||||
|
const explorer = new Explorer();
|
||||||
|
const addDatabasePane = explorer.addDatabasePane as AddDatabasePane;
|
||||||
|
expect(addDatabasePane.getSharedThroughputDefault()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be true if subscription type is Free", () => {
|
||||||
|
updateUserContext({
|
||||||
|
subscriptionType: SubscriptionType.Free,
|
||||||
|
});
|
||||||
|
const explorer = new Explorer();
|
||||||
|
const addDatabasePane = explorer.addDatabasePane as AddDatabasePane;
|
||||||
|
expect(addDatabasePane.getSharedThroughputDefault()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be true if subscription type is Internal", () => {
|
||||||
|
updateUserContext({
|
||||||
|
subscriptionType: SubscriptionType.Internal,
|
||||||
|
});
|
||||||
|
const explorer = new Explorer();
|
||||||
|
const addDatabasePane = explorer.addDatabasePane as AddDatabasePane;
|
||||||
|
expect(addDatabasePane.getSharedThroughputDefault()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be true if subscription type is PAYG", () => {
|
||||||
|
updateUserContext({
|
||||||
|
subscriptionType: SubscriptionType.PAYG,
|
||||||
|
});
|
||||||
|
const explorer = new Explorer();
|
||||||
|
const addDatabasePane = explorer.addDatabasePane as AddDatabasePane;
|
||||||
|
expect(addDatabasePane.getSharedThroughputDefault()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should display free tier text in upsell messaging", () => {
|
||||||
|
updateUserContext({ databaseAccount: mockFreeTierDatabaseAccount });
|
||||||
|
const explorer = new Explorer();
|
||||||
|
const addDatabasePane = explorer.addDatabasePane as AddDatabasePane;
|
||||||
|
expect(addDatabasePane.isFreeTierAccount()).toBe(true);
|
||||||
|
expect(addDatabasePane.upsellMessage()).toContain("With free tier");
|
||||||
|
expect(addDatabasePane.upsellAnchorUrl()).toBe(Constants.Urls.freeTierInformation);
|
||||||
|
expect(addDatabasePane.upsellAnchorText()).toBe("Learn more");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should display standard texr in upsell messaging", () => {
|
||||||
|
updateUserContext({ databaseAccount: mockDatabaseAccount });
|
||||||
|
const explorer = new Explorer();
|
||||||
|
const addDatabasePane = explorer.addDatabasePane as AddDatabasePane;
|
||||||
|
expect(addDatabasePane.isFreeTierAccount()).toBe(false);
|
||||||
|
expect(addDatabasePane.upsellMessage()).toContain("Start at");
|
||||||
|
expect(addDatabasePane.upsellAnchorUrl()).toBe(Constants.Urls.cosmosPricing);
|
||||||
|
expect(addDatabasePane.upsellAnchorText()).toBe("More details");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
452
src/Explorer/Panes/AddDatabasePane.ts
Normal file
452
src/Explorer/Panes/AddDatabasePane.ts
Normal file
@@ -0,0 +1,452 @@
|
|||||||
|
import * as ko from "knockout";
|
||||||
|
import * as Constants from "../../Common/Constants";
|
||||||
|
import { createDatabase } from "../../Common/dataAccess/createDatabase";
|
||||||
|
import editable from "../../Common/EditableUtility";
|
||||||
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
|
import { configContext, Platform } from "../../ConfigContext";
|
||||||
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
|
import { SubscriptionType } from "../../Contracts/SubscriptionType";
|
||||||
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
|
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 { ContextualPaneBase } from "./ContextualPaneBase";
|
||||||
|
|
||||||
|
export default class AddDatabasePane extends ContextualPaneBase {
|
||||||
|
public defaultExperience: ko.Computed<string>;
|
||||||
|
public databaseIdLabel: ko.Computed<string>;
|
||||||
|
public databaseIdPlaceHolder: ko.Computed<string>;
|
||||||
|
public databaseId: ko.Observable<string>;
|
||||||
|
public databaseIdTooltipText: ko.Computed<string>;
|
||||||
|
public databaseLevelThroughputTooltipText: ko.Computed<string>;
|
||||||
|
public databaseCreateNewShared: ko.Observable<boolean>;
|
||||||
|
public formErrorsDetails: ko.Observable<string>;
|
||||||
|
public throughput: ViewModels.Editable<number>;
|
||||||
|
public maxThroughputRU: ko.Observable<number>;
|
||||||
|
public minThroughputRU: ko.Observable<number>;
|
||||||
|
public maxThroughputRUText: ko.PureComputed<string>;
|
||||||
|
public throughputRangeText: ko.Computed<string>;
|
||||||
|
public throughputSpendAckText: ko.Observable<string>;
|
||||||
|
public throughputSpendAck: ko.Observable<boolean>;
|
||||||
|
public throughputSpendAckVisible: ko.Computed<boolean>;
|
||||||
|
public requestUnitsUsageCost: ko.Computed<string>;
|
||||||
|
public canRequestSupport: ko.PureComputed<boolean>;
|
||||||
|
public costsVisible: ko.PureComputed<boolean>;
|
||||||
|
public upsellMessage: ko.PureComputed<string>;
|
||||||
|
public upsellMessageAriaLabel: ko.PureComputed<string>;
|
||||||
|
public upsellAnchorUrl: ko.PureComputed<string>;
|
||||||
|
public upsellAnchorText: ko.PureComputed<string>;
|
||||||
|
public isAutoPilotSelected: ko.Observable<boolean>;
|
||||||
|
public maxAutoPilotThroughputSet: ko.Observable<number>;
|
||||||
|
public autoPilotUsageCost: ko.Computed<string>;
|
||||||
|
public canExceedMaximumValue: ko.PureComputed<boolean>;
|
||||||
|
public ruToolTipText: ko.Computed<string>;
|
||||||
|
public freeTierExceedThroughputTooltip: ko.Computed<string>;
|
||||||
|
public isFreeTierAccount: ko.Computed<boolean>;
|
||||||
|
public canConfigureThroughput: ko.PureComputed<boolean>;
|
||||||
|
public showUpsellMessage: ko.PureComputed<boolean>;
|
||||||
|
|
||||||
|
constructor(options: ViewModels.PaneOptions) {
|
||||||
|
super(options);
|
||||||
|
this.title((this.container && this.container.addDatabaseText()) || "New Database");
|
||||||
|
this.databaseId = ko.observable<string>();
|
||||||
|
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText());
|
||||||
|
this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled());
|
||||||
|
|
||||||
|
this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue());
|
||||||
|
|
||||||
|
// TODO 388844: get defaults from parent frame
|
||||||
|
this.databaseCreateNewShared = ko.observable<boolean>(this.getSharedThroughputDefault());
|
||||||
|
|
||||||
|
this.databaseIdLabel = ko.computed<string>(() =>
|
||||||
|
userContext.apiType === "Cassandra" ? "Keyspace id" : "Database id"
|
||||||
|
);
|
||||||
|
|
||||||
|
this.databaseIdPlaceHolder = ko.computed<string>(() =>
|
||||||
|
userContext.apiType === "Cassandra" ? "Type a new keyspace id" : "Type a new database id"
|
||||||
|
);
|
||||||
|
|
||||||
|
this.databaseIdTooltipText = ko.computed<string>(() => {
|
||||||
|
const isCassandraAccount: boolean = userContext.apiType === "Cassandra";
|
||||||
|
return `A ${isCassandraAccount ? "keyspace" : "database"} is a logical container of one or more ${
|
||||||
|
isCassandraAccount ? "tables" : "collections"
|
||||||
|
}`;
|
||||||
|
});
|
||||||
|
this.databaseLevelThroughputTooltipText = ko.computed<string>(() => {
|
||||||
|
const isCassandraAccount: boolean = userContext.apiType === "Cassandra";
|
||||||
|
const databaseLabel: string = isCassandraAccount ? "keyspace" : "database";
|
||||||
|
const collectionsLabel: string = isCassandraAccount ? "tables" : "collections";
|
||||||
|
return `Provisioned throughput at the ${databaseLabel} level will be shared across all ${collectionsLabel} within the ${databaseLabel}.`;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.throughput = editable.observable<number>();
|
||||||
|
this.maxThroughputRU = ko.observable<number>();
|
||||||
|
this.minThroughputRU = ko.observable<number>();
|
||||||
|
this.throughputSpendAckText = ko.observable<string>();
|
||||||
|
this.throughputSpendAck = ko.observable<boolean>(false);
|
||||||
|
this.isAutoPilotSelected = ko.observable<boolean>(false);
|
||||||
|
this.maxAutoPilotThroughputSet = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput);
|
||||||
|
this.autoPilotUsageCost = ko.pureComputed<string>(() => {
|
||||||
|
const autoPilot = this._isAutoPilotSelectedAndWhatTier();
|
||||||
|
if (!autoPilot) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, true /* isDatabaseThroughput */);
|
||||||
|
});
|
||||||
|
this.throughputRangeText = ko.pureComputed<string>(() => {
|
||||||
|
if (this.isAutoPilotSelected()) {
|
||||||
|
return AutoPilotUtils.getAutoPilotHeaderText();
|
||||||
|
}
|
||||||
|
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.requestUnitsUsageCost = ko.computed(() => {
|
||||||
|
const offerThroughput: number = this.throughput();
|
||||||
|
if (
|
||||||
|
offerThroughput < this.minThroughputRU() ||
|
||||||
|
(offerThroughput > this.maxThroughputRU() && !this.canExceedMaximumValue())
|
||||||
|
) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const { databaseAccount: account } = userContext;
|
||||||
|
if (!account) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const regions = account?.properties?.readLocations?.length || 1;
|
||||||
|
const multimaster = account?.properties?.enableMultipleWriteLocations || false;
|
||||||
|
|
||||||
|
let estimatedSpendAcknowledge: string;
|
||||||
|
let estimatedSpend: string;
|
||||||
|
if (!this.isAutoPilotSelected()) {
|
||||||
|
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
|
||||||
|
offerThroughput,
|
||||||
|
userContext.portalEnv,
|
||||||
|
regions,
|
||||||
|
multimaster
|
||||||
|
);
|
||||||
|
estimatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||||
|
offerThroughput,
|
||||||
|
userContext.portalEnv,
|
||||||
|
regions,
|
||||||
|
multimaster,
|
||||||
|
this.isAutoPilotSelected()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
|
||||||
|
this.maxAutoPilotThroughputSet(),
|
||||||
|
userContext.portalEnv,
|
||||||
|
regions,
|
||||||
|
multimaster
|
||||||
|
);
|
||||||
|
estimatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||||
|
this.maxAutoPilotThroughputSet(),
|
||||||
|
userContext.portalEnv,
|
||||||
|
regions,
|
||||||
|
multimaster,
|
||||||
|
this.isAutoPilotSelected()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// TODO: change throughputSpendAckText to be a computed value, instead of having this side effect
|
||||||
|
this.throughputSpendAckText(estimatedSpendAcknowledge);
|
||||||
|
return estimatedSpend;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.canRequestSupport = ko.pureComputed(() => {
|
||||||
|
if (
|
||||||
|
configContext.platform !== Platform.Emulator &&
|
||||||
|
!userContext.isTryCosmosDBSubscription &&
|
||||||
|
configContext.platform !== Platform.Portal
|
||||||
|
) {
|
||||||
|
const offerThroughput: number = this.throughput();
|
||||||
|
return offerThroughput <= 100000;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.isFreeTierAccount = ko.computed<boolean>(() => {
|
||||||
|
return userContext?.databaseAccount?.properties?.enableFreeTier;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.showUpsellMessage = ko.pureComputed(() => {
|
||||||
|
if (this.container.isServerlessEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isFreeTierAccount()) {
|
||||||
|
return this.databaseCreateNewShared();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.maxThroughputRUText = ko.pureComputed(() => {
|
||||||
|
return this.maxThroughputRU().toLocaleString();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.costsVisible = ko.pureComputed(() => {
|
||||||
|
return configContext.platform !== Platform.Emulator;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => {
|
||||||
|
const autoscaleThroughput = this.maxAutoPilotThroughputSet() * 1;
|
||||||
|
if (this.isAutoPilotSelected()) {
|
||||||
|
return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedThroughput: number = this.throughput();
|
||||||
|
const maxRU: number = this.maxThroughputRU && this.maxThroughputRU();
|
||||||
|
|
||||||
|
const isMaxRUGreaterThanDefault: boolean = maxRU > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
|
||||||
|
const isThroughputSetGreaterThanDefault: boolean =
|
||||||
|
selectedThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
|
||||||
|
|
||||||
|
if (this.canExceedMaximumValue()) {
|
||||||
|
return isThroughputSetGreaterThanDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isThroughputSetGreaterThanDefault && isMaxRUGreaterThanDefault;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.databaseCreateNewShared.subscribe((useShared: boolean) => {
|
||||||
|
this._updateThroughputLimitByDatabase();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.resetData();
|
||||||
|
|
||||||
|
this.freeTierExceedThroughputTooltip = ko.pureComputed<string>(() =>
|
||||||
|
this.isFreeTierAccount() && !this.container.isFirstResourceCreated()
|
||||||
|
? "The first 400 RU/s in this account are free. Billing will apply to any throughput beyond 400 RU/s."
|
||||||
|
: ""
|
||||||
|
);
|
||||||
|
|
||||||
|
this.upsellMessage = ko.pureComputed<string>(() => {
|
||||||
|
return PricingUtils.getUpsellMessage(
|
||||||
|
userContext.portalEnv,
|
||||||
|
this.isFreeTierAccount(),
|
||||||
|
this.container.isFirstResourceCreated(),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.upsellMessageAriaLabel = ko.pureComputed<string>(() => {
|
||||||
|
return `${this.upsellMessage()}. Click ${this.isFreeTierAccount() ? "to learn more" : "for more details"}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.upsellAnchorUrl = ko.pureComputed<string>(() => {
|
||||||
|
return this.isFreeTierAccount() ? Constants.Urls.freeTierInformation : Constants.Urls.cosmosPricing;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.upsellAnchorText = ko.pureComputed<string>(() => {
|
||||||
|
return this.isFreeTierAccount() ? "Learn more" : "More details";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public onMoreDetailsKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||||
|
if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) {
|
||||||
|
this.showErrorDetails();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
public open() {
|
||||||
|
super.open();
|
||||||
|
this.resetData();
|
||||||
|
const addDatabasePaneOpenMessage = {
|
||||||
|
subscriptionType: userContext.subscriptionType,
|
||||||
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
|
defaultsCheck: {
|
||||||
|
throughput: this.throughput(),
|
||||||
|
flight: userContext.addCollectionFlight,
|
||||||
|
},
|
||||||
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||||
|
};
|
||||||
|
const focusElement = document.getElementById("database-id");
|
||||||
|
focusElement && focusElement.focus();
|
||||||
|
TelemetryProcessor.trace(Action.CreateDatabase, ActionModifiers.Open, addDatabasePaneOpenMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public submit() {
|
||||||
|
if (!this._isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offerThroughput: number = this._computeOfferThroughput();
|
||||||
|
|
||||||
|
const addDatabasePaneStartMessage = {
|
||||||
|
database: ko.toJS({
|
||||||
|
id: this.databaseId(),
|
||||||
|
shared: this.databaseCreateNewShared(),
|
||||||
|
}),
|
||||||
|
offerThroughput,
|
||||||
|
subscriptionType: userContext.subscriptionType,
|
||||||
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
|
defaultsCheck: {
|
||||||
|
flight: userContext.addCollectionFlight,
|
||||||
|
},
|
||||||
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||||
|
};
|
||||||
|
const startKey: number = TelemetryProcessor.traceStart(Action.CreateDatabase, addDatabasePaneStartMessage);
|
||||||
|
this.formErrors("");
|
||||||
|
this.isExecuting(true);
|
||||||
|
|
||||||
|
const createDatabaseParams: DataModels.CreateDatabaseParams = {
|
||||||
|
databaseId: addDatabasePaneStartMessage.database.id,
|
||||||
|
databaseLevelThroughput: addDatabasePaneStartMessage.database.shared,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.isAutoPilotSelected()) {
|
||||||
|
createDatabaseParams.autoPilotMaxThroughput = this.maxAutoPilotThroughputSet();
|
||||||
|
} else {
|
||||||
|
createDatabaseParams.offerThroughput = addDatabasePaneStartMessage.offerThroughput;
|
||||||
|
}
|
||||||
|
|
||||||
|
createDatabase(createDatabaseParams).then(
|
||||||
|
(database: DataModels.Database) => {
|
||||||
|
this._onCreateDatabaseSuccess(offerThroughput, startKey);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
this._onCreateDatabaseFailure(error, offerThroughput, startKey);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public resetData() {
|
||||||
|
this.databaseId("");
|
||||||
|
this.databaseCreateNewShared(this.getSharedThroughputDefault());
|
||||||
|
this.isAutoPilotSelected(this.container.isAutoscaleDefaultEnabled());
|
||||||
|
this.maxAutoPilotThroughputSet(AutoPilotUtils.minAutoPilotThroughput);
|
||||||
|
this._updateThroughputLimitByDatabase();
|
||||||
|
this.throughputSpendAck(false);
|
||||||
|
super.resetData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSharedThroughputDefault(): boolean {
|
||||||
|
const { subscriptionType } = userContext;
|
||||||
|
|
||||||
|
if (subscriptionType === SubscriptionType.EA || this.container.isServerlessEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onCreateDatabaseSuccess(offerThroughput: number, startKey: number): void {
|
||||||
|
this.isExecuting(false);
|
||||||
|
this.close();
|
||||||
|
this.container.refreshAllDatabases();
|
||||||
|
const addDatabasePaneSuccessMessage = {
|
||||||
|
database: ko.toJS({
|
||||||
|
id: this.databaseId(),
|
||||||
|
shared: this.databaseCreateNewShared(),
|
||||||
|
}),
|
||||||
|
offerThroughput: offerThroughput,
|
||||||
|
subscriptionType: userContext.subscriptionType,
|
||||||
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
|
defaultsCheck: {
|
||||||
|
flight: userContext.addCollectionFlight,
|
||||||
|
},
|
||||||
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||||
|
};
|
||||||
|
TelemetryProcessor.traceSuccess(Action.CreateDatabase, addDatabasePaneSuccessMessage, startKey);
|
||||||
|
this.resetData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onCreateDatabaseFailure(error: any, offerThroughput: number, startKey: number): void {
|
||||||
|
this.isExecuting(false);
|
||||||
|
const errorMessage = getErrorMessage(error);
|
||||||
|
this.formErrors(errorMessage);
|
||||||
|
this.formErrorsDetails(errorMessage);
|
||||||
|
const addDatabasePaneFailedMessage = {
|
||||||
|
database: ko.toJS({
|
||||||
|
id: this.databaseId(),
|
||||||
|
shared: this.databaseCreateNewShared(),
|
||||||
|
}),
|
||||||
|
offerThroughput: offerThroughput,
|
||||||
|
subscriptionType: userContext.subscriptionType,
|
||||||
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
|
defaultsCheck: {
|
||||||
|
flight: userContext.addCollectionFlight,
|
||||||
|
},
|
||||||
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||||
|
error: errorMessage,
|
||||||
|
errorStack: getErrorStack(error),
|
||||||
|
};
|
||||||
|
TelemetryProcessor.traceFailure(Action.CreateDatabase, addDatabasePaneFailedMessage, startKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getThroughput(): number {
|
||||||
|
const throughput: number = this.throughput();
|
||||||
|
return isNaN(throughput) ? 0 : Number(throughput);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeOfferThroughput(): number {
|
||||||
|
if (!this.canConfigureThroughput()) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isAutoPilotSelected()) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._getThroughput();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _isValid(): boolean {
|
||||||
|
// TODO add feature flag that disables validation for customers with custom accounts
|
||||||
|
if (this.isAutoPilotSelected()) {
|
||||||
|
const autoPilot = this._isAutoPilotSelectedAndWhatTier();
|
||||||
|
if (
|
||||||
|
!autoPilot ||
|
||||||
|
!autoPilot.maxThroughput ||
|
||||||
|
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput)
|
||||||
|
) {
|
||||||
|
this.formErrors(
|
||||||
|
`Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const throughput = this._getThroughput();
|
||||||
|
|
||||||
|
if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !this.throughputSpendAck()) {
|
||||||
|
this.formErrors(`Please acknowledge the estimated daily spend.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const autoscaleThroughput = this.maxAutoPilotThroughputSet() * 1;
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.isAutoPilotSelected() &&
|
||||||
|
autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
|
||||||
|
!this.throughputSpendAck()
|
||||||
|
) {
|
||||||
|
this.formErrors(`Please acknowledge the estimated monthly spend.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _isAutoPilotSelectedAndWhatTier(): DataModels.AutoPilotCreationSettings {
|
||||||
|
if (this.isAutoPilotSelected() && this.maxAutoPilotThroughputSet()) {
|
||||||
|
return {
|
||||||
|
maxThroughput: this.maxAutoPilotThroughputSet() * 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateThroughputLimitByDatabase() {
|
||||||
|
const throughputDefaults = this.container.collectionCreationDefaults.throughput;
|
||||||
|
this.throughput(throughputDefaults.shared);
|
||||||
|
this.maxThroughputRU(throughputDefaults.unlimitedmax);
|
||||||
|
this.minThroughputRU(throughputDefaults.unlimitedmin);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -202,7 +202,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
placeholder={databaseIdPlaceHolder}
|
placeholder={databaseIdPlaceHolder}
|
||||||
value={databaseId}
|
value={databaseId}
|
||||||
onChange={handleonChangeDBId}
|
onChange={handleonChangeDBId}
|
||||||
autoFocus
|
|
||||||
style={{ fontSize: 12 }}
|
style={{ fontSize: 12 }}
|
||||||
styles={{ root: { width: 300 } }}
|
styles={{ root: { width: 300 } }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
|
|||||||
aria-label="Database id"
|
aria-label="Database id"
|
||||||
aria-required="true"
|
aria-required="true"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
autoFocus={true}
|
|
||||||
id="database-id"
|
id="database-id"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]"
|
pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]"
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ 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 { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import {
|
||||||
|
GenericRightPaneComponent,
|
||||||
|
GenericRightPaneProps,
|
||||||
|
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
|
||||||
import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent";
|
import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent";
|
||||||
|
|
||||||
interface Location {
|
interface Location {
|
||||||
@@ -39,6 +42,7 @@ 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>();
|
||||||
|
|
||||||
@@ -88,6 +92,7 @@ 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();
|
||||||
@@ -125,10 +130,14 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
|||||||
setSelectedLocation(option?.data);
|
setSelectedLocation(option?.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const props: RightPaneFormProps = {
|
const genericPaneProps: GenericRightPaneProps = {
|
||||||
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(),
|
||||||
};
|
};
|
||||||
@@ -140,8 +149,8 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...props}>
|
<GenericRightPaneComponent {...genericPaneProps}>
|
||||||
<CopyNotebookPaneComponent {...copyNotebookPaneProps} />
|
<CopyNotebookPaneComponent {...copyNotebookPaneProps} />
|
||||||
</RightPaneForm>
|
</GenericRightPaneComponent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -130,8 +130,8 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
.hostNodes()
|
.hostNodes()
|
||||||
.simulate("change", { target: { value: selectedCollectionId } });
|
.simulate("change", { target: { value: selectedCollectionId } });
|
||||||
|
|
||||||
expect(wrapper.exists("#sidePanelOkButton")).toBe(true);
|
expect(wrapper.exists(".genericPaneSubmitBtn")).toBe(true);
|
||||||
wrapper.find("#sidePanelOkButton").hostNodes().simulate("submit");
|
wrapper.find(".genericPaneSubmitBtn").hostNodes().simulate("click");
|
||||||
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("#sidePanelOkButton")).toBe(true);
|
expect(wrapper.exists(".genericPaneSubmitBtn")).toBe(true);
|
||||||
wrapper.find("#sidePanelOkButton").hostNodes().simulate("submit");
|
wrapper.find(".genericPaneSubmitBtn").hostNodes().simulate("click");
|
||||||
expect(deleteCollection).toHaveBeenCalledWith(databaseId, selectedCollectionId);
|
expect(deleteCollection).toHaveBeenCalledWith(databaseId, selectedCollectionId);
|
||||||
|
|
||||||
const deleteFeedback = new DeleteFeedback(
|
const deleteFeedback = new DeleteFeedback(
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ 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 { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import {
|
||||||
|
GenericRightPaneComponent,
|
||||||
|
GenericRightPaneProps,
|
||||||
|
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
|
||||||
export interface DeleteCollectionConfirmationPaneProps {
|
export interface DeleteCollectionConfirmationPaneProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
closePanel: () => void;
|
closePanel: () => void;
|
||||||
@@ -32,7 +35,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
};
|
};
|
||||||
const collectionName = getCollectionName().toLocaleLowerCase();
|
const collectionName = getCollectionName().toLocaleLowerCase();
|
||||||
const paneTitle = "Delete " + collectionName;
|
const paneTitle = "Delete " + collectionName;
|
||||||
const onSubmit = async (): Promise<void> => {
|
const submit = 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;
|
||||||
@@ -97,15 +100,19 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const props: RightPaneFormProps = {
|
const genericPaneProps: GenericRightPaneProps = {
|
||||||
formError: formError,
|
formError: formError,
|
||||||
|
formErrorDetail: formError,
|
||||||
|
id: "deleteCollectionpane",
|
||||||
isExecuting,
|
isExecuting,
|
||||||
|
title: paneTitle,
|
||||||
submitButtonText: "OK",
|
submitButtonText: "OK",
|
||||||
onSubmit,
|
onClose: closePanel,
|
||||||
|
onSubmit: submit,
|
||||||
expandConsole: () => explorer.expandConsole(),
|
expandConsole: () => explorer.expandConsole(),
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...props}>
|
<GenericRightPaneComponent {...genericPaneProps}>
|
||||||
<div className="panelFormWrapper">
|
<div className="panelFormWrapper">
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
<div className="confirmDeleteInput">
|
<div className="confirmDeleteInput">
|
||||||
@@ -113,7 +120,6 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
<Text variant="small">Confirm by typing the {collectionName.toLowerCase()} id</Text>
|
<Text variant="small">Confirm by typing the {collectionName.toLowerCase()} id</Text>
|
||||||
<TextField
|
<TextField
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
autoFocus
|
|
||||||
value={inputCollectionName}
|
value={inputCollectionName}
|
||||||
styles={{ fieldGroup: { width: 300 } }}
|
styles={{ fieldGroup: { width: 300 } }}
|
||||||
onChange={(event, newInput?: string) => {
|
onChange={(event, newInput?: string) => {
|
||||||
@@ -143,6 +149,6 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</RightPaneForm>
|
</GenericRightPaneComponent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
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("button")).toBe(true);
|
expect(wrapper.exists("#sidePanelOkButton")).toBe(true);
|
||||||
wrapper.find("button").hostNodes().simulate("submit");
|
wrapper.find("#sidePanelOkButton").hostNodes().simulate("submit");
|
||||||
expect(deleteDatabase).toHaveBeenCalledWith(selectedDatabaseId);
|
expect(deleteDatabase).toHaveBeenCalledWith(selectedDatabaseId);
|
||||||
wrapper.unmount();
|
wrapper.unmount();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ 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 { RightPaneForm, RightPaneFormProps } from "./RightPaneForm/RightPaneForm";
|
import { PanelLoadingScreen } from "./PanelLoadingScreen";
|
||||||
|
|
||||||
interface DeleteDatabaseConfirmationPanelProps {
|
interface DeleteDatabaseConfirmationPanelProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
@@ -22,19 +23,36 @@ interface DeleteDatabaseConfirmationPanelProps {
|
|||||||
selectedDatabase: Database;
|
selectedDatabase: Database;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseConfirmationPanelProps> = ({
|
export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseConfirmationPanelProps> = (
|
||||||
explorer,
|
props: DeleteDatabaseConfirmationPanelProps
|
||||||
openNotificationConsole,
|
): JSX.Element => {
|
||||||
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 submit = async (): Promise<void> => {
|
const getPanelErrorProps = (): PanelInfoErrorProps => {
|
||||||
|
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()}`);
|
||||||
@@ -51,7 +69,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await deleteDatabase(selectedDatabase.id());
|
await deleteDatabase(selectedDatabase.id());
|
||||||
closePanel();
|
props.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);
|
||||||
@@ -103,34 +121,19 @@ 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 (
|
||||||
<RightPaneForm {...props}>
|
<form className="panelFormWrapper" onSubmit={submit}>
|
||||||
{!formError && <PanelInfoErrorComponent {...errorProps} />}
|
<PanelInfoErrorComponent {...getPanelErrorProps()} />
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
<div className="confirmDeleteInput">
|
<div className="confirmDeleteInput">
|
||||||
<span className="mandatoryStar">* </span>
|
<span className="mandatoryStar">* </span>
|
||||||
<Text variant="small">Confirm by typing the database id</Text>
|
<Text variant="small">Confirm by typing the database id</Text>
|
||||||
<TextField
|
<TextField
|
||||||
id="confirmDatabaseId"
|
id="confirmDatabaseId"
|
||||||
autoFocus
|
|
||||||
styles={{ fieldGroup: { width: 300 } }}
|
styles={{ fieldGroup: { width: 300 } }}
|
||||||
onChange={(event, newInput?: string) => {
|
onChange={(event, newInput?: string) => {
|
||||||
setDatabaseInput(newInput);
|
setDatabaseInput(newInput);
|
||||||
@@ -157,6 +160,8 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</RightPaneForm>
|
<PanelFooterComponent buttonLabel="OK" />
|
||||||
|
{isLoading && <PanelLoadingScreen />}
|
||||||
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { IDropdownOption, IImageProps, Image, Stack, Text } from "@fluentui/react";
|
|
||||||
import { useBoolean } from "@fluentui/react-hooks";
|
import { useBoolean } from "@fluentui/react-hooks";
|
||||||
|
import { IDropdownOption, IImageProps, Image, Stack, Text } from "@fluentui/react";
|
||||||
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 { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import {
|
||||||
|
GenericRightPaneComponent,
|
||||||
|
GenericRightPaneProps,
|
||||||
|
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
|
||||||
import { InputParameter } from "./InputParameter";
|
import { InputParameter } from "./InputParameter";
|
||||||
|
|
||||||
interface ExecuteSprocParamsPaneProps {
|
interface ExecuteSprocParamsPaneProps {
|
||||||
@@ -33,11 +35,24 @@ 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++) {
|
||||||
@@ -51,7 +66,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}`);
|
||||||
logConsoleError(`Invalid param specified: ${invalidParam} is not a valid literal value`);
|
setFormErrorsDetails(`Invalid param specified: ${invalidParam} is not a valid literal value`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const submit = (): void => {
|
const submit = (): void => {
|
||||||
@@ -113,16 +128,8 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
|
|||||||
setParamKeyValues(cloneParamKeyValue);
|
setParamKeyValues(cloneParamKeyValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
const props: RightPaneFormProps = {
|
|
||||||
expandConsole,
|
|
||||||
formError: formError,
|
|
||||||
isExecuting: isLoading,
|
|
||||||
submitButtonText: "Execute",
|
|
||||||
onSubmit: () => submit(),
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...props}>
|
<GenericRightPaneComponent {...genericPaneProps}>
|
||||||
<div className="panelFormWrapper">
|
<div className="panelFormWrapper">
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
<InputParameter
|
<InputParameter
|
||||||
@@ -162,6 +169,6 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
|
|||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</RightPaneForm>
|
</GenericRightPaneComponent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ export const InputParameter: FunctionComponent<InputParameterProps> = ({
|
|||||||
<TextField
|
<TextField
|
||||||
label={inputLabel && inputLabel}
|
label={inputLabel && inputLabel}
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
autoFocus
|
|
||||||
value={paramValue}
|
value={paramValue}
|
||||||
onChange={onParamValueChange}
|
onChange={onParamValueChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,134 @@
|
|||||||
|
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||||
|
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}
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyDown={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} alt="loader" />
|
||||||
|
</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} onKeyDown={onClose}></div>
|
||||||
|
<div className="contextual-pane" id={id} style={{ height: panelHeight }} onKeyDown={onKeyDown}>
|
||||||
|
<div className="panelContentWrapper">
|
||||||
|
{renderPanelHeader()}
|
||||||
|
{renderErrorSection()}
|
||||||
|
{children}
|
||||||
|
{renderPanelFooter()}
|
||||||
|
</div>
|
||||||
|
{renderLoadingScreen()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -24,6 +24,49 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
|
|||||||
"_refreshSparkEnabledStateForAccount": [Function],
|
"_refreshSparkEnabledStateForAccount": [Function],
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"addCollectionText": [Function],
|
"addCollectionText": [Function],
|
||||||
|
"addDatabasePane": AddDatabasePane {
|
||||||
|
"autoPilotUsageCost": [Function],
|
||||||
|
"canConfigureThroughput": [Function],
|
||||||
|
"canExceedMaximumValue": [Function],
|
||||||
|
"canRequestSupport": [Function],
|
||||||
|
"container": [Circular],
|
||||||
|
"costsVisible": [Function],
|
||||||
|
"databaseCreateNewShared": [Function],
|
||||||
|
"databaseId": [Function],
|
||||||
|
"databaseIdLabel": [Function],
|
||||||
|
"databaseIdPlaceHolder": [Function],
|
||||||
|
"databaseIdTooltipText": [Function],
|
||||||
|
"databaseLevelThroughputTooltipText": [Function],
|
||||||
|
"firstFieldHasFocus": [Function],
|
||||||
|
"formErrors": [Function],
|
||||||
|
"formErrorsDetails": [Function],
|
||||||
|
"freeTierExceedThroughputTooltip": [Function],
|
||||||
|
"id": "adddatabasepane",
|
||||||
|
"isAutoPilotSelected": [Function],
|
||||||
|
"isExecuting": [Function],
|
||||||
|
"isFreeTierAccount": [Function],
|
||||||
|
"isTemplateReady": [Function],
|
||||||
|
"maxAutoPilotThroughputSet": [Function],
|
||||||
|
"maxThroughputRU": [Function],
|
||||||
|
"maxThroughputRUText": [Function],
|
||||||
|
"minThroughputRU": [Function],
|
||||||
|
"onMoreDetailsKeyPress": [Function],
|
||||||
|
"requestUnitsUsageCost": [Function],
|
||||||
|
"ruToolTipText": [Function],
|
||||||
|
"showUpsellMessage": [Function],
|
||||||
|
"throughput": [Function],
|
||||||
|
"throughputRangeText": [Function],
|
||||||
|
"throughputSpendAck": [Function],
|
||||||
|
"throughputSpendAckText": [Function],
|
||||||
|
"throughputSpendAckVisible": [Function],
|
||||||
|
"title": [Function],
|
||||||
|
"upsellAnchorText": [Function],
|
||||||
|
"upsellAnchorUrl": [Function],
|
||||||
|
"upsellMessage": [Function],
|
||||||
|
"upsellMessageAriaLabel": [Function],
|
||||||
|
"visible": [Function],
|
||||||
|
},
|
||||||
|
"addDatabaseText": [Function],
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ 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 { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import {
|
||||||
|
GenericRightPaneComponent,
|
||||||
|
GenericRightPaneProps,
|
||||||
|
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
|
||||||
|
|
||||||
interface LoadQueryPaneProps {
|
interface LoadQueryPaneProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
@@ -21,6 +24,7 @@ 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>();
|
||||||
|
|
||||||
@@ -31,6 +35,19 @@ 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);
|
||||||
@@ -39,8 +56,10 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -56,6 +75,7 @@ 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}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -80,20 +100,14 @@ 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 (
|
||||||
<RightPaneForm {...props}>
|
<GenericRightPaneComponent {...genericPaneProps}>
|
||||||
<div className="panelFormWrapper">
|
<div className="panelFormWrapper">
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
@@ -101,7 +115,6 @@ export const LoadQueryPane: FunctionComponent<LoadQueryPaneProps> = ({
|
|||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
label="Select a query document"
|
label="Select a query document"
|
||||||
value={selectedFileName}
|
value={selectedFileName}
|
||||||
autoFocus
|
|
||||||
readOnly
|
readOnly
|
||||||
styles={{ fieldGroup: { width: 300 } }}
|
styles={{ fieldGroup: { width: 300 } }}
|
||||||
/>
|
/>
|
||||||
@@ -118,6 +131,6 @@ export const LoadQueryPane: FunctionComponent<LoadQueryPaneProps> = ({
|
|||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</RightPaneForm>
|
</GenericRightPaneComponent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
// 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`] = `
|
||||||
<RightPaneForm
|
<GenericRightPaneComponent
|
||||||
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"
|
||||||
@@ -18,7 +22,6 @@ exports[`Load Query Pane should render Default properly 1`] = `
|
|||||||
horizontal={true}
|
horizontal={true}
|
||||||
>
|
>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
autoFocus={true}
|
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
label="Select a query document"
|
label="Select a query document"
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
@@ -54,5 +57,5 @@ exports[`Load Query Pane should render Default properly 1`] = `
|
|||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</RightPaneForm>
|
</GenericRightPaneComponent>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { mount, shallow, ShallowWrapper } from "enzyme";
|
import { 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 = mount(
|
const newWrapper = shallow(
|
||||||
<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 = mount(
|
const newWrapper = shallow(
|
||||||
<NewVertexPanel
|
<NewVertexPanel
|
||||||
explorer={fakeExplorer}
|
explorer={fakeExplorer}
|
||||||
partitionKeyPropertyProp={undefined}
|
partitionKeyPropertyProp={undefined}
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ 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 { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { PanelFooterComponent } from "../PanelFooterComponent";
|
||||||
|
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
|
||||||
|
import { PanelLoadingScreen } from "../PanelLoadingScreen";
|
||||||
export interface INewVertexPanelProps {
|
export interface INewVertexPanelProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
partitionKeyPropertyProp: string;
|
partitionKeyPropertyProp: string;
|
||||||
@@ -19,10 +21,14 @@ 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 = () => {
|
const submit = (event: React.MouseEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
setErrorMessage(undefined);
|
setErrorMessage(undefined);
|
||||||
|
setShowErrorDetails(false);
|
||||||
if (onSubmit !== undefined) {
|
if (onSubmit !== undefined) {
|
||||||
setLoadingTrue();
|
setLoadingTrue();
|
||||||
onSubmit(newVertexDataValue, onError, onSuccess);
|
onSubmit(newVertexDataValue, onError, onSuccess);
|
||||||
@@ -31,6 +37,7 @@ export const NewVertexPanel: FunctionComponent<INewVertexPanelProps> = ({
|
|||||||
|
|
||||||
const onError = (errorMsg: string) => {
|
const onError = (errorMsg: string) => {
|
||||||
setErrorMessage(errorMsg);
|
setErrorMessage(errorMsg);
|
||||||
|
setShowErrorDetails(true);
|
||||||
setLoadingFalse();
|
setLoadingFalse();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -42,16 +49,17 @@ 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 (
|
||||||
<RightPaneForm {...props}>
|
<form className="panelFormWrapper" onSubmit={(event: React.MouseEvent<HTMLFormElement>) => submit(event)}>
|
||||||
|
{errorMessage && (
|
||||||
|
<PanelInfoErrorComponent
|
||||||
|
message={errorMessage}
|
||||||
|
messageType="error"
|
||||||
|
showErrorDetails={showErrorDetails}
|
||||||
|
openNotificationConsole={openNotificationConsole}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
<NewVertexComponent
|
<NewVertexComponent
|
||||||
newVertexDataProp={newVertexDataValue}
|
newVertexDataProp={newVertexDataValue}
|
||||||
@@ -59,6 +67,8 @@ export const NewVertexPanel: FunctionComponent<INewVertexPanelProps> = ({
|
|||||||
onChangeProp={onChange}
|
onChangeProp={onChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</RightPaneForm>
|
<PanelFooterComponent buttonLabel={buttonLabel} />
|
||||||
|
{isLoading && <PanelLoadingScreen />}
|
||||||
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
// 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`] = `
|
||||||
<RightPaneForm
|
<form
|
||||||
expandConsole={[Function]}
|
className="panelFormWrapper"
|
||||||
formError=""
|
|
||||||
isExecuting={false}
|
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
submitButtonText="OK"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="panelMainContent"
|
className="panelMainContent"
|
||||||
@@ -16,5 +13,8 @@ exports[`New Vertex Panel should render default property 1`] = `
|
|||||||
partitionKeyPropertyProp=""
|
partitionKeyPropertyProp=""
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</RightPaneForm>
|
<PanelFooterComponent
|
||||||
|
buttonLabel="OK"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import AddDatabasePaneTemplate from "./AddDatabasePane.html";
|
||||||
import CassandraAddCollectionPaneTemplate from "./CassandraAddCollectionPane.html";
|
import CassandraAddCollectionPaneTemplate from "./CassandraAddCollectionPane.html";
|
||||||
export class PaneComponent {
|
export class PaneComponent {
|
||||||
constructor(data: any) {
|
constructor(data: any) {
|
||||||
@@ -5,6 +6,15 @@ export class PaneComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AddDatabasePaneComponent {
|
||||||
|
constructor() {
|
||||||
|
return {
|
||||||
|
viewModel: PaneComponent,
|
||||||
|
template: AddDatabasePaneTemplate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class CassandraAddCollectionPaneComponent {
|
export class CassandraAddCollectionPaneComponent {
|
||||||
constructor() {
|
constructor() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -89,6 +89,7 @@
|
|||||||
.paneErrorLink {
|
.paneErrorLink {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: @mediumFontSize;
|
font-size: @mediumFontSize;
|
||||||
|
color: @AccentMediumHigh;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ 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> = ({
|
||||||
@@ -41,7 +40,13 @@ export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProp
|
|||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
{showErrorDetails && (
|
{showErrorDetails && (
|
||||||
<a className="paneErrorLink" role="link" onClick={openNotificationConsole}>
|
<a
|
||||||
|
className="paneErrorLink"
|
||||||
|
role="link"
|
||||||
|
onClick={openNotificationConsole}
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyDown={openNotificationConsole}
|
||||||
|
>
|
||||||
More details
|
More details
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ import LoadingIndicator_3Squares from "../../../images/LoadingIndicator_3Squares
|
|||||||
|
|
||||||
export const PanelLoadingScreen: React.FunctionComponent = () => (
|
export const PanelLoadingScreen: React.FunctionComponent = () => (
|
||||||
<div className="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer">
|
<div className="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer">
|
||||||
<img className="dataExplorerLoader" src={LoadingIndicator_3Squares} />
|
<img className="dataExplorerLoader" src={LoadingIndicator_3Squares} alt="Loader" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ 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 { CodeOfConduct } from "../../Controls/NotebookGallery/CodeOfConduct/CodeOfConduct";
|
import { CodeOfConductComponent } from "../../Controls/NotebookGallery/CodeOfConductComponent";
|
||||||
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 { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import {
|
||||||
|
GenericRightPaneComponent,
|
||||||
|
GenericRightPaneProps,
|
||||||
|
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
|
||||||
import { PublishNotebookPaneComponent, PublishNotebookPaneProps } from "./PublishNotebookPaneComponent";
|
import { PublishNotebookPaneComponent, PublishNotebookPaneProps } from "./PublishNotebookPaneComponent";
|
||||||
|
|
||||||
export interface PublishNotebookPaneAProps {
|
export interface PublishNotebookPaneAProps {
|
||||||
@@ -152,6 +155,7 @@ export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> =
|
|||||||
clearPublishingMessage();
|
clearPublishingMessage();
|
||||||
setIsExecuting(false);
|
setIsExecuting(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
closePanel();
|
closePanel();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -166,11 +170,15 @@ export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> =
|
|||||||
setFormErrorDetail("");
|
setFormErrorDetail("");
|
||||||
};
|
};
|
||||||
|
|
||||||
const props: RightPaneFormProps = {
|
const props: GenericRightPaneProps = {
|
||||||
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,
|
||||||
};
|
};
|
||||||
@@ -193,10 +201,10 @@ export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> =
|
|||||||
onTakeSnapshot,
|
onTakeSnapshot,
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...props}>
|
<GenericRightPaneComponent {...props}>
|
||||||
{!isCodeOfConductAccepted ? (
|
{!isCodeOfConductAccepted ? (
|
||||||
<div style={{ padding: "25px", marginTop: "10px" }}>
|
<div style={{ padding: "25px", marginTop: "10px" }}>
|
||||||
<CodeOfConduct
|
<CodeOfConductComponent
|
||||||
junoClient={junoClient}
|
junoClient={junoClient}
|
||||||
onAcceptCodeOfConduct={(isAccepted) => {
|
onAcceptCodeOfConduct={(isAccepted) => {
|
||||||
setIsCodeOfConductAccepted(isAccepted);
|
setIsCodeOfConductAccepted(isAccepted);
|
||||||
@@ -206,6 +214,6 @@ export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> =
|
|||||||
) : (
|
) : (
|
||||||
<PublishNotebookPaneComponent {...publishNotebookPaneProps} />
|
<PublishNotebookPaneComponent {...publishNotebookPaneProps} />
|
||||||
)}
|
)}
|
||||||
</RightPaneForm>
|
</GenericRightPaneComponent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Text, TextField } from "@fluentui/react";
|
|
||||||
import { useBoolean } from "@fluentui/react-hooks";
|
import { useBoolean } from "@fluentui/react-hooks";
|
||||||
|
import { Text, TextField } from "@fluentui/react";
|
||||||
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,7 +9,10 @@ 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 { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import {
|
||||||
|
GenericRightPaneComponent,
|
||||||
|
GenericRightPaneProps,
|
||||||
|
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
|
||||||
|
|
||||||
interface SaveQueryPaneProps {
|
interface SaveQueryPaneProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
@@ -22,16 +25,32 @@ 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");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,17 +148,8 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const props: RightPaneFormProps = {
|
|
||||||
expandConsole: () => explorer.expandConsole(),
|
|
||||||
formError: formError,
|
|
||||||
isExecuting: isLoading,
|
|
||||||
submitButtonText: canSaveQueries() ? "Save" : "Complete setup",
|
|
||||||
onSubmit: () => {
|
|
||||||
canSaveQueries() ? submit() : setupQueries();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...props}>
|
<GenericRightPaneComponent {...genericPaneProps}>
|
||||||
<div className="panelFormWrapper">
|
<div className="panelFormWrapper">
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
{!canSaveQueries() ? (
|
{!canSaveQueries() ? (
|
||||||
@@ -148,7 +158,6 @@ 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);
|
||||||
@@ -157,6 +166,6 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</RightPaneForm>
|
</GenericRightPaneComponent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
// 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`] = `
|
||||||
<RightPaneForm
|
<GenericRightPaneComponent
|
||||||
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"
|
||||||
@@ -21,5 +25,5 @@ exports[`Save Query Pane should render Default properly 1`] = `
|
|||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</RightPaneForm>
|
</GenericRightPaneComponent>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ 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 { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import {
|
||||||
|
GenericRightPaneComponent,
|
||||||
|
GenericRightPaneProps,
|
||||||
|
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
|
||||||
|
|
||||||
export interface StringInputPanelProps {
|
export interface StringInputPanelProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
@@ -37,6 +40,7 @@ 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> => {
|
||||||
@@ -47,6 +51,7 @@ export const StringInputPane: FunctionComponent<StringInputPanelProps> = ({
|
|||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
setFormErrors("");
|
setFormErrors("");
|
||||||
|
setFormErrorsDetails("");
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearMessage = logConsoleProgress(`${inProgressMessage} ${stringInput}`);
|
const clearMessage = logConsoleProgress(`${inProgressMessage} ${stringInput}`);
|
||||||
@@ -73,32 +78,37 @@ 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 props: RightPaneFormProps = {
|
const genericPaneProps: GenericRightPaneProps = {
|
||||||
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 (
|
||||||
<RightPaneForm {...props}>
|
<GenericRightPaneComponent {...genericPaneProps}>
|
||||||
<div className="paneMainContent">
|
<div className="paneMainContent">
|
||||||
<TextField
|
<TextField
|
||||||
label={inputLabel}
|
label={inputLabel}
|
||||||
name="collectionIdConfirmation"
|
name="collectionIdConfirmation"
|
||||||
value={stringInput}
|
value={stringInput}
|
||||||
autoFocus
|
|
||||||
required
|
required
|
||||||
onChange={(event: FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) =>
|
onChange={(event: FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) =>
|
||||||
setStringInput(newValue)
|
setStringInput(newValue)
|
||||||
@@ -106,6 +116,6 @@ export const StringInputPane: FunctionComponent<StringInputPanelProps> = ({
|
|||||||
aria-label={inputLabel}
|
aria-label={inputLabel}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</RightPaneForm>
|
</GenericRightPaneComponent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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 "./TableQuerySelectPanel";
|
import { TableQuerySelectPanel } from "./index";
|
||||||
|
|
||||||
describe("Table query select Panel", () => {
|
describe("Table query select Panel", () => {
|
||||||
const fakeExplorer = {} as Explorer;
|
const fakeExplorer = {} as Explorer;
|
||||||
@@ -4,7 +4,10 @@ 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 { RightPaneForm, RightPaneFormProps } from "../../RightPaneForm/RightPaneForm";
|
import {
|
||||||
|
GenericRightPaneComponent,
|
||||||
|
GenericRightPaneProps,
|
||||||
|
} from "../../GenericRightPaneComponent/GenericRightPaneComponent";
|
||||||
|
|
||||||
interface TableQuerySelectPanelProps {
|
interface TableQuerySelectPanelProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
@@ -28,20 +31,24 @@ export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps
|
|||||||
]);
|
]);
|
||||||
const [isAvailableColumnChecked, setIsAvailableColumnChecked] = useState<boolean>(true);
|
const [isAvailableColumnChecked, setIsAvailableColumnChecked] = useState<boolean>(true);
|
||||||
|
|
||||||
const onSubmit = (): void => {
|
const genericPaneProps: GenericRightPaneProps = {
|
||||||
|
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) {
|
||||||
@@ -121,7 +128,7 @@ export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...props}>
|
<GenericRightPaneComponent {...genericPaneProps}>
|
||||||
<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>
|
||||||
@@ -146,6 +153,6 @@ export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</RightPaneForm>
|
</GenericRightPaneComponent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -78,7 +78,7 @@ export const UploadFilePane: FunctionComponent<UploadFilePanelProps> = ({
|
|||||||
return uploadFile(file.name, fileContent);
|
return uploadFile(file.name, fileContent);
|
||||||
};
|
};
|
||||||
|
|
||||||
const props: RightPaneFormProps = {
|
const genericPaneProps: RightPaneFormProps = {
|
||||||
expandConsole,
|
expandConsole,
|
||||||
formError: formErrors,
|
formError: formErrors,
|
||||||
isExecuting: isExecuting,
|
isExecuting: isExecuting,
|
||||||
@@ -87,7 +87,7 @@ export const UploadFilePane: FunctionComponent<UploadFilePanelProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...props}>
|
<RightPaneForm {...genericPaneProps}>
|
||||||
<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 props: RightPaneFormProps = {
|
const genericPaneProps: 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 {...props}>
|
<RightPaneForm {...genericPaneProps}>
|
||||||
<div className="paneMainContent">
|
<div className="paneMainContent">
|
||||||
<Upload
|
<Upload
|
||||||
label="Select JSON Files"
|
label="Select JSON Files"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,6 @@ import { AuthType } from "../../AuthType";
|
|||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { getDatabaseName } from "../../Utils/APITypeUtils";
|
|
||||||
import { FeaturePanelLauncher } from "../Controls/FeaturePanel/FeaturePanelLauncher";
|
import { FeaturePanelLauncher } from "../Controls/FeaturePanel/FeaturePanelLauncher";
|
||||||
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
|
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
@@ -291,7 +290,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
} else {
|
} else {
|
||||||
items.push({
|
items.push({
|
||||||
iconSrc: AddDatabaseIcon,
|
iconSrc: AddDatabaseIcon,
|
||||||
title: "New " + getDatabaseName(),
|
title: this.container.addDatabaseText(),
|
||||||
description: null,
|
description: null,
|
||||||
onClick: () => this.container.openAddDatabasePane(),
|
onClick: () => this.container.openAddDatabasePane(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ function TabNav({ tab, active }: { tab: Tab; active: boolean }) {
|
|||||||
aria-controls={tab.tabId}
|
aria-controls={tab.tabId}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role="tab"
|
role="tab"
|
||||||
|
onFocus={() => setHovering(true)}
|
||||||
>
|
>
|
||||||
<span className="tabNavContentContainer">
|
<span className="tabNavContentContainer">
|
||||||
<a data-toggle="tab" href={"#" + tab.tabId} tabIndex={-1}>
|
<a data-toggle="tab" href={"#" + tab.tabId} tabIndex={-1}>
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
import { useBoolean } from "@fluentui/react-hooks";
|
|
||||||
import { initializeIcons } from "@fluentui/react";
|
import { initializeIcons } from "@fluentui/react";
|
||||||
|
import { useBoolean } from "@fluentui/react-hooks";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { render } from "react-dom";
|
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();
|
||||||
|
|
||||||
@@ -86,8 +86,10 @@ const App: React.FunctionComponent = () => {
|
|||||||
<span
|
<span
|
||||||
className="title"
|
className="title"
|
||||||
onClick={() => window.open("https://portal.azure.com", "_blank")}
|
onClick={() => window.open("https://portal.azure.com", "_blank")}
|
||||||
tabIndex={0}
|
|
||||||
title="Go to Azure Portal"
|
title="Go to Azure Portal"
|
||||||
|
onKeyDown={() => window.open("https://portal.azure.com", "_blank")}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
Microsoft Azure
|
Microsoft Azure
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ const App: React.FunctionComponent = () => {
|
|||||||
closePanel={closeSidePanel}
|
closePanel={closeSidePanel}
|
||||||
isConsoleExpanded={isNotificationConsoleExpanded}
|
isConsoleExpanded={isNotificationConsoleExpanded}
|
||||||
/>
|
/>
|
||||||
|
<div data-bind='component: { name: "add-database-pane", params: {data: addDatabasePane} }' />
|
||||||
<div data-bind='component: { name: "cassandra-add-collection-pane", params: { data: cassandraAddCollectionPane} }' />
|
<div data-bind='component: { name: "cassandra-add-collection-pane", params: { data: cassandraAddCollectionPane} }' />
|
||||||
{showDialog && <Dialog {...dialogProps} />}
|
{showDialog && <Dialog {...dialogProps} />}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import * as React from "react";
|
|
||||||
import { useBoolean } from "@fluentui/react-hooks";
|
import { useBoolean } from "@fluentui/react-hooks";
|
||||||
import { HttpHeaders } from "../../../Common/Constants";
|
import * as React from "react";
|
||||||
import { GenerateTokenResponse } from "../../../Contracts/DataModels";
|
|
||||||
import { configContext } from "../../../ConfigContext";
|
|
||||||
import { AuthType } from "../../../AuthType";
|
import { AuthType } from "../../../AuthType";
|
||||||
|
import { HttpHeaders } from "../../../Common/Constants";
|
||||||
|
import { configContext } from "../../../ConfigContext";
|
||||||
|
import { GenerateTokenResponse } from "../../../Contracts/DataModels";
|
||||||
import { isResourceTokenConnectionString } from "../Helpers/ResourceTokenUtils";
|
import { isResourceTokenConnectionString } from "../Helpers/ResourceTokenUtils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -75,16 +75,16 @@ export const ConnectExplorer: React.FunctionComponent<Props> = ({
|
|||||||
<p className="connectExplorerContent">
|
<p className="connectExplorerContent">
|
||||||
<input className="filterbtnstyle" type="submit" value="Connect" />
|
<input className="filterbtnstyle" type="submit" value="Connect" />
|
||||||
</p>
|
</p>
|
||||||
<p className="switchConnectTypeText" onClick={login}>
|
<div className="switchConnectTypeText" onClick={login} onKeyDown={login} role="button" tabIndex={0}>
|
||||||
Sign In with Azure Account
|
Sign In with Azure Account
|
||||||
</p>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
) : (
|
) : (
|
||||||
<div id="connectWithAad">
|
<div id="connectWithAad">
|
||||||
<input className="filterbtnstyle" type="button" value="Sign In" onClick={login} />
|
<input className="filterbtnstyle" type="button" value="Sign In" onClick={login} />
|
||||||
<p className="switchConnectTypeText" onClick={showForm}>
|
<div className="switchConnectTypeText" onClick={showForm} onKeyDown={showForm} role="button" tabIndex={0}>
|
||||||
Connect to your account with connection string
|
Connect to your account with connection string
|
||||||
</p>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user