Compare commits

..

42 Commits

Author SHA1 Message Date
sunilyadav840
27d129e689 configure and fixed eslint jsx-a11y issues 2021-10-01 11:51:27 +05:30
sunilyadav840
fb9f97b43a configure eslint-plugin-jsx-a11y 2021-09-30 19:53:04 +05:30
vaidankarswapnil
6ca8e3c6f4 Fix execute Query 'Results' and 'Query status' section controls gets truncated at 200% resize mode of 'Query1' blade (#1105)
* Fix a11y querytab results section zoom section issue

* Update test smapshot

* Update test snapshot

* Resolved test case issue
2021-09-27 11:03:01 -07:00
vaidankarswapnil
f7e7240010 Fix keyboard focus does not retain on 'New Database' button after closing the 'New Database' blade via ESC key (#1109)
* Fix a11y new database button focus issue

* Update test snapshot and other issues
2021-09-23 08:13:18 -07:00
Sunil Kumar Yadav
acc095a482 fixed graph input query JAWS screen reader issue (#1108) 2021-09-23 08:11:46 -07:00
Hardikkumar Nai
288e6410f3 Choose Column: Aria Position set is not defined for check box present under choose column dialog box. (#1104) 2021-09-23 07:59:10 -07:00
Hardikkumar Nai
9ac3392271 Scale and Settings: 'Learn more' link is not adapting the high contrast mode after applying high contrast theme. (#1103) 2021-09-23 07:58:41 -07:00
Sunil Kumar Yadav
9a908dde9a fixed graph explorer visibility and graph expand keyboard accessibility issue (#1092)
* fixed graph explorer visibility issue

* fixed graph expand keyboard accessibility issue
2021-09-23 07:57:42 -07:00
siddjoshi-ms
ddd2e63fe7 Telemetry added for calculate cost function (#1018)
* Added telemetry for sql cost calculation
2021-09-22 09:49:45 -07:00
Hardikkumar Nai
34c59b4872 Scale and Settings: Text content of 'Info status message' and 'Warning' message is not visible properly at high contrast black mode. (#1090) 2021-09-22 07:40:18 -07:00
Jordi Bunster
7d28af4fc7 Make these required fields (#1101) 2021-09-21 15:50:44 -07:00
Sunil Kumar Yadav
50b99ceb42 fixed horizontal scrollbar issue on 400% resize mode (#1099) 2021-09-21 09:06:49 -07:00
Sunil Kumar Yadav
15a26d6500 fixed notification content visible issue on 400 resize mode (#1098) 2021-09-21 09:06:17 -07:00
Sunil Kumar Yadav
a8150af269 fixed incorrect notification console expand collaped screenreader issue (#1095) 2021-09-21 09:04:47 -07:00
Sunil Kumar Yadav
6a9a0156a3 fixed entity choose column role accessibility issue (#1088) 2021-09-21 09:02:04 -07:00
vaidankarswapnil
ead28f043f Fix after activating "Refresh tree" button, 'Querying database' message appears but screen reader does not provide any information about it (#1091)
* Fix a11y refresh tree querying database msg

* Update test snapshot issue
2021-09-21 09:00:28 -07:00
vaidankarswapnil
b05e5a2145 Fix delete database warning message is not announced by the screen reader after selecting 'Delete Database' menu item (#1074)
* Fix a11y delete database confirmation ississue

* Resolved lint issue - Removed Unnecessary semicolon

* Resolved compilation issue for extractFeature.ts and update test snapshot issue

Co-authored-by: Armando Trejo Oliver <ato9000@users.noreply.github.com>
2021-09-21 08:58:03 -07:00
Hardikkumar Nai
5e8aa491ba Load Graph: Name, role and state properties are not defined for 'full screen graph view' control present under 'Graph' tab section. (#1083) 2021-09-21 08:57:08 -07:00
Hardikkumar Nai
a730c08292 New SQL Query: Luminosity contrast ratio is 2.6:1 which is less than 3:1 for Close(X) icon button of Query 1 tab control (#1089) 2021-09-21 08:56:16 -07:00
Hardikkumar Nai
3dce5cd129 Tooltip is not provided for 'More' control present on the page. (#1093) 2021-09-21 08:54:44 -07:00
siddjoshi-ms
7c186c3ef2 Update GraphAPICompute.rp.ts (#1065) 2021-09-20 15:13:07 -07:00
Sunil Kumar Yadav
d8840a0dfd fixed aria-required property missing issue (#1020) 2021-09-16 14:25:28 -07:00
vaidankarswapnil
f853c4ec2f Fix 'Advanced' expand/collapse control and other issues under 'New Collection' blade (#1021)
* Fix ally issues for newCollection panel's advanced section

* Resolved test snapshot issue

* Removed unnecessary changes
2021-09-16 14:25:17 -07:00
Sunil Kumar Yadav
9bf5f48165 Fixed add collection tooltip accessibility issue (#1022)
* Fixed add collection tooltip accessibility issue

* Remove commented code

* cleanup commented code
2021-09-16 14:24:47 -07:00
Sunil Kumar Yadav
0b2a204b70 fixed delete database field missing issue at 200% resize mode (#1066) 2021-09-16 14:24:34 -07:00
vaidankarswapnil
c28593b752 Fix expand/collapse tree button of Data Explorer page is not accessible through keyboard (#1067)
* Fix ally issues for newCollection panel's advanced section

* Resolved test snapshot issue

* Fix a11y data explorer expand/collapse using keyboard issue
2021-09-16 14:24:24 -07:00
Sunil Kumar Yadav
4cbbef9574 change area label of UDF (#1068) 2021-09-16 14:24:11 -07:00
vaidankarswapnil
300c952a9b Fix a11y QueryTab focus and close button issue using keyboard (#1069) 2021-09-16 14:23:55 -07:00
Sunil Kumar Yadav
38c3761260 fixed input parameter keyboard accessibility issue (#1071)
* fixed input parameter keyboard accessibility issue

* Fixed autofocus and role issue

* make autofocus on close button
2021-09-16 14:23:43 -07:00
vaidankarswapnil
3032f689b6 Fix delete container and database labels appearing text are not associated with the edit fields (#1072)
* Fix a11y issues for delete container and database

* Update test snapshot issues
2021-09-16 14:23:29 -07:00
Hardikkumar Nai
8b30af3d9e Settings: At 200% resize mode controls present under 'Settings' blade are not visible while navigating over them. (#1075) 2021-09-16 14:23:03 -07:00
Sunil Kumar Yadav
e10240bd7a fixed setting keyboard accessibility issue (#1081) 2021-09-16 14:22:47 -07:00
vaidankarswapnil
ae9c27795e Fix execute query keyboard focus moves to hidden element under 'Results' section of executed Query 1 blade (#1082)
* fix a11y quertTab results section hidden element focus issue

* Removed commented code

* Resolved lint issues
2021-09-16 14:21:19 -07:00
Karthik chakravarthy
d7997d716e Data pane expand issue (#1085)
* Data pane expand issue

* Data pane expand issue-1

* Data pane expand issue format

* Data pane expand issue formating
2021-09-15 19:50:36 -04:00
victor-meng
af0dc3094b Temporarily lower test coverage threshold (#1084) 2021-09-15 16:38:51 -07:00
victor-meng
665270296f Fix throughput cost estimate in add collection panel (#1070) 2021-09-15 13:05:55 -07:00
Asier Isayas
2d945c8231 allowing azure client secret to be null in dev mode (#1079)
Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2021-09-14 12:33:09 -04:00
Asier Isayas
8866976bb4 fixed hasFlag test (#1076)
Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2021-09-14 12:28:33 -04:00
Asier Isayas
d10f3c69f1 MongoClient Feature Flag (#1073)
Adding a feature flag for Mongo Client that allows a user to specify a mongo endpoint and an API so that users can test specific APIs locally.

Example:

https://localhost:1234/hostedExplorer.html?feature.mongoproxyendpoint=https://localhost:12901&feature.mongoProxyAPIs=createDocument|readDocument

The above link says to test APIs createDocument and readDocument on https://localhost:12901

Co-authored-by: artrejo <ato9000@users.noreply.github.com>
Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2021-09-13 16:25:21 -04:00
kcheekuri
7e4f030547 Hidding container connection status behind the feature flag and initi… (#1019)
* Hidding container connection status behind the feature flag and initializing scratch issue

* maintaining connecting status UX part at notebooks context

* Changing scratch name to temporary and showing only after connected
2021-09-09 14:02:00 -04:00
siddjoshi-ms
05f46dd635 Sqlx approx cost bug fixes (#975)
* function naming changed

* bug fix: replacing multiple occurences of space correctly now
2021-09-08 14:04:31 -07:00
Meha Kaushik
65882ea831 Self-Server for GraphAPI Compute (#1017)
* Self-Server for GraphAPI Compute

* Update GraphAPICompute.json
2021-09-07 23:42:39 -07:00
115 changed files with 2803 additions and 1677 deletions

View File

@@ -137,10 +137,18 @@ src/Terminal/NotebookAppContracts.d.ts
src/applyExplorerBindings.ts
src/global.d.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/Graph/GraphExplorerComponent/GraphExplorer.test.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.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.tsx
src/Explorer/Menus/CommandBar/CommandBarUtil.tsx

View File

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

View File

@@ -1,16 +1,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">
<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"
transform="scale(0.5)"
fill="#000"
stroke="#CCC"
>
<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"
transform="scale(0.5)" fill="#000" stroke="#000">
</path>
</svg>

Before

Width:  |  Height:  |  Size: 503 B

After

Width:  |  Height:  |  Size: 449 B

View File

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

View File

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

91
package-lock.json generated
View File

@@ -6809,6 +6809,11 @@
"integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=",
"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": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
@@ -6876,6 +6881,11 @@
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
"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": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
@@ -6891,6 +6901,11 @@
}
}
},
"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": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
@@ -8878,6 +8893,11 @@
"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": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -10171,6 +10191,64 @@
"@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": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-no-null/-/eslint-plugin-no-null-1.0.2.tgz",
@@ -18065,6 +18143,19 @@
"resolved": "https://registry.npmjs.org/labella/-/labella-1.1.4.tgz",
"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": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
import { Icon, Label, Stack } from "@fluentui/react";
import * as React from "react";
import { NormalizedEventKey } from "../../../Common/Constants";
import { accordionStackTokens } from "../Settings/SettingsRenderUtils";
export interface CollapsibleSectionProps {
@@ -30,6 +31,13 @@ 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 {
return (
<>
@@ -39,6 +47,11 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
verticalAlign="center"
tokens={accordionStackTokens}
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"} />
<Label>{this.props.title}</Label>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -264,6 +264,6 @@ export class NotebookViewerComponent
};
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
Query and follow the prompt in order to save the query.
</div>
<img {...bannerProps} />
<img {...bannerProps} alt="Save query helper banner" />
</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 {
ITextFieldStyles,
DetailsList,
DetailsListLayoutMode,
DetailsRow,
ICheckboxStyles,
IStackProps,
IStackTokens,
IChoiceGroupStyles,
Link,
Text,
IMessageBarStyles,
ITextStyles,
IDetailsRowStyles,
IStackStyles,
IColumn,
IDetailsColumnStyles,
IDetailsListStyles,
IDetailsRowProps,
IDetailsRowStyles,
IDropdownStyles,
IMessageBarStyles,
ISeparatorStyles,
IStackProps,
IStackStyles,
IStackTokens,
ITextFieldStyles,
ITextStyles,
Link,
MessageBar,
MessageBarType,
Stack,
SelectionMode,
Spinner,
SpinnerSize,
DetailsList,
IColumn,
SelectionMode,
DetailsListLayoutMode,
IDetailsRowProps,
DetailsRow,
IDetailsColumnStyles,
Stack,
Text,
} from "@fluentui/react";
import { isDirtyTypes, isDirty } from "./SettingsUtils";
import * as React from "react";
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 {
costType: JSX.Element;
@@ -65,7 +65,7 @@ export interface PriceBreakdown {
currencySign: string;
}
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14 } };
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14, color: "windowtext" } };
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
label: {
@@ -223,14 +223,15 @@ export const getRuPriceBreakdown = (
multimasterEnabled: isMultimaster,
isAutoscale: isAutoscale,
});
const basePricePerRu: number = isAutoscale
? getAutoscalePricePerRu(serverId, getMultimasterMultiplier(numberOfRegions, isMultimaster))
: getPricePerRu(serverId);
const multimasterMultiplier = getMultimasterMultiplier(numberOfRegions, isMultimaster);
const pricePerRu: number = isAutoscale
? getAutoscalePricePerRu(serverId, multimasterMultiplier)
: getPricePerRu(serverId, multimasterMultiplier);
return {
hourlyPrice: hourlyPrice,
hourlyPrice,
dailyPrice: hourlyPrice * 24,
monthlyPrice: hourlyPrice * hoursInAMonth,
pricePerRu: basePricePerRu * getMultimasterMultiplier(numberOfRegions, isMultimaster),
pricePerRu,
currency: getPriceCurrency(serverId),
currencySign: getCurrencySign(serverId),
};
@@ -271,7 +272,7 @@ export const manualToAutoscaleDisclaimerElement: JSX.Element = (
<Text styles={infoAndToolTipTextStyle} id="manualToAutoscaleDisclaimerElement">
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.{" "}
<a href={Urls.autoscaleMigration}>Learn more</a>
<Link href={Urls.autoscaleMigration}>Learn more</Link>
</Text>
);

View File

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

View File

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

View File

@@ -20,6 +20,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}
@@ -39,6 +40,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}
@@ -73,6 +75,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}
@@ -80,11 +83,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.
<a
<StyledLinkBase
href="https://aka.ms/cosmos-autoscale-migration"
>
Learn more
</a>
</StyledLinkBase>
</Text>
</StyledMessageBar>
<StyledChoiceGroup
@@ -186,6 +189,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}
@@ -460,6 +464,7 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}

View File

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

View File

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

View File

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

View File

@@ -159,6 +159,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}
@@ -166,16 +167,17 @@ 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.
<a
<StyledLinkBase
href="https://aka.ms/cosmos-autoscale-migration"
>
Learn more
</a>
</StyledLinkBase>
</Text>
<Text
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}
@@ -195,6 +197,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}
@@ -207,6 +210,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}
@@ -219,6 +223,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}
@@ -230,6 +235,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}
@@ -249,6 +255,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}
@@ -264,6 +271,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}
@@ -278,6 +286,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}
@@ -291,6 +300,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}
@@ -302,6 +312,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}
@@ -321,6 +332,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}
@@ -363,6 +375,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}
@@ -378,6 +391,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}
@@ -394,6 +408,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
import * as React from "react";
import { GraphVizComponent, GraphVizComponentProps } from "./GraphVizComponent";
import CollapseArrowIcon from "../../../../images/Collapse_arrow_14x14.svg";
import ExpandIcon from "../../../../images/Expand_14x14.svg";
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
import { GraphVizComponent, GraphVizComponentProps } from "./GraphVizComponent";
interface MiddlePaneComponentProps {
isTabsContentExpanded: boolean;
@@ -17,7 +17,14 @@ export class MiddlePaneComponent extends React.Component<MiddlePaneComponentProp
<div className="middlePane">
<div className="graphTitle">
<span className="paneTitle">Graph</span>
<span className="graphExpandCollapseBtn pull-right" onClick={this.props.toggleExpandGraph}>
<span
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
src={this.props.isTabsContentExpanded ? CollapseArrowIcon : ExpandIcon}
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 * as Q from "q";
import React from "react";
import { GraphHighlightedNodeData, PossibleVertex } from "./GraphExplorer";
import { Mode, NodePropertiesComponent, NodePropertiesComponentProps } from "./NodePropertiesComponent";
import { NodePropertiesComponent, NodePropertiesComponentProps, Mode } from "./NodePropertiesComponent";
import { GraphHighlightedNodeData, EditedProperties, EditedEdges, PossibleVertex } from "./GraphExplorer";
describe("Property pane", () => {
const title = "My Title";
@@ -37,25 +37,17 @@ describe("Property pane", () => {
return {
expandedTitle: title,
isCollapsed: false,
onCollapsedChanged: (): void => {
("");
},
onCollapsedChanged: (newValue: boolean): void => {},
node: highlightedNode,
getPkIdFromNodeData: (): string => undefined,
collectionPartitionKeyProperty: undefined,
updateVertexProperties: (): Q.Promise<void> => Q.resolve(),
selectNode: (): void => {
("");
},
updatePossibleVertices: (): Q.Promise<PossibleVertex[]> => Q.resolve(undefined),
possibleEdgeLabels: undefined,
editGraphEdges: (): Q.Promise<unknown> => Q.resolve(),
deleteHighlightedNode: (): void => {
("");
},
onModeChanged: (): void => {
("");
},
getPkIdFromNodeData: (v: GraphHighlightedNodeData): string => null,
collectionPartitionKeyProperty: null,
updateVertexProperties: (editedProperties: EditedProperties): Q.Promise<void> => Q.resolve(),
selectNode: (id: string): void => {},
updatePossibleVertices: (): Q.Promise<PossibleVertex[]> => Q.resolve(null),
possibleEdgeLabels: null,
editGraphEdges: (editedEdges: EditedEdges): Q.Promise<any> => Q.resolve(),
deleteHighlightedNode: (): void => {},
onModeChanged: (newMode: Mode): void => {},
viewMode: Mode.READONLY_PROP,
};
};

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -161,7 +161,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
true
).toLocaleLowerCase()}.`}
>
<Icon iconName="Info" className="panelInfoIcon" />
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
</TooltipHost>
</Stack>
@@ -173,7 +173,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-checked={this.state.createNewDatabase}
name="databaseType"
type="radio"
role="radio"
id="databaseCreateNew"
tabIndex={0}
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
@@ -187,7 +186,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-checked={!this.state.createNewDatabase}
name="databaseType"
type="radio"
role="radio"
tabIndex={0}
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
/>
@@ -209,7 +207,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
size={40}
className="panelTextField"
aria-label="New database id"
autoFocus
tabIndex={0}
value={this.state.newDatabaseId}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
this.setState({ newDatabaseId: event.target.value })
@@ -236,7 +234,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
true
).toLocaleLowerCase()} within the database.`}
>
<Icon iconName="Info" className="panelInfoIcon" />
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
</TooltipHost>
</Stack>
)}
@@ -279,7 +277,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
directionalHint={DirectionalHint.bottomLeftEdge}
content={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
>
<Icon iconName="Info" className="panelInfoIcon" />
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
</TooltipHost>
</Stack>
@@ -319,7 +317,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-label="Turn on indexing"
aria-checked={this.state.enableIndexing}
type="radio"
role="radio"
tabIndex={0}
onChange={this.onTurnOnIndexing.bind(this)}
/>
@@ -331,7 +328,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-label="Turn off indexing"
aria-checked={!this.state.enableIndexing}
type="radio"
role="radio"
tabIndex={0}
onChange={this.onTurnOffIndexing.bind(this)}
/>
@@ -362,7 +358,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."
}
>
<Icon iconName="Info" className="panelInfoIcon" />
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
</TooltipHost>
</Stack>
@@ -374,7 +370,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-checked={!this.state.isSharded}
name="unsharded"
type="radio"
role="radio"
id="unshardedOption"
tabIndex={0}
onChange={this.onUnshardedRadioBtnChange.bind(this)}
@@ -388,7 +383,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-checked={this.state.isSharded}
name="sharded"
type="radio"
role="radio"
id="shardedOption"
tabIndex={0}
onChange={this.onShardedRadioBtnChange.bind(this)}
@@ -409,7 +403,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
directionalHint={DirectionalHint.bottomLeftEdge}
content={this.getPartitionKeyTooltipText()}
>
<Icon iconName="Info" className="panelInfoIcon" />
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
</TooltipHost>
</Stack>
@@ -467,7 +461,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
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.`}
>
<Icon iconName="Info" className="panelInfoIcon" />
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
</TooltipHost>
</Stack>
)}
@@ -497,7 +491,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
per partition key."
>
<Icon iconName="Info" className="panelInfoIcon" />
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
</TooltipHost>
</Stack>
@@ -514,7 +508,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
: "Comma separated paths e.g. /firstName,/address/zipCode"
}
className="panelTextField"
autoFocus
value={uniqueKey}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
const uniqueKeys = this.state.uniqueKeys.map((uniqueKey: string, j: number) => {
@@ -560,7 +553,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
directionalHint={DirectionalHint.bottomLeftEdge}
content={this.getAnalyticalStorageTooltipContent()}
>
<Icon iconName="Info" className="panelInfoIcon" />
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
</TooltipHost>
</Stack>
@@ -573,7 +566,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-checked={this.state.enableAnalyticalStore}
name="analyticalStore"
type="radio"
role="radio"
id="enableAnalyticalStoreBtn"
tabIndex={0}
onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)}
@@ -588,7 +580,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-checked={!this.state.enableAnalyticalStore}
name="analyticalStore"
type="radio"
role="radio"
id="disableAnalyticalStoreBtn"
tabIndex={0}
onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)}
@@ -637,7 +628,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
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."
>
<Icon iconName="Info" className="panelInfoIcon" />
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
</TooltipHost>
</Stack>

View File

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

View File

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

View File

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

View File

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

View File

@@ -108,6 +108,8 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
submitButtonText: "OK",
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 (
<RightPaneForm {...props}>
<div className="panelFormWrapper">
@@ -117,12 +119,12 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
<Text variant="small">Confirm by typing the {collectionName.toLowerCase()} id</Text>
<TextField
id="confirmCollectionId"
autoFocus
value={inputCollectionName}
styles={{ fieldGroup: { width: 300 } }}
onChange={(event, newInput?: string) => {
setInputCollectionName(newInput);
}}
ariaLabel={confirmContainer}
/>
</div>
{shouldRecordFeedback() && (
@@ -142,6 +144,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
onChange={(event, newInput?: string) => {
setDeleteCollectionFeedback(newInput);
}}
ariaLabel={reasonInfo}
/>
</div>
)}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -128,18 +128,18 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
<div className="title">Common Tasks</div>
<ul>
{commonTaskItems.map((item) => (
<li
className="focusable"
key={`${item.title}${item.description}`}
onClick={item.onClick}
onKeyPress={(event: React.KeyboardEvent) => this.onSplashScreenItemKeyPress(event, item.onClick)}
tabIndex={0}
role="button"
>
<img src={item.iconSrc} alt="" />
<span className="oneLineContent" title={item.info}>
{item.title}
</span>
<li className="focusable" key={`${item.title}${item.description}`}>
<div
onClick={item.onClick}
onKeyPress={(event: React.KeyboardEvent) => this.onSplashScreenItemKeyPress(event, item.onClick)}
tabIndex={0}
role="button"
>
<img src={item.iconSrc} alt="" />
<span className="oneLineContent" title={item.info}>
{item.title}
</span>
</div>
</li>
))}
</ul>
@@ -165,22 +165,23 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
<div className="title">Tips</div>
<ul>
{tipsItems.map((item) => (
<li
className="tipContainer focusable"
key={`${item.title}${item.description}`}
onClick={item.onClick}
onKeyPress={(event: React.KeyboardEvent) => this.onSplashScreenItemKeyPress(event, item.onClick)}
tabIndex={0}
role="link"
>
<div className="title" title={item.info}>
{item.title}
<li className="tipContainer focusable" key={`${item.title}${item.description}`}>
<div
onClick={item.onClick}
onKeyPress={(event: React.KeyboardEvent) => this.onSplashScreenItemKeyPress(event, item.onClick)}
tabIndex={0}
role="button"
className="tipsWrapper"
>
<div className="title" title={item.info}>
{item.title}
</div>
<div className="description">{item.description}</div>
</div>
<div className="description">{item.description}</div>
</li>
))}
<li>
<a role="link" href={SplashScreen.seeMoreItemUrl} rel="noreferrer" target="_blank" tabIndex={0}>
<a href={SplashScreen.seeMoreItemUrl} rel="noreferrer" target="_blank" tabIndex={0}>
{SplashScreen.seeMoreItemTitle}
</a>
</li>
@@ -307,16 +308,23 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
iconSrc: AddDatabaseIcon,
title: "New " + getDatabaseName(),
description: undefined,
onClick: () =>
useSidePanel
.getState()
.openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={this.container} />),
onClick: () => this.openAddDatabasePanel(),
});
}
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) {
return {
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
*/
function applyDefaultRendering(settings: any): DataTables.SettingsLegacy {
let tableColumns: DataTables.ColumnLegacy[] = null;
var tableColumns: DataTables.ColumnLegacy[] = null;
if (settings.aoColumns) {
tableColumns = settings.aoColumns;
@@ -34,7 +34,7 @@ function applyDefaultRendering(settings: any): DataTables.SettingsLegacy {
return settings;
}
for (let i = 0; i < tableColumns.length; i++) {
for (var i = 0; i < tableColumns.length; i++) {
// the column does not have a render function
if (!tableColumns[i].mRender) {
tableColumns[i].mRender = defaultDataRender;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
import { extractFeatures } from "./extractFeatures";
import { extractFeatures, hasFlag } from "./extractFeatures";
describe("extractFeatures", () => {
it("correctly detects feature flags in a case insensitive manner", () => {
@@ -14,9 +14,25 @@ describe("extractFeatures", () => {
});
const features = extractFeatures(params);
expect(features.notebookServerUrl).toBe(url);
expect(features.notebookServerToken).toBe(token);
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,6 +29,8 @@ export type Features = {
readonly pr?: string;
readonly showMinRUSurvey: boolean;
readonly ttl90Days: boolean;
readonly mongoProxyEndpoint?: string;
readonly mongoProxyAPIs?: string;
readonly notebooksTemporarilyDown: boolean;
};
@@ -63,6 +65,8 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
enableKoResourceTree: "true" === get("enablekoresourcetree"),
executeSproc: "true" === get("dataexplorerexecutesproc"),
hostedDataExplorer: "true" === get("hosteddataexplorerenabled"),
mongoProxyEndpoint: get("mongoproxyendpoint"),
mongoProxyAPIs: get("mongoproxyapis"),
junoEndpoint: get("junoendpoint"),
livyEndpoint: get("livyendpoint"),
notebookBasePath: get("notebookbasepath"),
@@ -80,3 +84,12 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
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

@@ -0,0 +1,200 @@
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