From f8cfb33618d85aa4d1f91913d3d5f00e3e0fdfd4 Mon Sep 17 00:00:00 2001 From: jawelton74 <103591340+jawelton74@users.noreply.github.com> Date: Wed, 26 Aug 2026 13:42:14 -0700 Subject: [PATCH] Remove Phoenix and notebooks (Phase 5) (#2561) * Remove Phoenix and notebook core Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4e4f2ebd-f2f8-4c2b-b63d-40e1a2b1c662 * Fix merge issue. --------- Copilot-Session: 4e4f2ebd-f2f8-4c2b-b63d-40e1a2b1c662 --- docs/remove-notebooks-plan.md | 31 +- package-lock.json | 533 +----------------- package.json | 6 +- src/Explorer/ContextMenuButtonFactory.tsx | 13 +- .../SettingsComponent.test.tsx.snap | 60 -- src/Explorer/Explorer.test.tsx | 30 +- src/Explorer/Explorer.tsx | 225 +------- .../CommandBar/CommandBarComponentAdapter.tsx | 26 +- .../CommandBarComponentButtonFactory.test.ts | 20 +- .../CommandBarComponentButtonFactory.tsx | 29 +- .../Menus/CommandBar/CommandBarUtil.tsx | 17 - .../CommandBar/ConnectionStatusComponent.less | 184 ------ .../CommandBar/ConnectionStatusComponent.tsx | 186 ------ .../CommandBar/MemoryTrackerComponent.less | 24 - .../CommandBar/MemoryTrackerComponent.tsx | 29 - .../Notebook/NotebookContainerClient.ts | 195 ------- src/Explorer/Notebook/NotebookContentItem.ts | 14 - src/Explorer/Notebook/NotebookManager.tsx | 32 -- src/Explorer/Notebook/NotebookUtil.test.ts | 42 -- src/Explorer/Notebook/NotebookUtil.ts | 81 --- src/Explorer/Notebook/useNotebook.ts | 248 -------- ...AddGlobalSecondaryIndexPanel.test.tsx.snap | 10 - src/Explorer/SplashScreen/SplashScreen.tsx | 7 - .../DocumentsTabV2/DocumentsTabV2.test.tsx | 9 - .../DocumentsTabV2Mongo.test.tsx | 9 - src/Explorer/Tree/Collection.ts | 1 - src/Explorer/Tree/Database.test.tsx | 9 - src/Explorer/Tree/ResourceTree.tsx | 31 +- src/Explorer/Tree/ResourceTreeAdapter.tsx | 148 ----- .../__snapshots__/treeNodeUtil.test.ts.snap | 2 +- src/Explorer/Tree/treeNodeUtil.test.ts | 56 +- src/Explorer/Tree/treeNodeUtil.tsx | 9 +- src/Explorer/useSelectedNode.ts | 6 - src/Main.tsx | 2 - src/Phoenix/PhoenixClient.ts | 256 --------- src/Utils/NotebookConfigurationUtils.ts | 87 --- src/hooks/useKnockoutExplorer.ts | 15 - src/hooks/useNotebookSnapshotStore.ts | 15 - src/hooks/useTabs.ts | 29 - tsconfig.strict.json | 4 +- 40 files changed, 122 insertions(+), 2608 deletions(-) delete mode 100644 src/Explorer/Menus/CommandBar/ConnectionStatusComponent.less delete mode 100644 src/Explorer/Menus/CommandBar/ConnectionStatusComponent.tsx delete mode 100644 src/Explorer/Menus/CommandBar/MemoryTrackerComponent.less delete mode 100644 src/Explorer/Menus/CommandBar/MemoryTrackerComponent.tsx delete mode 100644 src/Explorer/Notebook/NotebookContainerClient.ts delete mode 100644 src/Explorer/Notebook/NotebookContentItem.ts delete mode 100644 src/Explorer/Notebook/NotebookManager.tsx delete mode 100644 src/Explorer/Notebook/NotebookUtil.test.ts delete mode 100644 src/Explorer/Notebook/NotebookUtil.ts delete mode 100644 src/Explorer/Notebook/useNotebook.ts delete mode 100644 src/Phoenix/PhoenixClient.ts delete mode 100644 src/Utils/NotebookConfigurationUtils.ts delete mode 100644 src/hooks/useNotebookSnapshotStore.ts diff --git a/docs/remove-notebooks-plan.md b/docs/remove-notebooks-plan.md index 6070eaf3e..6ff123c93 100644 --- a/docs/remove-notebooks-plan.md +++ b/docs/remove-notebooks-plan.md @@ -7,14 +7,14 @@ the **Phoenix** compute-container service and the **Juno** service, plus a **Git integration used to pin/browse notebook repositories. This functionality is being retired. The goal is to remove all notebooks/Phoenix/Juno/GitHub-for-notebooks code, dependencies, UI surfaces, telemetry, and configuration, while **preserving the -database shell terminals** (Mongo / Cassandra / Postgres / VCoreMongo), which today +database shell terminals** (Mongo / Cassandra / Postgres / VCoreMongo / Cosmos DB NoSQL), which today share the Terminal infrastructure with notebooks. This document is the implementation plan only. No code changes are made here. ## Scope decisions (confirmed) -- **Database shells**: Keep the Mongo/Cassandra/Postgres/VCoreMongo shells, but migrate +- **Database shells**: Keep the Mongo/Cassandra/Postgres/VCoreMongo/Cosmos DB NoSQL shells, but migrate them to use the **CloudShell** path exclusively. Remove the legacy Phoenix notebook-server shell path. - **GitHub integration**: Remove entirely (it exists only for notebook pinned repos). @@ -108,7 +108,7 @@ This is a continuation of an in-progress removal effort. Reference commits: Each phase is independently buildable and shippable. Within each phase, **all references to removed code are also removed** so the tree compiles. After every phase run: `npm run compile`, `npm run compile:strict`, `npm run lint`, `npm run format:check`, -`npm test`, and a webpack build (`npm run build:ci`); manually verify the four shells +`npm test`, and a webpack build (`npm run build:ci`); manually verify the five shells still open. ### Phase 1 — Decouple database shells to CloudShell-only ✅ COMPLETED @@ -235,7 +235,28 @@ engine deleted there. No separate work remains for this phase. pinned-repo methods. - Remove GitHub-related localization keys from **all** locale files (`en` + non-English). -### Phase 5 — Remove Phoenix and the notebook container/allocation core +### Phase 5 — Remove Phoenix and the notebook container/allocation core ✅ COMPLETED +> **Status:** Implemented on branch `users/jawelton/remove-notebooks-phase5-081426`. +> The automated verification sweep is green: `compile`, `compile:strict`, `lint`, +> `format:check`, `test` (1931 passing, 5 skipped), and `build:ci` (webpack) all pass. +> +> **Implementation notes / deviations:** +> - Removed the Phoenix-only connection status and memory tracker command-bar UI, which +> depended entirely on the deleted `useNotebook` container state. +> - Moved the unrelated `isSynapseLinkUpdating` flag from `useNotebook` into the existing +> `useCommandBar` Zustand store and made static command creation react to it. +> - Finished the deferred Phase 1 cleanup by replacing remaining mixed +> `isShellEnabled`/notebook availability checks with the `enableCloudShell` feature gate. +> The non-Phoenix Mongo fallback remains available when CloudShell is disabled. +> - Removed the remaining direct nteract/runtime dependencies with no surviving importers: +> `@nteract/commutable`, `@nteract/core`, `@nteract/fixtures`, `@nteract/myths`, and +> `rx-jupyter`. Added `@types/uuid` explicitly because surviving CloudShell/UserContext +> code had previously received UUID declarations through the removed transitive graph. +> - Preserved the newer Cosmos DB (NoSQL) Cloud Shell added after this roadmap was written; +> all terminal tabs still instantiate `CloudShellTerminalComponentAdapter`. +> - Residual notebook/Phoenix config, contracts, telemetry, localization, Juno, generated +> ARM clients, and images remain intentionally deferred to Phase 6. + - Delete `src/Phoenix/`, `src/Explorer/Notebook/NotebookContainerClient.ts`, `src/Explorer/Notebook/NotebookManager.tsx`, `src/Explorer/Notebook/useNotebook.ts`, `src/Explorer/Notebook/NotebookContentItem.ts`, `src/Explorer/Notebook/NotebookUtil.ts` @@ -279,7 +300,7 @@ npm run format:check npm test npm run build:ci ``` -Plus manual smoke test: open Mongo, Cassandra, Postgres, and VCoreMongo shells. +Plus manual smoke test: open Mongo, Cassandra, Postgres, VCoreMongo, and Cosmos DB NoSQL shells. ## Notes & considerations diff --git a/package-lock.json b/package-lock.json index 099acec8d..66ca2a646 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,11 +19,7 @@ "@fluentui/react": "8.119.0", "@fluentui/react-components": "9.54.2", "@microsoft/applicationinsights-web": "2.6.1", - "@nteract/commutable": "7.5.1", - "@nteract/core": "15.1.9", - "@nteract/fixtures": "2.3.0", "@nteract/markdown": "4.6.0", - "@nteract/myths": "0.1.9", "@octokit/request": "8.4.1", "@octokit/rest": "17.9.2", "@testing-library/jest-dom": "6.4.6", @@ -92,7 +88,6 @@ "react-window": "1.8.10", "react-youtube": "9.0.1", "reflect-metadata": "0.1.13", - "rx-jupyter": "5.5.12", "shell-quote": "1.10.0", "styled-components": "5.0.1", "swr": "0.4.0", @@ -139,6 +134,7 @@ "@types/sinon": "2.3.3", "@types/styled-components": "5.1.32", "@types/underscore": "1.7.36", + "@types/uuid": "9.0.8", "@types/youtube-player": "5.5.6", "@typescript-eslint/eslint-plugin": "6.7.4", "@typescript-eslint/parser": "6.7.4", @@ -6396,154 +6392,6 @@ "node": ">= 8" } }, - "node_modules/@nteract/actions": { - "version": "7.1.0", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/commutable": "^7.4.5", - "@nteract/messaging": "^7.0.19", - "@nteract/types": "^7.1.9", - "immutable": "^4.0.0-rc.12", - "rx-jupyter": "^5.5.21" - } - }, - "node_modules/@nteract/actions/node_modules/rx-jupyter": { - "version": "5.5.21", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/commutable": "^7.4.5", - "@nteract/messaging": "^7.0.19", - "@types/ungap__url-search-params": "^0.1.0", - "@ungap/url-search-params": "^0.2.0", - "js-cookie": "^2.2.0", - "rxjs": "^6.6.0", - "url-join": "^4.0.0" - } - }, - "node_modules/@nteract/commutable": { - "version": "7.5.1", - "license": "BSD-3-Clause", - "dependencies": { - "immutable": "^4.0.0-rc.12", - "uuid": "^8.0.0" - } - }, - "node_modules/@nteract/commutable/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@nteract/core": { - "version": "15.1.9", - "resolved": "https://registry.npmjs.org/@nteract/core/-/core-15.1.9.tgz", - "integrity": "sha512-kQf+7d1Qi27aVOvu+q07pXw8dXFUsU7JJLZ3JeAzSxVizNE/PjbEPn9Ruc+AV/I/FqXZovGp7Q04AGr25hsuyA==", - "dependencies": { - "@nteract/actions": "^7.0.11", - "@nteract/commutable": "^7.4.5", - "@nteract/epics": "^5.0.11", - "@nteract/reducers": "^5.1.9", - "@nteract/selectors": "^3.1.9", - "@nteract/types": "^7.1.9", - "redux-logger": "^3.0.6" - }, - "peerDependencies": { - "immutable": "^4.0.0-rc.12" - } - }, - "node_modules/@nteract/epics": { - "version": "5.1.0", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/actions": "^7.0.11", - "@nteract/commutable": "^7.4.5", - "@nteract/messaging": "^7.0.19", - "@nteract/mythic-configuration": "^1.0.11", - "@nteract/mythic-notifications": "^0.2.11", - "@nteract/selectors": "^3.1.9", - "@nteract/types": "^7.1.9", - "file-saver": "^2.0.0", - "redux": "^4.0.1", - "redux-observable": "^2.0.0-alpha.0", - "rx-jupyter": "^5.5.21", - "rxjs": "^6.3.3" - } - }, - "node_modules/@nteract/epics/node_modules/rx-jupyter": { - "version": "5.5.21", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/commutable": "^7.4.5", - "@nteract/messaging": "^7.0.19", - "@types/ungap__url-search-params": "^0.1.0", - "@ungap/url-search-params": "^0.2.0", - "js-cookie": "^2.2.0", - "rxjs": "^6.6.0", - "url-join": "^4.0.0" - } - }, - "node_modules/@nteract/fixtures": { - "version": "2.3.0", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/commutable": "^7.2.6", - "@nteract/reducers": "^3.1.0", - "@nteract/types": "^6.0.0" - } - }, - "node_modules/@nteract/fixtures/node_modules/@nteract/actions": { - "version": "5.0.1", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/commutable": "^7.2.11", - "@nteract/messaging": "^7.0.6", - "@nteract/types": "^6.0.6", - "immutable": "^4.0.0-rc.12", - "rx-jupyter": "^5.5.8" - } - }, - "node_modules/@nteract/fixtures/node_modules/@nteract/reducers": { - "version": "3.2.1", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/actions": "^5.0.1", - "@nteract/commutable": "^7.2.11", - "@nteract/types": "^6.0.6", - "escape-carriage": "^1.3.0", - "immutable": "^4.0.0-rc.12", - "lodash.has": "^4.5.2", - "redux": "^4.0.1", - "redux-immutable": "^4.0.0", - "uuid": "^7.0.0" - } - }, - "node_modules/@nteract/fixtures/node_modules/@nteract/reducers/node_modules/uuid": { - "version": "7.0.3", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@nteract/fixtures/node_modules/@nteract/types": { - "version": "6.0.7", - "license": "MIT", - "dependencies": { - "@nteract/commutable": "^7.2.12", - "immutable": "^4.0.0-rc.12", - "rxjs": "^6.3.3", - "uuid": "^8.0.0" - } - }, - "node_modules/@nteract/fixtures/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@nteract/markdown": { "version": "4.6.0", "license": "BSD-3-Clause", @@ -6568,184 +6416,6 @@ "react-dom": "^16.13.0 || ^17.0.0" } }, - "node_modules/@nteract/messaging": { - "version": "7.0.20", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/types": "^7.1.9", - "@types/uuid": "^8.0.0", - "lodash.clonedeep": "^4.5.0", - "rxjs": "^6.6.0", - "uuid": "^8.0.0" - } - }, - "node_modules/@nteract/messaging/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@nteract/mythic-configuration": { - "version": "1.0.12", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/myths": "^0.2.11", - "fs-observable": "^4.1.14" - } - }, - "node_modules/@nteract/mythic-configuration/node_modules/@nteract/myths": { - "version": "0.2.13", - "license": "BSD-3-Clause", - "dependencies": { - "redux": "^4.0.0", - "redux-observable": "^2.0.0-alpha.0", - "rxjs": "^6.3.3" - }, - "peerDependencies": { - "immutable": "^4.0.0-rc.12", - "react": "^16.3.2", - "react-dom": "^16.3.2", - "react-redux": "^7.2.0" - } - }, - "node_modules/@nteract/mythic-configuration/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "license": "MIT", - "peer": true - }, - "node_modules/@nteract/mythic-configuration/node_modules/react-redux": { - "version": "7.2.9", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", - "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.15.4", - "@types/react-redux": "^7.1.20", - "hoist-non-react-statics": "^3.3.2", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^17.0.2" - }, - "peerDependencies": { - "react": "^16.8.3 || ^17 || ^18" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, - "node_modules/@nteract/mythic-notifications": { - "version": "0.2.11", - "license": "BSD-3-Clause", - "dependencies": { - "@blueprintjs/core": "^3.7.0", - "@nteract/myths": "^0.2.11" - }, - "peerDependencies": { - "immutable": "^4.0.0-rc.12", - "react": "^16.3.2", - "react-dom": "^16.3.2", - "styled-components": ">= 5.0.1" - } - }, - "node_modules/@nteract/mythic-notifications/node_modules/@nteract/myths": { - "version": "0.2.13", - "license": "BSD-3-Clause", - "dependencies": { - "redux": "^4.0.0", - "redux-observable": "^2.0.0-alpha.0", - "rxjs": "^6.3.3" - }, - "peerDependencies": { - "immutable": "^4.0.0-rc.12", - "react": "^16.3.2", - "react-dom": "^16.3.2", - "react-redux": "^7.2.0" - } - }, - "node_modules/@nteract/mythic-notifications/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "license": "MIT", - "peer": true - }, - "node_modules/@nteract/mythic-notifications/node_modules/react-redux": { - "version": "7.2.9", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", - "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.15.4", - "@types/react-redux": "^7.1.20", - "hoist-non-react-statics": "^3.3.2", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^17.0.2" - }, - "peerDependencies": { - "react": "^16.8.3 || ^17 || ^18" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, - "node_modules/@nteract/myths": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/@nteract/myths/-/myths-0.1.9.tgz", - "integrity": "sha512-aPLUamd0kTnzy+8usMsGs+kgMfxfx2QNvDuxd5+ko+9DKuyMJx8quNuqphlxaLXNGiJrnfNi5krXaAyNEil3Sw==", - "license": "BSD-3-Clause", - "dependencies": { - "react-redux": "^6.0.0", - "redux": "^4.0.0", - "redux-observable": "^2.0.0-alpha.0", - "rxjs": "^6.3.3" - }, - "peerDependencies": { - "immutable": "^4.0.0-rc.12", - "react": "^16.3.2", - "react-dom": "^16.3.2" - } - }, - "node_modules/@nteract/myths/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, - "node_modules/@nteract/myths/node_modules/react-redux": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-6.0.1.tgz", - "integrity": "sha512-T52I52Kxhbqy/6TEfBv85rQSDz6+Y28V/pf52vDWs1YRXG19mcFOGfHnY2HsNFHyhP+ST34Aih98fvt6tqwVcQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.3.1", - "hoist-non-react-statics": "^3.3.0", - "invariant": "^2.2.4", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^16.8.2" - }, - "peerDependencies": { - "react": "^16.4.0-0", - "redux": "^2.0.0 || ^3.0.0 || ^4.0.0-0" - } - }, "node_modules/@nteract/presentational-components": { "version": "3.4.12", "resolved": "https://registry.npmjs.org/@nteract/presentational-components/-/presentational-components-3.4.12.tgz", @@ -6763,71 +6433,6 @@ "styled-components": ">= 5.0.1" } }, - "node_modules/@nteract/reducers": { - "version": "5.1.13", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/actions": "^7.0.11", - "@nteract/commutable": "^7.4.5", - "@nteract/types": "^7.1.9", - "escape-carriage": "^1.3.0", - "immutable": "^4.0.0-rc.12", - "lodash.has": "^4.5.2", - "redux": "^4.0.1", - "redux-immutable": "^4.0.0", - "uuid": "^8.0.0" - } - }, - "node_modules/@nteract/reducers/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@nteract/selectors": { - "version": "3.2.0", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/commutable": "^7.4.5", - "@nteract/types": "^7.1.9", - "immutable": "^4.0.0-rc.12", - "reselect": "^4.0.0", - "rx-jupyter": "^5.5.21" - } - }, - "node_modules/@nteract/selectors/node_modules/rx-jupyter": { - "version": "5.5.21", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/commutable": "^7.4.5", - "@nteract/messaging": "^7.0.19", - "@types/ungap__url-search-params": "^0.1.0", - "@ungap/url-search-params": "^0.2.0", - "js-cookie": "^2.2.0", - "rxjs": "^6.6.0", - "url-join": "^4.0.0" - } - }, - "node_modules/@nteract/types": { - "version": "7.1.9", - "license": "MIT", - "dependencies": { - "@nteract/commutable": "^7.4.5", - "immutable": "^4.0.0-rc.12", - "rxjs": "^6.6.0", - "uuid": "^8.0.0" - } - }, - "node_modules/@nteract/types/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@octokit/auth-token": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", @@ -8364,6 +7969,7 @@ }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.5", + "devOptional": true, "license": "MIT", "dependencies": { "@types/react": "*", @@ -8555,6 +8161,7 @@ }, "node_modules/@types/react-redux": { "version": "7.1.7", + "dev": true, "license": "MIT", "dependencies": { "@types/hoist-non-react-statics": "^3.3.0", @@ -8678,16 +8285,15 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/ungap__url-search-params": { - "version": "0.1.2", - "license": "MIT" - }, "node_modules/@types/unist": { "version": "2.0.10", "license": "MIT" }, "node_modules/@types/uuid": { - "version": "8.3.4", + "version": "9.0.8", + "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha1-dUW6T8PAA9bHVvZR878WPY8PKbo=", + "dev": true, "license": "MIT" }, "node_modules/@types/ws": { @@ -9001,10 +8607,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, - "node_modules/@ungap/url-search-params": { - "version": "0.2.2", - "license": "ISC" - }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -12093,6 +11695,7 @@ }, "node_modules/debounce": { "version": "1.2.1", + "dev": true, "license": "MIT" }, "node_modules/debug": { @@ -12148,10 +11751,6 @@ } } }, - "node_modules/deep-diff": { - "version": "0.3.8", - "license": "MIT" - }, "node_modules/deep-equal": { "version": "1.1.2", "license": "MIT", @@ -13012,10 +12611,6 @@ "node": ">=6" } }, - "node_modules/escape-carriage": { - "version": "1.3.1", - "license": "MIT" - }, "node_modules/escape-html": { "version": "1.0.3", "dev": true, @@ -14021,10 +13616,6 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/file-saver": { - "version": "2.0.5", - "license": "MIT" - }, "node_modules/filesize": { "version": "8.0.7", "dev": true, @@ -14033,13 +13624,6 @@ "node": ">= 0.4.0" } }, - "node_modules/filewatcher": { - "version": "3.0.1", - "license": "MIT", - "dependencies": { - "debounce": "^1.0.0" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -14489,27 +14073,6 @@ "dev": true, "license": "Unlicense" }, - "node_modules/fs-observable": { - "version": "4.1.14", - "license": "BSD-3-Clause", - "dependencies": { - "filewatcher": "^3.0.1", - "mkdirp": "^0.5.1" - }, - "peerDependencies": { - "rxjs": "^6.3.3" - } - }, - "node_modules/fs-observable/node_modules/mkdirp": { - "version": "0.5.6", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "license": "ISC" @@ -14518,6 +14081,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -20659,10 +20223,6 @@ "jquery": ">=1.8.0 <4.0.0" } }, - "node_modules/js-cookie": { - "version": "2.2.1", - "license": "MIT" - }, "node_modules/js-tokens": { "version": "4.0.0", "license": "MIT" @@ -21090,10 +20650,6 @@ "version": "4.0.3", "license": "MIT" }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "license": "MIT" - }, "node_modules/lodash.debounce": { "version": "4.0.8", "license": "MIT" @@ -21108,10 +20664,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.has": { - "version": "4.5.2", - "license": "MIT" - }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -23757,42 +23309,6 @@ "@babel/runtime": "^7.9.2" } }, - "node_modules/redux-immutable": { - "version": "4.0.0", - "license": "BSD-3-Clause", - "peerDependencies": { - "immutable": "^3.8.1 || ^4.0.0-rc.1" - } - }, - "node_modules/redux-logger": { - "version": "3.0.6", - "license": "MIT", - "dependencies": { - "deep-diff": "^0.3.5" - } - }, - "node_modules/redux-observable": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "rxjs": "^7.0.0", - "tslib": "~2.1.0" - }, - "peerDependencies": { - "redux": ">=4 <5" - } - }, - "node_modules/redux-observable/node_modules/rxjs": { - "version": "7.8.1", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/redux-observable/node_modules/tslib": { - "version": "2.1.0", - "license": "0BSD" - }, "node_modules/reflect-metadata": { "version": "0.1.13", "license": "Apache-2.0" @@ -24087,10 +23603,6 @@ "dev": true, "license": "MIT" }, - "node_modules/reselect": { - "version": "4.1.8", - "license": "MIT" - }, "node_modules/resolve": { "version": "1.22.8", "devOptional": true, @@ -24233,29 +23745,6 @@ "version": "1.3.3", "license": "BSD-3-Clause" }, - "node_modules/rx-jupyter": { - "version": "5.5.12", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/commutable": "^7.3.2", - "@nteract/messaging": "^7.0.10", - "@types/ungap__url-search-params": "^0.1.0", - "@ungap/url-search-params": "^0.2.0", - "js-cookie": "^2.2.0", - "rxjs": "^6.6.0", - "url-join": "^4.0.0" - } - }, - "node_modules/rxjs": { - "version": "6.6.7", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, "node_modules/safe-array-concat": { "version": "1.1.2", "license": "MIT", @@ -26069,10 +25558,6 @@ "punycode": "^2.1.0" } }, - "node_modules/url-join": { - "version": "4.0.1", - "license": "MIT" - }, "node_modules/url-loader": { "version": "4.1.1", "dev": true, diff --git a/package.json b/package.json index a58c864bd..0bffefbbc 100644 --- a/package.json +++ b/package.json @@ -14,11 +14,7 @@ "@fluentui/react": "8.119.0", "@fluentui/react-components": "9.54.2", "@microsoft/applicationinsights-web": "2.6.1", - "@nteract/commutable": "7.5.1", - "@nteract/core": "15.1.9", - "@nteract/fixtures": "2.3.0", "@nteract/markdown": "4.6.0", - "@nteract/myths": "0.1.9", "@octokit/request": "8.4.1", "@octokit/rest": "17.9.2", "@testing-library/jest-dom": "6.4.6", @@ -87,7 +83,6 @@ "react-window": "1.8.10", "react-youtube": "9.0.1", "reflect-metadata": "0.1.13", - "rx-jupyter": "5.5.12", "shell-quote": "1.10.0", "styled-components": "5.0.1", "swr": "0.4.0", @@ -149,6 +144,7 @@ "@types/sinon": "2.3.3", "@types/styled-components": "5.1.32", "@types/underscore": "1.7.36", + "@types/uuid": "9.0.8", "@types/youtube-player": "5.5.6", "@typescript-eslint/eslint-plugin": "6.7.4", "@typescript-eslint/parser": "6.7.4", diff --git a/src/Explorer/ContextMenuButtonFactory.tsx b/src/Explorer/ContextMenuButtonFactory.tsx index 59c49e5ac..f1e85d6cd 100644 --- a/src/Explorer/ContextMenuButtonFactory.tsx +++ b/src/Explorer/ContextMenuButtonFactory.tsx @@ -28,7 +28,6 @@ import { userContext } from "../UserContext"; import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils"; import { useSidePanel } from "../hooks/useSidePanel"; import Explorer from "./Explorer"; -import { useNotebook } from "./Notebook/useNotebook"; import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane"; import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmationPanel"; import StoredProcedure from "./Tree/StoredProcedure"; @@ -120,23 +119,17 @@ export const createCollectionContextMenuButton = ( iconSrc: HostedTerminalIcon, onClick: () => { const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection(); - if (useNotebook.getState().isShellEnabled || userContext.features.enableCloudShell) { + if (userContext.features.enableCloudShell) { container.openNotebookTerminal(ViewModels.TerminalKind.Mongo); } else { selectedCollection && selectedCollection.onNewMongoShellClick(); } }, - label: - useNotebook.getState().isShellEnabled || userContext.features.enableCloudShell - ? t(Keys.contextMenu.openMongoShell) - : t(Keys.contextMenu.newShell), + label: userContext.features.enableCloudShell ? t(Keys.contextMenu.openMongoShell) : t(Keys.contextMenu.newShell), }); } - if ( - (useNotebook.getState().isShellEnabled || userContext.features.enableCloudShell) && - userContext.apiType === "Cassandra" - ) { + if (userContext.features.enableCloudShell && userContext.apiType === "Cassandra") { items.push({ iconSrc: HostedTerminalIcon, onClick: () => { diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap index 41e126a53..dd5d3bce8 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap @@ -109,25 +109,15 @@ exports[`SettingsComponent renders 1`] = ` "computedProperties": [Function], "conflictResolutionPolicy": [Function], "container": Explorer { - "_isInitializingNotebooks": false, "databasesRefreshed": Promise {}, "isFixedCollectionWithSharedThroughputSupported": [Function], "isTabsContentExpanded": [Function], "onRefreshDatabasesKeyPress": [Function], "onRefreshResourcesClick": [Function], - "phoenixClient": PhoenixClient { - "armResourceId": undefined, - "retryOptions": { - "maxTimeout": 5000, - "minTimeout": 5000, - "retries": 3, - }, - }, "provideFeedbackEmail": [Function], "queriesClient": QueriesClient { "container": [Circular], }, - "refreshNotebookList": [Function], "resourceTree": ResourceTreeAdapter { "container": [Circular], "parameters": [Function], @@ -230,25 +220,15 @@ exports[`SettingsComponent renders 1`] = ` "computedProperties": [Function], "conflictResolutionPolicy": [Function], "container": Explorer { - "_isInitializingNotebooks": false, "databasesRefreshed": Promise {}, "isFixedCollectionWithSharedThroughputSupported": [Function], "isTabsContentExpanded": [Function], "onRefreshDatabasesKeyPress": [Function], "onRefreshResourcesClick": [Function], - "phoenixClient": PhoenixClient { - "armResourceId": undefined, - "retryOptions": { - "maxTimeout": 5000, - "minTimeout": 5000, - "retries": 3, - }, - }, "provideFeedbackEmail": [Function], "queriesClient": QueriesClient { "container": [Circular], }, - "refreshNotebookList": [Function], "resourceTree": ResourceTreeAdapter { "container": [Circular], "parameters": [Function], @@ -455,25 +435,15 @@ exports[`SettingsComponent renders 1`] = ` "computedProperties": [Function], "conflictResolutionPolicy": [Function], "container": Explorer { - "_isInitializingNotebooks": false, "databasesRefreshed": Promise {}, "isFixedCollectionWithSharedThroughputSupported": [Function], "isTabsContentExpanded": [Function], "onRefreshDatabasesKeyPress": [Function], "onRefreshResourcesClick": [Function], - "phoenixClient": PhoenixClient { - "armResourceId": undefined, - "retryOptions": { - "maxTimeout": 5000, - "minTimeout": 5000, - "retries": 3, - }, - }, "provideFeedbackEmail": [Function], "queriesClient": QueriesClient { "container": [Circular], }, - "refreshNotebookList": [Function], "resourceTree": ResourceTreeAdapter { "container": [Circular], "parameters": [Function], @@ -526,25 +496,15 @@ exports[`SettingsComponent renders 1`] = ` } explorer={ Explorer { - "_isInitializingNotebooks": false, "databasesRefreshed": Promise {}, "isFixedCollectionWithSharedThroughputSupported": [Function], "isTabsContentExpanded": [Function], "onRefreshDatabasesKeyPress": [Function], "onRefreshResourcesClick": [Function], - "phoenixClient": PhoenixClient { - "armResourceId": undefined, - "retryOptions": { - "maxTimeout": 5000, - "minTimeout": 5000, - "retries": 3, - }, - }, "provideFeedbackEmail": [Function], "queriesClient": QueriesClient { "container": [Circular], }, - "refreshNotebookList": [Function], "resourceTree": ResourceTreeAdapter { "container": [Circular], "parameters": [Function], @@ -694,25 +654,15 @@ exports[`SettingsComponent renders 1`] = ` "computedProperties": [Function], "conflictResolutionPolicy": [Function], "container": Explorer { - "_isInitializingNotebooks": false, "databasesRefreshed": Promise {}, "isFixedCollectionWithSharedThroughputSupported": [Function], "isTabsContentExpanded": [Function], "onRefreshDatabasesKeyPress": [Function], "onRefreshResourcesClick": [Function], - "phoenixClient": PhoenixClient { - "armResourceId": undefined, - "retryOptions": { - "maxTimeout": 5000, - "minTimeout": 5000, - "retries": 3, - }, - }, "provideFeedbackEmail": [Function], "queriesClient": QueriesClient { "container": [Circular], }, - "refreshNotebookList": [Function], "resourceTree": ResourceTreeAdapter { "container": [Circular], "parameters": [Function], @@ -765,25 +715,15 @@ exports[`SettingsComponent renders 1`] = ` } explorer={ Explorer { - "_isInitializingNotebooks": false, "databasesRefreshed": Promise {}, "isFixedCollectionWithSharedThroughputSupported": [Function], "isTabsContentExpanded": [Function], "onRefreshDatabasesKeyPress": [Function], "onRefreshResourcesClick": [Function], - "phoenixClient": PhoenixClient { - "armResourceId": undefined, - "retryOptions": { - "maxTimeout": 5000, - "minTimeout": 5000, - "retries": 3, - }, - }, "provideFeedbackEmail": [Function], "queriesClient": QueriesClient { "container": [Circular], }, - "refreshNotebookList": [Function], "resourceTree": ResourceTreeAdapter { "container": [Circular], "parameters": [Function], diff --git a/src/Explorer/Explorer.test.tsx b/src/Explorer/Explorer.test.tsx index 5a4e89b94..dbb7bc118 100644 --- a/src/Explorer/Explorer.test.tsx +++ b/src/Explorer/Explorer.test.tsx @@ -10,6 +10,7 @@ import { Capability, DatabaseAccount } from "../Contracts/DataModels"; import { updateUserContext, userContext } from "../UserContext"; import { update } from "../Utils/arm/generatedClients/cosmos/databaseAccounts"; import Explorer from "./Explorer"; +import { useCommandBar } from "./Menus/CommandBar/CommandBarComponentAdapter"; const mockUpdate = update as jest.MockedFunction; @@ -26,21 +27,6 @@ jest.mock("./Controls/Dialog", () => ({ }, })); -// Silence useNotebook subscription calls -jest.mock("./Notebook/useNotebook", () => ({ - useNotebook: { - subscribe: jest.fn(), - getState: jest.fn().mockReturnValue( - new Proxy( - {}, - { - get: () => jest.fn().mockResolvedValue(undefined), - }, - ), - ), - }, -})); - describe("Explorer.openEnableSynapseLinkDialog", () => { let explorer: Explorer; @@ -69,6 +55,7 @@ describe("Explorer.openEnableSynapseLinkDialog", () => { beforeEach(() => { jest.clearAllMocks(); mockUpdate.mockResolvedValue(undefined); + useCommandBar.getState().setIsSynapseLinkUpdating(false); explorer = new Explorer(); }); @@ -102,6 +89,19 @@ describe("Explorer.openEnableSynapseLinkDialog", () => { expect(userContext.databaseAccount.properties.enableAnalyticalStorage).toBe(true); }); + + it("should track Synapse Link update progress in the command bar store", async () => { + mockUpdate.mockImplementation(async () => { + expect(useCommandBar.getState().isSynapseLinkUpdating).toBe(true); + return {} as Awaited>; + }); + explorer.openEnableSynapseLinkDialog(); + + const dialogProps = mockOpenDialog.mock.calls[0][0]; + await dialogProps.onPrimaryButtonClick(); + + expect(useCommandBar.getState().isSynapseLinkUpdating).toBe(false); + }); }); describe("with targetAccountOverride", () => { diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index bf1c5a33e..bc7df461d 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -1,9 +1,6 @@ import * as msal from "@azure/msal-browser"; -import { Link } from "@fluentui/react/lib/Link"; -import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility"; import { sendMessage } from "Common/MessageHandler"; import { stringifyError } from "Common/stringifyError"; -import { Platform, configContext } from "ConfigContext"; import { MessageTypes } from "Contracts/ExplorerContracts"; import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane"; import { @@ -13,32 +10,27 @@ import { scheduleRefreshFabricToken, } from "Platform/Fabric/FabricUtil"; import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils"; -import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils"; import { featureRegistered } from "Utils/FeatureRegistrationUtils"; import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts"; import * as ko from "knockout"; import React from "react"; import _ from "underscore"; -import shallow from "zustand/shallow"; import { AuthType } from "../AuthType"; import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer"; import * as Constants from "../Common/Constants"; -import { Areas, ConnectionStatusType, HttpStatusCodes, Notebook } from "../Common/Constants"; import { getErrorMessage, getErrorStack } from "../Common/ErrorHandlingUtils"; import * as Logger from "../Common/Logger"; import { QueriesClient } from "../Common/QueriesClient"; import { readCollection } from "../Common/dataAccess/readCollection"; import { readDatabases } from "../Common/dataAccess/readDatabases"; import * as DataModels from "../Contracts/DataModels"; -import { ContainerConnectionInfo, IPhoenixServiceInfo, IProvisionData, IResponse } from "../Contracts/DataModels"; import * as ViewModels from "../Contracts/ViewModels"; import { UploadDetailsRecord } from "../Contracts/ViewModels"; import MetricScenario from "../Metrics/MetricEvents"; import { ApplicationMetricPhase } from "../Metrics/ScenarioConfig"; import { scenarioMonitor } from "../Metrics/ScenarioMonitor"; -import { PhoenixClient } from "../Phoenix/PhoenixClient"; import * as ExplorerSettings from "../Shared/ExplorerSettings"; -import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants"; +import { Action } from "../Shared/Telemetry/TelemetryConstants"; import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor"; import { updateUserContext, userContext } from "../UserContext"; import { getCollectionName, getUploadName } from "../Utils/APITypeUtils"; @@ -49,8 +41,6 @@ import { ReactTabKind, useTabs } from "../hooks/useTabs"; import "./ComponentRegisterer"; import { DialogProps, useDialog } from "./Controls/Dialog"; import { useCommandBar } from "./Menus/CommandBar/CommandBarComponentAdapter"; -import type NotebookManager from "./Notebook/NotebookManager"; -import { useNotebook } from "./Notebook/useNotebook"; import { AddCollectionPanel } from "./Panes/AddCollectionPanel/AddCollectionPanel"; import { CassandraAddCollectionPane } from "./Panes/CassandraAddCollectionPane/CassandraAddCollectionPane"; import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane"; @@ -78,13 +68,7 @@ export default class Explorer { // Tabs public isTabsContentExpanded: ko.Observable; - // Notebooks - public notebookManager?: NotebookManager; - - private _isInitializingNotebooks: boolean; - private static readonly MaxNbDatabasesToAutoExpand = 5; - public phoenixClient: PhoenixClient; /** * Resolves when the initial refreshAllDatabases (including collection loading) completes. @@ -95,13 +79,6 @@ export default class Explorer { const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, { dataExplorerArea: Constants.Areas.ResourceTree, }); - this._isInitializingNotebooks = false; - - this.phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id); - useNotebook.subscribe( - () => this.refreshCommandBarButtons(), - (state) => state.isNotebooksEnabledForAccount, - ); this.queriesClient = new QueriesClient(this); @@ -156,31 +133,8 @@ export default class Explorer { startKey, ); - useNotebook.subscribe( - async () => this.initiateAndRefreshNotebookList(), - (state) => [state.isNotebookEnabled, state.isRefreshed], - shallow, - ); - this.resourceTree = new ResourceTreeAdapter(this); - // Override notebook server parameters from URL parameters - if ( - userContext.features.notebookServerUrl && - validateEndpoint(userContext.features.notebookServerUrl, allowedNotebookServerUrls) && - userContext.features.notebookServerToken - ) { - useNotebook.getState().setNotebookServerInfo({ - notebookServerEndpoint: userContext.features.notebookServerUrl, - authToken: userContext.features.notebookServerToken, - forwardingId: undefined, - }); - } - - if (userContext.features.notebookBasePath) { - useNotebook.getState().setNotebookBasePath(userContext.features.notebookBasePath); - } - if (isFabricMirrored()) { useTabs.getState().closeReactTab(ReactTabKind.Home); } @@ -188,23 +142,6 @@ export default class Explorer { this.refreshExplorer(); } - public async initiateAndRefreshNotebookList(): Promise { - 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(targetAccountOverride?: DataModels.AccountOverride): void { const subscriptionId = targetAccountOverride?.subscriptionId ?? userContext.subscriptionId; const resourceGroup = targetAccountOverride?.resourceGroup ?? userContext.resourceGroup; @@ -227,7 +164,7 @@ export default class Explorer { const clearInProgressMessage = logConsoleProgress( "Enabling Azure Synapse Link for this account. This may take a few minutes before you can enable analytical store for this account.", ); - useNotebook.getState().setIsSynapseLinkUpdating(true); + useCommandBar.getState().setIsSynapseLinkUpdating(true); useDialog.getState().closeDialog(); try { @@ -248,7 +185,7 @@ export default class Explorer { logConsoleError(`Enabling Azure Synapse Link for this account failed. ${getErrorMessage(error)}`); TelemetryProcessor.traceFailure(Action.EnableAzureSynapseLink, {}, startTime); } finally { - useNotebook.getState().setIsSynapseLinkUpdating(false); + useCommandBar.getState().setIsSynapseLinkUpdating(false); } }, @@ -431,7 +368,6 @@ export default class Explorer { await (userContext.authType === AuthType.ResourceToken ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases()); - await this.refreshNotebookList(); } logConsoleInfo("Successfully refreshed databases"); @@ -442,108 +378,6 @@ export default class Explorer { window.open(Constants.Urls.feedbackEmail, "_blank"); }; - public async initNotebooks(databaseAccount: DataModels.DatabaseAccount): Promise { - if (!databaseAccount) { - throw new Error("No database account specified"); - } - - if (this._isInitializingNotebooks) { - return; - } - this._isInitializingNotebooks = true; - this.refreshNotebookList(); - this._isInitializingNotebooks = false; - } - - public async allocateContainer(): Promise { - const notebookServerInfo = useNotebook.getState().notebookServerInfo; - const isAllocating = useNotebook.getState().isAllocating; - if ( - isAllocating === false && - (notebookServerInfo === undefined || - (notebookServerInfo && notebookServerInfo.notebookServerEndpoint === undefined)) - ) { - const connectionStatus: ContainerConnectionInfo = { - status: ConnectionStatusType.Connecting, - }; - - useNotebook.getState().setConnectionInfo(connectionStatus); - - let connectionInfo; - try { - TelemetryProcessor.traceStart(Action.PhoenixConnection, { - dataExplorerArea: Areas.Notebook, - }); - useNotebook.getState().setIsAllocating(true); - const provisionData: IProvisionData = { - cosmosEndpoint: userContext?.databaseAccount?.properties?.documentEndpoint, - poolId: undefined, - }; - connectionInfo = await this.phoenixClient.allocateContainer(provisionData); - if (!connectionInfo?.data?.phoenixServiceUrl) { - throw new Error(`PhoenixServiceUrl is invalid!`); - } - await this.setNotebookInfo(connectionInfo, connectionStatus); - TelemetryProcessor.traceSuccess(Action.PhoenixConnection, { - dataExplorerArea: Areas.Notebook, - }); - } catch (error) { - TelemetryProcessor.traceFailure(Action.PhoenixConnection, { - dataExplorerArea: Areas.Notebook, - status: error.status, - error: getErrorMessage(error), - errorStack: getErrorStack(error), - }); - connectionStatus.status = ConnectionStatusType.Failed; - useNotebook.getState().resetContainerConnection(connectionStatus); - if (error?.status === HttpStatusCodes.Forbidden && error.message) { - useDialog.getState().showOkModalDialog("Connection Failed", `${error.message}`); - } else { - useDialog - .getState() - .showOkModalDialog( - "Connection Failed", - "We are unable to connect to the temporary workspace. Please try again in a few minutes. If the error persists, file a support ticket.", - ); - } - throw error; - } finally { - useNotebook.getState().setIsAllocating(false); - this.refreshCommandBarButtons(); - this.refreshNotebookList(); - this._isInitializingNotebooks = false; - } - } - } - - public async setNotebookInfo( - connectionInfo: IResponse, - connectionStatus: DataModels.ContainerConnectionInfo, - ): Promise { - const containerData = { - forwardingId: connectionInfo.data.forwardingId, - dbAccountName: userContext.databaseAccount.name, - }; - await this.phoenixClient.initiateContainerHeartBeat(true, containerData); - - connectionStatus.status = ConnectionStatusType.Connected; - useNotebook.getState().setConnectionInfo(connectionStatus); - - const noteBookServerInfo = { - notebookServerEndpoint: - (validateEndpoint(userContext.features.notebookServerUrl, allowedNotebookServerUrls) && - userContext.features.notebookServerUrl) || - connectionInfo.data.phoenixServiceUrl, - authToken: userContext.features.notebookServerToken || connectionInfo.data.authToken, - forwardingId: connectionInfo.data.forwardingId, - }; - useNotebook.getState().setNotebookServerInfo(noteBookServerInfo); - - this.notebookManager?.notebookClient - .getMemoryUsage() - .then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo)); - } - private getDeltaDatabases( updatedDatabaseList: DataModels.Database[], databases: ViewModels.Database[], @@ -637,11 +471,6 @@ export default class Explorer { } } - private refreshNotebookList = (): Promise => { - // Notebook authoring and listing have been removed. - return Promise.resolve(); - }; - public openNotebookTerminal(kind: ViewModels.TerminalKind): void { this.connectToNotebookTerminal(kind); } @@ -742,24 +571,6 @@ export default class Explorer { .openSidePanel("Input parameters", ); } - public getDownloadModalContent(fileName: string): JSX.Element { - if (useNotebook.getState().isPhoenixNotebooks) { - return ( - <> -

{Notebook.galleryNotebookDownloadContent1}

-
-

- {Notebook.galleryNotebookDownloadContent2} - - {Notebook.learnMore} - -

- - ); - } - return

Download {fileName} from gallery as a copy to your notebooks to run and/or edit the notebook.

; - } - public async refreshExplorer(): Promise { // Start DatabaseLoad scenario before fetching databases if (userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo") { @@ -768,8 +579,7 @@ export default class Explorer { // Run independent initialization tasks in parallel: // - Database loading (ARM/SDK calls for databases + collections) - // - Notebook enabled check (Phoenix + Portal backend — no dependency on databases) - // - Feature registration check (ARM call — no dependency on databases or notebooks) + // - Feature registration check (ARM call — no dependency on databases) const databasesTask = userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo" ? (async () => { @@ -786,37 +596,12 @@ export default class Explorer { })() : Promise.resolve(); - const notebooksTask = !isFabricNative() - ? useNotebook.getState().refreshNotebooksEnabledStateForAccount() - : Promise.resolve(); - const featureRegistrationTask = userContext.authType === AuthType.AAD && userContext.apiType === "SQL" && !isFabricNative() ? featureRegistered(userContext.subscriptionId, "ThroughputBucketing") : Promise.resolve(false); - const [, , throughputBucketsEnabled] = await Promise.all([databasesTask, notebooksTask, featureRegistrationTask]); - - // Notebook initialization depends on refreshNotebooksEnabledStateForAccount completing above - // TODO: remove reference to isNotebookEnabled and isNotebooksEnabledForAccount - const isNotebookEnabled = - configContext.platform !== Platform.Fabric && - (userContext.features.notebooksDownBanner || - useNotebook.getState().isPhoenixNotebooks || - useNotebook.getState().isPhoenixFeatures); - useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled); - useNotebook - .getState() - .setIsShellEnabled(useNotebook.getState().isPhoenixFeatures && isPublicInternetAccessAllowed()); - - TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, { - isNotebookEnabled, - dataExplorerArea: Constants.Areas.Notebook, - }); - - if (useNotebook.getState().isPhoenixNotebooks) { - await this.initNotebooks(userContext.databaseAccount); - } + const [, throughputBucketsEnabled] = await Promise.all([databasesTask, featureRegistrationTask]); if (throughputBucketsEnabled) { updateUserContext({ throughputBucketsEnabled }); diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx index 4d2326f3f..17b89d650 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx @@ -5,14 +5,12 @@ */ import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react"; import { makeStyles, useFluent } from "@fluentui/react-components"; -import { useNotebook } from "Explorer/Notebook/useNotebook"; import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane"; import { KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts"; import { isFabric } from "Platform/Fabric/FabricUtil"; import { userContext } from "UserContext"; import * as React from "react"; import create, { UseStore } from "zustand"; -import { ConnectionStatusType } from "../../../Common/Constants"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import Explorer from "../../Explorer"; import { useSelectedNode } from "../../useSelectedNode"; @@ -28,6 +26,8 @@ export interface CommandBarStore { setContextButtons: (contextButtons: CommandButtonComponentProps[]) => void; isHidden: boolean; setIsHidden: (isHidden: boolean) => void; + isSynapseLinkUpdating: boolean; + setIsSynapseLinkUpdating: (isSynapseLinkUpdating: boolean) => void; } export const useCommandBar: UseStore = create((set) => ({ @@ -35,6 +35,8 @@ export const useCommandBar: UseStore = create((set) => ({ setContextButtons: (contextButtons: CommandButtonComponentProps[]) => set((state) => ({ ...state, contextButtons })), isHidden: false, setIsHidden: (isHidden: boolean) => set((state) => ({ ...state, isHidden })), + isSynapseLinkUpdating: false, + setIsSynapseLinkUpdating: (isSynapseLinkUpdating: boolean) => set({ isSynapseLinkUpdating }), })); const useStyles = makeStyles({ @@ -69,26 +71,25 @@ export const CommandBar: React.FC = ({ container }: Props) => { const selectedNodeState = useSelectedNode(); const buttons = useCommandBar((state) => state.contextButtons); const isHidden = useCommandBar((state) => state.isHidden); + const isSynapseLinkUpdating = useCommandBar((state) => state.isSynapseLinkUpdating); // targetDocument is used by referenced components // eslint-disable-next-line @typescript-eslint/no-unused-vars const { targetDocument } = useFluent(); const setKeyboardHandlers = useKeyboardActionGroup(KeyboardActionGroup.COMMAND_BAR); const styles = useStyles(); - const { connectionInfo, isPhoenixNotebooks, isPhoenixFeatures } = useNotebook((state) => ({ - connectionInfo: state.connectionInfo, - isPhoenixNotebooks: state.isPhoenixNotebooks, - isPhoenixFeatures: state.isPhoenixFeatures, - })); - // Subscribe to the store changes that affect button creation const dataPlaneRbacEnabled = useDataPlaneRbac((state) => state.dataPlaneRbacEnabled); const aadTokenUpdated = useDataPlaneRbac((state) => state.aadTokenUpdated); // Memoize the expensive button creation const staticButtons = React.useMemo(() => { - return CommandBarComponentButtonFactory.createStaticCommandBarButtons(container, selectedNodeState); - }, [container, selectedNodeState, dataPlaneRbacEnabled, aadTokenUpdated]); + return CommandBarComponentButtonFactory.createStaticCommandBarButtons( + container, + selectedNodeState, + isSynapseLinkUpdating, + ); + }, [container, selectedNodeState, dataPlaneRbacEnabled, aadTokenUpdated, isSynapseLinkUpdating]); if (userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo") { const buttons = @@ -134,11 +135,6 @@ export const CommandBar: React.FC = ({ container }: Props) => { const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, "var(--colorNeutralBackground1)"); uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true)); - // Add connection status if needed (using the hook values we got at the top level) - if ((isPhoenixNotebooks || isPhoenixFeatures) && connectionInfo?.status !== ConnectionStatusType.Connect) { - uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus(container, "connectionStatus")); - } - const rootStyle = { root: { backgroundColor: "var(--colorNeutralBackground1)", diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts index c0d97bb02..0008f99a3 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts @@ -4,7 +4,6 @@ import { DatabaseAccount } from "../../../Contracts/DataModels"; import { CollectionBase } from "../../../Contracts/ViewModels"; import { updateUserContext } from "../../../UserContext"; import Explorer from "../../Explorer"; -import { useNotebook } from "../../Notebook/useNotebook"; import { useDatabases } from "../../useDatabases"; import { useSelectedNode } from "../../useSelectedNode"; import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory"; @@ -37,6 +36,18 @@ describe("CommandBarComponentButtonFactory tests", () => { expect(enableAzureSynapseLinkBtn).toBeDefined(); }); + it("Button should be disabled while Synapse Link is updating", () => { + const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons( + mockExplorer, + selectedNodeState, + true, + ); + const enableAzureSynapseLinkBtn = buttons.find( + (button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel, + ); + expect(enableAzureSynapseLinkBtn.disabled).toBe(true); + }); + // TODO: Now that Tables API supports dataplane RBAC, calling createStaticCommandBarButtons will enable the // Entra ID Login button, which causes this test to fail due to "Invalid hook call.". This seems to be // unsupported in jest and needs to be tested with react-hooks-testing-library. @@ -99,11 +110,6 @@ describe("CommandBarComponentButtonFactory tests", () => { }); }); - afterEach(() => { - useNotebook.getState().setIsNotebookEnabled(false); - useNotebook.getState().setIsNotebooksEnabledForAccount(false); - }); - it("Cassandra Api not available - button should be hidden", () => { updateUserContext({ databaseAccount: { @@ -127,7 +133,7 @@ describe("CommandBarComponentButtonFactory tests", () => { expect(openCassandraShellBtn).toBeUndefined(); }); - it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => { + it("Cloud Shell is unavailable - button should be hidden", () => { const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); expect(openCassandraShellBtn).toBeUndefined(); diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx index ada89d807..c174c8f3a 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx @@ -30,7 +30,6 @@ import { useSidePanel } from "../../../hooks/useSidePanel"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { useDialog } from "../../Controls/Dialog"; import Explorer from "../../Explorer"; -import { useNotebook } from "../../Notebook/useNotebook"; import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane"; import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane"; import { SettingsPane, useDataPlaneRbac } from "../../Panes/SettingsPane/SettingsPane"; @@ -43,6 +42,7 @@ let counter = 0; export function createStaticCommandBarButtons( container: Explorer, selectedNodeState: SelectedNodeState, + isSynapseLinkUpdating = false, ): CommandButtonComponentProps[] { if (userContext.authType === AuthType.ResourceToken) { return createStaticCommandBarButtonsForResourceToken(container, selectedNodeState); @@ -62,7 +62,7 @@ export function createStaticCommandBarButtons( userContext.apiType !== "Tables" && userContext.apiType !== "Cassandra" ) { - const addSynapseLink = createOpenSynapseLinkDialogButton(container); + const addSynapseLink = createOpenSynapseLinkDialogButton(container, isSynapseLinkUpdating); if (addSynapseLink) { addDivider(); buttons.push(addSynapseLink); @@ -136,14 +136,13 @@ export function createContextCommandBarButtons( const buttons: CommandButtonComponentProps[] = []; if (!selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") { - const label = - useNotebook.getState().isShellEnabled || userContext.features.enableCloudShell ? "Open Mongo Shell" : "New Shell"; + const label = userContext.features.enableCloudShell ? "Open Mongo Shell" : "New Shell"; const newMongoShellBtn: CommandButtonComponentProps = { iconSrc: HostedTerminalIcon, iconAlt: label, onCommandClick: () => { const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection(); - if (useNotebook.getState().isShellEnabled || userContext.features.enableCloudShell) { + if (userContext.features.enableCloudShell) { container.openNotebookTerminal(ViewModels.TerminalKind.Mongo); } else { selectedCollection && selectedCollection.onNewMongoShellClick(); @@ -157,7 +156,7 @@ export function createContextCommandBarButtons( } if ( - (useNotebook.getState().isShellEnabled || userContext.features.enableCloudShell) && + userContext.features.enableCloudShell && !selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Cassandra" ) { @@ -252,7 +251,10 @@ function areScriptsSupported(): boolean { ); } -function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonComponentProps { +function createOpenSynapseLinkDialogButton( + container: Explorer, + isSynapseLinkUpdating: boolean, +): CommandButtonComponentProps { if (configContext.platform === Platform.Emulator) { return undefined; } @@ -273,7 +275,7 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo onCommandClick: () => container.openEnableSynapseLinkDialog(), commandButtonLabel: label, hasPopup: false, - disabled: useNotebook.getState().isSynapseLinkUpdating, + disabled: isSynapseLinkUpdating, ariaLabel: label, }; } @@ -514,14 +516,9 @@ function createOpenTerminalButtonByKind( } }; const label = `Open ${terminalFriendlyName()} Shell`; - const tooltip = - "This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."; + const tooltip = "Cloud Shell is not available for this account."; const isNativeAuthDisabled = terminalKind === ViewModels.TerminalKind.VCoreMongo && isVCoreMongoNativeAuthDisabled(); - const disableButton = - (!useNotebook.getState().isNotebooksEnabledForAccount && - !useNotebook.getState().isNotebookEnabled && - !userContext.features.enableCloudShell) || - isNativeAuthDisabled; + const disableButton = !userContext.features.enableCloudShell || isNativeAuthDisabled; return { iconSrc: HostedTerminalIcon, iconAlt: label, @@ -533,7 +530,7 @@ function createOpenTerminalButtonByKind( }); return; } - if (useNotebook.getState().isNotebookEnabled || userContext.features.enableCloudShell) { + if (userContext.features.enableCloudShell) { container.openNotebookTerminal(terminalKind); } }, diff --git a/src/Explorer/Menus/CommandBar/CommandBarUtil.tsx b/src/Explorer/Menus/CommandBar/CommandBarUtil.tsx index 40628db28..b298be795 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarUtil.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarUtil.tsx @@ -17,9 +17,6 @@ import { configContext, Platform } from "../../../ConfigContext"; import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; -import Explorer from "../../Explorer"; -import { ConnectionStatus } from "./ConnectionStatusComponent"; -import { MemoryTracker } from "./MemoryTrackerComponent"; /** * Convert our NavbarButtonConfig to UI Fabric buttons @@ -258,20 +255,6 @@ export const createDivider = (key: string): ICommandBarItemProps => { }; }; -export const createMemoryTracker = (key: string): ICommandBarItemProps => { - return { - key, - onRender: () => , - }; -}; - -export const createConnectionStatus = (container: Explorer, key: string): ICommandBarItemProps => { - return { - key, - onRender: () => , - }; -}; - export function createKeyboardHandlers(allButtons: CommandButtonComponentProps[]): KeyboardHandlerMap { const handlers: KeyboardHandlerMap = {}; diff --git a/src/Explorer/Menus/CommandBar/ConnectionStatusComponent.less b/src/Explorer/Menus/CommandBar/ConnectionStatusComponent.less deleted file mode 100644 index 0688e0baa..000000000 --- a/src/Explorer/Menus/CommandBar/ConnectionStatusComponent.less +++ /dev/null @@ -1,184 +0,0 @@ -@import "../../../../less/Common/Constants"; - -.connectionStatusContainer { - cursor: default; - align-items: center; - border: 1px; - min-height: 44px; - - > span { - padding-right: 12px; - font-size: 12px; - font-family: @DataExplorerFont; - color: @DefaultFontColor; - } - &:focus{ - outline: 0px; - } -} -.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; - display: block; - margin-right: 8px; - width: 1em; - height: 1em; - font-size: 9px!important; - padding: 0px!important; - border-radius: 0.5em; - } - - .status::before, - .status::after { - position: absolute; - content: ""; - } - - .status::before { - top: 0; - left: 0; - width: 1em; - height: 1em; - background-color: rgba(#fff, 0.1); - border-radius: 100%; - opacity: 1; - transform: translate3d(0, 0, 0) scale(0); - } - - .connected{ - background-color: green; - box-shadow: - 0 0 0 0em rgba(green, 0), - 0em 0.05em 0.1em rgba(#000000, 0.2); - transform: translate3d(0, 0, 0) scale(1); - } - .connecting{ - background-color:#ffbf00; - box-shadow: - 0 0 0 0em rgba(#ffbf00, 0), - 0em 0.05em 0.1em rgba(#000000, 0.2); - transform: translate3d(0, 0, 0) scale(1); - } - .failed{ - background-color:#bd1919; - box-shadow: - 0 0 0 0em rgba(#bd1919, 0), - 0em 0.05em 0.1em rgba(#000000, 0.2); - transform: translate3d(0, 0, 0) scale(1); - } - - .status.connecting.is-animating { - animation: status-outer-connecting 3000ms infinite; - } - .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); - } - } \ No newline at end of file diff --git a/src/Explorer/Menus/CommandBar/ConnectionStatusComponent.tsx b/src/Explorer/Menus/CommandBar/ConnectionStatusComponent.tsx deleted file mode 100644 index 60499661f..000000000 --- a/src/Explorer/Menus/CommandBar/ConnectionStatusComponent.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import { - FocusTrapCallout, - FocusZone, - FocusZoneTabbableElements, - FontWeights, - Icon, - mergeStyleSets, - ProgressIndicator, - Stack, - Text, - TooltipHost, -} from "@fluentui/react"; -import { useId } from "@fluentui/react-hooks"; -import { ActionButton, DefaultButton } from "@fluentui/react/lib/Button"; -import * as React from "react"; -import "../../../../less/hostedexplorer.less"; -import { ConnectionStatusType, ContainerStatusType, Notebook } from "../../../Common/Constants"; -import Explorer from "../../Explorer"; -import { useNotebook } from "../../Notebook/useNotebook"; -import "../CommandBar/ConnectionStatusComponent.less"; -interface Props { - container: Explorer; -} -export const ConnectionStatus: React.FC = ({ container }: Props): JSX.Element => { - const connectionInfo = useNotebook((state) => state.connectionInfo); - const [second, setSecond] = React.useState("00"); - const [minute, setMinute] = React.useState("00"); - const [isActive, setIsActive] = React.useState(false); - const [counter, setCounter] = React.useState(0); - const [statusColor, setStatusColor] = React.useState(""); - const [toolTipContent, setToolTipContent] = React.useState("Connect to temporary workspace."); - const [isBarDismissed, setIsBarDismissed] = React.useState(false); - const buttonId = useId("callout-button"); - const containerInfo = useNotebook((state) => state.containerStatus); - - const styles = mergeStyleSets({ - callout: { - width: 320, - padding: "20px 24px", - }, - title: { - marginBottom: 12, - fontWeight: FontWeights.semilight, - }, - buttons: { - display: "flex", - justifyContent: "flex-end", - marginTop: 20, - }, - }); - - React.useEffect(() => { - let intervalId: NodeJS.Timeout; - - if (isActive) { - intervalId = setInterval(() => { - const secondCounter = counter % 60; - const minuteCounter = Math.floor(counter / 60); - const computedSecond: string = String(secondCounter).length === 1 ? `0${secondCounter}` : `${secondCounter}`; - const computedMinute: string = String(minuteCounter).length === 1 ? `0${minuteCounter}` : `${minuteCounter}`; - - setSecond(computedSecond); - setMinute(computedMinute); - - setCounter((counter) => counter + 1); - }, 1000); - } - return () => clearInterval(intervalId); - }, [isActive, counter]); - - React.useEffect(() => { - if (connectionInfo?.status === ConnectionStatusType.Reconnect) { - setToolTipContent("Click here to Reconnect to temporary workspace."); - } else if (connectionInfo?.status === ConnectionStatusType.Failed) { - setStatusColor("status failed is-animating"); - setToolTipContent("Click here to Reconnect to temporary workspace."); - } - }, [connectionInfo.status]); - - const stopTimer = () => { - setIsActive(false); - setCounter(0); - setSecond("00"); - setMinute("00"); - }; - - const memoryUsageInfo = useNotebook((state) => state.memoryUsageInfo); - const totalGB = memoryUsageInfo ? memoryUsageInfo.totalKB / Notebook.memoryGuageToGB : 0; - const usedGB = totalGB > 0 ? totalGB - memoryUsageInfo.freeKB / Notebook.memoryGuageToGB : 0; - - if ( - connectionInfo && - (connectionInfo.status === ConnectionStatusType.Connect || connectionInfo.status === ConnectionStatusType.Reconnect) - ) { - return ( - container.allocateContainer()}> - - - - {connectionInfo.status} - - - - ); - } - - if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connecting && isActive === false) { - stopTimer(); - 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 ( - <> - - ) => - connectionInfo.status === ConnectionStatusType.Failed ? container.allocateContainer() : e.preventDefault() - } - > - - - - {connectionInfo.status} - - {connectionInfo.status === ConnectionStatusType.Connecting && isActive && ( - - )} - {connectionInfo.status === ConnectionStatusType.Connected && !isActive && ( - 0.8 ? "lowMemory" : ""} - description={usedGB.toFixed(1) + " of " + totalGB.toFixed(1) + " GB"} - percentComplete={totalGB !== 0 ? usedGB / totalGB : 0} - /> - )} - - {!isBarDismissed && - containerInfo.status && - containerInfo.status === ContainerStatusType.Active && - Math.round(containerInfo.durationLeftInMinutes) <= Notebook.remainingTimeForAlert ? ( - setIsBarDismissed(true)} - setInitialFocus - > - - Remaining Time - - - This temporary workspace will get disconnected in {Math.round(containerInfo.durationLeftInMinutes)}{" "} - minutes. To save your work permanently, save your notebooks to a GitHub repository or download the - notebooks to your local machine before the session ends. - - - - setIsBarDismissed(true)}>Dimiss - - - - ) : undefined} - - - - ); -}; diff --git a/src/Explorer/Menus/CommandBar/MemoryTrackerComponent.less b/src/Explorer/Menus/CommandBar/MemoryTrackerComponent.less deleted file mode 100644 index 404025a91..000000000 --- a/src/Explorer/Menus/CommandBar/MemoryTrackerComponent.less +++ /dev/null @@ -1,24 +0,0 @@ -@import "../../../../less/Common/Constants"; - -.memoryTrackerContainer { - cursor: default; - align-items: center; - margin: 0 9px; - border: 1px; - - > span { - padding-right: 12px; - font-size: 13px; - font-family: @DataExplorerFont; - color: @DefaultFontColor; - } - - > .lowMemory { - .ms-ProgressIndicator-progressBar { - background-color: @SelectionHigh; - } - .ms-ProgressIndicator-itemDescription { - color: @SelectionHigh; - } - } -} \ No newline at end of file diff --git a/src/Explorer/Menus/CommandBar/MemoryTrackerComponent.tsx b/src/Explorer/Menus/CommandBar/MemoryTrackerComponent.tsx deleted file mode 100644 index ac0621897..000000000 --- a/src/Explorer/Menus/CommandBar/MemoryTrackerComponent.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { ProgressIndicator, Spinner, SpinnerSize, Stack } from "@fluentui/react"; -import * as React from "react"; -import { useNotebook } from "../../Notebook/useNotebook"; - -export const MemoryTracker: React.FC = (): JSX.Element => { - const memoryUsageInfo = useNotebook((state) => state.memoryUsageInfo); - if (!memoryUsageInfo) { - return ( - - Memory - - - ); - } - - const totalGB = memoryUsageInfo.totalKB / 1048576; - const usedGB = totalGB - memoryUsageInfo.freeKB / 1048576; - - return ( - - Memory - 0.8 ? "lowMemory" : ""} - description={usedGB.toFixed(1) + " of " + totalGB.toFixed(1) + " GB"} - percentComplete={usedGB / totalGB} - /> - - ); -}; diff --git a/src/Explorer/Notebook/NotebookContainerClient.ts b/src/Explorer/Notebook/NotebookContainerClient.ts deleted file mode 100644 index 76d4ba2b8..000000000 --- a/src/Explorer/Notebook/NotebookContainerClient.ts +++ /dev/null @@ -1,195 +0,0 @@ -/** - * Notebook container related stuff - */ -import { useDialog } from "Explorer/Controls/Dialog"; -import promiseRetry, { AbortError, Options } from "p-retry"; -import { PhoenixClient } from "Phoenix/PhoenixClient"; -import * as Constants from "../../Common/Constants"; -import { ConnectionStatusType, HttpHeaders, HttpStatusCodes, Notebook, PoolIdType } from "../../Common/Constants"; -import { getErrorMessage } from "../../Common/ErrorHandlingUtils"; -import * as Logger from "../../Common/Logger"; -import * as DataModels from "../../Contracts/DataModels"; -import { IPhoenixServiceInfo, IProvisionData, IResponse } from "../../Contracts/DataModels"; -import { userContext } from "../../UserContext"; -import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils"; -import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; -import { useNotebook } from "./useNotebook"; - -export class NotebookContainerClient { - private clearReconnectionAttemptMessage? = () => {}; - private isResettingWorkspace: boolean; - private phoenixClient: PhoenixClient; - private retryOptions: Options; - private scheduleTimerId: NodeJS.Timeout; - - constructor(private onConnectionLost: () => void) { - this.phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id); - this.retryOptions = { - retries: Notebook.retryAttempts, - maxTimeout: Notebook.retryAttemptDelayMs, - minTimeout: Notebook.retryAttemptDelayMs, - }; - - this.initHeartbeat(Constants.Notebook.heartbeatDelayMs); - } - - private initHeartbeat(delayMs: number): void { - this.scheduleHeartbeat(delayMs); - - useNotebook.subscribe( - () => this.scheduleHeartbeat(delayMs), - (state) => state.notebookServerInfo, - ); - } - - private scheduleHeartbeat(delayMs: number) { - if (this.scheduleTimerId) { - clearInterval(this.scheduleTimerId); - } - - const notebookServerInfo = useNotebook.getState().notebookServerInfo; - if (notebookServerInfo?.notebookServerEndpoint) { - this.scheduleTimerId = setInterval(async () => { - const notebookServerInfo = useNotebook.getState().notebookServerInfo; - if (notebookServerInfo?.notebookServerEndpoint) { - const memoryUsageInfo = await this.getMemoryUsage(); - useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo); - } - }, delayMs); - } - } - - public async getMemoryUsage(): Promise { - const notebookServerInfo = useNotebook.getState().notebookServerInfo; - if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) { - const error = "No server endpoint detected"; - Logger.logError(error, "NotebookContainerClient/getMemoryUsage"); - return Promise.reject(error); - } - - if (this.isResettingWorkspace) { - return undefined; - } - - const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig(); - try { - const runMemoryAsync = async () => { - return await this._getMemoryAsync(notebookServerEndpoint, authToken); - }; - return await promiseRetry(runMemoryAsync, this.retryOptions); - } catch (error) { - Logger.logError(getErrorMessage(error), "NotebookContainerClient/getMemoryUsage"); - if (!this.clearReconnectionAttemptMessage) { - this.clearReconnectionAttemptMessage = logConsoleProgress( - "Connection lost with Notebook server. Attempting to reconnect...", - ); - } - this.onConnectionLost(); - return undefined; - } - } - - private async _getMemoryAsync( - notebookServerEndpoint: string, - authToken: string, - ): Promise { - if (this.shouldExecuteMemoryCall()) { - const response = await fetch(`${notebookServerEndpoint}api/metrics/memory`, { - method: "GET", - headers: { - Authorization: authToken, - "content-type": "application/json", - }, - }); - if (response.ok) { - if (this.clearReconnectionAttemptMessage) { - this.clearReconnectionAttemptMessage(); - this.clearReconnectionAttemptMessage = undefined; - } - const memoryUsageInfo = await response.json(); - if (memoryUsageInfo) { - return { - totalKB: memoryUsageInfo.total, - freeKB: memoryUsageInfo.free, - }; - } - } else if (response.status === HttpStatusCodes.NotFound) { - throw new AbortError(response.statusText); - } - throw new Error(response.statusText); - } else { - return undefined; - } - } - - private shouldExecuteMemoryCall(): boolean { - return ( - useNotebook.getState().containerStatus?.status === Constants.ContainerStatusType.Active && - useNotebook.getState().connectionInfo?.status === ConnectionStatusType.Connected - ); - } - - public async resetWorkspace(): Promise> { - this.isResettingWorkspace = true; - let response: IResponse; - try { - response = await this._resetWorkspace(); - } catch (error) { - Promise.reject(error); - return response; - } - this.isResettingWorkspace = false; - return response; - } - - private async _resetWorkspace(): Promise> { - const notebookServerInfo = useNotebook.getState().notebookServerInfo; - if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) { - const error = "No server endpoint detected"; - Logger.logError(error, "NotebookContainerClient/resetWorkspace"); - return Promise.reject(error); - } - - try { - if (useNotebook.getState().isPhoenixNotebooks) { - const provisionData: IProvisionData = { - cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint, - poolId: PoolIdType.DefaultPoolId, - }; - return await this.phoenixClient.resetContainer(provisionData); - } - return null; - } catch (error) { - Logger.logError(getErrorMessage(error), "NotebookContainerClient/resetWorkspace"); - if (error?.status === HttpStatusCodes.Forbidden && error.message) { - useDialog.getState().showOkModalDialog("Connection Failed", `${error.message}`); - } else { - useDialog - .getState() - .showOkModalDialog( - "Connection Failed", - "We are unable to connect to the temporary workspace. Please try again in a few minutes. If the error persists, file a support ticket.", - ); - } - throw error; - } - } - - private getNotebookServerConfig(): { notebookServerEndpoint: string; authToken: string } { - const notebookServerInfo = useNotebook.getState().notebookServerInfo; - const authToken: string = notebookServerInfo.authToken ? `Token ${notebookServerInfo.authToken}` : undefined; - - return { - notebookServerEndpoint: notebookServerInfo.notebookServerEndpoint, - authToken, - }; - } - - private getHeaders(): HeadersInit { - const authorizationHeader = getAuthorizationHeader(); - return { - [authorizationHeader.header]: authorizationHeader.token, - [HttpHeaders.contentType]: "application/json", - }; - } -} diff --git a/src/Explorer/Notebook/NotebookContentItem.ts b/src/Explorer/Notebook/NotebookContentItem.ts deleted file mode 100644 index bb79485b8..000000000 --- a/src/Explorer/Notebook/NotebookContentItem.ts +++ /dev/null @@ -1,14 +0,0 @@ -export interface NotebookContentItem { - name: string; - path: string; - type: NotebookContentItemType; - children?: NotebookContentItem[]; - parent?: NotebookContentItem; - timestamp?: number; -} - -export enum NotebookContentItemType { - Notebook, - File, - Directory, -} diff --git a/src/Explorer/Notebook/NotebookManager.tsx b/src/Explorer/Notebook/NotebookManager.tsx deleted file mode 100644 index 175c76061..000000000 --- a/src/Explorer/Notebook/NotebookManager.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Contains all notebook related stuff meant to be dynamically loaded by explorer - */ - -import { JunoClient } from "../../Juno/JunoClient"; -import { userContext } from "../../UserContext"; -import Explorer from "../Explorer"; -import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter"; -import { NotebookContainerClient } from "./NotebookContainerClient"; - -export interface NotebookManagerOptions { - container: Explorer; - resourceTree: ResourceTreeAdapter; - refreshCommandBarButtons: () => void; - refreshNotebookList: () => void; -} - -export default class NotebookManager { - private params: NotebookManagerOptions; - public junoClient: JunoClient; - - public notebookClient: NotebookContainerClient; - - public initialize(params: NotebookManagerOptions): void { - this.params = params; - this.junoClient = new JunoClient(); - - this.notebookClient = new NotebookContainerClient(() => - this.params.container.initNotebooks(userContext?.databaseAccount), - ); - } -} diff --git a/src/Explorer/Notebook/NotebookUtil.test.ts b/src/Explorer/Notebook/NotebookUtil.test.ts deleted file mode 100644 index 97ff4514a..000000000 --- a/src/Explorer/Notebook/NotebookUtil.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { NotebookUtil } from "./NotebookUtil"; - -const fileName = "file"; -const notebookName = "file.ipynb"; -const folderPath = "folder"; -const filePath = `${folderPath}/${fileName}`; -const notebookPath = `${folderPath}/${notebookName}`; - -describe("NotebookUtil", () => { - describe("isNotebookFile", () => { - it("works for jupyter file paths", () => { - expect(NotebookUtil.isNotebookFile(filePath)).toBeFalsy(); - expect(NotebookUtil.isNotebookFile(notebookPath)).toBeTruthy(); - }); - }); - - describe("getFilePath", () => { - it("works for jupyter file paths", () => { - expect(NotebookUtil.getFilePath(folderPath, fileName)).toEqual(filePath); - }); - }); - - describe("getParentPath", () => { - it("works for jupyter file paths", () => { - expect(NotebookUtil.getParentPath(filePath)).toEqual(folderPath); - }); - }); - - describe("getName", () => { - it("works for jupyter file paths", () => { - expect(NotebookUtil.getName(filePath)).toEqual(fileName); - expect(NotebookUtil.getName(notebookPath)).toEqual(notebookName); - }); - }); - - describe("replaceName", () => { - it("works for jupyter file paths", () => { - expect(NotebookUtil.replaceName(filePath, "newName")).toEqual(filePath.replace(fileName, "newName")); - expect(NotebookUtil.replaceName(notebookPath, "newName")).toEqual(notebookPath.replace(notebookName, "newName")); - }); - }); -}); diff --git a/src/Explorer/Notebook/NotebookUtil.ts b/src/Explorer/Notebook/NotebookUtil.ts deleted file mode 100644 index d8e752f3d..000000000 --- a/src/Explorer/Notebook/NotebookUtil.ts +++ /dev/null @@ -1,81 +0,0 @@ -import * as StringUtils from "../../Utils/StringUtils"; -import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem"; - -// Must match rx-jupyter' FileType -export type FileType = "directory" | "file" | "notebook"; - -// Utilities for notebooks -export class NotebookUtil { - /** - * It's a notebook file if the filename ends with .ipynb. - */ - public static isNotebookFile(notebookPath: string): boolean { - const fileName = NotebookUtil.getName(notebookPath); - return !!fileName && StringUtils.endsWith(fileName, ".ipynb"); - } - - /** - * Note: this does not connect the item to a parent in a tree. - * @param name - * @param path - */ - public static createNotebookContentItem(name: string, path: string, type: FileType): NotebookContentItem { - return { - name, - path, - type: NotebookUtil.getType(type), - timestamp: NotebookUtil.getCurrentTimestamp(), - }; - } - - /** - * Convert rx-jupyter type to our type - * @param type - */ - public static getType(type: FileType): NotebookContentItemType { - switch (type) { - case "directory": - return NotebookContentItemType.Directory; - case "notebook": - return NotebookContentItemType.Notebook; - case "file": - return NotebookContentItemType.File; - default: - throw new Error(`Unknown file type: ${type}`); - } - } - - public static getCurrentTimestamp(): number { - return new Date().getTime(); - } - - public static getFilePath(path: string, fileName: string): string { - return `${path}/${fileName}`; - } - - public static getParentPath(filepath: string): undefined | string { - const basename = NotebookUtil.getName(filepath); - if (basename) { - const parentPath = filepath.split(basename).shift(); - if (parentPath) { - return parentPath.replace(/\/$/, ""); // no trailling slash - } - } - - return undefined; - } - - public static getName(path: string): undefined | string { - return path.split("/").pop(); - } - - public static replaceName(path: string, newName: string): string { - const contentName = path.split("/").pop(); - if (!contentName) { - throw new Error(`Failed to extract name from path ${path}`); - } - - const basePath = path.split(contentName).shift(); - return `${basePath}${newName}`; - } -} diff --git a/src/Explorer/Notebook/useNotebook.ts b/src/Explorer/Notebook/useNotebook.ts deleted file mode 100644 index 608358cf7..000000000 --- a/src/Explorer/Notebook/useNotebook.ts +++ /dev/null @@ -1,248 +0,0 @@ -import { cloneDeep } from "lodash"; -import create, { UseStore } from "zustand"; -import { AuthType } from "../../AuthType"; -import * as Constants from "../../Common/Constants"; -import { ConnectionStatusType } from "../../Common/Constants"; -import { getErrorMessage } from "../../Common/ErrorHandlingUtils"; -import * as Logger from "../../Common/Logger"; -import { configContext } from "../../ConfigContext"; -import * as DataModels from "../../Contracts/DataModels"; -import { ContainerConnectionInfo, ContainerInfo } from "../../Contracts/DataModels"; -import { Action } from "../../Shared/Telemetry/TelemetryConstants"; -import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; -import { userContext } from "../../UserContext"; -import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils"; -import { useTabs } from "../../hooks/useTabs"; -import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem"; - -interface NotebookState { - isNotebookEnabled: boolean; - isNotebooksEnabledForAccount: boolean; - notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo; - sparkClusterConnectionInfo: DataModels.SparkClusterConnectionInfo; - isSynapseLinkUpdating: boolean; - memoryUsageInfo: DataModels.MemoryUsageInfo; - isShellEnabled: boolean; - notebookBasePath: string; - isInitializingNotebooks: boolean; - myNotebooksContentRoot: NotebookContentItem; - galleryContentRoot: NotebookContentItem; - connectionInfo: ContainerConnectionInfo; - notebookFolderName: string; - isAllocating: boolean; - isRefreshed: boolean; - containerStatus: ContainerInfo; - isPhoenixNotebooks: boolean; - isPhoenixFeatures: boolean; - setIsNotebookEnabled: (isNotebookEnabled: boolean) => void; - setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void; - setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void; - setSparkClusterConnectionInfo: (sparkClusterConnectionInfo: DataModels.SparkClusterConnectionInfo) => void; - setIsSynapseLinkUpdating: (isSynapseLinkUpdating: boolean) => void; - setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => void; - setIsShellEnabled: (isShellEnabled: boolean) => void; - setNotebookBasePath: (notebookBasePath: string) => void; - setNotebookFolderName: (notebookFolderName: string) => void; - refreshNotebooksEnabledStateForAccount: () => Promise; - findItem: (root: NotebookContentItem, item: NotebookContentItem) => NotebookContentItem; - insertNotebookItem: (parent: NotebookContentItem, item: NotebookContentItem) => void; - updateNotebookItem: (item: NotebookContentItem) => void; - deleteNotebookItem: (item: NotebookContentItem) => void; - initializeNotebooksTree: () => Promise; - setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => void; - setIsAllocating: (isAllocating: boolean) => void; - resetContainerConnection: (connectionStatus: ContainerConnectionInfo) => void; - setIsRefreshed: (isAllocating: boolean) => void; - setContainerStatus: (containerStatus: ContainerInfo) => void; - getPhoenixStatus: () => Promise; - setIsPhoenixNotebooks: (isPhoenixNotebooks: boolean) => void; - setIsPhoenixFeatures: (isPhoenixFeatures: boolean) => void; -} - -export const useNotebook: UseStore = create((set, get) => ({ - isNotebookEnabled: false, - isNotebooksEnabledForAccount: false, - notebookServerInfo: { - notebookServerEndpoint: undefined, - authToken: undefined, - forwardingId: undefined, - }, - sparkClusterConnectionInfo: { - userName: undefined, - password: undefined, - endpoints: [], - }, - isSynapseLinkUpdating: false, - memoryUsageInfo: undefined, - isShellEnabled: false, - notebookBasePath: Constants.Notebook.defaultBasePath, - isInitializingNotebooks: false, - myNotebooksContentRoot: undefined, - galleryContentRoot: undefined, - connectionInfo: { - status: ConnectionStatusType.Connect, - }, - notebookFolderName: undefined, - isAllocating: false, - isRefreshed: false, - containerStatus: { - status: undefined, - durationLeftInMinutes: undefined, - phoenixServerInfo: undefined, - }, - isPhoenixNotebooks: undefined, - isPhoenixFeatures: undefined, - setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }), - setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }), - setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => - set({ notebookServerInfo }), - setSparkClusterConnectionInfo: (sparkClusterConnectionInfo: DataModels.SparkClusterConnectionInfo) => - set({ sparkClusterConnectionInfo }), - setIsSynapseLinkUpdating: (isSynapseLinkUpdating: boolean) => set({ isSynapseLinkUpdating }), - setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => set({ memoryUsageInfo }), - setIsShellEnabled: (isShellEnabled: boolean) => set({ isShellEnabled }), - setNotebookBasePath: (notebookBasePath: string) => set({ notebookBasePath }), - setNotebookFolderName: (notebookFolderName: string) => set({ notebookFolderName }), - refreshNotebooksEnabledStateForAccount: async (): Promise => { - await get().getPhoenixStatus(); - const { databaseAccount, authType } = userContext; - if ( - authType === AuthType.EncryptedToken || - authType === AuthType.ResourceToken || - authType === AuthType.MasterKey - ) { - set({ isNotebooksEnabledForAccount: false }); - return; - } - - const firstWriteLocation = - userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo" - ? databaseAccount?.location - : databaseAccount?.properties?.writeLocations?.[0]?.locationName.toLowerCase(); - const disallowedLocationsUri: string = `${configContext.PORTAL_BACKEND_ENDPOINT}/api/disallowedlocations`; - const authorizationHeader = getAuthorizationHeader(); - const startKey = TelemetryProcessor.traceStart(Action.RefreshNotebooksEnabled, { - dataExplorerArea: "Notebook", - }); - try { - const response = await fetch(disallowedLocationsUri, { - method: "POST", - body: JSON.stringify({ - resourceTypes: [Constants.ArmResourceTypes.notebookWorkspaces], - }), - headers: { - [authorizationHeader.header]: authorizationHeader.token, - [Constants.HttpHeaders.contentType]: "application/json", - }, - }); - - if (!response.ok) { - throw new Error("Failed to fetch disallowed locations"); - } - - const disallowedLocations: string[] = await response.json(); - if (!disallowedLocations) { - Logger.logInfo("No disallowed locations found", "Explorer/isNotebooksEnabledForAccount"); - set({ isNotebooksEnabledForAccount: true }); - return; - } - - // firstWriteLocation should not be disallowed - const isAccountInAllowedLocation = firstWriteLocation && disallowedLocations.indexOf(firstWriteLocation) === -1; - set({ isNotebooksEnabledForAccount: isAccountInAllowedLocation }); - TelemetryProcessor.traceSuccess(Action.RefreshNotebooksEnabled, { isAccountInAllowedLocation }, startKey); - } catch (error) { - Logger.logError(getErrorMessage(error), "Explorer/isNotebooksEnabledForAccount"); - set({ isNotebooksEnabledForAccount: false }); - TelemetryProcessor.traceFailure(Action.RefreshNotebooksEnabled, { error: getErrorMessage(error) }, startKey); - } - }, - findItem: (root: NotebookContentItem, item: NotebookContentItem): NotebookContentItem => { - const currentItem = root || get().myNotebooksContentRoot; - - if (currentItem) { - if (currentItem.path === item.path && currentItem.name === item.name) { - return currentItem; - } - - if (currentItem.children) { - for (const childItem of currentItem.children) { - const result = get().findItem(childItem, item); - if (result) { - return result; - } - } - } - } - - return undefined; - }, - insertNotebookItem: (parent: NotebookContentItem, item: NotebookContentItem): void => { - const root = cloneDeep(get().myNotebooksContentRoot); - const parentItem = get().findItem(root, parent); - item.parent = parentItem; - if (parentItem.children) { - parentItem.children.push(item); - } else { - parentItem.children = [item]; - } - set({ myNotebooksContentRoot: root }); - }, - updateNotebookItem: (item: NotebookContentItem): void => { - const root = cloneDeep(get().myNotebooksContentRoot); - const parentItem = get().findItem(root, item.parent); - parentItem.children = parentItem.children.filter((child) => child.path !== item.path); - parentItem.children.push(item); - item.parent = parentItem; - set({ myNotebooksContentRoot: root }); - }, - deleteNotebookItem: (item: NotebookContentItem): void => { - const root = cloneDeep(get().myNotebooksContentRoot); - const parentItem = get().findItem(root, item.parent); - parentItem.children = parentItem.children.filter((child) => child.path !== item.path); - set({ myNotebooksContentRoot: root }); - }, - initializeNotebooksTree: async (): Promise => { - const notebookFolderName = get().isPhoenixNotebooks ? "Temporary Notebooks" : "My Notebooks"; - set({ notebookFolderName }); - const myNotebooksContentRoot = { - name: get().notebookFolderName, - path: get().notebookBasePath, - type: NotebookContentItemType.Directory, - }; - const galleryContentRoot = { - name: "Gallery", - path: "Gallery", - type: NotebookContentItemType.File, - }; - - set({ - myNotebooksContentRoot, - galleryContentRoot, - }); - }, - setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => set({ connectionInfo }), - setIsAllocating: (isAllocating: boolean) => set({ isAllocating }), - resetContainerConnection: (connectionStatus: ContainerConnectionInfo): void => { - useTabs.getState().closeAllNotebookTabs(true); - useNotebook.getState().setConnectionInfo(connectionStatus); - useNotebook.getState().setNotebookServerInfo(undefined); - useNotebook.getState().setIsAllocating(false); - useNotebook.getState().setContainerStatus({ - status: undefined, - durationLeftInMinutes: undefined, - phoenixServerInfo: undefined, - }); - }, - setIsRefreshed: (isRefreshed: boolean) => set({ isRefreshed }), - setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }), - getPhoenixStatus: async () => { - if (get().isPhoenixNotebooks === undefined || get().isPhoenixFeatures === undefined) { - // getDbAccountAllowedStatus has been deprecated; Phoenix features are no longer available. - set({ isPhoenixNotebooks: false }); - set({ isPhoenixFeatures: false }); - } - }, - setIsPhoenixNotebooks: (isPhoenixNotebooks: boolean) => set({ isPhoenixNotebooks: isPhoenixNotebooks }), - setIsPhoenixFeatures: (isPhoenixFeatures: boolean) => set({ isPhoenixFeatures: isPhoenixFeatures }), -})); diff --git a/src/Explorer/Panes/AddGlobalSecondaryIndexPanel/__snapshots__/AddGlobalSecondaryIndexPanel.test.tsx.snap b/src/Explorer/Panes/AddGlobalSecondaryIndexPanel/__snapshots__/AddGlobalSecondaryIndexPanel.test.tsx.snap index 2495d4165..fd58794e0 100644 --- a/src/Explorer/Panes/AddGlobalSecondaryIndexPanel/__snapshots__/AddGlobalSecondaryIndexPanel.test.tsx.snap +++ b/src/Explorer/Panes/AddGlobalSecondaryIndexPanel/__snapshots__/AddGlobalSecondaryIndexPanel.test.tsx.snap @@ -145,25 +145,15 @@ exports[`AddGlobalSecondaryIndexPanel render default panel 1`] = ` = ({ explorer }) => { React.useEffect(() => { subscriptions.push( - { - dispose: useNotebook.subscribe( - () => setState({}), - (state) => state.isNotebookEnabled, - ), - }, { dispose: useSelectedNode.subscribe(() => setState({})) }, { dispose: useCarousel.subscribe( diff --git a/src/Explorer/Tabs/DocumentsTabV2/DocumentsTabV2.test.tsx b/src/Explorer/Tabs/DocumentsTabV2/DocumentsTabV2.test.tsx index ded16bf78..6bb014011 100644 --- a/src/Explorer/Tabs/DocumentsTabV2/DocumentsTabV2.test.tsx +++ b/src/Explorer/Tabs/DocumentsTabV2/DocumentsTabV2.test.tsx @@ -35,15 +35,6 @@ import * as ViewModels from "../../../Contracts/ViewModels"; import { updateUserContext } from "../../../UserContext"; import Explorer from "../../Explorer"; -jest.mock("rx-jupyter", () => ({ - sessions: { - create: jest.fn(), - }, - contents: { - JupyterContentProvider: jest.fn().mockImplementation(() => ({})), - }, -})); - jest.mock("Common/dataAccess/queryDocuments", () => ({ queryDocuments: jest.fn(() => ({ // Omit headers, because we can't mock a private field and we don't need to test it diff --git a/src/Explorer/Tabs/DocumentsTabV2/DocumentsTabV2Mongo.test.tsx b/src/Explorer/Tabs/DocumentsTabV2/DocumentsTabV2Mongo.test.tsx index e0bacfa5a..7cee70afe 100644 --- a/src/Explorer/Tabs/DocumentsTabV2/DocumentsTabV2Mongo.test.tsx +++ b/src/Explorer/Tabs/DocumentsTabV2/DocumentsTabV2Mongo.test.tsx @@ -19,15 +19,6 @@ import { act } from "react-dom/test-utils"; import * as ViewModels from "../../../Contracts/ViewModels"; import Explorer from "../../Explorer"; -jest.mock("rx-jupyter", () => ({ - sessions: { - create: jest.fn(), - }, - contents: { - JupyterContentProvider: jest.fn().mockImplementation(() => ({})), - }, -})); - jest.requireActual("Explorer/Controls/Editor/EditorReact"); const PROPERTY_VALUE = "__SOME_PROPERTY_VALUE__"; diff --git a/src/Explorer/Tree/Collection.ts b/src/Explorer/Tree/Collection.ts index f78a95eac..78faf849c 100644 --- a/src/Explorer/Tree/Collection.ts +++ b/src/Explorer/Tree/Collection.ts @@ -5,7 +5,6 @@ import { TriggerDefinition, UserDefinedFunctionDefinition, } from "@azure/cosmos"; -import { useNotebook } from "Explorer/Notebook/useNotebook"; import { DocumentsTabV2 } from "Explorer/Tabs/DocumentsTabV2/DocumentsTabV2"; import { isFabricMirrored } from "Platform/Fabric/FabricUtil"; import { useDataplaneRbacAuthorization } from "Utils/AuthorizationUtils"; diff --git a/src/Explorer/Tree/Database.test.tsx b/src/Explorer/Tree/Database.test.tsx index 9fbeb56fe..4bd8f087a 100644 --- a/src/Explorer/Tree/Database.test.tsx +++ b/src/Explorer/Tree/Database.test.tsx @@ -6,15 +6,6 @@ import { updateUserContext, userContext } from "../../UserContext"; import Explorer from "../Explorer"; import Database from "./Database"; -jest.mock("rx-jupyter", () => ({ - sessions: { - create: jest.fn(), - }, - contents: { - JupyterContentProvider: jest.fn().mockImplementation(() => ({})), - }, -})); - const createMockContainer = (): Explorer => { const mockContainer = new Explorer(); return mockContainer; diff --git a/src/Explorer/Tree/ResourceTree.tsx b/src/Explorer/Tree/ResourceTree.tsx index dfcf53422..8ce6e2cb0 100644 --- a/src/Explorer/Tree/ResourceTree.tsx +++ b/src/Explorer/Tree/ResourceTree.tsx @@ -18,12 +18,8 @@ import { userContext } from "UserContext"; import { ReactTabKind, useTabs } from "hooks/useTabs"; import * as React from "react"; import { useEffect, useMemo } from "react"; -import shallow from "zustand/shallow"; import { useDatabaseLoadScenario } from "../../Metrics/useMetricPhases"; import Explorer from "../Explorer"; -import { useNotebook } from "../Notebook/useNotebook"; - -export const MyNotebooksTitle = "My Notebooks"; interface ResourceTreeProps { explorer: Explorer; @@ -40,13 +36,6 @@ export const ResourceTree: React.FC = ({ explorer }: Resource const [openItems, setOpenItems] = React.useState([]); const treeStyles = useTreeStyles(); - const { isNotebookEnabled } = useNotebook( - (state) => ({ - isNotebookEnabled: state.isNotebookEnabled, - }), - shallow, - ); - // We intentionally avoid using a state selector here because we want to re-render the tree if the active tab changes. const { refreshActiveTab } = useTabs(); @@ -65,24 +54,8 @@ export const ResourceTree: React.FC = ({ explorer }: Resource const databaseTreeNodes = useMemo(() => { return userContext.authType === AuthType.ResourceToken ? createResourceTokenTreeNodes(resourceTokenCollection) - : createDatabaseTreeNodes( - explorer, - isNotebookEnabled, - databases, - refreshActiveTab, - searchText, - sortOrder, - pinnedDatabaseIds, - ); - }, [ - resourceTokenCollection, - databases, - isNotebookEnabled, - refreshActiveTab, - searchText, - sortOrder, - pinnedDatabaseIds, - ]); + : createDatabaseTreeNodes(explorer, databases, refreshActiveTab, searchText, sortOrder, pinnedDatabaseIds); + }, [resourceTokenCollection, databases, refreshActiveTab, searchText, sortOrder, pinnedDatabaseIds]); const headerNodes: TreeNode[] = isFabricMirrored() ? [] diff --git a/src/Explorer/Tree/ResourceTreeAdapter.tsx b/src/Explorer/Tree/ResourceTreeAdapter.tsx index e25b76a45..f2f2af369 100644 --- a/src/Explorer/Tree/ResourceTreeAdapter.tsx +++ b/src/Explorer/Tree/ResourceTreeAdapter.tsx @@ -1,34 +1,20 @@ -import { TreeNodeMenuItem } from "Explorer/Controls/TreeComponent/TreeNodeComponent"; import { collectionWasOpened } from "Explorer/MostRecentActivity/MostRecentActivity"; import { shouldShowScriptNodes } from "Explorer/Tree/treeNodeUtil"; import { getItemName } from "Utils/APITypeUtils"; import * as ko from "knockout"; import * as React from "react"; import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg"; -import DeleteIcon from "../../../images/delete.svg"; -import CopyIcon from "../../../images/notebook/Notebook-copy.svg"; -import NewNotebookIcon from "../../../images/notebook/Notebook-new.svg"; -import NotebookIcon from "../../../images/notebook/Notebook-resource.svg"; -import FileIcon from "../../../images/notebook/file-cosmos.svg"; -import RefreshIcon from "../../../images/refresh-cosmos.svg"; import CollectionIcon from "../../../images/tree-collection.svg"; import { ReactAdapter } from "../../Bindings/ReactBindingHandler"; -import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility"; import * as DataModels from "../../Contracts/DataModels"; import * as ViewModels from "../../Contracts/ViewModels"; -import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; -import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import { userContext } from "../../UserContext"; import { isServerlessAccount } from "../../Utils/CapabilityUtils"; import { useTabs } from "../../hooks/useTabs"; import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFactory"; -import { useDialog } from "../Controls/Dialog"; import { LegacyTreeComponent, LegacyTreeNode } from "../Controls/TreeComponent/LegacyTreeComponent"; import Explorer from "../Explorer"; import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter"; -import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem"; -import { NotebookUtil } from "../Notebook/NotebookUtil"; -import { useNotebook } from "../Notebook/useNotebook"; import TabsBase from "../Tabs/TabsBase"; import { useDatabases } from "../useDatabases"; import { useSelectedNode } from "../useSelectedNode"; @@ -37,16 +23,10 @@ import Trigger from "./Trigger"; import UserDefinedFunction from "./UserDefinedFunction"; export class ResourceTreeAdapter implements ReactAdapter { - public static readonly MyNotebooksTitle = "My Notebooks"; - private static readonly DataTitle = "DATA"; - private static readonly NotebooksTitle = "NOTEBOOKS"; - private static readonly PseudoDirPath = "PsuedoDir"; public parameters: ko.Observable; - public myNotebooksContentRoot: NotebookContentItem; - public constructor(private container: Explorer) { this.parameters = ko.observable(Date.now()); @@ -55,56 +35,15 @@ export class ResourceTreeAdapter implements ReactAdapter { () => this.triggerRender(), (state) => state.activeTab, ); - useNotebook.subscribe( - () => this.triggerRender(), - (state) => state.isNotebookEnabled, - ); - useDatabases.subscribe(() => this.triggerRender()); this.triggerRender(); } - private traceMyNotebookTreeInfo() { - const myNotebooksTree = this.myNotebooksContentRoot; - if (myNotebooksTree.children) { - // Count 1st generation children (tree is lazy-loaded) - const nodeCounts = { files: 0, notebooks: 0, directories: 0 }; - myNotebooksTree.children.forEach((treeNode) => { - switch ((treeNode as NotebookContentItem).type) { - case NotebookContentItemType.File: - nodeCounts.files++; - break; - case NotebookContentItemType.Directory: - nodeCounts.directories++; - break; - case NotebookContentItemType.Notebook: - nodeCounts.notebooks++; - break; - default: - break; - } - }); - TelemetryProcessor.trace(Action.RefreshResourceTreeMyNotebooks, ActionModifiers.Mark, { ...nodeCounts }); - } - } - public renderComponent(): JSX.Element { const dataRootNode = this.buildDataTree(); return ; } - public async initialize(): Promise { - const refreshTasks: Promise[] = []; - - this.myNotebooksContentRoot = { - name: useNotebook.getState().notebookFolderName, - path: useNotebook.getState().notebookBasePath, - type: NotebookContentItemType.Directory, - }; - - return Promise.all(refreshTasks); - } - private buildDataTree(): LegacyTreeNode { const databaseTreeNodes: LegacyTreeNode[] = useDatabases .getState() @@ -403,93 +342,6 @@ export class ResourceTreeAdapter implements ReactAdapter { return traverse(schema); } - private buildChildNodes( - item: NotebookContentItem, - onFileClick: (item: NotebookContentItem) => void, - createDirectoryContextMenu: boolean, - createFileContextMenu: boolean, - ): LegacyTreeNode[] { - if (!item || !item.children) { - return []; - } else { - return item.children.map((item) => { - const result = - item.type === NotebookContentItemType.Directory - ? this.buildNotebookDirectoryNode(item, onFileClick, createDirectoryContextMenu, createFileContextMenu) - : this.buildNotebookFileNode(item, onFileClick, createFileContextMenu); - result.timestamp = item.timestamp; - return result; - }); - } - } - - private buildNotebookFileNode( - item: NotebookContentItem, - onFileClick: (item: NotebookContentItem) => void, - createFileContextMenu: boolean, - ): LegacyTreeNode { - return { - label: item.name, - iconSrc: NotebookUtil.isNotebookFile(item.path) ? NotebookIcon : FileIcon, - className: "notebookHeader", - onClick: () => onFileClick(item), - isSelected: () => { - const activeTab = useTabs.getState().activeTab; - return ( - activeTab && - activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 && - /* TODO Redesign Tab interface so that resource tree doesn't need to know about NotebookV2Tab. - NotebookV2Tab could be dynamically imported, but not worth it to just get this type right. - */ - (activeTab as any).notebookPath() === item.path - ); - }, - contextMenu: createFileContextMenu && this.createFileContextMenu(), - data: item, - }; - } - - private createFileContextMenu(): TreeNodeMenuItem[] { - return []; - } - - private createDirectoryContextMenu(): TreeNodeMenuItem[] { - return []; - } - - private buildNotebookDirectoryNode( - item: NotebookContentItem, - onFileClick: (item: NotebookContentItem) => void, - createDirectoryContextMenu: boolean, - createFileContextMenu: boolean, - ): LegacyTreeNode { - return { - label: item.name, - iconSrc: undefined, - className: "notebookHeader", - isAlphaSorted: true, - isLeavesParentsSeparate: true, - onClick: undefined, - isSelected: () => { - const activeTab = useTabs.getState().activeTab; - return ( - activeTab && - activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 && - /* TODO Redesign Tab interface so that resource tree doesn't need to know about NotebookV2Tab. - NotebookV2Tab could be dynamically imported, but not worth it to just get this type right. - */ - (activeTab as any).notebookPath() === item.path - ); - }, - contextMenu: - createDirectoryContextMenu && item.path !== ResourceTreeAdapter.PseudoDirPath - ? this.createDirectoryContextMenu() - : undefined, - data: item, - children: this.buildChildNodes(item, onFileClick, createDirectoryContextMenu, createFileContextMenu), - }; - } - public triggerRender() { window.requestAnimationFrame(() => this.parameters(Date.now())); } diff --git a/src/Explorer/Tree/__snapshots__/treeNodeUtil.test.ts.snap b/src/Explorer/Tree/__snapshots__/treeNodeUtil.test.ts.snap index ead9bdb15..53c49c24f 100644 --- a/src/Explorer/Tree/__snapshots__/treeNodeUtil.test.ts.snap +++ b/src/Explorer/Tree/__snapshots__/treeNodeUtil.test.ts.snap @@ -363,7 +363,7 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Ca ] `; -exports[`createDatabaseTreeNodes generates the correct tree structure for the Mongo API, with Notebooks and Phoenix features, on Emulator 1`] = ` +exports[`createDatabaseTreeNodes generates the correct tree structure for the Mongo API, on Emulator 1`] = ` [ { "children": [ diff --git a/src/Explorer/Tree/treeNodeUtil.test.ts b/src/Explorer/Tree/treeNodeUtil.test.ts index c64f9276e..f30c3e166 100644 --- a/src/Explorer/Tree/treeNodeUtil.test.ts +++ b/src/Explorer/Tree/treeNodeUtil.test.ts @@ -4,7 +4,6 @@ import { CosmosDbArtifactType } from "Contracts/FabricMessagesContract"; import { TreeNode } from "Explorer/Controls/TreeComponent/TreeNodeComponent"; import Explorer from "Explorer/Explorer"; import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter"; -import { useNotebook } from "Explorer/Notebook/useNotebook"; import { DeleteDatabaseConfirmationPanel } from "Explorer/Panes/DeleteDatabaseConfirmationPanel"; import TabsBase from "Explorer/Tabs/TabsBase"; import StoredProcedure from "Explorer/Tree/StoredProcedure"; @@ -363,7 +362,7 @@ describe("createDatabaseTreeNodes", () => { }, } as never, }); - nodes = createDatabaseTreeNodes(explorer, false, useDatabases.getState().databases, refreshActiveTab, ""); + nodes = createDatabaseTreeNodes(explorer, useDatabases.getState().databases, refreshActiveTab, ""); }); it("creates expected tree", () => { @@ -371,11 +370,10 @@ describe("createDatabaseTreeNodes", () => { }); }); - it.each<[string, Platform, boolean, Partial, Partial]>([ + it.each<[string, Platform, Partial, Partial]>([ [ "the SQL API, on Fabric read-only (mirrored)", Platform.Fabric, - false, { capabilities: [], enableMultipleWriteLocations: true }, { fabricContext: { @@ -387,7 +385,6 @@ describe("createDatabaseTreeNodes", () => { [ "the SQL API, on Fabric non read-only (native)", Platform.Fabric, - false, { capabilities: [], enableMultipleWriteLocations: true }, { fabricContext: { @@ -399,7 +396,6 @@ describe("createDatabaseTreeNodes", () => { [ "the SQL API, on Portal", Platform.Portal, - false, { capabilities: [], enableMultipleWriteLocations: true }, { fabricContext: undefined, @@ -408,7 +404,6 @@ describe("createDatabaseTreeNodes", () => { [ "the Cassandra API, serverless, on Hosted", Platform.Hosted, - false, { capabilities: [ { name: CapabilityNames.EnableCassandra, description: "" }, @@ -418,45 +413,34 @@ describe("createDatabaseTreeNodes", () => { { fabricContext: undefined }, ], [ - "the Mongo API, with Notebooks and Phoenix features, on Emulator", + "the Mongo API, on Emulator", Platform.Emulator, - true, { capabilities: [{ name: CapabilityNames.EnableMongo, description: "" }], }, { fabricContext: undefined }, ], - ])( - "generates the correct tree structure for %s", - (_, platform, isNotebookEnabled, dbAccountProperties, userContext) => { - useNotebook.setState({ isPhoenixFeatures: isNotebookEnabled }); - updateConfigContext({ platform }); - updateUserContext({ - ...userContext, - databaseAccount: { - properties: { - enableMultipleWriteLocations: true, - ...dbAccountProperties, - }, - } as unknown as DataModels.DatabaseAccount, - }); - const nodes = createDatabaseTreeNodes( - explorer, - isNotebookEnabled, - useDatabases.getState().databases, - refreshActiveTab, - "", - ); - expect(nodes).toMatchSnapshot(); - }, - ); + ])("generates the correct tree structure for %s", (_, platform, dbAccountProperties, userContext) => { + updateConfigContext({ platform }); + updateUserContext({ + ...userContext, + databaseAccount: { + properties: { + enableMultipleWriteLocations: true, + ...dbAccountProperties, + }, + } as unknown as DataModels.DatabaseAccount, + }); + const nodes = createDatabaseTreeNodes(explorer, useDatabases.getState().databases, refreshActiveTab, ""); + expect(nodes).toMatchSnapshot(); + }); // The above tests focused on the tree structure. The below tests focus on some core behaviors of the nodes. // They are not exhaustive, because exhaustive tests here require a lot of mocking and can become very brittle. // The goal is to cover some key behaviors like loading child nodes, opening tabs/side panels, etc. it("adds new collections to database as they appear", () => { - const nodes = createDatabaseTreeNodes(explorer, false, useDatabases.getState().databases, refreshActiveTab, ""); + const nodes = createDatabaseTreeNodes(explorer, useDatabases.getState().databases, refreshActiveTab, ""); const giganticDbNode = nodes.find((node) => node.label === giganticDb.id()); expect(giganticDbNode).toBeDefined(); expect(giganticDbNode.children.map((node) => node.label)).toStrictEqual(["schemaCollection", "load more"]); @@ -488,7 +472,7 @@ describe("createDatabaseTreeNodes", () => { }, } as unknown as DataModels.DatabaseAccount, }); - nodes = createDatabaseTreeNodes(explorer, false, useDatabases.getState().databases, refreshActiveTab, ""); + nodes = createDatabaseTreeNodes(explorer, useDatabases.getState().databases, refreshActiveTab, ""); standardDbNode = nodes.find((node) => node.label === standardDb.id()); sharedDbNode = nodes.find((node) => node.label === sharedDb.id()); giganticDbNode = nodes.find((node) => node.label === giganticDb.id()); @@ -643,7 +627,7 @@ describe("createDatabaseTreeNodes", () => { setup(); // Rebuild the nodes after changing the user/config context. - nodes = createDatabaseTreeNodes(explorer, false, useDatabases.getState().databases, refreshActiveTab, ""); + nodes = createDatabaseTreeNodes(explorer, useDatabases.getState().databases, refreshActiveTab, ""); standardDbNode = nodes.find((node) => node.label === standardDb.id()); standardCollectionNode = standardDbNode.children.find((node) => node.label === standardCollection.id()); diff --git a/src/Explorer/Tree/treeNodeUtil.tsx b/src/Explorer/Tree/treeNodeUtil.tsx index 224542755..d784080cb 100644 --- a/src/Explorer/Tree/treeNodeUtil.tsx +++ b/src/Explorer/Tree/treeNodeUtil.tsx @@ -137,7 +137,6 @@ export const createResourceTokenTreeNodes = (collection: ViewModels.CollectionBa export const createDatabaseTreeNodes = ( container: Explorer, - isNotebookEnabled: boolean, databases: ViewModels.Database[], refreshActiveTab: (comparator: (tab: TabsBase) => boolean) => void, searchText = "", @@ -184,9 +183,7 @@ export const createDatabaseTreeNodes = ( database .collections() .forEach((collection: ViewModels.Collection) => - databaseNode.children.push( - buildCollectionNode(database, collection, isNotebookEnabled, container, refreshActiveTab), - ), + databaseNode.children.push(buildCollectionNode(database, collection, container, refreshActiveTab)), ); if (database.collectionsContinuationToken) { @@ -254,14 +251,13 @@ export const createDatabaseTreeNodes = ( export const buildCollectionNode = ( database: ViewModels.Database, collection: ViewModels.Collection, - isNotebookEnabled: boolean, container: Explorer, refreshActiveTab: (comparator: (tab: TabsBase) => boolean) => void, ): TreeNode => { let children: TreeNode[]; // Flat Tree for Fabric if (!isFabricMirrored()) { - children = buildCollectionNodeChildren(database, collection, isNotebookEnabled, container, refreshActiveTab); + children = buildCollectionNodeChildren(database, collection, container, refreshActiveTab); } const collectionNode: TreeNode = { @@ -310,7 +306,6 @@ export const buildCollectionNode = ( const buildCollectionNodeChildren = ( database: ViewModels.Database, collection: ViewModels.Collection, - isNotebookEnabled: boolean, container: Explorer, refreshActiveTab: (comparator: (tab: TabsBase) => boolean) => void, ): TreeNode[] => { diff --git a/src/Explorer/useSelectedNode.ts b/src/Explorer/useSelectedNode.ts index 4dd34a91e..cbade8ed9 100644 --- a/src/Explorer/useSelectedNode.ts +++ b/src/Explorer/useSelectedNode.ts @@ -1,5 +1,3 @@ -import { ConnectionStatusType } from "Common/Constants"; -import { useNotebook } from "Explorer/Notebook/useNotebook"; import create, { UseStore } from "zustand"; import * as ViewModels from "../Contracts/ViewModels"; import { useTabs } from "../hooks/useTabs"; @@ -13,7 +11,6 @@ export interface SelectedNodeState { collectionId?: string, subnodeKinds?: ViewModels.CollectionTabKind[], ) => boolean; - isConnectedToContainer: () => boolean; } export const useSelectedNode: UseStore = create((set, get) => ({ @@ -61,7 +58,4 @@ export const useSelectedNode: UseStore = create((set, get) => subnodeKinds.includes(selectedSubnodeKind) ); }, - isConnectedToContainer: (): boolean => { - return useNotebook.getState().connectionInfo?.status === ConnectionStatusType.Connected; - }, })); diff --git a/src/Main.tsx b/src/Main.tsx index 6179711d6..e60a5e863 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -52,8 +52,6 @@ import { ErrorBoundary } from "./Explorer/ErrorBoundary"; import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less"; import "./Explorer/Menus/CommandBar/CommandBarComponent.less"; import { CommandBar } from "./Explorer/Menus/CommandBar/CommandBarComponentAdapter"; -import "./Explorer/Menus/CommandBar/ConnectionStatusComponent.less"; -import "./Explorer/Menus/CommandBar/MemoryTrackerComponent.less"; import "./Explorer/Menus/NotificationConsole/NotificationConsole.less"; import { NotificationConsole } from "./Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; import "./Explorer/Panes/PanelComponent.less"; diff --git a/src/Phoenix/PhoenixClient.ts b/src/Phoenix/PhoenixClient.ts deleted file mode 100644 index f1d722e3b..000000000 --- a/src/Phoenix/PhoenixClient.ts +++ /dev/null @@ -1,256 +0,0 @@ -import { configContext } from "ConfigContext"; -import { useDialog } from "Explorer/Controls/Dialog"; -import { Action } from "Shared/Telemetry/TelemetryConstants"; -import { userContext } from "UserContext"; -import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointUtils"; -import promiseRetry, { AbortError, Options } from "p-retry"; -import { - Areas, - ConnectionStatusType, - ContainerStatusType, - HttpHeaders, - HttpStatusCodes, - Notebook, -} from "../Common/Constants"; -import { getErrorMessage } from "../Common/ErrorHandlingUtils"; -import * as Logger from "../Common/Logger"; -import { - ContainerConnectionInfo, - ContainerInfo, - IContainerData, - IMaxAllocationTimeExceeded, - IPhoenixConnectionInfoResult, - IPhoenixError, - IPhoenixServiceInfo, - IProvisionData, - IResponse, - PhoenixErrorType, -} from "../Contracts/DataModels"; -import { useNotebook } from "../Explorer/Notebook/useNotebook"; -import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor"; -import { getAuthorizationHeader } from "../Utils/AuthorizationUtils"; - -export class PhoenixClient { - private armResourceId: string; - private containerHealthHandler: NodeJS.Timeout; - private retryOptions: Options = { - retries: Notebook.retryAttempts, - maxTimeout: Notebook.retryAttemptDelayMs, - minTimeout: Notebook.retryAttemptDelayMs, - }; - private abortController: AbortController; - private abortSignal: AbortSignal; - - constructor(armResourceId: string) { - this.armResourceId = armResourceId; - } - - public async allocateContainer(provisionData: IProvisionData): Promise> { - this.initializeCancelEventListener(); - - return promiseRetry(() => this.executeContainerAssignmentOperation(provisionData, "allocate"), { - retries: 4, - maxTimeout: 20000, - minTimeout: 20000, - signal: this.abortSignal, - }); - } - - public async resetContainer(provisionData: IProvisionData): Promise> { - return this.executeContainerAssignmentOperation(provisionData, "reset"); - } - - private async executeContainerAssignmentOperation( - provisionData: IProvisionData, - operation: string, - ): Promise> { - let response; - try { - response = await fetch(`${this.getPhoenixControlPlanePathPrefix()}/containerconnections/multicontainer`, { - method: operation === "allocate" ? "POST" : "PATCH", - headers: PhoenixClient.getHeaders(), - body: JSON.stringify(provisionData), - }); - const responseJson = await response?.json(); - if (response.ok) { - const phoenixConnectionInfoResult = responseJson as IPhoenixConnectionInfoResult[]; - if ( - !phoenixConnectionInfoResult || - phoenixConnectionInfoResult.length === 0 || - !phoenixConnectionInfoResult[0] - ) { - throw new Error("Received invalid phoenix connection response."); - } - return { - status: response.status, - data: phoenixConnectionInfoResult[0].phoenixServiceInfo, - }; - } - const phoenixError = responseJson as IPhoenixError; - if (response.status === HttpStatusCodes.Forbidden) { - if (phoenixError.message === "Sequence contains no elements") { - throw Error("Phoenix container allocation failed, please try again later."); - } - throw new AbortError(this.ConvertToForbiddenErrorString(phoenixError)); - } - throw new AbortError(phoenixError.message); - } catch (error) { - error.status = response?.status; - throw error; - } - } - - public async initiateContainerHeartBeat(shouldUseNotebookStates: boolean, containerData: IContainerData) { - if (this.containerHealthHandler) { - clearTimeout(this.containerHealthHandler); - } - await this.getContainerHealth(shouldUseNotebookStates, Notebook.containerStatusHeartbeatDelayMs, containerData); - } - - private scheduleContainerHeartbeat( - shouldUseNotebookStates: boolean, - delayMs: number, - containerData: IContainerData, - ): void { - this.containerHealthHandler = setTimeout(async () => { - await this.getContainerHealth(shouldUseNotebookStates, delayMs, containerData); - }, delayMs); - } - - private async getContainerStatusAsync( - shouldUseNotebookStates: boolean, - containerData: IContainerData, - ): Promise { - try { - const runContainerStatusAsync = async () => { - const response = await window.fetch( - `${this.getPhoenixControlPlanePathPrefix()}/${containerData.forwardingId}`, - { - method: "GET", - headers: PhoenixClient.getHeaders(), - }, - ); - if (response.status === HttpStatusCodes.OK) { - const containerStatus = await response.json(); - return { - durationLeftInMinutes: containerStatus?.durationLeftInMinutes, - phoenixServerInfo: containerStatus?.phoenixServerInfo, - status: ContainerStatusType.Active, - }; - } else if (response.status === HttpStatusCodes.NotFound) { - const error = "Disconnected from compute workspace"; - Logger.logError(error, ""); - const connectionStatus: ContainerConnectionInfo = { - status: ConnectionStatusType.Reconnect, - }; - TelemetryProcessor.traceMark(Action.PhoenixHeartBeat, { - dataExplorerArea: Areas.Notebook, - message: getErrorMessage(error), - }); - shouldUseNotebookStates ? useNotebook.getState().resetContainerConnection(connectionStatus) : undefined; - shouldUseNotebookStates && useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed); - shouldUseNotebookStates && - useDialog - .getState() - .showOkModalDialog( - "Disconnected", - "Disconnected from temporary workspace. Please click on connect button to connect to temporary workspace.", - ); - throw new AbortError(response.statusText); - } else if (response?.status === HttpStatusCodes.Forbidden) { - const validationMessage = this.ConvertToForbiddenErrorString(await response.json()); - if (validationMessage) { - useDialog.getState().showOkModalDialog("Connection Failed", `${validationMessage}`); - } - throw new AbortError(response.statusText); - } - throw new Error(response.statusText); - }; - return await promiseRetry(runContainerStatusAsync, this.retryOptions); - } catch (error) { - TelemetryProcessor.traceFailure(Action.PhoenixHeartBeat, { - dataExplorerArea: Areas.Notebook, - }); - Logger.logError(getErrorMessage(error), ""); - const connectionStatus: ContainerConnectionInfo = { - status: ConnectionStatusType.Failed, - }; - shouldUseNotebookStates ? useNotebook.getState().resetContainerConnection(connectionStatus) : undefined; - shouldUseNotebookStates && useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed); - return { - durationLeftInMinutes: undefined, - phoenixServerInfo: undefined, - status: ContainerStatusType.Disconnected, - }; - } - } - - private async getContainerHealth(shouldUseNotebookStates: boolean, delayMs: number, containerData: IContainerData) { - const containerInfo = await this.getContainerStatusAsync(shouldUseNotebookStates, containerData); - shouldUseNotebookStates ? useNotebook.getState().setContainerStatus(containerInfo) : undefined; - - const containerStatus = shouldUseNotebookStates ? useNotebook.getState().containerStatus?.status : undefined; - if (containerStatus === ContainerStatusType.Active) { - this.scheduleContainerHeartbeat(shouldUseNotebookStates, delayMs, containerData); - } - } - - private getPhoenixControlPlanePathPrefix(): string { - if (!this.armResourceId) { - throw new Error("The Phoenix client was not initialized properly: missing ARM resource id"); - } - - const toolsEndpoint = - userContext.features.phoenixEndpoint ?? userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT; - - if (!validateEndpoint(toolsEndpoint, allowedJunoOrigins)) { - const error = `${toolsEndpoint} not allowed as tools endpoint`; - console.error(error); - throw new Error(error); - } - - return `${toolsEndpoint}/api/controlplane/toolscontainer/cosmosaccounts${this.armResourceId}`; - } - - private static getHeaders(): HeadersInit { - const authorizationHeader = getAuthorizationHeader(); - return { - [authorizationHeader.header]: authorizationHeader.token, - [HttpHeaders.contentType]: "application/json", - }; - } - - private initializeCancelEventListener(): void { - this.abortController = new AbortController(); - this.abortSignal = this.abortController.signal; - - document.addEventListener("keydown", (event: KeyboardEvent) => { - if (event.ctrlKey && (event.key === "c" || event.key === "z")) { - this.abortController.abort(new AbortError("Request canceled")); - } - }); - } - - public ConvertToForbiddenErrorString(jsonData: IPhoenixError): string { - const errInfo = jsonData; - switch (errInfo?.type) { - case PhoenixErrorType.MaxAllocationTimeExceeded: { - const maxAllocationTimeExceeded = errInfo as IMaxAllocationTimeExceeded; - const allocateAfterTimestamp = new Date(maxAllocationTimeExceeded?.earliestAllocationTimestamp); - allocateAfterTimestamp.setDate(allocateAfterTimestamp.getDate() + 1); - return `${errInfo.message}` + ". Please try again after " + `${allocateAfterTimestamp.toLocaleString()}`; - } - case PhoenixErrorType.MaxDbAccountsPerUserExceeded: - case PhoenixErrorType.MaxUsersPerDbAccountExceeded: - case PhoenixErrorType.AllocationValidationResult: - case PhoenixErrorType.RegionNotServicable: - case PhoenixErrorType.UserMissingPermissionsError: - case PhoenixErrorType.SubscriptionNotAllowed: { - return `${errInfo.message}`; - } - default: { - return undefined; - } - } - } -} diff --git a/src/Utils/NotebookConfigurationUtils.ts b/src/Utils/NotebookConfigurationUtils.ts deleted file mode 100644 index 31bfccfdd..000000000 --- a/src/Utils/NotebookConfigurationUtils.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { getErrorMessage } from "../Common/ErrorHandlingUtils"; -import * as Logger from "../Common/Logger"; -import * as DataModels from "../Contracts/DataModels"; - -interface KernelConnectionMetadata { - name: string; - configurationEndpoints: DataModels.NotebookConfigurationEndpoints; - notebookConnectionInfo: DataModels.NotebookWorkspaceConnectionInfo; -} - -export const _configureServiceEndpoints = async (kernelMetadata: KernelConnectionMetadata): Promise => { - if (!kernelMetadata) { - // should never get into this state - Logger.logWarning("kernel metadata is null or undefined", "NotebookConfigurationUtils/configureServiceEndpoints"); - return; - } - - const notebookConnectionInfo = kernelMetadata.notebookConnectionInfo; - const configurationEndpoints = kernelMetadata.configurationEndpoints; - if (notebookConnectionInfo && configurationEndpoints) { - try { - const headers: HeadersInit = { "Content-Type": "application/json" }; - if (notebookConnectionInfo.authToken) { - headers["Authorization"] = `token ${notebookConnectionInfo.authToken}`; - } - const response = await fetch(`${notebookConnectionInfo.notebookServerEndpoint}/api/configureEndpoints`, { - method: "POST", - headers, - body: JSON.stringify(configurationEndpoints), - }); - if (!response.ok) { - const responseMessage = await response.json(); - Logger.logError( - getErrorMessage(responseMessage), - "NotebookConfigurationUtils/configureServiceEndpoints", - response.status, - ); - } - } catch (error) { - Logger.logError(getErrorMessage(error), "NotebookConfigurationUtils/configureServiceEndpoints"); - } - } -}; - -export const configureServiceEndpoints = async ( - notebookPath: string, - notebookConnectionInfo: DataModels.NotebookWorkspaceConnectionInfo, - kernelName: string, - clusterConnectionInfo: DataModels.SparkClusterConnectionInfo, -): Promise => { - if (!notebookPath || !notebookConnectionInfo || !kernelName) { - Logger.logError( - "Invalid or missing notebook connection info/path", - "NotebookConfigurationUtils/configureServiceEndpoints", - ); - return Promise.reject("Invalid or missing notebook connection info"); - } - - if (!clusterConnectionInfo || !clusterConnectionInfo.endpoints || clusterConnectionInfo.endpoints.length === 0) { - Logger.logError( - "Invalid or missing cluster connection info/endpoints", - "NotebookConfigurationUtils/configureServiceEndpoints", - ); - return Promise.reject("Invalid or missing cluster connection info"); - } - - const notebookEndpointInfo: DataModels.NotebookConfigurationEndpointInfo[] = clusterConnectionInfo.endpoints.map( - (clusterEndpoint) => ({ - type: clusterEndpoint.kind.toLowerCase(), - endpoint: clusterEndpoint && clusterEndpoint.endpoint, - username: clusterConnectionInfo.userName, - password: clusterConnectionInfo.password, - token: "", // TODO. This was arcadiaToken() when our synapse/spark integration comes back - }), - ); - const configurationEndpoints: DataModels.NotebookConfigurationEndpoints = { - path: notebookPath, - endpoints: notebookEndpointInfo, - }; - const kernelMetadata: KernelConnectionMetadata = { - configurationEndpoints, - notebookConnectionInfo, - name: kernelName, - }; - - return await _configureServiceEndpoints(kernelMetadata); -}; diff --git a/src/hooks/useKnockoutExplorer.ts b/src/hooks/useKnockoutExplorer.ts index 41bec9797..8b8c63a3a 100644 --- a/src/hooks/useKnockoutExplorer.ts +++ b/src/hooks/useKnockoutExplorer.ts @@ -83,12 +83,6 @@ export function useKnockoutExplorer(platform: Platform): Explorer { useEffect(() => { const effect = async () => { if (platform) { - //Updating phoenix feature flags for MPAC based of config context - if (configContext.isPhoenixEnabled === true) { - userContext.features.phoenixNotebooks = true; - userContext.features.phoenixFeatures = true; - } - let explorer: Explorer; try { if (platform === Platform.Hosted) { @@ -1010,15 +1004,6 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) { if (inputs.flights.indexOf(Flights.PKPartitionKeyTest) !== -1) { userContext.features.partitionKeyDefault2 = true; } - if (inputs.flights.indexOf(Flights.PhoenixNotebooks) !== -1) { - userContext.features.phoenixNotebooks = true; - } - if (inputs.flights.indexOf(Flights.PhoenixFeatures) !== -1) { - userContext.features.phoenixFeatures = true; - } - if (inputs.flights.indexOf(Flights.NotebooksDownBanner) !== -1) { - userContext.features.notebooksDownBanner = true; - } } // Handle initial theme from portal diff --git a/src/hooks/useNotebookSnapshotStore.ts b/src/hooks/useNotebookSnapshotStore.ts deleted file mode 100644 index 841f6b5d1..000000000 --- a/src/hooks/useNotebookSnapshotStore.ts +++ /dev/null @@ -1,15 +0,0 @@ -import create, { UseStore } from "zustand"; - -export interface NotebookSnapshotHooks { - snapshot?: string; - error?: string; - setSnapshot: (imageSrc: string) => void; - setError: (error: string) => void; -} - -export const useNotebookSnapshotStore: UseStore = create((set) => ({ - snapshot: undefined, - error: undefined, - setSnapshot: (imageSrc: string) => set((state) => ({ ...state, snapshot: imageSrc })), - setError: (error: string) => set((state) => ({ ...state, error })), -})); diff --git a/src/hooks/useTabs.ts b/src/hooks/useTabs.ts index f276e1498..4444f5c8c 100644 --- a/src/hooks/useTabs.ts +++ b/src/hooks/useTabs.ts @@ -9,7 +9,6 @@ import { } from "Shared/AppStatePersistenceUtility"; import create, { UseStore } from "zustand"; import * as ViewModels from "../Contracts/ViewModels"; -import { CollectionTabKind } from "../Contracts/ViewModels"; import TabsBase from "../Explorer/Tabs/TabsBase"; export interface TabsState { @@ -27,7 +26,6 @@ export interface TabsState { refreshActiveTab: (comparator: (tab: TabsBase) => boolean) => void; closeTabsByComparator: (comparator: (tab: TabsBase) => boolean) => void; closeTab: (tab: TabsBase) => void; - closeAllNotebookTabs: (hardClose: boolean) => void; openAndActivateReactTab: (tabKind: ReactTabKind) => void; closeReactTab: (tabKind: ReactTabKind) => void; setIsTabExecuting: (state: boolean) => void; @@ -132,33 +130,6 @@ export const useTabs: UseStore = create((set, get) => ({ get().persistTabsState(); }, - closeAllNotebookTabs: (): void => { - const isNotebook = (tabKind: CollectionTabKind): boolean => { - if ( - tabKind === CollectionTabKind.Notebook || - tabKind === CollectionTabKind.NotebookV2 || - tabKind === CollectionTabKind.SchemaAnalyzer || - tabKind === CollectionTabKind.Terminal - ) { - return true; - } - return false; - }; - - const tabList = get().openedTabs; - if (tabList && tabList.length > 0) { - tabList.forEach((tab: TabsBase) => { - const tabKind: CollectionTabKind = tab.tabKind; - if (tabKind && isNotebook(tabKind)) { - tab.onCloseTabButtonClick(); - } - }); - - if (get().openedTabs.length === 0 && !isFabricMirrored()) { - set({ activeTab: undefined, activeReactTab: undefined }); - } - } - }, openAndActivateReactTab: (tabKind: ReactTabKind) => { if (get().openedReactTabs.indexOf(tabKind) === -1) { set((state) => ({ diff --git a/tsconfig.strict.json b/tsconfig.strict.json index 947b073e5..1a2ae5ece 100644 --- a/tsconfig.strict.json +++ b/tsconfig.strict.json @@ -44,7 +44,6 @@ "./src/Explorer/Graph/GraphExplorerComponent/GraphData.ts", "./src/Explorer/LazyMonaco.ts", "./src/Explorer/Menus/NotificationConsole/ConsoleData.tsx", - "./src/Explorer/Notebook/NotebookContentItem.ts", "./src/Explorer/OpenFullScreen.test.tsx", "./src/Explorer/OpenFullScreen.tsx", "./src/Explorer/Panes/PanelContainerComponent.test.tsx", @@ -107,7 +106,6 @@ "./src/hooks/useConfig.ts", "./src/hooks/useDirectories.tsx", "./src/hooks/useGraphPhoto.tsx", - "./src/hooks/useNotebookSnapshotStore.ts", "./src/hooks/useNotificationConsole.ts", "./src/hooks/useObservable.ts", "./src/hooks/useSidePanel.ts", @@ -137,4 +135,4 @@ "src/Shared/Telemetry/**/*", "src/Utils/arm/**/*" ] -} +} \ No newline at end of file