Compare commits

..

2 Commits

115 changed files with 1704 additions and 2830 deletions

View File

@@ -137,18 +137,10 @@ src/Terminal/NotebookAppContracts.d.ts
src/applyExplorerBindings.ts src/applyExplorerBindings.ts
src/global.d.ts src/global.d.ts
src/setupTests.ts src/setupTests.ts
src/Explorer/Controls/InputTypeahead/InputTypeaheadComponent.tsx
src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx
src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx
src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx
src/Explorer/Controls/TreeComponent/TreeComponent.tsx src/Explorer/Controls/TreeComponent/TreeComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/LeftPaneComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.test.tsx
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx
src/Explorer/Menus/CommandBar/CommandBarUtil.tsx src/Explorer/Menus/CommandBar/CommandBarUtil.tsx

View File

@@ -3,8 +3,8 @@ module.exports = {
browser: true, browser: true,
es6: true, es6: true,
}, },
plugins: ["@typescript-eslint", "no-null", "prefer-arrow", "react-hooks", "jsx-a11y"], plugins: ["@typescript-eslint", "no-null", "prefer-arrow", "react-hooks"],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:jsx-a11y/recommended"], extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
globals: { globals: {
Atomics: "readonly", Atomics: "readonly",
SharedArrayBuffer: "readonly", SharedArrayBuffer: "readonly",

View File

@@ -1,8 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" <svg
width="16"
height="16"
version="1.1"
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"> xmlns:xlink="http://www.w3.org/1999/xlink">
<path <path
d="M14,2.691l-5.301,5.309l5.301,5.309l-0.691,0.691l-5.309,-5.301l-5.309,5.301l-0.691,-0.691l5.301,-5.309l-5.301,-5.309l0.691,-0.691l5.309,5.301l5.309,-5.301l0.691,0.691Z" d="M14,2.691l-5.301,5.309l5.301,5.309l-0.691,0.691l-5.309,-5.301l-5.309,5.301l-0.691,-0.691l5.301,-5.309l-5.301,-5.309l0.691,-0.691l5.309,5.301l5.309,-5.301l0.691,0.691Z"
transform="scale(0.5)" fill="#000" stroke="#000"> transform="scale(0.5)"
fill="#000"
stroke="#CCC"
>
</path> </path>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 449 B

After

Width:  |  Height:  |  Size: 503 B

View File

@@ -37,8 +37,8 @@ module.exports = {
global: { global: {
branches: 25, branches: 25,
functions: 25, functions: 25,
lines: 29.5, lines: 30,
statements: 29.5, statements: 30,
}, },
}, },

View File

@@ -2357,8 +2357,6 @@ a:link {
height: 100%; height: 100%;
flex-grow: 1; flex-grow: 1;
overflow: hidden; overflow: hidden;
min-height: 300px;
overflow-y: scroll;
} }
.tabs { .tabs {
@@ -2834,8 +2832,6 @@ a:link {
#explorerNotificationConsole { #explorerNotificationConsole {
z-index: 1000; z-index: 1000;
overflow-y: auto;
overflow-x: clip;
} }
.uniqueIndexesContainer { .uniqueIndexesContainer {
@@ -3079,6 +3075,3 @@ settings-pane {
background: white; background: white;
height: 100%; height: 100%;
} }
.moreOption {
color: #337ab7;
}

91
package-lock.json generated
View File

@@ -6809,11 +6809,6 @@
"integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=", "integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=",
"dev": true "dev": true
}, },
"ast-types-flow": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
"integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0="
},
"astral-regex": { "astral-regex": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
@@ -6881,11 +6876,6 @@
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
}, },
"axe-core": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.3.3.tgz",
"integrity": "sha512-/lqqLAmuIPi79WYfRpy2i8z+x+vxU3zX2uAm0gs1q52qTuKwolOj1P8XbufpXcsydrpKx2yGn2wzAnxCMV86QA=="
},
"axios": { "axios": {
"version": "0.21.1", "version": "0.21.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
@@ -6901,11 +6891,6 @@
} }
} }
}, },
"axobject-query": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
"integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA=="
},
"babel-code-frame": { "babel-code-frame": {
"version": "6.26.0", "version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
@@ -8893,11 +8878,6 @@
"d3-transition": "2" "d3-transition": "2"
} }
}, },
"damerau-levenshtein": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.7.tgz",
"integrity": "sha512-VvdQIPGdWP0SqFXghj79Wf/5LArmreyMsGLa6FG6iC4t3j7j5s71TrwWmT/4akbDQIqjfACkLZmjXhA7g2oUZw=="
},
"dashdash": { "dashdash": {
"version": "1.14.1", "version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -10191,64 +10171,6 @@
"@typescript-eslint/experimental-utils": "^2.5.0" "@typescript-eslint/experimental-utils": "^2.5.0"
} }
}, },
"eslint-plugin-jsx-a11y": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz",
"integrity": "sha512-0rGPJBbwHoGNPU73/QCLP/vveMlM1b1Z9PponxO87jfr6tuH5ligXbDT6nHSSzBC8ovX2Z+BQu7Bk5D/Xgq9zg==",
"requires": {
"@babel/runtime": "^7.11.2",
"aria-query": "^4.2.2",
"array-includes": "^3.1.1",
"ast-types-flow": "^0.0.7",
"axe-core": "^4.0.2",
"axobject-query": "^2.2.0",
"damerau-levenshtein": "^1.0.6",
"emoji-regex": "^9.0.0",
"has": "^1.0.3",
"jsx-ast-utils": "^3.1.0",
"language-tags": "^1.0.5"
},
"dependencies": {
"emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
},
"get-intrinsic": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
"requires": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1"
}
},
"jsx-ast-utils": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz",
"integrity": "sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA==",
"requires": {
"array-includes": "^3.1.3",
"object.assign": "^4.1.2"
},
"dependencies": {
"array-includes": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz",
"integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==",
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3",
"es-abstract": "^1.18.0-next.2",
"get-intrinsic": "^1.1.1",
"is-string": "^1.0.5"
}
}
}
}
}
},
"eslint-plugin-no-null": { "eslint-plugin-no-null": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-no-null/-/eslint-plugin-no-null-1.0.2.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-no-null/-/eslint-plugin-no-null-1.0.2.tgz",
@@ -18143,19 +18065,6 @@
"resolved": "https://registry.npmjs.org/labella/-/labella-1.1.4.tgz", "resolved": "https://registry.npmjs.org/labella/-/labella-1.1.4.tgz",
"integrity": "sha1-xsxaNA6N80DrM1YzaD6lm4KMMi0=" "integrity": "sha1-xsxaNA6N80DrM1YzaD6lm4KMMi0="
}, },
"language-subtag-registry": {
"version": "0.3.21",
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz",
"integrity": "sha512-L0IqwlIXjilBVVYKFT37X9Ih11Um5NEl9cbJIuU/SwP/zEEAbBPOnEeeuxVMf45ydWQRDQN3Nqc96OgbH1K+Pg=="
},
"language-tags": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz",
"integrity": "sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=",
"requires": {
"language-subtag-registry": "~0.3.2"
}
},
"lcid": { "lcid": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",

View File

@@ -61,7 +61,6 @@
"dom-to-image": "2.6.0", "dom-to-image": "2.6.0",
"dotenv": "8.2.0", "dotenv": "8.2.0",
"eslint-plugin-jest": "23.13.2", "eslint-plugin-jest": "23.13.2",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-react": "7.20.0", "eslint-plugin-react": "7.20.0",
"hasher": "1.2.0", "hasher": "1.2.0",
"html2canvas": "1.0.0-rc.5", "html2canvas": "1.0.0-rc.5",
@@ -231,4 +230,4 @@
"prettier": { "prettier": {
"printWidth": 120 "printWidth": 120
} }
} }

View File

@@ -1,7 +1,6 @@
import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react"; import React, { FunctionComponent } from "react";
import arrowLeftImg from "../../images/imgarrowlefticon.svg"; import arrowLeftImg from "../../images/imgarrowlefticon.svg";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
import { NormalizedEventKey } from "./Constants";
export interface CollapsedResourceTreeProps { export interface CollapsedResourceTreeProps {
toggleLeftPaneExpanded: () => void; toggleLeftPaneExpanded: () => void;
@@ -12,37 +11,21 @@ export const CollapsedResourceTree: FunctionComponent<CollapsedResourceTreeProps
toggleLeftPaneExpanded, toggleLeftPaneExpanded,
isLeftPaneExpanded, isLeftPaneExpanded,
}: CollapsedResourceTreeProps): JSX.Element => { }: CollapsedResourceTreeProps): JSX.Element => {
const focusButton = useRef<HTMLLIElement>() as MutableRefObject<HTMLLIElement>;
useEffect(() => {
if (focusButton.current) {
focusButton.current.focus();
}
});
const onKeyPressToggleLeftPaneExpanded = (event: React.KeyboardEvent) => {
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
toggleLeftPaneExpanded();
event.stopPropagation();
}
};
return ( return (
<div id="mini" className={!isLeftPaneExpanded ? "mini toggle-mini" : "hiddenMain"}> <div id="mini" className={!isLeftPaneExpanded ? "mini toggle-mini" : "hiddenMain"}>
<div className="main-nav nav"> <div className="main-nav nav">
<ul className="nav"> <ul className="nav">
<li className="resourceTreeCollapse" id="collapseToggleLeftPaneButton" aria-label="Expand Tree"> <li
<span className="resourceTreeCollapse"
className="leftarrowCollapsed" id="collapseToggleLeftPaneButton"
role="button" role="button"
tabIndex={0} tabIndex={0}
onClick={toggleLeftPaneExpanded} aria-label="Expand Tree"
onKeyPress={onKeyPressToggleLeftPaneExpanded} >
ref={focusButton} <span className="leftarrowCollapsed" onClick={toggleLeftPaneExpanded}>
>
<img className="arrowCollapsed" src={arrowLeftImg} alt="Expand" /> <img className="arrowCollapsed" src={arrowLeftImg} alt="Expand" />
</span> </span>
<span className="collectionCollapsed"> <span className="collectionCollapsed" onClick={toggleLeftPaneExpanded}>
<span>{userContext.apiType} API</span> <span>{userContext.apiType} API</span>
</span> </span>
</li> </li>

View File

@@ -340,6 +340,7 @@ export enum ConflictOperationType {
export enum ConnectionStatusType { export enum ConnectionStatusType {
Connecting = "Connecting", Connecting = "Connecting",
Allocating = "Allocating",
Connected = "Connected", Connected = "Connected",
Failed = "Connection Failed", Failed = "Connection Failed",
} }

View File

@@ -40,6 +40,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
<TextField <TextField
label={entityValueLabel && entityValueLabel} label={entityValueLabel && entityValueLabel}
id="entityTimeId" id="entityTimeId"
autoFocus
type="time" type="time"
value={entityTimeValue} value={entityTimeValue}
onChange={onEntityTimeValueChange} onChange={onEntityTimeValueChange}
@@ -54,6 +55,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
label={entityValueLabel && entityValueLabel} label={entityValueLabel && entityValueLabel}
className="addEntityTextField" className="addEntityTextField"
id="entityValueId" id="entityValueId"
autoFocus
disabled={isEntityValueDisable} disabled={isEntityValueDisable}
type={entityValueType} type={entityValueType}
placeholder={entityValuePlaceholder} placeholder={entityValuePlaceholder}

View File

@@ -3,16 +3,8 @@ import { resetConfigContext, updateConfigContext } from "../ConfigContext";
import { DatabaseAccount } from "../Contracts/DataModels"; import { DatabaseAccount } from "../Contracts/DataModels";
import { Collection } from "../Contracts/ViewModels"; import { Collection } from "../Contracts/ViewModels";
import DocumentId from "../Explorer/Tree/DocumentId"; import DocumentId from "../Explorer/Tree/DocumentId";
import { extractFeatures } from "../Platform/Hosted/extractFeatures";
import { updateUserContext } from "../UserContext"; import { updateUserContext } from "../UserContext";
import { import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
deleteDocument,
getEndpoint,
getFeatureEndpointOrDefault,
queryDocuments,
readDocument,
updateDocument,
} from "./MongoProxyClient";
const databaseId = "testDB"; const databaseId = "testDB";
@@ -254,31 +246,4 @@ describe("MongoProxyClient", () => {
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer"); expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer");
}); });
}); });
describe("getFeatureEndpointOrDefault", () => {
beforeEach(() => {
resetConfigContext();
updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
});
const params = new URLSearchParams({
"feature.mongoProxyEndpoint": "https://localhost:12901",
"feature.mongoProxyAPIs": "readDocument|createDocument",
});
const features = extractFeatures(params);
updateUserContext({
authType: AuthType.AAD,
features: features,
});
});
it("returns a local endpoint", () => {
const endpoint = getFeatureEndpointOrDefault("readDocument");
expect(endpoint).toEqual("https://localhost:12901/api/mongo/explorer");
});
it("returns a production endpoint", () => {
const endpoint = getFeatureEndpointOrDefault("deleteDocument");
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/mongo/explorer");
});
});
}); });

View File

@@ -6,7 +6,6 @@ import * as DataModels from "../Contracts/DataModels";
import { MessageTypes } from "../Contracts/ExplorerContracts"; import { MessageTypes } from "../Contracts/ExplorerContracts";
import { Collection } from "../Contracts/ViewModels"; import { Collection } from "../Contracts/ViewModels";
import DocumentId from "../Explorer/Tree/DocumentId"; import DocumentId from "../Explorer/Tree/DocumentId";
import { hasFlag } from "../Platform/Hosted/extractFeatures";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
import { logConsoleError } from "../Utils/NotificationConsoleUtils"; import { logConsoleError } from "../Utils/NotificationConsoleUtils";
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants"; import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
@@ -79,7 +78,7 @@ export function queryDocuments(
: "", : "",
}; };
const endpoint = getFeatureEndpointOrDefault("resourcelist") || ""; const endpoint = getEndpoint() || "";
const headers = { const headers = {
...defaultHeaders, ...defaultHeaders,
@@ -142,8 +141,7 @@ export function readDocument(
: "", : "",
}; };
const endpoint = getFeatureEndpointOrDefault("readDocument"); const endpoint = getEndpoint();
return window return window
.fetch(`${endpoint}?${queryString.stringify(params)}`, { .fetch(`${endpoint}?${queryString.stringify(params)}`, {
method: "GET", method: "GET",
@@ -183,7 +181,7 @@ export function createDocument(
pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "", pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "",
}; };
const endpoint = getFeatureEndpointOrDefault("createDocument"); const endpoint = getEndpoint();
return window return window
.fetch(`${endpoint}/resourcelist?${queryString.stringify(params)}`, { .fetch(`${endpoint}/resourcelist?${queryString.stringify(params)}`, {
@@ -227,7 +225,7 @@ export function updateDocument(
? documentId.partitionKeyProperty ? documentId.partitionKeyProperty
: "", : "",
}; };
const endpoint = getFeatureEndpointOrDefault("updateDocument"); const endpoint = getEndpoint();
return window return window
.fetch(`${endpoint}?${queryString.stringify(params)}`, { .fetch(`${endpoint}?${queryString.stringify(params)}`, {
@@ -268,7 +266,7 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
? documentId.partitionKeyProperty ? documentId.partitionKeyProperty
: "", : "",
}; };
const endpoint = getFeatureEndpointOrDefault("deleteDocument"); const endpoint = getEndpoint();
return window return window
.fetch(`${endpoint}?${queryString.stringify(params)}`, { .fetch(`${endpoint}?${queryString.stringify(params)}`, {
@@ -311,7 +309,7 @@ export function createMongoCollectionWithProxy(
autoPilotThroughput: params.autoPilotMaxThroughput?.toString(), autoPilotThroughput: params.autoPilotMaxThroughput?.toString(),
}; };
const endpoint = getFeatureEndpointOrDefault("createCollectionWithProxy"); const endpoint = getEndpoint();
return window return window
.fetch( .fetch(
@@ -335,15 +333,8 @@ export function createMongoCollectionWithProxy(
}); });
} }
export function getFeatureEndpointOrDefault(feature: string): string { export function getEndpoint(): string {
return hasFlag(userContext.features.mongoProxyAPIs, feature) let url = (configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT) + "/api/mongo/explorer";
? getEndpoint(userContext.features.mongoProxyEndpoint)
: getEndpoint();
}
export function getEndpoint(customEndpoint?: string): string {
let url = customEndpoint ? customEndpoint : configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT;
url += "/api/mongo/explorer";
if (userContext.authType === AuthType.EncryptedToken) { if (userContext.authType === AuthType.EncryptedToken) {
url = url.replace("api/mongo", "api/guest/mongo"); url = url.replace("api/mongo", "api/guest/mongo");

View File

@@ -1,4 +1,4 @@
import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react"; import React, { FunctionComponent } from "react";
import arrowLeftImg from "../../images/imgarrowlefticon.svg"; import arrowLeftImg from "../../images/imgarrowlefticon.svg";
import refreshImg from "../../images/refresh-cosmos.svg"; import refreshImg from "../../images/refresh-cosmos.svg";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
@@ -6,7 +6,6 @@ import Explorer from "../Explorer/Explorer";
import { ResourceTokenTree } from "../Explorer/Tree/ResourceTokenTree"; import { ResourceTokenTree } from "../Explorer/Tree/ResourceTokenTree";
import { ResourceTree } from "../Explorer/Tree/ResourceTree"; import { ResourceTree } from "../Explorer/Tree/ResourceTree";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
import { NormalizedEventKey } from "./Constants";
export interface ResourceTreeContainerProps { export interface ResourceTreeContainerProps {
toggleLeftPaneExpanded: () => void; toggleLeftPaneExpanded: () => void;
@@ -19,22 +18,6 @@ export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps
isLeftPaneExpanded, isLeftPaneExpanded,
container, container,
}: ResourceTreeContainerProps): JSX.Element => { }: ResourceTreeContainerProps): JSX.Element => {
const focusButton = useRef<HTMLLIElement>() as MutableRefObject<HTMLLIElement>;
useEffect(() => {
if (isLeftPaneExpanded) {
if (focusButton.current) {
focusButton.current.focus();
}
}
});
const onKeyPressToggleLeftPaneExpanded = (event: React.KeyboardEvent) => {
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
toggleLeftPaneExpanded();
event.stopPropagation();
}
};
return ( return (
<div id="main" className={isLeftPaneExpanded ? "main" : "hiddenMain"}> <div id="main" className={isLeftPaneExpanded ? "main" : "hiddenMain"}>
{/* Collections Window - - Start */} {/* Collections Window - - Start */}
@@ -60,11 +43,9 @@ export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps
id="expandToggleLeftPaneButton" id="expandToggleLeftPaneButton"
role="button" role="button"
onClick={toggleLeftPaneExpanded} onClick={toggleLeftPaneExpanded}
onKeyPress={onKeyPressToggleLeftPaneExpanded}
tabIndex={0} tabIndex={0}
aria-label="Collapse Tree" aria-label="Collapse Tree"
title="Collapse Tree" title="Collapse Tree"
ref={focusButton}
> >
<img className="refreshcol1" src={arrowLeftImg} alt="Hide" /> <img className="refreshcol1" src={arrowLeftImg} alt="Hide" />
</span> </span>

View File

@@ -96,6 +96,7 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
<TextField <TextField
label={entityPropertyLabel && entityPropertyLabel} label={entityPropertyLabel && entityPropertyLabel}
id="entityPropertyId" id="entityPropertyId"
autoFocus
disabled={isPropertyTypeDisable} disabled={isPropertyTypeDisable}
placeholder={entityPropertyPlaceHolder} placeholder={entityPropertyPlaceHolder}
value={entityProperty} value={entityProperty}

View File

@@ -9,7 +9,7 @@ export const InfoTooltip: React.FunctionComponent<TooltipProps> = ({ children }:
return ( return (
<span> <span>
<TooltipHost content={children}> <TooltipHost content={children}>
<Icon iconName="Info" ariaLabel="Info" className="panelInfoIcon" tabIndex={0} /> <Icon iconName="Info" ariaLabel="Info" className="panelInfoIcon" />
</TooltipHost> </TooltipHost>
</span> </span>
); );

View File

@@ -66,15 +66,9 @@ export const Upload: FunctionComponent<UploadProps> = ({
onChange={onUpload} onChange={onUpload}
role="button" role="button"
/> />
<span <a href="#" id="fileImportLinkNotebook" onClick={onImportLinkClick} onKeyPress={onImportLinkKeyPress}>
id="fileImportLinkNotebook"
role="button"
tabIndex={0}
onClick={onImportLinkClick}
onKeyPress={onImportLinkKeyPress}
>
<Image className="fileImportImg" src={FolderIcon} alt={title} title={title} /> <Image className="fileImportImg" src={FolderIcon} alt={title} title={title} />
</span> </a>
</Stack> </Stack>
</div> </div>
); );

View File

@@ -55,13 +55,7 @@ export class AccordionItemComponent extends React.Component<AccordionItemCompone
public render(): JSX.Element { public render(): JSX.Element {
return ( return (
<div className="accordionItemContainer"> <div className="accordionItemContainer">
<div <div className="accordionItemHeader" onClick={this.onHeaderClick} onKeyPress={this.onHeaderKeyPress}>
className="accordionItemHeader"
onClick={this.onHeaderClick}
onKeyPress={this.onHeaderKeyPress}
role="button"
tabIndex={0}
>
{this.renderCollapseExpandIcon()} {this.renderCollapseExpandIcon()}
{this.props.title} {this.props.title}
</div> </div>
@@ -80,6 +74,8 @@ export class AccordionItemComponent extends React.Component<AccordionItemCompone
className="expandCollapseIcon" className="expandCollapseIcon"
src={this.state.isExpanded ? TriangleDownIcon : TriangleRightIcon} src={this.state.isExpanded ? TriangleDownIcon : TriangleRightIcon}
alt="Hide" alt="Hide"
tabIndex={0}
role="button"
/> />
); );
} }

View File

@@ -1,6 +1,5 @@
import { Icon, Label, Stack } from "@fluentui/react"; import { Icon, Label, Stack } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import { NormalizedEventKey } from "../../../Common/Constants";
import { accordionStackTokens } from "../Settings/SettingsRenderUtils"; import { accordionStackTokens } from "../Settings/SettingsRenderUtils";
export interface CollapsibleSectionProps { export interface CollapsibleSectionProps {
@@ -31,13 +30,6 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
} }
} }
private onKeyPress = (event: React.KeyboardEvent) => {
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
this.toggleCollapsed();
event.stopPropagation();
}
};
public render(): JSX.Element { public render(): JSX.Element {
return ( return (
<> <>
@@ -47,11 +39,6 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
verticalAlign="center" verticalAlign="center"
tokens={accordionStackTokens} tokens={accordionStackTokens}
onClick={this.toggleCollapsed} onClick={this.toggleCollapsed}
onKeyPress={this.onKeyPress}
tabIndex={0}
aria-label="Advanced"
role="button"
aria-expanded={this.state.isExpanded}
> >
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} /> <Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} />
<Label>{this.props.title}</Label> <Label>{this.props.title}</Label>

View File

@@ -3,14 +3,9 @@
exports[`CollapsibleSectionComponent renders 1`] = ` exports[`CollapsibleSectionComponent renders 1`] = `
<Fragment> <Fragment>
<Stack <Stack
aria-expanded={true}
aria-label="Advanced"
className="collapsibleSection" className="collapsibleSection"
horizontal={true} horizontal={true}
onClick={[Function]} onClick={[Function]}
onKeyPress={[Function]}
role="button"
tabIndex={0}
tokens={ tokens={
Object { Object {
"childrenGap": 10, "childrenGap": 10,

View File

@@ -188,13 +188,12 @@ export class CommandButtonComponent extends React.Component<CommandButtonCompone
ref={(ref: HTMLElement) => { ref={(ref: HTMLElement) => {
this.expandButtonElt = ref; this.expandButtonElt = ref;
}} }}
role="button"
onKeyDown={(e: React.KeyboardEvent<HTMLDivElement>) => this.onLauncherKeyDown(e)} onKeyDown={(e: React.KeyboardEvent<HTMLDivElement>) => this.onLauncherKeyDown(e)}
> >
<div className="commandDropdownLauncher"> <div className="commandDropdownLauncher">
<span className="partialSplitter" /> <span className="partialSplitter" />
<span className="expandDropdown"> <span className="expandDropdown">
<img src={CollapseChevronDownIcon} alt="Collapse down icon" /> <img src={CollapseChevronDownIcon} />
</span> </span>
</div> </div>
<div <div

View File

@@ -8,9 +8,6 @@
.input-type-head-text-field { .input-type-head-text-field {
width: 100%; width: 100%;
} }
.input-query-form {
width: 100%;
}
textarea { textarea {
width: 100%; width: 100%;
line-height: 1; line-height: 1;

View File

@@ -50,7 +50,7 @@ export interface InputTypeaheadComponentProps {
* Override default jquery-typeahead options * Override default jquery-typeahead options
* WARNING: do not override input, source or callback to avoid breaking the components behavior. * WARNING: do not override input, source or callback to avoid breaking the components behavior.
*/ */
typeaheadOverrideOptions?: any; typeaheadOverrideOptions?: { dynamic: boolean };
/** /**
* This function gets called when pressing ENTER on the input box * This function gets called when pressing ENTER on the input box
@@ -132,6 +132,7 @@ export class InputTypeaheadComponent extends React.Component<
private filterChoiceByValue = (choices: Item[], searchKeyword: string): Item[] => { private filterChoiceByValue = (choices: Item[], searchKeyword: string): Item[] => {
return choices.filter((choice) => return choices.filter((choice) =>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
Object.keys(choice).some((key) => choice[key].toLowerCase().includes(searchKeyword.toLowerCase())) Object.keys(choice).some((key) => choice[key].toLowerCase().includes(searchKeyword.toLowerCase()))
); );
@@ -160,21 +161,18 @@ export class InputTypeaheadComponent extends React.Component<
return ( return (
<div className="input-typeahead-container"> <div className="input-typeahead-container">
<Stack horizontal> <Stack horizontal>
<form aria-labelledby="input" className="input-query-form"> <TextField
<TextField multiline={useTextarea}
multiline={useTextarea} rows={1}
rows={1} defaultValue={defaultValue}
id="input" ariaLabel="Input query"
defaultValue={defaultValue} placeholder={placeholder}
ariaLabel="Input query" className="input-type-head-text-field"
placeholder={placeholder} value={defaultValue}
className="input-type-head-text-field" onKeyDown={this.onSubmit}
value={defaultValue} onFocus={() => this.setState({ isSuggestionVisible: true })}
onKeyDown={this.onSubmit} onChange={(_event, newValue?: string) => this.handleChange(newValue)}
onFocus={() => this.setState({ isSuggestionVisible: true })} />
onChange={(_event, newValue?: string) => this.handleChange(newValue)}
/>
</form>
{this.props.showCancelButton && ( {this.props.showCancelButton && (
<IconButton <IconButton
styles={iconButtonStyles} styles={iconButtonStyles}

View File

@@ -7,22 +7,16 @@ exports[`inputTypeahead renders <input /> 1`] = `
<Stack <Stack
horizontal={true} horizontal={true}
> >
<form <StyledTextFieldBase
aria-labelledby="input" ariaLabel="Input query"
className="input-query-form" className="input-type-head-text-field"
> multiline={false}
<StyledTextFieldBase onChange={[Function]}
ariaLabel="Input query" onFocus={[Function]}
className="input-type-head-text-field" onKeyDown={[Function]}
id="input" placeholder="placeholder"
multiline={false} rows={1}
onChange={[Function]} />
onFocus={[Function]}
onKeyDown={[Function]}
placeholder="placeholder"
rows={1}
/>
</form>
</Stack> </Stack>
</div> </div>
`; `;
@@ -34,22 +28,16 @@ exports[`inputTypeahead renders <textarea /> 1`] = `
<Stack <Stack
horizontal={true} horizontal={true}
> >
<form <StyledTextFieldBase
aria-labelledby="input" ariaLabel="Input query"
className="input-query-form" className="input-type-head-text-field"
> multiline={true}
<StyledTextFieldBase onChange={[Function]}
ariaLabel="Input query" onFocus={[Function]}
className="input-type-head-text-field" onKeyDown={[Function]}
id="input" placeholder="placeholder"
multiline={true} rows={1}
onChange={[Function]} />
onFocus={[Function]}
onKeyDown={[Function]}
placeholder="placeholder"
rows={1}
/>
</form>
</Stack> </Stack>
</div> </div>
`; `;

View File

@@ -264,6 +264,6 @@ export class NotebookViewerComponent
}; };
private reportAbuse = (): void => { private reportAbuse = (): void => {
GalleryUtils.reportAbuse(this.props.junoClient, this.state.galleryItem, this, () => {}); GalleryUtils.reportAbuse(this.props.junoClient, this.state.galleryItem, this, () => ({}));
}; };
} }

View File

@@ -150,7 +150,7 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
To write a new query, open a new query tab and enter the desired query. Once ready to save, click on Save To write a new query, open a new query tab and enter the desired query. Once ready to save, click on Save
Query and follow the prompt in order to save the query. Query and follow the prompt in order to save the query.
</div> </div>
<img {...bannerProps} alt="Save query helper banner" /> <img {...bannerProps} />
</div> </div>
); );
} }

View File

@@ -1,45 +1,45 @@
import * as React from "react";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import { AutopilotDocumentation, hoursInAMonth } from "../../../Shared/Constants";
import { Urls, StyleConstants } from "../../../Common/Constants";
import {
getPriceCurrency,
getCurrencySign,
getAutoscalePricePerRu,
getMultimasterMultiplier,
computeRUUsagePriceHourly,
getPricePerRu,
estimatedCostDisclaimer,
} from "../../../Utils/PricingUtils";
import { import {
DetailsList,
DetailsListLayoutMode,
DetailsRow,
ICheckboxStyles,
IChoiceGroupStyles,
IColumn,
IDetailsColumnStyles,
IDetailsListStyles,
IDetailsRowProps,
IDetailsRowStyles,
IDropdownStyles,
IMessageBarStyles,
ISeparatorStyles,
IStackProps,
IStackStyles,
IStackTokens,
ITextFieldStyles, ITextFieldStyles,
ITextStyles, ICheckboxStyles,
IStackProps,
IStackTokens,
IChoiceGroupStyles,
Link, Link,
Text,
IMessageBarStyles,
ITextStyles,
IDetailsRowStyles,
IStackStyles,
IDetailsListStyles,
IDropdownStyles,
ISeparatorStyles,
MessageBar, MessageBar,
MessageBarType, MessageBarType,
SelectionMode, Stack,
Spinner, Spinner,
SpinnerSize, SpinnerSize,
Stack, DetailsList,
Text, IColumn,
SelectionMode,
DetailsListLayoutMode,
IDetailsRowProps,
DetailsRow,
IDetailsColumnStyles,
} from "@fluentui/react"; } from "@fluentui/react";
import * as React from "react"; import { isDirtyTypes, isDirty } from "./SettingsUtils";
import { StyleConstants, Urls } from "../../../Common/Constants";
import { AutopilotDocumentation, hoursInAMonth } from "../../../Shared/Constants";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import {
computeRUUsagePriceHourly,
estimatedCostDisclaimer,
getAutoscalePricePerRu,
getCurrencySign,
getMultimasterMultiplier,
getPriceCurrency,
getPricePerRu,
} from "../../../Utils/PricingUtils";
import { isDirty, isDirtyTypes } from "./SettingsUtils";
export interface EstimatedSpendingDisplayProps { export interface EstimatedSpendingDisplayProps {
costType: JSX.Element; costType: JSX.Element;
@@ -65,7 +65,7 @@ export interface PriceBreakdown {
currencySign: string; currencySign: string;
} }
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14, color: "windowtext" } }; export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14 } };
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = { export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
label: { label: {
@@ -223,15 +223,14 @@ export const getRuPriceBreakdown = (
multimasterEnabled: isMultimaster, multimasterEnabled: isMultimaster,
isAutoscale: isAutoscale, isAutoscale: isAutoscale,
}); });
const multimasterMultiplier = getMultimasterMultiplier(numberOfRegions, isMultimaster); const basePricePerRu: number = isAutoscale
const pricePerRu: number = isAutoscale ? getAutoscalePricePerRu(serverId, getMultimasterMultiplier(numberOfRegions, isMultimaster))
? getAutoscalePricePerRu(serverId, multimasterMultiplier) : getPricePerRu(serverId);
: getPricePerRu(serverId, multimasterMultiplier);
return { return {
hourlyPrice, hourlyPrice: hourlyPrice,
dailyPrice: hourlyPrice * 24, dailyPrice: hourlyPrice * 24,
monthlyPrice: hourlyPrice * hoursInAMonth, monthlyPrice: hourlyPrice * hoursInAMonth,
pricePerRu, pricePerRu: basePricePerRu * getMultimasterMultiplier(numberOfRegions, isMultimaster),
currency: getPriceCurrency(serverId), currency: getPriceCurrency(serverId),
currencySign: getCurrencySign(serverId), currencySign: getCurrencySign(serverId),
}; };
@@ -272,7 +271,7 @@ export const manualToAutoscaleDisclaimerElement: JSX.Element = (
<Text styles={infoAndToolTipTextStyle} id="manualToAutoscaleDisclaimerElement"> <Text styles={infoAndToolTipTextStyle} id="manualToAutoscaleDisclaimerElement">
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings
and storage of your resource. After autoscale has been enabled, you can change the max RU/s.{" "} and storage of your resource. After autoscale has been enabled, you can change the max RU/s.{" "}
<Link href={Urls.autoscaleMigration}>Learn more</Link> <a href={Urls.autoscaleMigration}>Learn more</a>
</Text> </Text>
); );

View File

@@ -122,7 +122,7 @@ export class IndexingPolicyComponent extends React.Component<
{isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && ( {isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && (
<MessageBar messageBarType={MessageBarType.warning}>{indexingPolicynUnsavedWarningMessage}</MessageBar> <MessageBar messageBarType={MessageBarType.warning}>{indexingPolicynUnsavedWarningMessage}</MessageBar>
)} )}
<div className="settingsV2IndexingPolicyEditor" ref={this.indexingPolicyDiv}></div> <div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.indexingPolicyDiv}></div>
</Stack> </Stack>
); );
} }

View File

@@ -8,7 +8,6 @@ exports[`IndexingPolicyRefreshComponent renders 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }

View File

@@ -20,7 +20,6 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -40,7 +39,6 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -75,7 +73,6 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -83,11 +80,11 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
> >
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s. The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s.
<StyledLinkBase <a
href="https://aka.ms/cosmos-autoscale-migration" href="https://aka.ms/cosmos-autoscale-migration"
> >
Learn more Learn more
</StyledLinkBase> </a>
</Text> </Text>
</StyledMessageBar> </StyledMessageBar>
<StyledChoiceGroup <StyledChoiceGroup
@@ -189,7 +186,6 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -464,7 +460,6 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }

View File

@@ -13,6 +13,7 @@ exports[`IndexingPolicyComponent renders 1`] = `
/> />
<div <div
className="settingsV2IndexingPolicyEditor" className="settingsV2IndexingPolicyEditor"
tabIndex={0}
/> />
</Stack> </Stack>
`; `;

View File

@@ -16,7 +16,6 @@ exports[`ScaleComponent renders with correct initial notification 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }

View File

@@ -136,7 +136,6 @@ exports[`SubSettingsComponent analyticalTimeToLive hidden 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -413,7 +412,6 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -954,7 +952,6 @@ exports[`SubSettingsComponent renders 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -1231,7 +1228,6 @@ exports[`SubSettingsComponent timeToLiveSeconds hidden 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }

View File

@@ -159,7 +159,6 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -167,17 +166,16 @@ exports[`SettingsUtils functions render 1`] = `
> >
The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s. The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s.
<StyledLinkBase <a
href="https://aka.ms/cosmos-autoscale-migration" href="https://aka.ms/cosmos-autoscale-migration"
> >
Learn more Learn more
</StyledLinkBase> </a>
</Text> </Text>
<Text <Text
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -197,7 +195,6 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -210,7 +207,6 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -223,7 +219,6 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -235,7 +230,6 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -255,7 +249,6 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -271,7 +264,6 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -286,7 +278,6 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -300,7 +291,6 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -312,7 +302,6 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -332,7 +321,6 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -375,7 +363,6 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -391,7 +378,6 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }
@@ -408,7 +394,6 @@ exports[`SettingsUtils functions render 1`] = `
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
} }

View File

@@ -1,6 +1,7 @@
@import "../../../../less/Common/Constants"; @import "../../../../less/Common/Constants";
.tabComponentContainer { .tabComponentContainer {
overflow: hidden;
height: 100%; height: 100%;
.flex-display(); .flex-display();
.flex-direction(); .flex-direction();

View File

@@ -6,7 +6,6 @@ import { userContext } from "../../../../UserContext";
import { import {
calculateEstimateNumber, calculateEstimateNumber,
computeRUUsagePriceHourly, computeRUUsagePriceHourly,
estimatedCostDisclaimer,
getAutoscalePricePerRu, getAutoscalePricePerRu,
getCurrencySign, getCurrencySign,
getMultimasterMultiplier, getMultimasterMultiplier,
@@ -43,9 +42,11 @@ export const CostEstimateText: FunctionComponent<CostEstimateTextProps> = ({
const currency: string = getPriceCurrency(serverId); const currency: string = getPriceCurrency(serverId);
const currencySign: string = getCurrencySign(serverId); const currencySign: string = getCurrencySign(serverId);
const multiplier = getMultimasterMultiplier(numberOfRegions, multimasterEnabled); const multiplier = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
const pricePerRu = isAutoscale ? getAutoscalePricePerRu(serverId, multiplier) : getPricePerRu(serverId, multiplier); const pricePerRu = isAutoscale
? getAutoscalePricePerRu(serverId, multiplier) * multiplier
: getPricePerRu(serverId) * multiplier;
const iconWithEstimatedCostDisclaimer: JSX.Element = <InfoTooltip>{estimatedCostDisclaimer}</InfoTooltip>; const iconWithEstimatedCostDisclaimer: JSX.Element = <InfoTooltip>PricingUtils.estimatedCostDisclaimer</InfoTooltip>;
if (isAutoscale) { if (isAutoscale) {
return ( return (

View File

@@ -120,6 +120,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
aria-label="Autoscale mode" aria-label="Autoscale mode"
checked={isAutoscaleSelected} checked={isAutoscaleSelected}
type="radio" type="radio"
role="radio"
tabIndex={0} tabIndex={0}
onChange={(e) => handleOnChangeMode(e, "Autoscale")} onChange={(e) => handleOnChangeMode(e, "Autoscale")}
/> />
@@ -130,6 +131,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
aria-label="Manual mode" aria-label="Manual mode"
checked={!isAutoscaleSelected} checked={!isAutoscaleSelected}
type="radio" type="radio"
role="radio"
tabIndex={0} tabIndex={0}
onChange={(e) => handleOnChangeMode(e, "Manual")} onChange={(e) => handleOnChangeMode(e, "Manual")}
/> />

View File

@@ -345,14 +345,12 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
ariaLabel="Info" ariaLabel="Info"
className="panelInfoIcon" className="panelInfoIcon"
iconName="Info" iconName="Info"
tabIndex={0}
> >
<IconBase <IconBase
ariaLabel="Info" ariaLabel="Info"
className="panelInfoIcon" className="panelInfoIcon"
iconName="Info" iconName="Info"
styles={[Function]} styles={[Function]}
tabIndex={0}
theme={ theme={
Object { Object {
"disableGlobalClassNames": false, "disableGlobalClassNames": false,
@@ -632,7 +630,6 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
className="panelInfoIcon root-57" className="panelInfoIcon root-57"
data-icon-name="Info" data-icon-name="Info"
role="img" role="img"
tabIndex={0}
> >
</i> </i>
@@ -658,6 +655,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
className="throughputInputRadioBtn" className="throughputInputRadioBtn"
key=".0:$.0" key=".0:$.0"
onChange={[Function]} onChange={[Function]}
role="radio"
tabIndex={0} tabIndex={0}
type="radio" type="radio"
/> />
@@ -673,6 +671,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
className="throughputInputRadioBtn" className="throughputInputRadioBtn"
key=".0:$.2" key=".0:$.2"
onChange={[Function]} onChange={[Function]}
role="radio"
tabIndex={0} tabIndex={0}
type="radio" type="radio"
/> />
@@ -1328,14 +1327,12 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
ariaLabel="Info" ariaLabel="Info"
className="panelInfoIcon" className="panelInfoIcon"
iconName="Info" iconName="Info"
tabIndex={0}
> >
<IconBase <IconBase
ariaLabel="Info" ariaLabel="Info"
className="panelInfoIcon" className="panelInfoIcon"
iconName="Info" iconName="Info"
styles={[Function]} styles={[Function]}
tabIndex={0}
theme={ theme={
Object { Object {
"disableGlobalClassNames": false, "disableGlobalClassNames": false,
@@ -1615,7 +1612,6 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
className="panelInfoIcon root-57" className="panelInfoIcon root-57"
data-icon-name="Info" data-icon-name="Info"
role="img" role="img"
tabIndex={0}
> >
</i> </i>

View File

@@ -243,7 +243,6 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
<div ref={this.contextMenuRef} onContextMenu={this.onRightClick} onKeyPress={this.onMoreButtonKeyPress}> <div ref={this.contextMenuRef} onContextMenu={this.onRightClick} onKeyPress={this.onMoreButtonKeyPress}>
<IconButton <IconButton
name="More" name="More"
title="More"
className="treeMenuEllipsis" className="treeMenuEllipsis"
ariaLabel={menuItemLabel} ariaLabel={menuItemLabel}
menuIconProps={{ menuIconProps={{

View File

@@ -211,7 +211,6 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
}, },
} }
} }
title="More"
/> />
</div> </div>
</div> </div>
@@ -424,7 +423,6 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
}, },
} }
} }
title="More"
/> />
</div> </div>
</div> </div>

View File

@@ -4,6 +4,7 @@ import _ from "underscore";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer"; import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
import * as Constants from "../Common/Constants"; import * as Constants from "../Common/Constants";
import { ConnectionStatusType } from "../Common/Constants";
import { readCollection } from "../Common/dataAccess/readCollection"; import { readCollection } from "../Common/dataAccess/readCollection";
import { readDatabases } from "../Common/dataAccess/readDatabases"; import { readDatabases } from "../Common/dataAccess/readDatabases";
import { isPublicInternetAccessAllowed } from "../Common/DatabaseAccountUtility"; import { isPublicInternetAccessAllowed } from "../Common/DatabaseAccountUtility";
@@ -346,6 +347,10 @@ export default class Explorer {
} }
this._isInitializingNotebooks = true; this._isInitializingNotebooks = true;
if (userContext.features.phoenix) { if (userContext.features.phoenix) {
const connectionStatus: DataModels.ContainerConnectionInfo = {
status: ConnectionStatusType.Allocating,
};
useNotebook.getState().setConnectionInfo(connectionStatus);
const provisionData = { const provisionData = {
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint, cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
resourceId: userContext.databaseAccount.id, resourceId: userContext.databaseAccount.id,
@@ -356,6 +361,9 @@ export default class Explorer {
}; };
const connectionInfo = await this.phoenixClient.containerConnectionInfo(provisionData); const connectionInfo = await this.phoenixClient.containerConnectionInfo(provisionData);
if (connectionInfo.data && connectionInfo.data.notebookServerUrl) { if (connectionInfo.data && connectionInfo.data.notebookServerUrl) {
connectionStatus.status = ConnectionStatusType.Connected;
useNotebook.getState().setConnectionInfo(connectionStatus);
useNotebook.getState().setNotebookServerInfo({ useNotebook.getState().setNotebookServerInfo({
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.data.notebookServerUrl, notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.data.notebookServerUrl,
authToken: userContext.features.notebookServerToken || connectionInfo.data.notebookAuthToken, authToken: userContext.features.notebookServerToken || connectionInfo.data.notebookAuthToken,

View File

@@ -214,14 +214,8 @@ export class EditorNeighborsComponent extends React.Component<EditorNeighborsCom
/> />
</td> </td>
<td className="actionCol"> <td className="actionCol">
<span <span className="rightPaneTrashIcon rightPaneBtns">
className="rightPaneTrashIcon rightPaneBtns" <img src={DeleteIcon} alt="Delete" onClick={() => this.removeAddedEdgeToNeighbor(index)} />
role="button"
onKeyPress={() => this.removeAddedEdgeToNeighbor(index)}
onClick={() => this.removeAddedEdgeToNeighbor(index)}
tabIndex={0}
>
<img src={DeleteIcon} alt="Delete" />
</span> </span>
</td> </td>
</tr> </tr>

View File

@@ -123,7 +123,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
<select <select
className="typeSelect" className="typeSelect"
value={singleValue.type} value={singleValue.type}
onBlur={(e) => { onChange={(e) => {
singleValue.type = e.target.value as ViewModels.InputPropertyValueTypeString; singleValue.type = e.target.value as ViewModels.InputPropertyValueTypeString;
if (singleValue.type === "null") { if (singleValue.type === "null") {
singleValue.value = undefined; singleValue.value = undefined;
@@ -217,7 +217,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
<select <select
className="typeSelect" className="typeSelect"
value={firstValue.type} value={firstValue.type}
onBlur={(e) => { onChange={(e) => {
firstValue.type = e.target.value as ViewModels.InputPropertyValueTypeString; firstValue.type = e.target.value as ViewModels.InputPropertyValueTypeString;
this.props.onUpdateProperties(this.props.editedProperties); this.props.onUpdateProperties(this.props.editedProperties);
}} }}

View File

@@ -330,10 +330,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
const partitionKeyProperty = this.props.collectionPartitionKeyProperty; const partitionKeyProperty = this.props.collectionPartitionKeyProperty;
// aggregate all the properties, remove dropped ones // aggregate all the properties, remove dropped ones
let finalProperties = editedProperties.existingProperties.concat(editedProperties.addedProperties); const finalProperties = editedProperties.existingProperties.concat(editedProperties.addedProperties);
// Compose the query // Compose the query
let pkId = editedProperties.pkId; const pkId = editedProperties.pkId;
let updateQueryFragment = ""; let updateQueryFragment = "";
finalProperties.forEach((p) => { finalProperties.forEach((p) => {
@@ -473,7 +473,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
return false; return false;
} }
let pairs: any[] = data; const pairs: any[] = data;
for (let i = 0; i < pairs.length; i++) { for (let i = 0; i < pairs.length; i++) {
const item = pairs[i]; const item = pairs[i];
if ( if (
@@ -772,8 +772,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
return; return;
} }
let edge = edges[0]; const edge = edges[0];
let graphData = this.originalGraphData; const graphData = this.originalGraphData;
graphData.addEdge(edge); graphData.addEdge(edge);
// Allow loadNeighbors to load list new edge // Allow loadNeighbors to load list new edge
@@ -803,7 +803,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
public removeEdge(edgeId: string): Q.Promise<any> { public removeEdge(edgeId: string): Q.Promise<any> {
return this.submitToBackend(`g.E('${GraphUtil.escapeSingleQuotes(edgeId)}').drop()`).then( return this.submitToBackend(`g.E('${GraphUtil.escapeSingleQuotes(edgeId)}').drop()`).then(
() => { () => {
let graphData = this.originalGraphData; const graphData = this.originalGraphData;
graphData.removeEdge(edgeId, false); graphData.removeEdge(edgeId, false);
this.updateGraphData(graphData, this.state.igraphConfig); this.updateGraphData(graphData, this.state.igraphConfig);
}, },
@@ -826,9 +826,9 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
return false; return false;
} }
let vertices: any[] = data; const vertices: any[] = data;
if (vertices.length > 0) { if (vertices.length > 0) {
let v0 = vertices[0]; const v0 = vertices[0];
if (!v0.hasOwnProperty("id") || !v0.hasOwnProperty("type") || v0.type !== "vertex") { if (!v0.hasOwnProperty("id") || !v0.hasOwnProperty("type") || v0.type !== "vertex") {
return false; return false;
} }
@@ -933,7 +933,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
throw { title: err }; throw { title: err };
} }
let vertex = vertices[0]; const vertex = vertices[0];
const graphData = this.originalGraphData; const graphData = this.originalGraphData;
graphData.addVertex(vertex); graphData.addVertex(vertex);
this.updateGraphData(graphData, this.state.igraphConfig); this.updateGraphData(graphData, this.state.igraphConfig);
@@ -1082,7 +1082,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
public static reportToConsole(type: ConsoleDataType.Info, msg: string, ...errorData: any[]): void; public static reportToConsole(type: ConsoleDataType.Info, msg: string, ...errorData: any[]): void;
public static reportToConsole(type: ConsoleDataType.Error, msg: string, ...errorData: any[]): void; public static reportToConsole(type: ConsoleDataType.Error, msg: string, ...errorData: any[]): void;
public static reportToConsole(type: ConsoleDataType, msg: string, ...errorData: any[]): void | (() => void) { public static reportToConsole(type: ConsoleDataType, msg: string, ...errorData: any[]): void | (() => void) {
let errorDataStr: string = ""; let errorDataStr = "";
if (errorData && errorData.length > 0) { if (errorData && errorData.length > 0) {
console.error(msg, errorData); console.error(msg, errorData);
errorDataStr = ": " + JSON.stringify(errorData); errorDataStr = ": " + JSON.stringify(errorData);
@@ -1224,7 +1224,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
return $.map( return $.map(
this.state.rootMap, this.state.rootMap,
(value: any, index: number): LeftPane.CaptionId => { (value: any, index: number): LeftPane.CaptionId => {
let result = GraphData.GraphData.getNodePropValue(value, key); const result = GraphData.GraphData.getNodePropValue(value, key);
return { return {
caption: result !== undefined ? result : value.id, caption: result !== undefined ? result : value.id,
id: value.id, id: value.id,
@@ -1388,7 +1388,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
* @return id * @return id
*/ */
public static getPkIdFromDocumentId(d: DataModels.DocumentId, collectionPartitionKeyProperty: string): string { public static getPkIdFromDocumentId(d: DataModels.DocumentId, collectionPartitionKeyProperty: string): string {
let { id } = d; const { id } = d;
if (typeof id !== "string") { if (typeof id !== "string") {
const error = `Vertex id is not a string: ${JSON.stringify(id)}.`; const error = `Vertex id is not a string: ${JSON.stringify(id)}.`;
logConsoleError(error); logConsoleError(error);
@@ -1425,7 +1425,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
}"] AS p FROM c WHERE NOT IS_DEFINED(c._isEdge)`; }"] AS p FROM c WHERE NOT IS_DEFINED(c._isEdge)`;
return this.executeNonPagedDocDbQuery(q).then( return this.executeNonPagedDocDbQuery(q).then(
(documents: DataModels.DocumentId[]) => { (documents: DataModels.DocumentId[]) => {
let possibleVertices = [] as PossibleVertex[]; const possibleVertices = [] as PossibleVertex[];
$.each(documents, (index: number, item: any) => { $.each(documents, (index: number, item: any) => {
if (highlightedNodeId && item.id === highlightedNodeId) { if (highlightedNodeId && item.id === highlightedNodeId) {
// Exclude highlighed node in the list // Exclude highlighed node in the list
@@ -1463,16 +1463,16 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
* @return promise when done * @return promise when done
*/ */
private editGraphEdges(editedEdges: EditedEdges): Q.Promise<any> { private editGraphEdges(editedEdges: EditedEdges): Q.Promise<any> {
let promises = []; const promises = [];
// Drop edges // Drop edges
for (let i = 0; i < editedEdges.droppedIds.length; i++) { for (let i = 0; i < editedEdges.droppedIds.length; i++) {
let id = editedEdges.droppedIds[i]; const id = editedEdges.droppedIds[i];
promises.push(this.removeEdge(id)); promises.push(this.removeEdge(id));
} }
// Add edges // Add edges
for (let i = 0; i < editedEdges.addedEdges.length; i++) { for (let i = 0; i < editedEdges.addedEdges.length; i++) {
let e = editedEdges.addedEdges[i]; const e = editedEdges.addedEdges[i];
promises.push( promises.push(
this.createNewEdge(e).then(() => { this.createNewEdge(e).then(() => {
// Reload neighbors in case we linked to a vertex that isn't loaded in the graph // Reload neighbors in case we linked to a vertex that isn't loaded in the graph
@@ -1533,7 +1533,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
private collectNodeProperties(vertices: GraphData.GremlinVertex[]) { private collectNodeProperties(vertices: GraphData.GremlinVertex[]) {
const props = {} as any; // Hashset const props = {} as any; // Hashset
$.each(vertices, (index: number, item: GraphData.GremlinVertex) => { $.each(vertices, (index: number, item: GraphData.GremlinVertex) => {
for (var p in item) { for (const p in item) {
// DocDB: Exclude type because it's always 'vertex' // DocDB: Exclude type because it's always 'vertex'
if (p !== "type" && typeof (item as any)[p] === "string") { if (p !== "type" && typeof (item as any)[p] === "string") {
props[p] = true; props[p] = true;
@@ -1543,7 +1543,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
if (item.hasOwnProperty("properties")) { if (item.hasOwnProperty("properties")) {
// TODO This is DocDB-graph specific // TODO This is DocDB-graph specific
// Assume each property value is [{value:... }] // Assume each property value is [{value:... }]
for (var f in item.properties) { for (const f in item.properties) {
props[f] = true; props[f] = true;
} }
} }
@@ -1570,21 +1570,21 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
return; return;
} }
let data = this.originalGraphData.getVertexById(id); const data = this.originalGraphData.getVertexById(id);
// A bit of translation to make it easier to display // A bit of translation to make it easier to display
let props: { [id: string]: ViewModels.GremlinPropertyValueType[] } = {}; const props: { [id: string]: ViewModels.GremlinPropertyValueType[] } = {};
for (let p in data.properties) { for (const p in data.properties) {
props[p] = data.properties[p].map((gremlinProperty) => gremlinProperty.value); props[p] = data.properties[p].map((gremlinProperty) => gremlinProperty.value);
} }
// update neighbors // update neighbors
let sources: NeighborVertexBasicInfo[] = []; const sources: NeighborVertexBasicInfo[] = [];
let targets: NeighborVertexBasicInfo[] = []; const targets: NeighborVertexBasicInfo[] = [];
this.props.onResetDefaultGraphConfigValues(); this.props.onResetDefaultGraphConfigValues();
let nodeCaption = this.state.igraphConfigUiData.nodeCaptionChoice; const nodeCaption = this.state.igraphConfigUiData.nodeCaptionChoice;
this.updateSelectedNodeNeighbors(data.id, nodeCaption, sources, targets); this.updateSelectedNodeNeighbors(data.id, nodeCaption, sources, targets);
let sData: GraphHighlightedNodeData = { const sData: GraphHighlightedNodeData = {
id: data.id, id: data.id,
label: data.label, label: data.label,
properties: props, properties: props,
@@ -1611,16 +1611,16 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
targets: NeighborVertexBasicInfo[] targets: NeighborVertexBasicInfo[]
): void { ): void {
// update neighbors // update neighbors
let gd = this.originalGraphData; const gd = this.originalGraphData;
let v = gd.getVertexById(id); const v = gd.getVertexById(id);
// Clear the array while keeping the references // Clear the array while keeping the references
sources.length = 0; sources.length = 0;
targets.length = 0; targets.length = 0;
let possibleEdgeLabels = {} as any; // Collect all edge labels in a hashset const possibleEdgeLabels = {} as any; // Collect all edge labels in a hashset
for (let p in v.inE) { for (const p in v.inE) {
possibleEdgeLabels[p] = true; possibleEdgeLabels[p] = true;
const edges = v.inE[p]; const edges = v.inE[p];
$.each(edges, (index: number, edge: GraphData.GremlinShortInEdge) => { $.each(edges, (index: number, edge: GraphData.GremlinShortInEdge) => {
@@ -1629,7 +1629,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
// If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet // If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet
return; return;
} }
let caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string; const caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
sources.push({ sources.push({
name: caption, name: caption,
id: neighborId, id: neighborId,
@@ -1639,7 +1639,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
}); });
} }
for (let p in v.outE) { for (const p in v.outE) {
possibleEdgeLabels[p] = true; possibleEdgeLabels[p] = true;
const edges = v.outE[p]; const edges = v.outE[p];
$.each(edges, (index: number, edge: GraphData.GremlinShortOutEdge) => { $.each(edges, (index: number, edge: GraphData.GremlinShortOutEdge) => {
@@ -1648,7 +1648,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
// If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet // If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet
return; return;
} }
let caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string; const caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
targets.push({ targets.push({
name: caption, name: caption,
id: neighborId, id: neighborId,
@@ -1681,20 +1681,20 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
return; return;
} }
let updatedVertex = vertices[0]; const updatedVertex = vertices[0];
if (this.originalGraphData.hasVertexId(updatedVertex.id)) { if (this.originalGraphData.hasVertexId(updatedVertex.id)) {
let currentVertex = this.originalGraphData.getVertexById(updatedVertex.id); const currentVertex = this.originalGraphData.getVertexById(updatedVertex.id);
// Copy updated properties // Copy updated properties
if (currentVertex.hasOwnProperty("properties")) { if (currentVertex.hasOwnProperty("properties")) {
delete currentVertex["properties"]; delete currentVertex["properties"];
} }
for (var p in updatedVertex) { for (const p in updatedVertex) {
(currentVertex as any)[p] = updatedVertex[p]; (currentVertex as any)[p] = updatedVertex[p];
} }
} }
// TODO This kind of assumes saveVertexProperty is done from property panes. // TODO This kind of assumes saveVertexProperty is done from property panes.
let hn = this.state.highlightedNode; const hn = this.state.highlightedNode;
if (hn && hn.id === updatedVertex.id) { if (hn && hn.id === updatedVertex.id) {
this.updatePropertiesPane(hn.id); this.updatePropertiesPane(hn.id);
} }
@@ -1708,7 +1708,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
igraphConfig?: IGraphConfig igraphConfig?: IGraphConfig
) { ) {
this.originalGraphData = graphData; this.originalGraphData = graphData;
let gd = JSON.parse(JSON.stringify(this.originalGraphData)); const gd = JSON.parse(JSON.stringify(this.originalGraphData));
if (!this.d3ForceGraph) { if (!this.d3ForceGraph) {
console.warn("Attempting to update graph, but d3ForceGraph not initialized, yet."); console.warn("Attempting to update graph, but d3ForceGraph not initialized, yet.");
return; return;

View File

@@ -58,7 +58,7 @@ export class LeftPaneComponent extends React.Component<LeftPaneComponentProps> {
className={className} className={className}
as="tr" as="tr"
aria-label={node.caption} aria-label={node.caption}
onActivated={(e) => this.props.onRootNodeSelected(node.id)} onActivated={() => this.props.onRootNodeSelected(node.id)}
key={node.id} key={node.id}
> >
<td className="resultItem"> <td className="resultItem">

View File

@@ -1,8 +1,8 @@
import * as React from "react"; import * as React from "react";
import { GraphVizComponent, GraphVizComponentProps } from "./GraphVizComponent";
import CollapseArrowIcon from "../../../../images/Collapse_arrow_14x14.svg"; import CollapseArrowIcon from "../../../../images/Collapse_arrow_14x14.svg";
import ExpandIcon from "../../../../images/Expand_14x14.svg"; import ExpandIcon from "../../../../images/Expand_14x14.svg";
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif"; import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
import { GraphVizComponent, GraphVizComponentProps } from "./GraphVizComponent";
interface MiddlePaneComponentProps { interface MiddlePaneComponentProps {
isTabsContentExpanded: boolean; isTabsContentExpanded: boolean;
@@ -17,14 +17,7 @@ export class MiddlePaneComponent extends React.Component<MiddlePaneComponentProp
<div className="middlePane"> <div className="middlePane">
<div className="graphTitle"> <div className="graphTitle">
<span className="paneTitle">Graph</span> <span className="paneTitle">Graph</span>
<span <span className="graphExpandCollapseBtn pull-right" onClick={this.props.toggleExpandGraph}>
className="graphExpandCollapseBtn pull-right"
onClick={this.props.toggleExpandGraph}
role="button"
aria-expanded={this.props.isTabsContentExpanded}
aria-name="View graph in full screen"
tabIndex={0}
>
<img <img
src={this.props.isTabsContentExpanded ? CollapseArrowIcon : ExpandIcon} src={this.props.isTabsContentExpanded ? CollapseArrowIcon : ExpandIcon}
alt={this.props.isTabsContentExpanded ? "collapse graph content" : "expand graph content"} alt={this.props.isTabsContentExpanded ? "collapse graph content" : "expand graph content"}

View File

@@ -1,8 +1,8 @@
import React from "react";
import { mount, ReactWrapper } from "enzyme"; import { mount, ReactWrapper } from "enzyme";
import * as Q from "q"; import * as Q from "q";
import { NodePropertiesComponent, NodePropertiesComponentProps, Mode } from "./NodePropertiesComponent"; import React from "react";
import { GraphHighlightedNodeData, EditedProperties, EditedEdges, PossibleVertex } from "./GraphExplorer"; import { GraphHighlightedNodeData, PossibleVertex } from "./GraphExplorer";
import { Mode, NodePropertiesComponent, NodePropertiesComponentProps } from "./NodePropertiesComponent";
describe("Property pane", () => { describe("Property pane", () => {
const title = "My Title"; const title = "My Title";
@@ -37,17 +37,25 @@ describe("Property pane", () => {
return { return {
expandedTitle: title, expandedTitle: title,
isCollapsed: false, isCollapsed: false,
onCollapsedChanged: (newValue: boolean): void => {}, onCollapsedChanged: (): void => {
("");
},
node: highlightedNode, node: highlightedNode,
getPkIdFromNodeData: (v: GraphHighlightedNodeData): string => null, getPkIdFromNodeData: (): string => undefined,
collectionPartitionKeyProperty: null, collectionPartitionKeyProperty: undefined,
updateVertexProperties: (editedProperties: EditedProperties): Q.Promise<void> => Q.resolve(), updateVertexProperties: (): Q.Promise<void> => Q.resolve(),
selectNode: (id: string): void => {}, selectNode: (): void => {
updatePossibleVertices: (): Q.Promise<PossibleVertex[]> => Q.resolve(null), ("");
possibleEdgeLabels: null, },
editGraphEdges: (editedEdges: EditedEdges): Q.Promise<any> => Q.resolve(), updatePossibleVertices: (): Q.Promise<PossibleVertex[]> => Q.resolve(undefined),
deleteHighlightedNode: (): void => {}, possibleEdgeLabels: undefined,
onModeChanged: (newMode: Mode): void => {}, editGraphEdges: (): Q.Promise<unknown> => Q.resolve(),
deleteHighlightedNode: (): void => {
("");
},
onModeChanged: (): void => {
("");
},
viewMode: Mode.READONLY_PROP, viewMode: Mode.READONLY_PROP,
}; };
}; };

View File

@@ -45,7 +45,7 @@ export interface NodePropertiesComponentProps {
selectNode: (id: string) => void; selectNode: (id: string) => void;
updatePossibleVertices: () => Q.Promise<PossibleVertex[]>; updatePossibleVertices: () => Q.Promise<PossibleVertex[]>;
possibleEdgeLabels: Item[]; possibleEdgeLabels: Item[];
editGraphEdges: (editedEdges: EditedEdges) => Q.Promise<any>; editGraphEdges: (editedEdges: EditedEdges) => Q.Promise<unknown>;
deleteHighlightedNode: () => void; deleteHighlightedNode: () => void;
onModeChanged: (newMode: Mode) => void; onModeChanged: (newMode: Mode) => void;
viewMode: Mode; // If viewMode is specified in parent, keep state in sync with it viewMode: Mode; // If viewMode is specified in parent, keep state in sync with it
@@ -72,7 +72,7 @@ export class NodePropertiesComponent extends React.Component<
super(props); super(props);
this.state = { this.state = {
editedProperties: { editedProperties: {
pkId: null, pkId: undefined,
readOnlyProperties: [], readOnlyProperties: [],
existingProperties: [], existingProperties: [],
addedProperties: [], addedProperties: [],
@@ -98,15 +98,12 @@ export class NodePropertiesComponent extends React.Component<
}; };
} }
public static getDerivedStateFromProps( public static getDerivedStateFromProps(props: NodePropertiesComponentProps): Partial<NodePropertiesComponentState> {
props: NodePropertiesComponentProps,
state: NodePropertiesComponentState
): Partial<NodePropertiesComponentState> {
if (props.viewMode !== Mode.READONLY_PROP) { if (props.viewMode !== Mode.READONLY_PROP) {
return { isDeleteConfirm: false }; return { isDeleteConfirm: false };
} }
return null; return undefined;
} }
public render(): JSX.Element { public render(): JSX.Element {
@@ -137,11 +134,14 @@ export class NodePropertiesComponent extends React.Component<
* Get type option. Limit to string, number or boolean * Get type option. Limit to string, number or boolean
* @param value * @param value
*/ */
private static getTypeOption(value: any): ViewModels.InputPropertyValueTypeString { private static getTypeOption(
if (value == null) { value: null | string | number | undefined | boolean
): ViewModels.InputPropertyValueTypeString {
// eslint-disable-next-line no-null/no-null
if (value === null) {
return "null"; return "null";
} }
let type = typeof value; const type = typeof value;
switch (type) { switch (type) {
case "number": case "number":
case "boolean": case "boolean":
@@ -173,9 +173,10 @@ export class NodePropertiesComponent extends React.Component<
const existingProps: ViewModels.InputProperty[] = []; const existingProps: ViewModels.InputProperty[] = [];
// eslint-disable-next-line no-prototype-builtins
if (this.props.node.hasOwnProperty("properties")) { if (this.props.node.hasOwnProperty("properties")) {
const hProps = this.props.node["properties"]; const hProps = this.props.node["properties"];
for (let p in hProps) { for (const p in hProps) {
const propValues = hProps[p]; const propValues = hProps[p];
(p === partitionKeyProperty ? readOnlyProps : existingProps).push({ (p === partitionKeyProperty ? readOnlyProps : existingProps).push({
key: p, key: p,
@@ -437,7 +438,7 @@ export class NodePropertiesComponent extends React.Component<
</div> </div>
); );
} else { } else {
return null; return undefined;
} }
} }

View File

@@ -93,7 +93,7 @@ exports[`<EditorNodePropertiesComponent /> renders component 1`] = `
<td> <td>
<select <select
className="typeSelect" className="typeSelect"
onBlur={[Function]} onChange={[Function]}
required={true} required={true}
value="string" value="string"
> >
@@ -282,7 +282,7 @@ exports[`<EditorNodePropertiesComponent /> renders proper unicode 1`] = `
<td> <td>
<select <select
className="typeSelect" className="typeSelect"
onBlur={[Function]} onChange={[Function]}
required={true} required={true}
value="string" value="string"
> >
@@ -344,7 +344,7 @@ exports[`<EditorNodePropertiesComponent /> renders proper unicode 1`] = `
<td> <td>
<select <select
className="typeSelect" className="typeSelect"
onBlur={[Function]} onChange={[Function]}
required={true} required={true}
value="string" value="string"
> >

File diff suppressed because it is too large Load Diff

View File

@@ -132,6 +132,7 @@ export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = (
onChange={(event: React.ChangeEvent<HTMLInputElement>) => { onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
onLabelChange(event); onLabelChange(event);
}} }}
autoFocus
/> />
<div className="actionCol"></div> <div className="actionCol"></div>
</div> </div>

View File

@@ -9,7 +9,6 @@ import create, { UseStore } from "zustand";
import { StyleConstants } from "../../../Common/Constants"; import { StyleConstants } from "../../../Common/Constants";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { useTabs } from "../../../hooks/useTabs"; import { useTabs } from "../../../hooks/useTabs";
import { userContext } from "../../../UserContext";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { useSelectedNode } from "../../useSelectedNode"; import { useSelectedNode } from "../../useSelectedNode";
@@ -55,13 +54,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor); const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true)); uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
if ( uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus("connectionStatus"));
userContext.features.notebooksTemporarilyDown === false &&
userContext.features.phoenix === true &&
useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2
) {
uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus("connectionStatus"));
}
if (useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2) { if (useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker")); uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker"));

View File

@@ -1,4 +1,4 @@
import { Icon, ProgressIndicator, Stack, TooltipHost } from "@fluentui/react"; import { Icon, ProgressIndicator, Spinner, SpinnerSize, Stack, TooltipHost } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import { ConnectionStatusType } from "../../../Common/Constants"; import { ConnectionStatusType } from "../../../Common/Constants";
import { useNotebook } from "../../Notebook/useNotebook"; import { useNotebook } from "../../Notebook/useNotebook";
@@ -40,9 +40,14 @@ export const ConnectionStatus: React.FC = (): JSX.Element => {
const connectionInfo = useNotebook((state) => state.connectionInfo); const connectionInfo = useNotebook((state) => state.connectionInfo);
if (!connectionInfo) { if (!connectionInfo) {
return <></>; return (
<Stack className="connectionStatusContainer" horizontal>
<span>Connecting</span>
<Spinner size={SpinnerSize.medium} />
</Stack>
);
} }
if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connecting && isActive === false) { if (connectionInfo && connectionInfo.status === ConnectionStatusType.Allocating && isActive === false) {
setIsActive(true); setIsActive(true);
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connected && isActive === true) { } else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connected && isActive === true) {
stopTimer(); stopTimer();
@@ -63,7 +68,7 @@ export const ConnectionStatus: React.FC = (): JSX.Element => {
<span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}> <span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}>
{connectionInfo.status} {connectionInfo.status}
</span> </span>
{connectionInfo.status === ConnectionStatusType.Connecting && isActive && ( {connectionInfo.status === ConnectionStatusType.Allocating && isActive && (
<ProgressIndicator description={minute + ":" + second} /> <ProgressIndicator description={minute + ":" + second} />
)} )}
</Stack> </Stack>

View File

@@ -103,7 +103,6 @@ export class NotificationConsoleComponent extends React.Component<
onClick={() => this.expandCollapseConsole()} onClick={() => this.expandCollapseConsole()}
onKeyDown={(event: React.KeyboardEvent<HTMLDivElement>) => this.onExpandCollapseKeyPress(event)} onKeyDown={(event: React.KeyboardEvent<HTMLDivElement>) => this.onExpandCollapseKeyPress(event)}
tabIndex={0} tabIndex={0}
role="button"
> >
<div className="statusBar"> <div className="statusBar">
<span className="dataTypeIcons"> <span className="dataTypeIcons">
@@ -130,7 +129,7 @@ export class NotificationConsoleComponent extends React.Component<
className="expandCollapseButton" className="expandCollapseButton"
role="button" role="button"
tabIndex={0} tabIndex={0}
aria-label={"console button" + (this.props.isConsoleExpanded ? " expanded" : " collapsed")} aria-label={"console button" + (this.props.isConsoleExpanded ? " collapsed" : " expanded")}
aria-expanded={!this.props.isConsoleExpanded} aria-expanded={!this.props.isConsoleExpanded}
> >
<img <img
@@ -163,7 +162,7 @@ export class NotificationConsoleComponent extends React.Component<
onKeyDown={(event: React.KeyboardEvent<HTMLSpanElement>) => this.onClearNotificationsKeyPress(event)} onKeyDown={(event: React.KeyboardEvent<HTMLSpanElement>) => this.onClearNotificationsKeyPress(event)}
tabIndex={0} tabIndex={0}
> >
<img src={ClearIcon} alt="clear notifications icon" /> <img src={ClearIcon} alt="clear notifications image" />
Clear Notifications Clear Notifications
</span> </span>
</div> </div>
@@ -206,9 +205,7 @@ export class NotificationConsoleComponent extends React.Component<
{item.type === ConsoleDataType.Error && <img className="errorIcon" src={ErrorRedIcon} alt="error" />} {item.type === ConsoleDataType.Error && <img className="errorIcon" src={ErrorRedIcon} alt="error" />}
{item.type === ConsoleDataType.InProgress && <img className="loaderIcon" src={LoaderIcon} alt="in progress" />} {item.type === ConsoleDataType.InProgress && <img className="loaderIcon" src={LoaderIcon} alt="in progress" />}
<span className="date">{item.date}</span> <span className="date">{item.date}</span>
<span className="message" role="alert" aria-live="assertive"> <span className="message">{item.message}</span>
{item.message}
</span>
</div> </div>
)); ));
} }

View File

@@ -9,7 +9,6 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
id="notificationConsoleHeader" id="notificationConsoleHeader"
onClick={[Function]} onClick={[Function]}
onKeyDown={[Function]} onKeyDown={[Function]}
role="button"
tabIndex={0} tabIndex={0}
> >
<div <div
@@ -71,7 +70,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
</div> </div>
<div <div
aria-expanded={true} aria-expanded={true}
aria-label="console button collapsed" aria-label="console button expanded"
className="expandCollapseButton" className="expandCollapseButton"
role="button" role="button"
tabIndex={0} tabIndex={0}
@@ -151,7 +150,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
tabIndex={0} tabIndex={0}
> >
<img <img
alt="clear notifications icon" alt="clear notifications image"
src="" src=""
/> />
Clear Notifications Clear Notifications
@@ -174,7 +173,6 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
id="notificationConsoleHeader" id="notificationConsoleHeader"
onClick={[Function]} onClick={[Function]}
onKeyDown={[Function]} onKeyDown={[Function]}
role="button"
tabIndex={0} tabIndex={0}
> >
<div <div
@@ -238,7 +236,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
</div> </div>
<div <div
aria-expanded={true} aria-expanded={true}
aria-label="console button collapsed" aria-label="console button expanded"
className="expandCollapseButton" className="expandCollapseButton"
role="button" role="button"
tabIndex={0} tabIndex={0}
@@ -318,7 +316,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
tabIndex={0} tabIndex={0}
> >
<img <img
alt="clear notifications icon" alt="clear notifications image"
src="" src=""
/> />
Clear Notifications Clear Notifications
@@ -342,9 +340,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
date date
</span> </span>
<span <span
aria-live="assertive"
className="message" className="message"
role="alert"
> >
message message
</span> </span>

View File

@@ -29,7 +29,7 @@ interface NotebookState {
gitHubNotebooksContentRoot: NotebookContentItem; gitHubNotebooksContentRoot: NotebookContentItem;
galleryContentRoot: NotebookContentItem; galleryContentRoot: NotebookContentItem;
connectionInfo: DataModels.ContainerConnectionInfo; connectionInfo: DataModels.ContainerConnectionInfo;
notebookFolderName: string; NotebookFolderName: string;
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void; setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void; setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void; setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
@@ -38,7 +38,6 @@ interface NotebookState {
setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => void; setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => void;
setIsShellEnabled: (isShellEnabled: boolean) => void; setIsShellEnabled: (isShellEnabled: boolean) => void;
setNotebookBasePath: (notebookBasePath: string) => void; setNotebookBasePath: (notebookBasePath: string) => void;
setNotebookFolderName: (notebookFolderName: string) => void;
refreshNotebooksEnabledStateForAccount: () => Promise<void>; refreshNotebooksEnabledStateForAccount: () => Promise<void>;
findItem: (root: NotebookContentItem, item: NotebookContentItem) => NotebookContentItem; findItem: (root: NotebookContentItem, item: NotebookContentItem) => NotebookContentItem;
insertNotebookItem: (parent: NotebookContentItem, item: NotebookContentItem, isGithubTree?: boolean) => void; insertNotebookItem: (parent: NotebookContentItem, item: NotebookContentItem, isGithubTree?: boolean) => void;
@@ -70,7 +69,7 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
gitHubNotebooksContentRoot: undefined, gitHubNotebooksContentRoot: undefined,
galleryContentRoot: undefined, galleryContentRoot: undefined,
connectionInfo: undefined, connectionInfo: undefined,
notebookFolderName: undefined, NotebookFolderName: userContext.features.phoenix ? "My Notebooks Scratch" : "My Notebooks",
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }), setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }), setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
@@ -81,7 +80,6 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => set({ memoryUsageInfo }), setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => set({ memoryUsageInfo }),
setIsShellEnabled: (isShellEnabled: boolean) => set({ isShellEnabled }), setIsShellEnabled: (isShellEnabled: boolean) => set({ isShellEnabled }),
setNotebookBasePath: (notebookBasePath: string) => set({ notebookBasePath }), setNotebookBasePath: (notebookBasePath: string) => set({ notebookBasePath }),
setNotebookFolderName: (notebookFolderName: string) => set({ notebookFolderName }),
refreshNotebooksEnabledStateForAccount: async (): Promise<void> => { refreshNotebooksEnabledStateForAccount: async (): Promise<void> => {
const { databaseAccount, authType } = userContext; const { databaseAccount, authType } = userContext;
if ( if (
@@ -175,10 +173,8 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root }); isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
}, },
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => { initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
const notebookFolderName = userContext.features.phoenix === true ? "Temporary Notebooks" : "My Notebooks";
set({ notebookFolderName });
const myNotebooksContentRoot = { const myNotebooksContentRoot = {
name: get().notebookFolderName, name: get().NotebookFolderName,
path: get().notebookBasePath, path: get().notebookBasePath,
type: NotebookContentItemType.Directory, type: NotebookContentItemType.Directory,
}; };
@@ -187,14 +183,11 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
path: "Gallery", path: "Gallery",
type: NotebookContentItemType.File, type: NotebookContentItemType.File,
}; };
const gitHubNotebooksContentRoot = notebookManager?.gitHubOAuthService?.isLoggedIn() const gitHubNotebooksContentRoot = {
? { name: "GitHub repos",
name: "GitHub repos", path: "PsuedoDir",
path: "PsuedoDir", type: NotebookContentItemType.Directory,
type: NotebookContentItemType.Directory, };
}
: undefined;
set({ set({
myNotebooksContentRoot, myNotebooksContentRoot,
galleryContentRoot, galleryContentRoot,

View File

@@ -161,7 +161,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
true true
).toLocaleLowerCase()}.`} ).toLocaleLowerCase()}.`}
> >
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} /> <Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost> </TooltipHost>
</Stack> </Stack>
@@ -173,6 +173,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-checked={this.state.createNewDatabase} aria-checked={this.state.createNewDatabase}
name="databaseType" name="databaseType"
type="radio" type="radio"
role="radio"
id="databaseCreateNew" id="databaseCreateNew"
tabIndex={0} tabIndex={0}
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)} onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
@@ -186,6 +187,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-checked={!this.state.createNewDatabase} aria-checked={!this.state.createNewDatabase}
name="databaseType" name="databaseType"
type="radio" type="radio"
role="radio"
tabIndex={0} tabIndex={0}
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)} onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
/> />
@@ -207,7 +209,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
size={40} size={40}
className="panelTextField" className="panelTextField"
aria-label="New database id" aria-label="New database id"
tabIndex={0} autoFocus
value={this.state.newDatabaseId} value={this.state.newDatabaseId}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
this.setState({ newDatabaseId: event.target.value }) this.setState({ newDatabaseId: event.target.value })
@@ -234,7 +236,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
true true
).toLocaleLowerCase()} within the database.`} ).toLocaleLowerCase()} within the database.`}
> >
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} /> <Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost> </TooltipHost>
</Stack> </Stack>
)} )}
@@ -277,7 +279,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
directionalHint={DirectionalHint.bottomLeftEdge} directionalHint={DirectionalHint.bottomLeftEdge}
content={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`} content={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
> >
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} /> <Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost> </TooltipHost>
</Stack> </Stack>
@@ -317,6 +319,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-label="Turn on indexing" aria-label="Turn on indexing"
aria-checked={this.state.enableIndexing} aria-checked={this.state.enableIndexing}
type="radio" type="radio"
role="radio"
tabIndex={0} tabIndex={0}
onChange={this.onTurnOnIndexing.bind(this)} onChange={this.onTurnOnIndexing.bind(this)}
/> />
@@ -328,6 +331,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-label="Turn off indexing" aria-label="Turn off indexing"
aria-checked={!this.state.enableIndexing} aria-checked={!this.state.enableIndexing}
type="radio" type="radio"
role="radio"
tabIndex={0} tabIndex={0}
onChange={this.onTurnOffIndexing.bind(this)} onChange={this.onTurnOffIndexing.bind(this)}
/> />
@@ -358,7 +362,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
"Sharded collections split your data across many replica sets (shards) to achieve unlimited scalability. Sharded collections require choosing a shard key (field) to evenly distribute your data." "Sharded collections split your data across many replica sets (shards) to achieve unlimited scalability. Sharded collections require choosing a shard key (field) to evenly distribute your data."
} }
> >
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} /> <Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost> </TooltipHost>
</Stack> </Stack>
@@ -370,6 +374,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-checked={!this.state.isSharded} aria-checked={!this.state.isSharded}
name="unsharded" name="unsharded"
type="radio" type="radio"
role="radio"
id="unshardedOption" id="unshardedOption"
tabIndex={0} tabIndex={0}
onChange={this.onUnshardedRadioBtnChange.bind(this)} onChange={this.onUnshardedRadioBtnChange.bind(this)}
@@ -383,6 +388,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-checked={this.state.isSharded} aria-checked={this.state.isSharded}
name="sharded" name="sharded"
type="radio" type="radio"
role="radio"
id="shardedOption" id="shardedOption"
tabIndex={0} tabIndex={0}
onChange={this.onShardedRadioBtnChange.bind(this)} onChange={this.onShardedRadioBtnChange.bind(this)}
@@ -403,7 +409,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
directionalHint={DirectionalHint.bottomLeftEdge} directionalHint={DirectionalHint.bottomLeftEdge}
content={this.getPartitionKeyTooltipText()} content={this.getPartitionKeyTooltipText()}
> >
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} /> <Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost> </TooltipHost>
</Stack> </Stack>
@@ -461,7 +467,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
does not count towards the throughput you provisioned for the database. This throughput amount will be does not count towards the throughput you provisioned for the database. This throughput amount will be
billed in addition to the throughput amount you provisioned at the database level.`} billed in addition to the throughput amount you provisioned at the database level.`}
> >
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} /> <Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost> </TooltipHost>
</Stack> </Stack>
)} )}
@@ -491,7 +497,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
creating a unique key policy when a container is created, you ensure the uniqueness of one or more values creating a unique key policy when a container is created, you ensure the uniqueness of one or more values
per partition key." per partition key."
> >
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} /> <Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost> </TooltipHost>
</Stack> </Stack>
@@ -508,6 +514,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
: "Comma separated paths e.g. /firstName,/address/zipCode" : "Comma separated paths e.g. /firstName,/address/zipCode"
} }
className="panelTextField" className="panelTextField"
autoFocus
value={uniqueKey} value={uniqueKey}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => { onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
const uniqueKeys = this.state.uniqueKeys.map((uniqueKey: string, j: number) => { const uniqueKeys = this.state.uniqueKeys.map((uniqueKey: string, j: number) => {
@@ -553,7 +560,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
directionalHint={DirectionalHint.bottomLeftEdge} directionalHint={DirectionalHint.bottomLeftEdge}
content={this.getAnalyticalStorageTooltipContent()} content={this.getAnalyticalStorageTooltipContent()}
> >
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} /> <Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost> </TooltipHost>
</Stack> </Stack>
@@ -566,6 +573,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-checked={this.state.enableAnalyticalStore} aria-checked={this.state.enableAnalyticalStore}
name="analyticalStore" name="analyticalStore"
type="radio" type="radio"
role="radio"
id="enableAnalyticalStoreBtn" id="enableAnalyticalStoreBtn"
tabIndex={0} tabIndex={0}
onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)} onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)}
@@ -580,6 +588,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-checked={!this.state.enableAnalyticalStore} aria-checked={!this.state.enableAnalyticalStore}
name="analyticalStore" name="analyticalStore"
type="radio" type="radio"
role="radio"
id="disableAnalyticalStoreBtn" id="disableAnalyticalStoreBtn"
tabIndex={0} tabIndex={0}
onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)} onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)}
@@ -628,7 +637,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
directionalHint={DirectionalHint.bottomLeftEdge} directionalHint={DirectionalHint.bottomLeftEdge}
content="The _id field is indexed by default. Creating a wildcard index for all fields will optimize queries and is recommended for development." content="The _id field is indexed by default. Creating a wildcard index for all fields will optimize queries and is recommended for development."
> >
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} /> <Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost> </TooltipHost>
</Stack> </Stack>

View File

@@ -23,12 +23,10 @@ import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneFor
export interface AddDatabasePaneProps { export interface AddDatabasePaneProps {
explorer: Explorer; explorer: Explorer;
buttonElement?: HTMLElement;
} }
export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
explorer: container, explorer: container,
buttonElement,
}: AddDatabasePaneProps) => { }: AddDatabasePaneProps) => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel); const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
let throughput: number; let throughput: number;
@@ -79,7 +77,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
dataExplorerArea: Constants.Areas.ContextualPane, dataExplorerArea: Constants.Areas.ContextualPane,
}; };
TelemetryProcessor.trace(Action.CreateDatabase, ActionModifiers.Open, addDatabasePaneOpenMessage); TelemetryProcessor.trace(Action.CreateDatabase, ActionModifiers.Open, addDatabasePaneOpenMessage);
buttonElement.focus();
}, []); }, []);
const onSubmit = () => { const onSubmit = () => {
@@ -210,6 +207,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
placeholder={databaseIdPlaceHolder} placeholder={databaseIdPlaceHolder}
value={databaseId} value={databaseId}
onChange={handleonChangeDBId} onChange={handleonChangeDBId}
autoFocus
styles={getTextFieldStyles()} styles={getTextFieldStyles()}
/> />

View File

@@ -33,6 +33,7 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
aria-label="Database id" aria-label="Database id"
aria-required="true" aria-required="true"
autoComplete="off" autoComplete="off"
autoFocus={true}
id="database-id" id="database-id"
onChange={[Function]} onChange={[Function]}
pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]" pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]"

View File

@@ -169,6 +169,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
aria-label="Create new keyspace" aria-label="Create new keyspace"
checked={keyspaceCreateNew} checked={keyspaceCreateNew}
type="radio" type="radio"
role="radio"
tabIndex={0} tabIndex={0}
onChange={() => { onChange={() => {
setKeyspaceCreateNew(true); setKeyspaceCreateNew(true);
@@ -183,6 +184,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
aria-label="Use existing keyspace" aria-label="Use existing keyspace"
checked={!keyspaceCreateNew} checked={!keyspaceCreateNew}
type="radio" type="radio"
role="radio"
tabIndex={0} tabIndex={0}
onChange={() => { onChange={() => {
setKeyspaceCreateNew(false); setKeyspaceCreateNew(false);
@@ -196,7 +198,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
<Stack className="panelGroupSpacing"> <Stack className="panelGroupSpacing">
<TextField <TextField
aria-required="true" aria-required="true"
required={true}
autoComplete="off" autoComplete="off"
styles={getTextFieldStyles()} styles={getTextFieldStyles()}
pattern="[^/?#\\]*[^/?# \\]" pattern="[^/?#\\]*[^/?# \\]"
@@ -206,6 +207,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
value={newKeyspaceId} value={newKeyspaceId}
onChange={(e, newValue) => setNewKeyspaceId(newValue)} onChange={(e, newValue) => setNewKeyspaceId(newValue)}
ariaLabel="Keyspace id" ariaLabel="Keyspace id"
autoFocus
/> />
{!isServerlessAccount() && ( {!isServerlessAccount() && (
@@ -283,7 +285,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
underlined underlined
styles={getTextFieldStyles({ fontSize: 12, width: 150 })} styles={getTextFieldStyles({ fontSize: 12, width: 150 })}
aria-required="true" aria-required="true"
required={true}
ariaLabel="addCollection-tableId" ariaLabel="addCollection-tableId"
autoComplete="off" autoComplete="off"
pattern="[^/?#\\]*[^/?# \\]" pattern="[^/?#\\]*[^/?# \\]"

View File

@@ -49,8 +49,8 @@ export const CopyNotebookPaneComponent: FunctionComponent<CopyNotebookPaneProps>
const options: IDropdownOption[] = []; const options: IDropdownOption[] = [];
options.push({ options.push({
key: "MyNotebooks-Item", key: "MyNotebooks-Item",
text: useNotebook.getState().notebookFolderName, text: useNotebook.getState().NotebookFolderName,
title: useNotebook.getState().notebookFolderName, title: useNotebook.getState().NotebookFolderName,
data: { data: {
type: "MyNotebooks", type: "MyNotebooks",
} as Location, } as Location,

View File

@@ -108,8 +108,6 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
submitButtonText: "OK", submitButtonText: "OK",
onSubmit, onSubmit,
}; };
const confirmContainer = `Confirm by typing the ${collectionName.toLowerCase()} id`;
const reasonInfo = `Help us improve Azure Cosmos DB! What is the reason why you are deleting this ${collectionName}?`;
return ( return (
<RightPaneForm {...props}> <RightPaneForm {...props}>
<div className="panelFormWrapper"> <div className="panelFormWrapper">
@@ -119,12 +117,12 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
<Text variant="small">Confirm by typing the {collectionName.toLowerCase()} id</Text> <Text variant="small">Confirm by typing the {collectionName.toLowerCase()} id</Text>
<TextField <TextField
id="confirmCollectionId" id="confirmCollectionId"
autoFocus
value={inputCollectionName} value={inputCollectionName}
styles={{ fieldGroup: { width: 300 } }} styles={{ fieldGroup: { width: 300 } }}
onChange={(event, newInput?: string) => { onChange={(event, newInput?: string) => {
setInputCollectionName(newInput); setInputCollectionName(newInput);
}} }}
ariaLabel={confirmContainer}
/> />
</div> </div>
{shouldRecordFeedback() && ( {shouldRecordFeedback() && (
@@ -144,7 +142,6 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
onChange={(event, newInput?: string) => { onChange={(event, newInput?: string) => {
setDeleteCollectionFeedback(newInput); setDeleteCollectionFeedback(newInput);
}} }}
ariaLabel={reasonInfo}
/> />
</div> </div>
)} )}

View File

@@ -40,7 +40,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
</span> </span>
</Text> </Text>
<StyledTextFieldBase <StyledTextFieldBase
ariaLabel="Confirm by typing the container id" autoFocus={true}
id="confirmCollectionId" id="confirmCollectionId"
onChange={[Function]} onChange={[Function]}
styles={ styles={
@@ -53,7 +53,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
value="" value=""
> >
<TextFieldBase <TextFieldBase
ariaLabel="Confirm by typing the container id" autoFocus={true}
deferredValidationTime={200} deferredValidationTime={200}
id="confirmCollectionId" id="confirmCollectionId"
onChange={[Function]} onChange={[Function]}
@@ -346,7 +346,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
> >
<input <input
aria-invalid={false} aria-invalid={false}
aria-label="Confirm by typing the container id" autoFocus={true}
className="ms-TextField-field field-57" className="ms-TextField-field field-57"
id="confirmCollectionId" id="confirmCollectionId"
onBlur={[Function]} onBlur={[Function]}

View File

@@ -118,8 +118,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
message: message:
"Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.", "Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.",
}; };
const confirmDatabase = "Confirm by typing the database id";
const reasonInfo = "Help us improve Azure Cosmos DB! What is the reason why you are deleting this database?";
return ( return (
<RightPaneForm {...props}> <RightPaneForm {...props}>
{!formError && <PanelInfoErrorComponent {...errorProps} />} {!formError && <PanelInfoErrorComponent {...errorProps} />}
@@ -129,11 +128,11 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
<Text variant="small">Confirm by typing the database id</Text> <Text variant="small">Confirm by typing the database id</Text>
<TextField <TextField
id="confirmDatabaseId" id="confirmDatabaseId"
autoFocus
styles={{ fieldGroup: { width: 300 } }} styles={{ fieldGroup: { width: 300 } }}
onChange={(event, newInput?: string) => { onChange={(event, newInput?: string) => {
setDatabaseInput(newInput); setDatabaseInput(newInput);
}} }}
ariaLabel={confirmDatabase}
/> />
</div> </div>
{isLastNonEmptyDatabase() && ( {isLastNonEmptyDatabase() && (
@@ -152,7 +151,6 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
onChange={(event, newInput?: string) => { onChange={(event, newInput?: string) => {
setDatabaseFeedbackInput(newInput); setDatabaseFeedbackInput(newInput);
}} }}
ariaLabel={reasonInfo}
/> />
</div> </div>
)} )}

View File

@@ -153,7 +153,7 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
selectedKey={paramKeyValue && paramKeyValue.key} selectedKey={paramKeyValue && paramKeyValue.key}
/> />
))} ))}
<Stack horizontal onClick={addNewParamAtLastIndex} tabIndex={0}> <Stack horizontal onClick={addNewParamAtLastIndex}>
<Image {...imageProps} src={AddPropertyIcon} alt="Add param" /> <Image {...imageProps} src={AddPropertyIcon} alt="Add param" />
<Text className="addNewParamStyle">Add New Param</Text> <Text className="addNewParamStyle">Add New Param</Text>
</Stack> </Stack>

View File

@@ -59,22 +59,30 @@ export const InputParameter: FunctionComponent<InputParameterProps> = ({
onChange={onParamKeyChange} onChange={onParamKeyChange}
options={options} options={options}
styles={dropdownStyles} styles={dropdownStyles}
tabIndex={0}
/> />
<TextField <TextField
label={inputLabel && inputLabel} label={inputLabel && inputLabel}
id="confirmCollectionId" id="confirmCollectionId"
autoFocus
value={paramValue} value={paramValue}
onChange={onParamValueChange} onChange={onParamValueChange}
/> />
{isAddRemoveVisible && ( {isAddRemoveVisible && (
<> <>
<div tabIndex={0} onClick={onDeleteParamKeyPress} role="button" onKeyPress={onDeleteParamKeyPress}> <Image
<Image {...imageProps} src={EntityCancelIcon} alt="Delete param" id="deleteparam" /> {...imageProps}
</div> src={EntityCancelIcon}
<div tabIndex={0} onClick={onAddNewParamKeyPress} role="button" onKeyPress={onAddNewParamKeyPress}> alt="Delete param"
<Image {...imageProps} src={AddPropertyIcon} alt="Add param" id="addparam" /> id="deleteparam"
</div> onClick={onDeleteParamKeyPress}
/>
<Image
{...imageProps}
src={AddPropertyIcon}
alt="Add param"
id="addparam"
onClick={onAddNewParamKeyPress}
/>
</> </>
)} )}
</Stack> </Stack>

View File

@@ -92,6 +92,7 @@ export const LoadQueryPane: FunctionComponent = (): JSX.Element => {
id="confirmCollectionId" id="confirmCollectionId"
label="Select a query document" label="Select a query document"
value={selectedFileName} value={selectedFileName}
autoFocus
readOnly readOnly
styles={{ fieldGroup: { width: 300 } }} styles={{ fieldGroup: { width: 300 } }}
/> />

View File

@@ -17,6 +17,7 @@ exports[`Load Query Pane should render Default properly 1`] = `
horizontal={true} horizontal={true}
> >
<StyledTextFieldBase <StyledTextFieldBase
autoFocus={true}
id="confirmCollectionId" id="confirmCollectionId"
label="Select a query document" label="Select a query document"
readOnly={true} readOnly={true}

View File

@@ -3,13 +3,13 @@
.panelFormWrapper { .panelFormWrapper {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 100%; height: 100%;
.panelMainContent { .panelMainContent {
flex-grow: 1; flex-grow: 1;
padding: 0 34px; padding: 0 34px;
margin: 20px 0; margin: 20px 0;
overflow-x: hidden; overflow: auto;
& > :not(.collapsibleSection) { & > :not(.collapsibleSection) {
margin-bottom: @DefaultSpace; margin-bottom: @DefaultSpace;

View File

@@ -33,13 +33,7 @@ export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProp
<Stack className="panelInfoErrorContainer" horizontal verticalAlign="center"> <Stack className="panelInfoErrorContainer" horizontal verticalAlign="center">
{icon} {icon}
<span className="panelWarningErrorDetailsLinkContainer"> <span className="panelWarningErrorDetailsLinkContainer">
<Text <Text className="panelWarningErrorMessage" variant="small" aria-label="message">
role="alert"
aria-live="assertive"
aria-label={message}
className="panelWarningErrorMessage"
variant="small"
>
{message} {message}
{link && linkText && ( {link && linkText && (
<Link target="_blank" href={link}> <Link target="_blank" href={link}>
@@ -48,15 +42,9 @@ export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProp
)} )}
</Text> </Text>
{showErrorDetails && ( {showErrorDetails && (
<span <a className="paneErrorLink" role="link" onClick={expandConsole}>
className="paneErrorLink moreOption"
role="link"
onClick={expandConsole}
onKeyPress={expandConsole}
tabIndex={0}
>
More details More details
</span> </a>
)} )}
</span> </span>
</Stack> </Stack>

View File

@@ -3,6 +3,6 @@ import LoadingIndicator_3Squares from "../../../images/LoadingIndicator_3Squares
export const PanelLoadingScreen: React.FunctionComponent = () => ( export const PanelLoadingScreen: React.FunctionComponent = () => (
<div className="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer"> <div className="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer">
<img className="dataExplorerLoader" src={LoadingIndicator_3Squares} alt="loading indicator" /> <img className="dataExplorerLoader" src={LoadingIndicator_3Squares} />
</div> </div>
); );

View File

@@ -34,6 +34,6 @@ describe("Right Pane Form", () => {
it("should render error in header", () => { it("should render error in header", () => {
render(<RightPaneForm {...props} formError="file already Exist" />); render(<RightPaneForm {...props} formError="file already Exist" />);
expect(screen.getByLabelText("error")).toBeDefined(); expect(screen.getByLabelText("error")).toBeDefined();
expect(screen.getByRole("alert").innerHTML).toEqual("file already Exist"); expect(screen.getByLabelText("message").innerHTML).toEqual("file already Exist");
}); });
}); });

View File

@@ -146,6 +146,7 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({ explorer
<TextField <TextField
id="saveQueryInput" id="saveQueryInput"
label="Name" label="Name"
autoFocus
styles={{ fieldGroup: { width: 300 } }} styles={{ fieldGroup: { width: 300 } }}
onChange={(event, newInput?: string) => { onChange={(event, newInput?: string) => {
setQueryName(newInput); setQueryName(newInput);

View File

@@ -195,6 +195,7 @@ export const SettingsPane: FunctionComponent = () => {
step={1} step={1}
className="textfontclr" className="textfontclr"
role="textbox" role="textbox"
tabIndex={0}
id="max-degree" id="max-degree"
value={"" + maxDegreeOfParallelism} value={"" + maxDegreeOfParallelism}
onIncrement={(newValue) => setMaxDegreeOfParallelism(parseInt(newValue) + 1 || maxDegreeOfParallelism)} onIncrement={(newValue) => setMaxDegreeOfParallelism(parseInt(newValue) + 1 || maxDegreeOfParallelism)}

View File

@@ -123,6 +123,7 @@ exports[`Settings Pane should render Default properly 1`] = `
onValidate={[Function]} onValidate={[Function]}
role="textbox" role="textbox"
step={1} step={1}
tabIndex={0}
value="6" value="6"
/> />
</div> </div>

View File

@@ -97,6 +97,7 @@ export const StringInputPane: FunctionComponent<StringInputPanelProps> = ({
label={inputLabel} label={inputLabel}
name="collectionIdConfirmation" name="collectionIdConfirmation"
value={stringInput} value={stringInput}
autoFocus
required required
onChange={(event: FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => onChange={(event: FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) =>
setStringInput(newValue) setStringInput(newValue)

View File

@@ -56,6 +56,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
> >
<StyledTextFieldBase <StyledTextFieldBase
aria-label="Enter new directory name" aria-label="Enter new directory name"
autoFocus={true}
label="Enter new directory name" label="Enter new directory name"
name="collectionIdConfirmation" name="collectionIdConfirmation"
onChange={[Function]} onChange={[Function]}
@@ -64,6 +65,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
> >
<TextFieldBase <TextFieldBase
aria-label="Enter new directory name" aria-label="Enter new directory name"
autoFocus={true}
deferredValidationTime={200} deferredValidationTime={200}
label="Enter new directory name" label="Enter new directory name"
name="collectionIdConfirmation" name="collectionIdConfirmation"
@@ -653,6 +655,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
<input <input
aria-invalid={false} aria-invalid={false}
aria-labelledby="TextFieldLabel2" aria-labelledby="TextFieldLabel2"
autoFocus={true}
className="ms-TextField-field field-56" className="ms-TextField-field field-56"
id="TextField0" id="TextField0"
name="collectionIdConfirmation" name="collectionIdConfirmation"

View File

@@ -29,7 +29,6 @@ describe("Table query select Panel", () => {
it("Should checked availableCheckbox by default", () => { it("Should checked availableCheckbox by default", () => {
const wrapper = mount(<TableQuerySelectPanel {...props} />); const wrapper = mount(<TableQuerySelectPanel {...props} />);
expect(wrapper.find("#availableCheckbox").first().props()).toEqual({ expect(wrapper.find("#availableCheckbox").first().props()).toEqual({
ariaPositionInSet: 0,
id: "availableCheckbox", id: "availableCheckbox",
label: "Available Columns", label: "Available Columns",
checked: true, checked: true,

View File

@@ -128,9 +128,8 @@ export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps
label="Available Columns" label="Available Columns"
checked={isAvailableColumnChecked} checked={isAvailableColumnChecked}
onChange={availableColumnsCheckboxClick} onChange={availableColumnsCheckboxClick}
ariaPositionInSet={0}
/> />
{columnOptions.map((column, index) => { {columnOptions.map((column) => {
return ( return (
<Checkbox <Checkbox
label={column.columnName} label={column.columnName}
@@ -138,7 +137,6 @@ export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps
key={column.columnName} key={column.columnName}
checked={column.selected} checked={column.selected}
disabled={!column.editable} disabled={!column.editable}
ariaPositionInSet={index + 1}
/> />
); );
})} })}

View File

@@ -37,14 +37,12 @@ exports[`Table query select Panel should render Default properly 1`] = `
className="column-select-view" className="column-select-view"
> >
<StyledCheckboxBase <StyledCheckboxBase
ariaPositionInSet={0}
checked={true} checked={true}
id="availableCheckbox" id="availableCheckbox"
label="Available Columns" label="Available Columns"
onChange={[Function]} onChange={[Function]}
> >
<CheckboxBase <CheckboxBase
ariaPositionInSet={0}
checked={true} checked={true}
id="availableCheckbox" id="availableCheckbox"
label="Available Columns" label="Available Columns"
@@ -330,7 +328,6 @@ exports[`Table query select Panel should render Default properly 1`] = `
<input <input
aria-checked="true" aria-checked="true"
aria-label="Available Columns" aria-label="Available Columns"
aria-posinset={0}
checked={true} checked={true}
className="input-55" className="input-55"
data-ktp-execute-target={true} data-ktp-execute-target={true}
@@ -649,7 +646,6 @@ exports[`Table query select Panel should render Default properly 1`] = `
</CheckboxBase> </CheckboxBase>
</StyledCheckboxBase> </StyledCheckboxBase>
<StyledCheckboxBase <StyledCheckboxBase
ariaPositionInSet={1}
checked={true} checked={true}
disabled={false} disabled={false}
key="" key=""
@@ -657,7 +653,6 @@ exports[`Table query select Panel should render Default properly 1`] = `
onChange={[Function]} onChange={[Function]}
> >
<CheckboxBase <CheckboxBase
ariaPositionInSet={1}
checked={true} checked={true}
disabled={false} disabled={false}
label="" label=""
@@ -944,7 +939,6 @@ exports[`Table query select Panel should render Default properly 1`] = `
aria-checked="true" aria-checked="true"
aria-disabled={false} aria-disabled={false}
aria-label="" aria-label=""
aria-posinset={1}
checked={true} checked={true}
className="input-55" className="input-55"
data-ktp-execute-target={true} data-ktp-execute-target={true}

View File

@@ -327,17 +327,13 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
key=".0:$.1" key=".0:$.1"
> >
<Text <Text
aria-label="Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources." aria-label="message"
aria-live="assertive"
className="panelWarningErrorMessage" className="panelWarningErrorMessage"
role="alert"
variant="small" variant="small"
> >
<span <span
aria-label="Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources." aria-label="message"
aria-live="assertive"
className="panelWarningErrorMessage css-56" className="panelWarningErrorMessage css-56"
role="alert"
> >
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>
@@ -367,7 +363,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
</span> </span>
</Text> </Text>
<StyledTextFieldBase <StyledTextFieldBase
ariaLabel="Confirm by typing the database id" autoFocus={true}
id="confirmDatabaseId" id="confirmDatabaseId"
onChange={[Function]} onChange={[Function]}
styles={ styles={
@@ -379,7 +375,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
} }
> >
<TextFieldBase <TextFieldBase
ariaLabel="Confirm by typing the database id" autoFocus={true}
deferredValidationTime={200} deferredValidationTime={200}
id="confirmDatabaseId" id="confirmDatabaseId"
onChange={[Function]} onChange={[Function]}
@@ -671,7 +667,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
> >
<input <input
aria-invalid={false} aria-invalid={false}
aria-label="Confirm by typing the database id" autoFocus={true}
className="ms-TextField-field field-60" className="ms-TextField-field field-60"
id="confirmDatabaseId" id="confirmDatabaseId"
onBlur={[Function]} onBlur={[Function]}
@@ -711,7 +707,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
</span> </span>
</Text> </Text>
<StyledTextFieldBase <StyledTextFieldBase
ariaLabel="Help us improve Azure Cosmos DB! What is the reason why you are deleting this database?"
id="deleteDatabaseFeedbackInput" id="deleteDatabaseFeedbackInput"
multiline={true} multiline={true}
onChange={[Function]} onChange={[Function]}
@@ -725,7 +720,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
} }
> >
<TextFieldBase <TextFieldBase
ariaLabel="Help us improve Azure Cosmos DB! What is the reason why you are deleting this database?"
deferredValidationTime={200} deferredValidationTime={200}
id="deleteDatabaseFeedbackInput" id="deleteDatabaseFeedbackInput"
multiline={true} multiline={true}
@@ -1019,7 +1013,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
> >
<textarea <textarea
aria-invalid={false} aria-invalid={false}
aria-label="Help us improve Azure Cosmos DB! What is the reason why you are deleting this database?"
className="ms-TextField-field field-71" className="ms-TextField-field field-71"
id="deleteDatabaseFeedbackInput" id="deleteDatabaseFeedbackInput"
onBlur={[Function]} onBlur={[Function]}

View File

@@ -108,8 +108,7 @@
} }
.oneLineContent { .oneLineContent {
margin-top: 6px; margin-top: 4px;
margin-left: 6px;
} }
.twoLineContent { .twoLineContent {
@@ -134,18 +133,18 @@
.flex-display(); .flex-display();
.flex-direction(); .flex-direction();
.title { >.title {
color: @BaseDark; color: @BaseDark;
padding: 0px; padding: 0px;
font-size: 12px; font-size: 12px;
} }
.description { >.description {
color: @BaseDark; color: @BaseDark;
} }
&:not(:hover):not(:focus) { &:not(:hover):not(:focus) {
background-color: @BaseLow; background-color: @BaseLow;
} }
} }
&.commonTasks { &.commonTasks {

View File

@@ -128,18 +128,18 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
<div className="title">Common Tasks</div> <div className="title">Common Tasks</div>
<ul> <ul>
{commonTaskItems.map((item) => ( {commonTaskItems.map((item) => (
<li className="focusable" key={`${item.title}${item.description}`}> <li
<div className="focusable"
onClick={item.onClick} key={`${item.title}${item.description}`}
onKeyPress={(event: React.KeyboardEvent) => this.onSplashScreenItemKeyPress(event, item.onClick)} onClick={item.onClick}
tabIndex={0} onKeyPress={(event: React.KeyboardEvent) => this.onSplashScreenItemKeyPress(event, item.onClick)}
role="button" tabIndex={0}
> role="button"
<img src={item.iconSrc} alt="" /> >
<span className="oneLineContent" title={item.info}> <img src={item.iconSrc} alt="" />
{item.title} <span className="oneLineContent" title={item.info}>
</span> {item.title}
</div> </span>
</li> </li>
))} ))}
</ul> </ul>
@@ -165,23 +165,22 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
<div className="title">Tips</div> <div className="title">Tips</div>
<ul> <ul>
{tipsItems.map((item) => ( {tipsItems.map((item) => (
<li className="tipContainer focusable" key={`${item.title}${item.description}`}> <li
<div className="tipContainer focusable"
onClick={item.onClick} key={`${item.title}${item.description}`}
onKeyPress={(event: React.KeyboardEvent) => this.onSplashScreenItemKeyPress(event, item.onClick)} onClick={item.onClick}
tabIndex={0} onKeyPress={(event: React.KeyboardEvent) => this.onSplashScreenItemKeyPress(event, item.onClick)}
role="button" tabIndex={0}
className="tipsWrapper" role="link"
> >
<div className="title" title={item.info}> <div className="title" title={item.info}>
{item.title} {item.title}
</div>
<div className="description">{item.description}</div>
</div> </div>
<div className="description">{item.description}</div>
</li> </li>
))} ))}
<li> <li>
<a href={SplashScreen.seeMoreItemUrl} rel="noreferrer" target="_blank" tabIndex={0}> <a role="link" href={SplashScreen.seeMoreItemUrl} rel="noreferrer" target="_blank" tabIndex={0}>
{SplashScreen.seeMoreItemTitle} {SplashScreen.seeMoreItemTitle}
</a> </a>
</li> </li>
@@ -308,23 +307,16 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
iconSrc: AddDatabaseIcon, iconSrc: AddDatabaseIcon,
title: "New " + getDatabaseName(), title: "New " + getDatabaseName(),
description: undefined, description: undefined,
onClick: () => this.openAddDatabasePanel(), onClick: () =>
useSidePanel
.getState()
.openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={this.container} />),
}); });
} }
return items; return items;
} }
private openAddDatabasePanel() {
const newDatabaseButton = document.activeElement as HTMLElement;
useSidePanel
.getState()
.openSidePanel(
"New " + getDatabaseName(),
<AddDatabasePanel explorer={this.container} buttonElement={newDatabaseButton} />
);
}
private decorateOpenCollectionActivity({ databaseId, collectionId }: MostRecentActivity.OpenCollectionItem) { private decorateOpenCollectionActivity({ databaseId, collectionId }: MostRecentActivity.OpenCollectionItem) {
return { return {
iconSrc: NotebookIcon, iconSrc: NotebookIcon,

View File

@@ -19,7 +19,7 @@ export function createDataTable($dataTableElem: JQuery, settings: any): DataTabl
* @return The given settings with all columns having a rendering function * @return The given settings with all columns having a rendering function
*/ */
function applyDefaultRendering(settings: any): DataTables.SettingsLegacy { function applyDefaultRendering(settings: any): DataTables.SettingsLegacy {
var tableColumns: DataTables.ColumnLegacy[] = null; let tableColumns: DataTables.ColumnLegacy[] = null;
if (settings.aoColumns) { if (settings.aoColumns) {
tableColumns = settings.aoColumns; tableColumns = settings.aoColumns;
@@ -34,7 +34,7 @@ function applyDefaultRendering(settings: any): DataTables.SettingsLegacy {
return settings; return settings;
} }
for (var i = 0; i < tableColumns.length; i++) { for (let i = 0; i < tableColumns.length; i++) {
// the column does not have a render function // the column does not have a render function
if (!tableColumns[i].mRender) { if (!tableColumns[i].mRender) {
tableColumns[i].mRender = defaultDataRender; tableColumns[i].mRender = defaultDataRender;

View File

@@ -1,4 +1,4 @@
import { DetailsList, DetailsListLayoutMode, IColumn, Pivot, PivotItem, SelectionMode, Text } from "@fluentui/react"; import { DetailsList, DetailsListLayoutMode, IColumn, Pivot, PivotItem, SelectionMode } from "@fluentui/react";
import React, { Fragment } from "react"; import React, { Fragment } from "react";
import SplitterLayout from "react-splitter-layout"; import SplitterLayout from "react-splitter-layout";
import "react-splitter-layout/lib/index.css"; import "react-splitter-layout/lib/index.css";
@@ -120,13 +120,21 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
constructor(props: IQueryTabComponentProps) { constructor(props: IQueryTabComponentProps) {
super(props); super(props);
const columns: IColumn[] = [ const columns: IColumn[] = [
{
key: "column1",
name: "",
minWidth: 16,
maxWidth: 16,
data: String,
fieldName: "toolTip",
onRender: this.onRenderColumnItem,
},
{ {
key: "column2", key: "column2",
name: "METRIC", name: "METRIC",
minWidth: 200, minWidth: 200,
data: String, data: String,
fieldName: "metric", fieldName: "metric",
onRender: this.onRenderColumnItem,
}, },
{ {
key: "column3", key: "column3",
@@ -198,12 +206,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
public onRenderColumnItem(item: IDocument): JSX.Element { public onRenderColumnItem(item: IDocument): JSX.Element {
if (item.toolTip !== "") { if (item.toolTip !== "") {
return ( return <InfoTooltip>{`${item.toolTip}`}</InfoTooltip>;
<>
<InfoTooltip>{`${item.toolTip}`}</InfoTooltip>
<Text style={{ paddingLeft: 10, margin: 0 }}>{`${item.metric}`}</Text>
</>
);
} else { } else {
return undefined; return undefined;
} }
@@ -966,15 +969,10 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
<> <>
<span className="queryResultDivider">|</span> <span className="queryResultDivider">|</span>
<span className="queryResultNextEnable"> <span className="queryResultNextEnable">
<div <a onClick={this.onFetchNextPageClick.bind(this)}>
onClick={this.onFetchNextPageClick.bind(this)} <span>Load more</span>
role="button"
tabIndex={0}
onKeyPress={this.onFetchNextPageClick.bind(this)}
>
<span className="moreOption">Load more</span>
<img className="queryResultnextImg" src={QueryEditorNext} alt="Fetch next page" /> <img className="queryResultnextImg" src={QueryEditorNext} alt="Fetch next page" />
</div> </a>
</span> </span>
</> </>
)} )}
@@ -1004,7 +1002,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
"data-order": 2, "data-order": 2,
"data-title": "Query Stats", "data-title": "Query Stats",
}} }}
style={{ height: "100%", overflowY: "scroll" }} style={{ height: "100%" }}
> >
{this.state.allResultsMetadata.length > 0 && !this.state.error && ( {this.state.allResultsMetadata.length > 0 && !this.state.error && (
<div className="queryMetricsSummaryContainer"> <div className="queryMetricsSummaryContainer">
@@ -1020,7 +1018,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
</div> </div>
{this.state.isQueryMetricsEnabled && ( {this.state.isQueryMetricsEnabled && (
<div className="downloadMetricsLinkContainer"> <div className="downloadMetricsLinkContainer">
<span <a
id="downloadMetricsLink" id="downloadMetricsLink"
role="button" role="button"
tabIndex={0} tabIndex={0}
@@ -1035,7 +1033,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
alt="download query metrics csv" alt="download query metrics csv"
/> />
<span>Per-partition query metrics (CSV)</span> <span>Per-partition query metrics (CSV)</span>
</span> </a>
</div> </div>
)} )}
</div> </div>
@@ -1049,7 +1047,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
<div className="errorContent"> <div className="errorContent">
<span className="errorMessage">{this.state.error}</span> <span className="errorMessage">{this.state.error}</span>
<span className="errorDetailsLink"> <span className="errorDetailsLink">
<span <a
onClick={() => this.onErrorDetailsClick()} onClick={() => this.onErrorDetailsClick()}
onKeyPress={(event: React.KeyboardEvent<HTMLAnchorElement>) => onKeyPress={(event: React.KeyboardEvent<HTMLAnchorElement>) =>
this.onErrorDetailsKeyPress(event) this.onErrorDetailsKeyPress(event)
@@ -1057,11 +1055,9 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
id="error-display" id="error-display"
tabIndex={0} tabIndex={0}
aria-label="Error details link" aria-label="Error details link"
role="button"
className="moreOption"
> >
More details More details
</span> </a>
</span> </span>
</div> </div>
</div> </div>

View File

@@ -47,9 +47,7 @@
<img class="and-or-svg" src="/And-Or.svg" alt="Group selected clauses" /> <img class="and-or-svg" src="/And-Or.svg" alt="Group selected clauses" />
</button> </button>
</th> </th>
<th class="clause-table-cell header-background"> <th class="clause-table-cell header-background"><!-- Grouping indicator --></th>
<!-- Grouping indicator -->
</th>
<th class="clause-table-cell header-background and-or-header"> <th class="clause-table-cell header-background and-or-header">
<span data-bind="text: andLabel"></span> <span data-bind="text: andLabel"></span>
</th> </th>
@@ -138,7 +136,7 @@
class="select-options-link" class="select-options-link"
data-bind="click: selectQueryOptions, event: { keydown: onselectQueryOptionsKeyDown }" data-bind="click: selectQueryOptions, event: { keydown: onselectQueryOptionsKeyDown }"
tabindex="0" tabindex="0"
role="button" role="link"
> >
<span>Choose Columns... </span> <span>Choose Columns... </span>
</a> </a>

View File

@@ -579,16 +579,13 @@ export default class StoredProcedureTabComponent extends React.Component<
<div className="errorContent"> <div className="errorContent">
<span className="errorMessage">{this.state.error}</span> <span className="errorMessage">{this.state.error}</span>
<span className="errorDetailsLink"> <span className="errorDetailsLink">
<span <a
role="button"
tabIndex={0}
className="moreOption"
aria-label="Error details link" aria-label="Error details link"
onClick={() => this.onErrorDetailsClick()} onClick={() => this.onErrorDetailsClick()}
onKeyPress={(event: React.KeyboardEvent<HTMLAnchorElement>) => this.onErrorDetailsKeyPress(event)} onKeyPress={(event: React.KeyboardEvent<HTMLAnchorElement>) => this.onErrorDetailsKeyPress(event)}
> >
More details More details
</span> </a>
</span> </span>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,5 @@
import ko from "knockout"; import ko from "knockout";
import React, { MutableRefObject, useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import loadingIcon from "../../../images/circular_loader_black_16x16.gif"; import loadingIcon from "../../../images/circular_loader_black_16x16.gif";
import errorIcon from "../../../images/close-black.svg"; import errorIcon from "../../../images/close-black.svg";
import { useObservable } from "../../hooks/useObservable"; import { useObservable } from "../../hooks/useObservable";
@@ -32,13 +32,7 @@ export const Tabs = (): JSX.Element => {
function TabNav({ tab, active }: { tab: Tab; active: boolean }) { function TabNav({ tab, active }: { tab: Tab; active: boolean }) {
const [hovering, setHovering] = useState(false); const [hovering, setHovering] = useState(false);
const focusTab = useRef<HTMLLIElement>() as MutableRefObject<HTMLLIElement>;
useEffect(() => {
if (active && focusTab.current) {
focusTab.current.focus();
}
});
return ( return (
<li <li
onMouseOver={() => setHovering(true)} onMouseOver={() => setHovering(true)}
@@ -52,8 +46,6 @@ function TabNav({ tab, active }: { tab: Tab; active: boolean }) {
aria-controls={tab.tabId} aria-controls={tab.tabId}
tabIndex={0} tabIndex={0}
role="tab" role="tab"
ref={focusTab}
onFocus={() => setHovering(true)}
> >
<span className="tabNavContentContainer"> <span className="tabNavContentContainer">
<a data-toggle="tab" href={"#" + tab.tabId} tabIndex={-1}> <a data-toggle="tab" href={"#" + tab.tabId} tabIndex={-1}>

View File

@@ -295,6 +295,8 @@ export class TriggerTabContent extends Component<TriggerTab, ITriggerTabContentS
className="trigger-field" className="trigger-field"
label="Trigger Id" label="Trigger Id"
id="entityTimeId" id="entityTimeId"
autoFocus
required
type="text" type="text"
pattern="[^/?#\\]*[^/?# \\]" pattern="[^/?#\\]*[^/?# \\]"
placeholder="Enter the new trigger id" placeholder="Enter the new trigger id"

View File

@@ -276,6 +276,7 @@ export default class UserDefinedFunctionTabContent extends Component<
className="trigger-field" className="trigger-field"
label="User Defined Function Id" label="User Defined Function Id"
id="entityTimeId" id="entityTimeId"
autoFocus
required required
readOnly={!isUdfIdEditable} readOnly={!isUdfIdEditable}
type="text" type="text"
@@ -290,7 +291,7 @@ export default class UserDefinedFunctionTabContent extends Component<
language={"javascript"} language={"javascript"}
content={udfBody} content={udfBody}
isReadOnly={false} isReadOnly={false}
ariaLabel={"User defined function body"} ariaLabel={"Graph JSON"}
onContentChanged={this.handleUdfBodyChange} onContentChanged={this.handleUdfBodyChange}
/> />
</div> </div>

View File

@@ -11,7 +11,7 @@ import NotebookIcon from "../../../images/notebook/Notebook-resource.svg";
import PublishIcon from "../../../images/notebook/publish_content.svg"; import PublishIcon from "../../../images/notebook/publish_content.svg";
import RefreshIcon from "../../../images/refresh-cosmos.svg"; import RefreshIcon from "../../../images/refresh-cosmos.svg";
import CollectionIcon from "../../../images/tree-collection.svg"; import CollectionIcon from "../../../images/tree-collection.svg";
import { Areas, ConnectionStatusType, Notebook } from "../../Common/Constants"; import { Areas, Notebook } from "../../Common/Constants";
import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility"; import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
@@ -128,13 +128,15 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
notebooksTree.children.push(buildGalleryNotebooksTree()); notebooksTree.children.push(buildGalleryNotebooksTree());
} }
if (myNotebooksContentRoot && useNotebook.getState().connectionInfo.status == ConnectionStatusType.Connected) { if (myNotebooksContentRoot) {
notebooksTree.children.push(buildMyNotebooksTree()); notebooksTree.children.push(buildMyNotebooksTree());
} }
if (container.notebookManager?.gitHubOAuthService.isLoggedIn()) { if (container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
// collapse all other notebook nodes // collapse all other notebook nodes
notebooksTree.children.forEach((node) => (node.isExpanded = false)); notebooksTree.children.forEach((node) => (node.isExpanded = false));
notebooksTree.children.push(buildGitHubNotebooksTree(true)); notebooksTree.children.push(buildGitHubNotebooksTree(true));
} else if (container.notebookManager && !container.notebookManager.gitHubOAuthService.isLoggedIn()) {
notebooksTree.children.push(buildGitHubNotebooksTree(false));
} }
} }
return notebooksTree; return notebooksTree;

View File

@@ -132,7 +132,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
type: NotebookContentItemType.File, type: NotebookContentItemType.File,
}; };
this.myNotebooksContentRoot = { this.myNotebooksContentRoot = {
name: useNotebook.getState().notebookFolderName, name: useNotebook.getState().NotebookFolderName,
path: useNotebook.getState().notebookBasePath, path: useNotebook.getState().notebookBasePath,
type: NotebookContentItemType.Directory, type: NotebookContentItemType.Directory,
}; };

View File

@@ -89,8 +89,6 @@ const App: React.FunctionComponent = () => {
onClick={() => window.open("https://portal.azure.com", "_blank")} onClick={() => window.open("https://portal.azure.com", "_blank")}
tabIndex={0} tabIndex={0}
title="Go to Azure Portal" title="Go to Azure Portal"
role="button"
onKeyDown={() => window.open("https://portal.azure.com", "_blank")}
> >
Microsoft Azure Microsoft Azure
</span> </span>

View File

@@ -33,23 +33,13 @@ const Index = (): JSX.Element => {
<div <div
id="Quickstart" id="Quickstart"
onClick={quickstart_click} onClick={quickstart_click}
onKeyPress={quickstart_click}
role="button"
tabIndex={0}
className={navigationSelection === "quickstart" ? "topSelected" : ""} className={navigationSelection === "quickstart" ? "topSelected" : ""}
> >
<img id="imgiconwidth1" src={Quickstart} alt="Open Quick Start" /> <img id="imgiconwidth1" src={Quickstart} alt="Open Quick Start" />
<span className="menuQuickStart">Quickstart</span> <span className="menuQuickStart">Quickstart</span>
</div> </div>
<div <div id="Explorer" onClick={explorer_click} className={navigationSelection === "explorer" ? "topSelected" : ""}>
id="Explorer"
onClick={explorer_click}
className={navigationSelection === "explorer" ? "topSelected" : ""}
role="button"
tabIndex={0}
onKeyPress={explorer_click}
>
<img id="imgiconwidth1" src={Explorer} alt="Open Data Explorer" /> <img id="imgiconwidth1" src={Explorer} alt="Open Data Explorer" />
<span className="menuExplorer">Explorer</span> <span className="menuExplorer">Explorer</span>
</div> </div>
@@ -63,11 +53,11 @@ const Index = (): JSX.Element => {
</nav> </nav>
{navigationSelection === "quickstart" && ( {navigationSelection === "quickstart" && (
<iframe name="quickstart" className="iframe" src="quickstart.html" title="Quick Start"></iframe> <iframe name="quickstart" className="iframe" src="quickstart.html"></iframe>
)} )}
{navigationSelection === "explorer" && ( {navigationSelection === "explorer" && (
<iframe name="explorer" className="iframe" src="explorer.html?platform=Emulator" title="Explorer"></iframe> <iframe name="explorer" className="iframe" src="explorer.html?platform=Emulator"></iframe>
)} )}
</React.Fragment> </React.Fragment>
); );

View File

@@ -1,57 +0,0 @@
{
"GraphAPIDescription": "Provision a Graph API Compute for your Azure Cosmos DB account.",
"GraphAPICompute": "Graph GraphAPI Compute",
"Provisioned": "Provisioned",
"Deprovisioned": "Deprovisioned",
"Compute": "Compute",
"GremlinV2": "GremlinV2",
"LearnAboutCompute": "Learn more about GraphAPI Compute.",
"DeprovisioningDetailsText": "Learn more about deprovisioning the GraphAPI Compute.",
"ComputePricing": "Learn more about GraphAPI Compute pricing.",
"SKUs": "SKUs",
"SKUsPlaceHolder": "Select SKUs",
"NumberOfInstances": "Number of instances",
"CosmosD4s": "Cosmos.D4s (General Purpose Cosmos Compute with 4 vCPUs, 16 GB Memory)",
"CosmosD8s": "Cosmos.D8s (General Purpose Cosmos Compute with 8 vCPUs, 32 GB Memory)",
"CosmosD16s": "Cosmos.D16s (General Purpose Cosmos Compute with 16 vCPUs, 64 GB Memory)",
"CosmosD32s": "Cosmos.D32s (General Purpose Cosmos Compute with 32 vCPUs, 128 GB Memory)",
"CreateMessage": "Graph GraphAPI Compute resource is being created.",
"CreateInitializeTitle": "Provisioning resource",
"CreateInitializeMessage": "GraphAPI Compute resource will be provisioned.",
"CreateSuccessTitle": "Resource provisioned",
"CreateSuccesseMessage": "GraphAPI Compute resource provisioned.",
"CreateFailureTitle": "Failed to provision resource",
"CreateFailureMessage": "GraphAPI Compute resource provisioning failed.",
"UpdateMessage": "GraphAPI Compute resource is being updated.",
"UpdateInitializeTitle": "Updating resource",
"UpdateInitializeMessage": "GraphAPI Compute resource will be updated.",
"UpdateSuccessTitle": "Resource updated",
"UpdateSuccesseMessage": "GraphAPI Compute resource updated.",
"UpdateFailureTitle": "Failed to update resource",
"UpdateFailureMessage": "GraphAPI Compute resource updation failed.",
"DeleteMessage": "GraphAPI Compute resource is being deleted.",
"DeleteInitializeTitle": "Deleting resource",
"DeleteInitializeMessage": "GraphAPI Compute resource will be deleted.",
"DeleteSuccessTitle": "Resource deleted",
"DeleteSuccesseMessage": "GraphAPI Compute resource deleted.",
"DeleteFailureTitle": "Failed to delete resource",
"DeleteFailureMessage": "GraphAPI Compute resource deletion failed.",
"CannotSave": "Cannot save the changes to the GraphAPI Compute resource at the moment.",
"GraphAccountEndpoint": "Graph Account Endpoint",
"CosmosD4Details": "General Purpose Cosmos Compute with 4 vCPUs, 16 GB Memory",
"CosmosD8Details": "General Purpose Cosmos Compute with 8 vCPUs, 32 GB Memory",
"CosmosD16Details": "General Purpose Cosmos Compute with 16 vCPUs, 64 GB Memory",
"ApproximateCost": "Approximate Cost Per Hour",
"CostText": "Hourly cost of the GraphAPI Compute resource depends on the SKU selection, number of instances per region, and number of regions.",
"ConnectionString": "Connection String",
"ConnectionStringText": "To use the GraphAPI Compute, use the connection string shown in ",
"KeysBlade": "the keys blade.",
"MetricsString": "Metrics",
"MetricsText": "Monitor the CPU and memory usage for the GraphAPI Compute instances in ",
"MetricsBlade": "the metrics blade.",
"MonitorUsage": "Monitor Usage",
"ResizingDecisionText": "Number of instances has to be 1 during provisioning. Instances can only be incremented by 1 at once. ",
"ResizingDecisionLink": "Learn more about GraphAPI Compute sizing.",
"WarningBannerOnUpdate": "Adding or modifying GraphAPI Compute instances may affect your bill.",
"WarningBannerOnDelete": "After deprovisioning the GraphAPI Compute, you will not be able to connect to the Graph API account."
}

View File

@@ -1,6 +1,6 @@
import { ConnectionStatusType, HttpHeaders, HttpStatusCodes } from "../Common/Constants"; import { ConnectionStatusType, HttpHeaders, HttpStatusCodes } from "../Common/Constants";
import { configContext } from "../ConfigContext"; import { configContext } from "../ConfigContext";
import { ContainerConnectionInfo } from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import { useNotebook } from "../Explorer/Notebook/useNotebook"; import { useNotebook } from "../Explorer/Notebook/useNotebook";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils"; import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
@@ -25,40 +25,25 @@ export class PhoenixClient {
public async containerConnectionInfo( public async containerConnectionInfo(
provisionData: IProvosionData provisionData: IProvosionData
): Promise<IPhoenixResponse<IPhoenixConnectionInfoResult>> { ): Promise<IPhoenixResponse<IPhoenixConnectionInfoResult>> {
try { const response = await window.fetch(`${this.getPhoenixContainerPoolingEndPoint()}/provision`, {
const connectionStatus: ContainerConnectionInfo = { method: "POST",
status: ConnectionStatusType.Connecting, headers: PhoenixClient.getHeaders(),
}; body: JSON.stringify(provisionData),
useNotebook.getState().setConnectionInfo(connectionStatus); });
const response = await window.fetch(`${this.getPhoenixContainerPoolingEndPoint()}/provision`, { let data: IPhoenixConnectionInfoResult;
method: "POST", if (response.status === HttpStatusCodes.OK) {
headers: PhoenixClient.getHeaders(), data = await response.json();
body: JSON.stringify(provisionData), } else {
}); const connectionStatus: DataModels.ContainerConnectionInfo = {
let data: IPhoenixConnectionInfoResult;
if (response.status === HttpStatusCodes.OK) {
data = await response.json();
if (data && data.notebookServerUrl) {
connectionStatus.status = ConnectionStatusType.Connected;
useNotebook.getState().setConnectionInfo(connectionStatus);
}
} else {
connectionStatus.status = ConnectionStatusType.Failed;
useNotebook.getState().setConnectionInfo(connectionStatus);
}
return {
status: response.status,
data,
};
} catch (error) {
const connectionStatus: ContainerConnectionInfo = {
status: ConnectionStatusType.Failed, status: ConnectionStatusType.Failed,
}; };
useNotebook.getState().setConnectionInfo(connectionStatus); useNotebook.getState().setConnectionInfo(connectionStatus);
console.error(error);
throw error;
} }
return {
status: response.status,
data,
};
} }
public static getPhoenixEndpoint(): string { public static getPhoenixEndpoint(): string {

View File

@@ -77,16 +77,16 @@ export const ConnectExplorer: React.FunctionComponent<Props> = ({
<p className="connectExplorerContent"> <p className="connectExplorerContent">
<input className="filterbtnstyle" type="submit" value="Connect" /> <input className="filterbtnstyle" type="submit" value="Connect" />
</p> </p>
<div className="switchConnectTypeText" onClick={login} onKeyDown={login} role="button" tabIndex={0}> <p className="switchConnectTypeText" onClick={login}>
Sign In with Azure Account Sign In with Azure Account
</div> </p>
</form> </form>
) : ( ) : (
<div id="connectWithAad"> <div id="connectWithAad">
<input className="filterbtnstyle" type="button" value="Sign In" onClick={login} /> <input className="filterbtnstyle" type="button" value="Sign In" onClick={login} />
<div className="switchConnectTypeText" onClick={showForm} onKeyDown={showForm} role="button" tabIndex={0}> <p className="switchConnectTypeText" onClick={showForm}>
Connect to your account with connection string Connect to your account with connection string
</div> </p>
</div> </div>
)} )}
</div> </div>

View File

@@ -1,4 +1,4 @@
import { extractFeatures, hasFlag } from "./extractFeatures"; import { extractFeatures } from "./extractFeatures";
describe("extractFeatures", () => { describe("extractFeatures", () => {
it("correctly detects feature flags in a case insensitive manner", () => { it("correctly detects feature flags in a case insensitive manner", () => {
@@ -14,25 +14,9 @@ describe("extractFeatures", () => {
}); });
const features = extractFeatures(params); const features = extractFeatures(params);
expect(features.notebookServerUrl).toBe(url); expect(features.notebookServerUrl).toBe(url);
expect(features.notebookServerToken).toBe(token); expect(features.notebookServerToken).toBe(token);
expect(features.enableNotebooks).toBe(notebooksEnabled); expect(features.enableNotebooks).toBe(notebooksEnabled);
}); });
}); });
describe("hasFlag", () => {
it("correctly determines if value has flag", () => {
const desiredFlag = "readDocument";
const singleFlagValue = "readDocument";
const multipleFlagValues = "readDocument|createDocument";
const differentFlagValue = "createDocument";
expect(hasFlag(singleFlagValue, desiredFlag)).toBe(true);
expect(hasFlag(multipleFlagValues, desiredFlag)).toBe(true);
expect(hasFlag(differentFlagValue, desiredFlag)).toBe(false);
expect(hasFlag(multipleFlagValues, (undefined as unknown) as string)).toBe(false);
expect(hasFlag((undefined as unknown) as string, desiredFlag)).toBe(false);
expect(hasFlag((undefined as unknown) as string, (undefined as unknown) as string)).toBe(false);
});
});

View File

@@ -29,8 +29,6 @@ export type Features = {
readonly pr?: string; readonly pr?: string;
readonly showMinRUSurvey: boolean; readonly showMinRUSurvey: boolean;
readonly ttl90Days: boolean; readonly ttl90Days: boolean;
readonly mongoProxyEndpoint?: string;
readonly mongoProxyAPIs?: string;
readonly notebooksTemporarilyDown: boolean; readonly notebooksTemporarilyDown: boolean;
}; };
@@ -65,8 +63,6 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
enableKoResourceTree: "true" === get("enablekoresourcetree"), enableKoResourceTree: "true" === get("enablekoresourcetree"),
executeSproc: "true" === get("dataexplorerexecutesproc"), executeSproc: "true" === get("dataexplorerexecutesproc"),
hostedDataExplorer: "true" === get("hosteddataexplorerenabled"), hostedDataExplorer: "true" === get("hosteddataexplorerenabled"),
mongoProxyEndpoint: get("mongoproxyendpoint"),
mongoProxyAPIs: get("mongoproxyapis"),
junoEndpoint: get("junoendpoint"), junoEndpoint: get("junoendpoint"),
livyEndpoint: get("livyendpoint"), livyEndpoint: get("livyendpoint"),
notebookBasePath: get("notebookbasepath"), notebookBasePath: get("notebookbasepath"),
@@ -84,12 +80,3 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
phoenix: "true" === get("phoenix"), phoenix: "true" === get("phoenix"),
}; };
} }
export function hasFlag(flags: string | undefined, desiredFlag: string | undefined): boolean {
if (!flags || !desiredFlag) {
return false;
}
const features = flags.split("|");
return features.find((feature) => feature === desiredFlag) ? true : false;
}

View File

@@ -1,200 +0,0 @@
import { configContext } from "../../ConfigContext";
import { userContext } from "../../UserContext";
import { armRequestWithoutPolling } from "../../Utils/arm/request";
import { selfServeTraceFailure, selfServeTraceStart, selfServeTraceSuccess } from "../SelfServeTelemetryProcessor";
import { RefreshResult } from "../SelfServeTypes";
import GraphAPICompute from "./GraphAPICompute";
import {
FetchPricesResponse,
RegionsResponse,
GraphAPIComputeServiceResource,
UpdateComputeRequestParameters,
} from "./GraphAPICompute.types";
const apiVersion = "2021-04-01-preview";
const gremlinV2 = "GremlinV2";
export enum ResourceStatus {
Running = "Running",
Creating = "Creating",
Updating = "Updating",
Deleting = "Deleting",
}
export interface ComputeResponse {
sku: string;
instances: number;
status: string;
endpoint: string;
}
export const getPath = (subscriptionId: string, resourceGroup: string, name: string): string => {
return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${name}/services/${gremlinV2}`;
};
export const updateComputeResource = async (sku: string, instances: number): Promise<string> => {
const path = getPath(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name);
const body: UpdateComputeRequestParameters = {
properties: {
instanceSize: sku,
instanceCount: instances,
serviceType: gremlinV2,
},
};
const telemetryData = { ...body, httpMethod: "PUT", selfServeClassName: GraphAPICompute.name };
const updateTimeStamp = selfServeTraceStart(telemetryData);
let armRequestResult;
try {
armRequestResult = await armRequestWithoutPolling({
host: configContext.ARM_ENDPOINT,
path,
method: "PUT",
apiVersion,
body,
});
selfServeTraceSuccess(telemetryData, updateTimeStamp);
} catch (e) {
const failureTelemetry = { ...body, e, selfServeClassName: GraphAPICompute.name };
selfServeTraceFailure(failureTelemetry, updateTimeStamp);
throw e;
}
return armRequestResult?.operationStatusUrl;
};
export const deleteComputeResource = async (): Promise<string> => {
const path = getPath(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name);
const telemetryData = { httpMethod: "DELETE", selfServeClassName: GraphAPICompute.name };
const deleteTimeStamp = selfServeTraceStart(telemetryData);
let armRequestResult;
try {
armRequestResult = await armRequestWithoutPolling({
host: configContext.ARM_ENDPOINT,
path,
method: "DELETE",
apiVersion,
});
selfServeTraceSuccess(telemetryData, deleteTimeStamp);
} catch (e) {
const failureTelemetry = { e, selfServeClassName: GraphAPICompute.name };
selfServeTraceFailure(failureTelemetry, deleteTimeStamp);
throw e;
}
return armRequestResult?.operationStatusUrl;
};
export const getComputeResource = async (): Promise<GraphAPIComputeServiceResource> => {
const path = getPath(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name);
const telemetryData = { httpMethod: "GET", selfServeClassName: GraphAPICompute.name };
const getResourceTimeStamp = selfServeTraceStart(telemetryData);
let armRequestResult;
try {
armRequestResult = await armRequestWithoutPolling<GraphAPIComputeServiceResource>({
host: configContext.ARM_ENDPOINT,
path,
method: "GET",
apiVersion,
});
selfServeTraceSuccess(telemetryData, getResourceTimeStamp);
} catch (e) {
const failureTelemetry = { e, selfServeClassName: GraphAPICompute.name };
selfServeTraceFailure(failureTelemetry, getResourceTimeStamp);
throw e;
}
return armRequestResult?.result;
};
export const getCurrentProvisioningState = async (): Promise<ComputeResponse> => {
try {
const response = await getComputeResource();
return {
sku: response.properties.instanceSize,
instances: response.properties.instanceCount,
status: response.properties.status,
endpoint: response.properties.GraphAPIComputeEndPoint,
};
} catch (e) {
return { sku: undefined, instances: undefined, status: undefined, endpoint: undefined };
}
};
export const refreshComputeProvisioning = async (): Promise<RefreshResult> => {
try {
const response = await getComputeResource();
if (response.properties.status === ResourceStatus.Running.toString()) {
return { isUpdateInProgress: false, updateInProgressMessageTKey: undefined };
} else if (response.properties.status === ResourceStatus.Creating.toString()) {
return { isUpdateInProgress: true, updateInProgressMessageTKey: "CreateMessage" };
} else if (response.properties.status === ResourceStatus.Deleting.toString()) {
return { isUpdateInProgress: true, updateInProgressMessageTKey: "DeleteMessage" };
} else {
return { isUpdateInProgress: true, updateInProgressMessageTKey: "UpdateMessage" };
}
} catch {
//TODO differentiate between different failures
return { isUpdateInProgress: false, updateInProgressMessageTKey: undefined };
}
};
const getGeneralPath = (subscriptionId: string, resourceGroup: string, name: string): string => {
return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${name}`;
};
export const getReadRegions = async (): Promise<Array<string>> => {
try {
const readRegions = new Array<string>();
const response = await armRequestWithoutPolling<RegionsResponse>({
host: configContext.ARM_ENDPOINT,
path: getGeneralPath(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name),
method: "GET",
apiVersion: "2021-04-01-preview",
});
if (response.result.location !== undefined) {
readRegions.push(response.result.location.split(" ").join("").toLowerCase());
} else {
for (const location of response.result.locations) {
readRegions.push(location.locationName.split(" ").join("").toLowerCase());
}
}
return readRegions;
} catch (err) {
return new Array<string>();
}
};
const getFetchPricesPathForRegion = (subscriptionId: string): string => {
return `/subscriptions/${subscriptionId}/providers/Microsoft.CostManagement/fetchPrices`;
};
export const getPriceMap = async (regions: Array<string>): Promise<Map<string, Map<string, number>>> => {
try {
const priceMap = new Map<string, Map<string, number>>();
for (const region of regions) {
const regionPriceMap = new Map<string, number>();
const response = await armRequestWithoutPolling<FetchPricesResponse>({
host: configContext.ARM_ENDPOINT,
path: getFetchPricesPathForRegion(userContext.subscriptionId),
method: "POST",
apiVersion: "2020-01-01-preview",
queryParams: {
filter:
"armRegionNameeq '" +
region +
"'andserviceFamilyeq 'Databases' and productName eq 'Azure Cosmos DB Dedicated Gateway - General Purpose'",
},
});
for (const item of response.result.Items) {
regionPriceMap.set(item.skuName, item.retailPrice);
}
priceMap.set(region, regionPriceMap);
}
return priceMap;
} catch (err) {
return undefined;
}
};

Some files were not shown because too many files have changed in this diff Show More