mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-06 03:00:23 +00:00
pull master
This commit is contained in:
@@ -54,7 +54,6 @@ src/Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.ts
|
|||||||
src/Explorer/Controls/InputTypeahead/InputTypeahead.ts
|
src/Explorer/Controls/InputTypeahead/InputTypeahead.ts
|
||||||
src/Explorer/Controls/JsonEditor/JsonEditorComponent.ts
|
src/Explorer/Controls/JsonEditor/JsonEditorComponent.ts
|
||||||
src/Explorer/Controls/Notebook/NotebookAppMessageHandler.ts
|
src/Explorer/Controls/Notebook/NotebookAppMessageHandler.ts
|
||||||
src/Explorer/Controls/ThroughputInput/ThroughputInput.test.ts
|
|
||||||
src/Explorer/Controls/ThroughputInput/ThroughputInputComponent.ts
|
src/Explorer/Controls/ThroughputInput/ThroughputInputComponent.ts
|
||||||
src/Explorer/Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3.ts
|
src/Explorer/Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3.ts
|
||||||
src/Explorer/Controls/Toolbar/IToolbarAction.ts
|
src/Explorer/Controls/Toolbar/IToolbarAction.ts
|
||||||
@@ -109,10 +108,8 @@ src/Explorer/Notebook/NotebookUtil.ts
|
|||||||
src/Explorer/OpenActions.test.ts
|
src/Explorer/OpenActions.test.ts
|
||||||
src/Explorer/OpenActions.ts
|
src/Explorer/OpenActions.ts
|
||||||
src/Explorer/OpenActionsStubs.ts
|
src/Explorer/OpenActionsStubs.ts
|
||||||
src/Explorer/Panes/AddCollectionPane.test.ts
|
|
||||||
src/Explorer/Panes/AddCollectionPane.ts
|
|
||||||
src/Explorer/Panes/AddDatabasePane.test.ts
|
|
||||||
src/Explorer/Panes/AddDatabasePane.ts
|
src/Explorer/Panes/AddDatabasePane.ts
|
||||||
|
src/Explorer/Panes/AddDatabasePane.test.ts
|
||||||
src/Explorer/Panes/BrowseQueriesPane.ts
|
src/Explorer/Panes/BrowseQueriesPane.ts
|
||||||
src/Explorer/Panes/CassandraAddCollectionPane.ts
|
src/Explorer/Panes/CassandraAddCollectionPane.ts
|
||||||
src/Explorer/Panes/ContextualPaneBase.ts
|
src/Explorer/Panes/ContextualPaneBase.ts
|
||||||
|
|||||||
@@ -1757,7 +1757,7 @@ input::-webkit-calendar-picker-indicator {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contextual-pane .paneMainContent {
|
.paneMainContent {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding-left: 34px;
|
padding-left: 34px;
|
||||||
padding-right: 34px;
|
padding-right: 34px;
|
||||||
|
|||||||
38516
package-lock.json
generated
38516
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
26
package.json
26
package.json
@@ -9,9 +9,9 @@
|
|||||||
"@azure/cosmos-language-service": "0.0.5",
|
"@azure/cosmos-language-service": "0.0.5",
|
||||||
"@azure/identity": "1.2.1",
|
"@azure/identity": "1.2.1",
|
||||||
"@azure/ms-rest-nodeauth": "3.0.7",
|
"@azure/ms-rest-nodeauth": "3.0.7",
|
||||||
"@babel/plugin-proposal-class-properties": "7.13.0",
|
"@babel/plugin-proposal-class-properties": "7.12.1",
|
||||||
"@babel/plugin-proposal-decorators": "7.13.15",
|
"@babel/plugin-proposal-decorators": "7.12.12",
|
||||||
"@fluentui/react": "8.14.4",
|
"@fluentui/react": "8.14.3",
|
||||||
"@jupyterlab/services": "6.0.2",
|
"@jupyterlab/services": "6.0.2",
|
||||||
"@jupyterlab/terminal": "3.0.3",
|
"@jupyterlab/terminal": "3.0.3",
|
||||||
"@microsoft/applicationinsights-web": "2.6.1",
|
"@microsoft/applicationinsights-web": "2.6.1",
|
||||||
@@ -40,9 +40,6 @@
|
|||||||
"@nteract/transform-vega": "7.0.6",
|
"@nteract/transform-vega": "7.0.6",
|
||||||
"@octokit/rest": "17.9.2",
|
"@octokit/rest": "17.9.2",
|
||||||
"@phosphor/widgets": "1.9.3",
|
"@phosphor/widgets": "1.9.3",
|
||||||
"@types/mkdirp": "1.0.1",
|
|
||||||
"@types/node-fetch": "2.5.7",
|
|
||||||
"@uifabric/react-cards": "0.109.110",
|
|
||||||
"applicationinsights": "1.8.0",
|
"applicationinsights": "1.8.0",
|
||||||
"bootstrap": "3.4.1",
|
"bootstrap": "3.4.1",
|
||||||
"canvas": "file:./canvas",
|
"canvas": "file:./canvas",
|
||||||
@@ -54,10 +51,11 @@
|
|||||||
"datatables.net-colreorder-dt": "1.5.1",
|
"datatables.net-colreorder-dt": "1.5.1",
|
||||||
"datatables.net-dt": "1.10.19",
|
"datatables.net-dt": "1.10.19",
|
||||||
"date-fns": "1.29.0",
|
"date-fns": "1.29.0",
|
||||||
"dayjs": "1.10.4",
|
"dayjs": "1.8.19",
|
||||||
"dotenv": "9.0.0",
|
"dom-to-image": "2.6.0",
|
||||||
"eslint-plugin-jest": "24.3.6",
|
"dotenv": "8.2.0",
|
||||||
"eslint-plugin-react": "7.23.2",
|
"eslint-plugin-jest": "23.13.2",
|
||||||
|
"eslint-plugin-react": "7.20.0",
|
||||||
"hasher": "1.2.0",
|
"hasher": "1.2.0",
|
||||||
"html2canvas": "1.0.0-rc.7",
|
"html2canvas": "1.0.0-rc.7",
|
||||||
"i18next": "20.2.2",
|
"i18next": "20.2.2",
|
||||||
@@ -95,7 +93,8 @@
|
|||||||
"styled-components": "5.3.0",
|
"styled-components": "5.3.0",
|
||||||
"swr": "0.4.0",
|
"swr": "0.4.0",
|
||||||
"underscore": "1.9.1",
|
"underscore": "1.9.1",
|
||||||
"utility-types": "3.10.0"
|
"utility-types": "3.10.0",
|
||||||
|
"zustand": "3.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.14.0",
|
"@babel/core": "7.14.0",
|
||||||
@@ -110,14 +109,17 @@
|
|||||||
"@types/crossroads": "0.0.30",
|
"@types/crossroads": "0.0.30",
|
||||||
"@types/d3": "5.9.2",
|
"@types/d3": "5.9.2",
|
||||||
"@types/enzyme": "3.10.8",
|
"@types/enzyme": "3.10.8",
|
||||||
|
"@types/dom-to-image": "2.6.2",
|
||||||
"@types/hasher": "0.0.31",
|
"@types/hasher": "0.0.31",
|
||||||
"@types/jest": "26.0.23",
|
"@types/jest": "26.0.23",
|
||||||
"@types/memoize-one": "4.1.1",
|
"@types/memoize-one": "4.1.1",
|
||||||
"@types/node": "12.11.1",
|
"@types/node": "12.11.1",
|
||||||
|
"@types/mkdirp": "1.0.1",
|
||||||
|
"@types/node-fetch": "2.5.7",
|
||||||
"@types/post-robot": "10.0.1",
|
"@types/post-robot": "10.0.1",
|
||||||
"@types/promise.prototype.finally": "2.0.3",
|
"@types/promise.prototype.finally": "2.0.3",
|
||||||
"@types/q": "1.5.1",
|
"@types/q": "1.5.1",
|
||||||
"@types/react": "17.0.5",
|
"@types/react": "17.0.3",
|
||||||
"@types/react-dom": "17.0.3",
|
"@types/react-dom": "17.0.3",
|
||||||
"@types/react-notification-system": "0.2.39",
|
"@types/react-notification-system": "0.2.39",
|
||||||
"@types/react-redux": "7.1.16",
|
"@types/react-redux": "7.1.16",
|
||||||
|
|||||||
@@ -9,11 +9,17 @@ import postRobot from "post-robot";
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ReactDOM from "react-dom";
|
import * as ReactDOM from "react-dom";
|
||||||
import "../../externals/iframeResizer.contentWindow.min.js"; // Required for iFrameResizer to work
|
import "../../externals/iframeResizer.contentWindow.min.js"; // Required for iFrameResizer to work
|
||||||
|
import { SnapshotRequest } from "../Explorer/Notebook/NotebookComponent/types";
|
||||||
import "../Explorer/Notebook/NotebookRenderer/base.css";
|
import "../Explorer/Notebook/NotebookRenderer/base.css";
|
||||||
import "../Explorer/Notebook/NotebookRenderer/default.css";
|
import "../Explorer/Notebook/NotebookRenderer/default.css";
|
||||||
|
import { NotebookUtil } from "../Explorer/Notebook/NotebookUtil";
|
||||||
import "./CellOutputViewer.less";
|
import "./CellOutputViewer.less";
|
||||||
import { TransformMedia } from "./TransformMedia";
|
import { TransformMedia } from "./TransformMedia";
|
||||||
|
|
||||||
|
export interface SnapshotResponse {
|
||||||
|
imageSrc: string;
|
||||||
|
requestId: string;
|
||||||
|
}
|
||||||
export interface CellOutputViewerProps {
|
export interface CellOutputViewerProps {
|
||||||
id: string;
|
id: string;
|
||||||
contentRef: ContentRef;
|
contentRef: ContentRef;
|
||||||
@@ -62,6 +68,36 @@ const onInit = async () => {
|
|||||||
ReactDOM.render(outputs, document.getElementById("cellOutput"));
|
ReactDOM.render(outputs, document.getElementById("cellOutput"));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
postRobot.on(
|
||||||
|
"snapshotRequest",
|
||||||
|
{
|
||||||
|
window: window.parent,
|
||||||
|
domain: window.location.origin,
|
||||||
|
},
|
||||||
|
async (event): Promise<SnapshotResponse> => {
|
||||||
|
const topNode = document.getElementById("cellOutput");
|
||||||
|
if (!topNode) {
|
||||||
|
const errorMsg = "No top node to snapshot";
|
||||||
|
return Promise.reject(new Error(errorMsg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Typescript definition for event is wrong. So read props by casting to <any>
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const snapshotRequest = (event as any).data as SnapshotRequest;
|
||||||
|
const result = await NotebookUtil.takeScreenshotDomToImage(
|
||||||
|
topNode,
|
||||||
|
snapshotRequest.aspectRatio,
|
||||||
|
undefined,
|
||||||
|
snapshotRequest.downloadFilename
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
imageSrc: result.imageSrc,
|
||||||
|
requestId: snapshotRequest.requestId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Entry point
|
// Entry point
|
||||||
|
|||||||
16
src/Common/Tooltip/InfoTooltip.tsx
Normal file
16
src/Common/Tooltip/InfoTooltip.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Icon, TooltipHost } from "@fluentui/react";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
export interface TooltipProps {
|
||||||
|
children: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InfoTooltip: React.FunctionComponent = ({ children }: TooltipProps) => {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<TooltipHost content={children}>
|
||||||
|
<Icon iconName="Info" ariaLabel="Info" className="panelInfoIcon" />
|
||||||
|
</TooltipHost>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { ITooltipHostStyles, TooltipHost } from "@fluentui/react";
|
|
||||||
import { useId } from "@fluentui/react-hooks";
|
|
||||||
import { ReactComponent as InfoBubble } from "images/info-bubble.svg";
|
|
||||||
import React, { FunctionComponent } from "react";
|
|
||||||
|
|
||||||
const calloutProps = { gapSpace: 0 };
|
|
||||||
const hostStyles: Partial<ITooltipHostStyles> = { root: { display: "inline-block" } };
|
|
||||||
|
|
||||||
export interface TooltipProps {
|
|
||||||
children: string;
|
|
||||||
}
|
|
||||||
export const Tooltip: FunctionComponent = ({ children }: TooltipProps) => {
|
|
||||||
const tooltipId = useId("tooltip");
|
|
||||||
|
|
||||||
return children ? (
|
|
||||||
<span>
|
|
||||||
<TooltipHost content={children} id={tooltipId} calloutProps={calloutProps} styles={hostStyles}>
|
|
||||||
<InfoBubble />
|
|
||||||
</TooltipHost>
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -2,7 +2,7 @@ import { Image, Stack, TextField } from "@fluentui/react";
|
|||||||
import FolderIcon from "images/folder_16x16.svg";
|
import FolderIcon from "images/folder_16x16.svg";
|
||||||
import React, { ChangeEvent, FunctionComponent, KeyboardEvent, useRef, useState } from "react";
|
import React, { ChangeEvent, FunctionComponent, KeyboardEvent, useRef, useState } from "react";
|
||||||
import * as Constants from "../Constants";
|
import * as Constants from "../Constants";
|
||||||
import { Tooltip } from "../Tooltip/Tooltip";
|
import { InfoTooltip } from "../Tooltip/InfoTooltip";
|
||||||
|
|
||||||
interface UploadProps {
|
interface UploadProps {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -51,7 +51,7 @@ export const Upload: FunctionComponent<UploadProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<span className="renewUploadItemsHeader">{label}</span>
|
<span className="renewUploadItemsHeader">{label}</span>
|
||||||
<Tooltip>{tooltip}</Tooltip>
|
{tooltip && <InfoTooltip>{tooltip}</InfoTooltip>}
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<TextField styles={{ fieldGroup: { width: 300 } }} readOnly value={selectedFilesTitle.toString()} />
|
<TextField styles={{ fieldGroup: { width: 300 } }} readOnly value={selectedFilesTitle.toString()} />
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -20,10 +20,6 @@ describe("Component Registerer", () => {
|
|||||||
expect(ko.components.isRegistered("json-editor")).toBe(true);
|
expect(ko.components.isRegistered("json-editor")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should registeradd-collection-pane component", () => {
|
|
||||||
expect(ko.components.isRegistered("add-collection-pane")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should register graph-styling-pane component", () => {
|
it("should register graph-styling-pane component", () => {
|
||||||
expect(ko.components.isRegistered("graph-styling-pane")).toBe(true);
|
expect(ko.components.isRegistered("graph-styling-pane")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ 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("add-database-pane", new PaneComponents.AddDatabasePaneComponent());
|
||||||
ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent());
|
|
||||||
ko.components.register("graph-styling-pane", new PaneComponents.GraphStylingPaneComponent());
|
ko.components.register("graph-styling-pane", new PaneComponents.GraphStylingPaneComponent());
|
||||||
ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent());
|
ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent());
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
import {
|
import {
|
||||||
BaseButton,
|
BaseButton,
|
||||||
Button,
|
Button,
|
||||||
FontWeights,
|
DocumentCard,
|
||||||
|
DocumentCardActivity,
|
||||||
|
DocumentCardDetails,
|
||||||
|
DocumentCardPreview,
|
||||||
|
DocumentCardTitle,
|
||||||
Icon,
|
Icon,
|
||||||
IconButton,
|
IconButton,
|
||||||
Image,
|
IDocumentCardPreviewProps,
|
||||||
|
IDocumentCardStyles,
|
||||||
ImageFit,
|
ImageFit,
|
||||||
Link,
|
Link,
|
||||||
Persona,
|
|
||||||
Separator,
|
Separator,
|
||||||
Spinner,
|
Spinner,
|
||||||
SpinnerSize,
|
SpinnerSize,
|
||||||
Text,
|
Text,
|
||||||
TooltipHost,
|
TooltipHost,
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
import { Card } from "@uifabric/react-cards";
|
|
||||||
import CosmosDBLogo from "images/CosmosDB-logo.svg";
|
import CosmosDBLogo from "images/CosmosDB-logo.svg";
|
||||||
import React, { FunctionComponent, useState } from "react";
|
import React, { FunctionComponent, useState } from "react";
|
||||||
import { IGalleryItem } from "../../../../Juno/JunoClient";
|
import { IGalleryItem } from "../../../../Juno/JunoClient";
|
||||||
@@ -48,7 +51,6 @@ export const GalleryCardComponent: FunctionComponent<GalleryCardComponentProps>
|
|||||||
const CARD_WIDTH = 256;
|
const CARD_WIDTH = 256;
|
||||||
const cardImageHeight = 144;
|
const cardImageHeight = 144;
|
||||||
const cardDescriptionMaxChars = 80;
|
const cardDescriptionMaxChars = 80;
|
||||||
const cardItemGapBig = 10;
|
|
||||||
const cardItemGapSmall = 8;
|
const cardItemGapSmall = 8;
|
||||||
const cardDeleteSpinnerHeight = 360;
|
const cardDeleteSpinnerHeight = 360;
|
||||||
const smallTextLineHeight = 18;
|
const smallTextLineHeight = 18;
|
||||||
@@ -64,9 +66,9 @@ export const GalleryCardComponent: FunctionComponent<GalleryCardComponentProps>
|
|||||||
const dateString = new Date(data.created).toLocaleString("default", options);
|
const dateString = new Date(data.created).toLocaleString("default", options);
|
||||||
const cardTitle = FileSystemUtil.stripExtension(data.name, "ipynb");
|
const cardTitle = FileSystemUtil.stripExtension(data.name, "ipynb");
|
||||||
|
|
||||||
const renderTruncatedDescription = (): string => {
|
const renderTruncated = (text: string, totalLength: number): string => {
|
||||||
let truncatedDescription = data.description.substr(0, cardDescriptionMaxChars);
|
let truncatedDescription = text.substr(0, totalLength);
|
||||||
if (data.description.length > cardDescriptionMaxChars) {
|
if (text.length > totalLength) {
|
||||||
truncatedDescription = `${truncatedDescription} ...`;
|
truncatedDescription = `${truncatedDescription} ...`;
|
||||||
}
|
}
|
||||||
return truncatedDescription;
|
return truncatedDescription;
|
||||||
@@ -120,42 +122,35 @@ export const GalleryCardComponent: FunctionComponent<GalleryCardComponentProps>
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
activate();
|
activate();
|
||||||
};
|
};
|
||||||
|
const DocumentCardActivityPeople = [{ name: data.author, profileImageSrc: data.isSample && CosmosDBLogo }];
|
||||||
|
const previewProps: IDocumentCardPreviewProps = {
|
||||||
|
previewImages: [
|
||||||
|
{
|
||||||
|
previewImageSrc: data.thumbnailUrl,
|
||||||
|
imageFit: ImageFit.cover,
|
||||||
|
width: CARD_WIDTH,
|
||||||
|
height: cardImageHeight,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const cardStyles: IDocumentCardStyles = {
|
||||||
|
root: { display: "inline-block", marginRight: 20, width: CARD_WIDTH },
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<Card
|
<DocumentCard aria-label={cardTitle} styles={cardStyles} onClick={onClick}>
|
||||||
style={{ background: "white" }}
|
|
||||||
aria-label={cardTitle}
|
|
||||||
data-is-focusable="true"
|
|
||||||
tokens={{ width: CARD_WIDTH, childrenGap: 0 }}
|
|
||||||
onClick={(event) => handlerOnClick(event, onClick)}
|
|
||||||
>
|
|
||||||
{isDeletingPublishedNotebook && (
|
{isDeletingPublishedNotebook && (
|
||||||
<Card.Item tokens={{ padding: cardItemGapBig }}>
|
<Spinner
|
||||||
<Spinner
|
size={SpinnerSize.large}
|
||||||
size={SpinnerSize.large}
|
label={`Deleting '${cardTitle}'`}
|
||||||
label={`Deleting '${cardTitle}'`}
|
styles={{ root: { height: cardDeleteSpinnerHeight } }}
|
||||||
styles={{ root: { height: cardDeleteSpinnerHeight } }}
|
/>
|
||||||
/>
|
|
||||||
</Card.Item>
|
|
||||||
)}
|
)}
|
||||||
{!isDeletingPublishedNotebook && (
|
{!isDeletingPublishedNotebook && (
|
||||||
<>
|
<>
|
||||||
<Card.Item tokens={{ padding: cardItemGapBig }}>
|
<DocumentCardActivity activity={dateString} people={DocumentCardActivityPeople} />
|
||||||
<Persona imageUrl={data.isSample && CosmosDBLogo} text={data.author} secondaryText={dateString} />
|
<DocumentCardPreview {...previewProps} />
|
||||||
</Card.Item>
|
<DocumentCardDetails>
|
||||||
|
<Text variant="small" nowrap styles={{ root: { height: smallTextLineHeight, padding: "2px 16px" } }}>
|
||||||
<Card.Item>
|
|
||||||
<Image
|
|
||||||
src={data.thumbnailUrl}
|
|
||||||
width={CARD_WIDTH}
|
|
||||||
height={cardImageHeight}
|
|
||||||
imageFit={ImageFit.cover}
|
|
||||||
alt={`${cardTitle} cover image`}
|
|
||||||
/>
|
|
||||||
</Card.Item>
|
|
||||||
|
|
||||||
<Card.Section styles={{ root: { padding: cardItemGapBig } }}>
|
|
||||||
<Text variant="small" nowrap styles={{ root: { height: smallTextLineHeight } }}>
|
|
||||||
{data.tags ? (
|
{data.tags ? (
|
||||||
data.tags.map((tag, index, array) => (
|
data.tags.map((tag, index, array) => (
|
||||||
<span key={tag}>
|
<span key={tag}>
|
||||||
@@ -167,43 +162,22 @@ export const GalleryCardComponent: FunctionComponent<GalleryCardComponentProps>
|
|||||||
<br />
|
<br />
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
|
<DocumentCardTitle title={renderTruncated(cardTitle, 20)} shouldTruncate />
|
||||||
<Text
|
<DocumentCardTitle
|
||||||
styles={{
|
title={renderTruncated(data.description, cardDescriptionMaxChars)}
|
||||||
root: {
|
showAsSecondaryTitle
|
||||||
fontWeight: FontWeights.semibold,
|
/>
|
||||||
paddingTop: cardItemGapSmall,
|
<span style={{ padding: "8px 16px" }}>
|
||||||
paddingBottom: cardItemGapSmall,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
nowrap
|
|
||||||
>
|
|
||||||
{cardTitle}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Text variant="small" styles={{ root: { height: smallTextLineHeight * 2 } }}>
|
|
||||||
{renderTruncatedDescription()}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<span>
|
|
||||||
{data.views !== undefined && generateIconText("RedEye", data.views.toString())}
|
{data.views !== undefined && generateIconText("RedEye", data.views.toString())}
|
||||||
{data.downloads !== undefined && generateIconText("Download", data.downloads.toString())}
|
{data.downloads !== undefined && generateIconText("Download", data.downloads.toString())}
|
||||||
{data.favorites !== undefined && generateIconText("Heart", data.favorites.toString())}
|
{data.favorites !== undefined && generateIconText("Heart", data.favorites.toString())}
|
||||||
</span>
|
</span>
|
||||||
</Card.Section>
|
</DocumentCardDetails>
|
||||||
|
|
||||||
{cardButtonsVisible && (
|
{cardButtonsVisible && (
|
||||||
<Card.Section
|
<DocumentCardDetails>
|
||||||
styles={{
|
|
||||||
root: {
|
|
||||||
marginLeft: cardItemGapBig,
|
|
||||||
marginRight: cardItemGapBig,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Separator styles={{ root: { padding: 0, height: 1 } }} />
|
<Separator styles={{ root: { padding: 0, height: 1 } }} />
|
||||||
|
|
||||||
<span>
|
<span style={{ padding: "0px 16px" }}>
|
||||||
{isFavorite !== undefined &&
|
{isFavorite !== undefined &&
|
||||||
generateIconButtonWithTooltip(
|
generateIconButtonWithTooltip(
|
||||||
isFavorite ? "HeartFill" : "Heart",
|
isFavorite ? "HeartFill" : "Heart",
|
||||||
@@ -222,10 +196,10 @@ export const GalleryCardComponent: FunctionComponent<GalleryCardComponentProps>
|
|||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</Card.Section>
|
</DocumentCardDetails>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</DocumentCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,59 +1,49 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`GalleryCardComponent renders 1`] = `
|
exports[`GalleryCardComponent renders 1`] = `
|
||||||
<Card
|
<StyledDocumentCardBase
|
||||||
aria-label="name"
|
aria-label="name"
|
||||||
data-is-focusable="true"
|
styles={
|
||||||
onClick={[Function]}
|
|
||||||
style={
|
|
||||||
Object {
|
Object {
|
||||||
"background": "white",
|
"root": Object {
|
||||||
}
|
"display": "inline-block",
|
||||||
}
|
"marginRight": 20,
|
||||||
tokens={
|
"width": 256,
|
||||||
Object {
|
},
|
||||||
"childrenGap": 0,
|
|
||||||
"width": 256,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<CardItem
|
<StyledDocumentCardActivityBase
|
||||||
tokens={
|
activity="Invalid Date"
|
||||||
Object {
|
people={
|
||||||
"padding": 10,
|
Array [
|
||||||
}
|
Object {
|
||||||
}
|
"name": "author",
|
||||||
>
|
"profileImageSrc": false,
|
||||||
<StyledPersonaBase
|
|
||||||
imageUrl={false}
|
|
||||||
secondaryText="Invalid Date"
|
|
||||||
text="author"
|
|
||||||
/>
|
|
||||||
</CardItem>
|
|
||||||
<CardItem>
|
|
||||||
<Image
|
|
||||||
alt="name cover image"
|
|
||||||
height={144}
|
|
||||||
imageFit={2}
|
|
||||||
src="thumbnailUrl"
|
|
||||||
width={256}
|
|
||||||
/>
|
|
||||||
</CardItem>
|
|
||||||
<CardSection
|
|
||||||
styles={
|
|
||||||
Object {
|
|
||||||
"root": Object {
|
|
||||||
"padding": 10,
|
|
||||||
},
|
},
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
>
|
/>
|
||||||
|
<StyledDocumentCardPreviewBase
|
||||||
|
previewImages={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"height": 144,
|
||||||
|
"imageFit": 2,
|
||||||
|
"previewImageSrc": "thumbnailUrl",
|
||||||
|
"width": 256,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<StyledDocumentCardDetailsBase>
|
||||||
<Text
|
<Text
|
||||||
nowrap={true}
|
nowrap={true}
|
||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"height": 18,
|
"height": 18,
|
||||||
|
"padding": "2px 16px",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,33 +59,21 @@ exports[`GalleryCardComponent renders 1`] = `
|
|||||||
</StyledLinkBase>
|
</StyledLinkBase>
|
||||||
</span>
|
</span>
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<StyledDocumentCardTitleBase
|
||||||
nowrap={true}
|
shouldTruncate={true}
|
||||||
styles={
|
title="name"
|
||||||
|
/>
|
||||||
|
<StyledDocumentCardTitleBase
|
||||||
|
showAsSecondaryTitle={true}
|
||||||
|
title="description"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
style={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"padding": "8px 16px",
|
||||||
"fontWeight": 600,
|
|
||||||
"paddingBottom": 8,
|
|
||||||
"paddingTop": 8,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
name
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
styles={
|
|
||||||
Object {
|
|
||||||
"root": Object {
|
|
||||||
"height": 36,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
variant="small"
|
|
||||||
>
|
|
||||||
description
|
|
||||||
</Text>
|
|
||||||
<span>
|
|
||||||
<Text
|
<Text
|
||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
@@ -169,17 +147,8 @@ exports[`GalleryCardComponent renders 1`] = `
|
|||||||
0
|
0
|
||||||
</Text>
|
</Text>
|
||||||
</span>
|
</span>
|
||||||
</CardSection>
|
</StyledDocumentCardDetailsBase>
|
||||||
<CardSection
|
<StyledDocumentCardDetailsBase>
|
||||||
styles={
|
|
||||||
Object {
|
|
||||||
"root": Object {
|
|
||||||
"marginLeft": 10,
|
|
||||||
"marginRight": 10,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Separator
|
<Separator
|
||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
@@ -190,7 +159,13 @@ exports[`GalleryCardComponent renders 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<span>
|
<span
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": "0px 16px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
<StyledTooltipHostBase
|
<StyledTooltipHostBase
|
||||||
calloutProps={
|
calloutProps={
|
||||||
Object {
|
Object {
|
||||||
@@ -276,6 +251,6 @@ exports[`GalleryCardComponent renders 1`] = `
|
|||||||
/>
|
/>
|
||||||
</StyledTooltipHostBase>
|
</StyledTooltipHostBase>
|
||||||
</span>
|
</span>
|
||||||
</CardSection>
|
</StyledDocumentCardDetailsBase>
|
||||||
</Card>
|
</StyledDocumentCardBase>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import { CodeOfConductComponent } from "./CodeOfConductComponent";
|
|||||||
import "./GalleryViewerComponent.less";
|
import "./GalleryViewerComponent.less";
|
||||||
import { InfoComponent } from "./InfoComponent/InfoComponent";
|
import { InfoComponent } from "./InfoComponent/InfoComponent";
|
||||||
|
|
||||||
const CARD_WIDTH = 256;
|
|
||||||
export interface GalleryViewerComponentProps {
|
export interface GalleryViewerComponentProps {
|
||||||
container?: Explorer;
|
container?: Explorer;
|
||||||
junoClient: JunoClient;
|
junoClient: JunoClient;
|
||||||
@@ -87,7 +86,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
public static readonly PublishedTitle = "My published work";
|
public static readonly PublishedTitle = "My published work";
|
||||||
|
|
||||||
private static readonly rowsPerPage = 5;
|
private static readonly rowsPerPage = 5;
|
||||||
|
private static readonly CARD_WIDTH = 256;
|
||||||
private static readonly mostViewedText = "Most viewed";
|
private static readonly mostViewedText = "Most viewed";
|
||||||
private static readonly mostDownloadedText = "Most downloaded";
|
private static readonly mostDownloadedText = "Most downloaded";
|
||||||
private static readonly mostFavoritedText = "Most favorited";
|
private static readonly mostFavoritedText = "Most favorited";
|
||||||
@@ -644,7 +643,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
|
|
||||||
private getPageSpecification = (itemIndex?: number, visibleRect?: IRectangle): IPageSpecification => {
|
private getPageSpecification = (itemIndex?: number, visibleRect?: IRectangle): IPageSpecification => {
|
||||||
if (itemIndex === 0) {
|
if (itemIndex === 0) {
|
||||||
this.columnCount = Math.floor(visibleRect.width / CARD_WIDTH) || this.columnCount;
|
this.columnCount = Math.floor(visibleRect.width / GalleryViewerComponent.CARD_WIDTH) || this.columnCount;
|
||||||
this.rowCount = GalleryViewerComponent.rowsPerPage;
|
this.rowCount = GalleryViewerComponent.rowsPerPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -672,7 +671,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ float: "left", padding: 10 }}>
|
<div style={{ float: "left", padding: 5 }}>
|
||||||
<GalleryCardComponent {...props} />
|
<GalleryCardComponent {...props} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -75,89 +75,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"upsellMessageAriaLabel": [Function],
|
"upsellMessageAriaLabel": [Function],
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
AddCollectionPane {
|
|
||||||
"_isSynapseLinkEnabled": [Function],
|
|
||||||
"autoPilotThroughput": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
|
||||||
"canConfigureThroughput": [Function],
|
|
||||||
"canExceedMaximumValue": [Function],
|
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"collectionId": [Function],
|
|
||||||
"collectionIdTitle": [Function],
|
|
||||||
"collectionWithThroughputInShared": [Function],
|
|
||||||
"collectionWithThroughputInSharedTitle": [Function],
|
|
||||||
"container": [Circular],
|
|
||||||
"costsVisible": [Function],
|
|
||||||
"databaseCreateNew": [Function],
|
|
||||||
"databaseCreateNewShared": [Function],
|
|
||||||
"databaseHasSharedOffer": [Function],
|
|
||||||
"databaseId": [Function],
|
|
||||||
"databaseIds": [Function],
|
|
||||||
"dedicatedRequestUnitsUsageCost": [Function],
|
|
||||||
"displayCollectionThroughput": [Function],
|
|
||||||
"firstFieldHasFocus": [Function],
|
|
||||||
"formErrors": [Function],
|
|
||||||
"formErrorsDetails": [Function],
|
|
||||||
"formWarnings": [Function],
|
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "addcollectionpane",
|
|
||||||
"isAnalyticalStorageOn": [Function],
|
|
||||||
"isAutoPilotSelected": [Function],
|
|
||||||
"isEnableMongoCapabilityEnabled": [Function],
|
|
||||||
"isExecuting": [Function],
|
|
||||||
"isFixedStorageSelected": [Function],
|
|
||||||
"isFreeTierAccount": [Function],
|
|
||||||
"isNonTableApi": [Function],
|
|
||||||
"isPreferredApiTable": [Function],
|
|
||||||
"isSharedAutoPilotSelected": [Function],
|
|
||||||
"isSynapseLinkSupported": [Function],
|
|
||||||
"isSynapseLinkUpdating": [Function],
|
|
||||||
"isTemplateReady": [Function],
|
|
||||||
"isTryCosmosDBSubscription": [Function],
|
|
||||||
"isUnlimitedStorageSelected": [Function],
|
|
||||||
"largePartitionKey": [Function],
|
|
||||||
"lowerCasePartitionKeyName": [Function],
|
|
||||||
"maxCollectionsReached": [Function],
|
|
||||||
"maxCollectionsReachedMessage": [Function],
|
|
||||||
"maxThroughputRU": [Function],
|
|
||||||
"minThroughputRU": [Function],
|
|
||||||
"onMoreDetailsKeyPress": [Function],
|
|
||||||
"partitionKey": [Function],
|
|
||||||
"partitionKeyName": [Function],
|
|
||||||
"partitionKeyPattern": [Function],
|
|
||||||
"partitionKeyPlaceholder": [Function],
|
|
||||||
"partitionKeyTitle": [Function],
|
|
||||||
"partitionKeyVisible": [Function],
|
|
||||||
"requestUnitsUsageCost": [Function],
|
|
||||||
"ruToolTipText": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
|
||||||
"shouldUseDatabaseThroughput": [Function],
|
|
||||||
"showAnalyticalStore": [Function],
|
|
||||||
"showEnableSynapseLink": [Function],
|
|
||||||
"showIndexingOptionsForSharedThroughput": [Function],
|
|
||||||
"showUpsellMessage": [Function],
|
|
||||||
"storage": [Function],
|
|
||||||
"throughputDatabase": [Function],
|
|
||||||
"throughputMultiPartition": [Function],
|
|
||||||
"throughputRangeText": [Function],
|
|
||||||
"throughputSinglePartition": [Function],
|
|
||||||
"throughputSpendAck": [Function],
|
|
||||||
"throughputSpendAckText": [Function],
|
|
||||||
"throughputSpendAckVisible": [Function],
|
|
||||||
"title": [Function],
|
|
||||||
"ttl90DaysEnabled": [Function],
|
|
||||||
"uniqueKeys": [Function],
|
|
||||||
"uniqueKeysPlaceholder": [Function],
|
|
||||||
"uniqueKeysVisible": [Function],
|
|
||||||
"upsellAnchorText": [Function],
|
|
||||||
"upsellAnchorUrl": [Function],
|
|
||||||
"upsellMessage": [Function],
|
|
||||||
"upsellMessageAriaLabel": [Function],
|
|
||||||
"useIndexingForSharedThroughput": [Function],
|
|
||||||
"visible": [Function],
|
|
||||||
},
|
|
||||||
GraphStylingPane {
|
GraphStylingPane {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
@@ -226,89 +143,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
],
|
],
|
||||||
"_refreshSparkEnabledStateForAccount": [Function],
|
"_refreshSparkEnabledStateForAccount": [Function],
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"addCollectionPane": AddCollectionPane {
|
|
||||||
"_isSynapseLinkEnabled": [Function],
|
|
||||||
"autoPilotThroughput": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
|
||||||
"canConfigureThroughput": [Function],
|
|
||||||
"canExceedMaximumValue": [Function],
|
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"collectionId": [Function],
|
|
||||||
"collectionIdTitle": [Function],
|
|
||||||
"collectionWithThroughputInShared": [Function],
|
|
||||||
"collectionWithThroughputInSharedTitle": [Function],
|
|
||||||
"container": [Circular],
|
|
||||||
"costsVisible": [Function],
|
|
||||||
"databaseCreateNew": [Function],
|
|
||||||
"databaseCreateNewShared": [Function],
|
|
||||||
"databaseHasSharedOffer": [Function],
|
|
||||||
"databaseId": [Function],
|
|
||||||
"databaseIds": [Function],
|
|
||||||
"dedicatedRequestUnitsUsageCost": [Function],
|
|
||||||
"displayCollectionThroughput": [Function],
|
|
||||||
"firstFieldHasFocus": [Function],
|
|
||||||
"formErrors": [Function],
|
|
||||||
"formErrorsDetails": [Function],
|
|
||||||
"formWarnings": [Function],
|
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "addcollectionpane",
|
|
||||||
"isAnalyticalStorageOn": [Function],
|
|
||||||
"isAutoPilotSelected": [Function],
|
|
||||||
"isEnableMongoCapabilityEnabled": [Function],
|
|
||||||
"isExecuting": [Function],
|
|
||||||
"isFixedStorageSelected": [Function],
|
|
||||||
"isFreeTierAccount": [Function],
|
|
||||||
"isNonTableApi": [Function],
|
|
||||||
"isPreferredApiTable": [Function],
|
|
||||||
"isSharedAutoPilotSelected": [Function],
|
|
||||||
"isSynapseLinkSupported": [Function],
|
|
||||||
"isSynapseLinkUpdating": [Function],
|
|
||||||
"isTemplateReady": [Function],
|
|
||||||
"isTryCosmosDBSubscription": [Function],
|
|
||||||
"isUnlimitedStorageSelected": [Function],
|
|
||||||
"largePartitionKey": [Function],
|
|
||||||
"lowerCasePartitionKeyName": [Function],
|
|
||||||
"maxCollectionsReached": [Function],
|
|
||||||
"maxCollectionsReachedMessage": [Function],
|
|
||||||
"maxThroughputRU": [Function],
|
|
||||||
"minThroughputRU": [Function],
|
|
||||||
"onMoreDetailsKeyPress": [Function],
|
|
||||||
"partitionKey": [Function],
|
|
||||||
"partitionKeyName": [Function],
|
|
||||||
"partitionKeyPattern": [Function],
|
|
||||||
"partitionKeyPlaceholder": [Function],
|
|
||||||
"partitionKeyTitle": [Function],
|
|
||||||
"partitionKeyVisible": [Function],
|
|
||||||
"requestUnitsUsageCost": [Function],
|
|
||||||
"ruToolTipText": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
|
||||||
"shouldUseDatabaseThroughput": [Function],
|
|
||||||
"showAnalyticalStore": [Function],
|
|
||||||
"showEnableSynapseLink": [Function],
|
|
||||||
"showIndexingOptionsForSharedThroughput": [Function],
|
|
||||||
"showUpsellMessage": [Function],
|
|
||||||
"storage": [Function],
|
|
||||||
"throughputDatabase": [Function],
|
|
||||||
"throughputMultiPartition": [Function],
|
|
||||||
"throughputRangeText": [Function],
|
|
||||||
"throughputSinglePartition": [Function],
|
|
||||||
"throughputSpendAck": [Function],
|
|
||||||
"throughputSpendAckText": [Function],
|
|
||||||
"throughputSpendAckVisible": [Function],
|
|
||||||
"title": [Function],
|
|
||||||
"ttl90DaysEnabled": [Function],
|
|
||||||
"uniqueKeys": [Function],
|
|
||||||
"uniqueKeysPlaceholder": [Function],
|
|
||||||
"uniqueKeysVisible": [Function],
|
|
||||||
"upsellAnchorText": [Function],
|
|
||||||
"upsellAnchorUrl": [Function],
|
|
||||||
"upsellMessage": [Function],
|
|
||||||
"upsellMessageAriaLabel": [Function],
|
|
||||||
"useIndexingForSharedThroughput": [Function],
|
|
||||||
"visible": [Function],
|
|
||||||
},
|
|
||||||
"addCollectionText": [Function],
|
"addCollectionText": [Function],
|
||||||
"addDatabasePane": AddDatabasePane {
|
"addDatabasePane": AddDatabasePane {
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
@@ -1353,89 +1187,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"upsellMessageAriaLabel": [Function],
|
"upsellMessageAriaLabel": [Function],
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
AddCollectionPane {
|
|
||||||
"_isSynapseLinkEnabled": [Function],
|
|
||||||
"autoPilotThroughput": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
|
||||||
"canConfigureThroughput": [Function],
|
|
||||||
"canExceedMaximumValue": [Function],
|
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"collectionId": [Function],
|
|
||||||
"collectionIdTitle": [Function],
|
|
||||||
"collectionWithThroughputInShared": [Function],
|
|
||||||
"collectionWithThroughputInSharedTitle": [Function],
|
|
||||||
"container": [Circular],
|
|
||||||
"costsVisible": [Function],
|
|
||||||
"databaseCreateNew": [Function],
|
|
||||||
"databaseCreateNewShared": [Function],
|
|
||||||
"databaseHasSharedOffer": [Function],
|
|
||||||
"databaseId": [Function],
|
|
||||||
"databaseIds": [Function],
|
|
||||||
"dedicatedRequestUnitsUsageCost": [Function],
|
|
||||||
"displayCollectionThroughput": [Function],
|
|
||||||
"firstFieldHasFocus": [Function],
|
|
||||||
"formErrors": [Function],
|
|
||||||
"formErrorsDetails": [Function],
|
|
||||||
"formWarnings": [Function],
|
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "addcollectionpane",
|
|
||||||
"isAnalyticalStorageOn": [Function],
|
|
||||||
"isAutoPilotSelected": [Function],
|
|
||||||
"isEnableMongoCapabilityEnabled": [Function],
|
|
||||||
"isExecuting": [Function],
|
|
||||||
"isFixedStorageSelected": [Function],
|
|
||||||
"isFreeTierAccount": [Function],
|
|
||||||
"isNonTableApi": [Function],
|
|
||||||
"isPreferredApiTable": [Function],
|
|
||||||
"isSharedAutoPilotSelected": [Function],
|
|
||||||
"isSynapseLinkSupported": [Function],
|
|
||||||
"isSynapseLinkUpdating": [Function],
|
|
||||||
"isTemplateReady": [Function],
|
|
||||||
"isTryCosmosDBSubscription": [Function],
|
|
||||||
"isUnlimitedStorageSelected": [Function],
|
|
||||||
"largePartitionKey": [Function],
|
|
||||||
"lowerCasePartitionKeyName": [Function],
|
|
||||||
"maxCollectionsReached": [Function],
|
|
||||||
"maxCollectionsReachedMessage": [Function],
|
|
||||||
"maxThroughputRU": [Function],
|
|
||||||
"minThroughputRU": [Function],
|
|
||||||
"onMoreDetailsKeyPress": [Function],
|
|
||||||
"partitionKey": [Function],
|
|
||||||
"partitionKeyName": [Function],
|
|
||||||
"partitionKeyPattern": [Function],
|
|
||||||
"partitionKeyPlaceholder": [Function],
|
|
||||||
"partitionKeyTitle": [Function],
|
|
||||||
"partitionKeyVisible": [Function],
|
|
||||||
"requestUnitsUsageCost": [Function],
|
|
||||||
"ruToolTipText": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
|
||||||
"shouldUseDatabaseThroughput": [Function],
|
|
||||||
"showAnalyticalStore": [Function],
|
|
||||||
"showEnableSynapseLink": [Function],
|
|
||||||
"showIndexingOptionsForSharedThroughput": [Function],
|
|
||||||
"showUpsellMessage": [Function],
|
|
||||||
"storage": [Function],
|
|
||||||
"throughputDatabase": [Function],
|
|
||||||
"throughputMultiPartition": [Function],
|
|
||||||
"throughputRangeText": [Function],
|
|
||||||
"throughputSinglePartition": [Function],
|
|
||||||
"throughputSpendAck": [Function],
|
|
||||||
"throughputSpendAckText": [Function],
|
|
||||||
"throughputSpendAckVisible": [Function],
|
|
||||||
"title": [Function],
|
|
||||||
"ttl90DaysEnabled": [Function],
|
|
||||||
"uniqueKeys": [Function],
|
|
||||||
"uniqueKeysPlaceholder": [Function],
|
|
||||||
"uniqueKeysVisible": [Function],
|
|
||||||
"upsellAnchorText": [Function],
|
|
||||||
"upsellAnchorUrl": [Function],
|
|
||||||
"upsellMessage": [Function],
|
|
||||||
"upsellMessageAriaLabel": [Function],
|
|
||||||
"useIndexingForSharedThroughput": [Function],
|
|
||||||
"visible": [Function],
|
|
||||||
},
|
|
||||||
GraphStylingPane {
|
GraphStylingPane {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
@@ -1504,89 +1255,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
],
|
],
|
||||||
"_refreshSparkEnabledStateForAccount": [Function],
|
"_refreshSparkEnabledStateForAccount": [Function],
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"addCollectionPane": AddCollectionPane {
|
|
||||||
"_isSynapseLinkEnabled": [Function],
|
|
||||||
"autoPilotThroughput": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
|
||||||
"canConfigureThroughput": [Function],
|
|
||||||
"canExceedMaximumValue": [Function],
|
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"collectionId": [Function],
|
|
||||||
"collectionIdTitle": [Function],
|
|
||||||
"collectionWithThroughputInShared": [Function],
|
|
||||||
"collectionWithThroughputInSharedTitle": [Function],
|
|
||||||
"container": [Circular],
|
|
||||||
"costsVisible": [Function],
|
|
||||||
"databaseCreateNew": [Function],
|
|
||||||
"databaseCreateNewShared": [Function],
|
|
||||||
"databaseHasSharedOffer": [Function],
|
|
||||||
"databaseId": [Function],
|
|
||||||
"databaseIds": [Function],
|
|
||||||
"dedicatedRequestUnitsUsageCost": [Function],
|
|
||||||
"displayCollectionThroughput": [Function],
|
|
||||||
"firstFieldHasFocus": [Function],
|
|
||||||
"formErrors": [Function],
|
|
||||||
"formErrorsDetails": [Function],
|
|
||||||
"formWarnings": [Function],
|
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "addcollectionpane",
|
|
||||||
"isAnalyticalStorageOn": [Function],
|
|
||||||
"isAutoPilotSelected": [Function],
|
|
||||||
"isEnableMongoCapabilityEnabled": [Function],
|
|
||||||
"isExecuting": [Function],
|
|
||||||
"isFixedStorageSelected": [Function],
|
|
||||||
"isFreeTierAccount": [Function],
|
|
||||||
"isNonTableApi": [Function],
|
|
||||||
"isPreferredApiTable": [Function],
|
|
||||||
"isSharedAutoPilotSelected": [Function],
|
|
||||||
"isSynapseLinkSupported": [Function],
|
|
||||||
"isSynapseLinkUpdating": [Function],
|
|
||||||
"isTemplateReady": [Function],
|
|
||||||
"isTryCosmosDBSubscription": [Function],
|
|
||||||
"isUnlimitedStorageSelected": [Function],
|
|
||||||
"largePartitionKey": [Function],
|
|
||||||
"lowerCasePartitionKeyName": [Function],
|
|
||||||
"maxCollectionsReached": [Function],
|
|
||||||
"maxCollectionsReachedMessage": [Function],
|
|
||||||
"maxThroughputRU": [Function],
|
|
||||||
"minThroughputRU": [Function],
|
|
||||||
"onMoreDetailsKeyPress": [Function],
|
|
||||||
"partitionKey": [Function],
|
|
||||||
"partitionKeyName": [Function],
|
|
||||||
"partitionKeyPattern": [Function],
|
|
||||||
"partitionKeyPlaceholder": [Function],
|
|
||||||
"partitionKeyTitle": [Function],
|
|
||||||
"partitionKeyVisible": [Function],
|
|
||||||
"requestUnitsUsageCost": [Function],
|
|
||||||
"ruToolTipText": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
|
||||||
"shouldUseDatabaseThroughput": [Function],
|
|
||||||
"showAnalyticalStore": [Function],
|
|
||||||
"showEnableSynapseLink": [Function],
|
|
||||||
"showIndexingOptionsForSharedThroughput": [Function],
|
|
||||||
"showUpsellMessage": [Function],
|
|
||||||
"storage": [Function],
|
|
||||||
"throughputDatabase": [Function],
|
|
||||||
"throughputMultiPartition": [Function],
|
|
||||||
"throughputRangeText": [Function],
|
|
||||||
"throughputSinglePartition": [Function],
|
|
||||||
"throughputSpendAck": [Function],
|
|
||||||
"throughputSpendAckText": [Function],
|
|
||||||
"throughputSpendAckVisible": [Function],
|
|
||||||
"title": [Function],
|
|
||||||
"ttl90DaysEnabled": [Function],
|
|
||||||
"uniqueKeys": [Function],
|
|
||||||
"uniqueKeysPlaceholder": [Function],
|
|
||||||
"uniqueKeysVisible": [Function],
|
|
||||||
"upsellAnchorText": [Function],
|
|
||||||
"upsellAnchorUrl": [Function],
|
|
||||||
"upsellMessage": [Function],
|
|
||||||
"upsellMessageAriaLabel": [Function],
|
|
||||||
"useIndexingForSharedThroughput": [Function],
|
|
||||||
"visible": [Function],
|
|
||||||
},
|
|
||||||
"addCollectionText": [Function],
|
"addCollectionText": [Function],
|
||||||
"addDatabasePane": AddDatabasePane {
|
"addDatabasePane": AddDatabasePane {
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
@@ -2644,89 +2312,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"upsellMessageAriaLabel": [Function],
|
"upsellMessageAriaLabel": [Function],
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
AddCollectionPane {
|
|
||||||
"_isSynapseLinkEnabled": [Function],
|
|
||||||
"autoPilotThroughput": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
|
||||||
"canConfigureThroughput": [Function],
|
|
||||||
"canExceedMaximumValue": [Function],
|
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"collectionId": [Function],
|
|
||||||
"collectionIdTitle": [Function],
|
|
||||||
"collectionWithThroughputInShared": [Function],
|
|
||||||
"collectionWithThroughputInSharedTitle": [Function],
|
|
||||||
"container": [Circular],
|
|
||||||
"costsVisible": [Function],
|
|
||||||
"databaseCreateNew": [Function],
|
|
||||||
"databaseCreateNewShared": [Function],
|
|
||||||
"databaseHasSharedOffer": [Function],
|
|
||||||
"databaseId": [Function],
|
|
||||||
"databaseIds": [Function],
|
|
||||||
"dedicatedRequestUnitsUsageCost": [Function],
|
|
||||||
"displayCollectionThroughput": [Function],
|
|
||||||
"firstFieldHasFocus": [Function],
|
|
||||||
"formErrors": [Function],
|
|
||||||
"formErrorsDetails": [Function],
|
|
||||||
"formWarnings": [Function],
|
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "addcollectionpane",
|
|
||||||
"isAnalyticalStorageOn": [Function],
|
|
||||||
"isAutoPilotSelected": [Function],
|
|
||||||
"isEnableMongoCapabilityEnabled": [Function],
|
|
||||||
"isExecuting": [Function],
|
|
||||||
"isFixedStorageSelected": [Function],
|
|
||||||
"isFreeTierAccount": [Function],
|
|
||||||
"isNonTableApi": [Function],
|
|
||||||
"isPreferredApiTable": [Function],
|
|
||||||
"isSharedAutoPilotSelected": [Function],
|
|
||||||
"isSynapseLinkSupported": [Function],
|
|
||||||
"isSynapseLinkUpdating": [Function],
|
|
||||||
"isTemplateReady": [Function],
|
|
||||||
"isTryCosmosDBSubscription": [Function],
|
|
||||||
"isUnlimitedStorageSelected": [Function],
|
|
||||||
"largePartitionKey": [Function],
|
|
||||||
"lowerCasePartitionKeyName": [Function],
|
|
||||||
"maxCollectionsReached": [Function],
|
|
||||||
"maxCollectionsReachedMessage": [Function],
|
|
||||||
"maxThroughputRU": [Function],
|
|
||||||
"minThroughputRU": [Function],
|
|
||||||
"onMoreDetailsKeyPress": [Function],
|
|
||||||
"partitionKey": [Function],
|
|
||||||
"partitionKeyName": [Function],
|
|
||||||
"partitionKeyPattern": [Function],
|
|
||||||
"partitionKeyPlaceholder": [Function],
|
|
||||||
"partitionKeyTitle": [Function],
|
|
||||||
"partitionKeyVisible": [Function],
|
|
||||||
"requestUnitsUsageCost": [Function],
|
|
||||||
"ruToolTipText": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
|
||||||
"shouldUseDatabaseThroughput": [Function],
|
|
||||||
"showAnalyticalStore": [Function],
|
|
||||||
"showEnableSynapseLink": [Function],
|
|
||||||
"showIndexingOptionsForSharedThroughput": [Function],
|
|
||||||
"showUpsellMessage": [Function],
|
|
||||||
"storage": [Function],
|
|
||||||
"throughputDatabase": [Function],
|
|
||||||
"throughputMultiPartition": [Function],
|
|
||||||
"throughputRangeText": [Function],
|
|
||||||
"throughputSinglePartition": [Function],
|
|
||||||
"throughputSpendAck": [Function],
|
|
||||||
"throughputSpendAckText": [Function],
|
|
||||||
"throughputSpendAckVisible": [Function],
|
|
||||||
"title": [Function],
|
|
||||||
"ttl90DaysEnabled": [Function],
|
|
||||||
"uniqueKeys": [Function],
|
|
||||||
"uniqueKeysPlaceholder": [Function],
|
|
||||||
"uniqueKeysVisible": [Function],
|
|
||||||
"upsellAnchorText": [Function],
|
|
||||||
"upsellAnchorUrl": [Function],
|
|
||||||
"upsellMessage": [Function],
|
|
||||||
"upsellMessageAriaLabel": [Function],
|
|
||||||
"useIndexingForSharedThroughput": [Function],
|
|
||||||
"visible": [Function],
|
|
||||||
},
|
|
||||||
GraphStylingPane {
|
GraphStylingPane {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
@@ -2795,89 +2380,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
],
|
],
|
||||||
"_refreshSparkEnabledStateForAccount": [Function],
|
"_refreshSparkEnabledStateForAccount": [Function],
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"addCollectionPane": AddCollectionPane {
|
|
||||||
"_isSynapseLinkEnabled": [Function],
|
|
||||||
"autoPilotThroughput": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
|
||||||
"canConfigureThroughput": [Function],
|
|
||||||
"canExceedMaximumValue": [Function],
|
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"collectionId": [Function],
|
|
||||||
"collectionIdTitle": [Function],
|
|
||||||
"collectionWithThroughputInShared": [Function],
|
|
||||||
"collectionWithThroughputInSharedTitle": [Function],
|
|
||||||
"container": [Circular],
|
|
||||||
"costsVisible": [Function],
|
|
||||||
"databaseCreateNew": [Function],
|
|
||||||
"databaseCreateNewShared": [Function],
|
|
||||||
"databaseHasSharedOffer": [Function],
|
|
||||||
"databaseId": [Function],
|
|
||||||
"databaseIds": [Function],
|
|
||||||
"dedicatedRequestUnitsUsageCost": [Function],
|
|
||||||
"displayCollectionThroughput": [Function],
|
|
||||||
"firstFieldHasFocus": [Function],
|
|
||||||
"formErrors": [Function],
|
|
||||||
"formErrorsDetails": [Function],
|
|
||||||
"formWarnings": [Function],
|
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "addcollectionpane",
|
|
||||||
"isAnalyticalStorageOn": [Function],
|
|
||||||
"isAutoPilotSelected": [Function],
|
|
||||||
"isEnableMongoCapabilityEnabled": [Function],
|
|
||||||
"isExecuting": [Function],
|
|
||||||
"isFixedStorageSelected": [Function],
|
|
||||||
"isFreeTierAccount": [Function],
|
|
||||||
"isNonTableApi": [Function],
|
|
||||||
"isPreferredApiTable": [Function],
|
|
||||||
"isSharedAutoPilotSelected": [Function],
|
|
||||||
"isSynapseLinkSupported": [Function],
|
|
||||||
"isSynapseLinkUpdating": [Function],
|
|
||||||
"isTemplateReady": [Function],
|
|
||||||
"isTryCosmosDBSubscription": [Function],
|
|
||||||
"isUnlimitedStorageSelected": [Function],
|
|
||||||
"largePartitionKey": [Function],
|
|
||||||
"lowerCasePartitionKeyName": [Function],
|
|
||||||
"maxCollectionsReached": [Function],
|
|
||||||
"maxCollectionsReachedMessage": [Function],
|
|
||||||
"maxThroughputRU": [Function],
|
|
||||||
"minThroughputRU": [Function],
|
|
||||||
"onMoreDetailsKeyPress": [Function],
|
|
||||||
"partitionKey": [Function],
|
|
||||||
"partitionKeyName": [Function],
|
|
||||||
"partitionKeyPattern": [Function],
|
|
||||||
"partitionKeyPlaceholder": [Function],
|
|
||||||
"partitionKeyTitle": [Function],
|
|
||||||
"partitionKeyVisible": [Function],
|
|
||||||
"requestUnitsUsageCost": [Function],
|
|
||||||
"ruToolTipText": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
|
||||||
"shouldUseDatabaseThroughput": [Function],
|
|
||||||
"showAnalyticalStore": [Function],
|
|
||||||
"showEnableSynapseLink": [Function],
|
|
||||||
"showIndexingOptionsForSharedThroughput": [Function],
|
|
||||||
"showUpsellMessage": [Function],
|
|
||||||
"storage": [Function],
|
|
||||||
"throughputDatabase": [Function],
|
|
||||||
"throughputMultiPartition": [Function],
|
|
||||||
"throughputRangeText": [Function],
|
|
||||||
"throughputSinglePartition": [Function],
|
|
||||||
"throughputSpendAck": [Function],
|
|
||||||
"throughputSpendAckText": [Function],
|
|
||||||
"throughputSpendAckVisible": [Function],
|
|
||||||
"title": [Function],
|
|
||||||
"ttl90DaysEnabled": [Function],
|
|
||||||
"uniqueKeys": [Function],
|
|
||||||
"uniqueKeysPlaceholder": [Function],
|
|
||||||
"uniqueKeysVisible": [Function],
|
|
||||||
"upsellAnchorText": [Function],
|
|
||||||
"upsellAnchorUrl": [Function],
|
|
||||||
"upsellMessage": [Function],
|
|
||||||
"upsellMessageAriaLabel": [Function],
|
|
||||||
"useIndexingForSharedThroughput": [Function],
|
|
||||||
"visible": [Function],
|
|
||||||
},
|
|
||||||
"addCollectionText": [Function],
|
"addCollectionText": [Function],
|
||||||
"addDatabasePane": AddDatabasePane {
|
"addDatabasePane": AddDatabasePane {
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
@@ -3922,89 +3424,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"upsellMessageAriaLabel": [Function],
|
"upsellMessageAriaLabel": [Function],
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
AddCollectionPane {
|
|
||||||
"_isSynapseLinkEnabled": [Function],
|
|
||||||
"autoPilotThroughput": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
|
||||||
"canConfigureThroughput": [Function],
|
|
||||||
"canExceedMaximumValue": [Function],
|
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"collectionId": [Function],
|
|
||||||
"collectionIdTitle": [Function],
|
|
||||||
"collectionWithThroughputInShared": [Function],
|
|
||||||
"collectionWithThroughputInSharedTitle": [Function],
|
|
||||||
"container": [Circular],
|
|
||||||
"costsVisible": [Function],
|
|
||||||
"databaseCreateNew": [Function],
|
|
||||||
"databaseCreateNewShared": [Function],
|
|
||||||
"databaseHasSharedOffer": [Function],
|
|
||||||
"databaseId": [Function],
|
|
||||||
"databaseIds": [Function],
|
|
||||||
"dedicatedRequestUnitsUsageCost": [Function],
|
|
||||||
"displayCollectionThroughput": [Function],
|
|
||||||
"firstFieldHasFocus": [Function],
|
|
||||||
"formErrors": [Function],
|
|
||||||
"formErrorsDetails": [Function],
|
|
||||||
"formWarnings": [Function],
|
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "addcollectionpane",
|
|
||||||
"isAnalyticalStorageOn": [Function],
|
|
||||||
"isAutoPilotSelected": [Function],
|
|
||||||
"isEnableMongoCapabilityEnabled": [Function],
|
|
||||||
"isExecuting": [Function],
|
|
||||||
"isFixedStorageSelected": [Function],
|
|
||||||
"isFreeTierAccount": [Function],
|
|
||||||
"isNonTableApi": [Function],
|
|
||||||
"isPreferredApiTable": [Function],
|
|
||||||
"isSharedAutoPilotSelected": [Function],
|
|
||||||
"isSynapseLinkSupported": [Function],
|
|
||||||
"isSynapseLinkUpdating": [Function],
|
|
||||||
"isTemplateReady": [Function],
|
|
||||||
"isTryCosmosDBSubscription": [Function],
|
|
||||||
"isUnlimitedStorageSelected": [Function],
|
|
||||||
"largePartitionKey": [Function],
|
|
||||||
"lowerCasePartitionKeyName": [Function],
|
|
||||||
"maxCollectionsReached": [Function],
|
|
||||||
"maxCollectionsReachedMessage": [Function],
|
|
||||||
"maxThroughputRU": [Function],
|
|
||||||
"minThroughputRU": [Function],
|
|
||||||
"onMoreDetailsKeyPress": [Function],
|
|
||||||
"partitionKey": [Function],
|
|
||||||
"partitionKeyName": [Function],
|
|
||||||
"partitionKeyPattern": [Function],
|
|
||||||
"partitionKeyPlaceholder": [Function],
|
|
||||||
"partitionKeyTitle": [Function],
|
|
||||||
"partitionKeyVisible": [Function],
|
|
||||||
"requestUnitsUsageCost": [Function],
|
|
||||||
"ruToolTipText": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
|
||||||
"shouldUseDatabaseThroughput": [Function],
|
|
||||||
"showAnalyticalStore": [Function],
|
|
||||||
"showEnableSynapseLink": [Function],
|
|
||||||
"showIndexingOptionsForSharedThroughput": [Function],
|
|
||||||
"showUpsellMessage": [Function],
|
|
||||||
"storage": [Function],
|
|
||||||
"throughputDatabase": [Function],
|
|
||||||
"throughputMultiPartition": [Function],
|
|
||||||
"throughputRangeText": [Function],
|
|
||||||
"throughputSinglePartition": [Function],
|
|
||||||
"throughputSpendAck": [Function],
|
|
||||||
"throughputSpendAckText": [Function],
|
|
||||||
"throughputSpendAckVisible": [Function],
|
|
||||||
"title": [Function],
|
|
||||||
"ttl90DaysEnabled": [Function],
|
|
||||||
"uniqueKeys": [Function],
|
|
||||||
"uniqueKeysPlaceholder": [Function],
|
|
||||||
"uniqueKeysVisible": [Function],
|
|
||||||
"upsellAnchorText": [Function],
|
|
||||||
"upsellAnchorUrl": [Function],
|
|
||||||
"upsellMessage": [Function],
|
|
||||||
"upsellMessageAriaLabel": [Function],
|
|
||||||
"useIndexingForSharedThroughput": [Function],
|
|
||||||
"visible": [Function],
|
|
||||||
},
|
|
||||||
GraphStylingPane {
|
GraphStylingPane {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
@@ -4073,89 +3492,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
],
|
],
|
||||||
"_refreshSparkEnabledStateForAccount": [Function],
|
"_refreshSparkEnabledStateForAccount": [Function],
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"addCollectionPane": AddCollectionPane {
|
|
||||||
"_isSynapseLinkEnabled": [Function],
|
|
||||||
"autoPilotThroughput": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
|
||||||
"canConfigureThroughput": [Function],
|
|
||||||
"canExceedMaximumValue": [Function],
|
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"collectionId": [Function],
|
|
||||||
"collectionIdTitle": [Function],
|
|
||||||
"collectionWithThroughputInShared": [Function],
|
|
||||||
"collectionWithThroughputInSharedTitle": [Function],
|
|
||||||
"container": [Circular],
|
|
||||||
"costsVisible": [Function],
|
|
||||||
"databaseCreateNew": [Function],
|
|
||||||
"databaseCreateNewShared": [Function],
|
|
||||||
"databaseHasSharedOffer": [Function],
|
|
||||||
"databaseId": [Function],
|
|
||||||
"databaseIds": [Function],
|
|
||||||
"dedicatedRequestUnitsUsageCost": [Function],
|
|
||||||
"displayCollectionThroughput": [Function],
|
|
||||||
"firstFieldHasFocus": [Function],
|
|
||||||
"formErrors": [Function],
|
|
||||||
"formErrorsDetails": [Function],
|
|
||||||
"formWarnings": [Function],
|
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "addcollectionpane",
|
|
||||||
"isAnalyticalStorageOn": [Function],
|
|
||||||
"isAutoPilotSelected": [Function],
|
|
||||||
"isEnableMongoCapabilityEnabled": [Function],
|
|
||||||
"isExecuting": [Function],
|
|
||||||
"isFixedStorageSelected": [Function],
|
|
||||||
"isFreeTierAccount": [Function],
|
|
||||||
"isNonTableApi": [Function],
|
|
||||||
"isPreferredApiTable": [Function],
|
|
||||||
"isSharedAutoPilotSelected": [Function],
|
|
||||||
"isSynapseLinkSupported": [Function],
|
|
||||||
"isSynapseLinkUpdating": [Function],
|
|
||||||
"isTemplateReady": [Function],
|
|
||||||
"isTryCosmosDBSubscription": [Function],
|
|
||||||
"isUnlimitedStorageSelected": [Function],
|
|
||||||
"largePartitionKey": [Function],
|
|
||||||
"lowerCasePartitionKeyName": [Function],
|
|
||||||
"maxCollectionsReached": [Function],
|
|
||||||
"maxCollectionsReachedMessage": [Function],
|
|
||||||
"maxThroughputRU": [Function],
|
|
||||||
"minThroughputRU": [Function],
|
|
||||||
"onMoreDetailsKeyPress": [Function],
|
|
||||||
"partitionKey": [Function],
|
|
||||||
"partitionKeyName": [Function],
|
|
||||||
"partitionKeyPattern": [Function],
|
|
||||||
"partitionKeyPlaceholder": [Function],
|
|
||||||
"partitionKeyTitle": [Function],
|
|
||||||
"partitionKeyVisible": [Function],
|
|
||||||
"requestUnitsUsageCost": [Function],
|
|
||||||
"ruToolTipText": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
|
||||||
"shouldUseDatabaseThroughput": [Function],
|
|
||||||
"showAnalyticalStore": [Function],
|
|
||||||
"showEnableSynapseLink": [Function],
|
|
||||||
"showIndexingOptionsForSharedThroughput": [Function],
|
|
||||||
"showUpsellMessage": [Function],
|
|
||||||
"storage": [Function],
|
|
||||||
"throughputDatabase": [Function],
|
|
||||||
"throughputMultiPartition": [Function],
|
|
||||||
"throughputRangeText": [Function],
|
|
||||||
"throughputSinglePartition": [Function],
|
|
||||||
"throughputSpendAck": [Function],
|
|
||||||
"throughputSpendAckText": [Function],
|
|
||||||
"throughputSpendAckVisible": [Function],
|
|
||||||
"title": [Function],
|
|
||||||
"ttl90DaysEnabled": [Function],
|
|
||||||
"uniqueKeys": [Function],
|
|
||||||
"uniqueKeysPlaceholder": [Function],
|
|
||||||
"uniqueKeysVisible": [Function],
|
|
||||||
"upsellAnchorText": [Function],
|
|
||||||
"upsellAnchorUrl": [Function],
|
|
||||||
"upsellMessage": [Function],
|
|
||||||
"upsellMessageAriaLabel": [Function],
|
|
||||||
"useIndexingForSharedThroughput": [Function],
|
|
||||||
"visible": [Function],
|
|
||||||
},
|
|
||||||
"addCollectionText": [Function],
|
"addCollectionText": [Function],
|
||||||
"addDatabasePane": AddDatabasePane {
|
"addDatabasePane": AddDatabasePane {
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import { CostEstimateText } from "./CostEstimateText";
|
||||||
|
const props = {
|
||||||
|
requestUnits: 5,
|
||||||
|
isAutoscale: false,
|
||||||
|
};
|
||||||
|
describe("CostEstimateText Pane", () => {
|
||||||
|
it("should render Default properly", () => {
|
||||||
|
const wrapper = shallow(<CostEstimateText {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { Text } from "@fluentui/react";
|
||||||
|
import React, { FunctionComponent } from "react";
|
||||||
|
import { InfoTooltip } from "../../../../Common/Tooltip/InfoTooltip";
|
||||||
|
import * as SharedConstants from "../../../../Shared/Constants";
|
||||||
|
import { userContext } from "../../../../UserContext";
|
||||||
|
import {
|
||||||
|
calculateEstimateNumber,
|
||||||
|
computeRUUsagePriceHourly,
|
||||||
|
getAutoscalePricePerRu,
|
||||||
|
getCurrencySign,
|
||||||
|
getMultimasterMultiplier,
|
||||||
|
getPriceCurrency,
|
||||||
|
getPricePerRu,
|
||||||
|
} from "../../../../Utils/PricingUtils";
|
||||||
|
|
||||||
|
interface CostEstimateTextProps {
|
||||||
|
requestUnits: number;
|
||||||
|
isAutoscale: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CostEstimateText: FunctionComponent<CostEstimateTextProps> = ({
|
||||||
|
requestUnits,
|
||||||
|
isAutoscale,
|
||||||
|
}: CostEstimateTextProps) => {
|
||||||
|
const { databaseAccount } = userContext;
|
||||||
|
if (!databaseAccount?.properties) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const serverId: string = userContext.portalEnv;
|
||||||
|
const numberOfRegions: number = databaseAccount.properties.readLocations?.length || 1;
|
||||||
|
const multimasterEnabled: boolean = databaseAccount.properties.enableMultipleWriteLocations;
|
||||||
|
const hourlyPrice: number = computeRUUsagePriceHourly({
|
||||||
|
serverId,
|
||||||
|
requestUnits,
|
||||||
|
numberOfRegions,
|
||||||
|
multimasterEnabled,
|
||||||
|
isAutoscale,
|
||||||
|
});
|
||||||
|
const dailyPrice: number = hourlyPrice * 24;
|
||||||
|
const monthlyPrice: number = hourlyPrice * SharedConstants.hoursInAMonth;
|
||||||
|
const currency: string = getPriceCurrency(serverId);
|
||||||
|
const currencySign: string = getCurrencySign(serverId);
|
||||||
|
const multiplier = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
|
||||||
|
const pricePerRu = isAutoscale
|
||||||
|
? getAutoscalePricePerRu(serverId, multiplier) * multiplier
|
||||||
|
: getPricePerRu(serverId) * multiplier;
|
||||||
|
|
||||||
|
const iconWithEstimatedCostDisclaimer: JSX.Element = <InfoTooltip>PricingUtils.estimatedCostDisclaimer</InfoTooltip>;
|
||||||
|
|
||||||
|
if (isAutoscale) {
|
||||||
|
return (
|
||||||
|
<Text variant="small">
|
||||||
|
Estimated monthly cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "}
|
||||||
|
<b>
|
||||||
|
{currencySign + calculateEstimateNumber(monthlyPrice / 10)} -{" "}
|
||||||
|
{currencySign + calculateEstimateNumber(monthlyPrice)}{" "}
|
||||||
|
</b>
|
||||||
|
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits / 10} - {requestUnits}{" "}
|
||||||
|
RU/s, {currencySign + pricePerRu}/RU)
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text variant="small">
|
||||||
|
Estimated cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "}
|
||||||
|
<b>
|
||||||
|
{currencySign + calculateEstimateNumber(hourlyPrice)} hourly /{" "}
|
||||||
|
{currencySign + calculateEstimateNumber(dailyPrice)} daily /{" "}
|
||||||
|
{currencySign + calculateEstimateNumber(monthlyPrice)} monthly{" "}
|
||||||
|
</b>
|
||||||
|
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits}RU/s,{" "}
|
||||||
|
{currencySign + pricePerRu}/RU)
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`CostEstimateText Pane should render Default properly 1`] = `<Fragment />`;
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { mount, ReactWrapper } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import { ThroughputInput } from "./ThroughputInput";
|
||||||
|
const props = {
|
||||||
|
isDatabase: false,
|
||||||
|
showFreeTierExceedThroughputTooltip: true,
|
||||||
|
isSharded: false,
|
||||||
|
setThroughputValue: () => jest.fn(),
|
||||||
|
setIsAutoscale: () => jest.fn(),
|
||||||
|
onCostAcknowledgeChange: () => jest.fn(),
|
||||||
|
};
|
||||||
|
describe("ThroughputInput Pane", () => {
|
||||||
|
let wrapper: ReactWrapper;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = mount(<ThroughputInput {...props} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render Default properly", () => {
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("test Autoscale Mode select", () => {
|
||||||
|
wrapper.setProps({ isAutoscaleSelected: true });
|
||||||
|
expect(wrapper.find('[aria-label="ruDescription"]').at(0).text()).toBe(
|
||||||
|
"Estimate your required RU/s with capacity calculator."
|
||||||
|
);
|
||||||
|
expect(wrapper.find('[aria-label="maxRUDescription"]').at(0).text()).toContain("Max RU/s");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("test Manual Mode select", () => {
|
||||||
|
wrapper.setProps({ isAutoscaleSelected: false });
|
||||||
|
expect(wrapper.find('[aria-label="ruDescription"]').at(0).text()).toContain("Estimate your required RU/s with");
|
||||||
|
expect(wrapper.find('[aria-label="capacityLink"]').at(0).text()).toContain("capacity calculator");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
import { Checkbox, DirectionalHint, Icon, Link, Stack, Text, TextField, TooltipHost } from "@fluentui/react";
|
import { Checkbox, DirectionalHint, Link, Stack, Text, TextField, TooltipHost } from "@fluentui/react";
|
||||||
import React from "react";
|
import React, { FunctionComponent, useState } from "react";
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
|
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
||||||
import * as SharedConstants from "../../../Shared/Constants";
|
import * as SharedConstants from "../../../Shared/Constants";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { getCollectionName } from "../../../Utils/APITypeUtils";
|
import { getCollectionName } from "../../../Utils/APITypeUtils";
|
||||||
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||||
import * as PricingUtils from "../../../Utils/PricingUtils";
|
import * as PricingUtils from "../../../Utils/PricingUtils";
|
||||||
|
import { CostEstimateText } from "./CostEstimateText/CostEstimateText";
|
||||||
|
import "./ThroughputInput.less";
|
||||||
|
|
||||||
export interface ThroughputInputProps {
|
export interface ThroughputInputProps {
|
||||||
isDatabase: boolean;
|
isDatabase: boolean;
|
||||||
@@ -14,178 +17,25 @@ export interface ThroughputInputProps {
|
|||||||
setThroughputValue: (throughput: number) => void;
|
setThroughputValue: (throughput: number) => void;
|
||||||
setIsAutoscale: (isAutoscale: boolean) => void;
|
setIsAutoscale: (isAutoscale: boolean) => void;
|
||||||
onCostAcknowledgeChange: (isAcknowledged: boolean) => void;
|
onCostAcknowledgeChange: (isAcknowledged: boolean) => void;
|
||||||
|
isAutoscaleSelected?: boolean;
|
||||||
|
throughput?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ThroughputInputState {
|
export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||||
isAutoscaleSelected: boolean;
|
isDatabase,
|
||||||
throughput: number;
|
showFreeTierExceedThroughputTooltip,
|
||||||
isCostAcknowledged: boolean;
|
setThroughputValue,
|
||||||
throughputError: string;
|
setIsAutoscale,
|
||||||
}
|
isSharded,
|
||||||
|
isAutoscaleSelected = true,
|
||||||
export class ThroughputInput extends React.Component<ThroughputInputProps, ThroughputInputState> {
|
throughput = AutoPilotUtils.minAutoPilotThroughput,
|
||||||
constructor(props: ThroughputInputProps) {
|
onCostAcknowledgeChange,
|
||||||
super(props);
|
}: ThroughputInputProps) => {
|
||||||
|
const [isCostAcknowledged, setIsCostAcknowledged] = useState<boolean>(false);
|
||||||
this.state = {
|
const [throughputError, setThroughputError] = useState<string>("");
|
||||||
isAutoscaleSelected: true,
|
const getThroughputLabelText = (): string => {
|
||||||
throughput: AutoPilotUtils.minAutoPilotThroughput,
|
|
||||||
isCostAcknowledged: false,
|
|
||||||
throughputError: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.props.setThroughputValue(AutoPilotUtils.minAutoPilotThroughput);
|
|
||||||
this.props.setIsAutoscale(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<div className="throughputInputContainer throughputInputSpacing">
|
|
||||||
<Stack horizontal>
|
|
||||||
<span className="mandatoryStar">* </span>
|
|
||||||
<Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }}>
|
|
||||||
{this.getThroughputLabelText()}
|
|
||||||
</Text>
|
|
||||||
<TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={PricingUtils.getRuToolTipText()}>
|
|
||||||
<Icon iconName="Info" className="panelInfoIcon" />
|
|
||||||
</TooltipHost>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<Stack horizontal verticalAlign="center">
|
|
||||||
<input
|
|
||||||
className="throughputInputRadioBtn"
|
|
||||||
aria-label="Autoscale mode"
|
|
||||||
checked={this.state.isAutoscaleSelected}
|
|
||||||
type="radio"
|
|
||||||
role="radio"
|
|
||||||
tabIndex={0}
|
|
||||||
onChange={this.onAutoscaleRadioBtnChange.bind(this)}
|
|
||||||
/>
|
|
||||||
<span className="throughputInputRadioBtnLabel">Autoscale</span>
|
|
||||||
|
|
||||||
<input
|
|
||||||
className="throughputInputRadioBtn"
|
|
||||||
aria-label="Manual mode"
|
|
||||||
checked={!this.state.isAutoscaleSelected}
|
|
||||||
type="radio"
|
|
||||||
role="radio"
|
|
||||||
tabIndex={0}
|
|
||||||
onChange={this.onManualRadioBtnChange.bind(this)}
|
|
||||||
/>
|
|
||||||
<span className="throughputInputRadioBtnLabel">Manual</span>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
{this.state.isAutoscaleSelected && (
|
|
||||||
<Stack className="throughputInputSpacing">
|
|
||||||
<Text variant="small">
|
|
||||||
Estimate your required RU/s with
|
|
||||||
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/">
|
|
||||||
capacity calculator
|
|
||||||
</Link>
|
|
||||||
.
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Stack horizontal>
|
|
||||||
<Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }}>
|
|
||||||
{this.props.isDatabase ? "Database" : getCollectionName()} max RU/s
|
|
||||||
</Text>
|
|
||||||
<TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={this.getAutoScaleTooltip()}>
|
|
||||||
<Icon iconName="Info" className="panelInfoIcon" />
|
|
||||||
</TooltipHost>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<TextField
|
|
||||||
type="number"
|
|
||||||
styles={{
|
|
||||||
fieldGroup: { width: 300, height: 27 },
|
|
||||||
field: { fontSize: 12 },
|
|
||||||
}}
|
|
||||||
onChange={(event, newInput?: string) => this.onThroughputValueChange(newInput)}
|
|
||||||
step={AutoPilotUtils.autoPilotIncrementStep}
|
|
||||||
min={AutoPilotUtils.minAutoPilotThroughput}
|
|
||||||
value={this.state.throughput.toString()}
|
|
||||||
aria-label="Max request units per second"
|
|
||||||
errorMessage={this.state.throughputError}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Text variant="small">
|
|
||||||
Your {this.props.isDatabase ? "database" : getCollectionName().toLocaleLowerCase()} throughput will
|
|
||||||
automatically scale from{" "}
|
|
||||||
<b>
|
|
||||||
{AutoPilotUtils.getMinRUsBasedOnUserInput(this.state.throughput)} RU/s (10% of max RU/s) -{" "}
|
|
||||||
{this.state.throughput} RU/s
|
|
||||||
</b>{" "}
|
|
||||||
based on usage.
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!this.state.isAutoscaleSelected && (
|
|
||||||
<Stack className="throughputInputSpacing">
|
|
||||||
<Text variant="small">
|
|
||||||
Estimate your required RU/s with
|
|
||||||
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/">
|
|
||||||
capacity calculator
|
|
||||||
</Link>
|
|
||||||
.
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<TooltipHost
|
|
||||||
directionalHint={DirectionalHint.topLeftEdge}
|
|
||||||
content={
|
|
||||||
this.props.showFreeTierExceedThroughputTooltip &&
|
|
||||||
this.state.throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs400
|
|
||||||
? "The first 400 RU/s in this account are free. Billing will apply to any throughput beyond 400 RU/s."
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<TextField
|
|
||||||
type="number"
|
|
||||||
styles={{
|
|
||||||
fieldGroup: { width: 300, height: 27 },
|
|
||||||
field: { fontSize: 12 },
|
|
||||||
}}
|
|
||||||
onChange={(event, newInput?: string) => this.onThroughputValueChange(newInput)}
|
|
||||||
step={100}
|
|
||||||
min={SharedConstants.CollectionCreation.DefaultCollectionRUs400}
|
|
||||||
max={userContext.isTryCosmosDBSubscription ? Constants.TryCosmosExperience.maxRU : Infinity}
|
|
||||||
value={this.state.throughput.toString()}
|
|
||||||
aria-label="Max request units per second"
|
|
||||||
required={true}
|
|
||||||
errorMessage={this.state.throughputError}
|
|
||||||
/>
|
|
||||||
</TooltipHost>
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<CostEstimateText requestUnits={this.state.throughput} isAutoscale={this.state.isAutoscaleSelected} />
|
|
||||||
|
|
||||||
{this.state.throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && (
|
|
||||||
<Stack horizontal verticalAlign="start">
|
|
||||||
<span className="mandatoryStar">* </span>
|
|
||||||
<Checkbox
|
|
||||||
checked={this.state.isCostAcknowledged}
|
|
||||||
styles={{
|
|
||||||
checkbox: { width: 12, height: 12 },
|
|
||||||
label: { padding: 0, margin: "4px 4px 0 0" },
|
|
||||||
}}
|
|
||||||
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) => {
|
|
||||||
this.setState({ isCostAcknowledged: isChecked });
|
|
||||||
this.props.onCostAcknowledgeChange(isChecked);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Text variant="small" style={{ lineHeight: "20px" }}>
|
|
||||||
{this.getCostAcknowledgeText()}
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getThroughputLabelText(): string {
|
|
||||||
let throughputHeaderText: string;
|
let throughputHeaderText: string;
|
||||||
if (this.state.isAutoscaleSelected) {
|
if (isAutoscaleSelected) {
|
||||||
throughputHeaderText = AutoPilotUtils.getAutoPilotHeaderText().toLocaleLowerCase();
|
throughputHeaderText = AutoPilotUtils.getAutoPilotHeaderText().toLocaleLowerCase();
|
||||||
} else {
|
} else {
|
||||||
const minRU: string = SharedConstants.CollectionCreation.DefaultCollectionRUs400.toLocaleString();
|
const minRU: string = SharedConstants.CollectionCreation.DefaultCollectionRUs400.toLocaleString();
|
||||||
@@ -194,29 +44,26 @@ export class ThroughputInput extends React.Component<ThroughputInputProps, Throu
|
|||||||
: "unlimited";
|
: "unlimited";
|
||||||
throughputHeaderText = `throughput (${minRU} - ${maxRU} RU/s)`;
|
throughputHeaderText = `throughput (${minRU} - ${maxRU} RU/s)`;
|
||||||
}
|
}
|
||||||
|
return `${isDatabase ? "Database" : getCollectionName()} ${throughputHeaderText}`;
|
||||||
|
};
|
||||||
|
|
||||||
return `${this.props.isDatabase ? "Database" : getCollectionName()} ${throughputHeaderText}`;
|
const onThroughputValueChange = (newInput: string): void => {
|
||||||
}
|
|
||||||
|
|
||||||
private onThroughputValueChange(newInput: string): void {
|
|
||||||
const newThroughput = parseInt(newInput);
|
const newThroughput = parseInt(newInput);
|
||||||
this.setState({ throughput: newThroughput });
|
setThroughputValue(newThroughput);
|
||||||
this.props.setThroughputValue(newThroughput);
|
if (!isSharded && newThroughput > 10000) {
|
||||||
|
setThroughputError("Unsharded collections support up to 10,000 RUs");
|
||||||
if (!this.props.isSharded && newThroughput > 10000) {
|
|
||||||
this.setState({ throughputError: "Unsharded collections support up to 10,000 RUs" });
|
|
||||||
} else {
|
} else {
|
||||||
this.setState({ throughputError: undefined });
|
setThroughputError("");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
private getAutoScaleTooltip(): string {
|
const getAutoScaleTooltip = (): string => {
|
||||||
const collectionName = getCollectionName().toLocaleLowerCase();
|
const collectionName = getCollectionName().toLocaleLowerCase();
|
||||||
return `Set the max RU/s to the highest RU/s you want your ${collectionName} to scale to. The ${collectionName} will scale between 10% of max RU/s to the max RU/s based on usage.`;
|
return `Set the max RU/s to the highest RU/s you want your ${collectionName} to scale to. The ${collectionName} will scale between 10% of max RU/s to the max RU/s based on usage.`;
|
||||||
}
|
};
|
||||||
|
|
||||||
private getCostAcknowledgeText(): string {
|
const getCostAcknowledgeText = (): string => {
|
||||||
const { databaseAccount } = userContext;
|
const databaseAccount = userContext.databaseAccount;
|
||||||
if (!databaseAccount || !databaseAccount.properties) {
|
if (!databaseAccount || !databaseAccount.properties) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -225,98 +72,159 @@ export class ThroughputInput extends React.Component<ThroughputInputProps, Throu
|
|||||||
const multimasterEnabled: boolean = databaseAccount.properties.enableMultipleWriteLocations;
|
const multimasterEnabled: boolean = databaseAccount.properties.enableMultipleWriteLocations;
|
||||||
|
|
||||||
return PricingUtils.getEstimatedSpendAcknowledgeString(
|
return PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||||
this.state.throughput,
|
throughput,
|
||||||
userContext.portalEnv,
|
userContext.portalEnv,
|
||||||
numberOfRegions,
|
numberOfRegions,
|
||||||
multimasterEnabled,
|
multimasterEnabled,
|
||||||
this.state.isAutoscaleSelected
|
isAutoscaleSelected
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
private onAutoscaleRadioBtnChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
const handleOnChangeMode = (event: React.ChangeEvent<HTMLInputElement>, mode: string): void => {
|
||||||
if (event.target.checked && !this.state.isAutoscaleSelected) {
|
if (mode === "Autoscale") {
|
||||||
this.setState({ isAutoscaleSelected: true, throughput: AutoPilotUtils.minAutoPilotThroughput });
|
setThroughputValue(AutoPilotUtils.minAutoPilotThroughput);
|
||||||
this.props.setIsAutoscale(true);
|
setIsAutoscale(true);
|
||||||
|
} else {
|
||||||
|
setThroughputValue(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
||||||
|
setIsAutoscale(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
private onManualRadioBtnChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
|
||||||
if (event.target.checked && this.state.isAutoscaleSelected) {
|
|
||||||
this.setState({
|
|
||||||
isAutoscaleSelected: false,
|
|
||||||
throughput: SharedConstants.CollectionCreation.DefaultCollectionRUs400,
|
|
||||||
});
|
|
||||||
this.props.setIsAutoscale(false);
|
|
||||||
this.props.setThroughputValue(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CostEstimateTextProps {
|
|
||||||
requestUnits: number;
|
|
||||||
isAutoscale: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CostEstimateText: React.FunctionComponent<CostEstimateTextProps> = (props: CostEstimateTextProps) => {
|
|
||||||
const { requestUnits, isAutoscale } = props;
|
|
||||||
const { databaseAccount } = userContext;
|
|
||||||
if (!databaseAccount?.properties) {
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const serverId: string = userContext.portalEnv;
|
|
||||||
const numberOfRegions: number = databaseAccount.properties.readLocations?.length || 1;
|
|
||||||
const multimasterEnabled: boolean = databaseAccount.properties.enableMultipleWriteLocations;
|
|
||||||
const hourlyPrice: number = PricingUtils.computeRUUsagePriceHourly({
|
|
||||||
serverId,
|
|
||||||
requestUnits,
|
|
||||||
numberOfRegions,
|
|
||||||
multimasterEnabled,
|
|
||||||
isAutoscale,
|
|
||||||
});
|
|
||||||
const dailyPrice: number = hourlyPrice * 24;
|
|
||||||
const monthlyPrice: number = hourlyPrice * SharedConstants.hoursInAMonth;
|
|
||||||
const currency: string = PricingUtils.getPriceCurrency(serverId);
|
|
||||||
const currencySign: string = PricingUtils.getCurrencySign(serverId);
|
|
||||||
const multiplier = PricingUtils.getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
|
|
||||||
const pricePerRu = isAutoscale
|
|
||||||
? PricingUtils.getAutoscalePricePerRu(serverId, multiplier) * multiplier
|
|
||||||
: PricingUtils.getPricePerRu(serverId) * multiplier;
|
|
||||||
|
|
||||||
const iconWithEstimatedCostDisclaimer: JSX.Element = (
|
|
||||||
<TooltipHost
|
|
||||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
|
||||||
content={PricingUtils.estimatedCostDisclaimer}
|
|
||||||
styles={{ root: { verticalAlign: "bottom" } }}
|
|
||||||
>
|
|
||||||
<Icon iconName="Info" className="panelInfoIcon" />
|
|
||||||
</TooltipHost>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isAutoscale) {
|
|
||||||
return (
|
|
||||||
<Text variant="small">
|
|
||||||
Estimated monthly cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "}
|
|
||||||
<b>
|
|
||||||
{currencySign + PricingUtils.calculateEstimateNumber(monthlyPrice / 10)} -{" "}
|
|
||||||
{currencySign + PricingUtils.calculateEstimateNumber(monthlyPrice)}{" "}
|
|
||||||
</b>
|
|
||||||
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits / 10} - {requestUnits}{" "}
|
|
||||||
RU/s, {currencySign + pricePerRu}/RU)
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text variant="small">
|
<div className="throughputInputContainer throughputInputSpacing">
|
||||||
Estimated cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "}
|
<Stack horizontal>
|
||||||
<b>
|
<span className="mandatoryStar">* </span>
|
||||||
{currencySign + PricingUtils.calculateEstimateNumber(hourlyPrice)} hourly /{" "}
|
<Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }}>
|
||||||
{currencySign + PricingUtils.calculateEstimateNumber(dailyPrice)} daily /{" "}
|
{getThroughputLabelText()}
|
||||||
{currencySign + PricingUtils.calculateEstimateNumber(monthlyPrice)} monthly{" "}
|
</Text>
|
||||||
</b>
|
<InfoTooltip>{PricingUtils.getRuToolTipText()}</InfoTooltip>
|
||||||
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits}RU/s,{" "}
|
</Stack>
|
||||||
{currencySign + pricePerRu}/RU)
|
|
||||||
</Text>
|
<Stack horizontal verticalAlign="center">
|
||||||
|
<input
|
||||||
|
className="throughputInputRadioBtn"
|
||||||
|
aria-label="Autoscale mode"
|
||||||
|
checked={isAutoscaleSelected}
|
||||||
|
type="radio"
|
||||||
|
role="radio"
|
||||||
|
tabIndex={0}
|
||||||
|
onChange={(e) => handleOnChangeMode(e, "Autoscale")}
|
||||||
|
/>
|
||||||
|
<span className="throughputInputRadioBtnLabel">Autoscale</span>
|
||||||
|
|
||||||
|
<input
|
||||||
|
className="throughputInputRadioBtn"
|
||||||
|
aria-label="Manual mode"
|
||||||
|
checked={!isAutoscaleSelected}
|
||||||
|
type="radio"
|
||||||
|
role="radio"
|
||||||
|
tabIndex={0}
|
||||||
|
onChange={(e) => handleOnChangeMode(e, "Manual")}
|
||||||
|
/>
|
||||||
|
<span className="throughputInputRadioBtnLabel">Manual</span>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{isAutoscaleSelected && (
|
||||||
|
<Stack className="throughputInputSpacing">
|
||||||
|
<Text variant="small" aria-label="ruDescription">
|
||||||
|
Estimate your required RU/s with{" "}
|
||||||
|
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/" aria-label="ruDescription">
|
||||||
|
capacity calculator
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Stack horizontal>
|
||||||
|
<Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }} aria-label="maxRUDescription">
|
||||||
|
{isDatabase ? "Database" : getCollectionName()} Max RU/s
|
||||||
|
</Text>
|
||||||
|
<InfoTooltip>{getAutoScaleTooltip()}</InfoTooltip>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
type="number"
|
||||||
|
styles={{
|
||||||
|
fieldGroup: { width: 300, height: 27 },
|
||||||
|
field: { fontSize: 12 },
|
||||||
|
}}
|
||||||
|
onChange={(event, newInput?: string) => onThroughputValueChange(newInput)}
|
||||||
|
step={AutoPilotUtils.autoPilotIncrementStep}
|
||||||
|
min={AutoPilotUtils.minAutoPilotThroughput}
|
||||||
|
value={throughput.toString()}
|
||||||
|
aria-label="Max request units per second"
|
||||||
|
required={true}
|
||||||
|
errorMessage={throughputError}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Text variant="small">
|
||||||
|
Your {isDatabase ? "database" : getCollectionName().toLocaleLowerCase()} throughput will automatically scale
|
||||||
|
from{" "}
|
||||||
|
<b>
|
||||||
|
{AutoPilotUtils.getMinRUsBasedOnUserInput(throughput)} RU/s (10% of max RU/s) - {throughput} RU/s
|
||||||
|
</b>{" "}
|
||||||
|
based on usage.
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isAutoscaleSelected && (
|
||||||
|
<Stack className="throughputInputSpacing">
|
||||||
|
<Text variant="small" aria-label="ruDescription">
|
||||||
|
Estimate your required RU/s with
|
||||||
|
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/" aria-label="capacityLink">
|
||||||
|
capacity calculator
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<TooltipHost
|
||||||
|
directionalHint={DirectionalHint.topLeftEdge}
|
||||||
|
content={
|
||||||
|
showFreeTierExceedThroughputTooltip &&
|
||||||
|
throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs400
|
||||||
|
? "The first 400 RU/s in this account are free. Billing will apply to any throughput beyond 400 RU/s."
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
type="number"
|
||||||
|
styles={{
|
||||||
|
fieldGroup: { width: 300, height: 27 },
|
||||||
|
field: { fontSize: 12 },
|
||||||
|
}}
|
||||||
|
onChange={(event, newInput?: string) => onThroughputValueChange(newInput)}
|
||||||
|
step={100}
|
||||||
|
min={SharedConstants.CollectionCreation.DefaultCollectionRUs400}
|
||||||
|
max={userContext.isTryCosmosDBSubscription ? Constants.TryCosmosExperience.maxRU : Infinity}
|
||||||
|
value={throughput.toString()}
|
||||||
|
aria-label="Max request units per second"
|
||||||
|
required={true}
|
||||||
|
errorMessage={throughputError}
|
||||||
|
/>
|
||||||
|
</TooltipHost>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<CostEstimateText requestUnits={throughput} isAutoscale={isAutoscaleSelected} />
|
||||||
|
|
||||||
|
{throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && (
|
||||||
|
<Stack horizontal verticalAlign="start">
|
||||||
|
<Checkbox
|
||||||
|
checked={isCostAcknowledged}
|
||||||
|
styles={{
|
||||||
|
checkbox: { width: 12, height: 12 },
|
||||||
|
label: { padding: 0, margin: "4px 4px 0 0" },
|
||||||
|
}}
|
||||||
|
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) => {
|
||||||
|
setIsCostAcknowledged(isChecked);
|
||||||
|
onCostAcknowledgeChange(isChecked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text variant="small" style={{ lineHeight: "20px" }}>
|
||||||
|
{getCostAcknowledgeText()}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -31,7 +31,7 @@ import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants"
|
|||||||
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { ArcadiaResourceManager } from "../SparkClusterManager/ArcadiaResourceManager";
|
import { ArcadiaResourceManager } from "../SparkClusterManager/ArcadiaResourceManager";
|
||||||
import { updateUserContext, userContext } from "../UserContext";
|
import { updateUserContext, userContext } from "../UserContext";
|
||||||
import { getCollectionName } from "../Utils/APITypeUtils";
|
import { getCollectionName, getDatabaseName, getUploadName } from "../Utils/APITypeUtils";
|
||||||
import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||||
import { stringToBlob } from "../Utils/BlobUtils";
|
import { stringToBlob } from "../Utils/BlobUtils";
|
||||||
import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
|
import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
|
||||||
@@ -45,13 +45,14 @@ import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/Gallery
|
|||||||
import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter";
|
import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import { ConsoleData } from "./Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleData } from "./Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import * as FileSystemUtil from "./Notebook/FileSystemUtil";
|
import * as FileSystemUtil from "./Notebook/FileSystemUtil";
|
||||||
|
import { SnapshotRequest } from "./Notebook/NotebookComponent/types";
|
||||||
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";
|
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";
|
||||||
import type NotebookManager from "./Notebook/NotebookManager";
|
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 AddCollectionPane from "./Panes/AddCollectionPane";
|
|
||||||
import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
|
import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
|
||||||
import AddDatabasePane from "./Panes/AddDatabasePane";
|
import AddDatabasePane from "./Panes/AddDatabasePane";
|
||||||
|
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";
|
||||||
import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
|
import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
|
||||||
@@ -91,7 +92,7 @@ export interface ExplorerParams {
|
|||||||
setIsNotificationConsoleExpanded: (isExpanded: boolean) => void;
|
setIsNotificationConsoleExpanded: (isExpanded: boolean) => void;
|
||||||
setNotificationConsoleData: (consoleData: ConsoleData) => void;
|
setNotificationConsoleData: (consoleData: ConsoleData) => void;
|
||||||
setInProgressConsoleDataIdToBeDeleted: (id: string) => void;
|
setInProgressConsoleDataIdToBeDeleted: (id: string) => void;
|
||||||
openSidePanel: (headerText: string, panelContent: JSX.Element) => void;
|
openSidePanel: (headerText: string, panelContent: JSX.Element, onClose?: () => void) => void;
|
||||||
closeSidePanel: () => void;
|
closeSidePanel: () => void;
|
||||||
closeDialog: () => void;
|
closeDialog: () => void;
|
||||||
openDialog: (props: DialogProps) => void;
|
openDialog: (props: DialogProps) => void;
|
||||||
@@ -125,7 +126,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
// Panes
|
// Panes
|
||||||
public contextPanes: ContextualPaneBase[];
|
public contextPanes: ContextualPaneBase[];
|
||||||
public openSidePanel: (headerText: string, panelContent: JSX.Element) => void;
|
public openSidePanel: (headerText: string, panelContent: JSX.Element, onClose?: () => void) => void;
|
||||||
public closeSidePanel: () => void;
|
public closeSidePanel: () => void;
|
||||||
|
|
||||||
// Resource Tree
|
// Resource Tree
|
||||||
@@ -150,7 +151,6 @@ export default class Explorer {
|
|||||||
|
|
||||||
// Contextual panes
|
// Contextual panes
|
||||||
public addDatabasePane: AddDatabasePane;
|
public addDatabasePane: AddDatabasePane;
|
||||||
public addCollectionPane: AddCollectionPane;
|
|
||||||
public graphStylingPane: GraphStylingPane;
|
public graphStylingPane: GraphStylingPane;
|
||||||
public cassandraAddCollectionPane: CassandraAddCollectionPane;
|
public cassandraAddCollectionPane: CassandraAddCollectionPane;
|
||||||
private gitHubClient: GitHubClient;
|
private gitHubClient: GitHubClient;
|
||||||
@@ -412,14 +412,6 @@ export default class Explorer {
|
|||||||
container: this,
|
container: this,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addCollectionPane = new AddCollectionPane({
|
|
||||||
isPreferredApiTable: ko.computed(() => userContext.apiType === "Tables"),
|
|
||||||
id: "addcollectionpane",
|
|
||||||
visible: ko.observable<boolean>(false),
|
|
||||||
|
|
||||||
container: this,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.graphStylingPane = new GraphStylingPane({
|
this.graphStylingPane = new GraphStylingPane({
|
||||||
id: "graphstylingpane",
|
id: "graphstylingpane",
|
||||||
visible: ko.observable<boolean>(false),
|
visible: ko.observable<boolean>(false),
|
||||||
@@ -442,12 +434,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this._panes = [
|
this._panes = [this.addDatabasePane, this.graphStylingPane, this.cassandraAddCollectionPane];
|
||||||
this.addDatabasePane,
|
|
||||||
this.addCollectionPane,
|
|
||||||
this.graphStylingPane,
|
|
||||||
this.cassandraAddCollectionPane,
|
|
||||||
];
|
|
||||||
this.addDatabaseText.subscribe((addDatabaseText: string) => this.addDatabasePane.title(addDatabaseText));
|
this.addDatabaseText.subscribe((addDatabaseText: string) => this.addDatabasePane.title(addDatabaseText));
|
||||||
this.isTabsContentExpanded = ko.observable(false);
|
this.isTabsContentExpanded = ko.observable(false);
|
||||||
|
|
||||||
@@ -471,11 +458,6 @@ export default class Explorer {
|
|||||||
this.collectionTreeNodeAltText("Container");
|
this.collectionTreeNodeAltText("Container");
|
||||||
this.deleteCollectionText("Delete Container");
|
this.deleteCollectionText("Delete Container");
|
||||||
this.deleteDatabaseText("Delete Database");
|
this.deleteDatabaseText("Delete Database");
|
||||||
this.addCollectionPane.title("Add Container");
|
|
||||||
this.addCollectionPane.collectionIdTitle("Container id");
|
|
||||||
this.addCollectionPane.collectionWithThroughputInSharedTitle(
|
|
||||||
"Provision dedicated throughput for this container"
|
|
||||||
);
|
|
||||||
this.refreshTreeTitle("Refresh containers");
|
this.refreshTreeTitle("Refresh containers");
|
||||||
break;
|
break;
|
||||||
case "Mongo":
|
case "Mongo":
|
||||||
@@ -485,11 +467,6 @@ export default class Explorer {
|
|||||||
this.collectionTreeNodeAltText("Collection");
|
this.collectionTreeNodeAltText("Collection");
|
||||||
this.deleteCollectionText("Delete Collection");
|
this.deleteCollectionText("Delete Collection");
|
||||||
this.deleteDatabaseText("Delete Database");
|
this.deleteDatabaseText("Delete Database");
|
||||||
this.addCollectionPane.title("Add Collection");
|
|
||||||
this.addCollectionPane.collectionIdTitle("Collection id");
|
|
||||||
this.addCollectionPane.collectionWithThroughputInSharedTitle(
|
|
||||||
"Provision dedicated throughput for this collection"
|
|
||||||
);
|
|
||||||
this.refreshTreeTitle("Refresh collections");
|
this.refreshTreeTitle("Refresh collections");
|
||||||
break;
|
break;
|
||||||
case "Gremlin":
|
case "Gremlin":
|
||||||
@@ -499,9 +476,6 @@ export default class Explorer {
|
|||||||
this.deleteDatabaseText("Delete Database");
|
this.deleteDatabaseText("Delete Database");
|
||||||
this.collectionTitle("Gremlin API");
|
this.collectionTitle("Gremlin API");
|
||||||
this.collectionTreeNodeAltText("Graph");
|
this.collectionTreeNodeAltText("Graph");
|
||||||
this.addCollectionPane.title("Add Graph");
|
|
||||||
this.addCollectionPane.collectionIdTitle("Graph id");
|
|
||||||
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this graph");
|
|
||||||
this.refreshTreeTitle("Refresh graphs");
|
this.refreshTreeTitle("Refresh graphs");
|
||||||
break;
|
break;
|
||||||
case "Tables":
|
case "Tables":
|
||||||
@@ -511,9 +485,6 @@ export default class Explorer {
|
|||||||
this.deleteDatabaseText("Delete Database");
|
this.deleteDatabaseText("Delete Database");
|
||||||
this.collectionTitle("Azure Table API");
|
this.collectionTitle("Azure Table API");
|
||||||
this.collectionTreeNodeAltText("Table");
|
this.collectionTreeNodeAltText("Table");
|
||||||
this.addCollectionPane.title("Add Table");
|
|
||||||
this.addCollectionPane.collectionIdTitle("Table id");
|
|
||||||
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this table");
|
|
||||||
this.refreshTreeTitle("Refresh tables");
|
this.refreshTreeTitle("Refresh tables");
|
||||||
this.tableDataClient = new TablesAPIDataClient();
|
this.tableDataClient = new TablesAPIDataClient();
|
||||||
break;
|
break;
|
||||||
@@ -524,9 +495,6 @@ export default class Explorer {
|
|||||||
this.deleteDatabaseText("Delete Keyspace");
|
this.deleteDatabaseText("Delete Keyspace");
|
||||||
this.collectionTitle("Cassandra API");
|
this.collectionTitle("Cassandra API");
|
||||||
this.collectionTreeNodeAltText("Table");
|
this.collectionTreeNodeAltText("Table");
|
||||||
this.addCollectionPane.title("Add Table");
|
|
||||||
this.addCollectionPane.collectionIdTitle("Table id");
|
|
||||||
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this table");
|
|
||||||
this.refreshTreeTitle("Refresh tables");
|
this.refreshTreeTitle("Refresh tables");
|
||||||
this.tableDataClient = new CassandraAPIDataClient();
|
this.tableDataClient = new CassandraAPIDataClient();
|
||||||
break;
|
break;
|
||||||
@@ -1303,10 +1271,18 @@ export default class Explorer {
|
|||||||
public async publishNotebook(
|
public async publishNotebook(
|
||||||
name: string,
|
name: string,
|
||||||
content: NotebookPaneContent,
|
content: NotebookPaneContent,
|
||||||
parentDomElement?: HTMLElement
|
notebookContentRef?: string,
|
||||||
|
onTakeSnapshot?: (request: SnapshotRequest) => void,
|
||||||
|
onClosePanel?: () => void
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (this.notebookManager) {
|
if (this.notebookManager) {
|
||||||
await this.notebookManager.openPublishNotebookPane(name, content, parentDomElement);
|
await this.notebookManager.openPublishNotebookPane(
|
||||||
|
name,
|
||||||
|
content,
|
||||||
|
notebookContentRef,
|
||||||
|
onTakeSnapshot,
|
||||||
|
onClosePanel
|
||||||
|
);
|
||||||
this.isPublishNotebookPaneEnabled(true);
|
this.isPublishNotebookPaneEnabled(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1845,9 +1821,6 @@ export default class Explorer {
|
|||||||
public onNewCollectionClicked(databaseId?: string): void {
|
public onNewCollectionClicked(databaseId?: string): void {
|
||||||
if (userContext.apiType === "Cassandra") {
|
if (userContext.apiType === "Cassandra") {
|
||||||
this.cassandraAddCollectionPane.open();
|
this.cassandraAddCollectionPane.open();
|
||||||
} else if (userContext.features.enableKOPanel) {
|
|
||||||
this.addCollectionPane.open(this.selectedDatabaseId());
|
|
||||||
document.getElementById("linkAddCollection").focus();
|
|
||||||
} else {
|
} else {
|
||||||
this.openAddCollectionPanel(databaseId);
|
this.openAddCollectionPanel(databaseId);
|
||||||
}
|
}
|
||||||
@@ -1961,7 +1934,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
public openDeleteDatabaseConfirmationPane(): void {
|
public openDeleteDatabaseConfirmationPane(): void {
|
||||||
this.openSidePanel(
|
this.openSidePanel(
|
||||||
"Delete Database",
|
"Delete " + getDatabaseName(),
|
||||||
<DeleteDatabaseConfirmationPanel
|
<DeleteDatabaseConfirmationPanel
|
||||||
explorer={this}
|
explorer={this}
|
||||||
openNotificationConsole={this.expandConsole}
|
openNotificationConsole={this.expandConsole}
|
||||||
@@ -1972,12 +1945,12 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public openUploadItemsPanePane(): void {
|
public openUploadItemsPanePane(): void {
|
||||||
this.openSidePanel("Upload", <UploadItemsPane explorer={this} closePanel={this.closeSidePanel} />);
|
this.openSidePanel("Upload " + getUploadName(), <UploadItemsPane explorer={this} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
public openSettingPane(): void {
|
public openSettingPane(): void {
|
||||||
this.openSidePanel(
|
this.openSidePanel(
|
||||||
"Settings",
|
"Setting",
|
||||||
<SettingsPane expandConsole={() => this.expandConsole()} closePanel={this.closeSidePanel} />
|
<SettingsPane expandConsole={() => this.expandConsole()} closePanel={this.closeSidePanel} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2005,6 +1978,21 @@ export default class Explorer {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
public openAddDatabasePane(): void {
|
||||||
|
if (userContext.features.enableKOPanel) {
|
||||||
|
this.addDatabasePane.open();
|
||||||
|
document.getElementById("linkAddDatabase").focus();
|
||||||
|
} else {
|
||||||
|
this.openSidePanel(
|
||||||
|
"Add " + getDatabaseName(),
|
||||||
|
<AddDatabasePanel
|
||||||
|
explorer={this}
|
||||||
|
openNotificationConsole={this.expandConsole}
|
||||||
|
closePanel={this.closeSidePanel}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public openBrowseQueriesPanel(): void {
|
public openBrowseQueriesPanel(): void {
|
||||||
this.openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={this} closePanel={this.closeSidePanel} />);
|
this.openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={this} closePanel={this.closeSidePanel} />);
|
||||||
@@ -2021,7 +2009,7 @@ export default class Explorer {
|
|||||||
public openUploadFilePanel(parent?: NotebookContentItem): void {
|
public openUploadFilePanel(parent?: NotebookContentItem): void {
|
||||||
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||||
this.openSidePanel(
|
this.openSidePanel(
|
||||||
"Upload File",
|
"Upload file to notebook server",
|
||||||
<UploadFilePane
|
<UploadFilePane
|
||||||
expandConsole={() => this.expandConsole()}
|
expandConsole={() => this.expandConsole()}
|
||||||
closePanel={this.closeSidePanel}
|
closePanel={this.closeSidePanel}
|
||||||
|
|||||||
@@ -266,8 +266,7 @@ function createNewDatabase(container: Explorer): CommandButtonComponentProps {
|
|||||||
iconSrc: AddDatabaseIcon,
|
iconSrc: AddDatabaseIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
container.addDatabasePane.open();
|
container.openAddDatabasePane();
|
||||||
document.getElementById("linkAddDatabase").focus();
|
|
||||||
},
|
},
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
import * as React from "react";
|
|
||||||
|
|
||||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
|
||||||
import { NotebookClientV2 } from "../NotebookClientV2";
|
|
||||||
|
|
||||||
// Vendor modules
|
// Vendor modules
|
||||||
import { actions, createContentRef, createKernelRef, selectors } from "@nteract/core";
|
import { actions, createContentRef, createKernelRef, selectors } from "@nteract/core";
|
||||||
import VirtualCommandBarComponent from "./VirtualCommandBarComponent";
|
import * as React from "react";
|
||||||
|
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
||||||
|
import { NotebookClientV2 } from "../NotebookClientV2";
|
||||||
import { NotebookContentItem } from "../NotebookContentItem";
|
import { NotebookContentItem } from "../NotebookContentItem";
|
||||||
import { NotebookComponentBootstrapper } from "./NotebookComponentBootstrapper";
|
import { NotebookComponentBootstrapper } from "./NotebookComponentBootstrapper";
|
||||||
import { CdbAppState } from "./types";
|
import VirtualCommandBarComponent from "./VirtualCommandBarComponent";
|
||||||
|
|
||||||
export interface NotebookComponentAdapterOptions {
|
export interface NotebookComponentAdapterOptions {
|
||||||
contentItem: NotebookContentItem;
|
contentItem: NotebookContentItem;
|
||||||
@@ -19,7 +16,6 @@ export interface NotebookComponentAdapterOptions {
|
|||||||
|
|
||||||
export class NotebookComponentAdapter extends NotebookComponentBootstrapper implements ReactAdapter {
|
export class NotebookComponentAdapter extends NotebookComponentBootstrapper implements ReactAdapter {
|
||||||
private onUpdateKernelInfo: () => void;
|
private onUpdateKernelInfo: () => void;
|
||||||
public getNotebookParentElement: () => HTMLElement;
|
|
||||||
public parameters: any;
|
public parameters: any;
|
||||||
|
|
||||||
constructor(options: NotebookComponentAdapterOptions) {
|
constructor(options: NotebookComponentAdapterOptions) {
|
||||||
@@ -46,11 +42,6 @@ export class NotebookComponentAdapter extends NotebookComponentBootstrapper impl
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getNotebookParentElement = () => {
|
|
||||||
const cdbAppState = this.getStore().getState() as CdbAppState;
|
|
||||||
return cdbAppState.cdb.currentNotebookParentElements.get(this.contentRef);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected renderExtraComponent = (): JSX.Element => {
|
protected renderExtraComponent = (): JSX.Element => {
|
||||||
|
|||||||
@@ -1,35 +1,29 @@
|
|||||||
import * as React from "react";
|
import { CellId, CellType, ImmutableNotebook } from "@nteract/commutable";
|
||||||
|
|
||||||
import { NotebookComponent } from "./NotebookComponent";
|
|
||||||
import { NotebookClientV2 } from "../NotebookClientV2";
|
|
||||||
import { NotebookUtil } from "../NotebookUtil";
|
|
||||||
|
|
||||||
// Vendor modules
|
// Vendor modules
|
||||||
import {
|
import {
|
||||||
actions,
|
actions,
|
||||||
AppState,
|
AppState,
|
||||||
createKernelRef,
|
|
||||||
DocumentRecordProps,
|
|
||||||
ContentRef,
|
ContentRef,
|
||||||
|
DocumentRecordProps,
|
||||||
KernelRef,
|
KernelRef,
|
||||||
NotebookContentRecord,
|
NotebookContentRecord,
|
||||||
selectors,
|
selectors,
|
||||||
} from "@nteract/core";
|
} from "@nteract/core";
|
||||||
import * as Immutable from "immutable";
|
|
||||||
import { Provider } from "react-redux";
|
|
||||||
import { CellType, CellId, ImmutableNotebook } from "@nteract/commutable";
|
|
||||||
import { Store, AnyAction } from "redux";
|
|
||||||
|
|
||||||
import "./NotebookComponent.less";
|
|
||||||
|
|
||||||
import "codemirror/addon/hint/show-hint.css";
|
|
||||||
import "codemirror/lib/codemirror.css";
|
|
||||||
import "@nteract/styles/editor-overrides.css";
|
import "@nteract/styles/editor-overrides.css";
|
||||||
import "@nteract/styles/global-variables.css";
|
import "@nteract/styles/global-variables.css";
|
||||||
|
import "codemirror/addon/hint/show-hint.css";
|
||||||
|
import "codemirror/lib/codemirror.css";
|
||||||
|
import * as Immutable from "immutable";
|
||||||
|
import * as React from "react";
|
||||||
|
import { Provider } from "react-redux";
|
||||||
import "react-table/react-table.css";
|
import "react-table/react-table.css";
|
||||||
|
import { AnyAction, Store } from "redux";
|
||||||
import * as CdbActions from "./actions";
|
import { NotebookClientV2 } from "../NotebookClientV2";
|
||||||
|
import { NotebookUtil } from "../NotebookUtil";
|
||||||
import * as NteractUtil from "../NTeractUtil";
|
import * as NteractUtil from "../NTeractUtil";
|
||||||
|
import * as CdbActions from "./actions";
|
||||||
|
import { NotebookComponent } from "./NotebookComponent";
|
||||||
|
import "./NotebookComponent.less";
|
||||||
|
|
||||||
export interface NotebookComponentBootstrapperOptions {
|
export interface NotebookComponentBootstrapperOptions {
|
||||||
notebookClient: NotebookClientV2;
|
notebookClient: NotebookClientV2;
|
||||||
@@ -37,7 +31,7 @@ export interface NotebookComponentBootstrapperOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class NotebookComponentBootstrapper {
|
export class NotebookComponentBootstrapper {
|
||||||
protected contentRef: ContentRef;
|
public contentRef: ContentRef;
|
||||||
protected renderExtraComponent: () => JSX.Element;
|
protected renderExtraComponent: () => JSX.Element;
|
||||||
|
|
||||||
private notebookClient: NotebookClientV2;
|
private notebookClient: NotebookClientV2;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { CellId } from "@nteract/commutable";
|
import { CellId } from "@nteract/commutable";
|
||||||
import { ContentRef } from "@nteract/core";
|
import { ContentRef } from "@nteract/core";
|
||||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import { SnapshotFragment, SnapshotRequest } from "./types";
|
||||||
|
|
||||||
export const CLOSE_NOTEBOOK = "CLOSE_NOTEBOOK";
|
export const CLOSE_NOTEBOOK = "CLOSE_NOTEBOOK";
|
||||||
export interface CloseNotebookAction {
|
export interface CloseNotebookAction {
|
||||||
@@ -85,21 +86,68 @@ export const traceNotebookTelemetry = (payload: {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UPDATE_NOTEBOOK_PARENT_DOM_ELTS = "UPDATE_NOTEBOOK_PARENT_DOM_ELTS";
|
export const STORE_CELL_OUTPUT_SNAPSHOT = "STORE_CELL_OUTPUT_SNAPSHOT";
|
||||||
export interface UpdateNotebookParentDomEltAction {
|
export interface StoreCellOutputSnapshotAction {
|
||||||
type: "UPDATE_NOTEBOOK_PARENT_DOM_ELTS";
|
type: "STORE_CELL_OUTPUT_SNAPSHOT";
|
||||||
payload: {
|
payload: {
|
||||||
contentRef: ContentRef;
|
cellId: string;
|
||||||
parentElt: HTMLElement;
|
snapshot: SnapshotFragment;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UpdateNotebookParentDomElt = (payload: {
|
export const storeCellOutputSnapshot = (payload: {
|
||||||
contentRef: ContentRef;
|
cellId: string;
|
||||||
parentElt: HTMLElement;
|
snapshot: SnapshotFragment;
|
||||||
}): UpdateNotebookParentDomEltAction => {
|
}): StoreCellOutputSnapshotAction => {
|
||||||
return {
|
return {
|
||||||
type: UPDATE_NOTEBOOK_PARENT_DOM_ELTS,
|
type: STORE_CELL_OUTPUT_SNAPSHOT,
|
||||||
|
payload,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const STORE_NOTEBOOK_SNAPSHOT = "STORE_NOTEBOOK_SNAPSHOT";
|
||||||
|
export interface StoreNotebookSnapshotAction {
|
||||||
|
type: "STORE_NOTEBOOK_SNAPSHOT";
|
||||||
|
payload: {
|
||||||
|
imageSrc: string;
|
||||||
|
requestId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const storeNotebookSnapshot = (payload: {
|
||||||
|
imageSrc: string;
|
||||||
|
requestId: string;
|
||||||
|
}): StoreNotebookSnapshotAction => {
|
||||||
|
return {
|
||||||
|
type: STORE_NOTEBOOK_SNAPSHOT,
|
||||||
|
payload,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TAKE_NOTEBOOK_SNAPSHOT = "TAKE_NOTEBOOK_SNAPSHOT";
|
||||||
|
export interface TakeNotebookSnapshotAction {
|
||||||
|
type: "TAKE_NOTEBOOK_SNAPSHOT";
|
||||||
|
payload: SnapshotRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const takeNotebookSnapshot = (payload: SnapshotRequest): TakeNotebookSnapshotAction => {
|
||||||
|
return {
|
||||||
|
type: TAKE_NOTEBOOK_SNAPSHOT,
|
||||||
|
payload,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NOTEBOOK_SNAPSHOT_ERROR = "NOTEBOOK_SNAPSHOT_ERROR";
|
||||||
|
export interface NotebookSnapshotErrorAction {
|
||||||
|
type: "NOTEBOOK_SNAPSHOT_ERROR";
|
||||||
|
payload: {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const notebookSnapshotError = (payload: { error: string }): NotebookSnapshotErrorAction => {
|
||||||
|
return {
|
||||||
|
type: NOTEBOOK_SNAPSHOT_ERROR,
|
||||||
payload,
|
payload,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -70,17 +70,32 @@ export const cdbReducer = (state: CdbRecord, action: Action) => {
|
|||||||
return state.set("hoveredCellId", typedAction.payload.cellId);
|
return state.set("hoveredCellId", typedAction.payload.cellId);
|
||||||
}
|
}
|
||||||
|
|
||||||
case cdbActions.UPDATE_NOTEBOOK_PARENT_DOM_ELTS: {
|
case cdbActions.STORE_CELL_OUTPUT_SNAPSHOT: {
|
||||||
const typedAction = action as cdbActions.UpdateNotebookParentDomEltAction;
|
const typedAction = action as cdbActions.StoreCellOutputSnapshotAction;
|
||||||
var parentEltsMap = state.get("currentNotebookParentElements");
|
state.cellOutputSnapshots.set(typedAction.payload.cellId, typedAction.payload.snapshot);
|
||||||
const contentRef = typedAction.payload.contentRef;
|
// TODO Simpler datastructure to instantiate new Map?
|
||||||
const parentElt = typedAction.payload.parentElt;
|
return state.set("cellOutputSnapshots", new Map(state.cellOutputSnapshots));
|
||||||
if (parentElt) {
|
}
|
||||||
parentEltsMap.set(contentRef, parentElt);
|
|
||||||
} else {
|
case cdbActions.STORE_NOTEBOOK_SNAPSHOT: {
|
||||||
parentEltsMap.delete(contentRef);
|
const typedAction = action as cdbActions.StoreNotebookSnapshotAction;
|
||||||
}
|
// Clear pending request
|
||||||
return state.set("currentNotebookParentElements", parentEltsMap);
|
return state.set("notebookSnapshot", typedAction.payload).set("pendingSnapshotRequest", undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
case cdbActions.TAKE_NOTEBOOK_SNAPSHOT: {
|
||||||
|
const typedAction = action as cdbActions.TakeNotebookSnapshotAction;
|
||||||
|
// Clear previous snapshots
|
||||||
|
return state
|
||||||
|
.set("cellOutputSnapshots", new Map())
|
||||||
|
.set("notebookSnapshot", undefined)
|
||||||
|
.set("notebookSnapshotError", undefined)
|
||||||
|
.set("pendingSnapshotRequest", typedAction.payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
case cdbActions.NOTEBOOK_SNAPSHOT_ERROR: {
|
||||||
|
const typedAction = action as cdbActions.NotebookSnapshotErrorAction;
|
||||||
|
return state.set("notebookSnapshotError", typedAction.payload.error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
|
|||||||
@@ -1,15 +1,40 @@
|
|||||||
import * as Immutable from "immutable";
|
|
||||||
import { AppState, ContentRef } from "@nteract/core";
|
|
||||||
|
|
||||||
import { Notebook } from "../../../Common/Constants";
|
|
||||||
import { CellId } from "@nteract/commutable";
|
import { CellId } from "@nteract/commutable";
|
||||||
|
import { AppState } from "@nteract/core";
|
||||||
|
import * as Immutable from "immutable";
|
||||||
|
import { Notebook } from "../../../Common/Constants";
|
||||||
|
|
||||||
|
export interface SnapshotFragment {
|
||||||
|
image: HTMLImageElement;
|
||||||
|
boundingClientRect: DOMRect;
|
||||||
|
requestId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SnapshotRequest = NotebookSnapshotRequest | CellSnapshotRequest;
|
||||||
|
interface NotebookSnapshotRequestBase {
|
||||||
|
requestId: string;
|
||||||
|
aspectRatio: number;
|
||||||
|
notebookContentRef: string; // notebook redux contentRef
|
||||||
|
downloadFilename?: string; // Optional: will download as a file
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NotebookSnapshotRequest extends NotebookSnapshotRequestBase {
|
||||||
|
type: "notebook";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CellSnapshotRequest extends NotebookSnapshotRequestBase {
|
||||||
|
type: "celloutput";
|
||||||
|
cellId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CdbRecordProps {
|
export interface CdbRecordProps {
|
||||||
databaseAccountName: string | undefined;
|
databaseAccountName: string | undefined;
|
||||||
defaultExperience: string | undefined;
|
defaultExperience: string | undefined;
|
||||||
kernelRestartDelayMs: number;
|
kernelRestartDelayMs: number;
|
||||||
hoveredCellId: CellId | undefined;
|
hoveredCellId: CellId | undefined;
|
||||||
currentNotebookParentElements: Map<ContentRef, HTMLElement>;
|
cellOutputSnapshots: Map<string, SnapshotFragment>;
|
||||||
|
notebookSnapshot?: { imageSrc: string; requestId: string };
|
||||||
|
pendingSnapshotRequest?: SnapshotRequest;
|
||||||
|
notebookSnapshotError?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CdbRecord = Immutable.RecordOf<CdbRecordProps>;
|
export type CdbRecord = Immutable.RecordOf<CdbRecordProps>;
|
||||||
@@ -23,5 +48,8 @@ export const makeCdbRecord = Immutable.Record<CdbRecordProps>({
|
|||||||
defaultExperience: undefined,
|
defaultExperience: undefined,
|
||||||
kernelRestartDelayMs: Notebook.kernelRestartInitialDelayMs,
|
kernelRestartDelayMs: Notebook.kernelRestartInitialDelayMs,
|
||||||
hoveredCellId: undefined,
|
hoveredCellId: undefined,
|
||||||
currentNotebookParentElements: new Map<ContentRef, HTMLElement>(),
|
cellOutputSnapshots: new Map(),
|
||||||
|
notebookSnapshot: undefined,
|
||||||
|
pendingSnapshotRequest: undefined,
|
||||||
|
notebookSnapshotError: undefined,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { CopyNotebookPane } from "../Panes/CopyNotebookPane/CopyNotebookPane";
|
|||||||
import { PublishNotebookPane } from "../Panes/PublishNotebookPane/PublishNotebookPane";
|
import { PublishNotebookPane } from "../Panes/PublishNotebookPane/PublishNotebookPane";
|
||||||
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
|
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
|
||||||
import { NotebookContentProvider } from "./NotebookComponent/NotebookContentProvider";
|
import { NotebookContentProvider } from "./NotebookComponent/NotebookContentProvider";
|
||||||
|
import { SnapshotRequest } from "./NotebookComponent/types";
|
||||||
import { NotebookContainerClient } from "./NotebookContainerClient";
|
import { NotebookContainerClient } from "./NotebookContainerClient";
|
||||||
import { NotebookContentClient } from "./NotebookContentClient";
|
import { NotebookContentClient } from "./NotebookContentClient";
|
||||||
|
|
||||||
@@ -112,11 +113,13 @@ export default class NotebookManager {
|
|||||||
public async openPublishNotebookPane(
|
public async openPublishNotebookPane(
|
||||||
name: string,
|
name: string,
|
||||||
content: NotebookPaneContent,
|
content: NotebookPaneContent,
|
||||||
parentDomElement: HTMLElement
|
notebookContentRef: string,
|
||||||
|
onTakeSnapshot: (request: SnapshotRequest) => void,
|
||||||
|
onClosePanel: () => void
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const explorer = this.params.container;
|
const explorer = this.params.container;
|
||||||
explorer.openSidePanel(
|
explorer.openSidePanel(
|
||||||
"New Collection",
|
"Publish Notebook",
|
||||||
<PublishNotebookPane
|
<PublishNotebookPane
|
||||||
explorer={this.params.container}
|
explorer={this.params.container}
|
||||||
junoClient={this.junoClient}
|
junoClient={this.junoClient}
|
||||||
@@ -125,8 +128,10 @@ export default class NotebookManager {
|
|||||||
name={name}
|
name={name}
|
||||||
author={getFullName()}
|
author={getFullName()}
|
||||||
notebookContent={content}
|
notebookContent={content}
|
||||||
parentDomElement={parentDomElement}
|
notebookContentRef={notebookContentRef}
|
||||||
/>
|
onTakeSnapshot={onTakeSnapshot}
|
||||||
|
/>,
|
||||||
|
onClosePanel
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { CellId } from "@nteract/commutable";
|
import { CellId } from "@nteract/commutable";
|
||||||
import { CellType } from "@nteract/commutable/src";
|
import { CellType } from "@nteract/commutable/src";
|
||||||
import { actions, ContentRef } from "@nteract/core";
|
import { actions, ContentRef, selectors } from "@nteract/core";
|
||||||
import { Cells, CodeCell, RawCell } from "@nteract/stateful-components";
|
import { Cells, CodeCell, RawCell } from "@nteract/stateful-components";
|
||||||
import MonacoEditor from "@nteract/stateful-components/lib/inputs/connected-editors/monacoEditor";
|
import MonacoEditor from "@nteract/stateful-components/lib/inputs/connected-editors/monacoEditor";
|
||||||
import { PassedEditorProps } from "@nteract/stateful-components/lib/inputs/editor";
|
import { PassedEditorProps } from "@nteract/stateful-components/lib/inputs/editor";
|
||||||
@@ -12,6 +12,8 @@ import { Dispatch } from "redux";
|
|||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import * as cdbActions from "../NotebookComponent/actions";
|
import * as cdbActions from "../NotebookComponent/actions";
|
||||||
import loadTransform from "../NotebookComponent/loadTransform";
|
import loadTransform from "../NotebookComponent/loadTransform";
|
||||||
|
import { CdbAppState, SnapshotFragment, SnapshotRequest } from "../NotebookComponent/types";
|
||||||
|
import { NotebookUtil } from "../NotebookUtil";
|
||||||
import { AzureTheme } from "./AzureTheme";
|
import { AzureTheme } from "./AzureTheme";
|
||||||
import "./base.css";
|
import "./base.css";
|
||||||
import CellCreator from "./decorators/CellCreator";
|
import CellCreator from "./decorators/CellCreator";
|
||||||
@@ -32,10 +34,18 @@ export interface NotebookRendererBaseProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface NotebookRendererDispatchProps {
|
interface NotebookRendererDispatchProps {
|
||||||
updateNotebookParentDomElt: (contentRef: ContentRef, parentElt: HTMLElement) => void;
|
storeNotebookSnapshot: (imageSrc: string, requestId: string) => void;
|
||||||
|
notebookSnapshotError: (error: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type NotebookRendererProps = NotebookRendererBaseProps & NotebookRendererDispatchProps;
|
interface StateProps {
|
||||||
|
pendingSnapshotRequest: SnapshotRequest;
|
||||||
|
cellOutputSnapshots: Map<string, SnapshotFragment>;
|
||||||
|
notebookSnapshot: { imageSrc: string; requestId: string };
|
||||||
|
nbCodeCells: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotebookRendererProps = NotebookRendererBaseProps & NotebookRendererDispatchProps & StateProps;
|
||||||
|
|
||||||
const decorate = (id: string, contentRef: ContentRef, cell_type: CellType, children: React.ReactNode) => {
|
const decorate = (id: string, contentRef: ContentRef, cell_type: CellType, children: React.ReactNode) => {
|
||||||
const Cell = () => (
|
const Cell = () => (
|
||||||
@@ -60,27 +70,37 @@ const decorate = (id: string, contentRef: ContentRef, cell_type: CellType, child
|
|||||||
class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
|
class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
|
||||||
private notebookRendererRef = React.createRef<HTMLDivElement>();
|
private notebookRendererRef = React.createRef<HTMLDivElement>();
|
||||||
|
|
||||||
constructor(props: NotebookRendererProps) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
hoveredCellId: undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (!userContext.features.sandboxNotebookOutputs) {
|
if (!userContext.features.sandboxNotebookOutputs) {
|
||||||
loadTransform(this.props as any);
|
loadTransform(this.props as any);
|
||||||
}
|
}
|
||||||
this.props.updateNotebookParentDomElt(this.props.contentRef, this.notebookRendererRef.current);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
async componentDidUpdate(): Promise<void> {
|
||||||
this.props.updateNotebookParentDomElt(this.props.contentRef, this.notebookRendererRef.current);
|
// Take a snapshot if there's a pending request and all the outputs are also saved
|
||||||
}
|
if (
|
||||||
|
this.props.pendingSnapshotRequest &&
|
||||||
componentWillUnmount() {
|
this.props.pendingSnapshotRequest.type === "notebook" &&
|
||||||
this.props.updateNotebookParentDomElt(this.props.contentRef, undefined);
|
this.props.pendingSnapshotRequest.notebookContentRef === this.props.contentRef &&
|
||||||
|
(!this.props.notebookSnapshot ||
|
||||||
|
this.props.pendingSnapshotRequest.requestId !== this.props.notebookSnapshot.requestId) &&
|
||||||
|
this.props.cellOutputSnapshots.size === this.props.nbCodeCells
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// Use Html2Canvas because it is much more reliable and fast than dom-to-file
|
||||||
|
const result = await NotebookUtil.takeScreenshotHtml2Canvas(
|
||||||
|
this.notebookRendererRef.current,
|
||||||
|
this.props.pendingSnapshotRequest.aspectRatio,
|
||||||
|
[...this.props.cellOutputSnapshots.values()],
|
||||||
|
this.props.pendingSnapshotRequest.downloadFilename
|
||||||
|
);
|
||||||
|
this.props.storeNotebookSnapshot(result.imageSrc, this.props.pendingSnapshotRequest.requestId);
|
||||||
|
} catch (error) {
|
||||||
|
this.props.notebookSnapshotError(error.message);
|
||||||
|
} finally {
|
||||||
|
this.setState({ processedSnapshotRequest: undefined });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
@@ -156,28 +176,40 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const makeMapStateToProps = (
|
||||||
|
initialState: CdbAppState,
|
||||||
|
ownProps: NotebookRendererProps
|
||||||
|
): ((state: CdbAppState) => StateProps) => {
|
||||||
|
const mapStateToProps = (state: CdbAppState): StateProps => {
|
||||||
|
const { contentRef } = ownProps;
|
||||||
|
const model = selectors.model(state, { contentRef });
|
||||||
|
|
||||||
|
let nbCodeCells;
|
||||||
|
if (model && model.type === "notebook") {
|
||||||
|
nbCodeCells = NotebookUtil.findCodeCellWithDisplay(model.notebook).length;
|
||||||
|
}
|
||||||
|
const { pendingSnapshotRequest, cellOutputSnapshots, notebookSnapshot } = state.cdb;
|
||||||
|
return { pendingSnapshotRequest, cellOutputSnapshots, notebookSnapshot, nbCodeCells };
|
||||||
|
};
|
||||||
|
return mapStateToProps;
|
||||||
|
};
|
||||||
|
|
||||||
const makeMapDispatchToProps = (initialDispatch: Dispatch, initialProps: NotebookRendererBaseProps) => {
|
const makeMapDispatchToProps = (initialDispatch: Dispatch, initialProps: NotebookRendererBaseProps) => {
|
||||||
const mapDispatchToProps = (dispatch: Dispatch) => {
|
const mapDispatchToProps = (dispatch: Dispatch) => {
|
||||||
return {
|
return {
|
||||||
addTransform: (transform: React.ComponentType & { MIMETYPE: string }) => {
|
addTransform: (transform: React.ComponentType & { MIMETYPE: string }) =>
|
||||||
return dispatch(
|
dispatch(
|
||||||
actions.addTransform({
|
actions.addTransform({
|
||||||
mediaType: transform.MIMETYPE,
|
mediaType: transform.MIMETYPE,
|
||||||
component: transform,
|
component: transform,
|
||||||
})
|
})
|
||||||
);
|
),
|
||||||
},
|
storeNotebookSnapshot: (imageSrc: string, requestId: string) =>
|
||||||
updateNotebookParentDomElt: (contentRef: ContentRef, parentElt: HTMLElement) => {
|
dispatch(cdbActions.storeNotebookSnapshot({ imageSrc, requestId })),
|
||||||
return dispatch(
|
notebookSnapshotError: (error: string) => dispatch(cdbActions.notebookSnapshotError({ error })),
|
||||||
cdbActions.UpdateNotebookParentDomElt({
|
|
||||||
contentRef,
|
|
||||||
parentElt,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
return mapDispatchToProps;
|
return mapDispatchToProps;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(null, makeMapDispatchToProps)(BaseNotebookRenderer);
|
export default connect(makeMapStateToProps, makeMapDispatchToProps)(BaseNotebookRenderer);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ContextualMenuItemType, DirectionalHint, IconButton, IContextualMenuItem } from "@fluentui/react";
|
import { ContextualMenuItemType, DirectionalHint, IconButton, IContextualMenuItem } from "@fluentui/react";
|
||||||
import { CellId, CellType } from "@nteract/commutable";
|
import { CellId, CellType, ImmutableCodeCell } from "@nteract/commutable";
|
||||||
import { actions, AppState, DocumentRecordProps } from "@nteract/core";
|
import { actions, AppState, DocumentRecordProps } from "@nteract/core";
|
||||||
import * as selectors from "@nteract/selectors";
|
import * as selectors from "@nteract/selectors";
|
||||||
import { CellToolbarContext } from "@nteract/stateful-components";
|
import { CellToolbarContext } from "@nteract/stateful-components";
|
||||||
@@ -10,6 +10,8 @@ import { connect } from "react-redux";
|
|||||||
import { Dispatch } from "redux";
|
import { Dispatch } from "redux";
|
||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as cdbActions from "../NotebookComponent/actions";
|
import * as cdbActions from "../NotebookComponent/actions";
|
||||||
|
import { SnapshotRequest } from "../NotebookComponent/types";
|
||||||
|
import { NotebookUtil } from "../NotebookUtil";
|
||||||
|
|
||||||
export interface ComponentProps {
|
export interface ComponentProps {
|
||||||
contentRef: ContentRef;
|
contentRef: ContentRef;
|
||||||
@@ -26,12 +28,14 @@ interface DispatchProps {
|
|||||||
clearOutputs: () => void;
|
clearOutputs: () => void;
|
||||||
deleteCell: () => void;
|
deleteCell: () => void;
|
||||||
traceNotebookTelemetry: (action: Action, actionModifier?: string, data?: any) => void;
|
traceNotebookTelemetry: (action: Action, actionModifier?: string, data?: any) => void;
|
||||||
|
takeNotebookSnapshot: (payload: SnapshotRequest) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StateProps {
|
interface StateProps {
|
||||||
cellType: CellType;
|
cellType: CellType;
|
||||||
cellIdAbove: CellId;
|
cellIdAbove: CellId;
|
||||||
cellIdBelow: CellId;
|
cellIdBelow: CellId;
|
||||||
|
hasCodeOutput: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BaseToolbar extends React.PureComponent<ComponentProps & DispatchProps & StateProps> {
|
class BaseToolbar extends React.PureComponent<ComponentProps & DispatchProps & StateProps> {
|
||||||
@@ -58,11 +62,29 @@ class BaseToolbar extends React.PureComponent<ComponentProps & DispatchProps & S
|
|||||||
this.props.traceNotebookTelemetry(Action.NotebooksClearOutputsFromMenu, ActionModifiers.Mark);
|
this.props.traceNotebookTelemetry(Action.NotebooksClearOutputsFromMenu, ActionModifiers.Mark);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: "Divider",
|
|
||||||
itemType: ContextualMenuItemType.Divider,
|
|
||||||
},
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (this.props.hasCodeOutput) {
|
||||||
|
items.push({
|
||||||
|
key: "Export output to image",
|
||||||
|
text: "Export output to image",
|
||||||
|
onClick: () => {
|
||||||
|
this.props.takeNotebookSnapshot({
|
||||||
|
requestId: new Date().getTime().toString(),
|
||||||
|
aspectRatio: undefined,
|
||||||
|
type: "celloutput",
|
||||||
|
cellId: this.props.id,
|
||||||
|
notebookContentRef: this.props.contentRef,
|
||||||
|
downloadFilename: `celloutput-${this.props.contentRef}_${this.props.id}.png`,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
key: "Divider",
|
||||||
|
itemType: ContextualMenuItemType.Divider,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
items = items.concat([
|
items = items.concat([
|
||||||
@@ -183,12 +205,13 @@ const mapDispatchToProps = (
|
|||||||
deleteCell: () => dispatch(actions.deleteCell({ id, contentRef })),
|
deleteCell: () => dispatch(actions.deleteCell({ id, contentRef })),
|
||||||
traceNotebookTelemetry: (action: Action, actionModifier?: string, data?: any) =>
|
traceNotebookTelemetry: (action: Action, actionModifier?: string, data?: any) =>
|
||||||
dispatch(cdbActions.traceNotebookTelemetry({ action, actionModifier, data })),
|
dispatch(cdbActions.traceNotebookTelemetry({ action, actionModifier, data })),
|
||||||
|
takeNotebookSnapshot: (request: SnapshotRequest) => dispatch(cdbActions.takeNotebookSnapshot(request)),
|
||||||
});
|
});
|
||||||
|
|
||||||
const makeMapStateToProps = (state: AppState, ownProps: ComponentProps): ((state: AppState) => StateProps) => {
|
const makeMapStateToProps = (state: AppState, ownProps: ComponentProps): ((state: AppState) => StateProps) => {
|
||||||
const mapStateToProps = (state: AppState) => {
|
const mapStateToProps = (state: AppState) => {
|
||||||
const cellType = selectors.cell.cellFromState(state, { id: ownProps.id, contentRef: ownProps.contentRef })
|
const cell = selectors.cell.cellFromState(state, { id: ownProps.id, contentRef: ownProps.contentRef });
|
||||||
.cell_type;
|
const cellType = cell.cell_type;
|
||||||
const model = selectors.model(state, { contentRef: ownProps.contentRef });
|
const model = selectors.model(state, { contentRef: ownProps.contentRef });
|
||||||
const cellOrder = selectors.notebook.cellOrder(model as RecordOf<DocumentRecordProps>);
|
const cellOrder = selectors.notebook.cellOrder(model as RecordOf<DocumentRecordProps>);
|
||||||
const cellIndex = cellOrder.indexOf(ownProps.id);
|
const cellIndex = cellOrder.indexOf(ownProps.id);
|
||||||
@@ -199,6 +222,7 @@ const makeMapStateToProps = (state: AppState, ownProps: ComponentProps): ((state
|
|||||||
cellType,
|
cellType,
|
||||||
cellIdAbove,
|
cellIdAbove,
|
||||||
cellIdBelow,
|
cellIdBelow,
|
||||||
|
hasCodeOutput: cellType === "code" && NotebookUtil.hasCodeCellOutput(cell as ImmutableCodeCell),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
return mapStateToProps;
|
return mapStateToProps;
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import postRobot from "post-robot";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Dispatch } from "redux";
|
import { Dispatch } from "redux";
|
||||||
import { CellOutputViewerProps } from "../../../../CellOutputViewer/CellOutputViewer";
|
import { CellOutputViewerProps, SnapshotResponse } from "../../../../CellOutputViewer/CellOutputViewer";
|
||||||
|
import * as cdbActions from "../../NotebookComponent/actions";
|
||||||
|
import { CdbAppState, SnapshotFragment, SnapshotRequest } from "../../NotebookComponent/types";
|
||||||
|
|
||||||
// Adapted from https://github.com/nteract/nteract/blob/main/packages/stateful-components/src/outputs/index.tsx
|
// Adapted from https://github.com/nteract/nteract/blob/main/packages/stateful-components/src/outputs/index.tsx
|
||||||
// to add support for sandboxing using <iframe>
|
// to add support for sandboxing using <iframe>
|
||||||
@@ -24,27 +26,47 @@ interface StateProps {
|
|||||||
expanded: boolean;
|
expanded: boolean;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
outputs: Immutable.List<any>;
|
outputs: Immutable.List<any>;
|
||||||
|
|
||||||
|
pendingSnapshotRequest: SnapshotRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DispatchProps {
|
interface DispatchProps {
|
||||||
onMetadataChange?: (metadata: JSONObject, mediaType: string, index?: number) => void;
|
onMetadataChange?: (metadata: JSONObject, mediaType: string, index?: number) => void;
|
||||||
|
storeNotebookSnapshot: (imageSrc: string, requestId: string) => void;
|
||||||
|
storeSnapshotFragment: (cellId: string, snapshotFragment: SnapshotFragment) => void;
|
||||||
|
notebookSnapshotError: (error: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SandboxOutputs extends React.PureComponent<ComponentProps & StateProps & DispatchProps> {
|
type SandboxOutputsProps = ComponentProps & StateProps & DispatchProps;
|
||||||
|
|
||||||
|
export class SandboxOutputs extends React.Component<SandboxOutputsProps> {
|
||||||
private childWindow: Window;
|
private childWindow: Window;
|
||||||
|
private nodeRef = React.createRef<HTMLDivElement>();
|
||||||
|
|
||||||
|
constructor(props: SandboxOutputsProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
processedSnapshotRequest: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
// Using min-width to set the width of the iFrame, works around an issue in iOS that can prevent the iFrame from sizing correctly.
|
// Using min-width to set the width of the iFrame, works around an issue in iOS that can prevent the iFrame from sizing correctly.
|
||||||
return (
|
return this.props.outputs && this.props.outputs.size > 0 ? (
|
||||||
<IframeResizer
|
<div ref={this.nodeRef}>
|
||||||
checkOrigin={false}
|
<IframeResizer
|
||||||
loading="lazy"
|
checkOrigin={false}
|
||||||
heightCalculationMethod="taggedElement"
|
loading="lazy"
|
||||||
onLoad={(event) => this.handleFrameLoad(event)}
|
heightCalculationMethod="taggedElement"
|
||||||
src="./cellOutputViewer.html"
|
onLoad={(event) => this.handleFrameLoad(event)}
|
||||||
style={{ height: "1px", width: "1px", minWidth: "100%", border: "none" }}
|
src="./cellOutputViewer.html"
|
||||||
sandbox="allow-downloads allow-popups allow-forms allow-pointer-lock allow-scripts allow-popups-to-escape-sandbox"
|
style={{ height: "1px", width: "1px", minWidth: "100%", border: "none" }}
|
||||||
/>
|
sandbox="allow-downloads allow-popups allow-forms allow-pointer-lock allow-scripts allow-popups-to-escape-sandbox"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,8 +98,48 @@ export class SandboxOutputs extends React.PureComponent<ComponentProps & StatePr
|
|||||||
this.sendPropsToFrame();
|
this.sendPropsToFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(): void {
|
async componentDidUpdate(prevProps: SandboxOutputsProps): Promise<void> {
|
||||||
this.sendPropsToFrame();
|
this.sendPropsToFrame();
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.props.pendingSnapshotRequest &&
|
||||||
|
prevProps.pendingSnapshotRequest !== this.props.pendingSnapshotRequest &&
|
||||||
|
this.props.pendingSnapshotRequest.notebookContentRef === this.props.contentRef &&
|
||||||
|
this.nodeRef?.current
|
||||||
|
) {
|
||||||
|
const boundingClientRect = this.nodeRef.current.getBoundingClientRect();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = (await postRobot.send(
|
||||||
|
this.childWindow,
|
||||||
|
"snapshotRequest",
|
||||||
|
this.props.pendingSnapshotRequest
|
||||||
|
)) as { data: SnapshotResponse };
|
||||||
|
if (this.props.pendingSnapshotRequest.type === "notebook") {
|
||||||
|
if (data.imageSrc === undefined) {
|
||||||
|
this.props.storeSnapshotFragment(this.props.id, {
|
||||||
|
image: undefined,
|
||||||
|
boundingClientRect: boundingClientRect,
|
||||||
|
requestId: data.requestId,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const image = new Image();
|
||||||
|
image.src = data.imageSrc;
|
||||||
|
image.onload = () => {
|
||||||
|
this.props.storeSnapshotFragment(this.props.id, {
|
||||||
|
image,
|
||||||
|
boundingClientRect: boundingClientRect,
|
||||||
|
requestId: data.requestId,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
} else if (this.props.pendingSnapshotRequest.type === "celloutput") {
|
||||||
|
this.props.storeNotebookSnapshot(data.imageSrc, this.props.pendingSnapshotRequest.requestId);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.props.notebookSnapshotError(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +147,7 @@ export const makeMapStateToProps = (
|
|||||||
initialState: AppState,
|
initialState: AppState,
|
||||||
ownProps: ComponentProps
|
ownProps: ComponentProps
|
||||||
): ((state: AppState) => StateProps) => {
|
): ((state: AppState) => StateProps) => {
|
||||||
const mapStateToProps = (state: AppState): StateProps => {
|
const mapStateToProps = (state: CdbAppState): StateProps => {
|
||||||
let outputs = Immutable.List();
|
let outputs = Immutable.List();
|
||||||
let hidden = false;
|
let hidden = false;
|
||||||
let expanded = false;
|
let expanded = false;
|
||||||
@@ -102,7 +164,17 @@ export const makeMapStateToProps = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { outputs, hidden, expanded };
|
// Determine whether to take a snapshot or not
|
||||||
|
let pendingSnapshotRequest = state.cdb.pendingSnapshotRequest;
|
||||||
|
if (
|
||||||
|
pendingSnapshotRequest &&
|
||||||
|
pendingSnapshotRequest.type === "celloutput" &&
|
||||||
|
pendingSnapshotRequest.cellId !== id
|
||||||
|
) {
|
||||||
|
pendingSnapshotRequest = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { outputs, hidden, expanded, pendingSnapshotRequest };
|
||||||
};
|
};
|
||||||
return mapStateToProps;
|
return mapStateToProps;
|
||||||
};
|
};
|
||||||
@@ -125,6 +197,11 @@ export const makeMapDispatchToProps = (
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
storeSnapshotFragment: (cellId: string, snapshot: SnapshotFragment) =>
|
||||||
|
dispatch(cdbActions.storeCellOutputSnapshot({ cellId, snapshot })),
|
||||||
|
storeNotebookSnapshot: (imageSrc: string, requestId: string) =>
|
||||||
|
dispatch(cdbActions.storeNotebookSnapshot({ imageSrc, requestId })),
|
||||||
|
notebookSnapshotError: (error: string) => dispatch(cdbActions.notebookSnapshotError({ error })),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
return mapDispatchToProps;
|
return mapDispatchToProps;
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { NotebookUtil } from "./NotebookUtil";
|
|
||||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
|
||||||
import {
|
import {
|
||||||
ImmutableNotebook,
|
|
||||||
MediaBundle,
|
|
||||||
CodeCellParams,
|
CodeCellParams,
|
||||||
MarkdownCellParams,
|
ImmutableNotebook,
|
||||||
makeCodeCell,
|
makeCodeCell,
|
||||||
makeMarkdownCell,
|
makeMarkdownCell,
|
||||||
makeNotebookRecord,
|
makeNotebookRecord,
|
||||||
|
MarkdownCellParams,
|
||||||
|
MediaBundle,
|
||||||
} from "@nteract/commutable";
|
} from "@nteract/commutable";
|
||||||
import { List, Map } from "immutable";
|
import { List, Map } from "immutable";
|
||||||
|
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||||
|
import { NotebookUtil } from "./NotebookUtil";
|
||||||
|
|
||||||
const fileName = "file";
|
const fileName = "file";
|
||||||
const notebookName = "file.ipynb";
|
const notebookName = "file.ipynb";
|
||||||
@@ -131,7 +131,7 @@ describe("NotebookUtil", () => {
|
|||||||
describe("findFirstCodeCellWithDisplay", () => {
|
describe("findFirstCodeCellWithDisplay", () => {
|
||||||
it("works for Notebook file", () => {
|
it("works for Notebook file", () => {
|
||||||
const notebookObject = notebookRecord as ImmutableNotebook;
|
const notebookObject = notebookRecord as ImmutableNotebook;
|
||||||
expect(NotebookUtil.findFirstCodeCellWithDisplay(notebookObject)).toEqual(1);
|
expect(NotebookUtil.findCodeCellWithDisplay(notebookObject)[0]).toEqual("1");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import { ImmutableCodeCell, ImmutableNotebook } from "@nteract/commutable";
|
||||||
|
import domtoimage from "dom-to-image";
|
||||||
|
import Html2Canvas from "html2canvas";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { ImmutableNotebook, ImmutableCodeCell } from "@nteract/commutable";
|
|
||||||
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
|
|
||||||
import * as StringUtils from "../../Utils/StringUtils";
|
|
||||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||||
|
import * as StringUtils from "../../Utils/StringUtils";
|
||||||
|
import { SnapshotFragment } from "./NotebookComponent/types";
|
||||||
|
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
|
||||||
|
|
||||||
// Must match rx-jupyter' FileType
|
// Must match rx-jupyter' FileType
|
||||||
export type FileType = "directory" | "file" | "notebook";
|
export type FileType = "directory" | "file" | "notebook";
|
||||||
@@ -141,23 +144,175 @@ export class NotebookUtil {
|
|||||||
return `${basePath}${newName}`;
|
return `${basePath}${newName}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static findFirstCodeCellWithDisplay(notebookObject: ImmutableNotebook): number {
|
public static hasCodeCellOutput(cell: ImmutableCodeCell): boolean {
|
||||||
let codeCellIndex = 0;
|
return !!cell?.outputs?.find(
|
||||||
for (let i = 0; i < notebookObject.cellOrder.size; i++) {
|
(output) =>
|
||||||
const cellId = notebookObject.cellOrder.get(i);
|
output.output_type === "display_data" ||
|
||||||
if (cellId) {
|
output.output_type === "execute_result" ||
|
||||||
const cell = notebookObject.cellMap.get(cellId);
|
output.output_type === "stream"
|
||||||
if (cell?.cell_type === "code") {
|
);
|
||||||
const displayOutput = (cell as ImmutableCodeCell)?.outputs?.find(
|
}
|
||||||
(output) => output.output_type === "display_data" || output.output_type === "execute_result"
|
|
||||||
);
|
/**
|
||||||
if (displayOutput) {
|
* Find code cells with display
|
||||||
return codeCellIndex;
|
* @param notebookObject
|
||||||
}
|
* @returns array of cell ids
|
||||||
codeCellIndex++;
|
*/
|
||||||
|
public static findCodeCellWithDisplay(notebookObject: ImmutableNotebook): string[] {
|
||||||
|
return notebookObject.cellOrder.reduce((accumulator: string[], cellId) => {
|
||||||
|
const cell = notebookObject.cellMap.get(cellId);
|
||||||
|
if (cell?.cell_type === "code") {
|
||||||
|
if (NotebookUtil.hasCodeCellOutput(cell as ImmutableCodeCell)) {
|
||||||
|
accumulator.push(cellId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return accumulator;
|
||||||
throw new Error("Output does not exist for any of the cells.");
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static takeScreenshotHtml2Canvas = (
|
||||||
|
target: HTMLElement,
|
||||||
|
aspectRatio: number,
|
||||||
|
subSnapshots: SnapshotFragment[],
|
||||||
|
downloadFilename?: string
|
||||||
|
): Promise<{ imageSrc: string | undefined }> => {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
// target.scrollIntoView();
|
||||||
|
const canvas = await Html2Canvas(target, {
|
||||||
|
useCORS: true,
|
||||||
|
allowTaint: true,
|
||||||
|
scale: 1,
|
||||||
|
logging: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
//redraw canvas to fit aspect ratio
|
||||||
|
const originalImageData = canvas.toDataURL();
|
||||||
|
const width = parseInt(canvas.style.width.split("px")[0]);
|
||||||
|
if (aspectRatio) {
|
||||||
|
canvas.height = width * aspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (originalImageData === "data:,") {
|
||||||
|
// Empty output
|
||||||
|
resolve({ imageSrc: undefined });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const context = canvas.getContext("2d");
|
||||||
|
const image = new Image();
|
||||||
|
image.src = originalImageData;
|
||||||
|
image.onload = () => {
|
||||||
|
if (!context) {
|
||||||
|
reject(new Error("No context to draw on"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context.drawImage(image, 0, 0);
|
||||||
|
|
||||||
|
// draw sub images
|
||||||
|
if (subSnapshots) {
|
||||||
|
const parentRect = target.getBoundingClientRect();
|
||||||
|
subSnapshots.forEach((snapshot) => {
|
||||||
|
if (snapshot.image) {
|
||||||
|
context.drawImage(
|
||||||
|
snapshot.image,
|
||||||
|
snapshot.boundingClientRect.x - parentRect.x,
|
||||||
|
snapshot.boundingClientRect.y - parentRect.y
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve({ imageSrc: canvas.toDataURL() });
|
||||||
|
|
||||||
|
if (downloadFilename) {
|
||||||
|
NotebookUtil.downloadFile(
|
||||||
|
downloadFilename,
|
||||||
|
canvas.toDataURL("image/png").replace("image/png", "image/octet-stream")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
public static takeScreenshotDomToImage = (
|
||||||
|
target: HTMLElement,
|
||||||
|
aspectRatio: number,
|
||||||
|
subSnapshots: SnapshotFragment[],
|
||||||
|
downloadFilename?: string
|
||||||
|
): Promise<{ imageSrc?: string }> => {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
// target.scrollIntoView();
|
||||||
|
try {
|
||||||
|
const filter = (node: Node): boolean => {
|
||||||
|
const excludedList = ["IMG", "CANVAS"];
|
||||||
|
return !excludedList.includes((node as HTMLElement).tagName);
|
||||||
|
};
|
||||||
|
|
||||||
|
const originalImageData = await domtoimage.toPng(target, { filter });
|
||||||
|
if (originalImageData === "data:,") {
|
||||||
|
// Empty output
|
||||||
|
resolve({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseImage = new Image();
|
||||||
|
baseImage.src = originalImageData;
|
||||||
|
baseImage.onload = () => {
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = baseImage.width;
|
||||||
|
canvas.height = aspectRatio !== undefined ? baseImage.width * aspectRatio : baseImage.width;
|
||||||
|
|
||||||
|
const context = canvas.getContext("2d");
|
||||||
|
if (!context) {
|
||||||
|
reject(new Error("No Canvas to draw on"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// White background otherwise image is transparent
|
||||||
|
context.fillStyle = "white";
|
||||||
|
context.fillRect(0, 0, baseImage.width, baseImage.height);
|
||||||
|
|
||||||
|
context.drawImage(baseImage, 0, 0);
|
||||||
|
|
||||||
|
// draw sub images
|
||||||
|
if (subSnapshots) {
|
||||||
|
const parentRect = target.getBoundingClientRect();
|
||||||
|
subSnapshots.forEach((snapshot) => {
|
||||||
|
if (snapshot.image) {
|
||||||
|
context.drawImage(
|
||||||
|
snapshot.image,
|
||||||
|
snapshot.boundingClientRect.x - parentRect.x,
|
||||||
|
snapshot.boundingClientRect.y - parentRect.y
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve({ imageSrc: canvas.toDataURL() });
|
||||||
|
|
||||||
|
if (downloadFilename) {
|
||||||
|
NotebookUtil.downloadFile(
|
||||||
|
downloadFilename,
|
||||||
|
canvas.toDataURL("image/png").replace("image/png", "image/octet-stream")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private static downloadFile(filename: string, content: string): void {
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = content;
|
||||||
|
link.download = filename;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,602 +0,0 @@
|
|||||||
<div data-bind="visible: visible, event: { keydown: onPaneKeyDown }">
|
|
||||||
<div class="contextual-pane-out" data-bind="setTemplateReady: true, click: cancel, clickBubble: false"></div>
|
|
||||||
<div class="contextual-pane" data-bind="attr: { id: id }">
|
|
||||||
<!-- Add collection form -- Start -->
|
|
||||||
<div class="contextual-pane-in">
|
|
||||||
<form data-bind="submit: submit" style="height: 100%">
|
|
||||||
<div
|
|
||||||
class="paneContentContainer"
|
|
||||||
role="dialog"
|
|
||||||
aria-labelledby="containerTitle"
|
|
||||||
data-bind="template: { name: 'add-collection-inputs' }"
|
|
||||||
></div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<!-- Add collection form -- End -->
|
|
||||||
<!-- Loader - Start -->
|
|
||||||
<div class="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer" data-bind="visible: isExecuting">
|
|
||||||
<img class="dataExplorerLoader" src="/images/LoadingIndicator_3Squares.gif" />
|
|
||||||
</div>
|
|
||||||
<!-- Loader - End -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script type="text/html" id="add-collection-inputs">
|
|
||||||
<!-- Add collection header - Start -->
|
|
||||||
<div class="firstdivbg headerline">
|
|
||||||
<span id="containerTitle" role="heading" aria-level="2" data-bind="text: title"></span>
|
|
||||||
<div
|
|
||||||
class="closeImg"
|
|
||||||
id="closeBtnAddCollection"
|
|
||||||
role="button"
|
|
||||||
aria-label="Add collection close pane"
|
|
||||||
data-bind="click: cancel, event: { keypress: onCloseKeyPress }"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<img src="/images/close-black.svg" title="Close" alt="Close" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Add collection header - End -->
|
|
||||||
|
|
||||||
<!-- Add collection errors - Start -->
|
|
||||||
<div class="warningErrorContainer" aria-live="assertive" data-bind="visible: formErrors() && formErrors() !== ''">
|
|
||||||
<div class="warningErrorContent">
|
|
||||||
<span><img class="paneErrorIcon" src="/images/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>
|
|
||||||
<div class="warningErrorContainer" aria-live="assertive" data-bind="visible: formWarnings() && formWarnings() !== ''">
|
|
||||||
<div class="warningErrorContent">
|
|
||||||
<span><img class="paneErrorIcon" src="/images/warning.svg" alt="Warning" /></span>
|
|
||||||
<span class="warningErrorDetailsLinkContainer">
|
|
||||||
<span class="formErrors" data-bind="text: formWarnings, attr: { title: formWarnings }"></span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Add collection 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="/images/info_color.svg" alt="Promo" /></span>
|
|
||||||
<span class="infoBoxDetails">
|
|
||||||
<span class="infoBoxMessage" data-bind="text: upsellMessage, attr: { title: upsellMessage }"></span>
|
|
||||||
<a
|
|
||||||
class="underlinedLink"
|
|
||||||
id="linkAddCollection"
|
|
||||||
data-bind="text: upsellAnchorText, attr: { 'href': upsellAnchorUrl, 'aria-label': upsellMessageAriaLabel }"
|
|
||||||
target="_blank"
|
|
||||||
href=""
|
|
||||||
tabindex="0"
|
|
||||||
></a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- upsell message - end -->
|
|
||||||
|
|
||||||
<!-- Add collection inputs - Start -->
|
|
||||||
<div class="paneMainContent" data-bind="visible: !maxCollectionsReached()">
|
|
||||||
<div data-bind="visible: !isPreferredApiTable()">
|
|
||||||
<p>
|
|
||||||
<span class="mandatoryStar">*</span>
|
|
||||||
<span class="addCollectionLabel">Database id</span>
|
|
||||||
<span class="infoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="/images/info-bubble.svg" alt="More information" />
|
|
||||||
<span class="tooltiptext infoTooltipWidth"
|
|
||||||
>A database is analogous to a namespace. It is the unit of management for a set of containers.</span
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="createNewDatabaseOrUseExisting">
|
|
||||||
<input
|
|
||||||
class="createNewDatabaseOrUseExistingRadio"
|
|
||||||
aria-label="Create new database"
|
|
||||||
name="databaseType"
|
|
||||||
type="radio"
|
|
||||||
role="radio"
|
|
||||||
id="databaseCreateNew"
|
|
||||||
data-test="addCollection-createNewDatabase"
|
|
||||||
tabindex="0"
|
|
||||||
data-bind="checked: databaseCreateNew, checkedValue: true, attr: { 'aria-checked': databaseCreateNew() ? 'true' : 'false' }"
|
|
||||||
/>
|
|
||||||
<span class="createNewDatabaseOrUseExistingSpace" for="databaseCreateNew">Create new</span>
|
|
||||||
|
|
||||||
<input
|
|
||||||
class="createNewDatabaseOrUseExistingRadio"
|
|
||||||
aria-label="Use existing database"
|
|
||||||
name="databaseType"
|
|
||||||
type="radio"
|
|
||||||
role="radio"
|
|
||||||
id="databaseUseExisting"
|
|
||||||
data-test="addCollection-existingDatabase"
|
|
||||||
tabindex="0"
|
|
||||||
data-bind="checked: databaseCreateNew, checkedValue: false, attr: { 'aria-checked': !databaseCreateNew() ? 'true' : 'false' }"
|
|
||||||
/>
|
|
||||||
<span class="createNewDatabaseOrUseExistingSpace" for="databaseUseExisting">Use existing</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input
|
|
||||||
name="newDatabaseId"
|
|
||||||
id="databaseId"
|
|
||||||
data-test="addCollection-newDatabaseId"
|
|
||||||
aria-required="true"
|
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
pattern="[^/?#\\]*[^/?# \\]"
|
|
||||||
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
|
||||||
placeholder="Type a new database id"
|
|
||||||
size="40"
|
|
||||||
class="collid"
|
|
||||||
data-bind="visible: databaseCreateNew, textInput: databaseId, hasFocus: firstFieldHasFocus"
|
|
||||||
autofocus
|
|
||||||
/>
|
|
||||||
|
|
||||||
<input
|
|
||||||
name="existingDatabaseId"
|
|
||||||
id="existingDatabaseId"
|
|
||||||
data-test="addCollection-existingDatabaseId"
|
|
||||||
aria-required="true"
|
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
pattern="[^/?#\\]*[^/?# \\]"
|
|
||||||
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
|
||||||
list="databasesList"
|
|
||||||
placeholder="Choose an existing database"
|
|
||||||
size="40"
|
|
||||||
class="collid"
|
|
||||||
data-bind="visible: !databaseCreateNew(), textInput: databaseId, hasFocus: firstFieldHasFocus"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<datalist id="databasesList" data-bind="foreach: databaseIds" data-bind="visible: databaseCreateNew">
|
|
||||||
<option data-bind="value: $data"></option>
|
|
||||||
</datalist>
|
|
||||||
|
|
||||||
<!-- Database provisioned throughput - Start -->
|
|
||||||
<!-- ko if: canConfigureThroughput -->
|
|
||||||
<div class="databaseProvision" aria-label="Provision database throughput" data-bind="visible: databaseCreateNew">
|
|
||||||
<input
|
|
||||||
tabindex="0"
|
|
||||||
type="checkbox"
|
|
||||||
data-test="addCollectionPane-databaseSharedThroughput"
|
|
||||||
id="addCollection-databaseSharedThroughput"
|
|
||||||
title="Provision database throughput"
|
|
||||||
data-bind="checked: databaseCreateNewShared"
|
|
||||||
/>
|
|
||||||
<span class="databaseProvisionText" for="databaseSharedThroughput">Provision database throughput</span>
|
|
||||||
<span class="infoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="/images/info-bubble.svg" alt="More information" />
|
|
||||||
<span class="tooltiptext provisionDatabaseThroughput"
|
|
||||||
>Provisioned throughput at the database level will be shared across all containers within the
|
|
||||||
database.</span
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div data-bind="visible: databaseCreateNewShared() && databaseCreateNew()">
|
|
||||||
<!-- 1 -->
|
|
||||||
<throughput-input-autopilot-v3
|
|
||||||
params="{
|
|
||||||
testId: 'databaseThroughputValue',
|
|
||||||
value: throughputDatabase,
|
|
||||||
minimum: minThroughputRU,
|
|
||||||
maximum: maxThroughputRU,
|
|
||||||
isEnabled: databaseCreateNewShared() && databaseCreateNew(),
|
|
||||||
label: sharedThroughputRangeText,
|
|
||||||
ariaLabel: sharedThroughputRangeText,
|
|
||||||
costsVisible: costsVisible,
|
|
||||||
requestUnitsUsageCost: requestUnitsUsageCost,
|
|
||||||
spendAckChecked: throughputSpendAck,
|
|
||||||
spendAckId: 'throughputSpendAck',
|
|
||||||
spendAckText: throughputSpendAckText,
|
|
||||||
spendAckVisible: throughputSpendAckVisible,
|
|
||||||
showAsMandatory: true,
|
|
||||||
infoBubbleText: ruToolTipText,
|
|
||||||
throughputAutoPilotRadioId: 'newContainer-databaseThroughput-autoPilotRadio',
|
|
||||||
throughputProvisionedRadioId: 'newContainer-databaseThroughput-manualRadio',
|
|
||||||
throughputModeRadioName: 'sharedThroughputModeRadio',
|
|
||||||
isAutoPilotSelected: isSharedAutoPilotSelected,
|
|
||||||
maxAutoPilotThroughputSet: sharedAutoPilotThroughput,
|
|
||||||
autoPilotUsageCost: autoPilotUsageCost,
|
|
||||||
canExceedMaximumValue: canExceedMaximumValue,
|
|
||||||
freeTierExceedThroughputTooltip: freeTierExceedThroughputTooltip
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
</throughput-input-autopilot-v3>
|
|
||||||
</div>
|
|
||||||
<!-- /ko -->
|
|
||||||
<!-- Database provisioned throughput - End -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="seconddivpadding">
|
|
||||||
<p>
|
|
||||||
<span class="mandatoryStar">*</span>
|
|
||||||
<span class="addCollectionLabel" data-bind="text: collectionIdTitle"></span>
|
|
||||||
<span class="infoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="/images/info-bubble.svg" alt="More information" />
|
|
||||||
<span class="tooltiptext infoTooltipWidth"
|
|
||||||
>Unique identifier for the container and used for id-based routing through REST and all SDKs</span
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<input
|
|
||||||
name="collectionId"
|
|
||||||
id="containerId"
|
|
||||||
data-test="addCollection-collectionId"
|
|
||||||
type="text"
|
|
||||||
aria-required="true"
|
|
||||||
autocomplete="off"
|
|
||||||
pattern="[^/?#\\]*[^/?# \\]"
|
|
||||||
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
|
||||||
placeholder="e.g., Container1"
|
|
||||||
size="40"
|
|
||||||
class="textfontclr collid"
|
|
||||||
data-bind="value: collectionId"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Indexing For Shared Throughput - start -->
|
|
||||||
<div class="seconddivpadding" data-bind="visible: showIndexingOptionsForSharedThroughput() && !isMongo()">
|
|
||||||
<div
|
|
||||||
class="useIndexingForSharedThroughput createNewDatabaseOrUseExisting"
|
|
||||||
aria-label="Indexing For Shared Throughput"
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
<span class="mandatoryStar">*</span>
|
|
||||||
<span class="addCollectionLabel">Indexing</span>
|
|
||||||
</p>
|
|
||||||
<div>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
id="useIndexingForSharedThroughputOn"
|
|
||||||
name="useIndexingForSharedThroughput"
|
|
||||||
value="on"
|
|
||||||
class="createNewDatabaseOrUseExistingRadio"
|
|
||||||
data-bind="checked: useIndexingForSharedThroughput, checkedValue: true"
|
|
||||||
/>
|
|
||||||
<span class="createNewDatabaseOrUseExistingSpace" for="useIndexingForSharedThroughputOn">Automatic</span>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
id="useIndexingForSharedThroughputOff"
|
|
||||||
name="useIndexingForSharedThroughput"
|
|
||||||
value="off"
|
|
||||||
class="createNewDatabaseOrUseExistingRadio"
|
|
||||||
data-bind="checked: useIndexingForSharedThroughput, checkedValue: false"
|
|
||||||
/>
|
|
||||||
<span class="createNewDatabaseOrUseExistingSpace" for="useIndexingForSharedThroughputOff">Off</span>
|
|
||||||
</div>
|
|
||||||
<p data-bind="visible: useIndexingForSharedThroughput">
|
|
||||||
All properties in your documents will be indexed by default for flexible and efficient queries.
|
|
||||||
<a class="errorLink" href="https://aka.ms/cosmos-indexing-policy" target="_blank">Learn more</a>
|
|
||||||
</p>
|
|
||||||
<p data-bind="visible: useIndexingForSharedThroughput() === false">
|
|
||||||
Indexing will be turned off. Recommended if you don't need to run queries or only have key value operations.
|
|
||||||
<a class="errorLink" href="https://aka.ms/cosmos-indexing-policy" target="_blank">Learn more</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Indexing For Shared Throughput - end -->
|
|
||||||
|
|
||||||
<p
|
|
||||||
class="seconddivpadding"
|
|
||||||
data-bind="visible: isMongo() && !databaseHasSharedOffer() || container.isFixedCollectionWithSharedThroughputSupported"
|
|
||||||
>
|
|
||||||
<span class="mandatoryStar">*</span>
|
|
||||||
<span class="addCollectionLabel">Storage capacity</span>
|
|
||||||
<span class="infoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="/images/info-bubble.svg" alt="More information" />
|
|
||||||
<span class="tooltiptext infoTooltipWidth"
|
|
||||||
>This is the maximum storage size of the container. Storage is billed per GB based on consumption.</span
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="tabs">
|
|
||||||
<div
|
|
||||||
tabindex="0"
|
|
||||||
data-bind="event: { keydown: onStorageOptionsKeyDown }, visible: isMongo() && !databaseHasSharedOffer() || container.isFixedCollectionWithSharedThroughputSupported"
|
|
||||||
aria-label="Storage capacity"
|
|
||||||
>
|
|
||||||
<!-- Fixed option button - Start -->
|
|
||||||
<div class="tab">
|
|
||||||
<input type="radio" id="tab1" name="storage" value="10" class="radio" data-bind="checked: storage" />
|
|
||||||
<label for="tab1">Fixed (20 GB)</label>
|
|
||||||
</div>
|
|
||||||
<!-- Fixed option button - End -->
|
|
||||||
|
|
||||||
<!-- Unlimited option button - Start -->
|
|
||||||
<div class="tab">
|
|
||||||
<input type="radio" id="tab2" name="storage" value="100" class="radio" data-bind="checked: storage" />
|
|
||||||
<label for="tab2">Unlimited</label>
|
|
||||||
</div>
|
|
||||||
<!-- Unlimited option button - End -->
|
|
||||||
</div>
|
|
||||||
<!-- Unlimited Button Content - Start -->
|
|
||||||
<div class="tabcontent" data-bind="visible: isUnlimitedStorageSelected() || databaseHasSharedOffer()">
|
|
||||||
<div data-bind="visible: partitionKeyVisible">
|
|
||||||
<p>
|
|
||||||
<span class="mandatoryStar">*</span>
|
|
||||||
<span class="addCollectionLabel" data-bind="text: partitionKeyName"></span>
|
|
||||||
<span class="infoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="/images/info-bubble.svg" alt="More information" />
|
|
||||||
<span class="tooltiptext infoTooltipWidth"
|
|
||||||
>The <span data-bind="text: partitionKeyName"></span> is used to automatically partition data among
|
|
||||||
multiple servers for scalability. Choose a JSON property name that has a wide range of values and is
|
|
||||||
likely to have evenly distributed access patterns.</span
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="addCollection-partitionKeyValue"
|
|
||||||
data-test="addCollection-partitionKeyValue"
|
|
||||||
aria-required="true"
|
|
||||||
size="40"
|
|
||||||
class="textfontclr collid"
|
|
||||||
data-bind="textInput: partitionKey,
|
|
||||||
attr: {
|
|
||||||
placeholder: partitionKeyPlaceholder,
|
|
||||||
required: partitionKeyVisible(),
|
|
||||||
pattern: partitionKeyPattern,
|
|
||||||
title: partitionKeyTitle
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<!-- large parition key - start -->
|
|
||||||
<div class="largePartitionKey" aria-label="Large Partition Key" data-bind="visible: partitionKeyVisible">
|
|
||||||
<input
|
|
||||||
tabindex="0"
|
|
||||||
type="checkbox"
|
|
||||||
id="largePartitionKey"
|
|
||||||
data-test="addCollection-largePartitionKey"
|
|
||||||
title="Large Partition Key"
|
|
||||||
data-bind="checked: largePartitionKey"
|
|
||||||
/>
|
|
||||||
<span for="largePartitionKey"
|
|
||||||
>My <span data-bind="text: lowerCasePartitionKeyName"></span> is larger than 100 bytes</span
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
data-bind="visible: largePartitionKey"
|
|
||||||
class="largePartitionKeyDescription"
|
|
||||||
data-test="addCollection-largePartitionKeyDescription"
|
|
||||||
>
|
|
||||||
Old SDKs do not work with containers that support large
|
|
||||||
<span data-bind="text: lowerCasePartitionKeyName"></span>s, ensure you are using the right SDK version.
|
|
||||||
<a class="errorLink" href="https://aka.ms/cosmosdb/pkv2" target="_blank">Learn more</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<!-- large parition key - end -->
|
|
||||||
|
|
||||||
<!-- ko if: canConfigureThroughput -->
|
|
||||||
<!-- Provision collection throughput checkbox - start -->
|
|
||||||
<div class="pkPadding" data-bind="visible: databaseHasSharedOffer() && !databaseCreateNew()">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="collectionSharedThroughput"
|
|
||||||
data-bind="checked: collectionWithThroughputInShared, attr: {title:collectionWithThroughputInSharedTitle}"
|
|
||||||
/>
|
|
||||||
<span for="collectionSharedThroughput" data-bind="text: collectionWithThroughputInSharedTitle"></span>
|
|
||||||
<span class="leftAlignInfoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="/images/info-bubble.svg" alt="More information" />
|
|
||||||
<span class="tooltiptext sharedCollectionThroughputTooltipWidth"
|
|
||||||
>You can optionally provision dedicated throughput for a container within a database that has throughput
|
|
||||||
provisioned. This dedicated throughput amount will not be shared with other containers in the database and
|
|
||||||
does not count towards the throughput you provisioned for the database. This throughput amount will be
|
|
||||||
billed in addition to the throughput amount you provisioned at the database level.</span
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<!-- Provision collection throughput checkbox - end -->
|
|
||||||
|
|
||||||
<!-- Provision collection throughput spinner - start -->
|
|
||||||
<div data-bind="visible: displayCollectionThroughput" data-test="addCollection-displayCollectionThroughput">
|
|
||||||
<!-- 3 -->
|
|
||||||
<throughput-input-autopilot-v3
|
|
||||||
params="{
|
|
||||||
testId: 'collectionThroughputValue',
|
|
||||||
value: throughputMultiPartition,
|
|
||||||
minimum: minThroughputRU,
|
|
||||||
maximum: maxThroughputRU,
|
|
||||||
isEnabled: displayCollectionThroughput,
|
|
||||||
label: throughputRangeText,
|
|
||||||
ariaLabel: throughputRangeText,
|
|
||||||
costsVisible: costsVisible,
|
|
||||||
requestUnitsUsageCost: dedicatedRequestUnitsUsageCost,
|
|
||||||
spendAckChecked: throughputSpendAck,
|
|
||||||
spendAckId: 'throughputSpendAckCollection',
|
|
||||||
spendAckText: throughputSpendAckText,
|
|
||||||
spendAckVisible: throughputSpendAckVisible,
|
|
||||||
showAsMandatory: true,
|
|
||||||
infoBubbleText: ruToolTipText,
|
|
||||||
throughputAutoPilotRadioId: 'newContainer-containerThroughput-autoPilotRadio',
|
|
||||||
throughputProvisionedRadioId: 'newContainer-containerThroughput-manualRadio',
|
|
||||||
throughputModeRadioName: 'throughputModeRadioName',
|
|
||||||
isAutoPilotSelected: isAutoPilotSelected,
|
|
||||||
maxAutoPilotThroughputSet: autoPilotThroughput,
|
|
||||||
autoPilotUsageCost: autoPilotUsageCost,
|
|
||||||
canExceedMaximumValue: canExceedMaximumValue,
|
|
||||||
freeTierExceedThroughputTooltip: freeTierExceedThroughputTooltip
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
</throughput-input-autopilot-v3>
|
|
||||||
</div>
|
|
||||||
<!-- Provision collection throughput spinner - end -->
|
|
||||||
<!-- /ko -->
|
|
||||||
<!-- Provision collection throughput - end -->
|
|
||||||
|
|
||||||
<!-- Custom indexes for mongo checkbox - start -->
|
|
||||||
<div class="pkPadding" data-bind="visible: isEnableMongoCapabilityEnabled()">
|
|
||||||
<p>
|
|
||||||
<span class="addCollectionLabel">Indexing</span>
|
|
||||||
</p>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="mongoWildcardIndex"
|
|
||||||
title="mongoWildcardIndex"
|
|
||||||
data-bind="checked: shouldCreateMongoWildcardIndex"
|
|
||||||
/>
|
|
||||||
<span>Create a Wildcard Index on all fields</span>
|
|
||||||
<span class="infoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="/images/info-bubble.svg" alt="More information" />
|
|
||||||
<span class="tooltiptext mongoWildcardIndexTooltipWidth">
|
|
||||||
By default, only the field _id is indexed. Creating a wildcard index on all fields will quickly optimize
|
|
||||||
query performance and is recommended during development.
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<!-- Custom indexes for mongo checkbox - end -->
|
|
||||||
|
|
||||||
<!-- Enable analytical storage - start -->
|
|
||||||
<div
|
|
||||||
class="enableAnalyticalStorage pkPadding"
|
|
||||||
aria-label="Enable Analytical Store"
|
|
||||||
data-bind="visible: isSynapseLinkSupported"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<span class="mandatoryStar">*</span>
|
|
||||||
<span class="addCollectionLabel">Analytical store</span>
|
|
||||||
<span
|
|
||||||
class="infoTooltip"
|
|
||||||
role="tooltip"
|
|
||||||
tabindex="0"
|
|
||||||
data-bind="event: { focus: function(data, event) { transferFocus('tooltip1', 'link1') } }"
|
|
||||||
>
|
|
||||||
<img class="infoImg" src="/images/info-bubble.svg" alt="More information" />
|
|
||||||
<span id="tooltip1" class="tooltiptext infoTooltipWidth" data-bind="event: { mouseout: onMouseOut }">
|
|
||||||
Enable analytical store capability to perform near real-time analytics on your operational data, without
|
|
||||||
impacting the performance of transactional workloads. Learn more
|
|
||||||
<a
|
|
||||||
id="link1"
|
|
||||||
class="errorLink"
|
|
||||||
href="https://aka.ms/analytical-store-overview"
|
|
||||||
target="_blank"
|
|
||||||
data-bind="event: { focusout: onFocusOut, keydown: onKeyDown.bind($data, 'largePartitionKey') }"
|
|
||||||
>here</a
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="paragraph">
|
|
||||||
<input
|
|
||||||
class="enableAnalyticalStorageRadio"
|
|
||||||
id="enableAnalyticalStorageRadioOn"
|
|
||||||
name="analyticalStore"
|
|
||||||
type="radio"
|
|
||||||
role="radio"
|
|
||||||
tabindex="0"
|
|
||||||
data-bind="
|
|
||||||
disable: showEnableSynapseLink,
|
|
||||||
checked: isAnalyticalStorageOn,
|
|
||||||
checkedValue: true,
|
|
||||||
attr: {
|
|
||||||
'aria-checked': isAnalyticalStorageOn() ? 'true' : 'false'
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
<label for="enableAnalyticalStorageRadioOn" class="enableAnalyticalStorageRadioLabel">
|
|
||||||
<span data-bind="disable: showEnableSynapseLink"> On </span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<input
|
|
||||||
class="enableAnalyticalStorageRadio"
|
|
||||||
id="enableAnalyticalStorageRadioOff"
|
|
||||||
name="analyticalStore"
|
|
||||||
type="radio"
|
|
||||||
role="radio"
|
|
||||||
tabindex="0"
|
|
||||||
data-bind="
|
|
||||||
disable: showEnableSynapseLink,
|
|
||||||
checked: isAnalyticalStorageOn,
|
|
||||||
checkedValue: false,
|
|
||||||
attr: {
|
|
||||||
'aria-checked': isAnalyticalStorageOn() ? 'false' : 'true'
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
<label for="enableAnalyticalStorageRadioOff" class="enableAnalyticalStorageRadioLabel">
|
|
||||||
<span data-bind="disable: showEnableSynapseLink"> Off </span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="paragraph italic" data-bind="visible: ttl90DaysEnabled() && isAnalyticalStorageOn()">
|
|
||||||
By default, Analytical Time-to-Live will be configured to retain 90 days of data in the analytical store.
|
|
||||||
You can configure a custom retention policy in the 'Settings' tab.
|
|
||||||
<span
|
|
||||||
><a class="errorLink" href="https://aka.ms/cosmosdb-analytical-ttl" target="_blank">Learn more</a></span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="paragraph" data-bind="visible: showEnableSynapseLink">
|
|
||||||
Azure Synapse Link is required for creating an analytical store container. Enable Synapse Link for this
|
|
||||||
Cosmos DB account.
|
|
||||||
<span><a class="errorLink" href="https://aka.ms/cosmosdb-synapselink" target="_blank">Learn more</a></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="paragraph" data-bind="visible: showEnableSynapseLink">
|
|
||||||
<button
|
|
||||||
class="button"
|
|
||||||
type="button"
|
|
||||||
data-bind="
|
|
||||||
click: onEnableSynapseLinkButtonClicked,
|
|
||||||
disable: isSynapseLinkUpdating,
|
|
||||||
css: {
|
|
||||||
enabled: !isSynapseLinkUpdating(),
|
|
||||||
disabled: isSynapseLinkUpdating
|
|
||||||
}
|
|
||||||
"
|
|
||||||
>
|
|
||||||
Enable
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Enable analytical storage - end -->
|
|
||||||
</div>
|
|
||||||
<!-- Unlimited Button Content - End -->
|
|
||||||
</div>
|
|
||||||
<div class="uniqueIndexesContainer" data-bind="visible: uniqueKeysVisible">
|
|
||||||
<p class="uniqueKeys">
|
|
||||||
<span class="addCollectionLabel">Unique keys</span>
|
|
||||||
<span class="uniqueInfoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="/images/info-bubble.svg" alt="More information" />
|
|
||||||
<span class="uniqueTooltiptext infoTooltipWidth"
|
|
||||||
>Unique keys provide developers with the ability to add a layer of data integrity to their database. By
|
|
||||||
creating a unique key policy when a container is created, you ensure the uniqueness of one or more values
|
|
||||||
per partition key.</span
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<dynamic-list
|
|
||||||
params="{ listItems: uniqueKeys, placeholder: uniqueKeysPlaceholder(), ariaLabel: 'Write a comma separated path list of unique keys', buttonText: 'Add unique key' }"
|
|
||||||
>
|
|
||||||
</dynamic-list>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="paneFooter">
|
|
||||||
<div class="leftpanel-okbut">
|
|
||||||
<input
|
|
||||||
name="createCollection"
|
|
||||||
id="submitBtnAddCollection"
|
|
||||||
data-test="addCollection-createCollection"
|
|
||||||
type="submit"
|
|
||||||
value="OK"
|
|
||||||
class="btncreatecoll1"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div data-bind="visible: maxCollectionsReached">
|
|
||||||
<error-display params="{ errorMsg: maxCollectionsReachedMessage }"></error-display>
|
|
||||||
</div>
|
|
||||||
<!-- Add collection inputs - End -->
|
|
||||||
</script>
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
import * as Constants from "../../Common/Constants";
|
|
||||||
import { DatabaseAccount } from "../../Contracts/DataModels";
|
|
||||||
import { updateUserContext } from "../../UserContext";
|
|
||||||
import Explorer from "../Explorer";
|
|
||||||
import AddCollectionPane from "./AddCollectionPane";
|
|
||||||
|
|
||||||
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 Collection Pane", () => {
|
|
||||||
describe("isValid()", () => {
|
|
||||||
it("should be true if graph API and partition key is not /id nor /label", () => {
|
|
||||||
updateUserContext({
|
|
||||||
databaseAccount: {
|
|
||||||
properties: {
|
|
||||||
capabilities: [{ name: "EnableGremlin" }],
|
|
||||||
},
|
|
||||||
} as DatabaseAccount,
|
|
||||||
});
|
|
||||||
const explorer = new Explorer();
|
|
||||||
const addCollectionPane = explorer.addCollectionPane as AddCollectionPane;
|
|
||||||
addCollectionPane.partitionKey("/blah");
|
|
||||||
expect(addCollectionPane.isValid()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should be false if graph API and partition key is /id or /label", () => {
|
|
||||||
updateUserContext({
|
|
||||||
databaseAccount: {
|
|
||||||
properties: {
|
|
||||||
capabilities: [{ name: "EnableGremlin" }],
|
|
||||||
},
|
|
||||||
} as DatabaseAccount,
|
|
||||||
});
|
|
||||||
const explorer = new Explorer();
|
|
||||||
const addCollectionPane = explorer.addCollectionPane as AddCollectionPane;
|
|
||||||
addCollectionPane.partitionKey("/id");
|
|
||||||
expect(addCollectionPane.isValid()).toBe(false);
|
|
||||||
|
|
||||||
addCollectionPane.partitionKey("/label");
|
|
||||||
expect(addCollectionPane.isValid()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should be true for any non-graph API with /id or /label partition key", () => {
|
|
||||||
updateUserContext({
|
|
||||||
databaseAccount: {
|
|
||||||
properties: {
|
|
||||||
capabilities: [{ name: "EnableCassandra" }],
|
|
||||||
},
|
|
||||||
} as DatabaseAccount,
|
|
||||||
});
|
|
||||||
const explorer = new Explorer();
|
|
||||||
const addCollectionPane = explorer.addCollectionPane as AddCollectionPane;
|
|
||||||
|
|
||||||
addCollectionPane.partitionKey("/id");
|
|
||||||
expect(addCollectionPane.isValid()).toBe(true);
|
|
||||||
|
|
||||||
addCollectionPane.partitionKey("/label");
|
|
||||||
expect(addCollectionPane.isValid()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should display free tier text in upsell messaging", () => {
|
|
||||||
updateUserContext({ databaseAccount: mockFreeTierDatabaseAccount });
|
|
||||||
const explorer = new Explorer();
|
|
||||||
const addCollectionPane = explorer.addCollectionPane as AddCollectionPane;
|
|
||||||
expect(addCollectionPane.isFreeTierAccount()).toBe(true);
|
|
||||||
expect(addCollectionPane.upsellMessage()).toContain("With free tier");
|
|
||||||
expect(addCollectionPane.upsellAnchorUrl()).toBe(Constants.Urls.freeTierInformation);
|
|
||||||
expect(addCollectionPane.upsellAnchorText()).toBe("Learn more");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should display standard texr in upsell messaging", () => {
|
|
||||||
updateUserContext({ databaseAccount: mockDatabaseAccount });
|
|
||||||
const explorer = new Explorer();
|
|
||||||
const addCollectionPane = explorer.addCollectionPane as AddCollectionPane;
|
|
||||||
expect(addCollectionPane.isFreeTierAccount()).toBe(false);
|
|
||||||
expect(addCollectionPane.upsellMessage()).toContain("Start at");
|
|
||||||
expect(addCollectionPane.upsellAnchorUrl()).toBe(Constants.Urls.cosmosPricing);
|
|
||||||
expect(addCollectionPane.upsellAnchorText()).toBe("More details");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -213,6 +213,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
this.isFreeTierAccount() && !this.props.explorer.isFirstResourceCreated()
|
this.isFreeTierAccount() && !this.props.explorer.isFirstResourceCreated()
|
||||||
}
|
}
|
||||||
isDatabase={true}
|
isDatabase={true}
|
||||||
|
isAutoscaleSelected={this.isNewDatabaseAutoscale}
|
||||||
|
throughput={this.newDatabaseThroughput}
|
||||||
isSharded={this.state.isSharded}
|
isSharded={this.state.isSharded}
|
||||||
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
|
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
|
setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
|
||||||
@@ -442,6 +444,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
this.isFreeTierAccount() && !this.props.explorer.isFirstResourceCreated()
|
this.isFreeTierAccount() && !this.props.explorer.isFirstResourceCreated()
|
||||||
}
|
}
|
||||||
isDatabase={false}
|
isDatabase={false}
|
||||||
|
isAutoscaleSelected={this.isCollectionAutoscale}
|
||||||
|
throughput={this.collectionThroughput}
|
||||||
isSharded={this.state.isSharded}
|
isSharded={this.state.isSharded}
|
||||||
setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
|
setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)}
|
setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div data-bind="visible: visible, event: { keydown: onPaneKeyDown }">
|
<div data-bind="visible: visible, event: { keydown: onPaneKeyDown }">
|
||||||
<div class="contextual-pane-out" data-bind="click: cancel, clickBubble: false"></div>
|
<div class="contextual-pane-out" data-bind="click: cancel, clickBubble: false"></div>
|
||||||
<div class="contextual-pane" data-bind="attr: { id: id }">
|
<div class="contextual-pane" data-bind="attr: { id: id }">
|
||||||
<!-- Add database form -- Start -->
|
<!-- Add database form -- Start -->
|
||||||
@@ -126,31 +126,31 @@
|
|||||||
<div data-bind="visible: databaseCreateNewShared">
|
<div data-bind="visible: databaseCreateNewShared">
|
||||||
<throughput-input-autopilot-v3
|
<throughput-input-autopilot-v3
|
||||||
params="{
|
params="{
|
||||||
step: 100,
|
step: 100,
|
||||||
value: throughput,
|
value: throughput,
|
||||||
testId: 'sharedThroughputValue',
|
testId: 'sharedThroughputValue',
|
||||||
minimum: minThroughputRU,
|
minimum: minThroughputRU,
|
||||||
maximum: maxThroughputRU,
|
maximum: maxThroughputRU,
|
||||||
isEnabled: databaseCreateNewShared,
|
isEnabled: databaseCreateNewShared,
|
||||||
label: throughputRangeText,
|
label: throughputRangeText,
|
||||||
ariaLabel: throughputRangeText,
|
ariaLabel: throughputRangeText,
|
||||||
costsVisible: costsVisible,
|
costsVisible: costsVisible,
|
||||||
requestUnitsUsageCost: requestUnitsUsageCost,
|
requestUnitsUsageCost: requestUnitsUsageCost,
|
||||||
spendAckChecked: throughputSpendAck,
|
spendAckChecked: throughputSpendAck,
|
||||||
spendAckId: 'throughputSpendAckDatabase',
|
spendAckId: 'throughputSpendAckDatabase',
|
||||||
spendAckText: throughputSpendAckText,
|
spendAckText: throughputSpendAckText,
|
||||||
spendAckVisible: throughputSpendAckVisible,
|
spendAckVisible: throughputSpendAckVisible,
|
||||||
showAsMandatory: true,
|
showAsMandatory: true,
|
||||||
infoBubbleText: ruToolTipText,
|
infoBubbleText: ruToolTipText,
|
||||||
throughputAutoPilotRadioId: 'newDatabase-databaseThroughput-autoPilotRadio',
|
throughputAutoPilotRadioId: 'newDatabase-databaseThroughput-autoPilotRadio',
|
||||||
throughputProvisionedRadioId: 'newDatabase-databaseThroughput-manualRadio',
|
throughputProvisionedRadioId: 'newDatabase-databaseThroughput-manualRadio',
|
||||||
throughputModeRadioName: 'throughputModeRadioName',
|
throughputModeRadioName: 'throughputModeRadioName',
|
||||||
isAutoPilotSelected: isAutoPilotSelected,
|
isAutoPilotSelected: isAutoPilotSelected,
|
||||||
maxAutoPilotThroughputSet: maxAutoPilotThroughputSet,
|
maxAutoPilotThroughputSet: maxAutoPilotThroughputSet,
|
||||||
autoPilotUsageCost: autoPilotUsageCost,
|
autoPilotUsageCost: autoPilotUsageCost,
|
||||||
canExceedMaximumValue: canExceedMaximumValue,
|
canExceedMaximumValue: canExceedMaximumValue,
|
||||||
freeTierExceedThroughputTooltip: freeTierExceedThroughputTooltip
|
freeTierExceedThroughputTooltip: freeTierExceedThroughputTooltip
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
</throughput-input-autopilot-v3>
|
</throughput-input-autopilot-v3>
|
||||||
<p data-bind="visible: canRequestSupport">
|
<p data-bind="visible: canRequestSupport">
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
|
import { AddDatabasePanel } from "./AddDatabasePanel";
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
explorer: new Explorer(),
|
||||||
|
closePanel: (): void => undefined,
|
||||||
|
openNotificationConsole: (): void => undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("AddDatabasePane Pane", () => {
|
||||||
|
it("should render Default properly", () => {
|
||||||
|
const wrapper = shallow(<AddDatabasePanel {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
342
src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.tsx
Normal file
342
src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.tsx
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
import { Checkbox, Text, TextField } from "@fluentui/react";
|
||||||
|
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||||
|
import * as Constants from "../../../Common/Constants";
|
||||||
|
import { createDatabase } from "../../../Common/dataAccess/createDatabase";
|
||||||
|
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
||||||
|
import { configContext, Platform } from "../../../ConfigContext";
|
||||||
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
|
import { SubscriptionType } from "../../../Contracts/SubscriptionType";
|
||||||
|
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 { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
|
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
|
||||||
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
|
export interface AddDatabasePaneProps {
|
||||||
|
explorer: Explorer;
|
||||||
|
closePanel: () => void;
|
||||||
|
openNotificationConsole: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
||||||
|
explorer: container,
|
||||||
|
closePanel,
|
||||||
|
openNotificationConsole,
|
||||||
|
}: AddDatabasePaneProps) => {
|
||||||
|
const { subscriptionType } = userContext;
|
||||||
|
const getSharedThroughputDefault = !(subscriptionType === SubscriptionType.EA || container.isServerlessEnabled());
|
||||||
|
const _isAutoPilotSelectedAndWhatTier = (): DataModels.AutoPilotCreationSettings => {
|
||||||
|
if (isAutoPilotSelected && maxAutoPilotThroughputSet) {
|
||||||
|
return {
|
||||||
|
maxThroughput: maxAutoPilotThroughputSet * 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isCassandraAccount: boolean = userContext.apiType === "Cassandra";
|
||||||
|
const databaseLabel: string = isCassandraAccount ? "keyspace" : "database";
|
||||||
|
const collectionsLabel: string = isCassandraAccount ? "tables" : "collections";
|
||||||
|
const databaseIdLabel: string = isCassandraAccount ? "Keyspace id" : "Database id";
|
||||||
|
const databaseIdPlaceHolder: string = isCassandraAccount ? "Type a new keyspace id" : "Type a new database id";
|
||||||
|
|
||||||
|
const [databaseId, setDatabaseId] = useState<string>("");
|
||||||
|
const databaseIdTooltipText = `A ${
|
||||||
|
isCassandraAccount ? "keyspace" : "database"
|
||||||
|
} is a logical container of one or more ${isCassandraAccount ? "tables" : "collections"}`;
|
||||||
|
|
||||||
|
const databaseLevelThroughputTooltipText = `Provisioned throughput at the ${databaseLabel} level will be shared across all ${collectionsLabel} within the ${databaseLabel}.`;
|
||||||
|
const [databaseCreateNewShared, setDatabaseCreateNewShared] = useState<boolean>(getSharedThroughputDefault);
|
||||||
|
const [formErrorsDetails, setFormErrorsDetails] = useState<string>();
|
||||||
|
const [formErrors, setFormErrors] = useState<string>("");
|
||||||
|
|
||||||
|
const [isAutoPilotSelected, setIsAutoPilotSelected] = useState<boolean>(container.isAutoscaleDefaultEnabled());
|
||||||
|
|
||||||
|
const throughputDefaults = container.collectionCreationDefaults.throughput;
|
||||||
|
const [throughput, setThroughput] = useState<number>(
|
||||||
|
isAutoPilotSelected ? AutoPilotUtils.minAutoPilotThroughput : throughputDefaults.shared
|
||||||
|
);
|
||||||
|
|
||||||
|
const [throughputSpendAck, setThroughputSpendAck] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const canRequestSupport = () => {
|
||||||
|
if (
|
||||||
|
configContext.platform !== Platform.Emulator &&
|
||||||
|
!userContext.isTryCosmosDBSubscription &&
|
||||||
|
configContext.platform !== Platform.Portal
|
||||||
|
) {
|
||||||
|
const offerThroughput: number = throughput;
|
||||||
|
return offerThroughput <= 100000;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
|
||||||
|
const upsellMessage: string = PricingUtils.getUpsellMessage(
|
||||||
|
userContext.portalEnv,
|
||||||
|
isFreeTierAccount,
|
||||||
|
container.isFirstResourceCreated(),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
const upsellAnchorUrl: string = isFreeTierAccount ? Constants.Urls.freeTierInformation : Constants.Urls.cosmosPricing;
|
||||||
|
|
||||||
|
const upsellAnchorText: string = isFreeTierAccount ? "Learn more" : "More details";
|
||||||
|
const maxAutoPilotThroughputSet = AutoPilotUtils.minAutoPilotThroughput;
|
||||||
|
|
||||||
|
const canConfigureThroughput = !container.isServerlessEnabled();
|
||||||
|
const showUpsellMessage = () => {
|
||||||
|
if (container.isServerlessEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFreeTierAccount) {
|
||||||
|
return databaseCreateNewShared;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDatabaseCreateNewShared(getSharedThroughputDefault);
|
||||||
|
}, [subscriptionType]);
|
||||||
|
|
||||||
|
const addDatabasePaneMessage = {
|
||||||
|
database: {
|
||||||
|
id: databaseId,
|
||||||
|
shared: databaseCreateNewShared,
|
||||||
|
},
|
||||||
|
subscriptionType: SubscriptionType[subscriptionType],
|
||||||
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
|
defaultsCheck: {
|
||||||
|
flight: userContext.addCollectionFlight,
|
||||||
|
},
|
||||||
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const addDatabasePaneOpenMessage = {
|
||||||
|
subscriptionType: SubscriptionType[subscriptionType],
|
||||||
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
|
defaultsCheck: {
|
||||||
|
throughput: throughput,
|
||||||
|
flight: userContext.addCollectionFlight,
|
||||||
|
},
|
||||||
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||||
|
};
|
||||||
|
TelemetryProcessor.trace(Action.CreateDatabase, ActionModifiers.Open, addDatabasePaneOpenMessage);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
if (!_isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offerThroughput: number = _computeOfferThroughput();
|
||||||
|
|
||||||
|
const addDatabasePaneStartMessage = {
|
||||||
|
...addDatabasePaneMessage,
|
||||||
|
offerThroughput,
|
||||||
|
};
|
||||||
|
const startKey: number = TelemetryProcessor.traceStart(Action.CreateDatabase, addDatabasePaneStartMessage);
|
||||||
|
setFormErrors("");
|
||||||
|
setIsExecuting(true);
|
||||||
|
|
||||||
|
const createDatabaseParams: DataModels.CreateDatabaseParams = {
|
||||||
|
databaseId: addDatabasePaneStartMessage.database.id,
|
||||||
|
databaseLevelThroughput: addDatabasePaneStartMessage.database.shared,
|
||||||
|
};
|
||||||
|
if (isAutoPilotSelected) {
|
||||||
|
createDatabaseParams.autoPilotMaxThroughput = addDatabasePaneStartMessage.offerThroughput;
|
||||||
|
} else {
|
||||||
|
createDatabaseParams.offerThroughput = addDatabasePaneStartMessage.offerThroughput;
|
||||||
|
}
|
||||||
|
|
||||||
|
createDatabase(createDatabaseParams).then(
|
||||||
|
() => {
|
||||||
|
_onCreateDatabaseSuccess(offerThroughput, startKey);
|
||||||
|
},
|
||||||
|
(error: string) => {
|
||||||
|
_onCreateDatabaseFailure(error, offerThroughput, startKey);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const _onCreateDatabaseSuccess = (offerThroughput: number, startKey: number): void => {
|
||||||
|
setIsExecuting(false);
|
||||||
|
closePanel();
|
||||||
|
container.refreshAllDatabases();
|
||||||
|
const addDatabasePaneSuccessMessage = {
|
||||||
|
...addDatabasePaneMessage,
|
||||||
|
offerThroughput,
|
||||||
|
};
|
||||||
|
TelemetryProcessor.traceSuccess(Action.CreateDatabase, addDatabasePaneSuccessMessage, startKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
const _onCreateDatabaseFailure = (error: string, offerThroughput: number, startKey: number): void => {
|
||||||
|
setIsExecuting(false);
|
||||||
|
const errorMessage = getErrorMessage(error);
|
||||||
|
setFormErrors(errorMessage);
|
||||||
|
setFormErrorsDetails(errorMessage);
|
||||||
|
const addDatabasePaneFailedMessage = {
|
||||||
|
...addDatabasePaneMessage,
|
||||||
|
offerThroughput,
|
||||||
|
error: errorMessage,
|
||||||
|
errorStack: getErrorStack(error),
|
||||||
|
};
|
||||||
|
TelemetryProcessor.traceFailure(Action.CreateDatabase, addDatabasePaneFailedMessage, startKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
const _getThroughput = (): number => {
|
||||||
|
return isNaN(throughput) ? 0 : Number(throughput);
|
||||||
|
};
|
||||||
|
|
||||||
|
const _computeOfferThroughput = (): number => {
|
||||||
|
if (!canConfigureThroughput) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _getThroughput();
|
||||||
|
};
|
||||||
|
|
||||||
|
const _isValid = (): boolean => {
|
||||||
|
// TODO add feature flag that disables validation for customers with custom accounts
|
||||||
|
if (isAutoPilotSelected) {
|
||||||
|
const autoPilot = _isAutoPilotSelectedAndWhatTier();
|
||||||
|
if (
|
||||||
|
!autoPilot ||
|
||||||
|
!autoPilot.maxThroughput ||
|
||||||
|
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput)
|
||||||
|
) {
|
||||||
|
setFormErrors(
|
||||||
|
`Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const throughput = _getThroughput();
|
||||||
|
|
||||||
|
if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !throughputSpendAck) {
|
||||||
|
setFormErrors(`Please acknowledge the estimated daily spend.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const autoscaleThroughput = maxAutoPilotThroughputSet * 1;
|
||||||
|
|
||||||
|
if (
|
||||||
|
isAutoPilotSelected &&
|
||||||
|
autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
|
||||||
|
!throughputSpendAck
|
||||||
|
) {
|
||||||
|
setFormErrors(`Please acknowledge the estimated monthly spend.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleonChangeDBId = React.useCallback(
|
||||||
|
(event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => {
|
||||||
|
setDatabaseId(newValue || "");
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const props: RightPaneFormProps = {
|
||||||
|
expandConsole: container.expandConsole,
|
||||||
|
formError: formErrors,
|
||||||
|
formErrorDetail: formErrorsDetails,
|
||||||
|
isExecuting,
|
||||||
|
submitButtonText: "OK",
|
||||||
|
onSubmit,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RightPaneForm {...props}>
|
||||||
|
<div className="paneContentContainer" role="dialog" aria-labelledby="databaseTitle">
|
||||||
|
{showUpsellMessage && formErrors === "" && (
|
||||||
|
<PanelInfoErrorComponent
|
||||||
|
message={upsellMessage}
|
||||||
|
messageType="info"
|
||||||
|
showErrorDetails={false}
|
||||||
|
openNotificationConsole={openNotificationConsole}
|
||||||
|
link={upsellAnchorUrl}
|
||||||
|
linkText={upsellAnchorText}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="paneMainContent">
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<span className="mandatoryStar">*</span>
|
||||||
|
<Text variant="small">{databaseIdLabel}</Text>
|
||||||
|
<InfoTooltip>{databaseIdTooltipText}</InfoTooltip>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
id="database-id"
|
||||||
|
type="text"
|
||||||
|
aria-required="true"
|
||||||
|
autoComplete="off"
|
||||||
|
pattern="[^/?#\\]*[^/?# \\]"
|
||||||
|
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
||||||
|
size={40}
|
||||||
|
aria-label={databaseIdLabel}
|
||||||
|
placeholder={databaseIdPlaceHolder}
|
||||||
|
value={databaseId}
|
||||||
|
onChange={handleonChangeDBId}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="databaseProvision"
|
||||||
|
aria-label="New database provision support"
|
||||||
|
style={{ display: "block ruby" }}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
title="Provision shared throughput"
|
||||||
|
styles={{
|
||||||
|
checkbox: { width: 12, height: 12 },
|
||||||
|
label: { padding: 0, alignItems: "center" },
|
||||||
|
}}
|
||||||
|
label="Provision throughput"
|
||||||
|
checked={databaseCreateNewShared}
|
||||||
|
onChange={() => setDatabaseCreateNewShared(!databaseCreateNewShared)}
|
||||||
|
/>{" "}
|
||||||
|
<InfoTooltip>{databaseLevelThroughputTooltipText}</InfoTooltip>
|
||||||
|
</div>
|
||||||
|
{databaseCreateNewShared && (
|
||||||
|
<div>
|
||||||
|
<ThroughputInput
|
||||||
|
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container?.isFirstResourceCreated()}
|
||||||
|
isDatabase={true}
|
||||||
|
isSharded={databaseCreateNewShared}
|
||||||
|
isAutoscaleSelected={isAutoPilotSelected}
|
||||||
|
throughput={throughput}
|
||||||
|
setThroughputValue={(throughput: number) => setThroughput(throughput)}
|
||||||
|
setIsAutoscale={(isAutoscale: boolean) => setIsAutoPilotSelected(isAutoscale)}
|
||||||
|
onCostAcknowledgeChange={(isAcknowledged: boolean) => setThroughputSpendAck(isAcknowledged)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{canRequestSupport() && (
|
||||||
|
<p>
|
||||||
|
<a href="https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20More%20Throughput%20Request">
|
||||||
|
Contact support{" "}
|
||||||
|
</a>
|
||||||
|
for more than <span>{throughputDefaults.unlimitedmax?.toLocaleString()} </span> RU/s.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</RightPaneForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`AddDatabasePane Pane should render Default properly 1`] = `
|
||||||
|
<RightPaneForm
|
||||||
|
expandConsole={[Function]}
|
||||||
|
formError=""
|
||||||
|
isExecuting={false}
|
||||||
|
onSubmit={[Function]}
|
||||||
|
submitButtonText="OK"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-labelledby="databaseTitle"
|
||||||
|
className="paneContentContainer"
|
||||||
|
role="dialog"
|
||||||
|
>
|
||||||
|
<PanelInfoErrorComponent
|
||||||
|
link="https://aka.ms/azure-cosmos-db-pricing"
|
||||||
|
linkText="More details"
|
||||||
|
message="Start at $24/mo per database, multiple containers included"
|
||||||
|
messageType="info"
|
||||||
|
openNotificationConsole={[Function]}
|
||||||
|
showErrorDetails={false}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="paneMainContent"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<span
|
||||||
|
className="mandatoryStar"
|
||||||
|
>
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
<Text
|
||||||
|
variant="small"
|
||||||
|
>
|
||||||
|
Database id
|
||||||
|
</Text>
|
||||||
|
<InfoTooltip>
|
||||||
|
A database is a logical container of one or more collections
|
||||||
|
</InfoTooltip>
|
||||||
|
</p>
|
||||||
|
<StyledTextFieldBase
|
||||||
|
aria-label="Database id"
|
||||||
|
aria-required="true"
|
||||||
|
autoComplete="off"
|
||||||
|
autoFocus={true}
|
||||||
|
id="database-id"
|
||||||
|
onChange={[Function]}
|
||||||
|
pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]"
|
||||||
|
placeholder="Type a new database id"
|
||||||
|
size={40}
|
||||||
|
title="May not end with space nor contain characters '\\\\' '/' '#' '?'"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
aria-label="New database provision support"
|
||||||
|
className="databaseProvision"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"display": "block ruby",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<StyledCheckboxBase
|
||||||
|
checked={true}
|
||||||
|
label="Provision throughput"
|
||||||
|
onChange={[Function]}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"checkbox": Object {
|
||||||
|
"height": 12,
|
||||||
|
"width": 12,
|
||||||
|
},
|
||||||
|
"label": Object {
|
||||||
|
"alignItems": "center",
|
||||||
|
"padding": 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
title="Provision shared throughput"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<InfoTooltip>
|
||||||
|
Provisioned throughput at the database level will be shared across all collections within the database.
|
||||||
|
</InfoTooltip>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ThroughputInput
|
||||||
|
isAutoscaleSelected={false}
|
||||||
|
isDatabase={true}
|
||||||
|
isSharded={true}
|
||||||
|
onCostAcknowledgeChange={[Function]}
|
||||||
|
setIsAutoscale={[Function]}
|
||||||
|
setThroughputValue={[Function]}
|
||||||
|
throughput={400}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</RightPaneForm>
|
||||||
|
`;
|
||||||
@@ -1099,7 +1099,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
aria-label="Close pane"
|
aria-label="Close pane"
|
||||||
className="ms-Button ms-Button--icon closePaneBtn root-202"
|
className="ms-Button ms-Button--icon closePaneBtn root-102"
|
||||||
data-is-focusable={true}
|
data-is-focusable={true}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
@@ -1112,16 +1112,16 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-flexContainer flexContainer-203"
|
className="ms-Button-flexContainer flexContainer-103"
|
||||||
data-automationid="splitbuttonprimary"
|
data-automationid="splitbuttonprimary"
|
||||||
>
|
>
|
||||||
<FontIcon
|
<FontIcon
|
||||||
className="ms-Button-icon icon-205"
|
className="ms-Button-icon icon-105"
|
||||||
iconName="Cancel"
|
iconName="Cancel"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
aria-hidden={true}
|
aria-hidden={true}
|
||||||
className="ms-Icon root-37 css-210 ms-Button-icon icon-205"
|
className="ms-Icon root-37 css-110 ms-Button-icon icon-105"
|
||||||
data-icon-name="Cancel"
|
data-icon-name="Cancel"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
@@ -1190,7 +1190,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
variant="small"
|
variant="small"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="css-211"
|
className="css-111"
|
||||||
>
|
>
|
||||||
Confirm by typing the
|
Confirm by typing the
|
||||||
container
|
container
|
||||||
@@ -1494,18 +1494,18 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
value=""
|
value=""
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField root-213"
|
className="ms-TextField root-113"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-wrapper"
|
className="ms-TextField-wrapper"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-fieldGroup fieldGroup-214"
|
className="ms-TextField-fieldGroup fieldGroup-114"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-invalid={false}
|
aria-invalid={false}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className="ms-TextField-field field-215"
|
className="ms-TextField-field field-115"
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -1528,7 +1528,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
variant="small"
|
variant="small"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="css-224"
|
className="css-124"
|
||||||
>
|
>
|
||||||
Help us improve Azure Cosmos DB!
|
Help us improve Azure Cosmos DB!
|
||||||
</span>
|
</span>
|
||||||
@@ -1538,7 +1538,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
variant="small"
|
variant="small"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="css-224"
|
className="css-124"
|
||||||
>
|
>
|
||||||
What is the reason why you are deleting this
|
What is the reason why you are deleting this
|
||||||
container
|
container
|
||||||
@@ -1844,17 +1844,17 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
value=""
|
value=""
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField ms-TextField--multiline root-213"
|
className="ms-TextField ms-TextField--multiline root-113"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-wrapper"
|
className="ms-TextField-wrapper"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-fieldGroup fieldGroup-225"
|
className="ms-TextField-fieldGroup fieldGroup-125"
|
||||||
>
|
>
|
||||||
<textarea
|
<textarea
|
||||||
aria-invalid={false}
|
aria-invalid={false}
|
||||||
className="ms-TextField-field field-226"
|
className="ms-TextField-field field-126"
|
||||||
id="deleteCollectionFeedbackInput"
|
id="deleteCollectionFeedbackInput"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -3616,7 +3616,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
aria-label="Submit"
|
aria-label="Submit"
|
||||||
className="ms-Button ms-Button--primary genericPaneSubmitBtn root-228"
|
className="ms-Button ms-Button--primary genericPaneSubmitBtn root-128"
|
||||||
data-is-focusable={true}
|
data-is-focusable={true}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
@@ -3634,14 +3634,14 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-flexContainer flexContainer-203"
|
className="ms-Button-flexContainer flexContainer-103"
|
||||||
data-automationid="splitbuttonprimary"
|
data-automationid="splitbuttonprimary"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-textContainer textContainer-204"
|
className="ms-Button-textContainer textContainer-104"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-label label-229"
|
className="ms-Button-label label-129"
|
||||||
id="id__9"
|
id="id__9"
|
||||||
key="id__9"
|
key="id__9"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -18,10 +18,6 @@ export interface GenericRightPaneProps {
|
|||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenericRightPaneState {
|
|
||||||
panelHeight: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GenericRightPaneComponent: FunctionComponent<GenericRightPaneProps> = ({
|
export const GenericRightPaneComponent: FunctionComponent<GenericRightPaneProps> = ({
|
||||||
expandConsole,
|
expandConsole,
|
||||||
formError,
|
formError,
|
||||||
|
|||||||
@@ -64,89 +64,6 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
|
|||||||
"upsellMessageAriaLabel": [Function],
|
"upsellMessageAriaLabel": [Function],
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
AddCollectionPane {
|
|
||||||
"_isSynapseLinkEnabled": [Function],
|
|
||||||
"autoPilotThroughput": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
|
||||||
"canConfigureThroughput": [Function],
|
|
||||||
"canExceedMaximumValue": [Function],
|
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"collectionId": [Function],
|
|
||||||
"collectionIdTitle": [Function],
|
|
||||||
"collectionWithThroughputInShared": [Function],
|
|
||||||
"collectionWithThroughputInSharedTitle": [Function],
|
|
||||||
"container": [Circular],
|
|
||||||
"costsVisible": [Function],
|
|
||||||
"databaseCreateNew": [Function],
|
|
||||||
"databaseCreateNewShared": [Function],
|
|
||||||
"databaseHasSharedOffer": [Function],
|
|
||||||
"databaseId": [Function],
|
|
||||||
"databaseIds": [Function],
|
|
||||||
"dedicatedRequestUnitsUsageCost": [Function],
|
|
||||||
"displayCollectionThroughput": [Function],
|
|
||||||
"firstFieldHasFocus": [Function],
|
|
||||||
"formErrors": [Function],
|
|
||||||
"formErrorsDetails": [Function],
|
|
||||||
"formWarnings": [Function],
|
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "addcollectionpane",
|
|
||||||
"isAnalyticalStorageOn": [Function],
|
|
||||||
"isAutoPilotSelected": [Function],
|
|
||||||
"isEnableMongoCapabilityEnabled": [Function],
|
|
||||||
"isExecuting": [Function],
|
|
||||||
"isFixedStorageSelected": [Function],
|
|
||||||
"isFreeTierAccount": [Function],
|
|
||||||
"isNonTableApi": [Function],
|
|
||||||
"isPreferredApiTable": [Function],
|
|
||||||
"isSharedAutoPilotSelected": [Function],
|
|
||||||
"isSynapseLinkSupported": [Function],
|
|
||||||
"isSynapseLinkUpdating": [Function],
|
|
||||||
"isTemplateReady": [Function],
|
|
||||||
"isTryCosmosDBSubscription": [Function],
|
|
||||||
"isUnlimitedStorageSelected": [Function],
|
|
||||||
"largePartitionKey": [Function],
|
|
||||||
"lowerCasePartitionKeyName": [Function],
|
|
||||||
"maxCollectionsReached": [Function],
|
|
||||||
"maxCollectionsReachedMessage": [Function],
|
|
||||||
"maxThroughputRU": [Function],
|
|
||||||
"minThroughputRU": [Function],
|
|
||||||
"onMoreDetailsKeyPress": [Function],
|
|
||||||
"partitionKey": [Function],
|
|
||||||
"partitionKeyName": [Function],
|
|
||||||
"partitionKeyPattern": [Function],
|
|
||||||
"partitionKeyPlaceholder": [Function],
|
|
||||||
"partitionKeyTitle": [Function],
|
|
||||||
"partitionKeyVisible": [Function],
|
|
||||||
"requestUnitsUsageCost": [Function],
|
|
||||||
"ruToolTipText": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
|
||||||
"shouldUseDatabaseThroughput": [Function],
|
|
||||||
"showAnalyticalStore": [Function],
|
|
||||||
"showEnableSynapseLink": [Function],
|
|
||||||
"showIndexingOptionsForSharedThroughput": [Function],
|
|
||||||
"showUpsellMessage": [Function],
|
|
||||||
"storage": [Function],
|
|
||||||
"throughputDatabase": [Function],
|
|
||||||
"throughputMultiPartition": [Function],
|
|
||||||
"throughputRangeText": [Function],
|
|
||||||
"throughputSinglePartition": [Function],
|
|
||||||
"throughputSpendAck": [Function],
|
|
||||||
"throughputSpendAckText": [Function],
|
|
||||||
"throughputSpendAckVisible": [Function],
|
|
||||||
"title": [Function],
|
|
||||||
"ttl90DaysEnabled": [Function],
|
|
||||||
"uniqueKeys": [Function],
|
|
||||||
"uniqueKeysPlaceholder": [Function],
|
|
||||||
"uniqueKeysVisible": [Function],
|
|
||||||
"upsellAnchorText": [Function],
|
|
||||||
"upsellAnchorUrl": [Function],
|
|
||||||
"upsellMessage": [Function],
|
|
||||||
"upsellMessageAriaLabel": [Function],
|
|
||||||
"useIndexingForSharedThroughput": [Function],
|
|
||||||
"visible": [Function],
|
|
||||||
},
|
|
||||||
GraphStylingPane {
|
GraphStylingPane {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
@@ -215,89 +132,6 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
|
|||||||
],
|
],
|
||||||
"_refreshSparkEnabledStateForAccount": [Function],
|
"_refreshSparkEnabledStateForAccount": [Function],
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"addCollectionPane": AddCollectionPane {
|
|
||||||
"_isSynapseLinkEnabled": [Function],
|
|
||||||
"autoPilotThroughput": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
|
||||||
"canConfigureThroughput": [Function],
|
|
||||||
"canExceedMaximumValue": [Function],
|
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"collectionId": [Function],
|
|
||||||
"collectionIdTitle": [Function],
|
|
||||||
"collectionWithThroughputInShared": [Function],
|
|
||||||
"collectionWithThroughputInSharedTitle": [Function],
|
|
||||||
"container": [Circular],
|
|
||||||
"costsVisible": [Function],
|
|
||||||
"databaseCreateNew": [Function],
|
|
||||||
"databaseCreateNewShared": [Function],
|
|
||||||
"databaseHasSharedOffer": [Function],
|
|
||||||
"databaseId": [Function],
|
|
||||||
"databaseIds": [Function],
|
|
||||||
"dedicatedRequestUnitsUsageCost": [Function],
|
|
||||||
"displayCollectionThroughput": [Function],
|
|
||||||
"firstFieldHasFocus": [Function],
|
|
||||||
"formErrors": [Function],
|
|
||||||
"formErrorsDetails": [Function],
|
|
||||||
"formWarnings": [Function],
|
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "addcollectionpane",
|
|
||||||
"isAnalyticalStorageOn": [Function],
|
|
||||||
"isAutoPilotSelected": [Function],
|
|
||||||
"isEnableMongoCapabilityEnabled": [Function],
|
|
||||||
"isExecuting": [Function],
|
|
||||||
"isFixedStorageSelected": [Function],
|
|
||||||
"isFreeTierAccount": [Function],
|
|
||||||
"isNonTableApi": [Function],
|
|
||||||
"isPreferredApiTable": [Function],
|
|
||||||
"isSharedAutoPilotSelected": [Function],
|
|
||||||
"isSynapseLinkSupported": [Function],
|
|
||||||
"isSynapseLinkUpdating": [Function],
|
|
||||||
"isTemplateReady": [Function],
|
|
||||||
"isTryCosmosDBSubscription": [Function],
|
|
||||||
"isUnlimitedStorageSelected": [Function],
|
|
||||||
"largePartitionKey": [Function],
|
|
||||||
"lowerCasePartitionKeyName": [Function],
|
|
||||||
"maxCollectionsReached": [Function],
|
|
||||||
"maxCollectionsReachedMessage": [Function],
|
|
||||||
"maxThroughputRU": [Function],
|
|
||||||
"minThroughputRU": [Function],
|
|
||||||
"onMoreDetailsKeyPress": [Function],
|
|
||||||
"partitionKey": [Function],
|
|
||||||
"partitionKeyName": [Function],
|
|
||||||
"partitionKeyPattern": [Function],
|
|
||||||
"partitionKeyPlaceholder": [Function],
|
|
||||||
"partitionKeyTitle": [Function],
|
|
||||||
"partitionKeyVisible": [Function],
|
|
||||||
"requestUnitsUsageCost": [Function],
|
|
||||||
"ruToolTipText": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
|
||||||
"shouldUseDatabaseThroughput": [Function],
|
|
||||||
"showAnalyticalStore": [Function],
|
|
||||||
"showEnableSynapseLink": [Function],
|
|
||||||
"showIndexingOptionsForSharedThroughput": [Function],
|
|
||||||
"showUpsellMessage": [Function],
|
|
||||||
"storage": [Function],
|
|
||||||
"throughputDatabase": [Function],
|
|
||||||
"throughputMultiPartition": [Function],
|
|
||||||
"throughputRangeText": [Function],
|
|
||||||
"throughputSinglePartition": [Function],
|
|
||||||
"throughputSpendAck": [Function],
|
|
||||||
"throughputSpendAckText": [Function],
|
|
||||||
"throughputSpendAckVisible": [Function],
|
|
||||||
"title": [Function],
|
|
||||||
"ttl90DaysEnabled": [Function],
|
|
||||||
"uniqueKeys": [Function],
|
|
||||||
"uniqueKeysPlaceholder": [Function],
|
|
||||||
"uniqueKeysVisible": [Function],
|
|
||||||
"upsellAnchorText": [Function],
|
|
||||||
"upsellAnchorUrl": [Function],
|
|
||||||
"upsellMessage": [Function],
|
|
||||||
"upsellMessageAriaLabel": [Function],
|
|
||||||
"useIndexingForSharedThroughput": [Function],
|
|
||||||
"visible": [Function],
|
|
||||||
},
|
|
||||||
"addCollectionText": [Function],
|
"addCollectionText": [Function],
|
||||||
"addDatabasePane": AddDatabasePane {
|
"addDatabasePane": AddDatabasePane {
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import AddCollectionPaneTemplate from "./AddCollectionPane.html";
|
|
||||||
import AddDatabasePaneTemplate from "./AddDatabasePane.html";
|
import AddDatabasePaneTemplate from "./AddDatabasePane.html";
|
||||||
import CassandraAddCollectionPaneTemplate from "./CassandraAddCollectionPane.html";
|
import CassandraAddCollectionPaneTemplate from "./CassandraAddCollectionPane.html";
|
||||||
import GraphStylingPaneTemplate from "./GraphStylingPane.html";
|
import GraphStylingPaneTemplate from "./GraphStylingPane.html";
|
||||||
@@ -18,15 +17,6 @@ export class AddDatabasePaneComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AddCollectionPaneComponent {
|
|
||||||
constructor() {
|
|
||||||
return {
|
|
||||||
viewModel: PaneComponent,
|
|
||||||
template: AddCollectionPaneTemplate,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GraphStylingPaneComponent {
|
export class GraphStylingPaneComponent {
|
||||||
constructor() {
|
constructor() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
|
||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
import { PanelContainerComponent, PanelContainerProps } from "./PanelContainerComponent";
|
import { PanelContainerComponent, PanelContainerProps } from "./PanelContainerComponent";
|
||||||
|
|
||||||
describe("PaneContainerComponent test", () => {
|
describe("PaneContainerComponent test", () => {
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import React from "react";
|
|
||||||
import { PrimaryButton } from "@fluentui/react";
|
import { PrimaryButton } from "@fluentui/react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
export interface PanelFooterProps {
|
export interface PanelFooterProps {
|
||||||
buttonLabel: string;
|
buttonLabel: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PanelFooterComponent: React.FunctionComponent<PanelFooterProps> = (
|
export const PanelFooterComponent: React.FunctionComponent<PanelFooterProps> = ({
|
||||||
props: PanelFooterProps
|
buttonLabel,
|
||||||
): JSX.Element => (
|
}: PanelFooterProps): JSX.Element => (
|
||||||
<div className="panelFooter">
|
<div className="panelFooter">
|
||||||
<PrimaryButton type="submit" id="sidePanelOkButton" text={props.buttonLabel} />
|
<PrimaryButton type="submit" id="sidePanelOkButton" text={buttonLabel} ariaLabel={buttonLabel} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,38 +8,47 @@ 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> = ({
|
||||||
props: PanelInfoErrorProps
|
message,
|
||||||
): JSX.Element => {
|
messageType,
|
||||||
|
showErrorDetails,
|
||||||
|
link,
|
||||||
|
linkText,
|
||||||
|
openNotificationConsole,
|
||||||
|
formError = true,
|
||||||
|
}: PanelInfoErrorProps): JSX.Element => {
|
||||||
let icon: JSX.Element;
|
let icon: JSX.Element;
|
||||||
if (props.messageType === "error") {
|
if (messageType === "error") {
|
||||||
icon = <Icon iconName="StatusErrorFull" className="panelErrorIcon" />;
|
icon = <Icon iconName="StatusErrorFull" className="panelErrorIcon" aria-label="error" />;
|
||||||
} else if (props.messageType === "warning") {
|
} else if (messageType === "warning") {
|
||||||
icon = <Icon iconName="WarningSolid" className="panelWarningIcon" />;
|
icon = <Icon iconName="WarningSolid" className="panelWarningIcon" aria-label="warning" />;
|
||||||
} else if (props.messageType === "info") {
|
} else if (messageType === "info") {
|
||||||
icon = <Icon iconName="InfoSolid" className="panelLargeInfoIcon" />;
|
icon = <Icon iconName="InfoSolid" className="panelLargeInfoIcon" aria-label="Infomation" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack className="panelInfoErrorContainer" horizontal verticalAlign="center">
|
formError && (
|
||||||
{icon}
|
<Stack className="panelInfoErrorContainer" horizontal verticalAlign="center">
|
||||||
<span className="panelWarningErrorDetailsLinkContainer">
|
{icon}
|
||||||
<Text className="panelWarningErrorMessage" variant="small">
|
<span className="panelWarningErrorDetailsLinkContainer">
|
||||||
{props.message}{" "}
|
<Text className="panelWarningErrorMessage" variant="small" aria-label="message">
|
||||||
{props.link && props.linkText && (
|
{message}
|
||||||
<Link target="_blank" href={props.link}>
|
{link && linkText && (
|
||||||
{props.linkText}
|
<Link target="_blank" href={link}>
|
||||||
</Link>
|
{linkText}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
{showErrorDetails && (
|
||||||
|
<a className="paneErrorLink" role="link" onClick={openNotificationConsole}>
|
||||||
|
More details
|
||||||
|
</a>
|
||||||
)}
|
)}
|
||||||
</Text>
|
</span>
|
||||||
{props.showErrorDetails && (
|
</Stack>
|
||||||
<a className="paneErrorLink" role="link" onClick={props.openNotificationConsole}>
|
)
|
||||||
More details
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</Stack>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,13 +12,14 @@ describe("PublishNotebookPaneComponent", () => {
|
|||||||
notebookAuthor: "CosmosDB",
|
notebookAuthor: "CosmosDB",
|
||||||
notebookCreatedDate: "2020-07-17T00:00:00Z",
|
notebookCreatedDate: "2020-07-17T00:00:00Z",
|
||||||
notebookObject: undefined,
|
notebookObject: undefined,
|
||||||
notebookParentDomElement: undefined,
|
notebookContentRef: undefined,
|
||||||
setNotebookName: undefined,
|
setNotebookName: undefined,
|
||||||
setNotebookDescription: undefined,
|
setNotebookDescription: undefined,
|
||||||
setNotebookTags: undefined,
|
setNotebookTags: undefined,
|
||||||
setImageSrc: undefined,
|
setImageSrc: undefined,
|
||||||
onError: undefined,
|
onError: undefined,
|
||||||
clearFormError: undefined,
|
clearFormError: undefined,
|
||||||
|
onTakeSnapshot: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(<PublishNotebookPaneComponent {...props} />);
|
const wrapper = shallow(<PublishNotebookPaneComponent {...props} />);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { ImmutableNotebook, toJS } from "@nteract/commutable";
|
|||||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||||
import { HttpStatusCodes } from "../../../Common/Constants";
|
import { HttpStatusCodes } from "../../../Common/Constants";
|
||||||
import { getErrorMessage, getErrorStack, handleError } from "../../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
import { useNotebookSnapshotStore } from "../../../hooks/useNotebookSnapshotStore";
|
||||||
import { JunoClient } from "../../../Juno/JunoClient";
|
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";
|
||||||
@@ -10,6 +11,7 @@ import { CodeOfConductComponent } from "../../Controls/NotebookGallery/CodeOfCon
|
|||||||
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 {
|
import {
|
||||||
GenericRightPaneComponent,
|
GenericRightPaneComponent,
|
||||||
GenericRightPaneProps,
|
GenericRightPaneProps,
|
||||||
@@ -24,7 +26,8 @@ export interface PublishNotebookPaneAProps {
|
|||||||
name: string;
|
name: string;
|
||||||
author: string;
|
author: string;
|
||||||
notebookContent: string | ImmutableNotebook;
|
notebookContent: string | ImmutableNotebook;
|
||||||
parentDomElement: HTMLElement;
|
notebookContentRef: string;
|
||||||
|
onTakeSnapshot: (request: SnapshotRequest) => void;
|
||||||
}
|
}
|
||||||
export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> = ({
|
export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> = ({
|
||||||
explorer: container,
|
explorer: container,
|
||||||
@@ -33,7 +36,8 @@ export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> =
|
|||||||
name,
|
name,
|
||||||
author,
|
author,
|
||||||
notebookContent,
|
notebookContent,
|
||||||
parentDomElement,
|
notebookContentRef,
|
||||||
|
onTakeSnapshot,
|
||||||
}: PublishNotebookPaneAProps): JSX.Element => {
|
}: PublishNotebookPaneAProps): JSX.Element => {
|
||||||
const [isCodeOfConductAccepted, setIsCodeOfConductAccepted] = useState<boolean>(false);
|
const [isCodeOfConductAccepted, setIsCodeOfConductAccepted] = useState<boolean>(false);
|
||||||
const [content, setContent] = useState<string>("");
|
const [content, setContent] = useState<string>("");
|
||||||
@@ -45,6 +49,7 @@ export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> =
|
|||||||
const [notebookDescription, setNotebookDescription] = useState<string>("");
|
const [notebookDescription, setNotebookDescription] = useState<string>("");
|
||||||
const [notebookTags, setNotebookTags] = useState<string>("");
|
const [notebookTags, setNotebookTags] = useState<string>("");
|
||||||
const [imageSrc, setImageSrc] = useState<string>();
|
const [imageSrc, setImageSrc] = useState<string>();
|
||||||
|
const { snapshot: notebookSnapshot, error: notebookSnapshotError } = useNotebookSnapshotStore();
|
||||||
|
|
||||||
const CodeOfConductAccepted = async () => {
|
const CodeOfConductAccepted = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -74,6 +79,14 @@ export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> =
|
|||||||
setContent(newContent);
|
setContent(newContent);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setImageSrc(notebookSnapshot);
|
||||||
|
}, [notebookSnapshot]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFormError(notebookSnapshotError);
|
||||||
|
}, [notebookSnapshotError]);
|
||||||
|
|
||||||
const submit = async (): Promise<void> => {
|
const submit = async (): Promise<void> => {
|
||||||
const clearPublishingMessage = NotificationConsoleUtils.logConsoleProgress(`Publishing ${name} to gallery`);
|
const clearPublishingMessage = NotificationConsoleUtils.logConsoleProgress(`Publishing ${name} to gallery`);
|
||||||
setIsExecuting(true);
|
setIsExecuting(true);
|
||||||
@@ -178,13 +191,14 @@ export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> =
|
|||||||
notebookAuthor: author,
|
notebookAuthor: author,
|
||||||
notebookCreatedDate: new Date().toISOString(),
|
notebookCreatedDate: new Date().toISOString(),
|
||||||
notebookObject: notebookObject,
|
notebookObject: notebookObject,
|
||||||
notebookParentDomElement: parentDomElement,
|
notebookContentRef,
|
||||||
onError: createFormError,
|
onError: createFormError,
|
||||||
clearFormError: clearFormError,
|
clearFormError: clearFormError,
|
||||||
setNotebookName,
|
setNotebookName,
|
||||||
setNotebookDescription,
|
setNotebookDescription,
|
||||||
setNotebookTags,
|
setNotebookTags,
|
||||||
setImageSrc,
|
setImageSrc,
|
||||||
|
onTakeSnapshot,
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<GenericRightPaneComponent {...props}>
|
<GenericRightPaneComponent {...props}>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Dropdown, IDropdownProps, ITextFieldProps, Stack, Text, TextField } from "@fluentui/react";
|
import { Dropdown, IDropdownProps, ITextFieldProps, Stack, Text, TextField } from "@fluentui/react";
|
||||||
import { ImmutableNotebook } from "@nteract/commutable";
|
import { ImmutableNotebook } from "@nteract/commutable";
|
||||||
import Html2Canvas from "html2canvas";
|
|
||||||
import React, { FunctionComponent, useState } from "react";
|
import React, { FunctionComponent, useState } from "react";
|
||||||
import { GalleryCardComponent } from "../../Controls/NotebookGallery/Cards/GalleryCardComponent";
|
import { GalleryCardComponent } from "../../Controls/NotebookGallery/Cards/GalleryCardComponent";
|
||||||
import * as FileSystemUtil from "../../Notebook/FileSystemUtil";
|
import * as FileSystemUtil from "../../Notebook/FileSystemUtil";
|
||||||
|
import { SnapshotRequest } from "../../Notebook/NotebookComponent/types";
|
||||||
import { NotebookUtil } from "../../Notebook/NotebookUtil";
|
import { NotebookUtil } from "../../Notebook/NotebookUtil";
|
||||||
import "./styled.less";
|
import "./styled.less";
|
||||||
|
|
||||||
@@ -11,17 +11,19 @@ export interface PublishNotebookPaneProps {
|
|||||||
notebookName: string;
|
notebookName: string;
|
||||||
notebookAuthor: string;
|
notebookAuthor: string;
|
||||||
notebookTags: string;
|
notebookTags: string;
|
||||||
imageSrc: string;
|
|
||||||
notebookDescription: string;
|
notebookDescription: string;
|
||||||
notebookCreatedDate: string;
|
notebookCreatedDate: string;
|
||||||
notebookObject: ImmutableNotebook;
|
notebookObject: ImmutableNotebook;
|
||||||
notebookParentDomElement?: HTMLElement;
|
notebookContentRef: string;
|
||||||
|
imageSrc: string;
|
||||||
|
|
||||||
onError: (formError: string, formErrorDetail: string, area: string) => void;
|
onError: (formError: string, formErrorDetail: string, area: string) => void;
|
||||||
clearFormError: () => void;
|
clearFormError: () => void;
|
||||||
setNotebookName: (newValue: string) => void;
|
setNotebookName: (newValue: string) => void;
|
||||||
setNotebookDescription: (newValue: string) => void;
|
setNotebookDescription: (newValue: string) => void;
|
||||||
setNotebookTags: (newValue: string) => void;
|
setNotebookTags: (newValue: string) => void;
|
||||||
setImageSrc: (newValue: string) => void;
|
setImageSrc: (newValue: string) => void;
|
||||||
|
onTakeSnapshot: (request: SnapshotRequest) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ImageTypes {
|
enum ImageTypes {
|
||||||
@@ -34,18 +36,19 @@ enum ImageTypes {
|
|||||||
export const PublishNotebookPaneComponent: FunctionComponent<PublishNotebookPaneProps> = ({
|
export const PublishNotebookPaneComponent: FunctionComponent<PublishNotebookPaneProps> = ({
|
||||||
notebookName,
|
notebookName,
|
||||||
notebookTags,
|
notebookTags,
|
||||||
imageSrc,
|
|
||||||
notebookDescription,
|
notebookDescription,
|
||||||
notebookAuthor,
|
notebookAuthor,
|
||||||
notebookCreatedDate,
|
notebookCreatedDate,
|
||||||
notebookObject,
|
notebookObject,
|
||||||
notebookParentDomElement,
|
notebookContentRef,
|
||||||
|
imageSrc,
|
||||||
onError,
|
onError,
|
||||||
clearFormError,
|
clearFormError,
|
||||||
setNotebookName,
|
setNotebookName,
|
||||||
setNotebookDescription,
|
setNotebookDescription,
|
||||||
setNotebookTags,
|
setNotebookTags,
|
||||||
setImageSrc,
|
setImageSrc,
|
||||||
|
onTakeSnapshot,
|
||||||
}: PublishNotebookPaneProps) => {
|
}: PublishNotebookPaneProps) => {
|
||||||
const [type, setType] = useState<string>(ImageTypes.CustomImage);
|
const [type, setType] = useState<string>(ImageTypes.CustomImage);
|
||||||
const CARD_WIDTH = 256;
|
const CARD_WIDTH = 256;
|
||||||
@@ -63,25 +66,40 @@ export const PublishNotebookPaneComponent: FunctionComponent<PublishNotebookPane
|
|||||||
)}" to the gallery?`;
|
)}" to the gallery?`;
|
||||||
|
|
||||||
const options: ImageTypes[] = [ImageTypes.CustomImage, ImageTypes.Url];
|
const options: ImageTypes[] = [ImageTypes.CustomImage, ImageTypes.Url];
|
||||||
|
if (onTakeSnapshot) {
|
||||||
|
options.push(ImageTypes.TakeScreenshot);
|
||||||
|
if (notebookObject) {
|
||||||
|
options.push(ImageTypes.UseFirstDisplayOutput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const thumbnailSelectorProps: IDropdownProps = {
|
const thumbnailSelectorProps: IDropdownProps = {
|
||||||
label: "Cover image",
|
label: "Cover image",
|
||||||
defaultSelectedKey: ImageTypes.CustomImage,
|
selectedKey: type,
|
||||||
ariaLabel: "Cover image",
|
ariaLabel: "Cover image",
|
||||||
options: options.map((value: string) => ({ text: value, key: value })),
|
options: options.map((value: string) => ({ text: value, key: value })),
|
||||||
onChange: async (event, options) => {
|
onChange: async (event, options) => {
|
||||||
setImageSrc("");
|
setImageSrc("");
|
||||||
clearFormError();
|
clearFormError();
|
||||||
if (options.text === ImageTypes.TakeScreenshot) {
|
if (options.text === ImageTypes.TakeScreenshot) {
|
||||||
try {
|
onTakeSnapshot({
|
||||||
await takeScreenshot(notebookParentDomElement, screenshotErrorHandler);
|
aspectRatio: cardHeightToWidthRatio,
|
||||||
} catch (error) {
|
requestId: new Date().getTime().toString(),
|
||||||
screenshotErrorHandler(error);
|
type: "notebook",
|
||||||
}
|
notebookContentRef,
|
||||||
|
});
|
||||||
} else if (options.text === ImageTypes.UseFirstDisplayOutput) {
|
} else if (options.text === ImageTypes.UseFirstDisplayOutput) {
|
||||||
try {
|
const cellIds = NotebookUtil.findCodeCellWithDisplay(notebookObject);
|
||||||
await takeScreenshot(findFirstOutput(), firstOutputErrorHandler);
|
if (cellIds.length > 0) {
|
||||||
} catch (error) {
|
onTakeSnapshot({
|
||||||
firstOutputErrorHandler(error);
|
aspectRatio: cardHeightToWidthRatio,
|
||||||
|
requestId: new Date().getTime().toString(),
|
||||||
|
type: "celloutput",
|
||||||
|
cellId: cellIds[0],
|
||||||
|
notebookContentRef,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
firstOutputErrorHandler(new Error("Output does not exist for any of the cells."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setType(options.text);
|
setType(options.text);
|
||||||
@@ -97,13 +115,6 @@ export const PublishNotebookPaneComponent: FunctionComponent<PublishNotebookPane
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const screenshotErrorHandler = (error: Error) => {
|
|
||||||
const formError = "Failed to take screen shot";
|
|
||||||
const formErrorDetail = `${error}`;
|
|
||||||
const area = "PublishNotebookPaneComponent/takeScreenshot";
|
|
||||||
onError(formError, formErrorDetail, area);
|
|
||||||
};
|
|
||||||
|
|
||||||
const firstOutputErrorHandler = (error: Error) => {
|
const firstOutputErrorHandler = (error: Error) => {
|
||||||
const formError = "Failed to capture first output";
|
const formError = "Failed to capture first output";
|
||||||
const formErrorDetail = `${error}`;
|
const formErrorDetail = `${error}`;
|
||||||
@@ -111,13 +122,6 @@ export const PublishNotebookPaneComponent: FunctionComponent<PublishNotebookPane
|
|||||||
onError(formError, formErrorDetail, area);
|
onError(formError, formErrorDetail, area);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (notebookParentDomElement) {
|
|
||||||
options.push(ImageTypes.TakeScreenshot);
|
|
||||||
if (notebookObject) {
|
|
||||||
options.push(ImageTypes.UseFirstDisplayOutput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const imageToBase64 = (file: File, updateImageSrc: (result: string) => void) => {
|
const imageToBase64 = (file: File, updateImageSrc: (result: string) => void) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
@@ -133,36 +137,6 @@ export const PublishNotebookPaneComponent: FunctionComponent<PublishNotebookPane
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const takeScreenshot = (target: HTMLElement, onError: (error: Error) => void): void => {
|
|
||||||
const updateImageSrcWithScreenshot = (canvasUrl: string): void => {
|
|
||||||
setImageSrc(canvasUrl);
|
|
||||||
};
|
|
||||||
|
|
||||||
target.scrollIntoView();
|
|
||||||
Html2Canvas(target, {
|
|
||||||
useCORS: true,
|
|
||||||
allowTaint: true,
|
|
||||||
scale: 1,
|
|
||||||
logging: true,
|
|
||||||
})
|
|
||||||
.then((canvas) => {
|
|
||||||
//redraw canvas to fit Card Cover Image dimensions
|
|
||||||
const originalImageData = canvas.toDataURL();
|
|
||||||
const requiredHeight = parseInt(canvas.style.width.split("px")[0]) * cardHeightToWidthRatio;
|
|
||||||
canvas.height = requiredHeight;
|
|
||||||
const context = canvas.getContext("2d");
|
|
||||||
const image = new Image();
|
|
||||||
image.src = originalImageData;
|
|
||||||
image.onload = () => {
|
|
||||||
context.drawImage(image, 0, 0);
|
|
||||||
updateImageSrcWithScreenshot(canvas.toDataURL());
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
onError(error);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderThumbnailSelectors = (type: string) => {
|
const renderThumbnailSelectors = (type: string) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ImageTypes.Url:
|
case ImageTypes.Url:
|
||||||
@@ -198,12 +172,6 @@ export const PublishNotebookPaneComponent: FunctionComponent<PublishNotebookPane
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const findFirstOutput = (): HTMLElement => {
|
|
||||||
const indexOfFirstCodeCellWithDisplay = NotebookUtil.findFirstCodeCellWithDisplay(notebookObject);
|
|
||||||
const cellOutputDomElements = notebookParentDomElement.querySelectorAll<HTMLElement>(".nteract-cell-outputs");
|
|
||||||
return cellOutputDomElements[indexOfFirstCodeCellWithDisplay];
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="publishNotebookPanelContent">
|
<div className="publishNotebookPanelContent">
|
||||||
<Stack className="panelMainContent" tokens={{ childrenGap: 20 }}>
|
<Stack className="panelMainContent" tokens={{ childrenGap: 20 }}>
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ exports[`PublishNotebookPaneComponent renders 1`] = `
|
|||||||
<StackItem>
|
<StackItem>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
ariaLabel="Cover image"
|
ariaLabel="Cover image"
|
||||||
defaultSelectedKey="Custom Image"
|
|
||||||
label="Cover image"
|
label="Cover image"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
options={
|
options={
|
||||||
@@ -67,6 +66,7 @@ exports[`PublishNotebookPaneComponent renders 1`] = `
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
selectedKey="Custom Image"
|
||||||
/>
|
/>
|
||||||
</StackItem>
|
</StackItem>
|
||||||
<StackItem>
|
<StackItem>
|
||||||
|
|||||||
40
src/Explorer/Panes/RightPaneForm/RightPaneForm.test.tsx
Normal file
40
src/Explorer/Panes/RightPaneForm/RightPaneForm.test.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { fireEvent, render, screen } from "@testing-library/react";
|
||||||
|
import { mount, ReactWrapper } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import { RightPaneForm } from "./RightPaneForm";
|
||||||
|
|
||||||
|
const onSubmit = jest.fn();
|
||||||
|
const expandConsole = jest.fn();
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
expandConsole,
|
||||||
|
formError: "",
|
||||||
|
formErrorDetail: "",
|
||||||
|
isExecuting: false,
|
||||||
|
submitButtonText: "Load",
|
||||||
|
onSubmit,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("Right Pane Form", () => {
|
||||||
|
let wrapper: ReactWrapper;
|
||||||
|
|
||||||
|
it("should render Default properly", () => {
|
||||||
|
wrapper = mount(<RightPaneForm {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
it("should call submit method enter in form", () => {
|
||||||
|
render(<RightPaneForm {...props} />);
|
||||||
|
fireEvent.click(screen.getByLabelText("Load"));
|
||||||
|
expect(onSubmit).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
it("should call submit method click on submit button", () => {
|
||||||
|
render(<RightPaneForm {...props} />);
|
||||||
|
fireEvent.click(screen.getByLabelText("Load"));
|
||||||
|
expect(onSubmit).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
it("should render error in header", () => {
|
||||||
|
render(<RightPaneForm {...props} formError="file already Exist" />);
|
||||||
|
expect(screen.getByLabelText("error")).toBeDefined();
|
||||||
|
expect(screen.getByLabelText("message").innerHTML).toEqual("file already Exist");
|
||||||
|
});
|
||||||
|
});
|
||||||
50
src/Explorer/Panes/RightPaneForm/RightPaneForm.tsx
Normal file
50
src/Explorer/Panes/RightPaneForm/RightPaneForm.tsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import React, { FunctionComponent, ReactNode } from "react";
|
||||||
|
import { PanelFooterComponent } from "../PanelFooterComponent";
|
||||||
|
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "../PanelInfoErrorComponent";
|
||||||
|
import { PanelLoadingScreen } from "../PanelLoadingScreen";
|
||||||
|
|
||||||
|
export interface RightPaneFormProps {
|
||||||
|
expandConsole: () => void;
|
||||||
|
formError: string;
|
||||||
|
formErrorDetail: string;
|
||||||
|
isExecuting: boolean;
|
||||||
|
onSubmit: () => void;
|
||||||
|
submitButtonText: string;
|
||||||
|
isSubmitButtonHidden?: boolean;
|
||||||
|
children?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RightPaneForm: FunctionComponent<RightPaneFormProps> = ({
|
||||||
|
expandConsole,
|
||||||
|
formError,
|
||||||
|
formErrorDetail,
|
||||||
|
isExecuting,
|
||||||
|
onSubmit,
|
||||||
|
submitButtonText,
|
||||||
|
isSubmitButtonHidden = false,
|
||||||
|
children,
|
||||||
|
}: RightPaneFormProps) => {
|
||||||
|
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
onSubmit();
|
||||||
|
};
|
||||||
|
|
||||||
|
const panelInfoErrorProps: PanelInfoErrorProps = {
|
||||||
|
messageType: "error",
|
||||||
|
message: formError,
|
||||||
|
formError: formError !== "",
|
||||||
|
showErrorDetails: formErrorDetail !== "",
|
||||||
|
openNotificationConsole: expandConsole,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PanelInfoErrorComponent {...panelInfoErrorProps} />
|
||||||
|
<form className="panelFormWrapper" onSubmit={handleOnSubmit}>
|
||||||
|
{children}
|
||||||
|
{!isSubmitButtonHidden && <PanelFooterComponent buttonLabel={submitButtonText} />}
|
||||||
|
</form>
|
||||||
|
{isExecuting && <PanelLoadingScreen />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,13 @@
|
|||||||
import { Checkbox, ChoiceGroup, IChoiceGroupOption, SpinButton } from "@fluentui/react";
|
import { Checkbox, ChoiceGroup, IChoiceGroupOption, SpinButton } from "@fluentui/react";
|
||||||
import * as Constants from "Common/Constants";
|
import * as Constants from "Common/Constants";
|
||||||
import { Tooltip } from "Common/Tooltip/Tooltip";
|
|
||||||
import { configContext } from "ConfigContext";
|
import { configContext } from "ConfigContext";
|
||||||
import React, { FunctionComponent, MouseEvent, useState } from "react";
|
import React, { FunctionComponent, MouseEvent, useState } from "react";
|
||||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
||||||
import * as StringUtility from "Shared/StringUtility";
|
import { LocalStorageUtility, StorageKey } from "../../../Shared/StorageUtility";
|
||||||
import { userContext } from "UserContext";
|
import * as StringUtility from "../../../Shared/StringUtility";
|
||||||
import { logConsoleInfo } from "Utils/NotificationConsoleUtils";
|
import { userContext } from "../../../UserContext";
|
||||||
import {
|
import { logConsoleInfo } from "../../../Utils/NotificationConsoleUtils";
|
||||||
GenericRightPaneComponent,
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
GenericRightPaneProps,
|
|
||||||
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
|
|
||||||
|
|
||||||
export interface SettingsPaneProps {
|
export interface SettingsPaneProps {
|
||||||
expandConsole: () => void;
|
expandConsole: () => void;
|
||||||
@@ -105,15 +102,12 @@ export const SettingsPane: FunctionComponent<SettingsPaneProps> = ({
|
|||||||
setGraphAutoVizDisabled(option.key);
|
setGraphAutoVizDisabled(option.key);
|
||||||
};
|
};
|
||||||
|
|
||||||
const genericPaneProps: GenericRightPaneProps = {
|
const genericPaneProps: RightPaneFormProps = {
|
||||||
expandConsole,
|
expandConsole,
|
||||||
formError: formErrors,
|
formError: formErrors,
|
||||||
formErrorDetail: "",
|
formErrorDetail: "",
|
||||||
id: "settingspane",
|
|
||||||
isExecuting,
|
isExecuting,
|
||||||
title: "Setting",
|
|
||||||
submitButtonText: "Apply",
|
submitButtonText: "Apply",
|
||||||
onClose: () => closePanel(),
|
|
||||||
onSubmit: () => handlerOnSubmit(undefined),
|
onSubmit: () => handlerOnSubmit(undefined),
|
||||||
};
|
};
|
||||||
const pageOptionList: IChoiceGroupOption[] = [
|
const pageOptionList: IChoiceGroupOption[] = [
|
||||||
@@ -130,17 +124,17 @@ export const SettingsPane: FunctionComponent<SettingsPaneProps> = ({
|
|||||||
setPageOption(option.key);
|
setPageOption(option.key);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<GenericRightPaneComponent {...genericPaneProps}>
|
<RightPaneForm {...genericPaneProps}>
|
||||||
<div className="paneMainContent">
|
<div className="paneMainContent">
|
||||||
{shouldShowQueryPageOptions && (
|
{shouldShowQueryPageOptions && (
|
||||||
<div className="settingsSection">
|
<div className="settingsSection">
|
||||||
<div className="settingsSectionPart pageOptionsPart">
|
<div className="settingsSectionPart pageOptionsPart">
|
||||||
<div className="settingsSectionLabel">
|
<div className="settingsSectionLabel">
|
||||||
Page options
|
Page options
|
||||||
<Tooltip>
|
<InfoTooltip>
|
||||||
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as many
|
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as many
|
||||||
query results per page.
|
query results per page.
|
||||||
</Tooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</div>
|
||||||
<ChoiceGroup selectedKey={pageOption} options={pageOptionList} onChange={handleOnPageOptionChange} />
|
<ChoiceGroup selectedKey={pageOption} options={pageOptionList} onChange={handleOnPageOptionChange} />
|
||||||
</div>
|
</div>
|
||||||
@@ -149,7 +143,7 @@ export const SettingsPane: FunctionComponent<SettingsPaneProps> = ({
|
|||||||
<div className="tabcontent">
|
<div className="tabcontent">
|
||||||
<div className="settingsSectionLabel">
|
<div className="settingsSectionLabel">
|
||||||
Query results per page
|
Query results per page
|
||||||
<Tooltip>Enter the number of query results that should be shown per page.</Tooltip>
|
<InfoTooltip>Enter the number of query results that should be shown per page.</InfoTooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SpinButton
|
<SpinButton
|
||||||
@@ -176,10 +170,10 @@ export const SettingsPane: FunctionComponent<SettingsPaneProps> = ({
|
|||||||
<div className="settingsSectionPart">
|
<div className="settingsSectionPart">
|
||||||
<div className="settingsSectionLabel">
|
<div className="settingsSectionLabel">
|
||||||
Enable cross-partition query
|
Enable cross-partition query
|
||||||
<Tooltip>
|
<InfoTooltip>
|
||||||
Send more than one request while executing a query. More than one request is necessary if the query is
|
Send more than one request while executing a query. More than one request is necessary if the query is
|
||||||
not scoped to single partition key value.
|
not scoped to single partition key value.
|
||||||
</Tooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@@ -199,11 +193,11 @@ export const SettingsPane: FunctionComponent<SettingsPaneProps> = ({
|
|||||||
<div className="settingsSectionPart">
|
<div className="settingsSectionPart">
|
||||||
<div className="settingsSectionLabel">
|
<div className="settingsSectionLabel">
|
||||||
Max degree of parallelism
|
Max degree of parallelism
|
||||||
<Tooltip>
|
<InfoTooltip>
|
||||||
Gets or sets the number of concurrent operations run client side during parallel query execution. A
|
Gets or sets the number of concurrent operations run client side during parallel query execution. A
|
||||||
positive property value limits the number of concurrent operations to the set value. If it is set to
|
positive property value limits the number of concurrent operations to the set value. If it is set to
|
||||||
less than 0, the system automatically decides the number of concurrent operations to run.
|
less than 0, the system automatically decides the number of concurrent operations to run.
|
||||||
</Tooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SpinButton
|
<SpinButton
|
||||||
@@ -227,10 +221,10 @@ export const SettingsPane: FunctionComponent<SettingsPaneProps> = ({
|
|||||||
<div className="settingsSectionPart">
|
<div className="settingsSectionPart">
|
||||||
<div className="settingsSectionLabel">
|
<div className="settingsSectionLabel">
|
||||||
Display Gremlin query results as:
|
Display Gremlin query results as:
|
||||||
<Tooltip>
|
<InfoTooltip>
|
||||||
Select Graph to automatically visualize the query results as a Graph or JSON to display the results as
|
Select Graph to automatically visualize the query results as a Graph or JSON to display the results as
|
||||||
JSON.
|
JSON.
|
||||||
</Tooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ChoiceGroup
|
<ChoiceGroup
|
||||||
@@ -249,6 +243,6 @@ export const SettingsPane: FunctionComponent<SettingsPaneProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GenericRightPaneComponent>
|
</RightPaneForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`Settings Pane should render Default properly 1`] = `
|
exports[`Settings Pane should render Default properly 1`] = `
|
||||||
<GenericRightPaneComponent
|
<RightPaneForm
|
||||||
expandConsole={[Function]}
|
expandConsole={[Function]}
|
||||||
formError=""
|
formError=""
|
||||||
formErrorDetail=""
|
formErrorDetail=""
|
||||||
id="settingspane"
|
|
||||||
isExecuting={false}
|
isExecuting={false}
|
||||||
onClose={[Function]}
|
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
submitButtonText="Apply"
|
submitButtonText="Apply"
|
||||||
title="Setting"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="paneMainContent"
|
className="paneMainContent"
|
||||||
@@ -25,9 +22,9 @@ exports[`Settings Pane should render Default properly 1`] = `
|
|||||||
className="settingsSectionLabel"
|
className="settingsSectionLabel"
|
||||||
>
|
>
|
||||||
Page options
|
Page options
|
||||||
<Tooltip>
|
<InfoTooltip>
|
||||||
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as many query results per page.
|
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as many query results per page.
|
||||||
</Tooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</div>
|
||||||
<StyledChoiceGroup
|
<StyledChoiceGroup
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -56,9 +53,9 @@ exports[`Settings Pane should render Default properly 1`] = `
|
|||||||
className="settingsSectionLabel"
|
className="settingsSectionLabel"
|
||||||
>
|
>
|
||||||
Query results per page
|
Query results per page
|
||||||
<Tooltip>
|
<InfoTooltip>
|
||||||
Enter the number of query results that should be shown per page.
|
Enter the number of query results that should be shown per page.
|
||||||
</Tooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</div>
|
||||||
<StyledSpinButton
|
<StyledSpinButton
|
||||||
ariaLabel="Custom query items per page"
|
ariaLabel="Custom query items per page"
|
||||||
@@ -85,9 +82,9 @@ exports[`Settings Pane should render Default properly 1`] = `
|
|||||||
className="settingsSectionLabel"
|
className="settingsSectionLabel"
|
||||||
>
|
>
|
||||||
Enable cross-partition query
|
Enable cross-partition query
|
||||||
<Tooltip>
|
<InfoTooltip>
|
||||||
Send more than one request while executing a query. More than one request is necessary if the query is not scoped to single partition key value.
|
Send more than one request while executing a query. More than one request is necessary if the query is not scoped to single partition key value.
|
||||||
</Tooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</div>
|
||||||
<StyledCheckboxBase
|
<StyledCheckboxBase
|
||||||
ariaLabel="Enable cross partition query"
|
ariaLabel="Enable cross partition query"
|
||||||
@@ -114,9 +111,9 @@ exports[`Settings Pane should render Default properly 1`] = `
|
|||||||
className="settingsSectionLabel"
|
className="settingsSectionLabel"
|
||||||
>
|
>
|
||||||
Max degree of parallelism
|
Max degree of parallelism
|
||||||
<Tooltip>
|
<InfoTooltip>
|
||||||
Gets or sets the number of concurrent operations run client side during parallel query execution. A positive property value limits the number of concurrent operations to the set value. If it is set to less than 0, the system automatically decides the number of concurrent operations to run.
|
Gets or sets the number of concurrent operations run client side during parallel query execution. A positive property value limits the number of concurrent operations to the set value. If it is set to less than 0, the system automatically decides the number of concurrent operations to run.
|
||||||
</Tooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</div>
|
||||||
<StyledSpinButton
|
<StyledSpinButton
|
||||||
ariaLabel="Max degree of parallelism"
|
ariaLabel="Max degree of parallelism"
|
||||||
@@ -148,20 +145,17 @@ exports[`Settings Pane should render Default properly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GenericRightPaneComponent>
|
</RightPaneForm>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Settings Pane should render Gremlin properly 1`] = `
|
exports[`Settings Pane should render Gremlin properly 1`] = `
|
||||||
<GenericRightPaneComponent
|
<RightPaneForm
|
||||||
expandConsole={[Function]}
|
expandConsole={[Function]}
|
||||||
formError=""
|
formError=""
|
||||||
formErrorDetail=""
|
formErrorDetail=""
|
||||||
id="settingspane"
|
|
||||||
isExecuting={false}
|
isExecuting={false}
|
||||||
onClose={[Function]}
|
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
submitButtonText="Apply"
|
submitButtonText="Apply"
|
||||||
title="Setting"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="paneMainContent"
|
className="paneMainContent"
|
||||||
@@ -176,9 +170,9 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
|
|||||||
className="settingsSectionLabel"
|
className="settingsSectionLabel"
|
||||||
>
|
>
|
||||||
Display Gremlin query results as:
|
Display Gremlin query results as:
|
||||||
<Tooltip>
|
<InfoTooltip>
|
||||||
Select Graph to automatically visualize the query results as a Graph or JSON to display the results as JSON.
|
Select Graph to automatically visualize the query results as a Graph or JSON to display the results as JSON.
|
||||||
</Tooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</div>
|
||||||
<StyledChoiceGroup
|
<StyledChoiceGroup
|
||||||
aria-label="Graph Auto-visualization"
|
aria-label="Graph Auto-visualization"
|
||||||
@@ -214,5 +208,5 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GenericRightPaneComponent>
|
</RightPaneForm>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -54,89 +54,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
"upsellMessageAriaLabel": [Function],
|
"upsellMessageAriaLabel": [Function],
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
AddCollectionPane {
|
|
||||||
"_isSynapseLinkEnabled": [Function],
|
|
||||||
"autoPilotThroughput": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
|
||||||
"canConfigureThroughput": [Function],
|
|
||||||
"canExceedMaximumValue": [Function],
|
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"collectionId": [Function],
|
|
||||||
"collectionIdTitle": [Function],
|
|
||||||
"collectionWithThroughputInShared": [Function],
|
|
||||||
"collectionWithThroughputInSharedTitle": [Function],
|
|
||||||
"container": [Circular],
|
|
||||||
"costsVisible": [Function],
|
|
||||||
"databaseCreateNew": [Function],
|
|
||||||
"databaseCreateNewShared": [Function],
|
|
||||||
"databaseHasSharedOffer": [Function],
|
|
||||||
"databaseId": [Function],
|
|
||||||
"databaseIds": [Function],
|
|
||||||
"dedicatedRequestUnitsUsageCost": [Function],
|
|
||||||
"displayCollectionThroughput": [Function],
|
|
||||||
"firstFieldHasFocus": [Function],
|
|
||||||
"formErrors": [Function],
|
|
||||||
"formErrorsDetails": [Function],
|
|
||||||
"formWarnings": [Function],
|
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "addcollectionpane",
|
|
||||||
"isAnalyticalStorageOn": [Function],
|
|
||||||
"isAutoPilotSelected": [Function],
|
|
||||||
"isEnableMongoCapabilityEnabled": [Function],
|
|
||||||
"isExecuting": [Function],
|
|
||||||
"isFixedStorageSelected": [Function],
|
|
||||||
"isFreeTierAccount": [Function],
|
|
||||||
"isNonTableApi": [Function],
|
|
||||||
"isPreferredApiTable": [Function],
|
|
||||||
"isSharedAutoPilotSelected": [Function],
|
|
||||||
"isSynapseLinkSupported": [Function],
|
|
||||||
"isSynapseLinkUpdating": [Function],
|
|
||||||
"isTemplateReady": [Function],
|
|
||||||
"isTryCosmosDBSubscription": [Function],
|
|
||||||
"isUnlimitedStorageSelected": [Function],
|
|
||||||
"largePartitionKey": [Function],
|
|
||||||
"lowerCasePartitionKeyName": [Function],
|
|
||||||
"maxCollectionsReached": [Function],
|
|
||||||
"maxCollectionsReachedMessage": [Function],
|
|
||||||
"maxThroughputRU": [Function],
|
|
||||||
"minThroughputRU": [Function],
|
|
||||||
"onMoreDetailsKeyPress": [Function],
|
|
||||||
"partitionKey": [Function],
|
|
||||||
"partitionKeyName": [Function],
|
|
||||||
"partitionKeyPattern": [Function],
|
|
||||||
"partitionKeyPlaceholder": [Function],
|
|
||||||
"partitionKeyTitle": [Function],
|
|
||||||
"partitionKeyVisible": [Function],
|
|
||||||
"requestUnitsUsageCost": [Function],
|
|
||||||
"ruToolTipText": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
|
||||||
"shouldUseDatabaseThroughput": [Function],
|
|
||||||
"showAnalyticalStore": [Function],
|
|
||||||
"showEnableSynapseLink": [Function],
|
|
||||||
"showIndexingOptionsForSharedThroughput": [Function],
|
|
||||||
"showUpsellMessage": [Function],
|
|
||||||
"storage": [Function],
|
|
||||||
"throughputDatabase": [Function],
|
|
||||||
"throughputMultiPartition": [Function],
|
|
||||||
"throughputRangeText": [Function],
|
|
||||||
"throughputSinglePartition": [Function],
|
|
||||||
"throughputSpendAck": [Function],
|
|
||||||
"throughputSpendAckText": [Function],
|
|
||||||
"throughputSpendAckVisible": [Function],
|
|
||||||
"title": [Function],
|
|
||||||
"ttl90DaysEnabled": [Function],
|
|
||||||
"uniqueKeys": [Function],
|
|
||||||
"uniqueKeysPlaceholder": [Function],
|
|
||||||
"uniqueKeysVisible": [Function],
|
|
||||||
"upsellAnchorText": [Function],
|
|
||||||
"upsellAnchorUrl": [Function],
|
|
||||||
"upsellMessage": [Function],
|
|
||||||
"upsellMessageAriaLabel": [Function],
|
|
||||||
"useIndexingForSharedThroughput": [Function],
|
|
||||||
"visible": [Function],
|
|
||||||
},
|
|
||||||
GraphStylingPane {
|
GraphStylingPane {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
@@ -205,89 +122,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
],
|
],
|
||||||
"_refreshSparkEnabledStateForAccount": [Function],
|
"_refreshSparkEnabledStateForAccount": [Function],
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"addCollectionPane": AddCollectionPane {
|
|
||||||
"_isSynapseLinkEnabled": [Function],
|
|
||||||
"autoPilotThroughput": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
|
||||||
"canConfigureThroughput": [Function],
|
|
||||||
"canExceedMaximumValue": [Function],
|
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"collectionId": [Function],
|
|
||||||
"collectionIdTitle": [Function],
|
|
||||||
"collectionWithThroughputInShared": [Function],
|
|
||||||
"collectionWithThroughputInSharedTitle": [Function],
|
|
||||||
"container": [Circular],
|
|
||||||
"costsVisible": [Function],
|
|
||||||
"databaseCreateNew": [Function],
|
|
||||||
"databaseCreateNewShared": [Function],
|
|
||||||
"databaseHasSharedOffer": [Function],
|
|
||||||
"databaseId": [Function],
|
|
||||||
"databaseIds": [Function],
|
|
||||||
"dedicatedRequestUnitsUsageCost": [Function],
|
|
||||||
"displayCollectionThroughput": [Function],
|
|
||||||
"firstFieldHasFocus": [Function],
|
|
||||||
"formErrors": [Function],
|
|
||||||
"formErrorsDetails": [Function],
|
|
||||||
"formWarnings": [Function],
|
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "addcollectionpane",
|
|
||||||
"isAnalyticalStorageOn": [Function],
|
|
||||||
"isAutoPilotSelected": [Function],
|
|
||||||
"isEnableMongoCapabilityEnabled": [Function],
|
|
||||||
"isExecuting": [Function],
|
|
||||||
"isFixedStorageSelected": [Function],
|
|
||||||
"isFreeTierAccount": [Function],
|
|
||||||
"isNonTableApi": [Function],
|
|
||||||
"isPreferredApiTable": [Function],
|
|
||||||
"isSharedAutoPilotSelected": [Function],
|
|
||||||
"isSynapseLinkSupported": [Function],
|
|
||||||
"isSynapseLinkUpdating": [Function],
|
|
||||||
"isTemplateReady": [Function],
|
|
||||||
"isTryCosmosDBSubscription": [Function],
|
|
||||||
"isUnlimitedStorageSelected": [Function],
|
|
||||||
"largePartitionKey": [Function],
|
|
||||||
"lowerCasePartitionKeyName": [Function],
|
|
||||||
"maxCollectionsReached": [Function],
|
|
||||||
"maxCollectionsReachedMessage": [Function],
|
|
||||||
"maxThroughputRU": [Function],
|
|
||||||
"minThroughputRU": [Function],
|
|
||||||
"onMoreDetailsKeyPress": [Function],
|
|
||||||
"partitionKey": [Function],
|
|
||||||
"partitionKeyName": [Function],
|
|
||||||
"partitionKeyPattern": [Function],
|
|
||||||
"partitionKeyPlaceholder": [Function],
|
|
||||||
"partitionKeyTitle": [Function],
|
|
||||||
"partitionKeyVisible": [Function],
|
|
||||||
"requestUnitsUsageCost": [Function],
|
|
||||||
"ruToolTipText": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
|
||||||
"shouldUseDatabaseThroughput": [Function],
|
|
||||||
"showAnalyticalStore": [Function],
|
|
||||||
"showEnableSynapseLink": [Function],
|
|
||||||
"showIndexingOptionsForSharedThroughput": [Function],
|
|
||||||
"showUpsellMessage": [Function],
|
|
||||||
"storage": [Function],
|
|
||||||
"throughputDatabase": [Function],
|
|
||||||
"throughputMultiPartition": [Function],
|
|
||||||
"throughputRangeText": [Function],
|
|
||||||
"throughputSinglePartition": [Function],
|
|
||||||
"throughputSpendAck": [Function],
|
|
||||||
"throughputSpendAckText": [Function],
|
|
||||||
"throughputSpendAckVisible": [Function],
|
|
||||||
"title": [Function],
|
|
||||||
"ttl90DaysEnabled": [Function],
|
|
||||||
"uniqueKeys": [Function],
|
|
||||||
"uniqueKeysPlaceholder": [Function],
|
|
||||||
"uniqueKeysVisible": [Function],
|
|
||||||
"upsellAnchorText": [Function],
|
|
||||||
"upsellAnchorUrl": [Function],
|
|
||||||
"upsellMessage": [Function],
|
|
||||||
"upsellMessageAriaLabel": [Function],
|
|
||||||
"useIndexingForSharedThroughput": [Function],
|
|
||||||
"visible": [Function],
|
|
||||||
},
|
|
||||||
"addCollectionText": [Function],
|
"addCollectionText": [Function],
|
||||||
"addDatabasePane": AddDatabasePane {
|
"addDatabasePane": AddDatabasePane {
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
@@ -2365,7 +2199,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
aria-label="Close pane"
|
aria-label="Close pane"
|
||||||
className="ms-Button ms-Button--icon closePaneBtn root-153"
|
className="ms-Button ms-Button--icon closePaneBtn root-53"
|
||||||
data-is-focusable={true}
|
data-is-focusable={true}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
@@ -2378,16 +2212,16 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-flexContainer flexContainer-154"
|
className="ms-Button-flexContainer flexContainer-54"
|
||||||
data-automationid="splitbuttonprimary"
|
data-automationid="splitbuttonprimary"
|
||||||
>
|
>
|
||||||
<FontIcon
|
<FontIcon
|
||||||
className="ms-Button-icon icon-156"
|
className="ms-Button-icon icon-56"
|
||||||
iconName="Cancel"
|
iconName="Cancel"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
aria-hidden={true}
|
aria-hidden={true}
|
||||||
className="ms-Icon root-37 css-161 ms-Button-icon icon-156"
|
className="ms-Icon root-37 css-61 ms-Button-icon icon-56"
|
||||||
data-icon-name="Cancel"
|
data-icon-name="Cancel"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
@@ -2737,7 +2571,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
value=""
|
value=""
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField is-required root-163"
|
className="ms-TextField is-required root-63"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-wrapper"
|
className="ms-TextField-wrapper"
|
||||||
@@ -3028,7 +2862,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
className="ms-Label root-174"
|
className="ms-Label root-74"
|
||||||
htmlFor="TextField3"
|
htmlFor="TextField3"
|
||||||
id="TextFieldLabel5"
|
id="TextFieldLabel5"
|
||||||
>
|
>
|
||||||
@@ -3037,13 +2871,13 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
</LabelBase>
|
</LabelBase>
|
||||||
</StyledLabelBase>
|
</StyledLabelBase>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-fieldGroup fieldGroup-164"
|
className="ms-TextField-fieldGroup fieldGroup-64"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-invalid={false}
|
aria-invalid={false}
|
||||||
aria-labelledby="TextFieldLabel5"
|
aria-labelledby="TextFieldLabel5"
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className="ms-TextField-field field-165"
|
className="ms-TextField-field field-65"
|
||||||
id="TextField3"
|
id="TextField3"
|
||||||
name="collectionIdConfirmation"
|
name="collectionIdConfirmation"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
@@ -4805,7 +4639,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
aria-label="Submit"
|
aria-label="Submit"
|
||||||
className="ms-Button ms-Button--primary genericPaneSubmitBtn root-175"
|
className="ms-Button ms-Button--primary genericPaneSubmitBtn root-75"
|
||||||
data-is-focusable={true}
|
data-is-focusable={true}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
@@ -4823,14 +4657,14 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-flexContainer flexContainer-154"
|
className="ms-Button-flexContainer flexContainer-54"
|
||||||
data-automationid="splitbuttonprimary"
|
data-automationid="splitbuttonprimary"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-textContainer textContainer-155"
|
className="ms-Button-textContainer textContainer-55"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-label label-176"
|
className="ms-Button-label label-76"
|
||||||
id="id__6"
|
id="id__6"
|
||||||
key="id__6"
|
key="id__6"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
<div data-bind="visible: visible, event: { keydown: onPaneKeyDown }">
|
|
||||||
<div class="contextual-pane-out" data-bind="click: close, clickBubble: false"></div>
|
|
||||||
<div class="contextual-pane" id="switchdirectorypane">
|
|
||||||
<!-- Switch Directory -- Start -->
|
|
||||||
<div class="contextual-pane-in">
|
|
||||||
<!-- Switch Directory header - Start -->
|
|
||||||
<div class="firstdivbg headerline">
|
|
||||||
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
|
||||||
<div class="closeImg" role="button" aria-label="Close pane" tabindex="0" data-bind="click: close">
|
|
||||||
<img src="/images/close-black.svg" title="Close" alt="Close" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Switch Directory header - End -->
|
|
||||||
|
|
||||||
<!-- Switch Directory content - Start -->
|
|
||||||
<div class="paneMainContent" data-bind="react: directoryComponentAdapter"></div>
|
|
||||||
<!-- Switch Directory content - Start -->
|
|
||||||
</div>
|
|
||||||
<!-- Switch Directory -- End -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
import * as ko from "knockout";
|
|
||||||
|
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
|
||||||
import { DirectoryListProps } from "../Controls/Directory/DirectoryListComponent";
|
|
||||||
import { DefaultDirectoryDropdownProps } from "../Controls/Directory/DefaultDirectoryDropdownComponent";
|
|
||||||
import { DirectoryComponentAdapter } from "../Controls/Directory/DirectoryComponentAdapter";
|
|
||||||
import SwitchDirectoryPaneTemplate from "./SwitchDirectoryPane.html";
|
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
|
|
||||||
class PaneComponent {
|
|
||||||
constructor(data: any) {
|
|
||||||
return data.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SwitchDirectoryPaneComponent {
|
|
||||||
constructor() {
|
|
||||||
return {
|
|
||||||
viewModel: PaneComponent,
|
|
||||||
template: SwitchDirectoryPaneTemplate,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SwitchDirectoryPane {
|
|
||||||
public id: string;
|
|
||||||
public firstFieldHasFocus: ko.Observable<boolean>;
|
|
||||||
public title: ko.Observable<string>;
|
|
||||||
public visible: ko.Observable<boolean>;
|
|
||||||
|
|
||||||
public directoryComponentAdapter: DirectoryComponentAdapter;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
dropdownProps: ko.Observable<DefaultDirectoryDropdownProps>,
|
|
||||||
listProps: ko.Observable<DirectoryListProps>
|
|
||||||
) {
|
|
||||||
this.id = "switchdirectorypane";
|
|
||||||
this.title = ko.observable("Switch directory");
|
|
||||||
this.visible = ko.observable(false);
|
|
||||||
this.firstFieldHasFocus = ko.observable(false);
|
|
||||||
this.resetData();
|
|
||||||
this.directoryComponentAdapter = new DirectoryComponentAdapter(dropdownProps, listProps);
|
|
||||||
}
|
|
||||||
|
|
||||||
public open() {
|
|
||||||
this.visible(true);
|
|
||||||
this.firstFieldHasFocus(true);
|
|
||||||
this.resizePane();
|
|
||||||
TelemetryProcessor.trace(Action.ContextualPane, ActionModifiers.Open, {
|
|
||||||
paneTitle: this.title(),
|
|
||||||
});
|
|
||||||
|
|
||||||
this.directoryComponentAdapter.forceRender();
|
|
||||||
}
|
|
||||||
|
|
||||||
public close() {
|
|
||||||
this.visible(false);
|
|
||||||
this.resetData();
|
|
||||||
this.directoryComponentAdapter.forceRender();
|
|
||||||
}
|
|
||||||
|
|
||||||
public resetData() {
|
|
||||||
this.firstFieldHasFocus(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onCloseKeyPress(source: any, event: KeyboardEvent): void {
|
|
||||||
if (event.key === " " || event.key === "Enter") {
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public onPaneKeyDown(source: any, event: KeyboardEvent): boolean {
|
|
||||||
if (event.key === "Escape") {
|
|
||||||
this.close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private resizePane(): void {
|
|
||||||
const paneElement: HTMLElement = document.getElementById(this.id);
|
|
||||||
const headerElement: HTMLElement = document.getElementsByTagName("header")[0];
|
|
||||||
const newPaneElementHeight = window.innerHeight - headerElement.offsetHeight;
|
|
||||||
|
|
||||||
paneElement.style.height = `${newPaneElementHeight}px`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,10 +2,7 @@ import { Upload } from "Common/Upload/Upload";
|
|||||||
import React, { ChangeEvent, FunctionComponent, useState } from "react";
|
import React, { ChangeEvent, FunctionComponent, useState } from "react";
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "Utils/NotificationConsoleUtils";
|
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "Utils/NotificationConsoleUtils";
|
||||||
import { NotebookContentItem } from "../../Notebook/NotebookContentItem";
|
import { NotebookContentItem } from "../../Notebook/NotebookContentItem";
|
||||||
import {
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
GenericRightPaneComponent,
|
|
||||||
GenericRightPaneProps,
|
|
||||||
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
|
|
||||||
|
|
||||||
export interface UploadFilePanelProps {
|
export interface UploadFilePanelProps {
|
||||||
expandConsole: () => void;
|
expandConsole: () => void;
|
||||||
@@ -18,9 +15,6 @@ export const UploadFilePane: FunctionComponent<UploadFilePanelProps> = ({
|
|||||||
closePanel,
|
closePanel,
|
||||||
uploadFile,
|
uploadFile,
|
||||||
}: UploadFilePanelProps) => {
|
}: UploadFilePanelProps) => {
|
||||||
const title = "Upload file to notebook server";
|
|
||||||
const submitButtonLabel = "Upload";
|
|
||||||
const selectFileInputLabel = "Select file to upload";
|
|
||||||
const extensions: string = undefined; //ex. ".ipynb"
|
const extensions: string = undefined; //ex. ".ipynb"
|
||||||
const errorMessage = "Could not upload file";
|
const errorMessage = "Could not upload file";
|
||||||
const inProgressMessage = "Uploading file to notebook server";
|
const inProgressMessage = "Uploading file to notebook server";
|
||||||
@@ -42,11 +36,8 @@ export const UploadFilePane: FunctionComponent<UploadFilePanelProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const file: File = files.item(0);
|
const file: File = files.item(0);
|
||||||
// const id: string = logConsoleProgress(
|
|
||||||
// `${inProgressMessage}: ${file.name}`
|
|
||||||
// );
|
|
||||||
|
|
||||||
logConsoleProgress(`${inProgressMessage}: ${file.name}`);
|
const clearMessage = logConsoleProgress(`${inProgressMessage}: ${file.name}`);
|
||||||
|
|
||||||
setIsExecuting(true);
|
setIsExecuting(true);
|
||||||
|
|
||||||
@@ -64,7 +55,7 @@ export const UploadFilePane: FunctionComponent<UploadFilePanelProps> = ({
|
|||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setIsExecuting(false);
|
setIsExecuting(false);
|
||||||
// clearInProgressMessageWithId(id);
|
clearMessage();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -91,23 +82,20 @@ export const UploadFilePane: FunctionComponent<UploadFilePanelProps> = ({
|
|||||||
return uploadFile(file.name, fileContent);
|
return uploadFile(file.name, fileContent);
|
||||||
};
|
};
|
||||||
|
|
||||||
const genericPaneProps: GenericRightPaneProps = {
|
const genericPaneProps: RightPaneFormProps = {
|
||||||
expandConsole,
|
expandConsole,
|
||||||
formError: formErrors,
|
formError: formErrors,
|
||||||
formErrorDetail: formErrorsDetails,
|
formErrorDetail: formErrorsDetails,
|
||||||
id: "uploadFilePane",
|
|
||||||
isExecuting: isExecuting,
|
isExecuting: isExecuting,
|
||||||
title,
|
submitButtonText: "Upload",
|
||||||
submitButtonText: submitButtonLabel,
|
|
||||||
onClose: closePanel,
|
|
||||||
onSubmit: submit,
|
onSubmit: submit,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GenericRightPaneComponent {...genericPaneProps}>
|
<RightPaneForm {...genericPaneProps}>
|
||||||
<div className="paneMainContent">
|
<div className="paneMainContent">
|
||||||
<Upload label={selectFileInputLabel} accept={extensions} onUpload={updateSelectedFiles} />
|
<Upload label="Select file to upload" accept={extensions} onUpload={updateSelectedFiles} />
|
||||||
</div>
|
</div>
|
||||||
</GenericRightPaneComponent>
|
</RightPaneForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,34 +2,16 @@ import { DetailsList, DetailsListLayoutMode, IColumn, SelectionMode } from "@flu
|
|||||||
import { Upload } from "Common/Upload/Upload";
|
import { Upload } from "Common/Upload/Upload";
|
||||||
import { UploadDetailsRecord } from "Contracts/ViewModels";
|
import { UploadDetailsRecord } from "Contracts/ViewModels";
|
||||||
import React, { ChangeEvent, FunctionComponent, useState } from "react";
|
import React, { ChangeEvent, FunctionComponent, useState } from "react";
|
||||||
import { userContext } from "UserContext";
|
|
||||||
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { getErrorMessage } from "../../Tables/Utilities";
|
import { getErrorMessage } from "../../Tables/Utilities";
|
||||||
import {
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
GenericRightPaneComponent,
|
|
||||||
GenericRightPaneProps,
|
|
||||||
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
|
|
||||||
|
|
||||||
export interface UploadItemsPaneProps {
|
export interface UploadItemsPaneProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
closePanel: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTitle = (): string => {
|
export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ explorer }: UploadItemsPaneProps) => {
|
||||||
if (userContext.apiType === "Cassandra" || userContext.apiType === "Tables") {
|
|
||||||
return "Upload Tables";
|
|
||||||
} else if (userContext.apiType === "Gremlin") {
|
|
||||||
return "Upload Graph";
|
|
||||||
} else {
|
|
||||||
return "Upload Items";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({
|
|
||||||
explorer,
|
|
||||||
closePanel,
|
|
||||||
}: UploadItemsPaneProps) => {
|
|
||||||
const [files, setFiles] = useState<FileList>();
|
const [files, setFiles] = useState<FileList>();
|
||||||
const [uploadFileData, setUploadFileData] = useState<UploadDetailsRecord[]>([]);
|
const [uploadFileData, setUploadFileData] = useState<UploadDetailsRecord[]>([]);
|
||||||
const [formError, setFormError] = useState<string>("");
|
const [formError, setFormError] = useState<string>("");
|
||||||
@@ -70,15 +52,12 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({
|
|||||||
setFiles(event.target.files);
|
setFiles(event.target.files);
|
||||||
};
|
};
|
||||||
|
|
||||||
const genericPaneProps: GenericRightPaneProps = {
|
const genericPaneProps: RightPaneFormProps = {
|
||||||
expandConsole: () => explorer.expandConsole(),
|
expandConsole: () => explorer.expandConsole(),
|
||||||
formError,
|
formError,
|
||||||
formErrorDetail,
|
formErrorDetail,
|
||||||
id: "uploaditemspane",
|
|
||||||
isExecuting: isExecuting,
|
isExecuting: isExecuting,
|
||||||
title: getTitle(),
|
|
||||||
submitButtonText: "Upload",
|
submitButtonText: "Upload",
|
||||||
onClose: closePanel,
|
|
||||||
onSubmit,
|
onSubmit,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -113,7 +92,7 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GenericRightPaneComponent {...genericPaneProps}>
|
<RightPaneForm {...genericPaneProps}>
|
||||||
<div className="paneMainContent">
|
<div className="paneMainContent">
|
||||||
<Upload
|
<Upload
|
||||||
label="Select JSON Files"
|
label="Select JSON Files"
|
||||||
@@ -139,6 +118,6 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</GenericRightPaneComponent>
|
</RightPaneForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`Upload Items Pane should render Default properly 1`] = `
|
exports[`Upload Items Pane should render Default properly 1`] = `
|
||||||
<GenericRightPaneComponent
|
<RightPaneForm
|
||||||
expandConsole={[Function]}
|
expandConsole={[Function]}
|
||||||
formError=""
|
formError=""
|
||||||
formErrorDetail=""
|
formErrorDetail=""
|
||||||
id="uploaditemspane"
|
|
||||||
onClose={[Function]}
|
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
submitButtonText="Upload"
|
submitButtonText="Upload"
|
||||||
title="Upload Items"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="paneMainContent"
|
className="paneMainContent"
|
||||||
@@ -20,10 +17,8 @@ exports[`Upload Items Pane should render Default properly 1`] = `
|
|||||||
multiple={true}
|
multiple={true}
|
||||||
onUpload={[Function]}
|
onUpload={[Function]}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
tooltip="Select one or more JSON files to upload. Each file can contain a single JSON document or an array of JSON
|
tooltip="Select one or more JSON files to upload. Each file can contain a single JSON document or an array of JSON documents. The combined size of all files in an individual upload operation must be less than 2 MB. You can perform multiple upload operations for larger data sets."
|
||||||
documents. The combined size of all files in an individual upload operation must be less than 2 MB. You
|
|
||||||
can perform multiple upload operations for larger data sets."
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</GenericRightPaneComponent>
|
</RightPaneForm>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -52,89 +52,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
"upsellMessageAriaLabel": [Function],
|
"upsellMessageAriaLabel": [Function],
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
AddCollectionPane {
|
|
||||||
"_isSynapseLinkEnabled": [Function],
|
|
||||||
"autoPilotThroughput": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
|
||||||
"canConfigureThroughput": [Function],
|
|
||||||
"canExceedMaximumValue": [Function],
|
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"collectionId": [Function],
|
|
||||||
"collectionIdTitle": [Function],
|
|
||||||
"collectionWithThroughputInShared": [Function],
|
|
||||||
"collectionWithThroughputInSharedTitle": [Function],
|
|
||||||
"container": [Circular],
|
|
||||||
"costsVisible": [Function],
|
|
||||||
"databaseCreateNew": [Function],
|
|
||||||
"databaseCreateNewShared": [Function],
|
|
||||||
"databaseHasSharedOffer": [Function],
|
|
||||||
"databaseId": [Function],
|
|
||||||
"databaseIds": [Function],
|
|
||||||
"dedicatedRequestUnitsUsageCost": [Function],
|
|
||||||
"displayCollectionThroughput": [Function],
|
|
||||||
"firstFieldHasFocus": [Function],
|
|
||||||
"formErrors": [Function],
|
|
||||||
"formErrorsDetails": [Function],
|
|
||||||
"formWarnings": [Function],
|
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "addcollectionpane",
|
|
||||||
"isAnalyticalStorageOn": [Function],
|
|
||||||
"isAutoPilotSelected": [Function],
|
|
||||||
"isEnableMongoCapabilityEnabled": [Function],
|
|
||||||
"isExecuting": [Function],
|
|
||||||
"isFixedStorageSelected": [Function],
|
|
||||||
"isFreeTierAccount": [Function],
|
|
||||||
"isNonTableApi": [Function],
|
|
||||||
"isPreferredApiTable": [Function],
|
|
||||||
"isSharedAutoPilotSelected": [Function],
|
|
||||||
"isSynapseLinkSupported": [Function],
|
|
||||||
"isSynapseLinkUpdating": [Function],
|
|
||||||
"isTemplateReady": [Function],
|
|
||||||
"isTryCosmosDBSubscription": [Function],
|
|
||||||
"isUnlimitedStorageSelected": [Function],
|
|
||||||
"largePartitionKey": [Function],
|
|
||||||
"lowerCasePartitionKeyName": [Function],
|
|
||||||
"maxCollectionsReached": [Function],
|
|
||||||
"maxCollectionsReachedMessage": [Function],
|
|
||||||
"maxThroughputRU": [Function],
|
|
||||||
"minThroughputRU": [Function],
|
|
||||||
"onMoreDetailsKeyPress": [Function],
|
|
||||||
"partitionKey": [Function],
|
|
||||||
"partitionKeyName": [Function],
|
|
||||||
"partitionKeyPattern": [Function],
|
|
||||||
"partitionKeyPlaceholder": [Function],
|
|
||||||
"partitionKeyTitle": [Function],
|
|
||||||
"partitionKeyVisible": [Function],
|
|
||||||
"requestUnitsUsageCost": [Function],
|
|
||||||
"ruToolTipText": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
|
||||||
"shouldUseDatabaseThroughput": [Function],
|
|
||||||
"showAnalyticalStore": [Function],
|
|
||||||
"showEnableSynapseLink": [Function],
|
|
||||||
"showIndexingOptionsForSharedThroughput": [Function],
|
|
||||||
"showUpsellMessage": [Function],
|
|
||||||
"storage": [Function],
|
|
||||||
"throughputDatabase": [Function],
|
|
||||||
"throughputMultiPartition": [Function],
|
|
||||||
"throughputRangeText": [Function],
|
|
||||||
"throughputSinglePartition": [Function],
|
|
||||||
"throughputSpendAck": [Function],
|
|
||||||
"throughputSpendAckText": [Function],
|
|
||||||
"throughputSpendAckVisible": [Function],
|
|
||||||
"title": [Function],
|
|
||||||
"ttl90DaysEnabled": [Function],
|
|
||||||
"uniqueKeys": [Function],
|
|
||||||
"uniqueKeysPlaceholder": [Function],
|
|
||||||
"uniqueKeysVisible": [Function],
|
|
||||||
"upsellAnchorText": [Function],
|
|
||||||
"upsellAnchorUrl": [Function],
|
|
||||||
"upsellMessage": [Function],
|
|
||||||
"upsellMessageAriaLabel": [Function],
|
|
||||||
"useIndexingForSharedThroughput": [Function],
|
|
||||||
"visible": [Function],
|
|
||||||
},
|
|
||||||
GraphStylingPane {
|
GraphStylingPane {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
@@ -203,89 +120,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
],
|
],
|
||||||
"_refreshSparkEnabledStateForAccount": [Function],
|
"_refreshSparkEnabledStateForAccount": [Function],
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"addCollectionPane": AddCollectionPane {
|
|
||||||
"_isSynapseLinkEnabled": [Function],
|
|
||||||
"autoPilotThroughput": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
|
||||||
"canConfigureThroughput": [Function],
|
|
||||||
"canExceedMaximumValue": [Function],
|
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"collectionId": [Function],
|
|
||||||
"collectionIdTitle": [Function],
|
|
||||||
"collectionWithThroughputInShared": [Function],
|
|
||||||
"collectionWithThroughputInSharedTitle": [Function],
|
|
||||||
"container": [Circular],
|
|
||||||
"costsVisible": [Function],
|
|
||||||
"databaseCreateNew": [Function],
|
|
||||||
"databaseCreateNewShared": [Function],
|
|
||||||
"databaseHasSharedOffer": [Function],
|
|
||||||
"databaseId": [Function],
|
|
||||||
"databaseIds": [Function],
|
|
||||||
"dedicatedRequestUnitsUsageCost": [Function],
|
|
||||||
"displayCollectionThroughput": [Function],
|
|
||||||
"firstFieldHasFocus": [Function],
|
|
||||||
"formErrors": [Function],
|
|
||||||
"formErrorsDetails": [Function],
|
|
||||||
"formWarnings": [Function],
|
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "addcollectionpane",
|
|
||||||
"isAnalyticalStorageOn": [Function],
|
|
||||||
"isAutoPilotSelected": [Function],
|
|
||||||
"isEnableMongoCapabilityEnabled": [Function],
|
|
||||||
"isExecuting": [Function],
|
|
||||||
"isFixedStorageSelected": [Function],
|
|
||||||
"isFreeTierAccount": [Function],
|
|
||||||
"isNonTableApi": [Function],
|
|
||||||
"isPreferredApiTable": [Function],
|
|
||||||
"isSharedAutoPilotSelected": [Function],
|
|
||||||
"isSynapseLinkSupported": [Function],
|
|
||||||
"isSynapseLinkUpdating": [Function],
|
|
||||||
"isTemplateReady": [Function],
|
|
||||||
"isTryCosmosDBSubscription": [Function],
|
|
||||||
"isUnlimitedStorageSelected": [Function],
|
|
||||||
"largePartitionKey": [Function],
|
|
||||||
"lowerCasePartitionKeyName": [Function],
|
|
||||||
"maxCollectionsReached": [Function],
|
|
||||||
"maxCollectionsReachedMessage": [Function],
|
|
||||||
"maxThroughputRU": [Function],
|
|
||||||
"minThroughputRU": [Function],
|
|
||||||
"onMoreDetailsKeyPress": [Function],
|
|
||||||
"partitionKey": [Function],
|
|
||||||
"partitionKeyName": [Function],
|
|
||||||
"partitionKeyPattern": [Function],
|
|
||||||
"partitionKeyPlaceholder": [Function],
|
|
||||||
"partitionKeyTitle": [Function],
|
|
||||||
"partitionKeyVisible": [Function],
|
|
||||||
"requestUnitsUsageCost": [Function],
|
|
||||||
"ruToolTipText": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
|
||||||
"shouldUseDatabaseThroughput": [Function],
|
|
||||||
"showAnalyticalStore": [Function],
|
|
||||||
"showEnableSynapseLink": [Function],
|
|
||||||
"showIndexingOptionsForSharedThroughput": [Function],
|
|
||||||
"showUpsellMessage": [Function],
|
|
||||||
"storage": [Function],
|
|
||||||
"throughputDatabase": [Function],
|
|
||||||
"throughputMultiPartition": [Function],
|
|
||||||
"throughputRangeText": [Function],
|
|
||||||
"throughputSinglePartition": [Function],
|
|
||||||
"throughputSpendAck": [Function],
|
|
||||||
"throughputSpendAckText": [Function],
|
|
||||||
"throughputSpendAckVisible": [Function],
|
|
||||||
"title": [Function],
|
|
||||||
"ttl90DaysEnabled": [Function],
|
|
||||||
"uniqueKeys": [Function],
|
|
||||||
"uniqueKeysPlaceholder": [Function],
|
|
||||||
"uniqueKeysVisible": [Function],
|
|
||||||
"upsellAnchorText": [Function],
|
|
||||||
"upsellAnchorUrl": [Function],
|
|
||||||
"upsellMessage": [Function],
|
|
||||||
"upsellMessageAriaLabel": [Function],
|
|
||||||
"useIndexingForSharedThroughput": [Function],
|
|
||||||
"visible": [Function],
|
|
||||||
},
|
|
||||||
"addCollectionText": [Function],
|
"addCollectionText": [Function],
|
||||||
"addDatabasePane": AddDatabasePane {
|
"addDatabasePane": AddDatabasePane {
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
@@ -1291,14 +1125,16 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
verticalAlign="center"
|
verticalAlign="center"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-Stack panelInfoErrorContainer css-202"
|
className="ms-Stack panelInfoErrorContainer css-102"
|
||||||
>
|
>
|
||||||
<StyledIconBase
|
<StyledIconBase
|
||||||
|
aria-label="warning"
|
||||||
className="panelWarningIcon"
|
className="panelWarningIcon"
|
||||||
iconName="WarningSolid"
|
iconName="WarningSolid"
|
||||||
key=".0:$.0"
|
key=".0:$.0"
|
||||||
>
|
>
|
||||||
<IconBase
|
<IconBase
|
||||||
|
aria-label="warning"
|
||||||
className="panelWarningIcon"
|
className="panelWarningIcon"
|
||||||
iconName="WarningSolid"
|
iconName="WarningSolid"
|
||||||
styles={[Function]}
|
styles={[Function]}
|
||||||
@@ -1577,9 +1413,10 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
aria-hidden={true}
|
aria-label="warning"
|
||||||
className="panelWarningIcon root-204"
|
className="panelWarningIcon root-104"
|
||||||
data-icon-name="WarningSolid"
|
data-icon-name="WarningSolid"
|
||||||
|
role="img"
|
||||||
>
|
>
|
||||||
|
|
||||||
</i>
|
</i>
|
||||||
@@ -1590,14 +1427,15 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
key=".0:$.1"
|
key=".0:$.1"
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
|
aria-label="message"
|
||||||
className="panelWarningErrorMessage"
|
className="panelWarningErrorMessage"
|
||||||
variant="small"
|
variant="small"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="panelWarningErrorMessage css-205"
|
aria-label="message"
|
||||||
|
className="panelWarningErrorMessage css-105"
|
||||||
>
|
>
|
||||||
Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.
|
Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
</Text>
|
</Text>
|
||||||
</span>
|
</span>
|
||||||
@@ -1619,7 +1457,7 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
variant="small"
|
variant="small"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="css-205"
|
className="css-105"
|
||||||
>
|
>
|
||||||
Confirm by typing the database id
|
Confirm by typing the database id
|
||||||
</span>
|
</span>
|
||||||
@@ -1919,18 +1757,18 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
validateOnLoad={true}
|
validateOnLoad={true}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField root-207"
|
className="ms-TextField root-107"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-wrapper"
|
className="ms-TextField-wrapper"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-fieldGroup fieldGroup-208"
|
className="ms-TextField-fieldGroup fieldGroup-108"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-invalid={false}
|
aria-invalid={false}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className="ms-TextField-field field-209"
|
className="ms-TextField-field field-109"
|
||||||
id="confirmDatabaseId"
|
id="confirmDatabaseId"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -1953,7 +1791,7 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
variant="small"
|
variant="small"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="css-226"
|
className="css-126"
|
||||||
>
|
>
|
||||||
Help us improve Azure Cosmos DB!
|
Help us improve Azure Cosmos DB!
|
||||||
</span>
|
</span>
|
||||||
@@ -1963,7 +1801,7 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
variant="small"
|
variant="small"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="css-226"
|
className="css-126"
|
||||||
>
|
>
|
||||||
What is the reason why you are deleting this database?
|
What is the reason why you are deleting this database?
|
||||||
</span>
|
</span>
|
||||||
@@ -2265,17 +2103,17 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
validateOnLoad={true}
|
validateOnLoad={true}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField ms-TextField--multiline root-207"
|
className="ms-TextField ms-TextField--multiline root-107"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-wrapper"
|
className="ms-TextField-wrapper"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-fieldGroup fieldGroup-227"
|
className="ms-TextField-fieldGroup fieldGroup-127"
|
||||||
>
|
>
|
||||||
<textarea
|
<textarea
|
||||||
aria-invalid={false}
|
aria-invalid={false}
|
||||||
className="ms-TextField-field field-228"
|
className="ms-TextField-field field-128"
|
||||||
id="deleteDatabaseFeedbackInput"
|
id="deleteDatabaseFeedbackInput"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -2298,11 +2136,13 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
className="panelFooter"
|
className="panelFooter"
|
||||||
>
|
>
|
||||||
<CustomizedPrimaryButton
|
<CustomizedPrimaryButton
|
||||||
|
ariaLabel="OK"
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="OK"
|
text="OK"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
|
ariaLabel="OK"
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="OK"
|
text="OK"
|
||||||
theme={
|
theme={
|
||||||
@@ -2581,6 +2421,7 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
<CustomizedDefaultButton
|
<CustomizedDefaultButton
|
||||||
|
ariaLabel="OK"
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
@@ -2861,6 +2702,7 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
|
ariaLabel="OK"
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
@@ -3141,6 +2983,7 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
|
ariaLabel="OK"
|
||||||
baseClassName="ms-Button"
|
baseClassName="ms-Button"
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -3995,7 +3838,8 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
variantClassName="ms-Button--primary"
|
variantClassName="ms-Button--primary"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="ms-Button ms-Button--primary root-218"
|
aria-label="OK"
|
||||||
|
className="ms-Button ms-Button--primary root-118"
|
||||||
data-is-focusable={true}
|
data-is-focusable={true}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
@@ -4007,14 +3851,14 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
|||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-flexContainer flexContainer-219"
|
className="ms-Button-flexContainer flexContainer-119"
|
||||||
data-automationid="splitbuttonprimary"
|
data-automationid="splitbuttonprimary"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-textContainer textContainer-220"
|
className="ms-Button-textContainer textContainer-120"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-label label-222"
|
className="ms-Button-label label-122"
|
||||||
id="id__3"
|
id="id__3"
|
||||||
key="id__3"
|
key="id__3"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
iconSrc: AddDatabaseIcon,
|
iconSrc: AddDatabaseIcon,
|
||||||
title: this.container.addDatabaseText(),
|
title: this.container.addDatabaseText(),
|
||||||
description: null,
|
description: null,
|
||||||
onClick: () => this.container.addDatabasePane.open(),
|
onClick: () => this.container.openAddDatabasePane(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import * as _ from "underscore";
|
|||||||
import { ArmApiVersions } from "../../Common/Constants";
|
import { ArmApiVersions } from "../../Common/Constants";
|
||||||
import { configContext } from "../../ConfigContext";
|
import { configContext } from "../../ConfigContext";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
|
import { useNotebookSnapshotStore } from "../../hooks/useNotebookSnapshotStore";
|
||||||
import { trackEvent } from "../../Shared/appInsights";
|
import { trackEvent } from "../../Shared/appInsights";
|
||||||
import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
@@ -24,7 +25,9 @@ import { logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
|||||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||||
import * as CommandBarComponentButtonFactory from "../Menus/CommandBar/CommandBarComponentButtonFactory";
|
import * as CommandBarComponentButtonFactory from "../Menus/CommandBar/CommandBarComponentButtonFactory";
|
||||||
import { KernelSpecsDisplay } from "../Notebook/NotebookClientV2";
|
import { KernelSpecsDisplay } from "../Notebook/NotebookClientV2";
|
||||||
|
import * as CdbActions from "../Notebook/NotebookComponent/actions";
|
||||||
import { NotebookComponentAdapter } from "../Notebook/NotebookComponent/NotebookComponentAdapter";
|
import { NotebookComponentAdapter } from "../Notebook/NotebookComponent/NotebookComponentAdapter";
|
||||||
|
import { CdbAppState, SnapshotRequest } from "../Notebook/NotebookComponent/types";
|
||||||
import { NotebookContentItem } from "../Notebook/NotebookContentItem";
|
import { NotebookContentItem } from "../Notebook/NotebookContentItem";
|
||||||
import NotebookTabBase, { NotebookTabBaseOptions } from "./NotebookTabBase";
|
import NotebookTabBase, { NotebookTabBaseOptions } from "./NotebookTabBase";
|
||||||
|
|
||||||
@@ -458,11 +461,32 @@ export default class NotebookTabV2 extends NotebookTabBase {
|
|||||||
source: Source.CommandBarMenu,
|
source: Source.CommandBarMenu,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const notebookReduxStore = NotebookTabV2.clientManager.getStore();
|
||||||
|
const unsubscribe = notebookReduxStore.subscribe(() => {
|
||||||
|
const cdbState = (notebookReduxStore.getState() as CdbAppState).cdb;
|
||||||
|
useNotebookSnapshotStore.setState({
|
||||||
|
snapshot: cdbState.notebookSnapshot?.imageSrc,
|
||||||
|
error: cdbState.notebookSnapshotError,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const notebookContent = this.notebookComponentAdapter.getContent();
|
const notebookContent = this.notebookComponentAdapter.getContent();
|
||||||
|
const notebookContentRef = this.notebookComponentAdapter.contentRef;
|
||||||
|
const onPanelClose = (): void => {
|
||||||
|
unsubscribe();
|
||||||
|
useNotebookSnapshotStore.setState({
|
||||||
|
snapshot: undefined,
|
||||||
|
error: undefined,
|
||||||
|
});
|
||||||
|
notebookReduxStore.dispatch(CdbActions.takeNotebookSnapshot(undefined));
|
||||||
|
};
|
||||||
|
|
||||||
await this.container.publishNotebook(
|
await this.container.publishNotebook(
|
||||||
notebookContent.name,
|
notebookContent.name,
|
||||||
notebookContent.content,
|
notebookContent.content,
|
||||||
this.notebookComponentAdapter.getNotebookParentElement()
|
notebookContentRef,
|
||||||
|
(request: SnapshotRequest) => notebookReduxStore.dispatch(CdbActions.takeNotebookSnapshot(request)),
|
||||||
|
onPanelClose
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ import "./Explorer/Controls/DynamicList/DynamicListComponent.less";
|
|||||||
import "./Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.less";
|
import "./Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.less";
|
||||||
import "./Explorer/Controls/JsonEditor/JsonEditorComponent.less";
|
import "./Explorer/Controls/JsonEditor/JsonEditorComponent.less";
|
||||||
import "./Explorer/Controls/Notebook/NotebookTerminalComponent.less";
|
import "./Explorer/Controls/Notebook/NotebookTerminalComponent.less";
|
||||||
import "./Explorer/Controls/ThroughputInput/ThroughputInput.less";
|
|
||||||
import "./Explorer/Controls/TreeComponent/treeComponent.less";
|
import "./Explorer/Controls/TreeComponent/treeComponent.less";
|
||||||
import { ExplorerParams } from "./Explorer/Explorer";
|
import { ExplorerParams } from "./Explorer/Explorer";
|
||||||
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
|
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
|
||||||
@@ -228,7 +227,6 @@ const App: React.FunctionComponent = () => {
|
|||||||
isConsoleExpanded={isNotificationConsoleExpanded}
|
isConsoleExpanded={isNotificationConsoleExpanded}
|
||||||
/>
|
/>
|
||||||
<div data-bind='component: { name: "add-database-pane", params: {data: addDatabasePane} }' />
|
<div data-bind='component: { name: "add-database-pane", params: {data: addDatabasePane} }' />
|
||||||
<div data-bind='component: { name: "add-collection-pane", params: { data: addCollectionPane} }' />
|
|
||||||
<div data-bind='component: { name: "graph-styling-pane", params: { data: graphStylingPane} }' />
|
<div data-bind='component: { name: "graph-styling-pane", params: { data: graphStylingPane} }' />
|
||||||
<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} />}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { TFunction } from "i18next";
|
|
||||||
import {
|
import {
|
||||||
CommandBar,
|
CommandBar,
|
||||||
ICommandBarItemProps,
|
ICommandBarItemProps,
|
||||||
@@ -10,6 +9,7 @@ import {
|
|||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
|
import { TFunction } from "i18next";
|
||||||
import promiseRetry, { AbortError } from "p-retry";
|
import promiseRetry, { AbortError } from "p-retry";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { WithTranslation } from "react-i18next";
|
import { WithTranslation } from "react-i18next";
|
||||||
@@ -130,10 +130,7 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
|
|||||||
|
|
||||||
const initialValues = await this.props.descriptor.initialize();
|
const initialValues = await this.props.descriptor.initialize();
|
||||||
this.props.descriptor.inputNames.map((inputName) => {
|
this.props.descriptor.inputNames.map((inputName) => {
|
||||||
let initialValue = initialValues.get(inputName);
|
const initialValue = initialValues.get(inputName);
|
||||||
if (!initialValue) {
|
|
||||||
initialValue = { value: undefined, hidden: false, disabled: false };
|
|
||||||
}
|
|
||||||
currentValues = currentValues.set(inputName, initialValue);
|
currentValues = currentValues.set(inputName, initialValue);
|
||||||
baselineValues = baselineValues.set(inputName, initialValue);
|
baselineValues = baselineValues.set(inputName, initialValue);
|
||||||
initialValues.delete(inputName);
|
initialValues.delete(inputName);
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export enum BladeType {
|
|||||||
CassandraKeys = "cassandraDbKeys",
|
CassandraKeys = "cassandraDbKeys",
|
||||||
GremlinKeys = "keys",
|
GremlinKeys = "keys",
|
||||||
TableKeys = "tableKeys",
|
TableKeys = "tableKeys",
|
||||||
|
Metrics = "metrics",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DecoratorProperties {
|
export interface DecoratorProperties {
|
||||||
|
|||||||
@@ -28,3 +28,30 @@ export const getCollectionName = (isPlural?: boolean): string => {
|
|||||||
|
|
||||||
return collectionName;
|
return collectionName;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getDatabaseName = (): string => {
|
||||||
|
const { apiType } = userContext;
|
||||||
|
switch (apiType) {
|
||||||
|
case "SQL":
|
||||||
|
case "Mongo":
|
||||||
|
case "Gremlin":
|
||||||
|
case "Tables":
|
||||||
|
return "Database";
|
||||||
|
case "Cassandra":
|
||||||
|
return "Keyspace";
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown API type: ${apiType}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUploadName = (): string => {
|
||||||
|
switch (userContext.apiType) {
|
||||||
|
case "Cassandra":
|
||||||
|
case "Tables":
|
||||||
|
return "Tables";
|
||||||
|
case "Gremlin":
|
||||||
|
return "Graph";
|
||||||
|
default:
|
||||||
|
return "Items";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
15
src/hooks/useNotebookSnapshotStore.ts
Normal file
15
src/hooks/useNotebookSnapshotStore.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import create, { UseStore } from "zustand";
|
||||||
|
|
||||||
|
export interface NotebookSnapshotHooks {
|
||||||
|
snapshot: string;
|
||||||
|
error: string;
|
||||||
|
setSnapshot: (imageSrc: string) => void;
|
||||||
|
setError: (error: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useNotebookSnapshotStore: UseStore<NotebookSnapshotHooks> = create((set) => ({
|
||||||
|
snapshot: undefined,
|
||||||
|
error: undefined,
|
||||||
|
setSnapshot: (imageSrc: string) => set((state) => ({ ...state, snapshot: imageSrc })),
|
||||||
|
setError: (error: string) => set((state) => ({ ...state, error })),
|
||||||
|
}));
|
||||||
@@ -4,7 +4,7 @@ export interface SidePanelHooks {
|
|||||||
isPanelOpen: boolean;
|
isPanelOpen: boolean;
|
||||||
panelContent: JSX.Element;
|
panelContent: JSX.Element;
|
||||||
headerText: string;
|
headerText: string;
|
||||||
openSidePanel: (headerText: string, panelContent: JSX.Element) => void;
|
openSidePanel: (headerText: string, panelContent: JSX.Element, onClose?: () => void) => void;
|
||||||
closeSidePanel: () => void;
|
closeSidePanel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12,17 +12,23 @@ export const useSidePanel = (): SidePanelHooks => {
|
|||||||
const [isPanelOpen, setIsPanelOpen] = useState<boolean>(false);
|
const [isPanelOpen, setIsPanelOpen] = useState<boolean>(false);
|
||||||
const [panelContent, setPanelContent] = useState<JSX.Element>();
|
const [panelContent, setPanelContent] = useState<JSX.Element>();
|
||||||
const [headerText, setHeaderText] = useState<string>();
|
const [headerText, setHeaderText] = useState<string>();
|
||||||
|
const [onCloseCallback, setOnCloseCallback] = useState<{ callback: () => void }>();
|
||||||
|
|
||||||
const openSidePanel = (headerText: string, panelContent: JSX.Element): void => {
|
const openSidePanel = (headerText: string, panelContent: JSX.Element, onClose?: () => void): void => {
|
||||||
setHeaderText(headerText);
|
setHeaderText(headerText);
|
||||||
setPanelContent(panelContent);
|
setPanelContent(panelContent);
|
||||||
setIsPanelOpen(true);
|
setIsPanelOpen(true);
|
||||||
|
setOnCloseCallback({ callback: onClose });
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeSidePanel = (): void => {
|
const closeSidePanel = (): void => {
|
||||||
setHeaderText("");
|
setHeaderText("");
|
||||||
setPanelContent(undefined);
|
setPanelContent(undefined);
|
||||||
setIsPanelOpen(false);
|
setIsPanelOpen(false);
|
||||||
|
if (onCloseCallback) {
|
||||||
|
onCloseCallback.callback();
|
||||||
|
setOnCloseCallback(undefined);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { isPanelOpen, panelContent, headerText, openSidePanel, closeSidePanel };
|
return { isPanelOpen, panelContent, headerText, openSidePanel, closeSidePanel };
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ configure({ adapter: new Adapter() });
|
|||||||
initializeIcons();
|
initializeIcons();
|
||||||
|
|
||||||
if (typeof window.URL.createObjectURL === "undefined") {
|
if (typeof window.URL.createObjectURL === "undefined") {
|
||||||
Object.defineProperty(window.URL, "createObjectURL", { value: () => { } });
|
Object.defineProperty(window.URL, "createObjectURL", { value: () => {} });
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Remove when jquery and documentdbclient SDK are removed
|
// TODO Remove when jquery and documentdbclient SDK are removed
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ test("Mongo CRUD", async () => {
|
|||||||
await safeClick(explorer, `.nodeItem >> text=${databaseId}`);
|
await safeClick(explorer, `.nodeItem >> text=${databaseId}`);
|
||||||
await safeClick(explorer, `.nodeItem >> text=${containerId}`);
|
await safeClick(explorer, `.nodeItem >> text=${containerId}`);
|
||||||
// Create indexing policy
|
// Create indexing policy
|
||||||
await safeClick(explorer, ".nodeItem >> text=Settings");
|
await safeClick(explorer, ".nodeItem >> text=Setting");
|
||||||
await explorer.click('button[role="tab"]:has-text("Indexing Policy")');
|
await explorer.click('button[role="tab"]:has-text("Indexing Policy")');
|
||||||
await explorer.click('[aria-label="Index Field Name 0"]');
|
await explorer.click('[aria-label="Index Field Name 0"]');
|
||||||
await explorer.fill('[aria-label="Index Field Name 0"]', "foo");
|
await explorer.fill('[aria-label="Index Field Name 0"]', "foo");
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ test("Notebooks", async () => {
|
|||||||
await explorer.click('[data-test="My Notebooks"] [aria-label="More"]');
|
await explorer.click('[data-test="My Notebooks"] [aria-label="More"]');
|
||||||
await explorer.click('button[role="menuitem"]:has-text("Upload File")');
|
await explorer.click('button[role="menuitem"]:has-text("Upload File")');
|
||||||
await explorer.setInputFiles("#importFileInput", path.join(__dirname, fileToUpload));
|
await explorer.setInputFiles("#importFileInput", path.join(__dirname, fileToUpload));
|
||||||
await explorer.click('[aria-label="Submit"]');
|
await explorer.click('[aria-label="Upload"]');
|
||||||
await explorer.click(`[data-test="${fileToUpload}"] [aria-label="More"]`);
|
await explorer.click(`[data-test="${fileToUpload}"] [aria-label="More"]`);
|
||||||
await explorer.click('button[role="menuitem"]:has-text("Delete")');
|
await explorer.click('button[role="menuitem"]:has-text("Delete")');
|
||||||
await explorer.click('button:has-text("Delete")');
|
await explorer.click('button:has-text("Delete")');
|
||||||
|
|||||||
Reference in New Issue
Block a user