mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-06-30 02:28:44 +01:00
Merge branch 'master' into quantized_vector_playwright
This commit is contained in:
+1
-1
@@ -40,7 +40,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
//CTODO uncomment when console debugging is reverted: "no-console": ["error", { allow: ["error", "warn", "dir"] }],
|
"no-console": ["error", { allow: ["error", "warn", "dir"] }],
|
||||||
curly: "error",
|
curly: "error",
|
||||||
"@typescript-eslint/switch-exhaustiveness-check": "error",
|
"@typescript-eslint/switch-exhaustiveness-check": "error",
|
||||||
"@typescript-eslint/no-unused-vars": "error",
|
"@typescript-eslint/no-unused-vars": "error",
|
||||||
|
|||||||
+73
-52
@@ -299,13 +299,26 @@ jobs:
|
|||||||
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --list
|
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --list
|
||||||
- name: Run test shard ${{ matrix['shardIndex'] }} of ${{ matrix['shardTotal']}}
|
- name: Run test shard ${{ matrix['shardIndex'] }} of ${{ matrix['shardTotal']}}
|
||||||
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers=3
|
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers=3
|
||||||
- name: Upload blob report to GitHub Actions Artifacts
|
- name: "Re-auth for upload (refresh OIDC token)"
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: Azure/login@v2
|
||||||
with:
|
with:
|
||||||
name: blob-report-${{ matrix.shardIndex }}
|
client-id: ${{ secrets.E2E_TESTS_CLIENT_ID }}
|
||||||
path: blob-report
|
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||||
retention-days: 1
|
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||||
|
- name: Upload shard blob-report to Azure Storage
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
env:
|
||||||
|
KEY: ${{ github.run_id }}-${{ github.run_attempt }}
|
||||||
|
SHARD: ${{ matrix.shardIndex }}
|
||||||
|
run: |
|
||||||
|
az storage blob upload-batch \
|
||||||
|
--account-name ${{ secrets.PREVIEW_STORAGE_ACCOUNT_NAME }} \
|
||||||
|
--auth-mode login \
|
||||||
|
-d playwright-reports \
|
||||||
|
-s blob-report \
|
||||||
|
--destination-path "${KEY}/shards/shard-${SHARD}" \
|
||||||
|
--overwrite true
|
||||||
|
|
||||||
merge-playwright-reports:
|
merge-playwright-reports:
|
||||||
name: "Merge Playwright Reports"
|
name: "Merge Playwright Reports"
|
||||||
@@ -317,6 +330,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
@@ -325,29 +339,67 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: Download blob reports from GitHub Actions Artifacts
|
- name: "Az CLI login"
|
||||||
uses: actions/download-artifact@v4
|
uses: Azure/login@v2
|
||||||
with:
|
with:
|
||||||
path: all-blob-reports
|
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||||
pattern: blob-report-*
|
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||||
merge-multiple: true
|
subscription-id: ${{ secrets.PREVIEW_SUBSCRIPTION_ID }}
|
||||||
|
|
||||||
|
- name: Download all shard reports from Azure Storage
|
||||||
|
env:
|
||||||
|
KEY: ${{ github.run_id }}-${{ github.run_attempt }}
|
||||||
|
run: |
|
||||||
|
mkdir -p all-blob-reports
|
||||||
|
az storage blob download-batch \
|
||||||
|
--account-name ${{ secrets.PREVIEW_STORAGE_ACCOUNT_NAME }} \
|
||||||
|
--auth-mode login \
|
||||||
|
-s playwright-reports \
|
||||||
|
--pattern "${KEY}/shards/*" \
|
||||||
|
-d ./all-blob-reports
|
||||||
|
find ./all-blob-reports -type f -name "*.zip" -exec mv -t ./all-blob-reports {} +
|
||||||
|
find ./all-blob-reports -type d -empty -delete
|
||||||
|
ls -la ./all-blob-reports
|
||||||
|
|
||||||
- name: Merge into HTML Report
|
- name: Merge into HTML Report
|
||||||
run: npx playwright merge-reports --reporter html ./all-blob-reports
|
run: npx playwright merge-reports --reporter html ./all-blob-reports
|
||||||
|
|
||||||
- name: Upload HTML report
|
- name: Bundle merged report into a single zip
|
||||||
uses: actions/upload-artifact@v4
|
run: |
|
||||||
with:
|
cd playwright-report
|
||||||
name: html-report--attempt-${{ github.run_attempt }}
|
zip -r ../report.zip .
|
||||||
path: playwright-report
|
cd ..
|
||||||
retention-days: 14
|
|
||||||
|
- name: Upload report.zip to Azure Storage
|
||||||
|
env:
|
||||||
|
KEY: ${{ github.run_id }}-${{ github.run_attempt }}
|
||||||
|
run: |
|
||||||
|
az storage blob upload \
|
||||||
|
--account-name ${{ secrets.PREVIEW_STORAGE_ACCOUNT_NAME }} \
|
||||||
|
--auth-mode login \
|
||||||
|
-c playwright-reports \
|
||||||
|
-n "${KEY}/report.zip" \
|
||||||
|
-f report.zip \
|
||||||
|
--overwrite
|
||||||
|
|
||||||
|
- name: Clean up shard intermediates from Azure Storage
|
||||||
|
if: ${{ always() }}
|
||||||
|
env:
|
||||||
|
KEY: ${{ github.run_id }}-${{ github.run_attempt }}
|
||||||
|
run: |
|
||||||
|
az storage blob delete-batch \
|
||||||
|
--account-name ${{ secrets.PREVIEW_STORAGE_ACCOUNT_NAME }} \
|
||||||
|
--auth-mode login \
|
||||||
|
-s playwright-reports \
|
||||||
|
--pattern "${KEY}/shards/*"
|
||||||
|
|
||||||
- name: Comment Playwright results on PR
|
- name: Comment Playwright results on PR
|
||||||
if: ${{ !cancelled() && github.event_name == 'pull_request' }}
|
if: ${{ !cancelled() && github.event_name == 'pull_request' }}
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
PR: ${{ github.event.pull_request.number }}
|
PR: ${{ github.event.pull_request.number }}
|
||||||
REPORT_URL: https://dataexplorerpreview.z5.web.core.windows.net/playwright-reports/${{ github.run_id }}-${{ github.run_attempt }}/index.html
|
SA_ID: /subscriptions/${{ secrets.PREVIEW_SUBSCRIPTION_ID }}/resourceGroups/dataexplorer-preview/providers/Microsoft.Storage/storageAccounts/dataexplorerpreview
|
||||||
|
KEY: ${{ github.run_id }}-${{ github.run_attempt }}
|
||||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||||
run: |
|
run: |
|
||||||
PLAYWRIGHT_JSON_OUTPUT_NAME=results.json npx playwright merge-reports --reporter json ./all-blob-reports
|
PLAYWRIGHT_JSON_OUTPUT_NAME=results.json npx playwright merge-reports --reporter json ./all-blob-reports
|
||||||
@@ -355,6 +407,8 @@ jobs:
|
|||||||
BROKEN=$(gh api "repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}/jobs" \
|
BROKEN=$(gh api "repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}/jobs" \
|
||||||
--jq '[.jobs[] | select(.name | startswith("Run Playwright Tests")) | select(.conclusion == "failure")] | length')
|
--jq '[.jobs[] | select(.name | startswith("Run Playwright Tests")) | select(.conclusion == "failure")] | length')
|
||||||
if [ "$FAILED" -gt 0 ] || [ "$BROKEN" -gt 0 ]; then ICON="❌ failed"; else ICON="✅ passed"; fi
|
if [ "$FAILED" -gt 0 ] || [ "$BROKEN" -gt 0 ]; then ICON="❌ failed"; else ICON="✅ passed"; fi
|
||||||
|
SA_ENC=$(printf '%s' "$SA_ID" | jq -sRr @uri)
|
||||||
|
CONTAINER_URL="https://portal.azure.com/#view/Microsoft_Azure_Storage/ContainerMenuBlade/~/overview/storageAccountId/${SA_ENC}/path/playwright-reports"
|
||||||
NOTE=""
|
NOTE=""
|
||||||
[ "$BROKEN" -gt 0 ] && NOTE="
|
[ "$BROKEN" -gt 0 ] && NOTE="
|
||||||
|
|
||||||
@@ -365,38 +419,5 @@ jobs:
|
|||||||
| :---: | :---: | :---: | :---: |
|
| :---: | :---: | :---: | :---: |
|
||||||
| $PASSED | $FAILED | $FLAKY | ${DURATION}s |
|
| $PASSED | $FAILED | $FLAKY | ${DURATION}s |
|
||||||
|
|
||||||
📊 [Open full report]($REPORT_URL) · [Workflow run]($RUN_URL)$NOTE"
|
📁 **Report:** \`${KEY}/report.zip\`
|
||||||
|
[Open container]($CONTAINER_URL) (Azure sign-in required) → click into \`${KEY}\` folder → click \`report.zip\` → **Download** → unzip → open \`index.html\` · [Workflow run]($RUN_URL)$NOTE"
|
||||||
publish-playwright-report:
|
|
||||||
name: "Publish Playwright Report to Blob"
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
needs: [merge-playwright-reports]
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- name: Download HTML report artifact
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: html-report--attempt-${{ github.run_attempt }}
|
|
||||||
path: playwright-report
|
|
||||||
|
|
||||||
- name: "Az CLI login"
|
|
||||||
uses: Azure/login@v2
|
|
||||||
with:
|
|
||||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
|
||||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
|
||||||
subscription-id: ${{ secrets.PREVIEW_SUBSCRIPTION_ID }}
|
|
||||||
|
|
||||||
- name: Upload Playwright report to blob storage
|
|
||||||
env:
|
|
||||||
KEY: ${{ github.run_id }}-${{ github.run_attempt }}
|
|
||||||
BASE: https://dataexplorerpreview.z5.web.core.windows.net
|
|
||||||
run: |
|
|
||||||
az storage blob upload-batch -d '$web' -s playwright-report \
|
|
||||||
--destination-path "playwright-reports/${KEY}" \
|
|
||||||
--account-name ${{ secrets.PREVIEW_STORAGE_ACCOUNT_NAME }} \
|
|
||||||
--auth-mode login --overwrite true
|
|
||||||
echo "📊 [Open Playwright report](${BASE}/playwright-reports/${KEY}/index.html)" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|||||||
@@ -0,0 +1,214 @@
|
|||||||
|
# Implementation Plan: Remove Phoenix & Notebooks
|
||||||
|
|
||||||
|
## Problem statement
|
||||||
|
|
||||||
|
Cosmos Explorer contains a large, deeply-integrated **notebooks** feature backed by
|
||||||
|
the **Phoenix** compute-container service and the **Juno** service, plus a **GitHub**
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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).
|
||||||
|
- **Schema Analyzer** (`src/Explorer/Notebook/SchemaAnalyzer`): Remove.
|
||||||
|
- **Phasing**: Every phase must leave the app in a **buildable, shippable** state
|
||||||
|
(build + lint + strict compile + unit tests green; shells still work).
|
||||||
|
- **Localization**: Remove notebook/GitHub strings from **all** resource files —
|
||||||
|
`src/Localization/en/Resources.json` **and** every non-English locale
|
||||||
|
(`src/Localization/<locale>/Resources.json`). (This deletion is an exception to the
|
||||||
|
usual convention of editing only the English file.)
|
||||||
|
|
||||||
|
## Prior art / related commits
|
||||||
|
|
||||||
|
This is a continuation of an in-progress removal effort. Reference commits:
|
||||||
|
|
||||||
|
- `7295d63a` — Remove gallery.html and all associated gallery functionality (#2474)
|
||||||
|
- `a36467f4` — Remove Phoenix `getDbAccountAllowedStatus`; `isPhoenixNotebooks`/
|
||||||
|
`isPhoenixFeatures` now always `false` (#2472)
|
||||||
|
- `31385950` — removed NotebookViewer file (#2281)
|
||||||
|
|
||||||
|
> **Note:** An unmerged branch `users/jawelton/remove-notebooks-terminal-052126`
|
||||||
|
> already contains related work (`5989c77c` "Remove terminal.html webpack entry point
|
||||||
|
> and notebooks terminal code", `c7f9d7e3` "Switch VCore Mongo quickstart to use
|
||||||
|
> CloudShell terminal"). These are **not** in `master`. Reconcile with that branch
|
||||||
|
> before/while starting Phase 1 to avoid duplicate or conflicting work.
|
||||||
|
|
||||||
|
## Current-state survey (what exists today)
|
||||||
|
|
||||||
|
**Core directories / files**
|
||||||
|
- `src/Phoenix/PhoenixClient.ts` — container allocation, heartbeat, status polling.
|
||||||
|
- `src/Juno/JunoClient.ts` (+ test) — pinned-repo / notebook metadata service client.
|
||||||
|
- `src/Explorer/Notebook/` — the bulk of the feature:
|
||||||
|
- `useNotebook.ts` (Zustand store), `NotebookManager.tsx`, `NotebookContentClient.ts`,
|
||||||
|
`NotebookClientV2.ts`, `NotebookContainerClient.ts`, `NotebookContentItem.ts`,
|
||||||
|
`NotebookUtil.ts`, `NTeractUtil.ts`, `FileSystemUtil.ts`
|
||||||
|
- `NotebookComponent/` (nteract redux store, epics, reducers, content providers)
|
||||||
|
- `NotebookRenderer/` (nteract cell rendering, decorators, outputs)
|
||||||
|
- `SchemaAnalyzer/`, `SecurityWarningBar/`
|
||||||
|
- `src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx` (+ less/test)
|
||||||
|
- `src/Explorer/Controls/NotebookViewer/` — read-only viewer + metadata.
|
||||||
|
- `src/Explorer/Tabs/NotebookV2Tab.ts`, `NotebookTabBase.ts`, `SchemaAnalyzerTab.ts`
|
||||||
|
- `src/Explorer/Panes/CopyNotebookPane/`
|
||||||
|
- `src/Explorer/Tabs/ShellAdapters/NotebookTerminalComponentAdapter.tsx`
|
||||||
|
- `src/CellOutputViewer/` — webpack entry `cellOutputViewer`.
|
||||||
|
- `src/Utils/NotebookConfigurationUtils.ts`, `src/hooks/useNotebookSnapshotStore.ts`
|
||||||
|
- `src/Utils/arm/generatedClients/cosmosNotebooks/` — generated ARM client.
|
||||||
|
|
||||||
|
**GitHub integration (notebook-only)**
|
||||||
|
- `src/GitHub/` (`GitHubClient.ts`, `GitHubContentProvider.ts`, `GitHubOAuthService.ts`,
|
||||||
|
`GitHubConnector.ts`), `src/Utils/GitHubUtils.ts`
|
||||||
|
- `src/Explorer/Controls/GitHub/` (AuthorizeAccess, AddRepo, GitHubRepos components)
|
||||||
|
- `src/Explorer/Panes/GitHubReposPanel/`
|
||||||
|
- webpack entry `connectToGitHub` + `src/connectToGitHub.html`
|
||||||
|
|
||||||
|
**Integration / glue points (edited, not deleted)**
|
||||||
|
- `src/Explorer/Explorer.tsx` — `phoenixClient`, `notebookManager`, `gitHubOAuthService`,
|
||||||
|
`initNotebooks`, `initiateAndRefreshNotebookList`, `allocateContainer`,
|
||||||
|
`openNotebook*`, `openNotebookTerminal`, `createNotebookContentItemFile`, etc.
|
||||||
|
- `src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx` (+ adapter, test)
|
||||||
|
— New Notebook / Open Terminal / shell buttons branching on `isShellEnabled`.
|
||||||
|
- `src/Explorer/Tree/treeNodeUtil.tsx` / `ResourceTreeAdapter.tsx` / `ResourceTree.tsx`
|
||||||
|
— "My Notebooks" / "GitHub" tree nodes; `isNotebookEnabled` plumbing.
|
||||||
|
- `src/Explorer/SplashScreen/SplashScreen.tsx` — notebook cards + `openNotebookTerminal`
|
||||||
|
for Postgres/VCoreMongo shells.
|
||||||
|
- `src/Explorer/Tabs/TerminalTab.tsx` — chooses CloudShell vs notebook-server adapter.
|
||||||
|
- `src/Explorer/OpenActions/OpenActions.tsx`, `src/Explorer/ContextMenuButtonFactory.tsx`,
|
||||||
|
`src/Explorer/Tree/Collection.ts`, `src/Explorer/useSelectedNode.ts`,
|
||||||
|
`src/Explorer/MostRecentActivity/MostRecentActivity.ts`
|
||||||
|
- `src/hooks/useKnockoutExplorer.ts`, `src/hooks/useTabs.ts`
|
||||||
|
- `src/ConfigContext.ts`, `src/Common/Constants.ts`, `src/Contracts/DataModels.ts`,
|
||||||
|
`src/Contracts/ViewModels.ts`, `src/Contracts/ActionContracts.ts`,
|
||||||
|
`src/Platform/Hosted/extractFeatures.ts` (+ test)
|
||||||
|
- `src/Shared/Telemetry/TelemetryConstants.ts`
|
||||||
|
- `src/Localization/en/Resources.json` **and all non-English** `src/Localization/<locale>/Resources.json`
|
||||||
|
- `webpack.config.js` — `cellOutputViewer`, `connectToGitHub` entries + HTML plugins.
|
||||||
|
- `package.json` — `@nteract/*`, `@jupyterlab/*`, `@phosphor/widgets`, `rx-jupyter`,
|
||||||
|
and other notebook-only dependencies.
|
||||||
|
|
||||||
|
**Critical coupling — Terminal / shells**
|
||||||
|
- `TerminalTab` uses `CloudShellTerminalComponentAdapter` when
|
||||||
|
`userContext.features.enableCloudShell`, otherwise `NotebookTerminalComponentAdapter`
|
||||||
|
(which needs a Phoenix-allocated notebook server + `terminal.html` iframe).
|
||||||
|
- Command-bar/splash shell buttons branch on
|
||||||
|
`useNotebook.getState().isShellEnabled || userContext.features.enableCloudShell`.
|
||||||
|
- `Explorer.openNotebookTerminal(...)` is the shared entry for opening shells and must
|
||||||
|
be retained (and rewired to CloudShell-only) — only its notebook-server behavior is
|
||||||
|
removed.
|
||||||
|
|
||||||
|
## Phased approach
|
||||||
|
|
||||||
|
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
|
||||||
|
still open.
|
||||||
|
|
||||||
|
### Phase 1 — Decouple database shells to CloudShell-only
|
||||||
|
Remove the legacy Phoenix notebook-server terminal path so shells no longer depend on
|
||||||
|
notebook provisioning.
|
||||||
|
- Rewire `TerminalTab` to always use `CloudShellTerminalComponentAdapter`; delete the
|
||||||
|
`NotebookTerminalComponentAdapter` branch and `getNotebookServerInfo`.
|
||||||
|
- Delete `src/Explorer/Tabs/ShellAdapters/NotebookTerminalComponentAdapter.tsx` and
|
||||||
|
`src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx` (+ less/test/snapshot).
|
||||||
|
- Simplify shell buttons in `CommandBarComponentButtonFactory` and `SplashScreen` to
|
||||||
|
drop the `isShellEnabled` branch (CloudShell path only); keep `openNotebookTerminal`.
|
||||||
|
- Verify whether the `terminal`/`terminal.html` webpack entry is still needed by
|
||||||
|
CloudShell. If unused, remove it and `src/Terminal/`; otherwise keep.
|
||||||
|
- **Outcome:** Shells run purely on CloudShell. Phoenix no longer needed for terminals.
|
||||||
|
|
||||||
|
### Phase 2 — Remove the in-app notebook authoring & rendering experience
|
||||||
|
Delete the notebook tabs, the nteract rendering engine, panes, and the read-only viewer,
|
||||||
|
and remove all UI entry points that open notebooks.
|
||||||
|
- Delete: `src/Explorer/Notebook/NotebookComponent/`,
|
||||||
|
`src/Explorer/Notebook/NotebookRenderer/`, `src/Explorer/Notebook/SecurityWarningBar/`,
|
||||||
|
`NotebookClientV2.ts`, `notebookClientV2.test.ts`, `NotebookContentClient.ts`,
|
||||||
|
`NTeractUtil.ts`, `NotebookContentItem.ts`, `NotebookUtil.ts` (+ test),
|
||||||
|
`FileSystemUtil.ts`.
|
||||||
|
- Delete tabs/panes/viewer: `src/Explorer/Tabs/NotebookV2Tab.ts`, `NotebookTabBase.ts`,
|
||||||
|
`src/Explorer/Panes/CopyNotebookPane/`, `src/Explorer/Controls/NotebookViewer/`,
|
||||||
|
`src/CellOutputViewer/` (+ `cellOutputViewer` webpack entry & HTML plugin).
|
||||||
|
- Remove notebook entry points: "New Notebook"/open-notebook buttons in
|
||||||
|
`CommandBarComponentButtonFactory` (+ test), `OpenActions.tsx`,
|
||||||
|
`ContextMenuButtonFactory.tsx`, splash-screen notebook cards & recent-notebook items
|
||||||
|
(`MostRecentActivity` OpenNotebook type), and the `openNotebook*` /
|
||||||
|
`createNotebookContentItemFile` methods on `Explorer`.
|
||||||
|
- Remove notebook deps from `package.json`: `@nteract/*`, `@jupyterlab/*`,
|
||||||
|
`@phosphor/widgets`, `rx-jupyter` (and any now-unused transitive notebook-only libs).
|
||||||
|
- **Outcome:** Notebooks can no longer be authored, opened, or rendered.
|
||||||
|
|
||||||
|
### Phase 3 — Remove Schema Analyzer
|
||||||
|
- Delete `src/Explorer/Notebook/SchemaAnalyzer/` and `src/Explorer/Tabs/SchemaAnalyzerTab.ts`.
|
||||||
|
- Remove Schema Analyzer command-bar button and any tree/menu entry points.
|
||||||
|
|
||||||
|
### Phase 4 — Remove GitHub integration
|
||||||
|
- Delete `src/GitHub/`, `src/Explorer/Controls/GitHub/`,
|
||||||
|
`src/Explorer/Panes/GitHubReposPanel/`, `src/Utils/GitHubUtils.ts`,
|
||||||
|
`src/connectToGitHub.html`, and the `connectToGitHub` webpack entry & HTML plugin.
|
||||||
|
- Remove `gitHubOAuthService`, GitHub pinned-repo wiring, and `gitHubNotebooksContentRoot`
|
||||||
|
usage from `Explorer.tsx`, `useNotebook.ts`, `NotebookManager.tsx`, and `JunoClient`
|
||||||
|
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
|
||||||
|
- Delete `src/Phoenix/`, `src/Explorer/Notebook/NotebookContainerClient.ts`,
|
||||||
|
`src/Explorer/Notebook/NotebookManager.tsx`, `src/Explorer/Notebook/useNotebook.ts`,
|
||||||
|
`src/Utils/NotebookConfigurationUtils.ts`, `src/hooks/useNotebookSnapshotStore.ts`.
|
||||||
|
- Remove from `Explorer.tsx`: `phoenixClient`, `notebookManager`, `_isInitializingNotebooks`,
|
||||||
|
`initNotebooks`, `initiateAndRefreshNotebookList`, `refreshNotebookList`,
|
||||||
|
`allocateContainer`, container heartbeat/connection logic, and notebook-server URL
|
||||||
|
feature overrides.
|
||||||
|
- Remove notebook tree nodes ("My Notebooks") and `isNotebookEnabled` plumbing from
|
||||||
|
`treeNodeUtil.tsx`, `ResourceTreeAdapter.tsx`, `ResourceTree.tsx`, `Collection.ts`,
|
||||||
|
`useSelectedNode.ts` (+ update tree snapshots/tests).
|
||||||
|
- Remove notebook initialization from `useKnockoutExplorer.ts` and notebook tab handling
|
||||||
|
in `useTabs.ts`.
|
||||||
|
|
||||||
|
### Phase 6 — Remove residual clients, config, contracts, telemetry & strings
|
||||||
|
- Delete `src/Juno/` and `src/Utils/arm/generatedClients/cosmosNotebooks/`.
|
||||||
|
- Remove notebook fields from `ConfigContext.ts`, `Constants.ts` (Notebook namespace),
|
||||||
|
`DataModels.ts` (notebook/Phoenix/container interfaces), `ViewModels.ts`,
|
||||||
|
`ActionContracts.ts`, and notebook feature flags from
|
||||||
|
`extractFeatures.ts` (+ update test).
|
||||||
|
- Remove notebook/Phoenix telemetry actions/areas from `TelemetryConstants.ts` (preserve
|
||||||
|
enum numbering if other systems depend on it — mirror the cautious approach in
|
||||||
|
`a36467f4`).
|
||||||
|
- Remove remaining notebook strings from **all** locale `Resources.json` files (`en` +
|
||||||
|
every non-English locale) and any notebook images (e.g. `images/notebook/`).
|
||||||
|
- Final full build + test sweep; update `EndpointUtils.ts` (`allowedNotebookServerUrls`)
|
||||||
|
and any docs/comments referencing notebooks.
|
||||||
|
|
||||||
|
## Cross-cutting verification (run after each phase)
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run compile
|
||||||
|
npm run compile:strict
|
||||||
|
npm run lint
|
||||||
|
npm run format:check
|
||||||
|
npm test
|
||||||
|
npm run build:ci
|
||||||
|
```
|
||||||
|
Plus manual smoke test: open Mongo, Cassandra, Postgres, and VCoreMongo shells.
|
||||||
|
|
||||||
|
## Notes & considerations
|
||||||
|
|
||||||
|
- **Strict null checks:** any file edited may need to stay in / be removed from
|
||||||
|
`tsconfig.strict.json`. Remove deleted files from that list.
|
||||||
|
- **Snapshots:** several Jest snapshots reference notebook UI
|
||||||
|
(`treeNodeUtil`, `SettingsComponent`, panel snapshots). Regenerate after edits.
|
||||||
|
- **Telemetry enum safety:** prior commit `a36467f4` deliberately reverted removal of
|
||||||
|
enum values to avoid breaking downstream consumers. Prefer leaving enum numeric values
|
||||||
|
intact unless confirmed safe to remove.
|
||||||
|
- **`enableCloudShell` feature flag:** confirm it is enabled in all target environments
|
||||||
|
before removing the Phoenix shell fallback, or shells will break.
|
||||||
|
- **E2E tests:** check `test/` for notebook/terminal specs to update or remove; shells
|
||||||
|
may have E2E coverage that needs the CloudShell-only path.
|
||||||
|
- **Reconcile** with branch `users/jawelton/remove-notebooks-terminal-052126` to avoid
|
||||||
|
rework, especially in Phase 1.
|
||||||
Generated
+77
-104
@@ -113,7 +113,7 @@
|
|||||||
"react-youtube": "9.0.1",
|
"react-youtube": "9.0.1",
|
||||||
"reflect-metadata": "0.1.13",
|
"reflect-metadata": "0.1.13",
|
||||||
"rx-jupyter": "5.5.12",
|
"rx-jupyter": "5.5.12",
|
||||||
"shell-quote": "1.7.3",
|
"shell-quote": "1.8.4",
|
||||||
"styled-components": "5.0.1",
|
"styled-components": "5.0.1",
|
||||||
"swr": "0.4.0",
|
"swr": "0.4.0",
|
||||||
"terser-webpack-plugin": "5.3.9",
|
"terser-webpack-plugin": "5.3.9",
|
||||||
@@ -122,7 +122,6 @@
|
|||||||
"utility-types": "3.10.0",
|
"utility-types": "3.10.0",
|
||||||
"uuid": "9.0.0",
|
"uuid": "9.0.0",
|
||||||
"web-vitals": "4.2.4",
|
"web-vitals": "4.2.4",
|
||||||
"ws": "8.17.1",
|
|
||||||
"zustand": "3.5.0"
|
"zustand": "3.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -163,7 +162,7 @@
|
|||||||
"@webpack-cli/serve": "2.0.5",
|
"@webpack-cli/serve": "2.0.5",
|
||||||
"babel-jest": "29.7.0",
|
"babel-jest": "29.7.0",
|
||||||
"babel-loader": "8.1.0",
|
"babel-loader": "8.1.0",
|
||||||
"brace-expansion": "1.1.12",
|
"brace-expansion": "1.1.15",
|
||||||
"buffer": "5.1.0",
|
"buffer": "5.1.0",
|
||||||
"case-sensitive-paths-webpack-plugin": "2.4.0",
|
"case-sensitive-paths-webpack-plugin": "2.4.0",
|
||||||
"create-file-webpack": "1.0.2",
|
"create-file-webpack": "1.0.2",
|
||||||
@@ -214,7 +213,7 @@
|
|||||||
"webpack-bundle-analyzer": "5.2.0",
|
"webpack-bundle-analyzer": "5.2.0",
|
||||||
"webpack-cli": "5.1.4",
|
"webpack-cli": "5.1.4",
|
||||||
"webpack-dev-server": "5.2.3",
|
"webpack-dev-server": "5.2.3",
|
||||||
"ws": "8.17.1"
|
"ws": "8.20.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@aashutoshrathi/word-wrap": {
|
"node_modules/@aashutoshrathi/word-wrap": {
|
||||||
@@ -11604,10 +11603,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/body-parser": {
|
"node_modules/body-parser": {
|
||||||
"version": "1.20.4",
|
"version": "1.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
|
||||||
"integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
|
"integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bytes": "~3.1.2",
|
"bytes": "~3.1.2",
|
||||||
"content-type": "~1.0.5",
|
"content-type": "~1.0.5",
|
||||||
@@ -11617,7 +11617,7 @@
|
|||||||
"http-errors": "~2.0.1",
|
"http-errors": "~2.0.1",
|
||||||
"iconv-lite": "~0.4.24",
|
"iconv-lite": "~0.4.24",
|
||||||
"on-finished": "~2.4.1",
|
"on-finished": "~2.4.1",
|
||||||
"qs": "~6.14.0",
|
"qs": "~6.15.1",
|
||||||
"raw-body": "~2.5.3",
|
"raw-body": "~2.5.3",
|
||||||
"type-is": "~1.6.18",
|
"type-is": "~1.6.18",
|
||||||
"unpipe": "~1.0.0"
|
"unpipe": "~1.0.0"
|
||||||
@@ -11632,6 +11632,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "2.0.0"
|
"ms": "2.0.0"
|
||||||
}
|
}
|
||||||
@@ -11641,6 +11642,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"safer-buffer": ">= 2.1.2 < 3"
|
"safer-buffer": ">= 2.1.2 < 3"
|
||||||
},
|
},
|
||||||
@@ -11652,7 +11654,8 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/bonjour-service": {
|
"node_modules/bonjour-service": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
@@ -11679,9 +11682,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "1.1.12",
|
"version": "1.1.15",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
|
||||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
@@ -12347,6 +12351,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
@@ -15224,14 +15229,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express": {
|
"node_modules/express": {
|
||||||
"version": "4.22.1",
|
"version": "4.22.2",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
|
||||||
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
|
"integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "~1.3.8",
|
"accepts": "~1.3.8",
|
||||||
"array-flatten": "1.1.1",
|
"array-flatten": "1.1.1",
|
||||||
"body-parser": "~1.20.3",
|
"body-parser": "~1.20.5",
|
||||||
"content-disposition": "~0.5.4",
|
"content-disposition": "~0.5.4",
|
||||||
"content-type": "~1.0.4",
|
"content-type": "~1.0.4",
|
||||||
"cookie": "~0.7.1",
|
"cookie": "~0.7.1",
|
||||||
@@ -15250,7 +15256,7 @@
|
|||||||
"parseurl": "~1.3.3",
|
"parseurl": "~1.3.3",
|
||||||
"path-to-regexp": "~0.1.12",
|
"path-to-regexp": "~0.1.12",
|
||||||
"proxy-addr": "~2.0.7",
|
"proxy-addr": "~2.0.7",
|
||||||
"qs": "~6.14.0",
|
"qs": "~6.15.1",
|
||||||
"range-parser": "~1.2.1",
|
"range-parser": "~1.2.1",
|
||||||
"safe-buffer": "5.2.1",
|
"safe-buffer": "5.2.1",
|
||||||
"send": "~0.19.0",
|
"send": "~0.19.0",
|
||||||
@@ -19459,27 +19465,6 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jest-environment-jsdom/node_modules/ws": {
|
|
||||||
"version": "8.18.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
|
||||||
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"bufferutil": "^4.0.1",
|
|
||||||
"utf-8-validate": ">=5.0.2"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"bufferutil": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"utf-8-validate": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/jest-environment-jsdom/node_modules/xml-name-validator": {
|
"node_modules/jest-environment-jsdom/node_modules/xml-name-validator": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
||||||
@@ -22317,20 +22302,14 @@
|
|||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/launch-editor": {
|
"node_modules/launch-editor": {
|
||||||
"version": "2.6.1",
|
"version": "2.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.1.tgz",
|
||||||
|
"integrity": "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.1.1",
|
||||||
"shell-quote": "^1.8.1"
|
"shell-quote": "^1.8.4"
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/launch-editor/node_modules/shell-quote": {
|
|
||||||
"version": "1.8.1",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/leaflet": {
|
"node_modules/leaflet": {
|
||||||
@@ -22762,6 +22741,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
@@ -24033,7 +24013,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.31",
|
"version": "8.5.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
|
||||||
|
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -24050,9 +24032,9 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.6",
|
"nanoid": "^3.3.12",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.1.1",
|
||||||
"source-map-js": "^1.0.2"
|
"source-map-js": "^1.2.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
@@ -24129,6 +24111,24 @@
|
|||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/postcss/node_modules/nanoid": {
|
||||||
|
"version": "3.3.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
||||||
|
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"nanoid": "bin/nanoid.cjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prebuild-install": {
|
"node_modules/prebuild-install": {
|
||||||
"version": "7.1.3",
|
"version": "7.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
||||||
@@ -24498,9 +24498,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/qs": {
|
"node_modules/qs": {
|
||||||
"version": "6.14.1",
|
"version": "6.15.2",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
|
||||||
"integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
|
"integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"side-channel": "^1.1.0"
|
"side-channel": "^1.1.0"
|
||||||
},
|
},
|
||||||
@@ -24585,6 +24586,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
|
||||||
"integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
|
"integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bytes": "~3.1.2",
|
"bytes": "~3.1.2",
|
||||||
"http-errors": "~2.0.1",
|
"http-errors": "~2.0.1",
|
||||||
@@ -24600,6 +24602,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"safer-buffer": ">= 2.1.2 < 3"
|
"safer-buffer": ">= 2.1.2 < 3"
|
||||||
},
|
},
|
||||||
@@ -26303,8 +26306,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/shell-quote": {
|
"node_modules/shell-quote": {
|
||||||
"version": "1.7.3",
|
"version": "1.8.4",
|
||||||
"license": "MIT"
|
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz",
|
||||||
|
"integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/shiki": {
|
"node_modules/shiki": {
|
||||||
"version": "1.12.0",
|
"version": "1.12.0",
|
||||||
@@ -26524,7 +26535,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.0.2",
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -27399,6 +27412,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"media-typer": "0.3.0",
|
"media-typer": "0.3.0",
|
||||||
"mime-types": "~2.1.24"
|
"mime-types": "~2.1.24"
|
||||||
@@ -28278,27 +28292,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webpack-bundle-analyzer/node_modules/ws": {
|
|
||||||
"version": "8.19.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
|
|
||||||
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"bufferutil": "^4.0.1",
|
|
||||||
"utf-8-validate": ">=5.0.2"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"bufferutil": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"utf-8-validate": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/webpack-cli": {
|
"node_modules/webpack-cli": {
|
||||||
"version": "5.1.4",
|
"version": "5.1.4",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -29105,27 +29098,6 @@
|
|||||||
"url": "https://opencollective.com/webpack"
|
"url": "https://opencollective.com/webpack"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webpack-dev-server/node_modules/ws": {
|
|
||||||
"version": "8.19.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
|
|
||||||
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"bufferutil": "^4.0.1",
|
|
||||||
"utf-8-validate": ">=5.0.2"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"bufferutil": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"utf-8-validate": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/webpack-merge": {
|
"node_modules/webpack-merge": {
|
||||||
"version": "5.10.0",
|
"version": "5.10.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -29503,10 +29475,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "8.17.1",
|
"version": "8.20.1",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz",
|
||||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
"integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
+4
-4
@@ -108,7 +108,7 @@
|
|||||||
"react-youtube": "9.0.1",
|
"react-youtube": "9.0.1",
|
||||||
"reflect-metadata": "0.1.13",
|
"reflect-metadata": "0.1.13",
|
||||||
"rx-jupyter": "5.5.12",
|
"rx-jupyter": "5.5.12",
|
||||||
"shell-quote": "1.7.3",
|
"shell-quote": "1.8.4",
|
||||||
"styled-components": "5.0.1",
|
"styled-components": "5.0.1",
|
||||||
"swr": "0.4.0",
|
"swr": "0.4.0",
|
||||||
"terser-webpack-plugin": "5.3.9",
|
"terser-webpack-plugin": "5.3.9",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"utility-types": "3.10.0",
|
"utility-types": "3.10.0",
|
||||||
"uuid": "9.0.0",
|
"uuid": "9.0.0",
|
||||||
"web-vitals": "4.2.4",
|
"web-vitals": "4.2.4",
|
||||||
"ws": "8.17.1",
|
"ws": "8.20.1",
|
||||||
"zustand": "3.5.0"
|
"zustand": "3.5.0"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
@@ -173,7 +173,7 @@
|
|||||||
"@webpack-cli/serve": "2.0.5",
|
"@webpack-cli/serve": "2.0.5",
|
||||||
"babel-jest": "29.7.0",
|
"babel-jest": "29.7.0",
|
||||||
"babel-loader": "8.1.0",
|
"babel-loader": "8.1.0",
|
||||||
"brace-expansion": "1.1.12",
|
"brace-expansion": "1.1.15",
|
||||||
"buffer": "5.1.0",
|
"buffer": "5.1.0",
|
||||||
"case-sensitive-paths-webpack-plugin": "2.4.0",
|
"case-sensitive-paths-webpack-plugin": "2.4.0",
|
||||||
"create-file-webpack": "1.0.2",
|
"create-file-webpack": "1.0.2",
|
||||||
@@ -224,7 +224,7 @@
|
|||||||
"webpack-bundle-analyzer": "5.2.0",
|
"webpack-bundle-analyzer": "5.2.0",
|
||||||
"webpack-cli": "5.1.4",
|
"webpack-cli": "5.1.4",
|
||||||
"webpack-dev-server": "5.2.3",
|
"webpack-dev-server": "5.2.3",
|
||||||
"ws": "8.17.1"
|
"ws": "8.20.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "patch-package && npm run generate:i18n-keys",
|
"postinstall": "patch-package && npm run generate:i18n-keys",
|
||||||
|
|||||||
Generated
+3
-1
@@ -695,7 +695,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/qs": {
|
"node_modules/qs": {
|
||||||
"version": "6.14.1",
|
"version": "6.15.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
|
||||||
|
"integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"side-channel": "^1.1.0"
|
"side-channel": "^1.1.0"
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ export const handleError = (
|
|||||||
consoleErrorPrefix?: string,
|
consoleErrorPrefix?: string,
|
||||||
options?: HandleErrorOptions,
|
options?: HandleErrorOptions,
|
||||||
): void => {
|
): void => {
|
||||||
console.log("{{cdbp}} in handleError(): raw error: " + stringifyError(error)); //CTODO in case a stray error happens
|
|
||||||
const errorMessage = getErrorMessage(error);
|
const errorMessage = getErrorMessage(error);
|
||||||
const errorCode = error instanceof ARMError ? error.code : undefined;
|
const errorCode = error instanceof ARMError ? error.code : undefined;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { stringifyError } from "Common/stringifyError";
|
|
||||||
import { CosmosDbArtifactType } from "Contracts/FabricMessagesContract";
|
import { CosmosDbArtifactType } from "Contracts/FabricMessagesContract";
|
||||||
import { isFabric, isFabricMirroredKey, isFabricNative } from "Platform/Fabric/FabricUtil";
|
import { isFabric, isFabricMirroredKey, isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
@@ -27,7 +26,6 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
|
|||||||
(userContext.fabricContext?.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY]).resourceTokenInfo
|
(userContext.fabricContext?.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY]).resourceTokenInfo
|
||||||
.resourceTokens
|
.resourceTokens
|
||||||
) {
|
) {
|
||||||
console.log("{{cdbp}} in readDatabases(): isFabricMirroredKey && has resourceTokens"); //CTODO should not get here
|
|
||||||
const tokensData = (userContext.fabricContext.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY])
|
const tokensData = (userContext.fabricContext.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY])
|
||||||
.resourceTokenInfo;
|
.resourceTokenInfo;
|
||||||
|
|
||||||
@@ -61,7 +59,6 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
|
|||||||
clearMessage();
|
clearMessage();
|
||||||
return databases;
|
return databases;
|
||||||
} else if (isFabricNative() && userContext.fabricContext?.databaseName) {
|
} else if (isFabricNative() && userContext.fabricContext?.databaseName) {
|
||||||
console.log("{{cdbp}} in readDatabases(): isFabricNative"); //CTODO should not get here
|
|
||||||
const databaseId = userContext.fabricContext.databaseName;
|
const databaseId = userContext.fabricContext.databaseName;
|
||||||
databases = [
|
databases = [
|
||||||
{
|
{
|
||||||
@@ -84,15 +81,9 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
|
|||||||
userContext.apiType !== "Tables" &&
|
userContext.apiType !== "Tables" &&
|
||||||
!isFabric()
|
!isFabric()
|
||||||
) {
|
) {
|
||||||
console.log("{{cdbp}} in readDatabases(): authType == AAD, enableSDKOperations, apiType != Tables, !isFabric");
|
|
||||||
console.log("{{cdbp}} in readDatabases(): databaseaccount: " + userContext.databaseAccount);
|
|
||||||
console.log("{{cdbp}} in readDatabases(): calling readDatabasesWithARM");
|
|
||||||
databases = await readDatabasesWithARM();
|
databases = await readDatabasesWithARM();
|
||||||
console.log("{{cdbp}} in readDatabases(): done readDatabasesWithARM");
|
|
||||||
} else {
|
} else {
|
||||||
console.log("{{cdbp}} in readDatabases(): calling SDK");
|
|
||||||
const sdkResponse = await client().databases.readAll().fetchAll();
|
const sdkResponse = await client().databases.readAll().fetchAll();
|
||||||
console.log("{{cdbp}} in readDatabases(): done SDK");
|
|
||||||
databases = sdkResponse.resources as DataModels.Database[];
|
databases = sdkResponse.resources as DataModels.Database[];
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -117,30 +108,22 @@ export async function readDatabasesWithARM(accountOverride?: {
|
|||||||
const accountName = accountOverride?.accountName ?? userContext?.databaseAccount?.name ?? "";
|
const accountName = accountOverride?.accountName ?? userContext?.databaseAccount?.name ?? "";
|
||||||
const apiType = accountOverride?.apiType ?? userContext.apiType;
|
const apiType = accountOverride?.apiType ?? userContext.apiType;
|
||||||
|
|
||||||
try {
|
switch (apiType) {
|
||||||
switch (apiType) {
|
case "SQL":
|
||||||
case "SQL":
|
rpResponse = await listSqlDatabases(subscriptionId, resourceGroup, accountName);
|
||||||
console.log("{{cdbp}} in readDatabasesWithARM(): calling listSqlDatabases");
|
break;
|
||||||
rpResponse = await listSqlDatabases(subscriptionId, resourceGroup, accountName);
|
case "Mongo":
|
||||||
console.log("{{cdbp}} in readDatabasesWithARM(): done listSqlDatabases");
|
rpResponse = await listMongoDBDatabases(subscriptionId, resourceGroup, accountName);
|
||||||
break;
|
break;
|
||||||
case "Mongo":
|
case "Cassandra":
|
||||||
rpResponse = await listMongoDBDatabases(subscriptionId, resourceGroup, accountName);
|
rpResponse = await listCassandraKeyspaces(subscriptionId, resourceGroup, accountName);
|
||||||
break;
|
break;
|
||||||
case "Cassandra":
|
case "Gremlin":
|
||||||
rpResponse = await listCassandraKeyspaces(subscriptionId, resourceGroup, accountName);
|
rpResponse = await listGremlinDatabases(subscriptionId, resourceGroup, accountName);
|
||||||
break;
|
break;
|
||||||
case "Gremlin":
|
default:
|
||||||
rpResponse = await listGremlinDatabases(subscriptionId, resourceGroup, accountName);
|
throw new Error(`Unsupported default experience type: ${apiType}`);
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported default experience type: ${apiType}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("{{cdbp}} in readDatabasesWithARM(): response: " + JSON.stringify(rpResponse));
|
|
||||||
return rpResponse?.value?.map((database) => database.properties?.resource as DataModels.Database) ?? [];
|
|
||||||
} catch (error) {
|
|
||||||
console.log("{{cdbp}} in readDatabasesWithARM(): ERROR: " + stringifyError(error));
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return rpResponse?.value?.map((database) => database.properties?.resource as DataModels.Database) ?? [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
@import "../../../../less/Common/Constants";
|
|
||||||
|
|
||||||
.notebookTerminalContainer {
|
|
||||||
padding: @DefaultSpace;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
iframe {
|
|
||||||
border: none;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
import { shallow } from "enzyme";
|
|
||||||
import React from "react";
|
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
|
||||||
import { NotebookTerminalComponent, NotebookTerminalComponentProps } from "./NotebookTerminalComponent";
|
|
||||||
|
|
||||||
const testAccount: DataModels.DatabaseAccount = {
|
|
||||||
id: "id",
|
|
||||||
kind: "kind",
|
|
||||||
location: "location",
|
|
||||||
name: "name",
|
|
||||||
properties: {
|
|
||||||
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
|
|
||||||
},
|
|
||||||
type: "type",
|
|
||||||
};
|
|
||||||
|
|
||||||
const testMongo32Account: DataModels.DatabaseAccount = {
|
|
||||||
...testAccount,
|
|
||||||
};
|
|
||||||
|
|
||||||
const testMongo36Account: DataModels.DatabaseAccount = {
|
|
||||||
...testAccount,
|
|
||||||
properties: {
|
|
||||||
mongoEndpoint: "https://testMongoEndpoint.azure.com/",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const testCassandraAccount: DataModels.DatabaseAccount = {
|
|
||||||
...testAccount,
|
|
||||||
properties: {
|
|
||||||
cassandraEndpoint: "https://testCassandraEndpoint.azure.com/",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const testPostgresAccount: DataModels.DatabaseAccount = {
|
|
||||||
...testAccount,
|
|
||||||
properties: {
|
|
||||||
postgresqlEndpoint: "https://testPostgresEndpoint.azure.com/",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const testVCoreMongoAccount: DataModels.DatabaseAccount = {
|
|
||||||
...testAccount,
|
|
||||||
properties: {
|
|
||||||
vcoreMongoEndpoint: "https://testVCoreMongoEndpoint.azure.com/",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const testNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
|
||||||
authToken: "authToken",
|
|
||||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com",
|
|
||||||
forwardingId: "Id",
|
|
||||||
};
|
|
||||||
|
|
||||||
const testMongoNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
|
||||||
authToken: "authToken",
|
|
||||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
|
|
||||||
forwardingId: "Id",
|
|
||||||
};
|
|
||||||
|
|
||||||
const testCassandraNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
|
||||||
authToken: "authToken",
|
|
||||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/cassandra",
|
|
||||||
forwardingId: "Id",
|
|
||||||
};
|
|
||||||
|
|
||||||
const testPostgresNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
|
||||||
authToken: "authToken",
|
|
||||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/postgresql",
|
|
||||||
forwardingId: "Id",
|
|
||||||
};
|
|
||||||
|
|
||||||
const testVCoreMongoNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
|
||||||
authToken: "authToken",
|
|
||||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongovcore",
|
|
||||||
forwardingId: "Id",
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("NotebookTerminalComponent", () => {
|
|
||||||
it("renders terminal", () => {
|
|
||||||
const props: NotebookTerminalComponentProps = {
|
|
||||||
databaseAccount: testAccount,
|
|
||||||
notebookServerInfo: testNotebookServerInfo,
|
|
||||||
tabId: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders mongo 3.2 shell", () => {
|
|
||||||
const props: NotebookTerminalComponentProps = {
|
|
||||||
databaseAccount: testMongo32Account,
|
|
||||||
notebookServerInfo: testMongoNotebookServerInfo,
|
|
||||||
tabId: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders mongo 3.6 shell", () => {
|
|
||||||
const props: NotebookTerminalComponentProps = {
|
|
||||||
databaseAccount: testMongo36Account,
|
|
||||||
notebookServerInfo: testMongoNotebookServerInfo,
|
|
||||||
tabId: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders cassandra shell", () => {
|
|
||||||
const props: NotebookTerminalComponentProps = {
|
|
||||||
databaseAccount: testCassandraAccount,
|
|
||||||
notebookServerInfo: testCassandraNotebookServerInfo,
|
|
||||||
tabId: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders Postgres shell", () => {
|
|
||||||
const props: NotebookTerminalComponentProps = {
|
|
||||||
databaseAccount: testPostgresAccount,
|
|
||||||
notebookServerInfo: testPostgresNotebookServerInfo,
|
|
||||||
tabId: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders vCore Mongo shell", () => {
|
|
||||||
const props: NotebookTerminalComponentProps = {
|
|
||||||
databaseAccount: testVCoreMongoAccount,
|
|
||||||
notebookServerInfo: testVCoreMongoNotebookServerInfo,
|
|
||||||
tabId: undefined,
|
|
||||||
username: "username",
|
|
||||||
};
|
|
||||||
|
|
||||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
/**
|
|
||||||
* Wrapper around Notebook server terminal
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useTerminal } from "hooks/useTerminal";
|
|
||||||
import postRobot from "post-robot";
|
|
||||||
import * as React from "react";
|
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
|
||||||
import { TerminalProps } from "../../../Terminal/TerminalProps";
|
|
||||||
import { userContext } from "../../../UserContext";
|
|
||||||
import * as StringUtils from "../../../Utils/StringUtils";
|
|
||||||
|
|
||||||
export interface NotebookTerminalComponentProps {
|
|
||||||
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
|
||||||
databaseAccount: DataModels.DatabaseAccount;
|
|
||||||
tabId: string;
|
|
||||||
username?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NotebookTerminalComponent extends React.Component<NotebookTerminalComponentProps> {
|
|
||||||
private terminalWindow: Window;
|
|
||||||
|
|
||||||
constructor(props: NotebookTerminalComponentProps) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount(): void {
|
|
||||||
this.sendPropsToTerminalFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<div className="notebookTerminalContainer">
|
|
||||||
<iframe
|
|
||||||
title="Terminal to Notebook Server"
|
|
||||||
onLoad={(event) => this.handleFrameLoad(event)}
|
|
||||||
src="terminal.html"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFrameLoad(event: React.SyntheticEvent<HTMLIFrameElement, Event>): void {
|
|
||||||
this.terminalWindow = (event.target as HTMLIFrameElement).contentWindow;
|
|
||||||
useTerminal.getState().setTerminal(this.terminalWindow);
|
|
||||||
this.sendPropsToTerminalFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
sendPropsToTerminalFrame(): void {
|
|
||||||
if (!this.terminalWindow) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let props: TerminalProps = {
|
|
||||||
terminalEndpoint: this.tryGetTerminalEndpoint(),
|
|
||||||
notebookServerEndpoint: this.props.notebookServerInfo?.notebookServerEndpoint,
|
|
||||||
authToken: this.props.notebookServerInfo?.authToken,
|
|
||||||
subscriptionId: userContext.subscriptionId,
|
|
||||||
apiType: userContext.apiType,
|
|
||||||
authType: userContext.authType,
|
|
||||||
databaseAccount: userContext.databaseAccount,
|
|
||||||
tabId: this.props.tabId,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.props.username) {
|
|
||||||
props = {
|
|
||||||
...props,
|
|
||||||
username: this.props.username,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
postRobot.send(this.terminalWindow, "props", props, {
|
|
||||||
domain: window.location.origin,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public tryGetTerminalEndpoint(): string | undefined {
|
|
||||||
let terminalEndpoint: string | undefined;
|
|
||||||
|
|
||||||
const notebookServerEndpoint = this.props.notebookServerInfo?.notebookServerEndpoint;
|
|
||||||
if (StringUtils.endsWith(notebookServerEndpoint, "mongo")) {
|
|
||||||
// mongoEndpoint is only available for Mongo 3.6 and higher, fallback to documentEndpoint otherwise
|
|
||||||
terminalEndpoint =
|
|
||||||
this.props.databaseAccount?.properties.mongoEndpoint || this.props.databaseAccount?.properties.documentEndpoint;
|
|
||||||
} else if (StringUtils.endsWith(notebookServerEndpoint, "cassandra")) {
|
|
||||||
terminalEndpoint = this.props.databaseAccount?.properties.cassandraEndpoint;
|
|
||||||
} else if (StringUtils.endsWith(notebookServerEndpoint, "postgresql")) {
|
|
||||||
return this.props.databaseAccount?.properties.postgresqlEndpoint;
|
|
||||||
} else if (StringUtils.endsWith(notebookServerEndpoint, "mongovcore")) {
|
|
||||||
return this.props.databaseAccount?.properties.vcoreMongoEndpoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (terminalEndpoint) {
|
|
||||||
return new URL(terminalEndpoint).host;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`NotebookTerminalComponent renders Postgres shell 1`] = `
|
|
||||||
<div
|
|
||||||
className="notebookTerminalContainer"
|
|
||||||
>
|
|
||||||
<iframe
|
|
||||||
onLoad={[Function]}
|
|
||||||
src="terminal.html"
|
|
||||||
title="Terminal to Notebook Server"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`NotebookTerminalComponent renders cassandra shell 1`] = `
|
|
||||||
<div
|
|
||||||
className="notebookTerminalContainer"
|
|
||||||
>
|
|
||||||
<iframe
|
|
||||||
onLoad={[Function]}
|
|
||||||
src="terminal.html"
|
|
||||||
title="Terminal to Notebook Server"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`NotebookTerminalComponent renders mongo 3.2 shell 1`] = `
|
|
||||||
<div
|
|
||||||
className="notebookTerminalContainer"
|
|
||||||
>
|
|
||||||
<iframe
|
|
||||||
onLoad={[Function]}
|
|
||||||
src="terminal.html"
|
|
||||||
title="Terminal to Notebook Server"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`NotebookTerminalComponent renders mongo 3.6 shell 1`] = `
|
|
||||||
<div
|
|
||||||
className="notebookTerminalContainer"
|
|
||||||
>
|
|
||||||
<iframe
|
|
||||||
onLoad={[Function]}
|
|
||||||
src="terminal.html"
|
|
||||||
title="Terminal to Notebook Server"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`NotebookTerminalComponent renders terminal 1`] = `
|
|
||||||
<div
|
|
||||||
className="notebookTerminalContainer"
|
|
||||||
>
|
|
||||||
<iframe
|
|
||||||
onLoad={[Function]}
|
|
||||||
src="terminal.html"
|
|
||||||
title="Terminal to Notebook Server"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`NotebookTerminalComponent renders vCore Mongo shell 1`] = `
|
|
||||||
<div
|
|
||||||
className="notebookTerminalContainer"
|
|
||||||
>
|
|
||||||
<iframe
|
|
||||||
onLoad={[Function]}
|
|
||||||
src="terminal.html"
|
|
||||||
title="Terminal to Notebook Server"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -402,27 +402,19 @@ export default class Explorer {
|
|||||||
},
|
},
|
||||||
startKey,
|
startKey,
|
||||||
);
|
);
|
||||||
console.log("{{cdbp}} in refreshAllDatabases(): done readDatabases");
|
|
||||||
const currentDatabases = useDatabases.getState().databases;
|
const currentDatabases = useDatabases.getState().databases;
|
||||||
console.log("{{cdbp}} in refreshAllDatabases(): currentDatabases: " + currentDatabases);
|
|
||||||
const deltaDatabases = this.getDeltaDatabases(databases, currentDatabases);
|
const deltaDatabases = this.getDeltaDatabases(databases, currentDatabases);
|
||||||
console.log("{{cdbp}} in refreshAllDatabases(): deltaDatabases: " + deltaDatabases);
|
|
||||||
let updatedDatabases = currentDatabases.filter(
|
let updatedDatabases = currentDatabases.filter(
|
||||||
(database) => !deltaDatabases.toDelete.some((deletedDatabase) => deletedDatabase.id() === database.id()),
|
(database) => !deltaDatabases.toDelete.some((deletedDatabase) => deletedDatabase.id() === database.id()),
|
||||||
);
|
);
|
||||||
console.log("{{cdbp}} in refreshAllDatabases(): updatedDatabases after filter: " + updatedDatabases);
|
|
||||||
updatedDatabases = [...updatedDatabases, ...deltaDatabases.toAdd].sort((db1, db2) =>
|
updatedDatabases = [...updatedDatabases, ...deltaDatabases.toAdd].sort((db1, db2) =>
|
||||||
db1.id().localeCompare(db2.id()),
|
db1.id().localeCompare(db2.id()),
|
||||||
);
|
);
|
||||||
console.log("{{cdbp}} in refreshAllDatabases(): updatedDatabases after sort: " + updatedDatabases);
|
|
||||||
useDatabases.setState({ databases: updatedDatabases, databasesFetchedSuccessfully: true });
|
useDatabases.setState({ databases: updatedDatabases, databasesFetchedSuccessfully: true });
|
||||||
scenarioMonitor.completePhase(MetricScenario.DatabaseLoad, ApplicationMetricPhase.DatabasesFetched);
|
scenarioMonitor.completePhase(MetricScenario.DatabaseLoad, ApplicationMetricPhase.DatabasesFetched);
|
||||||
|
|
||||||
console.log("{{cdbp}} in refreshAllDatabases(): calling refreshAndExpandNewDatabases");
|
|
||||||
await this.refreshAndExpandNewDatabases(deltaDatabases.toAdd, updatedDatabases);
|
await this.refreshAndExpandNewDatabases(deltaDatabases.toAdd, updatedDatabases);
|
||||||
console.log("{{cdbp}} in refreshAllDatabases(): done refreshAndExpandNewDatabases");
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("{{cdbp}} in refreshAllDatabases(): ERROR: " + stringifyError(error)); //CTODO this should be logged already but just in case
|
|
||||||
const errorMessage = getErrorMessage(error);
|
const errorMessage = getErrorMessage(error);
|
||||||
TelemetryProcessor.traceFailure(
|
TelemetryProcessor.traceFailure(
|
||||||
Action.LoadDatabases,
|
Action.LoadDatabases,
|
||||||
@@ -612,7 +604,6 @@ export default class Explorer {
|
|||||||
? databases
|
? databases
|
||||||
: databases.filter((db) => db.isDatabaseExpanded() || db.id() === Constants.SavedQueries.DatabaseName);
|
: databases.filter((db) => db.isDatabaseExpanded() || db.id() === Constants.SavedQueries.DatabaseName);
|
||||||
|
|
||||||
console.log("{{cdbp}} in refreshAndExpandNewDatabases(): databasesToLoad: " + databasesToLoad);
|
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.LoadCollections, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.LoadCollections, {
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
@@ -621,7 +612,6 @@ export default class Explorer {
|
|||||||
try {
|
try {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
databasesToLoad.map(async (database: ViewModels.Database) => {
|
databasesToLoad.map(async (database: ViewModels.Database) => {
|
||||||
console.log("{{cdbp}} in refreshAndExpandNewDatabases(): loadCollections for database: " + database.id);
|
|
||||||
await database.loadCollections(true);
|
await database.loadCollections(true);
|
||||||
const isNewDatabase: boolean = _.some(newDatabases, (db: ViewModels.Database) => db.id() === database.id());
|
const isNewDatabase: boolean = _.some(newDatabases, (db: ViewModels.Database) => db.id() === database.id());
|
||||||
if (isNewDatabase) {
|
if (isNewDatabase) {
|
||||||
@@ -641,7 +631,6 @@ export default class Explorer {
|
|||||||
// Start DatabaseTreeRendered — React render cycle will complete it in ResourceTree
|
// Start DatabaseTreeRendered — React render cycle will complete it in ResourceTree
|
||||||
scenarioMonitor.startPhase(MetricScenario.DatabaseLoad, ApplicationMetricPhase.DatabaseTreeRendered);
|
scenarioMonitor.startPhase(MetricScenario.DatabaseLoad, ApplicationMetricPhase.DatabaseTreeRendered);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("{{cdbp}} in refreshAndExpandNewDatabases(): ERROR: " + stringifyError(error)); //CTODO this should be logged already but just in case
|
|
||||||
TelemetryProcessor.traceFailure(
|
TelemetryProcessor.traceFailure(
|
||||||
Action.LoadCollections,
|
Action.LoadCollections,
|
||||||
{
|
{
|
||||||
@@ -970,25 +959,8 @@ export default class Explorer {
|
|||||||
await this.notebookManager?.notebookContentClient.updateItemChildrenInPlace(item);
|
await this.notebookManager?.notebookContentClient.updateItemChildrenInPlace(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async openNotebookTerminal(kind: ViewModels.TerminalKind): Promise<void> {
|
public openNotebookTerminal(kind: ViewModels.TerminalKind): void {
|
||||||
if (userContext.features.enableCloudShell) {
|
this.connectToNotebookTerminal(kind);
|
||||||
this.connectToNotebookTerminal(kind);
|
|
||||||
} else if (useNotebook.getState().isPhoenixFeatures) {
|
|
||||||
await this.allocateContainer();
|
|
||||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
|
||||||
if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) {
|
|
||||||
this.connectToNotebookTerminal(kind);
|
|
||||||
} else {
|
|
||||||
useDialog
|
|
||||||
.getState()
|
|
||||||
.showOkModalDialog(
|
|
||||||
"Failed to connect",
|
|
||||||
"Failed to connect to temporary workspace. This could happen because of network issues. Please refresh the page and try again.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.connectToNotebookTerminal(kind);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private connectToNotebookTerminal(kind: ViewModels.TerminalKind): void {
|
private connectToNotebookTerminal(kind: ViewModels.TerminalKind): void {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useTerminal } from "hooks/useTerminal";
|
||||||
import React, { useEffect, useRef } from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
import { Terminal } from "xterm";
|
import { Terminal } from "xterm";
|
||||||
import { FitAddon } from "xterm-addon-fit";
|
import { FitAddon } from "xterm-addon-fit";
|
||||||
@@ -59,7 +60,14 @@ export const CloudShellTerminalComponent: React.FC<CloudShellTerminalComponentPr
|
|||||||
});
|
});
|
||||||
resizeObserver.observe(terminalRef.current);
|
resizeObserver.observe(terminalRef.current);
|
||||||
|
|
||||||
socketRef.current = startCloudShellTerminal(terminal, props.shellType);
|
const initTerminal = async () => {
|
||||||
|
const socket = await startCloudShellTerminal(terminal, props.shellType);
|
||||||
|
socketRef.current = socket;
|
||||||
|
if (socket) {
|
||||||
|
useTerminal.getState().setSocket(socket);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
initTerminal();
|
||||||
|
|
||||||
// Cleanup function to close WebSocket and dispose terminal
|
// Cleanup function to close WebSocket and dispose terminal
|
||||||
return () => {
|
return () => {
|
||||||
@@ -73,6 +81,7 @@ export const CloudShellTerminalComponent: React.FC<CloudShellTerminalComponentPr
|
|||||||
resizeObserver.unobserve(terminalRef.current);
|
resizeObserver.unobserve(terminalRef.current);
|
||||||
}
|
}
|
||||||
terminal.dispose(); // Clean up XTerm instance
|
terminal.dispose(); // Clean up XTerm instance
|
||||||
|
useTerminal.getState().setSocket(undefined);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { CosmosFluentProvider } from "Explorer/Theme/ThemeUtil";
|
|||||||
import { KeyboardAction } from "KeyboardShortcuts";
|
import { KeyboardAction } from "KeyboardShortcuts";
|
||||||
import { Keys, t } from "Localization";
|
import { Keys, t } from "Localization";
|
||||||
import { QueryConstants } from "Shared/Constants";
|
import { QueryConstants } from "Shared/Constants";
|
||||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey, getDefaultQueryResultsView } from "Shared/StorageUtility";
|
||||||
import { Allotment } from "allotment";
|
import { Allotment } from "allotment";
|
||||||
import { useClientWriteEnabled } from "hooks/useClientWriteEnabled";
|
import { useClientWriteEnabled } from "hooks/useClientWriteEnabled";
|
||||||
import { TabsState, useTabs } from "hooks/useTabs";
|
import { TabsState, useTabs } from "hooks/useTabs";
|
||||||
@@ -163,8 +163,11 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
isExecuting: false,
|
isExecuting: false,
|
||||||
cancelQueryTimeoutID: undefined,
|
cancelQueryTimeoutID: undefined,
|
||||||
currentTabActive: true,
|
currentTabActive: true,
|
||||||
queryResultsView:
|
queryResultsView: props.splitterDirection
|
||||||
props.splitterDirection === "vertical" ? SplitterDirection.Vertical : SplitterDirection.Horizontal,
|
? props.splitterDirection === "vertical"
|
||||||
|
? SplitterDirection.Vertical
|
||||||
|
: SplitterDirection.Horizontal
|
||||||
|
: getDefaultQueryResultsView(),
|
||||||
queryViewSizePercent: props.queryViewSizePercent,
|
queryViewSizePercent: props.queryViewSizePercent,
|
||||||
};
|
};
|
||||||
this.isCloseClicked = false;
|
this.isCloseClicked = false;
|
||||||
@@ -486,6 +489,13 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
commandButtonLabel: t(Keys.tabs.query.view),
|
commandButtonLabel: t(Keys.tabs.query.view),
|
||||||
ariaLabel: t(Keys.tabs.query.view),
|
ariaLabel: t(Keys.tabs.query.view),
|
||||||
hasPopup: true,
|
hasPopup: true,
|
||||||
|
onCommandClick: () => {
|
||||||
|
const newDirection =
|
||||||
|
this.state.queryResultsView === SplitterDirection.Vertical
|
||||||
|
? SplitterDirection.Horizontal
|
||||||
|
: SplitterDirection.Vertical;
|
||||||
|
this._setViewLayout(newDirection);
|
||||||
|
},
|
||||||
children: [verticalButton, horizontalButton],
|
children: [verticalButton, horizontalButton],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,18 @@
|
|||||||
import { Spinner, SpinnerSize, Stack, Text } from "@fluentui/react";
|
import { Stack } from "@fluentui/react";
|
||||||
|
|
||||||
import { NotebookWorkspaceConnectionInfo } from "Contracts/DataModels";
|
|
||||||
import { MessageTypes } from "Contracts/ExplorerContracts";
|
import { MessageTypes } from "Contracts/ExplorerContracts";
|
||||||
import { NotebookTerminalComponent } from "Explorer/Controls/Notebook/NotebookTerminalComponent";
|
import { TerminalKind } from "Contracts/ViewModels";
|
||||||
import Explorer from "Explorer/Explorer";
|
|
||||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
|
||||||
import { QuickstartFirewallNotification } from "Explorer/Quickstart/QuickstartFirewallNotification";
|
import { QuickstartFirewallNotification } from "Explorer/Quickstart/QuickstartFirewallNotification";
|
||||||
import { QuickstartGuide } from "Explorer/Quickstart/QuickstartGuide";
|
import { QuickstartGuide } from "Explorer/Quickstart/QuickstartGuide";
|
||||||
|
import { CloudShellTerminalComponent } from "Explorer/Tabs/CloudShellTab/CloudShellTerminalComponent";
|
||||||
import { checkFirewallRules } from "Explorer/Tabs/Shared/CheckFirewallRules";
|
import { checkFirewallRules } from "Explorer/Tabs/Shared/CheckFirewallRules";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import FirewallRuleScreenshot from "../../../images/firewallRule.png";
|
import FirewallRuleScreenshot from "../../../images/firewallRule.png";
|
||||||
|
|
||||||
interface QuickstartTabProps {
|
export const QuickstartTab: React.FC = (): JSX.Element => {
|
||||||
explorer: Explorer;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const QuickstartTab: React.FC<QuickstartTabProps> = ({ explorer }: QuickstartTabProps): JSX.Element => {
|
|
||||||
const notebookServerInfo = useNotebook((state) => state.notebookServerInfo);
|
|
||||||
const [isAllPublicIPAddressEnabled, setIsAllPublicIPAddressEnabled] = useState<boolean>(true);
|
const [isAllPublicIPAddressEnabled, setIsAllPublicIPAddressEnabled] = useState<boolean>(true);
|
||||||
|
|
||||||
const getNotebookServerInfo = (): NotebookWorkspaceConnectionInfo => ({
|
|
||||||
authToken: notebookServerInfo.authToken,
|
|
||||||
notebookServerEndpoint: `${notebookServerInfo.notebookServerEndpoint?.replace(/\/+$/, "")}/postgresql`,
|
|
||||||
forwardingId: notebookServerInfo.forwardingId,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkFirewallRules(
|
checkFirewallRules(
|
||||||
"2022-11-08",
|
"2022-11-08",
|
||||||
@@ -34,10 +21,6 @@ export const QuickstartTab: React.FC<QuickstartTabProps> = ({ explorer }: Quicks
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
explorer.allocateContainer();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack style={{ width: "100%" }} horizontal>
|
<Stack style={{ width: "100%" }} horizontal>
|
||||||
<Stack style={{ width: "50%" }}>
|
<Stack style={{ width: "50%" }}>
|
||||||
@@ -51,24 +34,13 @@ export const QuickstartTab: React.FC<QuickstartTabProps> = ({ explorer }: Quicks
|
|||||||
shellName="PostgreSQL"
|
shellName="PostgreSQL"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isAllPublicIPAddressEnabled && notebookServerInfo?.notebookServerEndpoint && (
|
{isAllPublicIPAddressEnabled && (
|
||||||
<NotebookTerminalComponent
|
<CloudShellTerminalComponent
|
||||||
notebookServerInfo={getNotebookServerInfo()}
|
|
||||||
databaseAccount={userContext.databaseAccount}
|
databaseAccount={userContext.databaseAccount}
|
||||||
tabId="QuickstartPSQLShell"
|
tabId="QuickstartPSQLShell"
|
||||||
|
shellType={TerminalKind.Postgres}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isAllPublicIPAddressEnabled && !notebookServerInfo?.notebookServerEndpoint && (
|
|
||||||
<Stack style={{ margin: "auto 0" }}>
|
|
||||||
<Text block style={{ margin: "auto" }}>
|
|
||||||
Connecting to the PostgreSQL shell.
|
|
||||||
</Text>
|
|
||||||
<Text block style={{ margin: "auto" }}>
|
|
||||||
If the cluster was just created, this could take up to a minute.
|
|
||||||
</Text>
|
|
||||||
<Spinner styles={{ root: { marginTop: 16 } }} size={SpinnerSize.large}></Spinner>
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
import { NotebookTerminalComponent } from "Explorer/Controls/Notebook/NotebookTerminalComponent";
|
|
||||||
import * as React from "react";
|
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
|
||||||
import { BaseTerminalComponentAdapter } from "./BaseTerminalComponentAdapter";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notebook terminal tab
|
|
||||||
*/
|
|
||||||
export class NotebookTerminalComponentAdapter extends BaseTerminalComponentAdapter {
|
|
||||||
constructor(
|
|
||||||
private getNotebookServerInfo: () => DataModels.NotebookWorkspaceConnectionInfo,
|
|
||||||
getDatabaseAccount: () => DataModels.DatabaseAccount,
|
|
||||||
getTabId: () => string,
|
|
||||||
getUsername: () => string,
|
|
||||||
isAllPublicIPAddressesEnabled: ko.Observable<boolean>,
|
|
||||||
kind: ViewModels.TerminalKind,
|
|
||||||
) {
|
|
||||||
super(getDatabaseAccount, getTabId, getUsername, isAllPublicIPAddressesEnabled, kind);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected renderTerminalComponent(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<NotebookTerminalComponent
|
|
||||||
notebookServerInfo={this.getNotebookServerInfo()}
|
|
||||||
databaseAccount={this.getDatabaseAccount()}
|
|
||||||
tabId={this.getTabId()}
|
|
||||||
username={this.getUsername()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -333,11 +333,7 @@ const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): J
|
|||||||
return <SplashScreen explorer={explorer} />;
|
return <SplashScreen explorer={explorer} />;
|
||||||
}
|
}
|
||||||
case ReactTabKind.Quickstart:
|
case ReactTabKind.Quickstart:
|
||||||
return userContext.apiType === "VCoreMongo" ? (
|
return userContext.apiType === "VCoreMongo" ? <VcoreMongoQuickstartTab /> : <QuickstartTab />;
|
||||||
<VcoreMongoQuickstartTab explorer={explorer} />
|
|
||||||
) : (
|
|
||||||
<QuickstartTab explorer={explorer} />
|
|
||||||
);
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported tab kind ${ReactTabKind[activeReactTab]}`);
|
throw new Error(`Unsupported tab kind ${ReactTabKind[activeReactTab]}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import * as ViewModels from "../../Contracts/ViewModels";
|
|||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { useNotebook } from "../Notebook/useNotebook";
|
|
||||||
import { NotebookTerminalComponentAdapter } from "./ShellAdapters/NotebookTerminalComponentAdapter";
|
|
||||||
import TabsBase from "./TabsBase";
|
import TabsBase from "./TabsBase";
|
||||||
|
|
||||||
export interface TerminalTabOptions extends ViewModels.TabOptions {
|
export interface TerminalTabOptions extends ViewModels.TabOptions {
|
||||||
@@ -29,41 +27,17 @@ export default class TerminalTab extends TabsBase {
|
|||||||
this.container = options.container;
|
this.container = options.container;
|
||||||
this.isAllPublicIPAddressesEnabled = ko.observable(true);
|
this.isAllPublicIPAddressesEnabled = ko.observable(true);
|
||||||
|
|
||||||
const commonArgs: [
|
this.notebookTerminalComponentAdapter = new CloudShellTerminalComponentAdapter(
|
||||||
() => DataModels.DatabaseAccount,
|
|
||||||
() => string,
|
|
||||||
() => string,
|
|
||||||
ko.Observable<boolean>,
|
|
||||||
ViewModels.TerminalKind,
|
|
||||||
] = [
|
|
||||||
() => userContext?.databaseAccount,
|
() => userContext?.databaseAccount,
|
||||||
() => this.tabId,
|
() => this.tabId,
|
||||||
() => this.getUsername(),
|
() => this.getUsername(),
|
||||||
this.isAllPublicIPAddressesEnabled,
|
this.isAllPublicIPAddressesEnabled,
|
||||||
options.kind,
|
options.kind,
|
||||||
];
|
);
|
||||||
|
|
||||||
if (userContext.features.enableCloudShell) {
|
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
|
||||||
this.notebookTerminalComponentAdapter = new CloudShellTerminalComponentAdapter(...commonArgs);
|
return this.isTemplateReady() && this.isAllPublicIPAddressesEnabled();
|
||||||
|
});
|
||||||
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
|
|
||||||
return this.isTemplateReady() && this.isAllPublicIPAddressesEnabled();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.notebookTerminalComponentAdapter = new NotebookTerminalComponentAdapter(
|
|
||||||
() => this.getNotebookServerInfo(options),
|
|
||||||
...commonArgs,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
|
|
||||||
return (
|
|
||||||
this.isTemplateReady() &&
|
|
||||||
useNotebook.getState().isNotebookEnabled &&
|
|
||||||
useNotebook.getState().notebookServerInfo?.notebookServerEndpoint &&
|
|
||||||
this.isAllPublicIPAddressesEnabled()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.kind === ViewModels.TerminalKind.Postgres) {
|
if (options.kind === ViewModels.TerminalKind.Postgres) {
|
||||||
checkFirewallRules(
|
checkFirewallRules(
|
||||||
@@ -72,16 +46,6 @@ export default class TerminalTab extends TabsBase {
|
|||||||
this.isAllPublicIPAddressesEnabled,
|
this.isAllPublicIPAddressesEnabled,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.kind === ViewModels.TerminalKind.VCoreMongo) {
|
|
||||||
checkFirewallRules(
|
|
||||||
"2023-03-01-preview",
|
|
||||||
(rule) =>
|
|
||||||
rule.name.startsWith("AllowAllAzureServicesAndResourcesWithinAzureIps") ||
|
|
||||||
(rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255"),
|
|
||||||
this.isAllPublicIPAddressesEnabled,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getContainer(): Explorer {
|
public getContainer(): Explorer {
|
||||||
@@ -96,42 +60,6 @@ export default class TerminalTab extends TabsBase {
|
|||||||
this.updateNavbarWithTabsButtons();
|
this.updateNavbarWithTabsButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
private getNotebookServerInfo(options: TerminalTabOptions): DataModels.NotebookWorkspaceConnectionInfo {
|
|
||||||
let endpointSuffix: string;
|
|
||||||
|
|
||||||
switch (options.kind) {
|
|
||||||
case ViewModels.TerminalKind.Default:
|
|
||||||
endpointSuffix = "";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ViewModels.TerminalKind.Mongo:
|
|
||||||
endpointSuffix = "mongo";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ViewModels.TerminalKind.Cassandra:
|
|
||||||
endpointSuffix = "cassandra";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ViewModels.TerminalKind.Postgres:
|
|
||||||
endpointSuffix = "postgresql";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ViewModels.TerminalKind.VCoreMongo:
|
|
||||||
endpointSuffix = "mongovcore";
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(`Terminal kind: ${options.kind} not supported`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const info: DataModels.NotebookWorkspaceConnectionInfo = useNotebook.getState().notebookServerInfo;
|
|
||||||
return {
|
|
||||||
authToken: info.authToken,
|
|
||||||
notebookServerEndpoint: `${info.notebookServerEndpoint.replace(/\/+$/, "")}/${endpointSuffix}`,
|
|
||||||
forwardingId: info.forwardingId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private getUsername(): string {
|
private getUsername(): string {
|
||||||
if (userContext.apiType !== "VCoreMongo" || !userContext?.vcoreMongoConnectionParams?.adminLogin) {
|
if (userContext.apiType !== "VCoreMongo" || !userContext?.vcoreMongoConnectionParams?.adminLogin) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -1,79 +1,24 @@
|
|||||||
import { Spinner, SpinnerSize, Stack, Text } from "@fluentui/react";
|
import { Stack } from "@fluentui/react";
|
||||||
|
|
||||||
import { NotebookWorkspaceConnectionInfo } from "Contracts/DataModels";
|
import { TerminalKind } from "Contracts/ViewModels";
|
||||||
import { MessageTypes } from "Contracts/ExplorerContracts";
|
|
||||||
import { NotebookTerminalComponent } from "Explorer/Controls/Notebook/NotebookTerminalComponent";
|
|
||||||
import Explorer from "Explorer/Explorer";
|
|
||||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
|
||||||
import { QuickstartFirewallNotification } from "Explorer/Quickstart/QuickstartFirewallNotification";
|
|
||||||
import { VcoreMongoQuickstartGuide } from "Explorer/Quickstart/VCoreMongoQuickstartGuide";
|
import { VcoreMongoQuickstartGuide } from "Explorer/Quickstart/VCoreMongoQuickstartGuide";
|
||||||
import { checkFirewallRules } from "Explorer/Tabs/Shared/CheckFirewallRules";
|
import { CloudShellTerminalComponent } from "Explorer/Tabs/CloudShellTab/CloudShellTerminalComponent";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import React, { useEffect, useState } from "react";
|
import React from "react";
|
||||||
import FirewallRuleScreenshot from "../../../images/vcoreMongoFirewallRule.png";
|
|
||||||
|
|
||||||
interface VCoreMongoQuickstartTabProps {
|
|
||||||
explorer: Explorer;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const VcoreMongoQuickstartTab: React.FC<VCoreMongoQuickstartTabProps> = ({
|
|
||||||
explorer,
|
|
||||||
}: VCoreMongoQuickstartTabProps): JSX.Element => {
|
|
||||||
const notebookServerInfo = useNotebook((state) => state.notebookServerInfo);
|
|
||||||
const [isAllPublicIPAddressEnabled, setIsAllPublicIPAddressEnabled] = useState<boolean>(true);
|
|
||||||
|
|
||||||
const getNotebookServerInfo = (): NotebookWorkspaceConnectionInfo => ({
|
|
||||||
authToken: notebookServerInfo.authToken,
|
|
||||||
notebookServerEndpoint: `${notebookServerInfo.notebookServerEndpoint?.replace(/\/+$/, "")}/mongovcore`,
|
|
||||||
forwardingId: notebookServerInfo.forwardingId,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
checkFirewallRules(
|
|
||||||
"2023-03-01-preview",
|
|
||||||
(rule) =>
|
|
||||||
rule.name.startsWith("AllowAllAzureServicesAndResourcesWithinAzureIps") ||
|
|
||||||
(rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255"),
|
|
||||||
setIsAllPublicIPAddressEnabled,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
explorer.allocateContainer();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
export const VcoreMongoQuickstartTab: React.FC = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<Stack style={{ width: "100%" }} horizontal>
|
<Stack style={{ width: "100%" }} horizontal>
|
||||||
<Stack style={{ width: "50%" }}>
|
<Stack style={{ width: "50%" }}>
|
||||||
<VcoreMongoQuickstartGuide />
|
<VcoreMongoQuickstartGuide />
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack style={{ width: "50%", borderLeft: "black solid 1px" }}>
|
<Stack style={{ width: "50%", borderLeft: "black solid 1px" }}>
|
||||||
{!isAllPublicIPAddressEnabled && (
|
<CloudShellTerminalComponent
|
||||||
<QuickstartFirewallNotification
|
databaseAccount={userContext.databaseAccount}
|
||||||
messageType={MessageTypes.OpenVCoreMongoNetworkingBlade}
|
tabId="QuickstartVcoreMongoShell"
|
||||||
screenshot={FirewallRuleScreenshot}
|
username={userContext.vcoreMongoConnectionParams?.adminLogin}
|
||||||
shellName="MongoDB"
|
shellType={TerminalKind.VCoreMongo}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
{isAllPublicIPAddressEnabled && notebookServerInfo?.notebookServerEndpoint && (
|
|
||||||
<NotebookTerminalComponent
|
|
||||||
notebookServerInfo={getNotebookServerInfo()}
|
|
||||||
databaseAccount={userContext.databaseAccount}
|
|
||||||
tabId="QuickstartVcoreMongoShell"
|
|
||||||
username={userContext.vcoreMongoConnectionParams.adminLogin}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isAllPublicIPAddressEnabled && !notebookServerInfo?.notebookServerEndpoint && (
|
|
||||||
<Stack style={{ margin: "auto 0" }}>
|
|
||||||
<Text block style={{ margin: "auto" }}>
|
|
||||||
Connecting to the Mongo shell.
|
|
||||||
</Text>
|
|
||||||
<Text block style={{ margin: "auto" }}>
|
|
||||||
If the cluster was just created, this could take up to a minute.
|
|
||||||
</Text>
|
|
||||||
<Spinner styles={{ root: { marginTop: 16 } }} size={SpinnerSize.large}></Spinner>
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ import "./Explorer/Controls/CollapsiblePanel/CollapsiblePanelComponent.less";
|
|||||||
import { Dialog } from "./Explorer/Controls/Dialog";
|
import { Dialog } from "./Explorer/Controls/Dialog";
|
||||||
import "./Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.less";
|
import "./Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.less";
|
||||||
import "./Explorer/Controls/JsonEditor/JsonEditorComponent.less";
|
import "./Explorer/Controls/JsonEditor/JsonEditorComponent.less";
|
||||||
import "./Explorer/Controls/Notebook/NotebookTerminalComponent.less";
|
|
||||||
import "./Explorer/Controls/TreeComponent/treeComponent.less";
|
import "./Explorer/Controls/TreeComponent/treeComponent.less";
|
||||||
import { ErrorBoundary } from "./Explorer/ErrorBoundary";
|
import { ErrorBoundary } from "./Explorer/ErrorBoundary";
|
||||||
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
|
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
|
||||||
|
|||||||
@@ -1,125 +0,0 @@
|
|||||||
/**
|
|
||||||
* JupyterLab applications based on jupyterLab components
|
|
||||||
*/
|
|
||||||
import { ServerConnection, TerminalManager } from "@jupyterlab/services";
|
|
||||||
import { IMessage, ITerminalConnection } from "@jupyterlab/services/lib/terminal/terminal";
|
|
||||||
import { Terminal } from "@jupyterlab/terminal";
|
|
||||||
import { Panel, Widget } from "@phosphor/widgets";
|
|
||||||
import { userContext } from "UserContext";
|
|
||||||
|
|
||||||
export class JupyterLabAppFactory {
|
|
||||||
private isShellStarted: boolean | undefined;
|
|
||||||
private checkShellStarted: ((content: string | undefined) => void) | undefined;
|
|
||||||
private onShellExited: (restartShell: boolean) => void;
|
|
||||||
private restartShell: boolean;
|
|
||||||
|
|
||||||
private isShellExited(content: string | undefined) {
|
|
||||||
if (userContext.apiType === "VCoreMongo" && content?.includes("MongoServerError: Invalid key")) {
|
|
||||||
this.restartShell = true;
|
|
||||||
}
|
|
||||||
return content?.includes("cosmosshelluser@");
|
|
||||||
}
|
|
||||||
|
|
||||||
private isMongoShellStarted(content: string | undefined) {
|
|
||||||
this.isShellStarted = content?.includes("MongoDB shell version");
|
|
||||||
}
|
|
||||||
|
|
||||||
private isCassandraShellStarted(content: string | undefined) {
|
|
||||||
this.isShellStarted = content?.includes("Connected to") && content?.includes("cqlsh");
|
|
||||||
}
|
|
||||||
|
|
||||||
private isPostgresShellStarted(content: string | undefined) {
|
|
||||||
this.isShellStarted = content?.includes("citus=>");
|
|
||||||
}
|
|
||||||
|
|
||||||
private isVCoreMongoShellStarted(content: string | undefined) {
|
|
||||||
this.isShellStarted = content?.includes("Enter password");
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(closeTab: (restartShell: boolean) => void) {
|
|
||||||
this.onShellExited = closeTab;
|
|
||||||
this.isShellStarted = false;
|
|
||||||
this.checkShellStarted = undefined;
|
|
||||||
this.restartShell = false;
|
|
||||||
|
|
||||||
switch (userContext.apiType) {
|
|
||||||
case "Mongo":
|
|
||||||
this.checkShellStarted = this.isMongoShellStarted;
|
|
||||||
break;
|
|
||||||
case "Cassandra":
|
|
||||||
this.checkShellStarted = this.isCassandraShellStarted;
|
|
||||||
break;
|
|
||||||
case "Postgres":
|
|
||||||
this.checkShellStarted = this.isPostgresShellStarted;
|
|
||||||
break;
|
|
||||||
case "VCoreMongo":
|
|
||||||
this.checkShellStarted = this.isVCoreMongoShellStarted;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createTerminalApp(serverSettings: ServerConnection.ISettings): Promise<ITerminalConnection | undefined> {
|
|
||||||
const configurationSettings: Partial<ServerConnection.ISettings> = serverSettings;
|
|
||||||
(configurationSettings.appendToken as boolean) = false;
|
|
||||||
serverSettings = ServerConnection.makeSettings(configurationSettings);
|
|
||||||
const manager = new TerminalManager({
|
|
||||||
serverSettings: serverSettings,
|
|
||||||
});
|
|
||||||
const session = await manager.startNew();
|
|
||||||
session.messageReceived.connect(async (_, message: IMessage) => {
|
|
||||||
const content = message.content && message.content[0]?.toString();
|
|
||||||
if (this.checkShellStarted && message.type == "stdout") {
|
|
||||||
//Close the terminal tab once the shell closed messages are received
|
|
||||||
if (!this.isShellStarted) {
|
|
||||||
this.checkShellStarted(content);
|
|
||||||
} else if (this.isShellExited(content)) {
|
|
||||||
this.onShellExited(this.restartShell);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
let internalSend = session.send;
|
|
||||||
session.send = (message: IMessage) => {
|
|
||||||
message?.content?.push(serverSettings?.token);
|
|
||||||
internalSend.call(session, message);
|
|
||||||
};
|
|
||||||
const term = new Terminal(session, { theme: "dark", shutdownOnClose: true });
|
|
||||||
|
|
||||||
if (!term) {
|
|
||||||
console.error("Failed starting terminal");
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
term.title.closable = false;
|
|
||||||
term.addClass("terminalWidget");
|
|
||||||
|
|
||||||
let panel = new Panel();
|
|
||||||
panel.addWidget(term as any);
|
|
||||||
panel.id = "main";
|
|
||||||
|
|
||||||
// Attach the widget to the dom.
|
|
||||||
Widget.attach(panel, document.body);
|
|
||||||
|
|
||||||
// Switch focus to the terminal
|
|
||||||
term.activate();
|
|
||||||
|
|
||||||
// Handle resize events.
|
|
||||||
window.addEventListener("resize", () => {
|
|
||||||
panel.update();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Dispose terminal when unloading.
|
|
||||||
window.addEventListener("unload", () => {
|
|
||||||
panel.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Close terminal when Ctrl key is pressed
|
|
||||||
term.node.addEventListener("keydown", (event: KeyboardEvent) => {
|
|
||||||
if (event.ctrlKey) {
|
|
||||||
this.onShellExited(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-111
@@ -1,111 +0,0 @@
|
|||||||
/**
|
|
||||||
* Message handling with iframe parent
|
|
||||||
*/
|
|
||||||
export interface UpdateMessage {
|
|
||||||
command: string;
|
|
||||||
arg?: any;
|
|
||||||
}
|
|
||||||
export declare type ContentType = "notebook" | "file" | "directory";
|
|
||||||
export interface ContentItem {
|
|
||||||
name: string;
|
|
||||||
path: string;
|
|
||||||
type: ContentType;
|
|
||||||
}
|
|
||||||
export interface UploadData {
|
|
||||||
filepath: string;
|
|
||||||
content: string;
|
|
||||||
}
|
|
||||||
export interface RenameFileData {
|
|
||||||
sourcePath: string;
|
|
||||||
targetPath: string;
|
|
||||||
}
|
|
||||||
export interface RenameFileResult {
|
|
||||||
source: string;
|
|
||||||
target: ContentItem;
|
|
||||||
}
|
|
||||||
export interface FromDataExplorerMessage {
|
|
||||||
type: MessageTypes;
|
|
||||||
params: any;
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
export declare type KernelStatusStates =
|
|
||||||
| "unknown"
|
|
||||||
| "starting"
|
|
||||||
| "reconnecting"
|
|
||||||
| "idle"
|
|
||||||
| "busy"
|
|
||||||
| "restarting"
|
|
||||||
| "autorestarting"
|
|
||||||
| "dead"
|
|
||||||
| "connected";
|
|
||||||
/**
|
|
||||||
* Unsolicited message
|
|
||||||
*/
|
|
||||||
export interface FromNotebookUpdateMessage {
|
|
||||||
type: NotebookUpdateTypes;
|
|
||||||
arg?: any;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Response to a Data Explorer request
|
|
||||||
*/
|
|
||||||
export interface FromNotebookResponseMessage {
|
|
||||||
id: string;
|
|
||||||
data?: any;
|
|
||||||
error?: any;
|
|
||||||
}
|
|
||||||
export interface FromNotebookMessage {
|
|
||||||
actionType: ActionTypes;
|
|
||||||
message: FromNotebookUpdateMessage | FromNotebookResponseMessage;
|
|
||||||
}
|
|
||||||
export declare type KernelOption = {
|
|
||||||
name: string;
|
|
||||||
displayName: string;
|
|
||||||
};
|
|
||||||
export interface KernelSpecs {
|
|
||||||
defaultName: string;
|
|
||||||
kernelSpecs: {
|
|
||||||
[name: string]: KernelOption;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export declare enum ActionTypes {
|
|
||||||
Update = 0,
|
|
||||||
Response = 1,
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Messages Data Explorer -> JupyterLabApp
|
|
||||||
*/
|
|
||||||
export declare enum MessageTypes {
|
|
||||||
FileList = 0,
|
|
||||||
CreateInDir = 1,
|
|
||||||
DeleteFile = 2,
|
|
||||||
UploadFile = 3,
|
|
||||||
RenameFile = 4,
|
|
||||||
ReadFileContent = 5,
|
|
||||||
CreateDirectory = 6,
|
|
||||||
InsertBelow = 7,
|
|
||||||
RunAndAdvance = 8,
|
|
||||||
Copy = 9,
|
|
||||||
Cut = 10,
|
|
||||||
Paste = 11,
|
|
||||||
Undo = 12,
|
|
||||||
ClearAllOutputs = 13,
|
|
||||||
RunAll = 14,
|
|
||||||
Redo = 15,
|
|
||||||
Save = 16,
|
|
||||||
RestartKernel = 17,
|
|
||||||
ChangeCellType = 18,
|
|
||||||
SwitchKernel = 19,
|
|
||||||
ChangeKernel = 20,
|
|
||||||
Status = 21,
|
|
||||||
KernelList = 22,
|
|
||||||
IsDirty = 23,
|
|
||||||
Shutdown = 24,
|
|
||||||
}
|
|
||||||
export declare enum NotebookUpdateTypes {
|
|
||||||
Ready = 0,
|
|
||||||
ClickEvent = 1,
|
|
||||||
ActiveCellType = 2,
|
|
||||||
KernelChange = 3,
|
|
||||||
FileSaved = 4,
|
|
||||||
SessionStatusChange = 5,
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
# Summary
|
|
||||||
This describes how to run a custom version of the Data Explorer in the Emulator which can open a jupyter notebook from with a tab.
|
|
||||||
|
|
||||||
# Requirements
|
|
||||||
This requires:
|
|
||||||
* a running instance of CosmosDB Emulator
|
|
||||||
* a running instance of the jupyter server
|
|
||||||
* access to the cosmosdb-dataexplorer git repository
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
## Install CosmosDB Emulator
|
|
||||||
* Download from https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator
|
|
||||||
* Open the Emulator and create at least one Collection
|
|
||||||
|
|
||||||
## Install Jupyter server on local machine (Windows)
|
|
||||||
We use the Anaconda distribution which comes with a packaged jupyter and python.
|
|
||||||
* Download and install Anaconda from https://www.anaconda.com/distribution/ (python3 64-bit version)
|
|
||||||
Keep all default options. Install Visual Studio Code as well.
|
|
||||||
### Verify Jupyter installation and create mynotebook
|
|
||||||
* Open an "Anaconda Prompt" (hit the Window key, type "Anaconda", select "Anaconda Prompt" hit Enter)
|
|
||||||
> cd src/jupyter-server (the notebooks will be saved in this directory)
|
|
||||||
> jupyter notebook
|
|
||||||
* It should open the browser at http://localhost:8888/ with the jupyter notebook.
|
|
||||||
* Edit the notebook and save it as "mynotebook" (This should create a file: mynotebook.ipynb).
|
|
||||||
We do this, because right now, the notebook filename is hardcoded as mynotebook.
|
|
||||||
|
|
||||||
### Modify jupyter server install
|
|
||||||
In order to serve the jupyter frontend from the emulator, we need to turn off a bunch of things.
|
|
||||||
* Stop the jupyter server (Ctrl-C twice from the Anaconda Prompt where you started jupyter notebook)
|
|
||||||
* From the Anaconda Prompt, type: juypter notebook --generate-config
|
|
||||||
* This should create the file: .jupyter/jupyter_notebook in your home directory.
|
|
||||||
* Edit this file:
|
|
||||||
|
|
||||||
Enable embedding the jupyter frontend inside an iFrame in DataExplorer:
|
|
||||||
c.NotebookApp.tornado_settings = { 'headers': { 'Content-Security-Policy': "frame-ancestors * localhost:1234 localhost:12900"} }
|
|
||||||
|
|
||||||
Enable a remotely-served jupyter frontend to still talk to the jupyter server:
|
|
||||||
c.NotebookApp.allow_origin = '*'
|
|
||||||
c.NotebookApp.allow_remote_access = True <--- not sure if this one matters
|
|
||||||
c.NotebookApp.token = ''
|
|
||||||
c.NotebookApp.disable_check_xsrf = True
|
|
||||||
|
|
||||||
## Install custom Data Explorer in Emulator
|
|
||||||
* Install git from https://git-scm.com/download/win (keep all default options)
|
|
||||||
* Install nodejs and npm from: https://nodejs.org/en/ (10.15.1 LTS)
|
|
||||||
|
|
||||||
### Download and build Data Explorer
|
|
||||||
* From the Git Bash terminal:
|
|
||||||
* cd ~/src
|
|
||||||
* git clone https://msdata.visualstudio.com/DefaultCollection/CosmosDB/_git/cosmosdb-dataexplorer
|
|
||||||
* cd cosmosdb-dataexplorer/Product/Portal
|
|
||||||
* git checkout users/languye/spark-in-dataexplorer
|
|
||||||
* cd JupyterLab
|
|
||||||
* npm i
|
|
||||||
* npm run build (this builds jupyterlab (the frontend of jupyter) and copies it into ../DataExplorer/notebookapp/)
|
|
||||||
* cd ../DataExplorer
|
|
||||||
* npm i
|
|
||||||
* npm run build (this builds and copies DataExplorer into the Emulator folder)
|
|
||||||
|
|
||||||
# How to run the setup
|
|
||||||
* Run the jupter-server by opening an Anaconda Prompt and typing: jupyter notebook
|
|
||||||
* Open the emulator at: http://localhost:8081/_explorer/index.html
|
|
||||||
* Click on any Collection
|
|
||||||
* Click on "New Notebook" button in the Command bar
|
|
||||||
* You should see the "mynotebook" jupyter notebook displayed in tab (inside an iframe).
|
|
||||||
* There is a "New Cell" button in the CommandBar outside the jupyter iframe which will add a cell inside the notebook.
|
|
||||||
|
|
||||||
# Notes
|
|
||||||
* The Emulator is located in: C:\Program Files\Azure Cosmos DB Emulator\Packages\DataExplorer
|
|
||||||
* Running "jupyter notebook" serves the jupyter traditional frontend. There is an alternate frontend also developed by jupyter which is modular and customizable called: JupyterLab. We use their "notebook" example in this project slightly modified to pass the server and notebook pathname via iframe url's parameters:
|
|
||||||
https://github.com/jupyterlab/jupyterlab/tree/master/examples/notebook
|
|
||||||
jupyterlab uses the same communication protocol as the traditional frontend, so it can connect to any jupyter-server,
|
|
||||||
so one can use multiple frontends (at the same time) to connect to a given jupyter-server.
|
|
||||||
* The jupyter frontend and the server use websockets to communicate.
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { AuthType } from "../AuthType";
|
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
|
||||||
import { ApiType } from "../UserContext";
|
|
||||||
|
|
||||||
export interface TerminalProps {
|
|
||||||
authToken: string;
|
|
||||||
notebookServerEndpoint: string;
|
|
||||||
terminalEndpoint: string;
|
|
||||||
databaseAccount: DataModels.DatabaseAccount;
|
|
||||||
authType: AuthType;
|
|
||||||
apiType: ApiType;
|
|
||||||
subscriptionId: string;
|
|
||||||
tabId: string;
|
|
||||||
username?: string;
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/*-----------------------------------------------------------------------------
|
|
||||||
| Copyright (c) Jupyter Development Team.
|
|
||||||
| Distributed under the terms of the Modified BSD License.
|
|
||||||
|----------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
body {
|
|
||||||
background: white;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#main {
|
|
||||||
position: absolute;
|
|
||||||
top: 0px;
|
|
||||||
left: 0px;
|
|
||||||
right: 0px;
|
|
||||||
bottom: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.jp-NotebookPanel {
|
|
||||||
border-bottom: 1px solid #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminalWidget {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>Notebook</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<script id="jupyter-config-data" type="application/json">
|
|
||||||
{
|
|
||||||
"terminalsAvailable": "true"
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
import { ServerConnection } from "@jupyterlab/services";
|
|
||||||
import { IMessage, ITerminalConnection } from "@jupyterlab/services/lib/terminal/terminal";
|
|
||||||
import "@jupyterlab/terminal/style/index.css";
|
|
||||||
import { MessageTypes } from "Contracts/ExplorerContracts";
|
|
||||||
import postRobot from "post-robot";
|
|
||||||
import { HttpHeaders } from "../Common/Constants";
|
|
||||||
import { Action } from "../Shared/Telemetry/TelemetryConstants";
|
|
||||||
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import { updateUserContext } from "../UserContext";
|
|
||||||
import { JupyterLabAppFactory } from "./JupyterLabAppFactory";
|
|
||||||
import { TerminalProps } from "./TerminalProps";
|
|
||||||
import "./index.css";
|
|
||||||
|
|
||||||
let session: ITerminalConnection | undefined;
|
|
||||||
|
|
||||||
const createServerSettings = (props: TerminalProps): ServerConnection.ISettings => {
|
|
||||||
let body: BodyInit | undefined;
|
|
||||||
let headers: HeadersInit | undefined;
|
|
||||||
if (props.terminalEndpoint) {
|
|
||||||
let bodyObj: { endpoint: string; username?: string } = {
|
|
||||||
endpoint: props.terminalEndpoint,
|
|
||||||
};
|
|
||||||
if (props.username) {
|
|
||||||
bodyObj = {
|
|
||||||
...bodyObj,
|
|
||||||
username: props.username,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
body = JSON.stringify(bodyObj);
|
|
||||||
headers = {
|
|
||||||
[HttpHeaders.contentType]: "application/json",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const server = props.notebookServerEndpoint;
|
|
||||||
let options: Partial<ServerConnection.ISettings> = {
|
|
||||||
baseUrl: server,
|
|
||||||
init: { body, headers },
|
|
||||||
fetch: window.parent.fetch,
|
|
||||||
};
|
|
||||||
if (props.authToken) {
|
|
||||||
options = {
|
|
||||||
baseUrl: server,
|
|
||||||
token: props.authToken,
|
|
||||||
appendToken: true,
|
|
||||||
init: { body, headers },
|
|
||||||
fetch: window.parent.fetch,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return ServerConnection.makeSettings(options);
|
|
||||||
};
|
|
||||||
|
|
||||||
const initTerminal = async (props: TerminalProps): Promise<void> => {
|
|
||||||
// Initialize userContext (only properties which are needed by TelemetryProcessor)
|
|
||||||
updateUserContext({
|
|
||||||
subscriptionId: props.subscriptionId,
|
|
||||||
apiType: props.apiType,
|
|
||||||
authType: props.authType,
|
|
||||||
databaseAccount: props.databaseAccount,
|
|
||||||
});
|
|
||||||
|
|
||||||
const serverSettings = createServerSettings(props);
|
|
||||||
|
|
||||||
createTerminalApp(props, serverSettings);
|
|
||||||
};
|
|
||||||
|
|
||||||
const createTerminalApp = async (props: TerminalProps, serverSettings: ServerConnection.ISettings) => {
|
|
||||||
const data = { baseUrl: serverSettings.baseUrl };
|
|
||||||
const startTime = TelemetryProcessor.traceStart(Action.OpenTerminal, data);
|
|
||||||
|
|
||||||
try {
|
|
||||||
session = await new JupyterLabAppFactory((restartShell: boolean) =>
|
|
||||||
closeTab(props, serverSettings, restartShell),
|
|
||||||
).createTerminalApp(serverSettings);
|
|
||||||
TelemetryProcessor.traceSuccess(Action.OpenTerminal, data, startTime);
|
|
||||||
} catch (error) {
|
|
||||||
TelemetryProcessor.traceFailure(Action.OpenTerminal, data, startTime);
|
|
||||||
session = undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeTab = (props: TerminalProps, serverSettings: ServerConnection.ISettings, restartShell: boolean): void => {
|
|
||||||
if (restartShell) {
|
|
||||||
createTerminalApp(props, serverSettings);
|
|
||||||
} else {
|
|
||||||
window.parent.postMessage(
|
|
||||||
{ type: MessageTypes.CloseTab, data: { tabId: props.tabId }, signature: "pcIframe" },
|
|
||||||
window.document.referrer,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const main = async (): Promise<void> => {
|
|
||||||
postRobot.on(
|
|
||||||
"props",
|
|
||||||
{
|
|
||||||
window: window.parent,
|
|
||||||
domain: window.location.origin,
|
|
||||||
},
|
|
||||||
async (event) => {
|
|
||||||
// Typescript definition for event is wrong. So read props by casting to <any>
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const props = (event as any).data as TerminalProps;
|
|
||||||
await initTerminal(props);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
postRobot.on(
|
|
||||||
"sendMessage",
|
|
||||||
{
|
|
||||||
window: window.parent,
|
|
||||||
domain: window.location.origin,
|
|
||||||
},
|
|
||||||
async (event) => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const message = (event as any).data as IMessage;
|
|
||||||
if (session) {
|
|
||||||
session.send(message);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("load", main);
|
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/DocumentDB/preview/2025-11-01-preview/cosmos-db.json
|
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/DocumentDB/preview/2025-11-01-preview/cosmos-db.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { stringifyError } from "Common/stringifyError";
|
|
||||||
import { configContext } from "../../../../ConfigContext";
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
@@ -19,14 +18,7 @@ export async function listSqlDatabases(
|
|||||||
accountName: string,
|
accountName: string,
|
||||||
): Promise<Types.SqlDatabaseListResult> {
|
): Promise<Types.SqlDatabaseListResult> {
|
||||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases`;
|
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases`;
|
||||||
console.log("{{cdbp}} in listSqlDatabases(): path: " + path);
|
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "GET", apiVersion });
|
||||||
try {
|
|
||||||
console.log("{{cdbp}} in listSqlDatabases(): calling armRequest");
|
|
||||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "GET", apiVersion });
|
|
||||||
} catch (error) {
|
|
||||||
console.log("{{cdbp}} in listSqlDatabases(): ERROR: " + stringifyError(error));
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Gets the SQL database under an existing Azure Cosmos DB database account with the provided name. */
|
/* Gets the SQL database under an existing Azure Cosmos DB database account with the provided name. */
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ Instead, generate ARM clients that consume this function with stricter typing.
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { stringifyError } from "Common/stringifyError";
|
|
||||||
import promiseRetry, { AbortError } from "p-retry";
|
import promiseRetry, { AbortError } from "p-retry";
|
||||||
import { HttpHeaders } from "../../Common/Constants";
|
import { HttpHeaders } from "../../Common/Constants";
|
||||||
import { configContext } from "../../ConfigContext";
|
import { configContext } from "../../ConfigContext";
|
||||||
@@ -78,9 +77,6 @@ export async function armRequestWithoutPolling<T>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!userContext?.authorizationToken && !customHeaders?.["Authorization"]) {
|
if (!userContext?.authorizationToken && !customHeaders?.["Authorization"]) {
|
||||||
console.log(
|
|
||||||
"{{cdbp}} in armRequestWithoutPolling(): condition '!userContext?.authorizationToken && !customHeaders?.['Authorization']' met, throwing 'No authority token provided' error",
|
|
||||||
);
|
|
||||||
throw new Error("No authority token provided");
|
throw new Error("No authority token provided");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,9 +94,6 @@ export async function armRequestWithoutPolling<T>({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const effectiveTimeoutMs = timeoutMs ?? DEFAULT_ARM_TIMEOUT_MS;
|
const effectiveTimeoutMs = timeoutMs ?? DEFAULT_ARM_TIMEOUT_MS;
|
||||||
console.log(
|
|
||||||
`{{cdbp}} in armRequestWithoutPolling(): calling fetchWithRetry (method=${method}, timeoutMs=${effectiveTimeoutMs}, hasSignal=${!!signal})`,
|
|
||||||
);
|
|
||||||
const response = await fetchWithRetry(url.href, fetchInit, method, effectiveTimeoutMs, signal);
|
const response = await fetchWithRetry(url.href, fetchInit, method, effectiveTimeoutMs, signal);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -115,11 +108,9 @@ export async function armRequestWithoutPolling<T>({
|
|||||||
error.code = errorResponse.code;
|
error.code = errorResponse.code;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("{{cdbp}} in armRequestWithoutPolling(): ERROR: " + stringifyError(error));
|
|
||||||
throw new Error(await response.text());
|
throw new Error(await response.text());
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("{{cdbp}} in armRequestWithoutPolling(): ERROR: " + stringifyError(error));
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +175,6 @@ async function fetchWithRetry(
|
|||||||
(attemptNumber: number) => {
|
(attemptNumber: number) => {
|
||||||
const attemptTimeoutMs =
|
const attemptTimeoutMs =
|
||||||
timeoutMs * RETRY_TIMEOUT_MULTIPLIERS[Math.min(attemptNumber - 1, RETRY_TIMEOUT_MULTIPLIERS.length - 1)];
|
timeoutMs * RETRY_TIMEOUT_MULTIPLIERS[Math.min(attemptNumber - 1, RETRY_TIMEOUT_MULTIPLIERS.length - 1)];
|
||||||
console.log(`{{cdbp}} in fetchWithRetry(): calling fetchWithTimeout: attempt=${attemptNumber} url=${url}`);
|
|
||||||
return fetchWithTimeout(url, fetchInit, attemptTimeoutMs);
|
return fetchWithTimeout(url, fetchInit, attemptTimeoutMs);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,26 +1,20 @@
|
|||||||
import postRobot from "post-robot";
|
|
||||||
import create, { UseStore } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
|
|
||||||
interface TerminalState {
|
interface TerminalState {
|
||||||
terminalWindow: Window;
|
socket: WebSocket | undefined;
|
||||||
setTerminal: (terminalWindow: Window) => void;
|
setSocket: (socket: WebSocket | undefined) => void;
|
||||||
sendMessage: (message: string) => void;
|
sendMessage: (message: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTerminal: UseStore<TerminalState> = create((set, get) => ({
|
export const useTerminal: UseStore<TerminalState> = create((set, get) => ({
|
||||||
terminalWindow: undefined,
|
socket: undefined,
|
||||||
setTerminal: (terminalWindow: Window) => {
|
setSocket: (socket: WebSocket | undefined) => {
|
||||||
set({ terminalWindow });
|
set({ socket });
|
||||||
},
|
},
|
||||||
sendMessage: (message: string) => {
|
sendMessage: (message: string) => {
|
||||||
const terminalWindow = get().terminalWindow;
|
const socket = get().socket;
|
||||||
postRobot.send(
|
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||||
terminalWindow,
|
socket.send(message + "\r");
|
||||||
"sendMessage",
|
}
|
||||||
{ type: "stdin", content: [message] },
|
|
||||||
{
|
|
||||||
domain: window.location.origin,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
+1
-3
@@ -122,9 +122,6 @@ export async function getTestExplorerUrl(accountType: TestAccount, options?: Tes
|
|||||||
params.set("feature.enableCopilot", "false");
|
params.set("feature.enableCopilot", "false");
|
||||||
|
|
||||||
const nosqlRbacToken = getNoSqlRbacToken();
|
const nosqlRbacToken = getNoSqlRbacToken();
|
||||||
if (!nosqlRbacToken) {
|
|
||||||
throw new Error("No NOSQL RBAC token found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const nosqlReadOnlyRbacToken = process.env.NOSQL_READONLY_TESTACCOUNT_TOKEN;
|
const nosqlReadOnlyRbacToken = process.env.NOSQL_READONLY_TESTACCOUNT_TOKEN;
|
||||||
const nosqlContainerCopyRbacToken = process.env.NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN;
|
const nosqlContainerCopyRbacToken = process.env.NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN;
|
||||||
@@ -411,6 +408,7 @@ export enum CommandBarButton {
|
|||||||
ExecuteQuery = "Execute Query",
|
ExecuteQuery = "Execute Query",
|
||||||
UploadItem = "Upload Item",
|
UploadItem = "Upload Item",
|
||||||
NewDocument = "New Document",
|
NewDocument = "New Document",
|
||||||
|
View = "View",
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Helper class that provides locator methods for DataExplorer components, on top of a Frame */
|
/** Helper class that provides locator methods for DataExplorer components, on top of a Frame */
|
||||||
|
|||||||
@@ -91,3 +91,51 @@ test("Query errors", async () => {
|
|||||||
await expect(queryTab.errorList.getByTestId("Row:1/Column:code")).toHaveText("SC2005");
|
await expect(queryTab.errorList.getByTestId("Row:1/Column:code")).toHaveText("SC2005");
|
||||||
await expect(queryTab.errorList.getByTestId("Row:1/Column:location")).toHaveText("Line 3");
|
await expect(queryTab.errorList.getByTestId("Row:1/Column:location")).toHaveText("Line 3");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("View button toggles between vertical and horizontal layout", async () => {
|
||||||
|
// The default layout is Horizontal (Allotment vertical={true} → split-view-vertical class)
|
||||||
|
const allotment = queryTab.locator.locator(".split-view-vertical, .split-view-horizontal");
|
||||||
|
await expect(allotment).toBeAttached();
|
||||||
|
await expect(allotment).toHaveClass(/split-view-vertical/);
|
||||||
|
|
||||||
|
// Click the main View button (not the chevron) to toggle to Vertical layout
|
||||||
|
const viewButton = explorer.commandBarButton(CommandBarButton.View);
|
||||||
|
await viewButton.click();
|
||||||
|
|
||||||
|
// After toggle, Allotment should have split-view-horizontal class (Vertical direction)
|
||||||
|
const allotmentAfterToggle = queryTab.locator.locator(".split-view-vertical, .split-view-horizontal");
|
||||||
|
await expect(allotmentAfterToggle).toHaveClass(/split-view-horizontal/);
|
||||||
|
|
||||||
|
// Click again to toggle back to Horizontal
|
||||||
|
await viewButton.click();
|
||||||
|
const allotmentAfterSecondToggle = queryTab.locator.locator(".split-view-vertical, .split-view-horizontal");
|
||||||
|
await expect(allotmentAfterSecondToggle).toHaveClass(/split-view-vertical/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("View dropdown allows selecting vertical or horizontal layout", async () => {
|
||||||
|
// The default layout is Horizontal (split-view-vertical)
|
||||||
|
const allotment = queryTab.locator.locator(".split-view-vertical, .split-view-horizontal");
|
||||||
|
await expect(allotment).toHaveClass(/split-view-vertical/);
|
||||||
|
|
||||||
|
// Find the View split button's menu trigger (chevron).
|
||||||
|
// The primary button has data-test, so navigate to the split button container and find the menu button.
|
||||||
|
const viewPrimaryButton = explorer.commandBarButton(CommandBarButton.View);
|
||||||
|
const splitContainer = viewPrimaryButton.locator("..");
|
||||||
|
const menuButton = splitContainer.locator('[aria-haspopup="true"]');
|
||||||
|
await menuButton.click();
|
||||||
|
|
||||||
|
// Select "Vertical" from the dropdown menu
|
||||||
|
await explorer.frame.getByRole("menuitem", { name: "Vertical" }).click();
|
||||||
|
|
||||||
|
// Verify the layout changed to Vertical (split-view-horizontal)
|
||||||
|
const allotmentAfterVertical = queryTab.locator.locator(".split-view-vertical, .split-view-horizontal");
|
||||||
|
await expect(allotmentAfterVertical).toHaveClass(/split-view-horizontal/);
|
||||||
|
|
||||||
|
// Open dropdown again and select "Horizontal"
|
||||||
|
await menuButton.click();
|
||||||
|
await explorer.frame.getByRole("menuitem", { name: "Horizontal" }).click();
|
||||||
|
|
||||||
|
// Verify it reverted to Horizontal (split-view-vertical)
|
||||||
|
const allotmentAfterHorizontal = queryTab.locator.locator(".split-view-vertical, .split-view-horizontal");
|
||||||
|
await expect(allotmentAfterHorizontal).toHaveClass(/split-view-vertical/);
|
||||||
|
});
|
||||||
|
|||||||
@@ -145,7 +145,6 @@
|
|||||||
"src/Platform/Emulator/**/*",
|
"src/Platform/Emulator/**/*",
|
||||||
"src/SelfServe/Documentation/**/*",
|
"src/SelfServe/Documentation/**/*",
|
||||||
"src/Shared/Telemetry/**/*",
|
"src/Shared/Telemetry/**/*",
|
||||||
"src/Terminal/**/*",
|
|
||||||
"src/Utils/arm/**/*"
|
"src/Utils/arm/**/*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -111,7 +111,6 @@ module.exports = function (_env = {}, argv = {}) {
|
|||||||
index: "./src/Index.tsx",
|
index: "./src/Index.tsx",
|
||||||
quickstart: "./src/quickstart.ts",
|
quickstart: "./src/quickstart.ts",
|
||||||
hostedExplorer: "./src/HostedExplorer.tsx",
|
hostedExplorer: "./src/HostedExplorer.tsx",
|
||||||
terminal: "./src/Terminal/index.ts",
|
|
||||||
cellOutputViewer: "./src/CellOutputViewer/CellOutputViewer.tsx",
|
cellOutputViewer: "./src/CellOutputViewer/CellOutputViewer.tsx",
|
||||||
selfServe: "./src/SelfServe/SelfServe.tsx",
|
selfServe: "./src/SelfServe/SelfServe.tsx",
|
||||||
connectToGitHub: "./src/GitHub/GitHubConnector.ts",
|
connectToGitHub: "./src/GitHub/GitHubConnector.ts",
|
||||||
@@ -128,11 +127,6 @@ module.exports = function (_env = {}, argv = {}) {
|
|||||||
template: "src/explorer.html",
|
template: "src/explorer.html",
|
||||||
chunks: ["main"],
|
chunks: ["main"],
|
||||||
}),
|
}),
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
filename: "terminal.html",
|
|
||||||
template: "src/Terminal/index.html",
|
|
||||||
chunks: ["terminal"],
|
|
||||||
}),
|
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
filename: "quickstart.html",
|
filename: "quickstart.html",
|
||||||
template: "src/quickstart.html",
|
template: "src/quickstart.html",
|
||||||
|
|||||||
Reference in New Issue
Block a user