Compare commits

..

16 Commits

Author SHA1 Message Date
sunilyadav840
bd1009c849 fixed eslint of ConflictsTab 2021-10-20 14:13:47 +05:30
victor-meng
55837db65b Revert "Fix keyboard focus does not retain on 'New Database' button a… (#1139)
* Revert "Fix keyboard focus does not retain on 'New Database' button after closing the 'New Database' blade via ESC key (#1109)"

This reverts commit f7e7240010.

* Revert "Fix ally database panel open issue (#1120)"

This reverts commit ed1ffb692f.
2021-10-15 17:36:48 -07:00
victor-meng
9f27cb95b9 Only use the SET keyword once in the update query (#1138) 2021-10-15 12:33:59 -07:00
Hardikkumar Nai
271256bffb resolve_eslint_NodePropertiesComponent (#921)
* resolve_eslint_NodePropertiesComponent

* address commit

* Open new screen: Screen reader does not pass the 'Copied' information after selecting 'Copy' button.

* resolve lint error
2021-10-12 08:43:35 -07:00
vaidankarswapnil
aff7133095 Fix eslint issues for TableCommands and other files (#1132) 2021-10-12 08:07:06 -07:00
Hardikkumar Nai
bfd4948fb9 absulte_path setting (#984)
* absulte_path setting

* resolve build time error
2021-10-12 07:38:34 -07:00
victor-meng
1c54459708 If unsharded is checked, set partition key to undefined (#1128) 2021-10-11 12:09:38 -07:00
Sunil Kumar Yadav
df3b18d585 fixed eslint of NotebookComponentBootstrapper and NotebookReadOnlyRenderer (#1122) 2021-10-11 08:29:21 -07:00
Sunil Kumar Yadav
882f0e1554 fixed GraphExplorer.tsx ellint issue (#1124) 2021-10-11 08:21:52 -07:00
vaidankarswapnil
b67b76cc87 Fix eslint issues for QueryBuilderViewModal and QueryClauseViewModel (#1125) 2021-10-11 08:20:38 -07:00
vaidankarswapnil
734ee1e436 Fix eslint issues for ClauseGroup and ClauseGroupViewModel files (#1127) 2021-10-11 07:56:12 -07:00
Sunil Kumar Yadav
ff498b51e2 fixed eslint of Trigger.ts GithubOAuthService.ts etc (#1126) 2021-10-11 07:55:21 -07:00
vaidankarswapnil
ed1ffb692f Fix ally database panel open issue (#1120) 2021-10-06 07:53:46 -07:00
Karthik chakravarthy
f7fa3f7c09 Fix Unit Test: Mock the class to its instance (#1117)
* mock to instance

* Update jest.config.js

Co-authored-by: Jordi Bunster <jbunster@microsoft.com>
2021-10-05 13:06:26 -07:00
Jordi Bunster
6ebf19c0c9 Close tab fixes (#1107)
* Close tab fixes

Ensure that when promoting a new tab to being the 'active' tab
(as a consequence of, say, closing the active tab) that the newly
promoted tab has a chance to install its buttons and what not.

* Set new active tab even if undefined
2021-10-05 09:25:35 -07:00
Karthik chakravarthy
f968f57543 Allocation of container on demand (#1097)
* Allocation of container only on demand

* git notebook click emits connect to container and refresh notebooks

* git cleanup local repo

* Reconnect rename and memory heartbeat change

* Fix new notebook click event in case of git login

* Mongo proxy test replace with master file

* code refactor

* format check

* compile errors resolved

* address review comments

* rename environment to workspace

* Added content html rendering in Dialog

* format issue

* Added contentHtml in the dialog which renders jsx element
2021-09-30 12:53:33 -04:00
102 changed files with 1242 additions and 935 deletions

View File

@@ -81,21 +81,12 @@ src/Explorer/Tables/DataTable/DataTableBindingManager.ts
src/Explorer/Tables/DataTable/DataTableBuilder.ts src/Explorer/Tables/DataTable/DataTableBuilder.ts
src/Explorer/Tables/DataTable/DataTableContextMenu.ts src/Explorer/Tables/DataTable/DataTableContextMenu.ts
src/Explorer/Tables/DataTable/DataTableOperationManager.ts src/Explorer/Tables/DataTable/DataTableOperationManager.ts
src/Explorer/Tables/DataTable/DataTableOperations.ts
src/Explorer/Tables/DataTable/DataTableViewModel.ts src/Explorer/Tables/DataTable/DataTableViewModel.ts
src/Explorer/Tables/DataTable/TableCommands.ts
src/Explorer/Tables/DataTable/TableEntityCache.ts
src/Explorer/Tables/DataTable/TableEntityListViewModel.ts src/Explorer/Tables/DataTable/TableEntityListViewModel.ts
src/Explorer/Tables/Entities.ts
src/Explorer/Tables/QueryBuilder/ClauseGroup.ts
src/Explorer/Tables/QueryBuilder/ClauseGroupViewModel.ts
src/Explorer/Tables/QueryBuilder/CustomTimestampHelper.ts src/Explorer/Tables/QueryBuilder/CustomTimestampHelper.ts
src/Explorer/Tables/QueryBuilder/QueryBuilderViewModel.ts
src/Explorer/Tables/QueryBuilder/QueryClauseViewModel.ts
src/Explorer/Tables/TableDataClient.ts src/Explorer/Tables/TableDataClient.ts
src/Explorer/Tables/TableEntityProcessor.ts src/Explorer/Tables/TableEntityProcessor.ts
src/Explorer/Tables/Utilities.ts src/Explorer/Tables/Utilities.ts
src/Explorer/Tabs/ConflictsTab.ts
src/Explorer/Tabs/DatabaseSettingsTab.ts src/Explorer/Tabs/DatabaseSettingsTab.ts
src/Explorer/Tabs/DocumentsTab.test.ts src/Explorer/Tabs/DocumentsTab.test.ts
src/Explorer/Tabs/DocumentsTab.ts src/Explorer/Tabs/DocumentsTab.ts
@@ -115,15 +106,10 @@ src/Explorer/Tree/ObjectId.ts
src/Explorer/Tree/ResourceTokenCollection.ts src/Explorer/Tree/ResourceTokenCollection.ts
src/Explorer/Tree/StoredProcedure.ts src/Explorer/Tree/StoredProcedure.ts
src/Explorer/Tree/TreeComponents.ts src/Explorer/Tree/TreeComponents.ts
src/Explorer/Tree/Trigger.ts
src/Explorer/WaitsForTemplateViewModel.ts src/Explorer/WaitsForTemplateViewModel.ts
src/GitHub/GitHubClient.test.ts src/GitHub/GitHubClient.test.ts
src/GitHub/GitHubClient.ts src/GitHub/GitHubClient.ts
src/GitHub/GitHubConnector.ts
src/GitHub/GitHubOAuthService.ts
src/Index.ts src/Index.ts
src/Juno/JunoClient.test.ts
src/Juno/JunoClient.ts
src/Platform/Hosted/Authorization.ts src/Platform/Hosted/Authorization.ts
src/ReactDevTools.ts src/ReactDevTools.ts
src/Shared/Constants.ts src/Shared/Constants.ts
@@ -143,20 +129,13 @@ src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx
src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx
src/Explorer/Controls/TreeComponent/TreeComponent.tsx src/Explorer/Controls/TreeComponent/TreeComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx
src/Explorer/Graph/GraphExplorerComponent/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/NodePropertiesComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx
src/Explorer/Menus/CommandBar/CommandBarUtil.tsx src/Explorer/Menus/CommandBar/CommandBarUtil.tsx
src/Explorer/Notebook/NotebookComponent/NotebookComponentAdapter.tsx src/Explorer/Notebook/NotebookComponent/NotebookComponentAdapter.tsx
src/Explorer/Notebook/NotebookComponent/NotebookComponentBootstrapper.tsx
src/Explorer/Notebook/NotebookComponent/VirtualCommandBarComponent.tsx src/Explorer/Notebook/NotebookComponent/VirtualCommandBarComponent.tsx
src/Explorer/Notebook/NotebookComponent/contents/index.tsx src/Explorer/Notebook/NotebookComponent/contents/index.tsx
src/Explorer/Notebook/NotebookRenderer/NotebookReadOnlyRenderer.tsx
src/Explorer/Notebook/NotebookRenderer/NotebookRenderer.tsx src/Explorer/Notebook/NotebookRenderer/NotebookRenderer.tsx
src/Explorer/Notebook/NotebookRenderer/decorators/draggable/index.tsx src/Explorer/Notebook/NotebookRenderer/decorators/draggable/index.tsx
src/Explorer/Notebook/NotebookRenderer/decorators/hijack-scroll/index.tsx src/Explorer/Notebook/NotebookRenderer/decorators/hijack-scroll/index.tsx

View File

@@ -3,8 +3,8 @@ module.exports = {
browser: true, browser: true,
es6: true, es6: true,
}, },
plugins: ["@typescript-eslint", "no-null", "prefer-arrow", "react-hooks", "jsx-a11y"], plugins: ["@typescript-eslint", "no-null", "prefer-arrow", "react-hooks"],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:jsx-a11y/recommended"], extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
globals: { globals: {
Atomics: "readonly", Atomics: "readonly",
SharedArrayBuffer: "readonly", SharedArrayBuffer: "readonly",
@@ -39,7 +39,6 @@ module.exports = {
"@typescript-eslint/switch-exhaustiveness-check": "error", "@typescript-eslint/switch-exhaustiveness-check": "error",
"@typescript-eslint/no-unused-vars": "error", "@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-extraneous-class": "error", "@typescript-eslint/no-extraneous-class": "error",
"no-null/no-null": "error",
"@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-explicit-any": "error",
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }], "prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
eqeqeq: "error", eqeqeq: "error",

View File

@@ -22,5 +22,6 @@
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true, "source.fixAll.eslint": true,
"source.organizeImports": true "source.organizeImports": true
} },
"typescript.preferences.importModuleSpecifier": "non-relative"
} }

View File

@@ -37,8 +37,8 @@ module.exports = {
global: { global: {
branches: 25, branches: 25,
functions: 25, functions: 25,
lines: 29.5, lines: 29,
statements: 29.5, statements: 29,
}, },
}, },
@@ -129,6 +129,8 @@ module.exports = {
// The test environment that will be used for testing // The test environment that will be used for testing
// testEnvironment: "jest-environment-jsdom", // testEnvironment: "jest-environment-jsdom",
modulePaths: ["node_modules", "<rootDir>/src"],
// Options that will be passed to the testEnvironment // Options that will be passed to the testEnvironment
// testEnvironmentOptions: {}, // testEnvironmentOptions: {},

View File

@@ -3079,6 +3079,3 @@ settings-pane {
background: white; background: white;
height: 100%; height: 100%;
} }
.moreOption {
color: #337ab7;
}

91
package-lock.json generated
View File

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

View File

@@ -61,7 +61,6 @@
"dom-to-image": "2.6.0", "dom-to-image": "2.6.0",
"dotenv": "8.2.0", "dotenv": "8.2.0",
"eslint-plugin-jest": "23.13.2", "eslint-plugin-jest": "23.13.2",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-react": "7.20.0", "eslint-plugin-react": "7.20.0",
"hasher": "1.2.0", "hasher": "1.2.0",
"html2canvas": "1.0.0-rc.5", "html2canvas": "1.0.0-rc.5",

View File

@@ -31,15 +31,17 @@ export const CollapsedResourceTree: FunctionComponent<CollapsedResourceTreeProps
<div id="mini" className={!isLeftPaneExpanded ? "mini toggle-mini" : "hiddenMain"}> <div id="mini" className={!isLeftPaneExpanded ? "mini toggle-mini" : "hiddenMain"}>
<div className="main-nav nav"> <div className="main-nav nav">
<ul className="nav"> <ul className="nav">
<li className="resourceTreeCollapse" id="collapseToggleLeftPaneButton" aria-label="Expand Tree"> <li
<span className="resourceTreeCollapse"
className="leftarrowCollapsed" id="collapseToggleLeftPaneButton"
role="button" role="button"
tabIndex={0} tabIndex={0}
aria-label="Expand Tree"
onClick={toggleLeftPaneExpanded} onClick={toggleLeftPaneExpanded}
onKeyPress={onKeyPressToggleLeftPaneExpanded} onKeyPress={onKeyPressToggleLeftPaneExpanded}
ref={focusButton} ref={focusButton}
> >
<span className="leftarrowCollapsed">
<img className="arrowCollapsed" src={arrowLeftImg} alt="Expand" /> <img className="arrowCollapsed" src={arrowLeftImg} alt="Expand" />
</span> </span>
<span className="collectionCollapsed"> <span className="collectionCollapsed">

View File

@@ -339,9 +339,11 @@ export enum ConflictOperationType {
} }
export enum ConnectionStatusType { export enum ConnectionStatusType {
Connect = "Connect",
Connecting = "Connecting", Connecting = "Connecting",
Connected = "Connected", Connected = "Connected",
Failed = "Connection Failed", Failed = "Connection Failed",
ReConnect = "Reconnect",
} }
export const EmulatorMasterKey = export const EmulatorMasterKey =
@@ -353,15 +355,32 @@ export const StyleConstants = require("less-vars-loader!../../less/Common/Consta
export class Notebook { export class Notebook {
public static readonly defaultBasePath = "./notebooks"; public static readonly defaultBasePath = "./notebooks";
public static readonly heartbeatDelayMs = 5000; public static readonly heartbeatDelayMs = 60000;
public static readonly kernelRestartInitialDelayMs = 1000; public static readonly kernelRestartInitialDelayMs = 1000;
public static readonly kernelRestartMaxDelayMs = 20000; public static readonly kernelRestartMaxDelayMs = 20000;
public static readonly autoSaveIntervalMs = 120000; public static readonly autoSaveIntervalMs = 120000;
public static readonly memoryGuageToGB = 1048576;
public static readonly temporarilyDownMsg = "Notebooks is currently not available. We are working on it."; public static readonly temporarilyDownMsg = "Notebooks is currently not available. We are working on it.";
public static readonly mongoShellTemporarilyDownMsg = public static readonly mongoShellTemporarilyDownMsg =
"We have identified an issue with the Mongo Shell and it is unavailable right now. We are actively working on the mitigation."; "We have identified an issue with the Mongo Shell and it is unavailable right now. We are actively working on the mitigation.";
public static readonly cassandraShellTemporarilyDownMsg = public static readonly cassandraShellTemporarilyDownMsg =
"We have identified an issue with the Cassandra Shell and it is unavailable right now. We are actively working on the mitigation."; "We have identified an issue with the Cassandra Shell and it is unavailable right now. We are actively working on the mitigation.";
public static saveNotebookModalTitle = "Save Notebook in temporary workspace";
public static saveNotebookModalContent =
"This notebook will be saved in the temporary workspace and will be removed when the session expires. To save your work permanently, save your notebooks to a GitHub repository or download the notebooks to your local machine before the session ends.";
public static newNotebookModalTitle = "Create Notebook in temporary workspace";
public static newNotebookUploadModalTitle = "Upload Notebook in temporary workspace";
public static newNotebookModalContent1 =
"A temporary workspace will be created to enable you to work with notebooks. When the session expires, any notebooks in the workspace will be removed.";
public static newNotebookModalContent2 =
"To save your work permanently, save your notebooks to a GitHub repository or download the notebooks to your local machine before the session ends. ";
public static galleryNotebookDownloadContent1 =
"To download, run, and make changes to this sample notebook, a temporary workspace will be created. When the session expires, any notebooks in the workspace will be removed.";
public static galleryNotebookDownloadContent2 =
"To save your work permanently, save your notebooks to a GitHub repository or download the Notebooks to your local machine before the session ends. ";
public static cosmosNotebookHomePageUrl = "https://aka.ms/cosmos-notebooks-limits";
public static cosmosNotebookGitDocumentationUrl = "https://aka.ms/cosmos-notebooks-github";
public static learnMore = "Learn more.";
} }
export class SparkLibrary { export class SparkLibrary {

View File

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

View File

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

View File

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

View File

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

View File

@@ -49,7 +49,7 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
onClick={this.toggleCollapsed} onClick={this.toggleCollapsed}
onKeyPress={this.onKeyPress} onKeyPress={this.onKeyPress}
tabIndex={0} tabIndex={0}
aria-label="Advanced" aria-name="Advanced"
role="button" role="button"
aria-expanded={this.state.isExpanded} aria-expanded={this.state.isExpanded}
> >

View File

@@ -4,7 +4,7 @@ exports[`CollapsibleSectionComponent renders 1`] = `
<Fragment> <Fragment>
<Stack <Stack
aria-expanded={true} aria-expanded={true}
aria-label="Advanced" aria-name="Advanced"
className="collapsibleSection" className="collapsibleSection"
horizontal={true} horizontal={true}
onClick={[Function]} onClick={[Function]}

View File

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

View File

@@ -13,6 +13,7 @@ import {
Link, Link,
PrimaryButton, PrimaryButton,
ProgressIndicator, ProgressIndicator,
Text,
TextField, TextField,
} from "@fluentui/react"; } from "@fluentui/react";
import React, { FC } from "react"; import React, { FC } from "react";
@@ -30,6 +31,7 @@ export interface DialogState {
onOk: () => void, onOk: () => void,
cancelLabel: string, cancelLabel: string,
onCancel: () => void, onCancel: () => void,
contentHtml?: JSX.Element,
choiceGroupProps?: IChoiceGroupProps, choiceGroupProps?: IChoiceGroupProps,
textFieldProps?: TextFieldProps, textFieldProps?: TextFieldProps,
primaryButtonDisabled?: boolean primaryButtonDisabled?: boolean
@@ -58,6 +60,7 @@ export const useDialog: UseStore<DialogState> = create((set, get) => ({
onOk: () => void, onOk: () => void,
cancelLabel: string, cancelLabel: string,
onCancel: () => void, onCancel: () => void,
contentHtml?: JSX.Element,
choiceGroupProps?: IChoiceGroupProps, choiceGroupProps?: IChoiceGroupProps,
textFieldProps?: TextFieldProps, textFieldProps?: TextFieldProps,
primaryButtonDisabled?: boolean primaryButtonDisabled?: boolean
@@ -76,6 +79,7 @@ export const useDialog: UseStore<DialogState> = create((set, get) => ({
get().closeDialog(); get().closeDialog();
onCancel && onCancel(); onCancel && onCancel();
}, },
contentHtml,
choiceGroupProps, choiceGroupProps,
textFieldProps, textFieldProps,
primaryButtonDisabled, primaryButtonDisabled,
@@ -124,6 +128,7 @@ export interface DialogProps {
type?: DialogType; type?: DialogType;
showCloseButton?: boolean; showCloseButton?: boolean;
onDismiss?: () => void; onDismiss?: () => void;
contentHtml?: JSX.Element;
} }
const DIALOG_MIN_WIDTH = "400px"; const DIALOG_MIN_WIDTH = "400px";
@@ -150,6 +155,7 @@ export const Dialog: FC = () => {
type, type,
showCloseButton, showCloseButton,
onDismiss, onDismiss,
contentHtml,
} = props || {}; } = props || {};
const dialogProps: IDialogProps = { const dialogProps: IDialogProps = {
@@ -191,6 +197,7 @@ export const Dialog: FC = () => {
{linkProps.linkText} <FontIcon iconName="NavigateExternalInline" /> {linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
</Link> </Link>
)} )}
{contentHtml && <Text>{contentHtml}</Text>}
{progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />} {progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />}
<DialogFooter> <DialogFooter>
<PrimaryButton {...primaryButtonProps} /> <PrimaryButton {...primaryButtonProps} />

View File

@@ -17,6 +17,8 @@ import Explorer from "../../Explorer";
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2"; import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper"; import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer"; import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer";
import { NotebookUtil } from "../../Notebook/NotebookUtil";
import { useNotebook } from "../../Notebook/useNotebook";
import { Dialog, TextFieldProps, useDialog } from "../Dialog"; import { Dialog, TextFieldProps, useDialog } from "../Dialog";
import { NotebookMetadataComponent } from "./NotebookMetadataComponent"; import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
import "./NotebookViewerComponent.less"; import "./NotebookViewerComponent.less";
@@ -146,7 +148,9 @@ export class NotebookViewerComponent
<NotebookMetadataComponent <NotebookMetadataComponent
data={this.state.galleryItem} data={this.state.galleryItem}
isFavorite={this.state.isFavorite} isFavorite={this.state.isFavorite}
downloadButtonText={this.props.container && "Download to my notebooks"} downloadButtonText={
this.props.container && NotebookUtil.getNotebookBtnTitle(useNotebook.getState().notebookFolderName)
}
onTagClick={this.props.onTagClick} onTagClick={this.props.onTagClick}
onFavoriteClick={this.favoriteItem} onFavoriteClick={this.favoriteItem}
onUnfavoriteClick={this.unfavoriteItem} onUnfavoriteClick={this.unfavoriteItem}

View File

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

View File

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

View File

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

View File

@@ -118,8 +118,10 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
<input <input
className="throughputInputRadioBtn" className="throughputInputRadioBtn"
aria-label="Autoscale mode" aria-label="Autoscale mode"
aria-required={true}
checked={isAutoscaleSelected} checked={isAutoscaleSelected}
type="radio" type="radio"
role="radio"
tabIndex={0} tabIndex={0}
onChange={(e) => handleOnChangeMode(e, "Autoscale")} onChange={(e) => handleOnChangeMode(e, "Autoscale")}
/> />
@@ -130,6 +132,8 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
aria-label="Manual mode" aria-label="Manual mode"
checked={!isAutoscaleSelected} checked={!isAutoscaleSelected}
type="radio" type="radio"
aria-required={true}
role="radio"
tabIndex={0} tabIndex={0}
onChange={(e) => handleOnChangeMode(e, "Manual")} onChange={(e) => handleOnChangeMode(e, "Manual")}
/> />

View File

@@ -654,10 +654,12 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
> >
<input <input
aria-label="Autoscale mode" aria-label="Autoscale mode"
aria-required={true}
checked={true} checked={true}
className="throughputInputRadioBtn" className="throughputInputRadioBtn"
key=".0:$.0" key=".0:$.0"
onChange={[Function]} onChange={[Function]}
role="radio"
tabIndex={0} tabIndex={0}
type="radio" type="radio"
/> />
@@ -669,10 +671,12 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
</span> </span>
<input <input
aria-label="Manual mode" aria-label="Manual mode"
aria-required={true}
checked={false} checked={false}
className="throughputInputRadioBtn" className="throughputInputRadioBtn"
key=".0:$.2" key=".0:$.2"
onChange={[Function]} onChange={[Function]}
role="radio"
tabIndex={0} tabIndex={0}
type="radio" type="radio"
/> />

View File

@@ -1,9 +1,11 @@
import { Link } from "@fluentui/react/lib/Link";
import * as ko from "knockout"; import * as ko from "knockout";
import React from "react"; import React from "react";
import _ from "underscore"; import _ from "underscore";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer"; import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
import * as Constants from "../Common/Constants"; import * as Constants from "../Common/Constants";
import { ConnectionStatusType, HttpStatusCodes, Notebook } from "../Common/Constants";
import { readCollection } from "../Common/dataAccess/readCollection"; import { readCollection } from "../Common/dataAccess/readCollection";
import { readDatabases } from "../Common/dataAccess/readDatabases"; import { readDatabases } from "../Common/dataAccess/readDatabases";
import { isPublicInternetAccessAllowed } from "../Common/DatabaseAccountUtility"; import { isPublicInternetAccessAllowed } from "../Common/DatabaseAccountUtility";
@@ -11,6 +13,7 @@ import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHand
import * as Logger from "../Common/Logger"; import * as Logger from "../Common/Logger";
import { QueriesClient } from "../Common/QueriesClient"; import { QueriesClient } from "../Common/QueriesClient";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import { ContainerConnectionInfo } from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService"; import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
import { useSidePanel } from "../hooks/useSidePanel"; import { useSidePanel } from "../hooks/useSidePanel";
@@ -163,23 +166,10 @@ export default class Explorer {
useNotebook.subscribe( useNotebook.subscribe(
async () => { async () => {
if (!this.notebookManager) { this.initiateAndRefreshNotebookList();
const NotebookManager = await ( useNotebook.getState().setIsRefreshed(false);
await import(/* webpackChunkName: "NotebookManager" */ "./Notebook/NotebookManager")
).default;
this.notebookManager = new NotebookManager();
this.notebookManager.initialize({
container: this,
resourceTree: this.resourceTree,
refreshCommandBarButtons: () => this.refreshCommandBarButtons(),
refreshNotebookList: () => this.refreshNotebookList(),
});
}
this.refreshCommandBarButtons();
this.refreshNotebookList();
}, },
(state) => state.isNotebookEnabled (state) => state.isNotebookEnabled || state.isRefreshed
); );
this.resourceTree = new ResourceTreeAdapter(this); this.resourceTree = new ResourceTreeAdapter(this);
@@ -212,6 +202,23 @@ export default class Explorer {
this.refreshExplorer(); this.refreshExplorer();
} }
public async initiateAndRefreshNotebookList(): Promise<void> {
if (!this.notebookManager) {
const NotebookManager = (await import(/* webpackChunkName: "NotebookManager" */ "./Notebook/NotebookManager"))
.default;
this.notebookManager = new NotebookManager();
this.notebookManager.initialize({
container: this,
resourceTree: this.resourceTree,
refreshCommandBarButtons: () => this.refreshCommandBarButtons(),
refreshNotebookList: () => this.refreshNotebookList(),
});
}
this.refreshCommandBarButtons();
this.refreshNotebookList();
}
public openEnableSynapseLinkDialog(): void { public openEnableSynapseLinkDialog(): void {
const addSynapseLinkDialogProps: DialogProps = { const addSynapseLinkDialogProps: DialogProps = {
linkProps: { linkProps: {
@@ -345,23 +352,7 @@ export default class Explorer {
return; return;
} }
this._isInitializingNotebooks = true; this._isInitializingNotebooks = true;
if (userContext.features.phoenix) { if (userContext.features.phoenix === false) {
const provisionData = {
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
resourceId: userContext.databaseAccount.id,
dbAccountName: userContext.databaseAccount.name,
aadToken: userContext.authorizationToken,
resourceGroup: userContext.resourceGroup,
subscriptionId: userContext.subscriptionId,
};
const connectionInfo = await this.phoenixClient.containerConnectionInfo(provisionData);
if (connectionInfo.data && connectionInfo.data.notebookServerUrl) {
useNotebook.getState().setNotebookServerInfo({
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.data.notebookServerUrl,
authToken: userContext.features.notebookServerToken || connectionInfo.data.notebookAuthToken,
});
}
} else {
await this.ensureNotebookWorkspaceRunning(); await this.ensureNotebookWorkspaceRunning();
const connectionInfo = await listConnectionInfo( const connectionInfo = await listConnectionInfo(
userContext.subscriptionId, userContext.subscriptionId,
@@ -376,13 +367,59 @@ export default class Explorer {
}); });
} }
useNotebook.getState().initializeNotebooksTree(this.notebookManager);
this.refreshNotebookList(); this.refreshNotebookList();
this._isInitializingNotebooks = false; this._isInitializingNotebooks = false;
} }
public async allocateContainer(): Promise<void> {
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
const isAllocating = useNotebook.getState().isAllocating;
if (isAllocating === false && notebookServerInfo && notebookServerInfo.notebookServerEndpoint === undefined) {
const provisionData = {
aadToken: userContext.authorizationToken,
subscriptionId: userContext.subscriptionId,
resourceGroup: userContext.resourceGroup,
dbAccountName: userContext.databaseAccount.name,
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
};
const connectionStatus: ContainerConnectionInfo = {
status: ConnectionStatusType.Connecting,
};
useNotebook.getState().setConnectionInfo(connectionStatus);
try {
useNotebook.getState().setIsAllocating(true);
const connectionInfo = await this.phoenixClient.containerConnectionInfo(provisionData);
if (
connectionInfo.status === HttpStatusCodes.OK &&
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,
});
this.notebookManager?.notebookClient
.getMemoryUsage()
.then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo));
useNotebook.getState().setIsAllocating(false);
} else {
connectionStatus.status = ConnectionStatusType.Failed;
useNotebook.getState().resetConatinerConnection(connectionStatus);
}
} catch (error) {
connectionStatus.status = ConnectionStatusType.Failed;
useNotebook.getState().resetConatinerConnection(connectionStatus);
throw error;
}
this.refreshNotebookList();
this._isInitializingNotebooks = false;
}
}
public resetNotebookWorkspace(): void { public resetNotebookWorkspace(): void {
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookClient) { if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookClient) {
handleError( handleError(
@@ -654,6 +691,9 @@ export default class Explorer {
if (!notebookContentItem || !notebookContentItem.path) { if (!notebookContentItem || !notebookContentItem.path) {
throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`); throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`);
} }
if (notebookContentItem.type === NotebookContentItemType.Notebook && NotebookUtil.isPhoenixEnabled()) {
this.allocateContainer();
}
const notebookTabs = useTabs const notebookTabs = useTabs
.getState() .getState()
@@ -875,9 +915,51 @@ export default class Explorer {
handleError(error, "Explorer/onNewNotebookClicked"); handleError(error, "Explorer/onNewNotebookClicked");
throw new Error(error); throw new Error(error);
} }
const isPhoenixEnabled = NotebookUtil.isPhoenixEnabled();
if (isPhoenixEnabled) {
if (isGithubTree) {
async () => {
await this.allocateContainer();
parent = parent || this.resourceTree.myNotebooksContentRoot; parent = parent || this.resourceTree.myNotebooksContentRoot;
this.createNewNoteBook(parent, isGithubTree);
};
} else {
useDialog.getState().showOkCancelModalDialog(
Notebook.newNotebookModalTitle,
undefined,
"Create",
async () => {
await this.allocateContainer();
parent = parent || this.resourceTree.myNotebooksContentRoot;
this.createNewNoteBook(parent, isGithubTree);
},
"Cancel",
undefined,
this.getNewNoteWarningText()
);
}
} else {
parent = parent || this.resourceTree.myNotebooksContentRoot;
this.createNewNoteBook(parent, isGithubTree);
}
}
private getNewNoteWarningText(): JSX.Element {
return (
<>
<p>{Notebook.newNotebookModalContent1}</p>
<br />
<p>
{Notebook.newNotebookModalContent2}
<Link href={Notebook.cosmosNotebookHomePageUrl} target="_blank">
{Notebook.learnMore}
</Link>
</p>
</>
);
}
private createNewNoteBook(parent?: NotebookContentItem, isGithubTree?: boolean): void {
const clearInProgressMessage = logConsoleProgress(`Creating new notebook in ${parent.path}`); const clearInProgressMessage = logConsoleProgress(`Creating new notebook in ${parent.path}`);
const startKey: number = TelemetryProcessor.traceStart(Action.CreateNewNotebook, { const startKey: number = TelemetryProcessor.traceStart(Action.CreateNewNotebook, {
dataExplorerArea: Constants.Areas.Notebook, dataExplorerArea: Constants.Areas.Notebook,
@@ -924,7 +1006,26 @@ export default class Explorer {
await this.notebookManager?.notebookContentClient.updateItemChildrenInPlace(item); await this.notebookManager?.notebookContentClient.updateItemChildrenInPlace(item);
} }
public openNotebookTerminal(kind: ViewModels.TerminalKind): void { public async openNotebookTerminal(kind: ViewModels.TerminalKind): Promise<void> {
if (NotebookUtil.isPhoenixEnabled()) {
await this.allocateContainer();
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) {
this.connectToNotebookTerminal(kind);
} else {
useDialog
.getState()
.showOkModalDialog(
"Failed to Connect",
"Failed to connect temporary workspace, this could happen because of network issue please refresh and try again."
);
}
} else {
this.connectToNotebookTerminal(kind);
}
}
private connectToNotebookTerminal(kind: ViewModels.TerminalKind): void {
let title: string; let title: string;
switch (kind) { switch (kind) {
@@ -975,7 +1076,7 @@ export default class Explorer {
notebookUrl?: string, notebookUrl?: string,
galleryItem?: IGalleryItem, galleryItem?: IGalleryItem,
isFavorite?: boolean isFavorite?: boolean
) { ): Promise<void> {
const title = "Gallery"; const title = "Gallery";
const GalleryTab = await (await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab")).default; const GalleryTab = await (await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab")).default;
const galleryTab = useTabs const galleryTab = useTabs
@@ -1079,7 +1180,27 @@ export default class Explorer {
} }
public openUploadFilePanel(parent?: NotebookContentItem): void { public openUploadFilePanel(parent?: NotebookContentItem): void {
if (NotebookUtil.isPhoenixEnabled()) {
useDialog.getState().showOkCancelModalDialog(
Notebook.newNotebookUploadModalTitle,
undefined,
"Upload",
async () => {
await this.allocateContainer();
parent = parent || this.resourceTree.myNotebooksContentRoot; parent = parent || this.resourceTree.myNotebooksContentRoot;
this.uploadFilePanel(parent);
},
"Cancel",
undefined,
this.getNewNoteWarningText()
);
} else {
parent = parent || this.resourceTree.myNotebooksContentRoot;
this.uploadFilePanel(parent);
}
}
private uploadFilePanel(parent?: NotebookContentItem): void {
useSidePanel useSidePanel
.getState() .getState()
.openSidePanel( .openSidePanel(
@@ -1088,6 +1209,24 @@ export default class Explorer {
); );
} }
public getDownloadModalConent(fileName: string): JSX.Element {
if (NotebookUtil.isPhoenixEnabled()) {
return (
<>
<p>{Notebook.galleryNotebookDownloadContent1}</p>
<br />
<p>
{Notebook.galleryNotebookDownloadContent2}
<Link href={Notebook.cosmosNotebookGitDocumentationUrl} target="_blank">
{Notebook.learnMore}
</Link>
</p>
</>
);
}
return <p> Download {fileName} from gallery as a copy to your notebooks to run and/or edit the notebook. </p>;
}
public async refreshExplorer(): Promise<void> { public async refreshExplorer(): Promise<void> {
userContext.authType === AuthType.ResourceToken userContext.authType === AuthType.ResourceToken
? this.refreshDatabaseForResourceToken() ? this.refreshDatabaseForResourceToken()

View File

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

View File

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

View File

@@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos"; import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import * as Q from "q"; import * as Q from "q";
import * as React from "react"; import * as React from "react";
@@ -294,8 +296,6 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
this.setGremlinParams(); this.setGremlinParams();
} }
const selectedNode = this.state.highlightedNode;
props.onGraphAccessorCreated({ props.onGraphAccessorCreated({
applyFilter: this.submitQuery.bind(this), applyFilter: this.submitQuery.bind(this),
addVertex: this.addVertex.bind(this), addVertex: this.addVertex.bind(this),
@@ -303,7 +303,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
}); });
} // constructor } // constructor
public shareIGraphConfig(igraphConfig: IGraphConfig) { public shareIGraphConfig(igraphConfig: IGraphConfig): void {
this.setState({ this.setState({
igraphConfig: { ...igraphConfig }, igraphConfig: { ...igraphConfig },
}); });
@@ -330,10 +330,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
const partitionKeyProperty = this.props.collectionPartitionKeyProperty; const partitionKeyProperty = this.props.collectionPartitionKeyProperty;
// aggregate all the properties, remove dropped ones // aggregate all the properties, remove dropped ones
let finalProperties = editedProperties.existingProperties.concat(editedProperties.addedProperties); const finalProperties = editedProperties.existingProperties.concat(editedProperties.addedProperties);
// Compose the query // Compose the query
let pkId = editedProperties.pkId; const pkId = editedProperties.pkId;
let updateQueryFragment = ""; let updateQueryFragment = "";
finalProperties.forEach((p) => { finalProperties.forEach((p) => {
@@ -422,7 +422,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
* Called from ko binding * Called from ko binding
* @param id * @param id
*/ */
public selectNode(id: string) { public selectNode(id: string): void {
if (!this.d3ForceGraph) { if (!this.d3ForceGraph) {
console.warn("Attempting to select node, but d3ForceGraph not initialized, yet."); console.warn("Attempting to select node, but d3ForceGraph not initialized, yet.");
return; return;
@@ -431,7 +431,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
this.d3ForceGraph.selectNode(id); this.d3ForceGraph.selectNode(id);
} }
public deleteHighlightedNode() { public deleteHighlightedNode(): void {
if (!this.state.highlightedNode) { if (!this.state.highlightedNode) {
GraphExplorer.reportToConsole(ConsoleDataType.Error, "No highlighted node to remove."); GraphExplorer.reportToConsole(ConsoleDataType.Error, "No highlighted node to remove.");
return; return;
@@ -467,23 +467,23 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
* Is of type: {e: GremlinEdge, v: GremlinVertex}[] * Is of type: {e: GremlinEdge, v: GremlinVertex}[]
* @param data * @param data
*/ */
public static isEdgeVertexPairArray(data: any) { public static isEdgeVertexPairArray(data: any): boolean {
if (!(data instanceof Array)) { if (!(data instanceof Array)) {
GraphExplorer.reportToConsole(ConsoleDataType.Info, "Query result not an array", data); GraphExplorer.reportToConsole(ConsoleDataType.Info, "Query result not an array", data);
return false; return false;
} }
let pairs: any[] = data; const pairs: any[] = data;
for (let i = 0; i < pairs.length; i++) { for (let i = 0; i < pairs.length; i++) {
const item = pairs[i]; const item = pairs[i];
if ( if (
!item.hasOwnProperty("e") || !Object.prototype.hasOwnProperty.call(item, "e") ||
!item.hasOwnProperty("v") || !Object.prototype.hasOwnProperty.call(item, "v") ||
!item["e"].hasOwnProperty("id") || !Object.prototype.hasOwnProperty.call(item["e"], "id") ||
!item["e"].hasOwnProperty("type") || !Object.prototype.hasOwnProperty.call(item["e"], "type") ||
item["e"].type !== "edge" || item["e"].type !== "edge" ||
!item["v"].hasOwnProperty("id") || !Object.prototype.hasOwnProperty.call(item["v"], "id") ||
!item["v"].hasOwnProperty("type") || !Object.prototype.hasOwnProperty.call(item["e"], "type") ||
item["v"].type !== "vertex" item["v"].type !== "vertex"
) { ) {
return false; return false;
@@ -514,7 +514,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
// Try hitting cache first // Try hitting cache first
const cache = outE ? this.outECache : this.inECache; const cache = outE ? this.outECache : this.inECache;
const pairs = cache.retrieve(vertex.id, startIndex, pageSize); const pairs = cache.retrieve(vertex.id, startIndex, pageSize);
if (pairs != null && pairs.length === pageSize) { if (pairs !== null && pairs.length === pageSize) {
const msg = `Retrieved ${pairs.length} ${outE ? "outE" : "inE"} edges from cache for vertex id: ${vertex.id}`; const msg = `Retrieved ${pairs.length} ${outE ? "outE" : "inE"} edges from cache for vertex id: ${vertex.id}`;
GraphExplorer.reportToConsole(ConsoleDataType.Info, msg); GraphExplorer.reportToConsole(ConsoleDataType.Info, msg);
return Q.resolve(pairs); return Q.resolve(pairs);
@@ -588,7 +588,6 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
vertex._outEAllLoaded && vertex._outEAllLoaded &&
vertex._inEAllLoaded vertex._inEAllLoaded
) { ) {
console.info("No more edges to load for vertex " + vertex.id);
updateGraphData(); updateGraphData();
return Q.resolve(graphData); return Q.resolve(graphData);
} }
@@ -668,7 +667,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
} }
); );
return promise.then((nbPairsFetched: number) => { return promise.then(() => {
if (offsetIndex >= GraphExplorer.LOAD_PAGE_SIZE || !vertex._outEAllLoaded || !vertex._inEAllLoaded) { if (offsetIndex >= GraphExplorer.LOAD_PAGE_SIZE || !vertex._outEAllLoaded || !vertex._inEAllLoaded) {
vertex._pagination = { vertex._pagination = {
total: total:
@@ -754,7 +753,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
* Create a new edge in docdb and update graph * Create a new edge in docdb and update graph
* @param e * @param e
*/ */
public createNewEdge(e: GraphNewEdgeData): Q.Promise<any> { public createNewEdge(e: GraphNewEdgeData): Q.Promise<unknown> {
const q = `g.V('${GraphUtil.escapeSingleQuotes(e.inputOutV)}').addE('${GraphUtil.escapeSingleQuotes( const q = `g.V('${GraphUtil.escapeSingleQuotes(e.inputOutV)}').addE('${GraphUtil.escapeSingleQuotes(
e.label e.label
)}').To(g.V('${GraphUtil.escapeSingleQuotes(e.inputInV)}'))`; )}').To(g.V('${GraphUtil.escapeSingleQuotes(e.inputInV)}'))`;
@@ -772,8 +771,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
return; return;
} }
let edge = edges[0]; const edge = edges[0];
let graphData = this.originalGraphData; const graphData = this.originalGraphData;
graphData.addEdge(edge); graphData.addEdge(edge);
// Allow loadNeighbors to load list new edge // Allow loadNeighbors to load list new edge
@@ -800,10 +799,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
* Manually update in-memory graph. * Manually update in-memory graph.
* @param edgeId * @param edgeId
*/ */
public removeEdge(edgeId: string): Q.Promise<any> { public removeEdge(edgeId: string): Q.Promise<unknown> {
return this.submitToBackend(`g.E('${GraphUtil.escapeSingleQuotes(edgeId)}').drop()`).then( return this.submitToBackend(`g.E('${GraphUtil.escapeSingleQuotes(edgeId)}').drop()`).then(
() => { () => {
let graphData = this.originalGraphData; const graphData = this.originalGraphData;
graphData.removeEdge(edgeId, false); graphData.removeEdge(edgeId, false);
this.updateGraphData(graphData, this.state.igraphConfig); this.updateGraphData(graphData, this.state.igraphConfig);
}, },
@@ -826,10 +825,14 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
return false; return false;
} }
let vertices: any[] = data; const vertices: any[] = data;
if (vertices.length > 0) { if (vertices.length > 0) {
let v0 = vertices[0]; const v0 = vertices[0];
if (!v0.hasOwnProperty("id") || !v0.hasOwnProperty("type") || v0.type !== "vertex") { if (
!Object.prototype.hasOwnProperty.call(v0, "id") ||
!Object.prototype.hasOwnProperty.call(v0, "type") ||
v0.type !== "vertex"
) {
return false; return false;
} }
} }
@@ -837,7 +840,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
} }
public processGremlinQueryResults(result: GremlinClient.GremlinRequestResult): void { public processGremlinQueryResults(result: GremlinClient.GremlinRequestResult): void {
const data = result.data as any; const data = result.data as GraphData.GremlinVertex[];
this.setFilterQueryStatus(FilterQueryStatus.GraphEmptyResult); this.setFilterQueryStatus(FilterQueryStatus.GraphEmptyResult);
if (data === null) { if (data === null) {
@@ -927,13 +930,13 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
throw { title: err }; throw { title: err };
} }
if (vertices == null || vertices.length < 1) { if (vertices === null || vertices.length < 1) {
const err = "Failed to create vertex (no vertex in response)"; const err = "Failed to create vertex (no vertex in response)";
GraphExplorer.reportToConsole(ConsoleDataType.Error, err, vertices); GraphExplorer.reportToConsole(ConsoleDataType.Error, err, vertices);
throw { title: err }; throw { title: err };
} }
let vertex = vertices[0]; const vertex = vertices[0];
const graphData = this.originalGraphData; const graphData = this.originalGraphData;
graphData.addVertex(vertex); graphData.addVertex(vertex);
this.updateGraphData(graphData, this.state.igraphConfig); this.updateGraphData(graphData, this.state.igraphConfig);
@@ -1022,7 +1025,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
this.gremlinClient.destroy(); this.gremlinClient.destroy();
} }
public componentDidMount(): void { public componentDidMount(): void {
if (this.props.onLoadStartKey != null && this.props.onLoadStartKey != undefined) { if (this.props.onLoadStartKey !== null && this.props.onLoadStartKey !== undefined) {
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.Tab, Action.Tab,
{ {
@@ -1082,7 +1085,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
public static reportToConsole(type: ConsoleDataType.Info, msg: string, ...errorData: any[]): void; public static reportToConsole(type: ConsoleDataType.Info, msg: string, ...errorData: any[]): void;
public static reportToConsole(type: ConsoleDataType.Error, msg: string, ...errorData: any[]): void; public static reportToConsole(type: ConsoleDataType.Error, msg: string, ...errorData: any[]): void;
public static reportToConsole(type: ConsoleDataType, msg: string, ...errorData: any[]): void | (() => void) { public static reportToConsole(type: ConsoleDataType, msg: string, ...errorData: any[]): void | (() => void) {
let errorDataStr: string = ""; let errorDataStr = "";
if (errorData && errorData.length > 0) { if (errorData && errorData.length > 0) {
console.error(msg, errorData); console.error(msg, errorData);
errorDataStr = ": " + JSON.stringify(errorData); errorDataStr = ": " + JSON.stringify(errorData);
@@ -1161,12 +1164,15 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
)}"` )}"`
).then( ).then(
(documents: DataModels.DocumentId[]) => { (documents: DataModels.DocumentId[]) => {
$.each(documents, (index: number, doc: any) => { $.each(
documents,
(index: number, doc: { _graph_icon_property_value: string; icon: string; format: string }) => {
newIconsMap[doc["_graph_icon_property_value"]] = { newIconsMap[doc["_graph_icon_property_value"]] = {
data: doc["icon"], data: doc["icon"],
format: doc["format"], format: doc["format"],
}; };
}); }
);
// Update graph configuration // Update graph configuration
this.setState({ this.setState({
@@ -1223,8 +1229,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
const key = this.state.igraphConfig.nodeCaption; const key = this.state.igraphConfig.nodeCaption;
return $.map( return $.map(
this.state.rootMap, this.state.rootMap,
(value: any, index: number): LeftPane.CaptionId => { (value: any): LeftPane.CaptionId => {
let result = GraphData.GraphData.getNodePropValue(value, key); const result = GraphData.GraphData.getNodePropValue(value, key);
return { return {
caption: result !== undefined ? result : value.id, caption: result !== undefined ? result : value.id,
id: value.id, id: value.id,
@@ -1237,7 +1243,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
* Selecting a root node means * Selecting a root node means
* @param node * @param node
*/ */
private selectRootNode(id: string): Q.Promise<any> { private selectRootNode(id: string): Q.Promise<unknown> {
if (!this.d3ForceGraph) { if (!this.d3ForceGraph) {
console.warn("Attempting to reset zoom, but d3ForceGraph not initialized, yet."); console.warn("Attempting to reset zoom, but d3ForceGraph not initialized, yet.");
} else { } else {
@@ -1282,7 +1288,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
this.collectNodeProperties(this.originalGraphData.vertices); this.collectNodeProperties(this.originalGraphData.vertices);
this.updatePropertiesPane(id); this.updatePropertiesPane(id);
}, },
(reason: any) => { (reason: string) => {
GraphExplorer.reportToConsole(ConsoleDataType.Error, `Failed to select root node. Reason:${reason}`); GraphExplorer.reportToConsole(ConsoleDataType.Error, `Failed to select root node. Reason:${reason}`);
} }
); );
@@ -1349,10 +1355,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
private getPkIdFromVertex(v: GraphData.GremlinVertex): string { private getPkIdFromVertex(v: GraphData.GremlinVertex): string {
if ( if (
this.props.collectionPartitionKeyProperty && this.props.collectionPartitionKeyProperty &&
v.hasOwnProperty("properties") && Object.prototype.hasOwnProperty.call(v, "properties") &&
v.properties.hasOwnProperty(this.props.collectionPartitionKeyProperty) && Object.prototype.hasOwnProperty.call(v.properties, this.props.collectionPartitionKeyProperty) &&
v.properties[this.props.collectionPartitionKeyProperty].length > 0 && v.properties[this.props.collectionPartitionKeyProperty].length > 0 &&
v.properties[this.props.collectionPartitionKeyProperty][0].hasOwnProperty("value") Object.prototype.hasOwnProperty.call(v.properties[this.props.collectionPartitionKeyProperty][0], "value")
) { ) {
const pk = v.properties[this.props.collectionPartitionKeyProperty][0].value; const pk = v.properties[this.props.collectionPartitionKeyProperty][0].value;
return GraphExplorer.generatePkIdPair(pk, v.id); return GraphExplorer.generatePkIdPair(pk, v.id);
@@ -1370,8 +1376,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
private getPkIdFromNodeData(v: GraphHighlightedNodeData): string { private getPkIdFromNodeData(v: GraphHighlightedNodeData): string {
if ( if (
this.props.collectionPartitionKeyProperty && this.props.collectionPartitionKeyProperty &&
v.hasOwnProperty("properties") && Object.prototype.hasOwnProperty.call(v, "properties") &&
v.properties.hasOwnProperty(this.props.collectionPartitionKeyProperty) Object.prototype.hasOwnProperty.call(v.properties, this.props.collectionPartitionKeyProperty)
) { ) {
const pk = v.properties[this.props.collectionPartitionKeyProperty]; const pk = v.properties[this.props.collectionPartitionKeyProperty];
return GraphExplorer.generatePkIdPair(pk[0] as PartitionKeyValueType, v.id); return GraphExplorer.generatePkIdPair(pk[0] as PartitionKeyValueType, v.id);
@@ -1388,14 +1394,14 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
* @return id * @return id
*/ */
public static getPkIdFromDocumentId(d: DataModels.DocumentId, collectionPartitionKeyProperty: string): string { public static getPkIdFromDocumentId(d: DataModels.DocumentId, collectionPartitionKeyProperty: string): string {
let { id } = d; const { id } = d;
if (typeof id !== "string") { if (typeof id !== "string") {
const error = `Vertex id is not a string: ${JSON.stringify(id)}.`; const error = `Vertex id is not a string: ${JSON.stringify(id)}.`;
logConsoleError(error); logConsoleError(error);
throw new Error(error); throw new Error(error);
} }
if (collectionPartitionKeyProperty && d.hasOwnProperty(collectionPartitionKeyProperty)) { if (collectionPartitionKeyProperty && Object.prototype.hasOwnProperty.call(d, collectionPartitionKeyProperty)) {
let pk = (d as any)[collectionPartitionKeyProperty]; let pk = (d as any)[collectionPartitionKeyProperty];
if (typeof pk !== "string" && typeof pk !== "number" && typeof pk !== "boolean") { if (typeof pk !== "string" && typeof pk !== "number" && typeof pk !== "boolean") {
if (Array.isArray(pk) && pk.length > 0) { if (Array.isArray(pk) && pk.length > 0) {
@@ -1425,7 +1431,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
}"] AS p FROM c WHERE NOT IS_DEFINED(c._isEdge)`; }"] AS p FROM c WHERE NOT IS_DEFINED(c._isEdge)`;
return this.executeNonPagedDocDbQuery(q).then( return this.executeNonPagedDocDbQuery(q).then(
(documents: DataModels.DocumentId[]) => { (documents: DataModels.DocumentId[]) => {
let possibleVertices = [] as PossibleVertex[]; const possibleVertices = [] as PossibleVertex[];
$.each(documents, (index: number, item: any) => { $.each(documents, (index: number, item: any) => {
if (highlightedNodeId && item.id === highlightedNodeId) { if (highlightedNodeId && item.id === highlightedNodeId) {
// Exclude highlighed node in the list // Exclude highlighed node in the list
@@ -1439,7 +1445,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
caption: item.p, caption: item.p,
}); });
} else { } else {
if (item.hasOwnProperty("p")) { if (Object.prototype.hasOwnProperty.call(item, "p")) {
possibleVertices.push({ possibleVertices.push({
value: item.id, value: item.id,
caption: item.p[0]["_value"], caption: item.p[0]["_value"],
@@ -1462,17 +1468,17 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
* @param addedEdges * @param addedEdges
* @return promise when done * @return promise when done
*/ */
private editGraphEdges(editedEdges: EditedEdges): Q.Promise<any> { private editGraphEdges(editedEdges: EditedEdges): Q.Promise<unknown> {
let promises = []; const promises = [];
// Drop edges // Drop edges
for (let i = 0; i < editedEdges.droppedIds.length; i++) { for (let i = 0; i < editedEdges.droppedIds.length; i++) {
let id = editedEdges.droppedIds[i]; const id = editedEdges.droppedIds[i];
promises.push(this.removeEdge(id)); promises.push(this.removeEdge(id));
} }
// Add edges // Add edges
for (let i = 0; i < editedEdges.addedEdges.length; i++) { for (let i = 0; i < editedEdges.addedEdges.length; i++) {
let e = editedEdges.addedEdges[i]; const e = editedEdges.addedEdges[i];
promises.push( promises.push(
this.createNewEdge(e).then(() => { this.createNewEdge(e).then(() => {
// Reload neighbors in case we linked to a vertex that isn't loaded in the graph // Reload neighbors in case we linked to a vertex that isn't loaded in the graph
@@ -1525,7 +1531,9 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
/** /**
* For unit testing purposes * For unit testing purposes
*/ */
public onGraphUpdated(timestamp: number): void {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public onGraphUpdated(_timestamp: number): void {}
/** /**
* Get node properties for styling purposes. Result is the union of all properties of all nodes. * Get node properties for styling purposes. Result is the union of all properties of all nodes.
@@ -1533,17 +1541,17 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
private collectNodeProperties(vertices: GraphData.GremlinVertex[]) { private collectNodeProperties(vertices: GraphData.GremlinVertex[]) {
const props = {} as any; // Hashset const props = {} as any; // Hashset
$.each(vertices, (index: number, item: GraphData.GremlinVertex) => { $.each(vertices, (index: number, item: GraphData.GremlinVertex) => {
for (var p in item) { for (const p in item) {
// DocDB: Exclude type because it's always 'vertex' // DocDB: Exclude type because it's always 'vertex'
if (p !== "type" && typeof (item as any)[p] === "string") { if (p !== "type" && typeof (item as any)[p] === "string") {
props[p] = true; props[p] = true;
} }
} }
// Inspect properties // Inspect properties
if (item.hasOwnProperty("properties")) { if (Object.prototype.hasOwnProperty.call(item, "properties")) {
// TODO This is DocDB-graph specific // TODO This is DocDB-graph specific
// Assume each property value is [{value:... }] // Assume each property value is [{value:... }]
for (var f in item.properties) { for (const f in item.properties) {
props[f] = true; props[f] = true;
} }
} }
@@ -1570,21 +1578,21 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
return; return;
} }
let data = this.originalGraphData.getVertexById(id); const data = this.originalGraphData.getVertexById(id);
// A bit of translation to make it easier to display // A bit of translation to make it easier to display
let props: { [id: string]: ViewModels.GremlinPropertyValueType[] } = {}; const props: { [id: string]: ViewModels.GremlinPropertyValueType[] } = {};
for (let p in data.properties) { for (const p in data.properties) {
props[p] = data.properties[p].map((gremlinProperty) => gremlinProperty.value); props[p] = data.properties[p].map((gremlinProperty) => gremlinProperty.value);
} }
// update neighbors // update neighbors
let sources: NeighborVertexBasicInfo[] = []; const sources: NeighborVertexBasicInfo[] = [];
let targets: NeighborVertexBasicInfo[] = []; const targets: NeighborVertexBasicInfo[] = [];
this.props.onResetDefaultGraphConfigValues(); this.props.onResetDefaultGraphConfigValues();
let nodeCaption = this.state.igraphConfigUiData.nodeCaptionChoice; const nodeCaption = this.state.igraphConfigUiData.nodeCaptionChoice;
this.updateSelectedNodeNeighbors(data.id, nodeCaption, sources, targets); this.updateSelectedNodeNeighbors(data.id, nodeCaption, sources, targets);
let sData: GraphHighlightedNodeData = { const sData: GraphHighlightedNodeData = {
id: data.id, id: data.id,
label: data.label, label: data.label,
properties: props, properties: props,
@@ -1611,16 +1619,16 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
targets: NeighborVertexBasicInfo[] targets: NeighborVertexBasicInfo[]
): void { ): void {
// update neighbors // update neighbors
let gd = this.originalGraphData; const gd = this.originalGraphData;
let v = gd.getVertexById(id); const v = gd.getVertexById(id);
// Clear the array while keeping the references // Clear the array while keeping the references
sources.length = 0; sources.length = 0;
targets.length = 0; targets.length = 0;
let possibleEdgeLabels = {} as any; // Collect all edge labels in a hashset const possibleEdgeLabels = {} as any; // Collect all edge labels in a hashset
for (let p in v.inE) { for (const p in v.inE) {
possibleEdgeLabels[p] = true; possibleEdgeLabels[p] = true;
const edges = v.inE[p]; const edges = v.inE[p];
$.each(edges, (index: number, edge: GraphData.GremlinShortInEdge) => { $.each(edges, (index: number, edge: GraphData.GremlinShortInEdge) => {
@@ -1629,7 +1637,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
// If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet // If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet
return; return;
} }
let caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string; const caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
sources.push({ sources.push({
name: caption, name: caption,
id: neighborId, id: neighborId,
@@ -1639,7 +1647,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
}); });
} }
for (let p in v.outE) { for (const p in v.outE) {
possibleEdgeLabels[p] = true; possibleEdgeLabels[p] = true;
const edges = v.outE[p]; const edges = v.outE[p];
$.each(edges, (index: number, edge: GraphData.GremlinShortOutEdge) => { $.each(edges, (index: number, edge: GraphData.GremlinShortOutEdge) => {
@@ -1648,7 +1656,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
// If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet // If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet
return; return;
} }
let caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string; const caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
targets.push({ targets.push({
name: caption, name: caption,
id: neighborId, id: neighborId,
@@ -1660,7 +1668,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
this.setState({ this.setState({
possibleEdgeLabels: Object.keys(possibleEdgeLabels).map( possibleEdgeLabels: Object.keys(possibleEdgeLabels).map(
(value: string, index: number, array: string[]): InputTypeaheadComponent.Item => { (value: string): InputTypeaheadComponent.Item => {
return { caption: value, value: value }; return { caption: value, value: value };
} }
), ),
@@ -1681,20 +1689,20 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
return; return;
} }
let updatedVertex = vertices[0]; const updatedVertex = vertices[0];
if (this.originalGraphData.hasVertexId(updatedVertex.id)) { if (this.originalGraphData.hasVertexId(updatedVertex.id)) {
let currentVertex = this.originalGraphData.getVertexById(updatedVertex.id); const currentVertex = this.originalGraphData.getVertexById(updatedVertex.id);
// Copy updated properties // Copy updated properties
if (currentVertex.hasOwnProperty("properties")) { if (Object.prototype.hasOwnProperty.call(currentVertex, "properties")) {
delete currentVertex["properties"]; delete currentVertex["properties"];
} }
for (var p in updatedVertex) { for (const p in updatedVertex) {
(currentVertex as any)[p] = updatedVertex[p]; (currentVertex as any)[p] = updatedVertex[p];
} }
} }
// TODO This kind of assumes saveVertexProperty is done from property panes. // TODO This kind of assumes saveVertexProperty is done from property panes.
let hn = this.state.highlightedNode; const hn = this.state.highlightedNode;
if (hn && hn.id === updatedVertex.id) { if (hn && hn.id === updatedVertex.id) {
this.updatePropertiesPane(hn.id); this.updatePropertiesPane(hn.id);
} }
@@ -1708,7 +1716,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
igraphConfig?: IGraphConfig igraphConfig?: IGraphConfig
) { ) {
this.originalGraphData = graphData; this.originalGraphData = graphData;
let gd = JSON.parse(JSON.stringify(this.originalGraphData)); const gd = JSON.parse(JSON.stringify(this.originalGraphData));
if (!this.d3ForceGraph) { if (!this.d3ForceGraph) {
console.warn("Attempting to update graph, but d3ForceGraph not initialized, yet."); console.warn("Attempting to update graph, but d3ForceGraph not initialized, yet.");
return; return;
@@ -1873,7 +1881,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
promise promise
.then((result: GremlinClient.GremlinRequestResult) => this.processGremlinQueryResults(result)) .then((result: GremlinClient.GremlinRequestResult) => this.processGremlinQueryResults(result))
.catch((error: any) => { .catch((error: Error) => {
const errorMsg = `Failed to process query result: ${getErrorMessage(error)}`; const errorMsg = `Failed to process query result: ${getErrorMessage(error)}`;
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg); GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
this.setState({ this.setState({

View File

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

View File

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

View File

@@ -72,7 +72,7 @@ export class NodePropertiesComponent extends React.Component<
super(props); super(props);
this.state = { this.state = {
editedProperties: { editedProperties: {
pkId: null, pkId: undefined,
readOnlyProperties: [], readOnlyProperties: [],
existingProperties: [], existingProperties: [],
addedProperties: [], addedProperties: [],
@@ -98,15 +98,12 @@ export class NodePropertiesComponent extends React.Component<
}; };
} }
public static getDerivedStateFromProps( public static getDerivedStateFromProps(props: NodePropertiesComponentProps): Partial<NodePropertiesComponentState> {
props: NodePropertiesComponentProps,
state: NodePropertiesComponentState
): Partial<NodePropertiesComponentState> {
if (props.viewMode !== Mode.READONLY_PROP) { if (props.viewMode !== Mode.READONLY_PROP) {
return { isDeleteConfirm: false }; return { isDeleteConfirm: false };
} }
return null; return undefined;
} }
public render(): JSX.Element { public render(): JSX.Element {
@@ -138,10 +135,10 @@ export class NodePropertiesComponent extends React.Component<
* @param value * @param value
*/ */
private static getTypeOption(value: any): ViewModels.InputPropertyValueTypeString { private static getTypeOption(value: any): ViewModels.InputPropertyValueTypeString {
if (value == null) { if (value === undefined) {
return "null"; return "null";
} }
let type = typeof value; const type = typeof value;
switch (type) { switch (type) {
case "number": case "number":
case "boolean": case "boolean":
@@ -172,10 +169,9 @@ export class NodePropertiesComponent extends React.Component<
]; ];
const existingProps: ViewModels.InputProperty[] = []; const existingProps: ViewModels.InputProperty[] = [];
if (this.props.node.hasOwnProperty("properties")) { if (this.props.node.hasOwnProperty("properties")) {
const hProps = this.props.node["properties"]; const hProps = this.props.node["properties"];
for (let p in hProps) { for (const p in hProps) {
const propValues = hProps[p]; const propValues = hProps[p];
(p === partitionKeyProperty ? readOnlyProps : existingProps).push({ (p === partitionKeyProperty ? readOnlyProps : existingProps).push({
key: p, key: p,
@@ -437,7 +433,7 @@ export class NodePropertiesComponent extends React.Component<
</div> </div>
); );
} else { } else {
return null; return undefined;
} }
} }

View File

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

View File

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

View File

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

View File

@@ -596,7 +596,7 @@ function createManageGitHubAccountButton(container: Explorer): CommandButtonComp
return { return {
iconSrc: GitHubIcon, iconSrc: GitHubIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => onCommandClick: () => {
useSidePanel useSidePanel
.getState() .getState()
.openSidePanel( .openSidePanel(
@@ -606,7 +606,8 @@ function createManageGitHubAccountButton(container: Explorer): CommandButtonComp
gitHubClientProp={container.notebookManager.gitHubClient} gitHubClientProp={container.notebookManager.gitHubClient}
junoClientProp={junoClient} junoClientProp={junoClient}
/> />
), );
},
commandButtonLabel: label, commandButtonLabel: label,
hasPopup: false, hasPopup: false,
disabled: false, disabled: false,

View File

@@ -13,6 +13,7 @@ import { StyleConstants } from "../../../Common/Constants";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer";
import { ConnectionStatus } from "./ConnectionStatusComponent"; import { ConnectionStatus } from "./ConnectionStatusComponent";
import { MemoryTracker } from "./MemoryTrackerComponent"; import { MemoryTracker } from "./MemoryTrackerComponent";
@@ -203,9 +204,9 @@ export const createMemoryTracker = (key: string): ICommandBarItemProps => {
}; };
}; };
export const createConnectionStatus = (key: string): ICommandBarItemProps => { export const createConnectionStatus = (container: Explorer, key: string): ICommandBarItemProps => {
return { return {
key, key,
onRender: () => <ConnectionStatus />, onRender: () => <ConnectionStatus container={container} />,
}; };
}; };

View File

@@ -3,77 +3,182 @@
.connectionStatusContainer { .connectionStatusContainer {
cursor: default; cursor: default;
align-items: center; align-items: center;
margin: 0 9px;
border: 1px; border: 1px;
min-height: 44px; min-height: 44px;
> span { > span {
padding-right: 12px; padding-right: 12px;
font-size: 13px; font-size: 12px;
font-family: @DataExplorerFont; font-family: @DataExplorerFont;
color: @DefaultFontColor; color: @DefaultFontColor;
} }
&:focus{
outline: 0px;
} }
.connectionStatusFailed{
color: #bd1919;
} }
.ring-container { .commandReactBtn {
&:hover {
background-color: rgb(238, 247, 255);
color: rgb(32, 31, 30);
cursor: pointer;
}
&:focus{
outline: 1px dashed #605e5c;
}
}
.connectedReactBtn {
&:hover {
background-color: rgb(238, 247, 255);
color: rgb(32, 31, 30);
cursor: pointer;
}
&:focus{
outline: 0px;
}
}
.connectIcon{
margin: 0px 4px;
height: 18px;
width: 18px;
color: rgb(0, 120, 212);
}
.status {
position: relative; position: relative;
display: block;
margin-right: 8px;
width: 1em;
height: 1em;
font-size: 9px!important;
padding: 0px!important;
border-radius: 0.5em;
} }
.ringringGreen { .status::before,
border: 3px solid green; .status::after {
border-radius: 30px;
height: 18px;
width: 18px;
position: absolute; position: absolute;
margin: .4285em 0em 0em 0.07477em; content: "";
animation: pulsate 3s ease-out;
animation-iteration-count: infinite;
opacity: 0.0
} }
.ringringYellow{
border: 3px solid #ffbf00; .status::before {
border-radius: 30px; top: 0;
height: 18px; left: 0;
width: 18px; width: 1em;
position: absolute; height: 1em;
margin: .4285em 0em 0em 0.07477em; background-color: rgba(#fff, 0.1);
animation: pulsate 3s ease-out; border-radius: 100%;
animation-iteration-count: infinite; opacity: 1;
opacity: 0.0 transform: translate3d(0, 0, 0) scale(0);
} }
.ringringRed{
border: 3px solid #bd1919; .connected{
border-radius: 30px; background-color: green;
height: 18px; box-shadow:
width: 18px; 0 0 0 0em rgba(green, 0),
position: absolute; 0em 0.05em 0.1em rgba(#000000, 0.2);
margin: .4285em 0em 0em 0.07477em; transform: translate3d(0, 0, 0) scale(1);
animation: pulsate 3s ease-out;
animation-iteration-count: infinite;
opacity: 0.0
} }
@keyframes pulsate { .connecting{
0% {-webkit-transform: scale(0.1, 0.1); opacity: 0.0;} background-color:#ffbf00;
15% {opacity: 0.8;} box-shadow:
25% {opacity: 0.6;} 0 0 0 0em rgba(#ffbf00, 0),
45% {opacity: 0.4;} 0em 0.05em 0.1em rgba(#000000, 0.2);
70% {opacity: 0.3;} transform: translate3d(0, 0, 0) scale(1);
100% {-webkit-transform: scale(.7, .7); opacity: 0.1;}
} }
.locationGreenDot{ .failed{
font-size: 20px; background-color:#bd1919;
margin-right: 0.07em; box-shadow:
color: green; 0 0 0 0em rgba(#bd1919, 0),
0em 0.05em 0.1em rgba(#000000, 0.2);
transform: translate3d(0, 0, 0) scale(1);
} }
.locationYellowDot{
font-size: 20px; .status.connecting.is-animating {
margin-right: 0.07em; animation: status-outer-connecting 3000ms infinite;
color: #ffbf00; }
.status.failed.is-animating {
animation: status-outer-failed 3000ms infinite;
}
.status.connected.is-animating {
animation: status-outer-connected 3000ms infinite;
}
@keyframes status-outer-connected {
0% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em #008000, 0em 0.05em 0.1em rgba(0, 0, 0, 0.2);
}
20% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0.6), 0em 0.05em 0.1em rgba(0, 0, 0, 0.5);
}
40% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0.5), 0em 0.05em 0.1em rgba(0, 0, 0, 0.4);
}
60% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0.3), 0em 0.05em 0.1em rgba(0, 0, 0, 0.3);
}
80% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0.5em rgba(0, 128, 0, 0.1), 0em 0.05em 0.1em rgba(0, 0, 0, 0.1);
}
85% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0), 0em 0.05em 0.1em rgba(0, 0, 0, 0);
}
}
@keyframes status-outer-failed {
0% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em #bd1919, 0em 0.05em 0.1em rgba(0, 0, 0, 0.2);
}
20% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em #c52d2d, 0em 0.05em 0.1em rgba(0, 0, 0, 0.5);
}
40% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em #b47b7b, 0em 0.05em 0.1em rgba(0, 0, 0, 0.4);
}
60% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0.3), 0em 0.05em 0.1em rgba(0, 0, 0, 0.3);
}
80% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0.5em rgba(0, 128, 0, 0.1), 0em 0.05em 0.1em rgba(0, 0, 0, 0.1);
}
85% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0), 0em 0.05em 0.1em rgba(0, 0, 0, 0);
}
}
@keyframes status-outer-connecting {
0% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em #ffbf00, 0em 0.05em 0.1em rgba(0, 0, 0, 0.2);
}
20% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em #f0dfad, 0em 0.05em 0.1em rgba(0, 0, 0, 0.5);
}
40% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em rgba(198, 243, 198, 0.5), 0em 0.05em 0.1em rgba(0, 0, 0, 0.4);
}
60% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em rgba(213, 241, 213, 0.3), 0em 0.05em 0.1em rgba(0, 0, 0, 0.3);
}
80% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0.5em rgba(0, 128, 0, 0.1), 0em 0.05em 0.1em rgba(0, 0, 0, 0.1);
}
85% {
transform: translate3d(0, 0, 0) scale(1);
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0), 0em 0.05em 0.1em rgba(0, 0, 0, 0);
} }
.locationRedDot{
font-size: 20px;
margin-right: 0.07em;
color: #bd1919;
} }

View File

@@ -1,17 +1,21 @@
import { Icon, ProgressIndicator, Stack, TooltipHost } from "@fluentui/react"; import { Icon, ProgressIndicator, Stack, TooltipHost } from "@fluentui/react";
import { ActionButton } from "@fluentui/react/lib/Button";
import * as React from "react"; import * as React from "react";
import { ConnectionStatusType } from "../../../Common/Constants"; import "../../../../less/hostedexplorer.less";
import { ConnectionStatusType, Notebook } from "../../../Common/Constants";
import Explorer from "../../Explorer";
import { useNotebook } from "../../Notebook/useNotebook"; import { useNotebook } from "../../Notebook/useNotebook";
import "../CommandBar/ConnectionStatusComponent.less"; import "../CommandBar/ConnectionStatusComponent.less";
interface Props {
export const ConnectionStatus: React.FC = (): JSX.Element => { container: Explorer;
}
export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Element => {
const [second, setSecond] = React.useState("00"); const [second, setSecond] = React.useState("00");
const [minute, setMinute] = React.useState("00"); const [minute, setMinute] = React.useState("00");
const [isActive, setIsActive] = React.useState(false); const [isActive, setIsActive] = React.useState(false);
const [counter, setCounter] = React.useState(0); const [counter, setCounter] = React.useState(0);
const [statusColor, setStatusColor] = React.useState("locationYellowDot"); const [statusColor, setStatusColor] = React.useState("");
const [statusColorAnimation, setStatusColorAnimation] = React.useState("ringringYellow"); const [toolTipContent, setToolTipContent] = React.useState("Connect to temporary workspace.");
const toolTipContent = "Hosted runtime status.";
React.useEffect(() => { React.useEffect(() => {
let intervalId: NodeJS.Timeout; let intervalId: NodeJS.Timeout;
@@ -39,34 +43,65 @@ export const ConnectionStatus: React.FC = (): JSX.Element => {
}; };
const connectionInfo = useNotebook((state) => state.connectionInfo); const connectionInfo = useNotebook((state) => state.connectionInfo);
if (!connectionInfo) { const memoryUsageInfo = useNotebook((state) => state.memoryUsageInfo);
return <></>;
} const totalGB = memoryUsageInfo ? memoryUsageInfo.totalKB / Notebook.memoryGuageToGB : 0;
if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connecting && isActive === false) { const usedGB = totalGB > 0 ? totalGB - memoryUsageInfo.freeKB / Notebook.memoryGuageToGB : 0;
setIsActive(true);
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connected && isActive === true) { if (
stopTimer(); connectionInfo &&
setStatusColor("locationGreenDot"); (connectionInfo.status === ConnectionStatusType.Connect || connectionInfo.status === ConnectionStatusType.ReConnect)
setStatusColorAnimation("ringringGreen"); ) {
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Failed && isActive === true) {
stopTimer();
setStatusColor("locationRedDot");
setStatusColorAnimation("ringringRed");
}
return ( return (
<ActionButton className="commandReactBtn" onClick={() => container.allocateContainer()}>
<TooltipHost content={toolTipContent}> <TooltipHost content={toolTipContent}>
<Stack className="connectionStatusContainer" horizontal> <Stack className="connectionStatusContainer" horizontal>
<div className="ring-container"> <Icon iconName="ConnectVirtualMachine" className="connectIcon" />
<div className={statusColorAnimation}></div> <span>{connectionInfo.status}</span>
<Icon iconName="LocationDot" className={statusColor} /> </Stack>
</div> </TooltipHost>
</ActionButton>
);
}
if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connecting && isActive === false) {
setIsActive(true);
setStatusColor("status connecting is-animating");
setToolTipContent("Connecting to temporary workspace.");
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connected && isActive === true) {
stopTimer();
setStatusColor("status connected is-animating");
setToolTipContent("Connected to temporary workspace.");
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Failed && isActive === true) {
stopTimer();
setStatusColor("status failed is-animating");
setToolTipContent("Click here to Reconnect to temporary workspace.");
}
return (
<ActionButton
className={connectionInfo.status === ConnectionStatusType.Failed ? "commandReactBtn" : "connectedReactBtn"}
onClick={(e: React.MouseEvent<HTMLSpanElement>) =>
connectionInfo.status === ConnectionStatusType.Failed ? container.allocateContainer() : e.preventDefault()
}
>
<TooltipHost content={toolTipContent}>
<Stack className="connectionStatusContainer" horizontal>
<i className={statusColor}></i>
<span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}> <span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}>
{connectionInfo.status} {connectionInfo.status}
</span> </span>
{connectionInfo.status === ConnectionStatusType.Connecting && isActive && ( {connectionInfo.status === ConnectionStatusType.Connecting && isActive && (
<ProgressIndicator description={minute + ":" + second} /> <ProgressIndicator description={minute + ":" + second} />
)} )}
{connectionInfo.status === ConnectionStatusType.Connected && !isActive && (
<ProgressIndicator
className={usedGB / totalGB > 0.8 ? "lowMemory" : ""}
description={usedGB.toFixed(1) + " of " + totalGB.toFixed(1) + " GB"}
percentComplete={usedGB / totalGB}
/>
)}
</Stack> </Stack>
</TooltipHost> </TooltipHost>
</ActionButton>
); );
}; };

View File

@@ -103,7 +103,6 @@ export class NotificationConsoleComponent extends React.Component<
onClick={() => this.expandCollapseConsole()} onClick={() => this.expandCollapseConsole()}
onKeyDown={(event: React.KeyboardEvent<HTMLDivElement>) => this.onExpandCollapseKeyPress(event)} onKeyDown={(event: React.KeyboardEvent<HTMLDivElement>) => this.onExpandCollapseKeyPress(event)}
tabIndex={0} tabIndex={0}
role="button"
> >
<div className="statusBar"> <div className="statusBar">
<span className="dataTypeIcons"> <span className="dataTypeIcons">
@@ -163,7 +162,7 @@ export class NotificationConsoleComponent extends React.Component<
onKeyDown={(event: React.KeyboardEvent<HTMLSpanElement>) => this.onClearNotificationsKeyPress(event)} onKeyDown={(event: React.KeyboardEvent<HTMLSpanElement>) => this.onClearNotificationsKeyPress(event)}
tabIndex={0} tabIndex={0}
> >
<img src={ClearIcon} alt="clear notifications icon" /> <img src={ClearIcon} alt="clear notifications image" />
Clear Notifications Clear Notifications
</span> </span>
</div> </div>

View File

@@ -9,7 +9,6 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
id="notificationConsoleHeader" id="notificationConsoleHeader"
onClick={[Function]} onClick={[Function]}
onKeyDown={[Function]} onKeyDown={[Function]}
role="button"
tabIndex={0} tabIndex={0}
> >
<div <div
@@ -151,7 +150,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
tabIndex={0} tabIndex={0}
> >
<img <img
alt="clear notifications icon" alt="clear notifications image"
src="" src=""
/> />
Clear Notifications Clear Notifications
@@ -174,7 +173,6 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
id="notificationConsoleHeader" id="notificationConsoleHeader"
onClick={[Function]} onClick={[Function]}
onKeyDown={[Function]} onKeyDown={[Function]}
role="button"
tabIndex={0} tabIndex={0}
> >
<div <div
@@ -318,7 +316,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
tabIndex={0} tabIndex={0}
> >
<img <img
alt="clear notifications icon" alt="clear notifications image"
src="" src=""
/> />
Clear Notifications Clear Notifications

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { CellId, CellType, ImmutableNotebook } from "@nteract/commutable"; import { CellId, CellType, ImmutableNotebook } from "@nteract/commutable";
// Vendor modules // Vendor modules
import { import {
@@ -30,6 +31,19 @@ export interface NotebookComponentBootstrapperOptions {
contentRef: ContentRef; contentRef: ContentRef;
} }
interface IWrapModel {
name: string;
path: string;
last_modified: Date;
created: string;
content: unknown;
format: string;
mimetype: unknown;
size: number;
writeable: boolean;
type: string;
}
export class NotebookComponentBootstrapper { export class NotebookComponentBootstrapper {
public contentRef: ContentRef; public contentRef: ContentRef;
protected renderExtraComponent: () => JSX.Element; protected renderExtraComponent: () => JSX.Element;
@@ -41,7 +55,7 @@ export class NotebookComponentBootstrapper {
this.contentRef = options.contentRef; this.contentRef = options.contentRef;
} }
protected static wrapModelIntoContent(name: string, path: string, content: any) { protected static wrapModelIntoContent(name: string, path: string, content: unknown): IWrapModel {
return { return {
name, name,
path, path,
@@ -49,7 +63,7 @@ export class NotebookComponentBootstrapper {
created: "", created: "",
content, content,
format: "json", format: "json",
mimetype: null as any, mimetype: undefined,
size: 0, size: 0,
writeable: false, writeable: false,
type: "notebook", type: "notebook",
@@ -85,7 +99,7 @@ export class NotebookComponentBootstrapper {
}; };
} }
public setContent(name: string, content: any): void { public setContent(name: string, content: unknown): void {
this.getStore().dispatch( this.getStore().dispatch(
actions.fetchContentFulfilled({ actions.fetchContentFulfilled({
filepath: undefined, filepath: undefined,
@@ -270,7 +284,6 @@ export class NotebookComponentBootstrapper {
public isContentDirty(): boolean { public isContentDirty(): boolean {
const content = selectors.content(this.getStore().getState(), { contentRef: this.contentRef }); const content = selectors.content(this.getStore().getState(), { contentRef: this.contentRef });
if (!content) { if (!content) {
console.log("No error");
return false; return false;
} }

View File

@@ -2,12 +2,15 @@
* Notebook container related stuff * Notebook container related stuff
*/ */
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import { ConnectionStatusType } from "../../Common/Constants";
import { getErrorMessage } from "../../Common/ErrorHandlingUtils"; import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
import * as Logger from "../../Common/Logger"; import * as Logger from "../../Common/Logger";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import { ContainerConnectionInfo } from "../../Contracts/DataModels";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { createOrUpdate, destroy } from "../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces"; import { createOrUpdate, destroy } from "../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { NotebookUtil } from "./NotebookUtil";
import { useNotebook } from "./useNotebook"; import { useNotebook } from "./useNotebook";
export class NotebookContainerClient { export class NotebookContainerClient {
@@ -42,7 +45,7 @@ export class NotebookContainerClient {
}, delayMs); }, delayMs);
} }
private async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> { public async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> {
const notebookServerInfo = useNotebook.getState().notebookServerInfo; const notebookServerInfo = useNotebook.getState().notebookServerInfo;
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) { if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
const error = "No server endpoint detected"; const error = "No server endpoint detected";
@@ -75,6 +78,12 @@ export class NotebookContainerClient {
freeKB: memoryUsageInfo.free, freeKB: memoryUsageInfo.free,
}; };
} }
} else if (NotebookUtil.isPhoenixEnabled()) {
const connectionStatus: ContainerConnectionInfo = {
status: ConnectionStatusType.ReConnect,
};
useNotebook.getState().resetConatinerConnection(connectionStatus);
useNotebook.getState().setIsRefreshed(true);
} }
return undefined; return undefined;
} catch (error) { } catch (error) {
@@ -84,6 +93,13 @@ export class NotebookContainerClient {
"Connection lost with Notebook server. Attempting to reconnect..." "Connection lost with Notebook server. Attempting to reconnect..."
); );
} }
if (NotebookUtil.isPhoenixEnabled()) {
const connectionStatus: ContainerConnectionInfo = {
status: ConnectionStatusType.Failed,
};
useNotebook.getState().resetConatinerConnection(connectionStatus);
useNotebook.getState().setIsRefreshed(true);
}
this.onConnectionLost(); this.onConnectionLost();
return undefined; return undefined;
} }

View File

@@ -212,6 +212,7 @@ export default class NotebookManager {
"Cancel", "Cancel",
() => reject(new Error("Commit dialog canceled")), () => reject(new Error("Commit dialog canceled")),
undefined, undefined,
undefined,
{ {
label: "Commit message", label: "Commit message",
autoAdjustHeight: true, autoAdjustHeight: true,

View File

@@ -16,9 +16,10 @@ import "./NotebookReadOnlyRenderer.less";
import SandboxOutputs from "./outputs/SandboxOutputs"; import SandboxOutputs from "./outputs/SandboxOutputs";
export interface NotebookRendererProps { export interface NotebookRendererProps {
contentRef: any; contentRef: ContentRef;
hideInputs?: boolean; hideInputs?: boolean;
hidePrompts?: boolean; hidePrompts?: boolean;
addTransform: (component: React.ComponentType & { MIMETYPE: string }) => void;
} }
/** /**
@@ -27,7 +28,7 @@ export interface NotebookRendererProps {
class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> { class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
componentDidMount() { componentDidMount() {
if (!userContext.features.sandboxNotebookOutputs) { if (!userContext.features.sandboxNotebookOutputs) {
loadTransform(this.props as any); loadTransform(this.props as NotebookRendererProps);
} }
} }
@@ -59,7 +60,7 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
<div className="NotebookReadOnlyRender"> <div className="NotebookReadOnlyRender">
<Cells contentRef={this.props.contentRef}> <Cells contentRef={this.props.contentRef}>
{{ {{
code: ({ id, contentRef }: { id: any; contentRef: ContentRef }) => ( code: ({ id, contentRef }: { id: string; contentRef: ContentRef }) => (
<CodeCell id={id} contentRef={contentRef}> <CodeCell id={id} contentRef={contentRef}>
{{ {{
prompt: (props: { id: string; contentRef: string }) => this.renderPrompt(props.id, props.contentRef), prompt: (props: { id: string; contentRef: string }) => this.renderPrompt(props.id, props.contentRef),
@@ -73,14 +74,14 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
}} }}
</CodeCell> </CodeCell>
), ),
markdown: ({ id, contentRef }: { id: any; contentRef: ContentRef }) => ( markdown: ({ id, contentRef }: { id: string; contentRef: ContentRef }) => (
<MarkdownCell id={id} contentRef={contentRef} cell_type="markdown"> <MarkdownCell id={id} contentRef={contentRef} cell_type="markdown">
{{ {{
editor: {}, editor: {},
}} }}
</MarkdownCell> </MarkdownCell>
), ),
raw: ({ id, contentRef }: { id: any; contentRef: ContentRef }) => ( raw: ({ id, contentRef }: { id: string; contentRef: ContentRef }) => (
<RawCell id={id} contentRef={contentRef} cell_type="raw"> <RawCell id={id} contentRef={contentRef} cell_type="raw">
{{ {{
editor: { editor: {
@@ -98,6 +99,7 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
} }
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const makeMapDispatchToProps = (initialDispatch: Dispatch, initialProps: NotebookRendererProps) => { const makeMapDispatchToProps = (initialDispatch: Dispatch, initialProps: NotebookRendererProps) => {
const mapDispatchToProps = (dispatch: Dispatch) => { const mapDispatchToProps = (dispatch: Dispatch) => {
return { return {
@@ -114,4 +116,4 @@ const makeMapDispatchToProps = (initialDispatch: Dispatch, initialProps: Noteboo
return mapDispatchToProps; return mapDispatchToProps;
}; };
export default connect(null, makeMapDispatchToProps)(NotebookReadOnlyRenderer); export default connect(undefined, makeMapDispatchToProps)(NotebookReadOnlyRenderer);

View File

@@ -3,6 +3,7 @@ import { AppState, selectors } from "@nteract/core";
import domtoimage from "dom-to-image"; import domtoimage from "dom-to-image";
import Html2Canvas from "html2canvas"; import Html2Canvas from "html2canvas";
import path from "path"; import path from "path";
import { userContext } from "../../UserContext";
import * as GitHubUtils from "../../Utils/GitHubUtils"; import * as GitHubUtils from "../../Utils/GitHubUtils";
import * as StringUtils from "../../Utils/StringUtils"; import * as StringUtils from "../../Utils/StringUtils";
import { SnapshotFragment } from "./NotebookComponent/types"; import { SnapshotFragment } from "./NotebookComponent/types";
@@ -328,4 +329,16 @@ export class NotebookUtil {
link.click(); link.click();
document.body.removeChild(link); document.body.removeChild(link);
} }
public static getNotebookBtnTitle(fileName: string): string {
if (this.isPhoenixEnabled()) {
return `Download to ${fileName}`;
} else {
return `Download to my notebooks`;
}
}
public static isPhoenixEnabled(): boolean {
return userContext.features.notebooksTemporarilyDown === false && userContext.features.phoenix === true;
}
} }

View File

@@ -2,10 +2,12 @@ import { cloneDeep } from "lodash";
import create, { UseStore } from "zustand"; import create, { UseStore } from "zustand";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import { ConnectionStatusType } from "../../Common/Constants";
import { getErrorMessage } from "../../Common/ErrorHandlingUtils"; import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
import * as Logger from "../../Common/Logger"; import * as Logger from "../../Common/Logger";
import { configContext } from "../../ConfigContext"; import { configContext } from "../../ConfigContext";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import { ContainerConnectionInfo } from "../../Contracts/DataModels";
import { IPinnedRepo } from "../../Juno/JunoClient"; import { IPinnedRepo } from "../../Juno/JunoClient";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
@@ -14,6 +16,7 @@ import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
import * as GitHubUtils from "../../Utils/GitHubUtils"; import * as GitHubUtils from "../../Utils/GitHubUtils";
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem"; import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
import NotebookManager from "./NotebookManager"; import NotebookManager from "./NotebookManager";
import { NotebookUtil } from "./NotebookUtil";
interface NotebookState { interface NotebookState {
isNotebookEnabled: boolean; isNotebookEnabled: boolean;
@@ -28,8 +31,10 @@ interface NotebookState {
myNotebooksContentRoot: NotebookContentItem; myNotebooksContentRoot: NotebookContentItem;
gitHubNotebooksContentRoot: NotebookContentItem; gitHubNotebooksContentRoot: NotebookContentItem;
galleryContentRoot: NotebookContentItem; galleryContentRoot: NotebookContentItem;
connectionInfo: DataModels.ContainerConnectionInfo; connectionInfo: ContainerConnectionInfo;
notebookFolderName: string; notebookFolderName: string;
isAllocating: boolean;
isRefreshed: boolean;
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void; setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void; setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void; setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
@@ -46,7 +51,10 @@ interface NotebookState {
deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean) => void; deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean) => void;
initializeNotebooksTree: (notebookManager: NotebookManager) => Promise<void>; initializeNotebooksTree: (notebookManager: NotebookManager) => Promise<void>;
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void; initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void;
setConnectionInfo: (connectionInfo: DataModels.ContainerConnectionInfo) => void; setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => void;
setIsAllocating: (isAllocating: boolean) => void;
resetConatinerConnection: (connectionStatus: ContainerConnectionInfo) => void;
setIsRefreshed: (isAllocating: boolean) => void;
} }
export const useNotebook: UseStore<NotebookState> = create((set, get) => ({ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
@@ -69,8 +77,12 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
myNotebooksContentRoot: undefined, myNotebooksContentRoot: undefined,
gitHubNotebooksContentRoot: undefined, gitHubNotebooksContentRoot: undefined,
galleryContentRoot: undefined, galleryContentRoot: undefined,
connectionInfo: undefined, connectionInfo: {
status: ConnectionStatusType.Connect,
},
notebookFolderName: undefined, notebookFolderName: undefined,
isAllocating: false,
isRefreshed: false,
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }), setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }), setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
@@ -175,7 +187,7 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root }); isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
}, },
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => { initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
const notebookFolderName = userContext.features.phoenix === true ? "Temporary Notebooks" : "My Notebooks"; const notebookFolderName = NotebookUtil.isPhoenixEnabled() === true ? "Temporary Notebooks" : "My Notebooks";
set({ notebookFolderName }); set({ notebookFolderName });
const myNotebooksContentRoot = { const myNotebooksContentRoot = {
name: get().notebookFolderName, name: get().notebookFolderName,
@@ -256,5 +268,15 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
set({ gitHubNotebooksContentRoot }); set({ gitHubNotebooksContentRoot });
} }
}, },
setConnectionInfo: (connectionInfo: DataModels.ContainerConnectionInfo) => set({ connectionInfo }), setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => set({ connectionInfo }),
setIsAllocating: (isAllocating: boolean) => set({ isAllocating }),
resetConatinerConnection: (connectionStatus: ContainerConnectionInfo): void => {
useNotebook.getState().setConnectionInfo(connectionStatus);
useNotebook.getState().setNotebookServerInfo({
notebookServerEndpoint: undefined,
authToken: undefined,
});
useNotebook.getState().setIsAllocating(false);
},
setIsRefreshed: (isRefreshed: boolean) => set({ isRefreshed }),
})); }));

View File

@@ -4,6 +4,8 @@ import * as React from "react";
import { useFullScreenURLs } from "../hooks/useFullScreenURLs"; import { useFullScreenURLs } from "../hooks/useFullScreenURLs";
export const OpenFullScreen: React.FunctionComponent = () => { export const OpenFullScreen: React.FunctionComponent = () => {
const [isReadUrlCopy, setIsReadUrlCopy] = React.useState<boolean>(false);
const [isReadWriteUrlCopy, setIsReadWriteUrlCopy] = React.useState<boolean>(false);
const result = useFullScreenURLs(); const result = useFullScreenURLs();
if (!result) { if (!result) {
return <Spinner label="Generating URLs..." ariaLive="assertive" labelPosition="right" />; return <Spinner label="Generating URLs..." ariaLive="assertive" labelPosition="right" />;
@@ -25,8 +27,9 @@ export const OpenFullScreen: React.FunctionComponent = () => {
<DefaultButton <DefaultButton
onClick={() => { onClick={() => {
copyToClipboard(readWriteUrl); copyToClipboard(readWriteUrl);
setIsReadWriteUrlCopy(true);
}} }}
text="Copy" text={isReadWriteUrlCopy ? "Copied" : "Copy"}
iconProps={{ iconName: "Copy" }} iconProps={{ iconName: "Copy" }}
/> />
<PrimaryButton <PrimaryButton
@@ -41,9 +44,10 @@ export const OpenFullScreen: React.FunctionComponent = () => {
<Stack horizontal tokens={{ childrenGap: 10 }}> <Stack horizontal tokens={{ childrenGap: 10 }}>
<DefaultButton <DefaultButton
onClick={() => { onClick={() => {
setIsReadUrlCopy(true);
copyToClipboard(readUrl); copyToClipboard(readUrl);
}} }}
text="Copy" text={isReadUrlCopy ? "Copied" : "Copy"}
iconProps={{ iconName: "Copy" }} iconProps={{ iconName: "Copy" }}
/> />
<PrimaryButton <PrimaryButton

View File

@@ -13,21 +13,21 @@ import {
Text, Text,
TooltipHost, TooltipHost,
} from "@fluentui/react"; } from "@fluentui/react";
import * as Constants from "Common/Constants";
import { createCollection } from "Common/dataAccess/createCollection";
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
import { configContext, Platform } from "ConfigContext";
import * as DataModels from "Contracts/DataModels";
import { SubscriptionType } from "Contracts/SubscriptionType";
import { useSidePanel } from "hooks/useSidePanel";
import React from "react"; import React from "react";
import * as Constants from "../../Common/Constants"; import { CollectionCreation } from "Shared/Constants";
import { createCollection } from "../../Common/dataAccess/createCollection"; import { Action } from "Shared/Telemetry/TelemetryConstants";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"; import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
import { configContext, Platform } from "../../ConfigContext"; import { userContext } from "UserContext";
import * as DataModels from "../../Contracts/DataModels"; import { getCollectionName } from "Utils/APITypeUtils";
import { SubscriptionType } from "../../Contracts/SubscriptionType"; import { isCapabilityEnabled, isServerlessAccount } from "Utils/CapabilityUtils";
import { useSidePanel } from "../../hooks/useSidePanel"; import { getUpsellMessage } from "Utils/PricingUtils";
import { CollectionCreation } from "../../Shared/Constants";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import { getCollectionName } from "../../Utils/APITypeUtils";
import { isCapabilityEnabled, isServerlessAccount } from "../../Utils/CapabilityUtils";
import { getUpsellMessage } from "../../Utils/PricingUtils";
import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent"; import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent";
import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput"; import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
@@ -173,6 +173,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-checked={this.state.createNewDatabase} aria-checked={this.state.createNewDatabase}
name="databaseType" name="databaseType"
type="radio" type="radio"
role="radio"
id="databaseCreateNew" id="databaseCreateNew"
tabIndex={0} tabIndex={0}
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)} onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
@@ -186,6 +187,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-checked={!this.state.createNewDatabase} aria-checked={!this.state.createNewDatabase}
name="databaseType" name="databaseType"
type="radio" type="radio"
role="radio"
tabIndex={0} tabIndex={0}
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)} onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
/> />
@@ -207,6 +209,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
size={40} size={40}
className="panelTextField" className="panelTextField"
aria-label="New database id" aria-label="New database id"
autoFocus
tabIndex={0} tabIndex={0}
value={this.state.newDatabaseId} value={this.state.newDatabaseId}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
@@ -317,6 +320,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-label="Turn on indexing" aria-label="Turn on indexing"
aria-checked={this.state.enableIndexing} aria-checked={this.state.enableIndexing}
type="radio" type="radio"
role="radio"
tabIndex={0} tabIndex={0}
onChange={this.onTurnOnIndexing.bind(this)} onChange={this.onTurnOnIndexing.bind(this)}
/> />
@@ -328,6 +332,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-label="Turn off indexing" aria-label="Turn off indexing"
aria-checked={!this.state.enableIndexing} aria-checked={!this.state.enableIndexing}
type="radio" type="radio"
role="radio"
tabIndex={0} tabIndex={0}
onChange={this.onTurnOffIndexing.bind(this)} onChange={this.onTurnOffIndexing.bind(this)}
/> />
@@ -370,6 +375,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-checked={!this.state.isSharded} aria-checked={!this.state.isSharded}
name="unsharded" name="unsharded"
type="radio" type="radio"
role="radio"
id="unshardedOption" id="unshardedOption"
tabIndex={0} tabIndex={0}
onChange={this.onUnshardedRadioBtnChange.bind(this)} onChange={this.onUnshardedRadioBtnChange.bind(this)}
@@ -383,6 +389,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-checked={this.state.isSharded} aria-checked={this.state.isSharded}
name="sharded" name="sharded"
type="radio" type="radio"
role="radio"
id="shardedOption" id="shardedOption"
tabIndex={0} tabIndex={0}
onChange={this.onShardedRadioBtnChange.bind(this)} onChange={this.onShardedRadioBtnChange.bind(this)}
@@ -508,6 +515,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
: "Comma separated paths e.g. /firstName,/address/zipCode" : "Comma separated paths e.g. /firstName,/address/zipCode"
} }
className="panelTextField" className="panelTextField"
autoFocus
value={uniqueKey} value={uniqueKey}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => { onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
const uniqueKeys = this.state.uniqueKeys.map((uniqueKey: string, j: number) => { const uniqueKeys = this.state.uniqueKeys.map((uniqueKey: string, j: number) => {
@@ -566,6 +574,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-checked={this.state.enableAnalyticalStore} aria-checked={this.state.enableAnalyticalStore}
name="analyticalStore" name="analyticalStore"
type="radio" type="radio"
role="radio"
id="enableAnalyticalStoreBtn" id="enableAnalyticalStoreBtn"
tabIndex={0} tabIndex={0}
onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)} onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)}
@@ -580,6 +589,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
aria-checked={!this.state.enableAnalyticalStore} aria-checked={!this.state.enableAnalyticalStore}
name="analyticalStore" name="analyticalStore"
type="radio" type="radio"
role="radio"
id="disableAnalyticalStoreBtn" id="disableAnalyticalStoreBtn"
tabIndex={0} tabIndex={0}
onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)} onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)}
@@ -989,7 +999,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
const collectionId: string = this.state.collectionId.trim(); const collectionId: string = this.state.collectionId.trim();
let databaseId = this.state.createNewDatabase ? this.state.newDatabaseId.trim() : this.state.selectedDatabaseId; let databaseId = this.state.createNewDatabase ? this.state.newDatabaseId.trim() : this.state.selectedDatabaseId;
let partitionKeyString = this.state.partitionKey.trim(); let partitionKeyString = this.state.isSharded ? this.state.partitionKey.trim() : undefined;
if (userContext.apiType === "Tables") { if (userContext.apiType === "Tables") {
// Table require fixed Database: TablesDB, and fixed Partition Key: /'$pk' // Table require fixed Database: TablesDB, and fixed Partition Key: /'$pk'

View File

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

View File

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

View File

@@ -1,14 +1,14 @@
import { Checkbox, Dropdown, IDropdownOption, Link, Stack, Text, TextField } from "@fluentui/react"; import { Checkbox, Dropdown, IDropdownOption, Link, Stack, Text, TextField } from "@fluentui/react";
import * as Constants from "Common/Constants";
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
import { useSidePanel } from "hooks/useSidePanel";
import React, { FunctionComponent, useState } from "react"; import React, { FunctionComponent, useState } from "react";
import * as Constants from "../../../Common/Constants"; import * as SharedConstants from "Shared/Constants";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils"; import { Action } from "Shared/Telemetry/TelemetryConstants";
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip"; import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
import { useSidePanel } from "../../../hooks/useSidePanel"; import { userContext } from "UserContext";
import * as SharedConstants from "../../../Shared/Constants"; import { isServerlessAccount } from "Utils/CapabilityUtils";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../UserContext";
import { isServerlessAccount } from "../../../Utils/CapabilityUtils";
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput"; import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { CassandraAPIDataClient } from "../../Tables/TableDataClient"; import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
@@ -169,6 +169,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
aria-label="Create new keyspace" aria-label="Create new keyspace"
checked={keyspaceCreateNew} checked={keyspaceCreateNew}
type="radio" type="radio"
role="radio"
tabIndex={0} tabIndex={0}
onChange={() => { onChange={() => {
setKeyspaceCreateNew(true); setKeyspaceCreateNew(true);
@@ -183,6 +184,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
aria-label="Use existing keyspace" aria-label="Use existing keyspace"
checked={!keyspaceCreateNew} checked={!keyspaceCreateNew}
type="radio" type="radio"
role="radio"
tabIndex={0} tabIndex={0}
onChange={() => { onChange={() => {
setKeyspaceCreateNew(false); setKeyspaceCreateNew(false);
@@ -206,6 +208,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
value={newKeyspaceId} value={newKeyspaceId}
onChange={(e, newValue) => setNewKeyspaceId(newValue)} onChange={(e, newValue) => setNewKeyspaceId(newValue)}
ariaLabel="Keyspace id" ariaLabel="Keyspace id"
autoFocus
/> />
{!isServerlessAccount() && ( {!isServerlessAccount() && (

View File

@@ -77,7 +77,7 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
selectedLocation.repo selectedLocation.repo
)} - ${selectedLocation.branch}`; )} - ${selectedLocation.branch}`;
} else if (selectedLocation.type === "MyNotebooks" && userContext.features.phoenix) { } else if (selectedLocation.type === "MyNotebooks" && userContext.features.phoenix) {
destination = "My Notebooks Scratch"; destination = useNotebook.getState().notebookFolderName;
} }
clearMessage = NotificationConsoleUtils.logConsoleProgress(`Copying ${name} to ${destination}`); clearMessage = NotificationConsoleUtils.logConsoleProgress(`Copying ${name} to ${destination}`);

View File

@@ -1,18 +1,18 @@
import { Text, TextField } from "@fluentui/react"; import { Text, TextField } from "@fluentui/react";
import { Areas } from "Common/Constants";
import { deleteCollection } from "Common/dataAccess/deleteCollection";
import DeleteFeedback from "Common/DeleteFeedback";
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
import { Collection } from "Contracts/ViewModels";
import { useSidePanel } from "hooks/useSidePanel";
import { useTabs } from "hooks/useTabs";
import React, { FunctionComponent, useState } from "react"; import React, { FunctionComponent, useState } from "react";
import { Areas } from "../../../Common/Constants"; import { DefaultExperienceUtility } from "Shared/DefaultExperienceUtility";
import { deleteCollection } from "../../../Common/dataAccess/deleteCollection"; import { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants";
import DeleteFeedback from "../../../Common/DeleteFeedback"; import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils"; import { userContext } from "UserContext";
import { Collection } from "../../../Contracts/ViewModels"; import { getCollectionName } from "Utils/APITypeUtils";
import { useSidePanel } from "../../../hooks/useSidePanel"; import * as NotificationConsoleUtils from "Utils/NotificationConsoleUtils";
import { useTabs } from "../../../hooks/useTabs";
import { DefaultExperienceUtility } from "../../../Shared/DefaultExperienceUtility";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../UserContext";
import { getCollectionName } from "../../../Utils/APITypeUtils";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
import { useDatabases } from "../../useDatabases"; import { useDatabases } from "../../useDatabases";
import { useSelectedNode } from "../../useSelectedNode"; import { useSelectedNode } from "../../useSelectedNode";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
@@ -119,6 +119,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
<Text variant="small">Confirm by typing the {collectionName.toLowerCase()} id</Text> <Text variant="small">Confirm by typing the {collectionName.toLowerCase()} id</Text>
<TextField <TextField
id="confirmCollectionId" id="confirmCollectionId"
autoFocus
value={inputCollectionName} value={inputCollectionName}
styles={{ fieldGroup: { width: 300 } }} styles={{ fieldGroup: { width: 300 } }}
onChange={(event, newInput?: string) => { onChange={(event, newInput?: string) => {

View File

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

View File

@@ -1,18 +1,18 @@
import { Text, TextField } from "@fluentui/react"; import { Text, TextField } from "@fluentui/react";
import { useBoolean } from "@fluentui/react-hooks"; import { useBoolean } from "@fluentui/react-hooks";
import { Areas } from "Common/Constants";
import { deleteDatabase } from "Common/dataAccess/deleteDatabase";
import DeleteFeedback from "Common/DeleteFeedback";
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
import { Collection, Database } from "Contracts/ViewModels";
import { useSidePanel } from "hooks/useSidePanel";
import { useTabs } from "hooks/useTabs";
import React, { FunctionComponent, useState } from "react"; import React, { FunctionComponent, useState } from "react";
import { Areas } from "../../Common/Constants"; import { DefaultExperienceUtility } from "Shared/DefaultExperienceUtility";
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase"; import { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants";
import DeleteFeedback from "../../Common/DeleteFeedback"; import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"; import { userContext } from "UserContext";
import { Collection, Database } from "../../Contracts/ViewModels"; import { logConsoleError } from "Utils/NotificationConsoleUtils";
import { useSidePanel } from "../../hooks/useSidePanel";
import { useTabs } from "../../hooks/useTabs";
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
import { useDatabases } from "../useDatabases"; import { useDatabases } from "../useDatabases";
import { useSelectedNode } from "../useSelectedNode"; import { useSelectedNode } from "../useSelectedNode";
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent"; import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";
@@ -129,6 +129,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
<Text variant="small">Confirm by typing the database id</Text> <Text variant="small">Confirm by typing the database id</Text>
<TextField <TextField
id="confirmDatabaseId" id="confirmDatabaseId"
autoFocus
styles={{ fieldGroup: { width: 300 } }} styles={{ fieldGroup: { width: 300 } }}
onChange={(event, newInput?: string) => { onChange={(event, newInput?: string) => {
setDatabaseInput(newInput); setDatabaseInput(newInput);

View File

@@ -69,11 +69,25 @@ export const InputParameter: FunctionComponent<InputParameterProps> = ({
/> />
{isAddRemoveVisible && ( {isAddRemoveVisible && (
<> <>
<div tabIndex={0} onClick={onDeleteParamKeyPress} role="button" onKeyPress={onDeleteParamKeyPress}> <div tabIndex={0}>
<Image {...imageProps} src={EntityCancelIcon} alt="Delete param" id="deleteparam" /> <Image
{...imageProps}
src={EntityCancelIcon}
alt="Delete param"
id="deleteparam"
role="button"
onClick={onDeleteParamKeyPress}
/>
</div> </div>
<div tabIndex={0} onClick={onAddNewParamKeyPress} role="button" onKeyPress={onAddNewParamKeyPress}> <div tabIndex={0}>
<Image {...imageProps} src={AddPropertyIcon} alt="Add param" id="addparam" /> <Image
{...imageProps}
src={AddPropertyIcon}
alt="Add param"
id="addparam"
role="button"
onClick={onAddNewParamKeyPress}
/>
</div> </div>
</> </>
)} )}

View File

@@ -4328,9 +4328,6 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
</TextFieldBase> </TextFieldBase>
</StyledTextFieldBase> </StyledTextFieldBase>
<div <div
onClick={[Function]}
onKeyPress={[Function]}
role="button"
tabIndex={0} tabIndex={0}
> >
<StyledImageBase <StyledImageBase
@@ -4338,6 +4335,8 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
className="addRemoveIconLabel" className="addRemoveIconLabel"
height={30} height={30}
id="deleteparam" id="deleteparam"
onClick={[Function]}
role="button"
src="" src=""
width={20} width={20}
> >
@@ -4346,6 +4345,8 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
className="addRemoveIconLabel" className="addRemoveIconLabel"
height={30} height={30}
id="deleteparam" id="deleteparam"
onClick={[Function]}
role="button"
src="" src=""
styles={[Function]} styles={[Function]}
theme={ theme={
@@ -4637,8 +4638,10 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
className="ms-Image-image ms-Image-image--portrait is-notLoaded is-fadeIn image-87" className="ms-Image-image ms-Image-image--portrait is-notLoaded is-fadeIn image-87"
id="deleteparam" id="deleteparam"
key="fabricImage" key="fabricImage"
onClick={[Function]}
onError={[Function]} onError={[Function]}
onLoad={[Function]} onLoad={[Function]}
role="button"
src="" src=""
/> />
</div> </div>
@@ -4646,9 +4649,6 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
</StyledImageBase> </StyledImageBase>
</div> </div>
<div <div
onClick={[Function]}
onKeyPress={[Function]}
role="button"
tabIndex={0} tabIndex={0}
> >
<StyledImageBase <StyledImageBase
@@ -4656,6 +4656,8 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
className="addRemoveIconLabel" className="addRemoveIconLabel"
height={30} height={30}
id="addparam" id="addparam"
onClick={[Function]}
role="button"
src="" src=""
width={20} width={20}
> >
@@ -4664,6 +4666,8 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
className="addRemoveIconLabel" className="addRemoveIconLabel"
height={30} height={30}
id="addparam" id="addparam"
onClick={[Function]}
role="button"
src="" src=""
styles={[Function]} styles={[Function]}
theme={ theme={
@@ -4955,8 +4959,10 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
className="ms-Image-image ms-Image-image--portrait is-notLoaded is-fadeIn image-87" className="ms-Image-image ms-Image-image--portrait is-notLoaded is-fadeIn image-87"
id="addparam" id="addparam"
key="fabricImage" key="fabricImage"
onClick={[Function]}
onError={[Function]} onError={[Function]}
onLoad={[Function]} onLoad={[Function]}
role="button"
src="" src=""
/> />
</div> </div>

View File

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

View File

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

View File

@@ -48,15 +48,9 @@ export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProp
)} )}
</Text> </Text>
{showErrorDetails && ( {showErrorDetails && (
<span <a className="paneErrorLink" role="link" onClick={expandConsole}>
className="paneErrorLink moreOption"
role="link"
onClick={expandConsole}
onKeyPress={expandConsole}
tabIndex={0}
>
More details More details
</span> </a>
)} )}
</span> </span>
</Stack> </Stack>

View File

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

View File

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

View File

@@ -1,13 +1,13 @@
import { Checkbox, ChoiceGroup, IChoiceGroupOption, SpinButton } from "@fluentui/react"; import { Checkbox, ChoiceGroup, IChoiceGroupOption, SpinButton } from "@fluentui/react";
import * as Constants from "Common/Constants";
import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
import { configContext } from "ConfigContext";
import { useSidePanel } from "hooks/useSidePanel";
import React, { FunctionComponent, MouseEvent, useState } from "react"; import React, { FunctionComponent, MouseEvent, useState } from "react";
import * as Constants from "../../../Common/Constants"; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip"; import * as StringUtility from "Shared/StringUtility";
import { configContext } from "../../../ConfigContext"; import { userContext } from "UserContext";
import { useSidePanel } from "../../../hooks/useSidePanel"; import { logConsoleInfo } from "Utils/NotificationConsoleUtils";
import { LocalStorageUtility, StorageKey } from "../../../Shared/StorageUtility";
import * as StringUtility from "../../../Shared/StringUtility";
import { userContext } from "../../../UserContext";
import { logConsoleInfo } from "../../../Utils/NotificationConsoleUtils";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
export const SettingsPane: FunctionComponent = () => { export const SettingsPane: FunctionComponent = () => {

View File

@@ -1,8 +1,8 @@
import { TextField } from "@fluentui/react"; import { TextField } from "@fluentui/react";
import * as ViewModels from "Contracts/ViewModels";
import { useTabs } from "hooks/useTabs";
import React, { FormEvent, FunctionComponent, useState } from "react"; import React, { FormEvent, FunctionComponent, useState } from "react";
import * as ViewModels from "../../../Contracts/ViewModels"; import { logConsoleError, logConsoleInfo, logConsoleProgress } from "Utils/NotificationConsoleUtils";
import { useTabs } from "../../../hooks/useTabs";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
import * as FileSystemUtil from "../../Notebook/FileSystemUtil"; import * as FileSystemUtil from "../../Notebook/FileSystemUtil";
import { NotebookContentItem } from "../../Notebook/NotebookContentItem"; import { NotebookContentItem } from "../../Notebook/NotebookContentItem";
import NotebookV2Tab from "../../Tabs/NotebookV2Tab"; import NotebookV2Tab from "../../Tabs/NotebookV2Tab";
@@ -97,6 +97,7 @@ export const StringInputPane: FunctionComponent<StringInputPanelProps> = ({
label={inputLabel} label={inputLabel}
name="collectionIdConfirmation" name="collectionIdConfirmation"
value={stringInput} value={stringInput}
autoFocus
required required
onChange={(event: FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => onChange={(event: FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) =>
setStringInput(newValue) setStringInput(newValue)

View File

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

View File

@@ -1,7 +1,7 @@
import { Upload } from "Common/Upload/Upload";
import { useSidePanel } from "hooks/useSidePanel";
import React, { ChangeEvent, FunctionComponent, useState } from "react"; import React, { ChangeEvent, FunctionComponent, useState } from "react";
import { Upload } from "../../../Common/Upload/Upload"; import { logConsoleError, logConsoleInfo, logConsoleProgress } from "Utils/NotificationConsoleUtils";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
import { NotebookContentItem } from "../../Notebook/NotebookContentItem"; import { NotebookContentItem } from "../../Notebook/NotebookContentItem";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";

View File

@@ -1,8 +1,8 @@
import { DetailsList, DetailsListLayoutMode, IColumn, SelectionMode } from "@fluentui/react"; import { DetailsList, DetailsListLayoutMode, IColumn, SelectionMode } from "@fluentui/react";
import { Upload } from "Common/Upload/Upload";
import { UploadDetailsRecord } from "Contracts/ViewModels";
import React, { ChangeEvent, FunctionComponent, useState } from "react"; import React, { ChangeEvent, FunctionComponent, useState } from "react";
import { Upload } from "../../../Common/Upload/Upload"; import { logConsoleError } from "Utils/NotificationConsoleUtils";
import { UploadDetailsRecord } from "../../../Contracts/ViewModels";
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
import { getErrorMessage } from "../../Tables/Utilities"; import { getErrorMessage } from "../../Tables/Utilities";
import { useSelectedNode } from "../../useSelectedNode"; import { useSelectedNode } from "../../useSelectedNode";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";

View File

@@ -368,6 +368,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
</Text> </Text>
<StyledTextFieldBase <StyledTextFieldBase
ariaLabel="Confirm by typing the database id" ariaLabel="Confirm by typing the database id"
autoFocus={true}
id="confirmDatabaseId" id="confirmDatabaseId"
onChange={[Function]} onChange={[Function]}
styles={ styles={
@@ -380,6 +381,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
> >
<TextFieldBase <TextFieldBase
ariaLabel="Confirm by typing the database id" ariaLabel="Confirm by typing the database id"
autoFocus={true}
deferredValidationTime={200} deferredValidationTime={200}
id="confirmDatabaseId" id="confirmDatabaseId"
onChange={[Function]} onChange={[Function]}
@@ -672,6 +674,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
<input <input
aria-invalid={false} aria-invalid={false}
aria-label="Confirm by typing the database id" aria-label="Confirm by typing the database id"
autoFocus={true}
className="ms-TextField-field field-60" className="ms-TextField-field field-60"
id="confirmDatabaseId" id="confirmDatabaseId"
onBlur={[Function]} onBlur={[Function]}

View File

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

View File

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

View File

@@ -1,12 +1,11 @@
import _ from "underscore";
import Q from "q"; import Q from "q";
import _ from "underscore";
import * as Entities from "../Entities";
import * as QueryBuilderConstants from "../Constants"; import * as QueryBuilderConstants from "../Constants";
import * as Entities from "../Entities";
import * as Utilities from "../Utilities"; import * as Utilities from "../Utilities";
export function getRowSelector(selectorSchema: Entities.IProperty[]): string { export function getRowSelector(selectorSchema: Entities.IProperty[]): string {
var selector: string = ""; let selector = "";
selectorSchema && selectorSchema &&
selectorSchema.forEach((p: Entities.IProperty) => { selectorSchema.forEach((p: Entities.IProperty) => {
selector += "[" + p.key + '="' + Utilities.jQuerySelectorEscape(p.value) + '"]'; selector += "[" + p.key + '="' + Utilities.jQuerySelectorEscape(p.value) + '"]';
@@ -15,10 +14,10 @@ export function getRowSelector(selectorSchema: Entities.IProperty[]): string {
} }
export function isRowVisible(dataTableScrollBodyQuery: JQuery, element: HTMLElement): boolean { export function isRowVisible(dataTableScrollBodyQuery: JQuery, element: HTMLElement): boolean {
var isVisible = false; let isVisible = false;
if (dataTableScrollBodyQuery.length && element) { if (dataTableScrollBodyQuery.length && element) {
var elementRect: ClientRect = element.getBoundingClientRect(), const elementRect: ClientRect = element.getBoundingClientRect(),
dataTableScrollBodyRect: ClientRect = dataTableScrollBodyQuery.get(0).getBoundingClientRect(); dataTableScrollBodyRect: ClientRect = dataTableScrollBodyQuery.get(0).getBoundingClientRect();
isVisible = elementRect.bottom <= dataTableScrollBodyRect.bottom && dataTableScrollBodyRect.top <= elementRect.top; isVisible = elementRect.bottom <= dataTableScrollBodyRect.bottom && dataTableScrollBodyRect.top <= elementRect.top;
@@ -29,17 +28,17 @@ export function isRowVisible(dataTableScrollBodyQuery: JQuery, element: HTMLElem
export function scrollToRowIfNeeded(dataTableRows: JQuery, currentIndex: number, isScrollUp: boolean): void { export function scrollToRowIfNeeded(dataTableRows: JQuery, currentIndex: number, isScrollUp: boolean): void {
if (dataTableRows.length) { if (dataTableRows.length) {
var dataTableScrollBodyQuery: JQuery = $(QueryBuilderConstants.htmlSelectors.dataTableScrollBodySelector), const dataTableScrollBodyQuery: JQuery = $(QueryBuilderConstants.htmlSelectors.dataTableScrollBodySelector),
selectedRowElement: HTMLElement = dataTableRows.get(currentIndex); selectedRowElement: HTMLElement = dataTableRows.get(currentIndex);
if (dataTableScrollBodyQuery.length && selectedRowElement) { if (dataTableScrollBodyQuery.length && selectedRowElement) {
var isVisible: boolean = isRowVisible(dataTableScrollBodyQuery, selectedRowElement); const isVisible: boolean = isRowVisible(dataTableScrollBodyQuery, selectedRowElement);
if (!isVisible) { if (!isVisible) {
var selectedRowQuery: JQuery = $(selectedRowElement), const selectedRowQuery: JQuery = $(selectedRowElement),
scrollPosition: number = dataTableScrollBodyQuery.scrollTop(), scrollPosition: number = dataTableScrollBodyQuery.scrollTop(),
selectedElementPosition: number = selectedRowQuery.position().top, selectedElementPosition: number = selectedRowQuery.position().top;
newScrollPosition: number = 0; let newScrollPosition = 0;
if (isScrollUp) { if (isScrollUp) {
newScrollPosition = scrollPosition + selectedElementPosition; newScrollPosition = scrollPosition + selectedElementPosition;
@@ -55,7 +54,7 @@ export function scrollToRowIfNeeded(dataTableRows: JQuery, currentIndex: number,
} }
export function scrollToTopIfNeeded(): void { export function scrollToTopIfNeeded(): void {
var $dataTableRows: JQuery = $(QueryBuilderConstants.htmlSelectors.dataTableAllRowsSelector), const $dataTableRows: JQuery = $(QueryBuilderConstants.htmlSelectors.dataTableAllRowsSelector),
$dataTableScrollBody: JQuery = $(QueryBuilderConstants.htmlSelectors.dataTableScrollBodySelector); $dataTableScrollBody: JQuery = $(QueryBuilderConstants.htmlSelectors.dataTableScrollBodySelector);
if ($dataTableRows.length && $dataTableScrollBody.length) { if ($dataTableRows.length && $dataTableScrollBody.length) {
@@ -88,13 +87,14 @@ export function reorderColumns(
table: DataTables.DataTable, table: DataTables.DataTable,
targetOrder: number[], targetOrder: number[],
currentOrder?: number[] currentOrder?: number[]
//eslint-disable-next-line
): Q.Promise<any> { ): Q.Promise<any> {
var columnsCount: number = targetOrder.length; const columnsCount: number = targetOrder.length;
var isCurrentOrderPassedIn: boolean = !!currentOrder; const isCurrentOrderPassedIn = !!currentOrder;
if (!isCurrentOrderPassedIn) { if (!isCurrentOrderPassedIn) {
currentOrder = getInitialOrder(columnsCount); currentOrder = getInitialOrder(columnsCount);
} }
var isSameOrder: boolean = Utilities.isEqual(currentOrder, targetOrder); const isSameOrder: boolean = Utilities.isEqual(currentOrder, targetOrder);
// if the targetOrder is the same as current order, do nothing. // if the targetOrder is the same as current order, do nothing.
if (!isSameOrder) { if (!isSameOrder) {
@@ -104,7 +104,7 @@ export function reorderColumns(
// Then the transformation order will be the same as target order. // Then the transformation order will be the same as target order.
// If current order is specified, then a transformation order is calculated. // If current order is specified, then a transformation order is calculated.
// Refer to calculateTransformationOrder for details about transformation order. // Refer to calculateTransformationOrder for details about transformation order.
var transformationOrder: number[] = isCurrentOrderPassedIn const transformationOrder: number[] = isCurrentOrderPassedIn
? calculateTransformationOrder(currentOrder, targetOrder) ? calculateTransformationOrder(currentOrder, targetOrder)
: targetOrder; : targetOrder;
try { try {
@@ -143,7 +143,7 @@ export function getCurrentOrder(table: DataTables.DataTable): number[] {
* Result: [0, 1, 2, 5, 6, 7, 3, 4, 8] * Result: [0, 1, 2, 5, 6, 7, 3, 4, 8]
*/ */
export function invertIndexValues(inputArray: number[]): number[] { export function invertIndexValues(inputArray: number[]): number[] {
var invertedArray: number[] = []; const invertedArray: number[] = [];
if (inputArray) { if (inputArray) {
inputArray.forEach((value: number, index: number) => { inputArray.forEach((value: number, index: number) => {
invertedArray[inputArray[index]] = index; invertedArray[inputArray[index]] = index;
@@ -170,20 +170,21 @@ export function invertIndexValues(inputArray: number[]): number[] {
* transformation order: Trans = [0, 1, 2, 7, 3, 4, 8, 5, 6] * transformation order: Trans = [0, 1, 2, 7, 3, 4, 8, 5, 6]
*/ */
export function calculateTransformationOrder(currentOrder: number[], targetOrder: number[]): number[] { export function calculateTransformationOrder(currentOrder: number[], targetOrder: number[]): number[] {
var transformationOrder: number[] = []; let transformationOrder: number[] = [];
if (currentOrder && targetOrder && currentOrder.length === targetOrder.length) { if (currentOrder && targetOrder && currentOrder.length === targetOrder.length) {
var invertedCurrentOrder: number[] = invertIndexValues(currentOrder); const invertedCurrentOrder: number[] = invertIndexValues(currentOrder);
transformationOrder = targetOrder.map((value: number) => invertedCurrentOrder[value]); transformationOrder = targetOrder.map((value: number) => invertedCurrentOrder[value]);
} }
return transformationOrder; return transformationOrder;
} }
export function getDataTableHeaders(table: DataTables.DataTable): string[] { export function getDataTableHeaders(table: DataTables.DataTable): string[] {
var columns: DataTables.ColumnsMethods = table.columns(); const columns: DataTables.ColumnsMethods = table.columns();
var headers: string[] = []; let headers: string[] = [];
if (columns) { if (columns) {
// table.columns() return ColumnsMethods which is an array of arrays // table.columns() return ColumnsMethods which is an array of arrays
var columnIndexes: number[] = (<any>columns)[0]; //eslint-disable-next-line
const columnIndexes: number[] = (<any>columns)[0];
if (columnIndexes) { if (columnIndexes) {
headers = columnIndexes.map((value: number) => $(table.columns(value).header()).html()); headers = columnIndexes.map((value: number) => $(table.columns(value).header()).html());
} }

View File

@@ -8,11 +8,11 @@ import TableEntityListViewModel from "./TableEntityListViewModel";
export default class TableCommands { export default class TableCommands {
// Command Ids // Command Ids
public static editEntityCommand: string = "edit"; public static editEntityCommand = "edit";
public static deleteEntitiesCommand: string = "delete"; public static deleteEntitiesCommand = "delete";
public static reorderColumnsCommand: string = "reorder"; public static reorderColumnsCommand = "reorder";
public static resetColumnsCommand: string = "reset"; public static resetColumnsCommand = "reset";
public static customizeColumnsCommand: string = "customizeColumns"; public static customizeColumnsCommand = "customizeColumns";
private _container: Explorer; private _container: Explorer;
@@ -21,8 +21,8 @@ export default class TableCommands {
} }
public isEnabled(commandName: string, selectedEntites: Entities.ITableEntity[]): boolean { public isEnabled(commandName: string, selectedEntites: Entities.ITableEntity[]): boolean {
var singleItemSelected: boolean = DataTableUtilities.containSingleItem(selectedEntites); const singleItemSelected = DataTableUtilities.containSingleItem(selectedEntites);
var atLeastOneItemSelected: boolean = DataTableUtilities.containItems(selectedEntites); const atLeastOneItemSelected = DataTableUtilities.containItems(selectedEntites);
switch (commandName) { switch (commandName) {
case TableCommands.editEntityCommand: case TableCommands.editEntityCommand:
return singleItemSelected; return singleItemSelected;
@@ -47,6 +47,7 @@ export default class TableCommands {
/** /**
* Edit entity * Edit entity
*/ */
//eslint-disable-next-line
public editEntityCommand(viewModel: TableEntityListViewModel): Q.Promise<any> { public editEntityCommand(viewModel: TableEntityListViewModel): Q.Promise<any> {
if (!viewModel) { if (!viewModel) {
return null; // Error return null; // Error
@@ -56,12 +57,9 @@ export default class TableCommands {
return null; // Erorr return null; // Erorr
} }
var entityToUpdate: Entities.ITableEntity = viewModel.selected()[0];
var originalNumberOfProperties = entityToUpdate ? 0 : Object.keys(entityToUpdate).length - 1; // .metadata is always a property for etag
return null; return null;
} }
//eslint-disable-next-line
public deleteEntitiesCommand(viewModel: TableEntityListViewModel): Q.Promise<any> { public deleteEntitiesCommand(viewModel: TableEntityListViewModel): Q.Promise<any> {
if (!viewModel) { if (!viewModel) {
return null; // Error return null; // Error
@@ -69,7 +67,7 @@ export default class TableCommands {
if (!DataTableUtilities.containItems(viewModel.selected())) { if (!DataTableUtilities.containItems(viewModel.selected())) {
return null; // Error return null; // Error
} }
var entitiesToDelete: Entities.ITableEntity[] = viewModel.selected(); const entitiesToDelete: Entities.ITableEntity[] = viewModel.selected();
const deleteMessage: string = const deleteMessage: string =
userContext.apiType === "Cassandra" userContext.apiType === "Cassandra"
? "Are you sure you want to delete the selected rows?" ? "Are you sure you want to delete the selected rows?"
@@ -82,7 +80,7 @@ export default class TableCommands {
() => { () => {
viewModel.queryTablesTab.container.tableDataClient viewModel.queryTablesTab.container.tableDataClient
.deleteDocuments(viewModel.queryTablesTab.collection, entitiesToDelete) .deleteDocuments(viewModel.queryTablesTab.collection, entitiesToDelete)
.then((results: any) => { .then(() => {
return viewModel.removeEntitiesFromCache(entitiesToDelete).then(() => { return viewModel.removeEntitiesFromCache(entitiesToDelete).then(() => {
viewModel.redrawTableThrottled(); viewModel.redrawTableThrottled();
}); });

View File

@@ -1,5 +1,5 @@
import * as Utilities from "../Utilities";
import * as Entities from "../Entities"; import * as Entities from "../Entities";
import * as Utilities from "../Utilities";
import CacheBase from "./CacheBase"; import CacheBase from "./CacheBase";
export default class TableEntityCache extends CacheBase<Entities.ITableEntity> { export default class TableEntityCache extends CacheBase<Entities.ITableEntity> {
@@ -21,7 +21,7 @@ export default class TableEntityCache extends CacheBase<Entities.ITableEntity> {
this._tableQuery = Utilities.copyTableQuery(tableQuery); this._tableQuery = Utilities.copyTableQuery(tableQuery);
} }
public preClear() { public preClear(): void {
this.tableQuery = null; this.tableQuery = null;
} }
} }

View File

@@ -1,4 +1,4 @@
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos"; import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
export interface ITableEntity { export interface ITableEntity {
[property: string]: ITableEntityAttribute; [property: string]: ITableEntityAttribute;
@@ -17,6 +17,7 @@ export interface ITableEntityAttribute {
export interface IListTableEntitiesResult { export interface IListTableEntitiesResult {
Results: ITableEntity[]; Results: ITableEntity[];
//eslint-disable-next-line
ContinuationToken: any; ContinuationToken: any;
iterator?: QueryIterator<ItemDefinition & Resource>; iterator?: QueryIterator<ItemDefinition & Resource>;
} }

View File

@@ -1,8 +1,9 @@
import QueryClauseViewModel from "./QueryClauseViewModel";
import * as Utilities from "../Utilities"; import * as Utilities from "../Utilities";
import QueryClauseViewModel from "./QueryClauseViewModel";
export default class ClauseGroup { export default class ClauseGroup {
public isRootGroup: boolean; public isRootGroup: boolean;
//eslint-disable-next-line
public children = new Array(); public children = new Array();
public parentGroup: ClauseGroup; public parentGroup: ClauseGroup;
private _id: string; private _id: string;
@@ -17,7 +18,7 @@ export default class ClauseGroup {
* Flattens the clause tree into an array, depth-first, left to right. * Flattens the clause tree into an array, depth-first, left to right.
*/ */
public flattenClauses(targetArray: ko.ObservableArray<QueryClauseViewModel>): void { public flattenClauses(targetArray: ko.ObservableArray<QueryClauseViewModel>): void {
var tempArray = new Array<QueryClauseViewModel>(); const tempArray = new Array<QueryClauseViewModel>();
this.flattenClausesImpl(this, tempArray); this.flattenClausesImpl(this, tempArray);
targetArray.removeAll(); targetArray.removeAll();
@@ -31,10 +32,10 @@ export default class ClauseGroup {
newClause.clauseGroup = this; newClause.clauseGroup = this;
this.children.push(newClause); this.children.push(newClause);
} else { } else {
var targetGroup = insertBefore.clauseGroup; const targetGroup = insertBefore.clauseGroup;
if (targetGroup) { if (targetGroup) {
var insertBeforeIndex = targetGroup.children.indexOf(insertBefore); const insertBeforeIndex = targetGroup.children.indexOf(insertBefore);
newClause.clauseGroup = targetGroup; newClause.clauseGroup = targetGroup;
targetGroup.children.splice(insertBeforeIndex, 0, newClause); targetGroup.children.splice(insertBeforeIndex, 0, newClause);
} }
@@ -42,19 +43,19 @@ export default class ClauseGroup {
} }
public deleteClause(clause: QueryClauseViewModel): void { public deleteClause(clause: QueryClauseViewModel): void {
var targetGroup = clause.clauseGroup; const targetGroup = clause.clauseGroup;
if (targetGroup) { if (targetGroup) {
var index = targetGroup.children.indexOf(clause); const index = targetGroup.children.indexOf(clause);
targetGroup.children.splice(index, 1); targetGroup.children.splice(index, 1);
clause.dispose(); clause.dispose();
if (targetGroup.children.length <= 1 && !targetGroup.isRootGroup) { if (targetGroup.children.length <= 1 && !targetGroup.isRootGroup) {
var parent = targetGroup.parentGroup; const parent = targetGroup.parentGroup;
var targetGroupIndex = parent.children.indexOf(targetGroup); const targetGroupIndex = parent.children.indexOf(targetGroup);
if (targetGroup.children.length === 1) { if (targetGroup.children.length === 1) {
var orphan = targetGroup.children.shift(); const orphan = targetGroup.children.shift();
if (orphan instanceof QueryClauseViewModel) { if (orphan instanceof QueryClauseViewModel) {
(<QueryClauseViewModel>orphan).clauseGroup = parent; (<QueryClauseViewModel>orphan).clauseGroup = parent;
@@ -71,14 +72,14 @@ export default class ClauseGroup {
} }
public removeAll(): void { public removeAll(): void {
var allClauses: QueryClauseViewModel[] = new Array<QueryClauseViewModel>(); const allClauses: QueryClauseViewModel[] = new Array<QueryClauseViewModel>();
this.flattenClausesImpl(this, allClauses); this.flattenClausesImpl(this, allClauses);
while (allClauses.length > 0) { while (allClauses.length > 0) {
allClauses.shift().dispose(); allClauses.shift().dispose();
} }
//eslint-disable-next-line
this.children = new Array<any>(); this.children = new Array<any>();
} }
@@ -87,12 +88,12 @@ export default class ClauseGroup {
*/ */
public groupSelectedItems(): boolean { public groupSelectedItems(): boolean {
// Find the selection start & end, also check for gaps between selected items (if found, cannot proceed). // Find the selection start & end, also check for gaps between selected items (if found, cannot proceed).
var selection = this.getCheckedItemsInfo(); const selection = this.getCheckedItemsInfo();
if (selection.canGroup) { if (selection.canGroup) {
var newGroup = new ClauseGroup(false, this); const newGroup = new ClauseGroup(false, this);
// Replace the selected items with the new group, and then move the selected items into the new group. // Replace the selected items with the new group, and then move the selected items into the new group.
var groupedItems = this.children.splice(selection.begin, selection.end - selection.begin + 1, newGroup); const groupedItems = this.children.splice(selection.begin, selection.end - selection.begin + 1, newGroup);
groupedItems && groupedItems &&
groupedItems.forEach((element) => { groupedItems.forEach((element) => {
@@ -118,13 +119,13 @@ export default class ClauseGroup {
return; return;
} }
var parentGroup = this.parentGroup; const parentGroup = this.parentGroup;
var index = parentGroup.children.indexOf(this); let index = parentGroup.children.indexOf(this);
if (index >= 0) { if (index >= 0) {
parentGroup.children.splice(index, 1); parentGroup.children.splice(index, 1);
var toPromote = this.children.splice(0, this.children.length); const toPromote = this.children.splice(0, this.children.length);
// Move all children one level up. // Move all children one level up.
toPromote && toPromote &&
@@ -146,16 +147,16 @@ export default class ClauseGroup {
} }
public findDeepestGroupInChildren(skipIndex?: number): ClauseGroup { public findDeepestGroupInChildren(skipIndex?: number): ClauseGroup {
var deepest: ClauseGroup = this; let deepest = <ClauseGroup>this;
var level: number = 0; let level = 0;
var func = (currentGroup: ClauseGroup): void => { const func = (currentGroup: ClauseGroup): void => {
level++; level++;
if (currentGroup.getCurrentGroupDepth() > deepest.getCurrentGroupDepth()) { if (currentGroup.getCurrentGroupDepth() > deepest.getCurrentGroupDepth()) {
deepest = currentGroup; deepest = currentGroup;
} }
for (var i = 0; i < currentGroup.children.length; i++) { for (let i = 0; i < currentGroup.children.length; i++) {
var currentItem = currentGroup.children[i]; const currentItem = currentGroup.children[i];
if ((i !== skipIndex || level > 1) && currentItem instanceof ClauseGroup) { if ((i !== skipIndex || level > 1) && currentItem instanceof ClauseGroup) {
func(currentItem); func(currentItem);
@@ -170,16 +171,16 @@ export default class ClauseGroup {
} }
private getCheckedItemsInfo(): { canGroup: boolean; begin: number; end: number } { private getCheckedItemsInfo(): { canGroup: boolean; begin: number; end: number } {
var beginIndex = -1; let beginIndex = -1;
var endIndex = -1; let endIndex = -1;
// In order to perform group, all selected items must be next to each other. // In order to perform group, all selected items must be next to each other.
// If one or more items are not selected between the first and the last selected item, the gapFlag will be set to True, meaning cannot perform group. // If one or more items are not selected between the first and the last selected item, the gapFlag will be set to True, meaning cannot perform group.
var gapFlag = false; let gapFlag = false;
var count = 0; let count = 0;
for (var i = 0; i < this.children.length; i++) { for (let i = 0; i < this.children.length; i++) {
var currentItem = this.children[i]; const currentItem = this.children[i];
var subGroupSelectionState: { allSelected: boolean; partiallySelected: boolean; nonSelected: boolean }; let subGroupSelectionState: { allSelected: boolean; partiallySelected: boolean; nonSelected: boolean };
if (currentItem instanceof ClauseGroup) { if (currentItem instanceof ClauseGroup) {
subGroupSelectionState = (<ClauseGroup>currentItem).getSelectionState(); subGroupSelectionState = (<ClauseGroup>currentItem).getSelectionState();
@@ -235,10 +236,10 @@ export default class ClauseGroup {
} }
private getSelectionState(): { allSelected: boolean; partiallySelected: boolean; nonSelected: boolean } { private getSelectionState(): { allSelected: boolean; partiallySelected: boolean; nonSelected: boolean } {
var selectedCount = 0; let selectedCount = 0;
for (var i = 0; i < this.children.length; i++) { for (let i = 0; i < this.children.length; i++) {
var currentItem = this.children[i]; const currentItem = this.children[i];
if (currentItem instanceof ClauseGroup && (<ClauseGroup>currentItem).getSelectionState().allSelected) { if (currentItem instanceof ClauseGroup && (<ClauseGroup>currentItem).getSelectionState().allSelected) {
selectedCount++; selectedCount++;
@@ -260,8 +261,8 @@ export default class ClauseGroup {
} }
private unselectAll(): void { private unselectAll(): void {
for (var i = 0; i < this.children.length; i++) { for (let i = 0; i < this.children.length; i++) {
var currentItem = this.children[i]; const currentItem = this.children[i];
if (currentItem instanceof ClauseGroup) { if (currentItem instanceof ClauseGroup) {
(<ClauseGroup>currentItem).unselectAll(); (<ClauseGroup>currentItem).unselectAll();
@@ -278,8 +279,8 @@ export default class ClauseGroup {
targetArray.splice(0, targetArray.length); targetArray.splice(0, targetArray.length);
} }
for (var i = 0; i < queryGroup.children.length; i++) { for (let i = 0; i < queryGroup.children.length; i++) {
var currentItem = queryGroup.children[i]; const currentItem = queryGroup.children[i];
if (currentItem instanceof ClauseGroup) { if (currentItem instanceof ClauseGroup) {
this.flattenClausesImpl(currentItem, targetArray); this.flattenClausesImpl(currentItem, targetArray);
@@ -292,13 +293,13 @@ export default class ClauseGroup {
} }
public getTreeDepth(): number { public getTreeDepth(): number {
var currentDepth = this.getCurrentGroupDepth(); let currentDepth = this.getCurrentGroupDepth();
for (var i = 0; i < this.children.length; i++) { for (let i = 0; i < this.children.length; i++) {
var currentItem = this.children[i]; const currentItem = this.children[i];
if (currentItem instanceof ClauseGroup) { if (currentItem instanceof ClauseGroup) {
var newDepth = (<ClauseGroup>currentItem).getTreeDepth(); const newDepth = (<ClauseGroup>currentItem).getTreeDepth();
if (newDepth > currentDepth) { if (newDepth > currentDepth) {
currentDepth = newDepth; currentDepth = newDepth;
@@ -310,8 +311,8 @@ export default class ClauseGroup {
} }
public getCurrentGroupDepth(): number { public getCurrentGroupDepth(): number {
var group = <ClauseGroup>this; let group = <ClauseGroup>this;
var depth = 0; let depth = 0;
while (!group.isRootGroup) { while (!group.isRootGroup) {
depth++; depth++;

View File

@@ -1,7 +1,7 @@
import * as ko from "knockout"; import * as ko from "knockout";
import * as Constants from "../Constants";
import ClauseGroup from "./ClauseGroup"; import ClauseGroup from "./ClauseGroup";
import QueryBuilderViewModel from "./QueryBuilderViewModel"; import QueryBuilderViewModel from "./QueryBuilderViewModel";
import * as Constants from "../Constants";
/** /**
* View model for showing group indicators on UI, contains information such as group color and border styles. * View model for showing group indicators on UI, contains information such as group color and border styles.
@@ -38,7 +38,7 @@ export default class ClauseGroupViewModel {
}; };
private getGroupBackgroundColor(group: ClauseGroup): string { private getGroupBackgroundColor(group: ClauseGroup): string {
var colorCount = Constants.clauseGroupColors.length; const colorCount = Constants.clauseGroupColors.length;
if (group.isRootGroup) { if (group.isRootGroup) {
return Constants.transparentColor; return Constants.transparentColor;

View File

@@ -29,7 +29,7 @@ export default class QueryBuilderViewModel {
public removeThisFilterLine = "Remove this filter line"; // localize public removeThisFilterLine = "Remove this filter line"; // localize
public groupSelectedClauses = "Group selected clauses"; // localize public groupSelectedClauses = "Group selected clauses"; // localize
public clauseArray = ko.observableArray<QueryClauseViewModel>(); // This is for storing the clauses in flattened form queryClauses for easier UI data binding. public clauseArray = ko.observableArray<QueryClauseViewModel>(); // This is for storing the clauses in flattened form queryClauses for easier UI data binding.
public queryClauses = new ClauseGroup(true, null); // The actual data structure containing the clause information. public queryClauses = new ClauseGroup(true, undefined); // The actual data structure containing the clause information.
public columnOptions: ko.ObservableArray<string>; public columnOptions: ko.ObservableArray<string>;
public canGroupClauses = ko.observable<boolean>(false); public canGroupClauses = ko.observable<boolean>(false);
@@ -107,7 +107,7 @@ export default class QueryBuilderViewModel {
} }
public setExample() { public setExample() {
var example1 = new QueryClauseViewModel( const example1 = new QueryClauseViewModel(
this, this,
"", "",
"PartitionKey", "PartitionKey",
@@ -121,7 +121,7 @@ export default class QueryBuilderViewModel {
//null, //null,
true true
); );
var example2 = new QueryClauseViewModel( const example2 = new QueryClauseViewModel(
this, this,
"And", "And",
"RowKey", "RowKey",
@@ -140,13 +140,13 @@ export default class QueryBuilderViewModel {
} }
public getODataFilterFromClauses = (): string => { public getODataFilterFromClauses = (): string => {
var filterString: string = ""; let filterString = "";
var treeTraversal = (group: ClauseGroup): void => { const treeTraversal = (group: ClauseGroup): void => {
for (var i = 0; i < group.children.length; i++) { for (let i = 0; i < group.children.length; i++) {
var currentItem = group.children[i]; const currentItem = group.children[i];
if (currentItem instanceof QueryClauseViewModel) { if (currentItem instanceof QueryClauseViewModel) {
var clause = <QueryClauseViewModel>currentItem; const clause = <QueryClauseViewModel>currentItem;
this.timestampToValue(clause); this.timestampToValue(clause);
filterString = filterString.concat( filterString = filterString.concat(
this.constructODataClause( this.constructODataClause(
@@ -173,7 +173,7 @@ export default class QueryBuilderViewModel {
}; };
public getSqlFilterFromClauses = (): string => { public getSqlFilterFromClauses = (): string => {
var filterString: string = "SELECT * FROM c"; let filterString = "SELECT * FROM c";
if (this._queryViewModel.selectText() && this._queryViewModel.selectText().length > 0) { if (this._queryViewModel.selectText() && this._queryViewModel.selectText().length > 0) {
filterString = "SELECT"; filterString = "SELECT";
const selectText = this._queryViewModel && this._queryViewModel.selectText && this._queryViewModel.selectText(); const selectText = this._queryViewModel && this._queryViewModel.selectText && this._queryViewModel.selectText();
@@ -199,15 +199,15 @@ export default class QueryBuilderViewModel {
return filterString; return filterString;
} }
filterString = filterString.concat(" WHERE"); filterString = filterString.concat(" WHERE");
var first = true; let first = true;
var treeTraversal = (group: ClauseGroup): void => { const treeTraversal = (group: ClauseGroup): void => {
for (var i = 0; i < group.children.length; i++) { for (let i = 0; i < group.children.length; i++) {
var currentItem = group.children[i]; const currentItem = group.children[i];
if (currentItem instanceof QueryClauseViewModel) { if (currentItem instanceof QueryClauseViewModel) {
var clause = <QueryClauseViewModel>currentItem; const clause = <QueryClauseViewModel>currentItem;
let timeStampValue: string = this.timestampToSqlValue(clause); const timeStampValue: string = this.timestampToSqlValue(clause);
var value = clause.value(); let value = clause.value();
if (!clause.isValue()) { if (!clause.isValue()) {
value = timeStampValue; value = timeStampValue;
} }
@@ -240,7 +240,7 @@ export default class QueryBuilderViewModel {
const databaseId = this._queryViewModel.queryTablesTab.collection.databaseId; const databaseId = this._queryViewModel.queryTablesTab.collection.databaseId;
const collectionId = this._queryViewModel.queryTablesTab.collection.id(); const collectionId = this._queryViewModel.queryTablesTab.collection.id();
const tableToQuery = `${getQuotedCqlIdentifier(databaseId)}.${getQuotedCqlIdentifier(collectionId)}`; const tableToQuery = `${getQuotedCqlIdentifier(databaseId)}.${getQuotedCqlIdentifier(collectionId)}`;
var filterString: string = `SELECT * FROM ${tableToQuery}`; let filterString = `SELECT * FROM ${tableToQuery}`;
if (this._queryViewModel.selectText() && this._queryViewModel.selectText().length > 0) { if (this._queryViewModel.selectText() && this._queryViewModel.selectText().length > 0) {
filterString = "SELECT"; filterString = "SELECT";
const selectText = this._queryViewModel && this._queryViewModel.selectText && this._queryViewModel.selectText(); const selectText = this._queryViewModel && this._queryViewModel.selectText && this._queryViewModel.selectText();
@@ -255,15 +255,15 @@ export default class QueryBuilderViewModel {
return filterString; return filterString;
} }
filterString = filterString.concat(" WHERE"); filterString = filterString.concat(" WHERE");
var first = true; let first = true;
var treeTraversal = (group: ClauseGroup): void => { const treeTraversal = (group: ClauseGroup): void => {
for (var i = 0; i < group.children.length; i++) { for (let i = 0; i < group.children.length; i++) {
var currentItem = group.children[i]; const currentItem = group.children[i];
if (currentItem instanceof QueryClauseViewModel) { if (currentItem instanceof QueryClauseViewModel) {
var clause = <QueryClauseViewModel>currentItem; const clause = <QueryClauseViewModel>currentItem;
let timeStampValue: string = this.timestampToSqlValue(clause); const timeStampValue = this.timestampToSqlValue(clause);
var value = clause.value(); let value = clause.value();
if (!clause.isValue()) { if (!clause.isValue()) {
value = timeStampValue; value = timeStampValue;
} }
@@ -293,13 +293,13 @@ export default class QueryBuilderViewModel {
}; };
public updateColumnOptions = (): void => { public updateColumnOptions = (): void => {
let originalHeaders = this.columnOptions(); // let originalHeaders = this.columnOptions();
let newHeaders = this.tableEntityListViewModel.headers; const newHeaders = this.tableEntityListViewModel.headers;
this.columnOptions(newHeaders.sort(DataTableUtilities.compareTableColumns)); this.columnOptions(newHeaders.sort(DataTableUtilities.compareTableColumns));
}; };
private generateLeftParentheses(clause: QueryClauseViewModel): string { private generateLeftParentheses(clause: QueryClauseViewModel): string {
var result = ""; let result = "";
if (clause.clauseGroup.isRootGroup || clause.clauseGroup.children.indexOf(clause) !== 0) { if (clause.clauseGroup.isRootGroup || clause.clauseGroup.children.indexOf(clause) !== 0) {
return result; return result;
@@ -307,7 +307,7 @@ export default class QueryBuilderViewModel {
result = result.concat("("); result = result.concat("(");
} }
var currentGroup: ClauseGroup = clause.clauseGroup; let currentGroup: ClauseGroup = clause.clauseGroup;
while ( while (
!currentGroup.isRootGroup && !currentGroup.isRootGroup &&
@@ -322,7 +322,7 @@ export default class QueryBuilderViewModel {
} }
private generateRightParentheses(clause: QueryClauseViewModel): string { private generateRightParentheses(clause: QueryClauseViewModel): string {
var result = ""; let result = "";
if ( if (
clause.clauseGroup.isRootGroup || clause.clauseGroup.isRootGroup ||
@@ -333,7 +333,7 @@ export default class QueryBuilderViewModel {
result = result.concat(")"); result = result.concat(")");
} }
var currentGroup: ClauseGroup = clause.clauseGroup; let currentGroup: ClauseGroup = clause.clauseGroup;
while ( while (
!currentGroup.isRootGroup && !currentGroup.isRootGroup &&
@@ -364,14 +364,17 @@ export default class QueryBuilderViewModel {
case Constants.TableType.String: case Constants.TableType.String:
return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter( return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter(
operator operator
// eslint-disable-next-line no-useless-escape
)} \'${value}\'${rightParentheses}`; )} \'${value}\'${rightParentheses}`;
case Constants.TableType.Guid: case Constants.TableType.Guid:
return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter( return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter(
operator operator
// eslint-disable-next-line no-useless-escape
)} guid\'${value}\'${rightParentheses}`; )} guid\'${value}\'${rightParentheses}`;
case Constants.TableType.Binary: case Constants.TableType.Binary:
return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter( return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter(
operator operator
// eslint-disable-next-line no-useless-escape
)} binary\'${value}\'${rightParentheses}`; )} binary\'${value}\'${rightParentheses}`;
default: default:
return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter( return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter(
@@ -391,9 +394,11 @@ export default class QueryBuilderViewModel {
): string => { ): string => {
if (propertyName === Constants.EntityKeyNames.PartitionKey) { if (propertyName === Constants.EntityKeyNames.PartitionKey) {
propertyName = TableEntityProcessor.keyProperties.PartitionKey; propertyName = TableEntityProcessor.keyProperties.PartitionKey;
// eslint-disable-next-line no-useless-escape
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c["${propertyName}"] ${operator} \'${value}\'${rightParentheses}`; return ` ${clauseRule.toLowerCase()} ${leftParentheses}c["${propertyName}"] ${operator} \'${value}\'${rightParentheses}`;
} else if (propertyName === Constants.EntityKeyNames.RowKey) { } else if (propertyName === Constants.EntityKeyNames.RowKey) {
propertyName = TableEntityProcessor.keyProperties.Id; propertyName = TableEntityProcessor.keyProperties.Id;
// eslint-disable-next-line no-useless-escape
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName} ${operator} \'${value}\'${rightParentheses}`; return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName} ${operator} \'${value}\'${rightParentheses}`;
} else if (propertyName === Constants.EntityKeyNames.Timestamp) { } else if (propertyName === Constants.EntityKeyNames.Timestamp) {
propertyName = TableEntityProcessor.keyProperties.Timestamp; propertyName = TableEntityProcessor.keyProperties.Timestamp;
@@ -403,16 +408,21 @@ export default class QueryBuilderViewModel {
} }
switch (type) { switch (type) {
case Constants.TableType.DateTime: case Constants.TableType.DateTime:
// eslint-disable-next-line no-useless-escape
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} \'${DateTimeUtilities.convertJSDateToTicksWithPadding( return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} \'${DateTimeUtilities.convertJSDateToTicksWithPadding(
value value
// eslint-disable-next-line no-useless-escape
)}\'${rightParentheses}`; )}\'${rightParentheses}`;
case Constants.TableType.Int64: case Constants.TableType.Int64:
// eslint-disable-next-line no-useless-escape
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} \'${Utilities.padLongWithZeros( return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} \'${Utilities.padLongWithZeros(
value value
// eslint-disable-next-line no-useless-escape
)}\'${rightParentheses}`; )}\'${rightParentheses}`;
case Constants.TableType.String: case Constants.TableType.String:
case Constants.TableType.Guid: case Constants.TableType.Guid:
case Constants.TableType.Binary: case Constants.TableType.Binary:
// eslint-disable-next-line no-useless-escape
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} \'${value}\'${rightParentheses}`; return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} \'${value}\'${rightParentheses}`;
default: default:
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} ${value}${rightParentheses}`; return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} ${value}${rightParentheses}`;
@@ -434,6 +444,7 @@ export default class QueryBuilderViewModel {
type === Constants.CassandraType.Ascii || type === Constants.CassandraType.Ascii ||
type === Constants.CassandraType.Varchar type === Constants.CassandraType.Varchar
) { ) {
// eslint-disable-next-line no-useless-escape
return ` ${clauseRule.toLowerCase()} ${leftParentheses} ${propertyName} ${operator} \'${value}\'${rightParentheses}`; return ` ${clauseRule.toLowerCase()} ${leftParentheses} ${propertyName} ${operator} \'${value}\'${rightParentheses}`;
} }
return ` ${clauseRule.toLowerCase()} ${leftParentheses} ${propertyName} ${operator} ${value}${rightParentheses}`; return ` ${clauseRule.toLowerCase()} ${leftParentheses} ${propertyName} ${operator} ${value}${rightParentheses}`;
@@ -454,7 +465,7 @@ export default class QueryBuilderViewModel {
case Constants.Operator.NotEqualTo: case Constants.Operator.NotEqualTo:
return Constants.ODataOperator.NotEqualTo; return Constants.ODataOperator.NotEqualTo;
} }
return null; return undefined;
}; };
public groupClauses = (): void => { public groupClauses = (): void => {
@@ -463,11 +474,11 @@ export default class QueryBuilderViewModel {
this.updateCanGroupClauses(); this.updateCanGroupClauses();
}; };
public addClauseIndex = (index: number, data: any): void => { public addClauseIndex = (index: number): void => {
if (index < 0) { if (index < 0) {
index = 0; index = 0;
} }
var newClause = new QueryClauseViewModel( const newClause = new QueryClauseViewModel(
this, this,
"And", "And",
"", "",
@@ -492,28 +503,28 @@ export default class QueryBuilderViewModel {
// adds a new clause to the end of the array // adds a new clause to the end of the array
public addNewClause = (): void => { public addNewClause = (): void => {
this.addClauseIndex(this.clauseArray().length, null); this.addClauseIndex(this.clauseArray().length);
}; };
public onAddClauseKeyDown = (index: number, data: any, event: KeyboardEvent, source: any): boolean => { public onAddClauseKeyDown = (index: number, event: KeyboardEvent): boolean => {
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) { if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
this.addClauseIndex(index, data); this.addClauseIndex(index);
event.stopPropagation(); event.stopPropagation();
return false; return false;
} }
return true; return true;
}; };
public onAddNewClauseKeyDown = (source: any, event: KeyboardEvent): boolean => { public onAddNewClauseKeyDown = (event: KeyboardEvent): boolean => {
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) { if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
this.addClauseIndex(this.clauseArray().length - 1, null); this.addClauseIndex(this.clauseArray().length - 1);
event.stopPropagation(); event.stopPropagation();
return false; return false;
} }
return true; return true;
}; };
public deleteClause = (index: number, data: any): void => { public deleteClause = (index: number): void => {
this.deleteClauseImpl(index); this.deleteClauseImpl(index);
if (this.clauseArray().length !== 0) { if (this.clauseArray().length !== 0) {
this.clauseArray()[0].and_or(""); this.clauseArray()[0].and_or("");
@@ -523,9 +534,9 @@ export default class QueryBuilderViewModel {
$(window).resize(); $(window).resize();
}; };
public onDeleteClauseKeyDown = (index: number, data: any, event: KeyboardEvent, source: any): boolean => { public onDeleteClauseKeyDown = (index: number, event: KeyboardEvent): boolean => {
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) { if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
this.deleteClause(index, data); this.deleteClause(index);
event.stopPropagation(); event.stopPropagation();
return false; return false;
} }
@@ -539,25 +550,26 @@ export default class QueryBuilderViewModel {
* (transparent) or its parent group view models. * (transparent) or its parent group view models.
*/ */
public getClauseGroupViewModels = (clause: QueryClauseViewModel): ClauseGroupViewModel[] => { public getClauseGroupViewModels = (clause: QueryClauseViewModel): ClauseGroupViewModel[] => {
var placeHolderGroupViewModel = new ClauseGroupViewModel(this.queryClauses, false, this); const placeHolderGroupViewModel = new ClauseGroupViewModel(this.queryClauses, false, this);
var treeDepth = this.queryClauses.getTreeDepth(); const treeDepth = this.queryClauses.getTreeDepth();
var groupViewModels = new Array<ClauseGroupViewModel>(treeDepth); const groupViewModels = new Array<ClauseGroupViewModel>(treeDepth);
// Prefill the arry with placeholders. // Prefill the arry with placeholders.
for (var i = 0; i < groupViewModels.length; i++) { for (let i = 0; i < groupViewModels.length; i++) {
groupViewModels[i] = placeHolderGroupViewModel; groupViewModels[i] = placeHolderGroupViewModel;
} }
var currentGroup = clause.clauseGroup; let currentGroup = clause.clauseGroup;
// This function determines whether the path from clause to the current group is on the left most. // This function determines whether the path from clause to the current group is on the left most.
var isLeftMostPath = (): boolean => { const isLeftMostPath = (): boolean => {
var group = clause.clauseGroup; let group = clause.clauseGroup;
if (group.children.indexOf(clause) !== 0) { if (group.children.indexOf(clause) !== 0) {
return false; return false;
} }
// eslint-disable-next-line no-constant-condition
while (true) { while (true) {
if (group.getId() === currentGroup.getId()) { if (group.getId() === currentGroup.getId()) {
break; break;
@@ -573,13 +585,14 @@ export default class QueryBuilderViewModel {
}; };
// This function determines whether the path from clause to the current group is on the right most. // This function determines whether the path from clause to the current group is on the right most.
var isRightMostPath = (): boolean => { const isRightMostPath = (): boolean => {
var group = clause.clauseGroup; let group = clause.clauseGroup;
if (group.children.indexOf(clause) !== group.children.length - 1) { if (group.children.indexOf(clause) !== group.children.length - 1) {
return false; return false;
} }
// eslint-disable-next-line no-constant-condition
while (true) { while (true) {
if (group.getId() === currentGroup.getId()) { if (group.getId() === currentGroup.getId()) {
break; break;
@@ -594,26 +607,26 @@ export default class QueryBuilderViewModel {
return true; return true;
}; };
var vmIndex = groupViewModels.length - 1; let vmIndex = groupViewModels.length - 1;
var skipIndex = -1; let skipIndex = -1;
var lastDepth = clause.groupDepth; let lastDepth = clause.groupDepth;
while (!currentGroup.isRootGroup) { while (!currentGroup.isRootGroup) {
// The current group will be rendered at least once, and if there are any sibling groups deeper // The current group will be rendered at least once, and if there are any sibling groups deeper
// than the current group, we will repeat rendering the current group to fill up the gap between // than the current group, we will repeat rendering the current group to fill up the gap between
// current & deepest sibling. // current & deepest sibling.
var deepestInSiblings = currentGroup.findDeepestGroupInChildren(skipIndex).getCurrentGroupDepth(); const deepestInSiblings = currentGroup.findDeepestGroupInChildren(skipIndex).getCurrentGroupDepth();
// Find out the depth difference between the deepest group under the siblings of currentGroup and // Find out the depth difference between the deepest group under the siblings of currentGroup and
// the deepest group under currentGroup. If the result n is a positive number, it means there are // the deepest group under currentGroup. If the result n is a positive number, it means there are
// deeper groups in siblings and we need to draw n + 1 group blocks on UI to fill up the depth // deeper groups in siblings and we need to draw n + 1 group blocks on UI to fill up the depth
// differences. If the result n is a negative number, it means current group contains the deepest // differences. If the result n is a negative number, it means current group contains the deepest
// sub-group, we only need to draw the group block once. // sub-group, we only need to draw the group block once.
var repeatCount = Math.max(deepestInSiblings - lastDepth, 0); const repeatCount = Math.max(deepestInSiblings - lastDepth, 0);
for (var i = 0; i <= repeatCount; i++) { for (let i = 0; i <= repeatCount; i++) {
var isLeftMost = isLeftMostPath(); const isLeftMost = isLeftMostPath();
var isRightMost = isRightMostPath(); const isRightMost = isRightMostPath();
var groupViewModel = new ClauseGroupViewModel(currentGroup, i === 0 && isLeftMost, this); const groupViewModel = new ClauseGroupViewModel(currentGroup, i === 0 && isLeftMost, this);
groupViewModel.showTopBorder(isLeftMost); groupViewModel.showTopBorder(isLeftMost);
groupViewModel.showBottomBorder(isRightMost); groupViewModel.showBottomBorder(isRightMost);
@@ -635,9 +648,9 @@ export default class QueryBuilderViewModel {
}; };
public addCustomRange(timestamp: CustomTimestampHelper.ITimestampQuery, clauseToAdd: QueryClauseViewModel): void { public addCustomRange(timestamp: CustomTimestampHelper.ITimestampQuery, clauseToAdd: QueryClauseViewModel): void {
var index = this.clauseArray.peek().indexOf(clauseToAdd); const index = this.clauseArray.peek().indexOf(clauseToAdd);
var newClause = new QueryClauseViewModel( const newClause = new QueryClauseViewModel(
this, this,
//this._tableEntityListViewModel.tableExplorerContext.hostProxy, //this._tableEntityListViewModel.tableExplorerContext.hostProxy,
"And", "And",
@@ -662,10 +675,10 @@ export default class QueryBuilderViewModel {
} }
private scrollToBottom(): void { private scrollToBottom(): void {
var scrollBox = document.getElementById("scroll"); const scrollBox = document.getElementById("scroll");
if (!this.scrollEventListener) { if (!this.scrollEventListener) {
scrollBox.addEventListener("scroll", function () { scrollBox.addEventListener("scroll", function () {
var translate = "translate(0," + this.scrollTop + "px)"; const translate = "translate(0," + this.scrollTop + "px)";
const allTh = <NodeListOf<HTMLElement>>this.querySelectorAll("thead td"); const allTh = <NodeListOf<HTMLElement>>this.querySelectorAll("thead td");
for (let i = 0; i < allTh.length; i++) { for (let i = 0; i < allTh.length; i++) {
allTh[i].style.transform = translate; allTh[i].style.transform = translate;
@@ -673,7 +686,7 @@ export default class QueryBuilderViewModel {
}); });
this.scrollEventListener = true; this.scrollEventListener = true;
} }
var isScrolledToBottom = scrollBox.scrollHeight - scrollBox.clientHeight <= scrollBox.scrollHeight + 1; const isScrolledToBottom = scrollBox.scrollHeight - scrollBox.clientHeight <= scrollBox.scrollHeight + 1;
if (isScrolledToBottom) { if (isScrolledToBottom) {
scrollBox.scrollTop = scrollBox.scrollHeight - scrollBox.clientHeight; scrollBox.scrollTop = scrollBox.scrollHeight - scrollBox.clientHeight;
} }
@@ -685,8 +698,8 @@ export default class QueryBuilderViewModel {
} }
private deleteClauseImpl(index: number): void { private deleteClauseImpl(index: number): void {
var clause = this.clauseArray()[index]; const clause = this.clauseArray()[index];
var previousClause = index === 0 ? 0 : index - 1; const previousClause = index === 0 ? 0 : index - 1;
this.queryClauses.deleteClause(clause); this.queryClauses.deleteClause(clause);
this.updateClauseArray(); this.updateClauseArray();
if (this.clauseArray()[previousClause]) { if (this.clauseArray()[previousClause]) {
@@ -731,7 +744,7 @@ export default class QueryBuilderViewModel {
private timestampToSqlValue(clause: QueryClauseViewModel): string { private timestampToSqlValue(clause: QueryClauseViewModel): string {
if (clause.isValue()) { if (clause.isValue()) {
return null; return undefined;
} else if (clause.isTimestamp()) { } else if (clause.isTimestamp()) {
return this.getTimeStampToSqlQuery(clause); return this.getTimeStampToSqlQuery(clause);
// } else if (clause.isCustomLastTimestamp()) { // } else if (clause.isCustomLastTimestamp()) {
@@ -743,7 +756,7 @@ export default class QueryBuilderViewModel {
return clause.customTimeValue(); return clause.customTimeValue();
} }
} }
return null; return undefined;
} }
private getTimeStampToQuery(clause: QueryClauseViewModel): void { private getTimeStampToQuery(clause: QueryClauseViewModel): void {
@@ -789,7 +802,7 @@ export default class QueryBuilderViewModel {
case Constants.timeOptions.currentYear: case Constants.timeOptions.currentYear:
return CustomTimestampHelper._queryCurrentYearLocal(); return CustomTimestampHelper._queryCurrentYearLocal();
} }
return null; return undefined;
} }
public checkIfClauseChanged(): void { public checkIfClauseChanged(): void {

View File

@@ -14,7 +14,7 @@ export default class QueryClauseViewModel {
public field: ko.Observable<string>; public field: ko.Observable<string>;
public type: ko.Observable<string>; public type: ko.Observable<string>;
public operator: ko.Observable<string>; public operator: ko.Observable<string>;
public value: ko.Observable<any>; public value: ko.Observable<string>;
public timeValue: ko.Observable<string>; public timeValue: ko.Observable<string>;
public customTimeValue: ko.Observable<string>; public customTimeValue: ko.Observable<string>;
public canAnd: ko.Observable<boolean>; public canAnd: ko.Observable<boolean>;
@@ -39,7 +39,7 @@ export default class QueryClauseViewModel {
field: string, field: string,
type: string, type: string,
operator: string, operator: string,
value: any, value: string,
canAnd: boolean, canAnd: boolean,
timeValue: string, timeValue: string,
customTimeValue: string, customTimeValue: string,
@@ -88,30 +88,30 @@ export default class QueryClauseViewModel {
userContext.apiType !== "Cassandra" userContext.apiType !== "Cassandra"
); );
this.and_or.subscribe((value) => { this.and_or.subscribe(() => {
this._queryBuilderViewModel.checkIfClauseChanged(); this._queryBuilderViewModel.checkIfClauseChanged();
}); });
this.field.subscribe((value) => { this.field.subscribe(() => {
this.changeField(); this.changeField();
}); });
this.type.subscribe((value) => { this.type.subscribe(() => {
this.changeType(); this.changeType();
}); });
this.timeValue.subscribe((value) => { this.timeValue.subscribe(() => {
// if (this.timeValue() === QueryBuilderConstants.timeOptions.custom) { // if (this.timeValue() === QueryBuilderConstants.timeOptions.custom) {
// this.customTimestampDialog(); // this.customTimestampDialog();
// } // }
}); });
this.customTimeValue.subscribe((value) => { this.customTimeValue.subscribe(() => {
this._queryBuilderViewModel.checkIfClauseChanged(); this._queryBuilderViewModel.checkIfClauseChanged();
}); });
this.value.subscribe((value) => { this.value.subscribe(() => {
this._queryBuilderViewModel.checkIfClauseChanged(); this._queryBuilderViewModel.checkIfClauseChanged();
}); });
this.operator.subscribe((value) => { this.operator.subscribe(() => {
this._queryBuilderViewModel.checkIfClauseChanged(); this._queryBuilderViewModel.checkIfClauseChanged();
}); });
this._groupCheckSubscription = this.checkedForGrouping.subscribe((value) => { this._groupCheckSubscription = this.checkedForGrouping.subscribe(() => {
this._queryBuilderViewModel.updateCanGroupClauses(); this._queryBuilderViewModel.updateCanGroupClauses();
}); });
this.isAndOrFocused = ko.observable<boolean>(false); this.isAndOrFocused = ko.observable<boolean>(false);
@@ -280,7 +280,7 @@ export default class QueryClauseViewModel {
this._groupCheckSubscription.dispose(); this._groupCheckSubscription.dispose();
} }
this.clauseGroup = null; this.clauseGroup = undefined;
this._queryBuilderViewModel = null; this._queryBuilderViewModel = undefined;
} }
} }

View File

@@ -202,14 +202,21 @@ export class CassandraAPIDataClient extends TableDataClient {
let updateQuery = `UPDATE ${collection.databaseId}.${collection.id()}`; let updateQuery = `UPDATE ${collection.databaseId}.${collection.id()}`;
let isPropertyUpdated = false; let isPropertyUpdated = false;
let isFirstPropertyToUpdate = true;
for (let property in newEntity) { for (let property in newEntity) {
if ( if (
!originalDocument[property] || !originalDocument[property] ||
newEntity[property]._.toString() !== originalDocument[property]._.toString() newEntity[property]._.toString() !== originalDocument[property]._.toString()
) { ) {
updateQuery += this.isStringType(newEntity[property].$) let propertyQuerySegment = this.isStringType(newEntity[property].$)
? ` SET ${property} = '${newEntity[property]._}',` ? `${property} = '${newEntity[property]._}',`
: ` SET ${property} = ${newEntity[property]._},`; : `${property} = ${newEntity[property]._},`;
// Only add the "SET" keyword once
if (isFirstPropertyToUpdate) {
propertyQuerySegment = " SET " + propertyQuerySegment;
isFirstPropertyToUpdate = false;
}
updateQuery += propertyQuerySegment;
isPropertyUpdated = true; isPropertyUpdated = true;
} }
} }

View File

@@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/switch-exhaustiveness-check */
import { ConflictDefinition, FeedOptions, QueryIterator, Resource } from "@azure/cosmos"; import { ConflictDefinition, FeedOptions, QueryIterator, Resource } from "@azure/cosmos";
import * as ko from "knockout"; import * as ko from "knockout";
import Q from "q"; import Q from "q";
@@ -69,13 +71,13 @@ export default class ConflictsTab extends TabsBase {
ViewModels.DocumentExplorerState.noDocumentSelected ViewModels.DocumentExplorerState.noDocumentSelected
); );
this.selectedConflictId = ko.observable<ConflictId>(); this.selectedConflictId = ko.observable<ConflictId>();
this.selectedConflictContent = editable.observable<any>(""); this.selectedConflictContent = editable.observable<string>("");
this.selectedConflictCurrent = editable.observable<any>(""); this.selectedConflictCurrent = editable.observable<string>("");
this.partitionKey = options.partitionKey || (this.collection && this.collection.partitionKey); this.partitionKey = options.partitionKey || (this.collection && this.collection.partitionKey);
this.conflictIds = options.conflictIds; this.conflictIds = options.conflictIds;
this.partitionKeyPropertyHeader = this.partitionKeyPropertyHeader =
(this.collection && this.collection.partitionKeyPropertyHeader) || this._getPartitionKeyPropertyHeader(); (this.collection && this.collection.partitionKeyPropertyHeader) || this._getPartitionKeyPropertyHeader();
this.partitionKeyProperty = !!this.partitionKeyPropertyHeader this.partitionKeyProperty = this.partitionKeyPropertyHeader
? this.partitionKeyPropertyHeader.replace(/[/]+/g, ".").substr(1).replace(/[']+/g, "") ? this.partitionKeyPropertyHeader.replace(/[/]+/g, ".").substr(1).replace(/[']+/g, "")
: null; : null;
@@ -187,7 +189,7 @@ export default class ConflictsTab extends TabsBase {
this.buildCommandBarOptions(); this.buildCommandBarOptions();
this.shouldShowDiffEditor = ko.pureComputed<boolean>(() => { this.shouldShowDiffEditor = ko.pureComputed<boolean>(() => {
const documentHasContent: boolean = const documentHasContent: boolean =
this.selectedConflictContent() != null && this.selectedConflictContent().length > 0; this.selectedConflictContent() !== null && this.selectedConflictContent().length > 0;
const operationIsReplace: boolean = this.conflictOperation() === Constants.ConflictOperationType.Replace; const operationIsReplace: boolean = this.conflictOperation() === Constants.ConflictOperationType.Replace;
const isLoadingData: boolean = this.loadingConflictData(); const isLoadingData: boolean = this.loadingConflictData();
return documentHasContent && operationIsReplace && !isLoadingData; return documentHasContent && operationIsReplace && !isLoadingData;
@@ -195,7 +197,7 @@ export default class ConflictsTab extends TabsBase {
this.shouldShowEditor = ko.pureComputed<boolean>(() => { this.shouldShowEditor = ko.pureComputed<boolean>(() => {
const documentHasContent: boolean = const documentHasContent: boolean =
this.selectedConflictContent() != null && this.selectedConflictContent().length > 0; this.selectedConflictContent() !== null && this.selectedConflictContent().length > 0;
const operationIsInsert: boolean = this.conflictOperation() === Constants.ConflictOperationType.Create; const operationIsInsert: boolean = this.conflictOperation() === Constants.ConflictOperationType.Create;
const operationIsDelete: boolean = this.conflictOperation() === Constants.ConflictOperationType.Delete; const operationIsDelete: boolean = this.conflictOperation() === Constants.ConflictOperationType.Delete;
const isLoadingData: boolean = this.loadingConflictData(); const isLoadingData: boolean = this.loadingConflictData();
@@ -242,7 +244,7 @@ export default class ConflictsTab extends TabsBase {
return true; return true;
}; };
public onConflictIdClick(clickedDocumentId: ConflictId): Q.Promise<any> { public onConflictIdClick(): Q.Promise<unknown> {
if (this.editorState() !== ViewModels.DocumentExplorerState.noDocumentSelected) { if (this.editorState() !== ViewModels.DocumentExplorerState.noDocumentSelected) {
return Q(); return Q();
} }
@@ -405,19 +407,19 @@ export default class ConflictsTab extends TabsBase {
} }
}; };
public onDiscardClick = (): Q.Promise<any> => { public onDiscardClick = (): Q.Promise<unknown> => {
this.selectedConflictContent(this.selectedConflictContent.getEditableOriginalValue()); this.selectedConflictContent(this.selectedConflictContent.getEditableOriginalValue());
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits); this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits);
return Q(); return Q();
}; };
public onValidDocumentEdit(): Q.Promise<any> { public onValidDocumentEdit(): Q.Promise<unknown> {
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentDirtyValid); this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentDirtyValid);
return Q(); return Q();
} }
public onInvalidDocumentEdit(): Q.Promise<any> { public onInvalidDocumentEdit(): Q.Promise<unknown> {
if ( if (
this.editorState() === ViewModels.DocumentExplorerState.exisitingDocumentNoEdits || this.editorState() === ViewModels.DocumentExplorerState.exisitingDocumentNoEdits ||
this.editorState() === ViewModels.DocumentExplorerState.exisitingDocumentDirtyValid this.editorState() === ViewModels.DocumentExplorerState.exisitingDocumentDirtyValid
@@ -442,7 +444,7 @@ export default class ConflictsTab extends TabsBase {
this._documentsIterator = await this.createIterator(); this._documentsIterator = await this.createIterator();
await this.loadNextPage(); await this.loadNextPage();
} catch (error) { } catch (error) {
if (this.onLoadStartKey != null && this.onLoadStartKey != undefined) { if (this.onLoadStartKey !== null && this.onLoadStartKey !== undefined) {
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.Tab, Action.Tab,
{ {
@@ -471,7 +473,7 @@ export default class ConflictsTab extends TabsBase {
return queryConflicts(this.collection.databaseId, this.collection.id(), query, options as FeedOptions); return queryConflicts(this.collection.databaseId, this.collection.id(), query, options as FeedOptions);
} }
public loadNextPage(): Q.Promise<any> { public loadNextPage(): Q.Promise<unknown> {
this.isExecuting(true); this.isExecuting(true);
this.isExecutionError(false); this.isExecutionError(false);
return this._loadNextPageInternal() return this._loadNextPageInternal()
@@ -491,7 +493,7 @@ export default class ConflictsTab extends TabsBase {
const merged = currentConflicts.concat(nextConflictIds); const merged = currentConflicts.concat(nextConflictIds);
this.conflictIds(merged); this.conflictIds(merged);
if (this.onLoadStartKey != null && this.onLoadStartKey != undefined) { if (this.onLoadStartKey !== null && this.onLoadStartKey !== undefined) {
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.Tab, Action.Tab,
{ {
@@ -508,7 +510,7 @@ export default class ConflictsTab extends TabsBase {
}, },
(error) => { (error) => {
this.isExecutionError(true); this.isExecutionError(true);
if (this.onLoadStartKey != null && this.onLoadStartKey != undefined) { if (this.onLoadStartKey !== null && this.onLoadStartKey !== undefined) {
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.Tab, Action.Tab,
{ {
@@ -541,18 +543,18 @@ export default class ConflictsTab extends TabsBase {
return Q(this._documentsIterator.fetchNext().then((response) => response.resources)); return Q(this._documentsIterator.fetchNext().then((response) => response.resources));
} }
protected _onEditorContentChange(newContent: string) { // eslint-disable-next-line @typescript-eslint/no-unused-vars
protected _onEditorContentChange(_newContent: string) {
try { try {
const parsed: any = JSON.parse(newContent);
this.onValidDocumentEdit(); this.onValidDocumentEdit();
} catch (e) { } catch (e) {
this.onInvalidDocumentEdit(); this.onInvalidDocumentEdit();
} }
} }
public initDocumentEditorForCreate(documentId: ConflictId, documentToInsert: any): Q.Promise<any> { public initDocumentEditorForCreate(documentId: ConflictId, documentToInsert: any): Q.Promise<unknown> {
if (documentId) { if (documentId) {
let parsedConflictContent: any = JSON.parse(documentToInsert); const parsedConflictContent: any = JSON.parse(documentToInsert);
const renderedConflictContent: string = this.renderObjectForEditor(parsedConflictContent, null, 4); const renderedConflictContent: string = this.renderObjectForEditor(parsedConflictContent, null, 4);
this.selectedConflictContent.setBaseline(renderedConflictContent); this.selectedConflictContent.setBaseline(renderedConflictContent);
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits); this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits);
@@ -565,7 +567,7 @@ export default class ConflictsTab extends TabsBase {
documentId: ConflictId, documentId: ConflictId,
conflictContent: any, conflictContent: any,
currentContent: any currentContent: any
): Q.Promise<any> { ): Q.Promise<unknown> {
if (documentId) { if (documentId) {
currentContent = ConflictsTab.removeSystemProperties(currentContent); currentContent = ConflictsTab.removeSystemProperties(currentContent);
const renderedCurrentContent: string = this.renderObjectForEditor(currentContent, null, 4); const renderedCurrentContent: string = this.renderObjectForEditor(currentContent, null, 4);
@@ -582,7 +584,7 @@ export default class ConflictsTab extends TabsBase {
return Q(); return Q();
} }
public initDocumentEditorForDelete(documentId: ConflictId, documentToDelete: any): Q.Promise<any> { public initDocumentEditorForDelete(documentId: ConflictId, documentToDelete: any): Q.Promise<unknown> {
if (documentId) { if (documentId) {
let parsedDocumentToDelete: any = JSON.parse(documentToDelete); let parsedDocumentToDelete: any = JSON.parse(documentToDelete);
parsedDocumentToDelete = ConflictsTab.removeSystemProperties(parsedDocumentToDelete); parsedDocumentToDelete = ConflictsTab.removeSystemProperties(parsedDocumentToDelete);
@@ -594,7 +596,8 @@ export default class ConflictsTab extends TabsBase {
return Q(); return Q();
} }
public initDocumentEditorForNoOp(documentId: ConflictId): Q.Promise<any> { // eslint-disable-next-line @typescript-eslint/no-unused-vars
public initDocumentEditorForNoOp(_documentId: ConflictId): Q.Promise<unknown> {
this.selectedConflictContent(null); this.selectedConflictContent(null);
this.selectedConflictCurrent(null); this.selectedConflictCurrent(null);
this.editorState(ViewModels.DocumentExplorerState.noDocumentSelected); this.editorState(ViewModels.DocumentExplorerState.noDocumentSelected);

View File

@@ -118,7 +118,7 @@ export default class NotebookTabV2 extends NotebookTabBase {
const saveButtonChildren = []; const saveButtonChildren = [];
if (this.container.notebookManager?.gitHubOAuthService.isLoggedIn()) { if (this.container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
saveButtonChildren.push({ saveButtonChildren.push({
iconName: "Copy", iconName: copyToLabel,
onCommandClick: () => this.copyNotebook(), onCommandClick: () => this.copyNotebook(),
commandButtonLabel: copyToLabel, commandButtonLabel: copyToLabel,
hasPopup: false, hasPopup: false,

View File

@@ -966,15 +966,10 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
<> <>
<span className="queryResultDivider">|</span> <span className="queryResultDivider">|</span>
<span className="queryResultNextEnable"> <span className="queryResultNextEnable">
<div <a onClick={this.onFetchNextPageClick.bind(this)}>
onClick={this.onFetchNextPageClick.bind(this)} <span>Load more</span>
role="button"
tabIndex={0}
onKeyPress={this.onFetchNextPageClick.bind(this)}
>
<span className="moreOption">Load more</span>
<img className="queryResultnextImg" src={QueryEditorNext} alt="Fetch next page" /> <img className="queryResultnextImg" src={QueryEditorNext} alt="Fetch next page" />
</div> </a>
</span> </span>
</> </>
)} )}
@@ -1020,7 +1015,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
</div> </div>
{this.state.isQueryMetricsEnabled && ( {this.state.isQueryMetricsEnabled && (
<div className="downloadMetricsLinkContainer"> <div className="downloadMetricsLinkContainer">
<span <a
id="downloadMetricsLink" id="downloadMetricsLink"
role="button" role="button"
tabIndex={0} tabIndex={0}
@@ -1035,7 +1030,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
alt="download query metrics csv" alt="download query metrics csv"
/> />
<span>Per-partition query metrics (CSV)</span> <span>Per-partition query metrics (CSV)</span>
</span> </a>
</div> </div>
)} )}
</div> </div>
@@ -1049,7 +1044,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
<div className="errorContent"> <div className="errorContent">
<span className="errorMessage">{this.state.error}</span> <span className="errorMessage">{this.state.error}</span>
<span className="errorDetailsLink"> <span className="errorDetailsLink">
<span <a
onClick={() => this.onErrorDetailsClick()} onClick={() => this.onErrorDetailsClick()}
onKeyPress={(event: React.KeyboardEvent<HTMLAnchorElement>) => onKeyPress={(event: React.KeyboardEvent<HTMLAnchorElement>) =>
this.onErrorDetailsKeyPress(event) this.onErrorDetailsKeyPress(event)
@@ -1057,11 +1052,9 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
id="error-display" id="error-display"
tabIndex={0} tabIndex={0}
aria-label="Error details link" aria-label="Error details link"
role="button"
className="moreOption"
> >
More details More details
</span> </a>
</span> </span>
</div> </div>
</div> </div>

View File

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

View File

@@ -53,7 +53,6 @@ function TabNav({ tab, active }: { tab: Tab; active: boolean }) {
tabIndex={0} tabIndex={0}
role="tab" role="tab"
ref={focusTab} ref={focusTab}
onFocus={() => setHovering(true)}
> >
<span className="tabNavContentContainer"> <span className="tabNavContentContainer">
<a data-toggle="tab" href={"#" + tab.tabId} tabIndex={-1}> <a data-toggle="tab" href={"#" + tab.tabId} tabIndex={-1}>

View File

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

View File

@@ -276,6 +276,7 @@ export default class UserDefinedFunctionTabContent extends Component<
className="trigger-field" className="trigger-field"
label="User Defined Function Id" label="User Defined Function Id"
id="entityTimeId" id="entityTimeId"
autoFocus
required required
readOnly={!isUdfIdEditable} readOnly={!isUdfIdEditable}
type="text" type="text"

View File

@@ -128,7 +128,12 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
notebooksTree.children.push(buildGalleryNotebooksTree()); notebooksTree.children.push(buildGalleryNotebooksTree());
} }
if (myNotebooksContentRoot && useNotebook.getState().connectionInfo.status == ConnectionStatusType.Connected) { if (
myNotebooksContentRoot &&
((NotebookUtil.isPhoenixEnabled() &&
useNotebook.getState().connectionInfo.status === ConnectionStatusType.Connected) ||
userContext.features.phoenix === false)
) {
notebooksTree.children.push(buildMyNotebooksTree()); notebooksTree.children.push(buildMyNotebooksTree());
} }
if (container.notebookManager?.gitHubOAuthService.isLoggedIn()) { if (container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
@@ -162,7 +167,11 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
myNotebooksContentRoot, myNotebooksContentRoot,
(item: NotebookContentItem) => { (item: NotebookContentItem) => {
container.openNotebook(item).then((hasOpened) => { container.openNotebook(item).then((hasOpened) => {
if (hasOpened) { if (
hasOpened &&
userContext.features.notebooksTemporarilyDown === false &&
userContext.features.phoenix === false
) {
mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item); mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item);
} }
}); });
@@ -181,7 +190,11 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
gitHubNotebooksContentRoot, gitHubNotebooksContentRoot,
(item: NotebookContentItem) => { (item: NotebookContentItem) => {
container.openNotebook(item).then((hasOpened) => { container.openNotebook(item).then((hasOpened) => {
if (hasOpened) { if (
hasOpened &&
userContext.features.notebooksTemporarilyDown === false &&
userContext.features.phoenix === false
) {
mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item); mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item);
} }
}); });
@@ -213,23 +226,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
}, },
}, },
]; ];
const connectGitContextMenu: TreeNodeMenuItem[] = [ gitHubNotebooksTree.contextMenu = manageGitContextMenu;
{
label: "Connect to GitHub",
onClick: () =>
useSidePanel
.getState()
.openSidePanel(
"Connect to GitHub",
<GitHubReposPanel
explorer={container}
gitHubClientProp={container.notebookManager.gitHubClient}
junoClientProp={container.notebookManager.junoClient}
/>
),
},
];
gitHubNotebooksTree.contextMenu = isConnected ? manageGitContextMenu : connectGitContextMenu;
gitHubNotebooksTree.isExpanded = true; gitHubNotebooksTree.isExpanded = true;
gitHubNotebooksTree.isAlphaSorted = true; gitHubNotebooksTree.isAlphaSorted = true;

View File

@@ -45,7 +45,6 @@ import UserDefinedFunction from "./UserDefinedFunction";
export class ResourceTreeAdapter implements ReactAdapter { export class ResourceTreeAdapter implements ReactAdapter {
public static readonly MyNotebooksTitle = "My Notebooks"; public static readonly MyNotebooksTitle = "My Notebooks";
public static readonly MyNotebooksScratchTitle = "My Notebooks Scratch";
public static readonly GitHubReposTitle = "GitHub repos"; public static readonly GitHubReposTitle = "GitHub repos";
private static readonly DataTitle = "DATA"; private static readonly DataTitle = "DATA";

View File

@@ -22,6 +22,7 @@ export default class Trigger {
public triggerType: ko.Observable<string>; public triggerType: ko.Observable<string>;
public triggerOperation: ko.Observable<string>; public triggerOperation: ko.Observable<string>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(container: Explorer, collection: ViewModels.Collection, data: any) { constructor(container: Explorer, collection: ViewModels.Collection, data: any) {
this.nodeKind = "Trigger"; this.nodeKind = "Trigger";
this.container = container; this.container = container;
@@ -34,7 +35,7 @@ export default class Trigger {
this.triggerType = ko.observable(data.triggerType); this.triggerType = ko.observable(data.triggerType);
} }
public select() { public select(): void {
useSelectedNode.getState().setSelectedNode(this); useSelectedNode.getState().setSelectedNode(this);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "Trigger node", description: "Trigger node",
@@ -43,7 +44,8 @@ export default class Trigger {
}); });
} }
public static create(source: ViewModels.Collection, event: MouseEvent) { // eslint-disable-next-line @typescript-eslint/no-unused-vars
public static create(source: ViewModels.Collection, _event: MouseEvent): void {
const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Triggers).length + 1; const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Triggers).length + 1;
const trigger = <StoredProcedureDefinition>{ const trigger = <StoredProcedureDefinition>{
id: "", id: "",
@@ -99,7 +101,7 @@ export default class Trigger {
} }
}; };
public delete() { public delete(): void {
useDialog.getState().showOkCancelModalDialog( useDialog.getState().showOkCancelModalDialog(
"Confirm delete", "Confirm delete",
"Are you sure you want to delete the trigger?", "Are you sure you want to delete the trigger?",
@@ -110,7 +112,8 @@ export default class Trigger {
useTabs.getState().closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid); useTabs.getState().closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid);
this.collection.children.remove(this); this.collection.children.remove(this);
}, },
(reason) => {} // eslint-disable-next-line @typescript-eslint/no-empty-function
() => {}
); );
}, },
"Cancel", "Cancel",

View File

@@ -64,7 +64,7 @@ export class GitHubOAuthService {
return params.state; return params.state;
} }
public async finishOAuth(params: IGitHubConnectorParams) { public async finishOAuth(params: IGitHubConnectorParams): Promise<void> {
try { try {
this.validateState(params.state); this.validateState(params.state);
const response = await this.junoClient.getGitHubToken(params.code); const response = await this.junoClient.getGitHubToken(params.code);
@@ -113,7 +113,7 @@ export class GitHubOAuthService {
return this.state; return this.state;
} }
public resetToken() { public resetToken(): void {
this.token(undefined); this.token(undefined);
} }

View File

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

View File

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

View File

@@ -50,7 +50,8 @@ describe("Pinned repos", () => {
}); });
it("updatePinnedRepos invokes pinned repos subscribers", async () => { it("updatePinnedRepos invokes pinned repos subscribers", async () => {
const callback = jest.fn().mockImplementation((pinnedRepos: IPinnedRepo[]) => {}); // eslint-disable-next-line @typescript-eslint/no-empty-function
const callback = jest.fn().mockImplementation(() => {});
junoClient.subscribeToPinnedRepos(callback); junoClient.subscribeToPinnedRepos(callback);
const response = await junoClient.updatePinnedRepos(samplePinnedRepos); const response = await junoClient.updatePinnedRepos(samplePinnedRepos);
@@ -60,7 +61,8 @@ describe("Pinned repos", () => {
}); });
it("getPinnedRepos invokes pinned repos subscribers", async () => { it("getPinnedRepos invokes pinned repos subscribers", async () => {
const callback = jest.fn().mockImplementation((pinnedRepos: IPinnedRepo[]) => {}); // eslint-disable-next-line @typescript-eslint/no-empty-function
const callback = jest.fn().mockImplementation(() => {});
junoClient.subscribeToPinnedRepos(callback); junoClient.subscribeToPinnedRepos(callback);
const response = await junoClient.getPinnedRepos("scope"); const response = await junoClient.getPinnedRepos("scope");
@@ -153,7 +155,7 @@ describe("Gallery", () => {
it("getSampleNotebooks", async () => { it("getSampleNotebooks", async () => {
window.fetch = jest.fn().mockReturnValue({ window.fetch = jest.fn().mockReturnValue({
status: HttpStatusCodes.OK, status: HttpStatusCodes.OK,
json: () => undefined as any, json: () => undefined as undefined,
}); });
const response = await junoClient.getSampleNotebooks(); const response = await junoClient.getSampleNotebooks();
@@ -165,7 +167,7 @@ describe("Gallery", () => {
it("getPublicNotebooks", async () => { it("getPublicNotebooks", async () => {
window.fetch = jest.fn().mockReturnValue({ window.fetch = jest.fn().mockReturnValue({
status: HttpStatusCodes.OK, status: HttpStatusCodes.OK,
json: () => undefined as any, json: () => undefined as undefined,
}); });
const response = await junoClient.getPublicNotebooks(); const response = await junoClient.getPublicNotebooks();
@@ -178,7 +180,7 @@ describe("Gallery", () => {
const id = "id"; const id = "id";
window.fetch = jest.fn().mockReturnValue({ window.fetch = jest.fn().mockReturnValue({
status: HttpStatusCodes.OK, status: HttpStatusCodes.OK,
json: () => undefined as any, json: () => undefined as undefined,
}); });
const response = await junoClient.getNotebookInfo(id); const response = await junoClient.getNotebookInfo(id);
@@ -191,7 +193,7 @@ describe("Gallery", () => {
const id = "id"; const id = "id";
window.fetch = jest.fn().mockReturnValue({ window.fetch = jest.fn().mockReturnValue({
status: HttpStatusCodes.OK, status: HttpStatusCodes.OK,
text: () => undefined as any, text: () => undefined as undefined,
}); });
const response = await junoClient.getNotebookContent(id); const response = await junoClient.getNotebookContent(id);
@@ -204,7 +206,7 @@ describe("Gallery", () => {
const id = "id"; const id = "id";
window.fetch = jest.fn().mockReturnValue({ window.fetch = jest.fn().mockReturnValue({
status: HttpStatusCodes.OK, status: HttpStatusCodes.OK,
json: () => undefined as any, json: () => undefined as undefined,
}); });
const response = await junoClient.increaseNotebookViews(id); const response = await junoClient.increaseNotebookViews(id);
@@ -218,7 +220,7 @@ describe("Gallery", () => {
const id = "id"; const id = "id";
window.fetch = jest.fn().mockReturnValue({ window.fetch = jest.fn().mockReturnValue({
status: HttpStatusCodes.OK, status: HttpStatusCodes.OK,
json: () => undefined as any, json: () => undefined as undefined,
}); });
const response = await junoClient.increaseNotebookDownloadCount(id); const response = await junoClient.increaseNotebookDownloadCount(id);
@@ -243,7 +245,7 @@ describe("Gallery", () => {
const id = "id"; const id = "id";
window.fetch = jest.fn().mockReturnValue({ window.fetch = jest.fn().mockReturnValue({
status: HttpStatusCodes.OK, status: HttpStatusCodes.OK,
json: () => undefined as any, json: () => undefined as undefined,
}); });
const response = await junoClient.favoriteNotebook(id); const response = await junoClient.favoriteNotebook(id);
@@ -268,7 +270,7 @@ describe("Gallery", () => {
const id = "id"; const id = "id";
window.fetch = jest.fn().mockReturnValue({ window.fetch = jest.fn().mockReturnValue({
status: HttpStatusCodes.OK, status: HttpStatusCodes.OK,
json: () => undefined as any, json: () => undefined as undefined,
}); });
const response = await junoClient.unfavoriteNotebook(id); const response = await junoClient.unfavoriteNotebook(id);
@@ -292,7 +294,7 @@ describe("Gallery", () => {
it("getFavoriteNotebooks", async () => { it("getFavoriteNotebooks", async () => {
window.fetch = jest.fn().mockReturnValue({ window.fetch = jest.fn().mockReturnValue({
status: HttpStatusCodes.OK, status: HttpStatusCodes.OK,
json: () => undefined as any, json: () => undefined as undefined,
}); });
const response = await junoClient.getFavoriteNotebooks(); const response = await junoClient.getFavoriteNotebooks();
@@ -315,7 +317,7 @@ describe("Gallery", () => {
it("getPublishedNotebooks", async () => { it("getPublishedNotebooks", async () => {
window.fetch = jest.fn().mockReturnValue({ window.fetch = jest.fn().mockReturnValue({
status: HttpStatusCodes.OK, status: HttpStatusCodes.OK,
json: () => undefined as any, json: () => undefined as undefined,
}); });
const response = await junoClient.getPublishedNotebooks(); const response = await junoClient.getPublishedNotebooks();
@@ -339,7 +341,7 @@ describe("Gallery", () => {
const id = "id"; const id = "id";
window.fetch = jest.fn().mockReturnValue({ window.fetch = jest.fn().mockReturnValue({
status: HttpStatusCodes.OK, status: HttpStatusCodes.OK,
json: () => undefined as any, json: () => undefined as undefined,
}); });
const response = await junoClient.deleteNotebook(id); const response = await junoClient.deleteNotebook(id);
@@ -369,7 +371,7 @@ describe("Gallery", () => {
const addLinkToNotebookViewer = true; const addLinkToNotebookViewer = true;
window.fetch = jest.fn().mockReturnValue({ window.fetch = jest.fn().mockReturnValue({
status: HttpStatusCodes.OK, status: HttpStatusCodes.OK,
json: () => undefined as any, json: () => undefined as undefined,
}); });
const response = await junoClient.publishNotebook(name, description, tags, thumbnailUrl, content); const response = await junoClient.publishNotebook(name, description, tags, thumbnailUrl, content);

View File

@@ -62,7 +62,7 @@ export interface IPublishNotebookRequest {
description: string; description: string;
tags: string[]; tags: string[];
thumbnailUrl: string; thumbnailUrl: string;
content: any; content: unknown;
addLinkToNotebookViewer: boolean; addLinkToNotebookViewer: boolean;
} }

View File

@@ -1,7 +1,5 @@
import { ConnectionStatusType, HttpHeaders, HttpStatusCodes } from "../Common/Constants"; import { HttpHeaders, HttpStatusCodes } from "../Common/Constants";
import { configContext } from "../ConfigContext"; import { configContext } from "../ConfigContext";
import { ContainerConnectionInfo } from "../Contracts/DataModels";
import { useNotebook } from "../Explorer/Notebook/useNotebook";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils"; import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
@@ -15,7 +13,6 @@ export interface IPhoenixConnectionInfoResult {
} }
export interface IProvosionData { export interface IProvosionData {
cosmosEndpoint: string; cosmosEndpoint: string;
resourceId: string;
dbAccountName: string; dbAccountName: string;
aadToken: string; aadToken: string;
resourceGroup: string; resourceGroup: string;
@@ -26,11 +23,7 @@ export class PhoenixClient {
provisionData: IProvosionData provisionData: IProvosionData
): Promise<IPhoenixResponse<IPhoenixConnectionInfoResult>> { ): Promise<IPhoenixResponse<IPhoenixConnectionInfoResult>> {
try { try {
const connectionStatus: ContainerConnectionInfo = { const response = await window.fetch(`${this.getPhoenixContainerPoolingEndPoint()}/allocate`, {
status: ConnectionStatusType.Connecting,
};
useNotebook.getState().setConnectionInfo(connectionStatus);
const response = await window.fetch(`${this.getPhoenixContainerPoolingEndPoint()}/provision`, {
method: "POST", method: "POST",
headers: PhoenixClient.getHeaders(), headers: PhoenixClient.getHeaders(),
body: JSON.stringify(provisionData), body: JSON.stringify(provisionData),
@@ -38,31 +31,20 @@ export class PhoenixClient {
let data: IPhoenixConnectionInfoResult; let data: IPhoenixConnectionInfoResult;
if (response.status === HttpStatusCodes.OK) { if (response.status === HttpStatusCodes.OK) {
data = await response.json(); 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 { return {
status: response.status, status: response.status,
data, data,
}; };
} catch (error) { } catch (error) {
const connectionStatus: ContainerConnectionInfo = {
status: ConnectionStatusType.Failed,
};
useNotebook.getState().setConnectionInfo(connectionStatus);
console.error(error); console.error(error);
throw error; throw error;
} }
} }
public static getPhoenixEndpoint(): string { public static getPhoenixEndpoint(): string {
const phoenixEndpoint = userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT; const phoenixEndpoint =
userContext.features.phoenixEndpoint ?? userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT;
if (configContext.allowedJunoOrigins.indexOf(new URL(phoenixEndpoint).origin) === -1) { if (configContext.allowedJunoOrigins.indexOf(new URL(phoenixEndpoint).origin) === -1) {
const error = `${phoenixEndpoint} not allowed as juno endpoint`; const error = `${phoenixEndpoint} not allowed as juno endpoint`;
console.error(error); console.error(error);
@@ -73,7 +55,7 @@ export class PhoenixClient {
} }
public getPhoenixContainerPoolingEndPoint(): string { public getPhoenixContainerPoolingEndPoint(): string {
return `${PhoenixClient.getPhoenixEndpoint()}/api/containerpooling`; return `${PhoenixClient.getPhoenixEndpoint()}/api/controlplane/toolscontainer`;
} }
private static getHeaders(): HeadersInit { private static getHeaders(): HeadersInit {
const authorizationHeader = getAuthorizationHeader(); const authorizationHeader = getAuthorizationHeader();

View File

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

View File

@@ -20,6 +20,7 @@ export type Features = {
readonly enableKoResourceTree: boolean; readonly enableKoResourceTree: boolean;
readonly hostedDataExplorer: boolean; readonly hostedDataExplorer: boolean;
readonly junoEndpoint?: string; readonly junoEndpoint?: string;
readonly phoenixEndpoint?: string;
readonly livyEndpoint?: string; readonly livyEndpoint?: string;
readonly notebookBasePath?: string; readonly notebookBasePath?: string;
readonly notebookServerToken?: string; readonly notebookServerToken?: string;
@@ -68,6 +69,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
mongoProxyEndpoint: get("mongoproxyendpoint"), mongoProxyEndpoint: get("mongoproxyendpoint"),
mongoProxyAPIs: get("mongoproxyapis"), mongoProxyAPIs: get("mongoproxyapis"),
junoEndpoint: get("junoendpoint"), junoEndpoint: get("junoendpoint"),
phoenixEndpoint: get("phoenixendpoint"),
livyEndpoint: get("livyendpoint"), livyEndpoint: get("livyendpoint"),
notebookBasePath: get("notebookbasepath"), notebookBasePath: get("notebookbasepath"),
notebookServerToken: get("notebookservertoken"), notebookServerToken: get("notebookservertoken"),

View File

@@ -30,7 +30,7 @@ describe("GalleryUtils", () => {
}); });
it("downloadItem shows dialog in data explorer", () => { it("downloadItem shows dialog in data explorer", () => {
const container = {} as Explorer; const container = new Explorer();
GalleryUtils.downloadItem(container, undefined, galleryItem, undefined); GalleryUtils.downloadItem(container, undefined, galleryItem, undefined);
expect(useDialog.getState().visible).toBe(true); expect(useDialog.getState().visible).toBe(true);

View File

@@ -10,6 +10,7 @@ import {
SortBy, SortBy,
} from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent"; } from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
import Explorer from "../Explorer/Explorer"; import Explorer from "../Explorer/Explorer";
import { NotebookUtil } from "../Explorer/Notebook/NotebookUtil";
import { useNotebook } from "../Explorer/Notebook/useNotebook"; import { useNotebook } from "../Explorer/Notebook/useNotebook";
import { IGalleryItem, JunoClient } from "../Juno/JunoClient"; import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
@@ -225,11 +226,38 @@ export function downloadItem(
const name = data.name; const name = data.name;
useDialog.getState().showOkCancelModalDialog( useDialog.getState().showOkCancelModalDialog(
`Download to ${useNotebook.getState().notebookFolderName}`, `Download to ${useNotebook.getState().notebookFolderName}`,
`Download ${name} from gallery as a copy to your notebooks to run and/or edit the notebook.`, undefined,
"Download", "Download",
async () => { async () => {
if (NotebookUtil.isPhoenixEnabled()) {
await container.allocateContainer();
}
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) {
downloadNotebookItem(name, data, junoClient, container, onComplete);
} else {
useDialog
.getState()
.showOkModalDialog(
"Failed to Connect",
"Failed to connect to temporary workspace. Please refresh the page and try again."
);
}
},
"Cancel",
undefined,
container.getDownloadModalConent(name)
);
}
export async function downloadNotebookItem(
fileName: string,
data: IGalleryItem,
junoClient: JunoClient,
container: Explorer,
onComplete: (item: IGalleryItem) => void
) {
const clearInProgressMessage = logConsoleProgress( const clearInProgressMessage = logConsoleProgress(
`Downloading ${name} to ${useNotebook.getState().notebookFolderName}` `Downloading ${fileName} to ${useNotebook.getState().notebookFolderName}`
); );
const startKey = traceStart(Action.NotebooksGalleryDownload, { const startKey = traceStart(Action.NotebooksGalleryDownload, {
notebookId: data.id, notebookId: data.id,
@@ -252,7 +280,7 @@ export function downloadItem(
} }
await container.importAndOpenContent(data.name, JSON.stringify(notebook)); await container.importAndOpenContent(data.name, JSON.stringify(notebook));
logConsoleInfo(`Successfully downloaded ${name} to My Notebooks`); logConsoleInfo(`Successfully downloaded ${data.name} to ${useNotebook.getState().notebookFolderName}`);
const increaseDownloadResponse = await junoClient.increaseNotebookDownloadCount(data.id); const increaseDownloadResponse = await junoClient.increaseNotebookDownloadCount(data.id);
if (increaseDownloadResponse.data) { if (increaseDownloadResponse.data) {
@@ -280,12 +308,7 @@ export function downloadItem(
} }
clearInProgressMessage(); clearInProgressMessage();
},
"Cancel",
undefined
);
} }
export const removeNotebookViewerLink = (notebook: Notebook, newCellId: string): void => { export const removeNotebookViewerLink = (notebook: Notebook, newCellId: string): void => {
if (!newCellId) { if (!newCellId) {
return; return;

View File

@@ -69,7 +69,11 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
if (tab.tabId === activeTab.tabId && tabIndex !== -1) { if (tab.tabId === activeTab.tabId && tabIndex !== -1) {
const tabToTheRight = updatedTabs[tabIndex]; const tabToTheRight = updatedTabs[tabIndex];
const lastOpenTab = updatedTabs[updatedTabs.length - 1]; const lastOpenTab = updatedTabs[updatedTabs.length - 1];
set({ activeTab: tabToTheRight || lastOpenTab }); const newActiveTab = tabToTheRight ?? lastOpenTab;
set({ activeTab: newActiveTab });
if (newActiveTab) {
newActiveTab.onActivate();
}
} }
set({ openedTabs: updatedTabs }); set({ openedTabs: updatedTabs });

View File

@@ -15,18 +15,13 @@
"target": "es2017", "target": "es2017",
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"lib": [ "lib": ["es5", "es6", "dom"],
"es5",
"es6",
"dom"
],
"jsx": "react", "jsx": "react",
"moduleResolution": "node", "moduleResolution": "node",
"resolveJsonModule": true, "resolveJsonModule": true,
"noEmit": true, "noEmit": true,
"types": [ "types": ["jest"],
"jest" "baseUrl": "src"
]
}, },
"typedocOptions": { "typedocOptions": {
"entryPoints": [ "entryPoints": [
@@ -42,11 +37,6 @@
"includes": "./src/SelfServe/Documentation", "includes": "./src/SelfServe/Documentation",
"disableSources": true "disableSources": true
}, },
"include": [ "include": ["src", "./src/**/*", "./utils/**/*"],
"./src/**/*", "exclude": ["./src/**/__mocks__/**/*"]
"./utils/**/*"
],
"exclude": [
"./src/**/__mocks__/**/*"
]
} }

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